aboutsummaryrefslogtreecommitdiff
path: root/src/system_wrappers/source/condition_variable_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/system_wrappers/source/condition_variable_unittest.cc')
-rw-r--r--src/system_wrappers/source/condition_variable_unittest.cc208
1 files changed, 208 insertions, 0 deletions
diff --git a/src/system_wrappers/source/condition_variable_unittest.cc b/src/system_wrappers/source/condition_variable_unittest.cc
new file mode 100644
index 0000000000..a9fdd0d24e
--- /dev/null
+++ b/src/system_wrappers/source/condition_variable_unittest.cc
@@ -0,0 +1,208 @@
+/*
+ * 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.
+ */
+
+#include "system_wrappers/interface/condition_variable_wrapper.h"
+
+#include "gtest/gtest.h"
+#include "system_wrappers/interface/critical_section_wrapper.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 int kLogTrace = false; // Set to true to enable debug logging to stdout.
+const int kLongWaitMs = 100*1000; // A long time in testing terms
+const int kShortWaitMs = 2*1000; // Long enough for process switches to happen
+
+#define LOG(...) WEBRTC_TRACE(kTraceStateInfo, kTraceUtility, -1, __VA_ARGS__);
+
+// A Baton is one possible control structure one can build using
+// conditional variables.
+// A Baton is always held by one and only one active thread - unlike
+// a lock, it can never be free.
+// One can pass it or grab it - both calls have timeouts.
+// Note - a production tool would guard against passing it without
+// grabbing it first. This one is for testing, so it doesn't.
+class Baton {
+ public:
+ Baton()
+ : giver_sect_(CriticalSectionWrapper::CreateCriticalSection()),
+ crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
+ cond_var_(ConditionVariableWrapper::CreateConditionVariable()),
+ being_passed_(false),
+ pass_count_(0) {
+ }
+
+ ~Baton() {
+ delete giver_sect_;
+ delete crit_sect_;
+ delete cond_var_;
+ }
+
+ // Pass the baton. Returns false if baton is not picked up in |max_msecs|.
+ // Only one process can pass at the same time; this property is
+ // ensured by the |giver_sect_| lock.
+ bool Pass(WebRtc_UWord32 max_msecs) {
+ LOG("Locking giver_sect");
+ CriticalSectionScoped cs_giver(giver_sect_);
+ LOG("Locked giver_sect, locking crit_sect");
+ CriticalSectionScoped cs(crit_sect_);
+ SignalBatonAvailable();
+ const bool result = TakeBatonIfStillFree(max_msecs);
+ if (result) {
+ ++pass_count_;
+ LOG("Pass count is %d", pass_count_);
+ }
+ return result;
+ }
+
+ // Grab the baton. Returns false if baton is not passed.
+ bool Grab(WebRtc_UWord32 max_msecs) {
+ CriticalSectionScoped cs(crit_sect_);
+ return WaitUntilBatonOffered(max_msecs);
+ }
+
+ int PassCount() {
+ // We don't allow polling PassCount() during a Pass()-call since there is
+ // no guarantee that |pass_count_| is incremented until the Pass()-call
+ // finishes. I.e. the Grab()-call may finish before |pass_count_| has been
+ // incremented.
+ // Thus, this function waits on giver_sect_.
+ CriticalSectionScoped cs(giver_sect_);
+ return pass_count_;
+ }
+
+ private:
+ // Wait/Signal forms a classical semaphore on |being_passed_|.
+ // These functions must be called with crit_sect_ held.
+ bool WaitUntilBatonOffered(int timeout_ms) {
+ while (!being_passed_) {
+ LOG("Wait waiting");
+ if (!cond_var_->SleepCS(*crit_sect_, timeout_ms)) {
+ LOG("Wait timeout");
+ return false;
+ }
+ }
+ being_passed_ = false;
+ cond_var_->Wake();
+ return true;
+ }
+
+ void SignalBatonAvailable() {
+ assert(!being_passed_);
+ being_passed_ = true;
+ LOG("Signal waking");
+ cond_var_->Wake();
+ }
+
+ // Timeout extension: Wait for a limited time for someone else to
+ // take it, and take it if it's not taken.
+ // Returns true if resource is taken by someone else, false
+ // if it is taken back by the caller.
+ // This function must be called with both |giver_sect_| and
+ // |crit_sect_| held.
+ bool TakeBatonIfStillFree(int timeout_ms) {
+ bool not_timeout = true;
+ while (being_passed_ && not_timeout) {
+ LOG("Takeback waiting");
+ not_timeout = cond_var_->SleepCS(*crit_sect_, timeout_ms);
+ // If we're woken up while variable is still held, we may have
+ // gotten a wakeup destined for a grabber thread.
+ // This situation is not treated specially here.
+ }
+ if (!being_passed_) {
+ return true;
+ } else {
+ LOG("Takeback grab");
+ assert(!not_timeout);
+ being_passed_ = false;
+ return false;
+ }
+ }
+
+ // Lock that ensures that there is only one thread in the active
+ // part of Pass() at a time.
+ // |giver_sect_| must always be acquired before |cond_var_|.
+ CriticalSectionWrapper* giver_sect_;
+ // Lock that protects |being_passed_|.
+ CriticalSectionWrapper* crit_sect_;
+ ConditionVariableWrapper* cond_var_;
+ bool being_passed_;
+ // Statistics information: Number of successfull passes.
+ int pass_count_;
+};
+
+// Function that waits on a Baton, and passes it right back.
+// We expect these calls never to time out.
+bool WaitingRunFunction(void* obj) {
+ Baton* the_baton = static_cast<Baton*> (obj);
+ LOG("Thread waiting");
+ EXPECT_TRUE(the_baton->Grab(kLongWaitMs));
+ LOG("Thread waking parent");
+ EXPECT_TRUE(the_baton->Pass(kLongWaitMs));
+ return true;
+}
+
+class CondVarTest : public ::testing::Test {
+ public:
+ CondVarTest()
+ : trace_(kLogTrace) {
+ }
+
+ virtual void SetUp() {
+ thread_ = ThreadWrapper::CreateThread(&WaitingRunFunction,
+ &baton_);
+ unsigned int id = 42;
+ ASSERT_TRUE(thread_->Start(id));
+ }
+
+ virtual void TearDown() {
+ // We have to wake the thread in order to make it obey the stop order.
+ // But we don't know if the thread has completed the run function, so
+ // we don't know if it will exit before or after the Pass.
+ // Thus, we need to pin it down inside its Run function (between Grab
+ // and Pass).
+ ASSERT_TRUE(baton_.Pass(kShortWaitMs));
+ thread_->SetNotAlive();
+ ASSERT_TRUE(baton_.Grab(kShortWaitMs));
+ ASSERT_TRUE(thread_->Stop());
+ delete thread_;
+ }
+
+ protected:
+ Baton baton_;
+
+ private:
+ ScopedTracing trace_;
+ ThreadWrapper* thread_;
+};
+
+// The SetUp and TearDown functions use condition variables.
+// This test verifies those pieces in isolation.
+TEST_F(CondVarTest, InitFunctionsWork) {
+ // All relevant asserts are in the SetUp and TearDown functions.
+}
+
+// This test verifies that one can use the baton multiple times.
+TEST_F(CondVarTest, PassBatonMultipleTimes) {
+ const int kNumberOfRounds = 2;
+ for (int i = 0; i < kNumberOfRounds; ++i) {
+ ASSERT_TRUE(baton_.Pass(kShortWaitMs));
+ ASSERT_TRUE(baton_.Grab(kShortWaitMs));
+ }
+ EXPECT_EQ(2*kNumberOfRounds, baton_.PassCount());
+}
+
+} // anonymous namespace
+
+} // namespace webrtc