/*
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifdef _WIN32
// For Sleep()
#include <windows.h>
#else
// For nanosleep()
#include <time.h>
#endif
#include "system_wrappers/interface/critical_section_wrapper.h"
#include "gtest/gtest.h"
#include "system_wrappers/interface/sleep.h"
#include "system_wrappers/interface/thread_wrapper.h"
#include "system_wrappers/interface/trace.h"
#include "system_wrappers/source/unittest_utilities.h"
namespace webrtc {
namespace {
const bool kLogTrace = false; // Set to true to enable debug logging to stdout.
#define LOG(...) WEBRTC_TRACE(kTraceStateInfo, kTraceUtility, -1, __VA_ARGS__);
// Cause a process switch. Needed to avoid depending on
// busy-wait in tests.
static void SwitchProcess() {
// Note - sched_yield has been tried as process switch. This does
// not cause a process switch enough of the time for reliability.
SleepMs(1);
}
class ProtectedCount {
public:
explicit ProtectedCount(CriticalSectionWrapper* crit_sect)
: crit_sect_(crit_sect),
count_(0) {
}
void Increment() {
CriticalSectionScoped cs(crit_sect_);
++count_;
LOG("Inc to %d", count_);
}
int Count() const {
CriticalSectionScoped cs(crit_sect_);
return count_;
}
private:
CriticalSectionWrapper* crit_sect_;
int count_;
};
class CritSectTest : public ::testing::Test {
public:
CritSectTest() : trace_(kLogTrace) {
}
// Waits a number of cycles for the count to reach a given value.
// Returns true if the target is reached or passed.
bool WaitForCount(int target, ProtectedCount* count) {
int loop_counter = 0;
// On Posix, this SwitchProcess() needs to be in a loop to make the
// test both fast and non-flaky.
// With 1 us wait as the switch, up to 7 rounds have been observed.
while (count->Count() < target && loop_counter < 100*target) {
++loop_counter;
SwitchProcess();
}
LOG("Test looped %d times\n", loop_counter);
return (count->Count() >= target);
}
private:
ScopedTracing trace_;
};
bool LockUnlockThenStopRunFunction(void* obj) {
LOG("Wait starting");
ProtectedCount* the_count = static_cast<ProtectedCount*> (obj);
LOG("Wait incrementing");
the_count->Increment();
LOG("Wait returning");
return false;
}
TEST_F(CritSectTest, ThreadWakesOnce) {
CriticalSectionWrapper* crit_sect
= CriticalSectionWrapper::CreateCriticalSection();
ProtectedCount count(crit_sect);
ThreadWrapper* thread = ThreadWrapper::CreateThread(
&LockUnlockThenStopRunFunction, &count);
unsigned int id = 42;
crit_sect->Enter();
ASSERT_TRUE(thread->Start(id));
SwitchProcess();
// The critical section is of reentrant mode, so this should not release
// the lock, even though count.Count() locks and unlocks the critical section
// again.
// Thus, the thread should not be able to increment the count
ASSERT_EQ(0, count.Count());
crit_sect->Leave(); // This frees the thread to act.
EXPECT_TRUE(WaitForCount(1, &count));
EXPECT_TRUE(thread->Stop());
delete thread;
delete crit_sect;
}
bool LockUnlockRunFunction(void* obj) {
LOG("Wait starting");
ProtectedCount* the_count = static_cast<ProtectedCount*> (obj);
LOG("Wait incrementing");
the_count->Increment();
SwitchProcess();
LOG("Wait returning");
return true;
}
TEST_F(CritSectTest, ThreadWakesTwice) {
CriticalSectionWrapper* crit_sect
= CriticalSectionWrapper::CreateCriticalSection();
ProtectedCount count(crit_sect);
ThreadWrapper* thread = ThreadWrapper::CreateThread(&LockUnlockRunFunction,
&count);
unsigned int id = 42;
crit_sect->Enter(); // Make sure counter stays 0 until we wait for it.
ASSERT_TRUE(thread->Start(id));
crit_sect->Leave();
// The thread is capable of grabbing the lock multiple times,
// incrementing counter once each time.
// It's possible for the count to be incremented by more than 2.
EXPECT_TRUE(WaitForCount(2, &count));
EXPECT_LE(2, count.Count());
// The thread does not increment while lock is held.
crit_sect->Enter();
int count_before = count.Count();
for (int i = 0; i < 10; i++) {
SwitchProcess();
}
EXPECT_EQ(count_before, count.Count());
crit_sect->Leave();
thread->SetNotAlive(); // Tell thread to exit once run function finishes.
SwitchProcess();
EXPECT_LT(count_before, count.Count());
EXPECT_TRUE(thread->Stop());
delete thread;
delete crit_sect;
}
} // anonymous namespace
} // namespace webrtc