| /* |
| * Copyright 2012 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // Author: jmarantz@google.com (Joshua Marantz) |
| |
| // Unit-test for ThreadSynchronizer |
| |
| #include "pagespeed/kernel/thread/thread_synchronizer.h" |
| |
| #include "pagespeed/kernel/base/function.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/util/platform.h" |
| #include "pagespeed/kernel/thread/queued_worker_pool.h" |
| #include "pagespeed/kernel/base/scoped_ptr.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/base/thread_system.h" |
| #include "pagespeed/kernel/thread/worker_test_base.h" |
| |
| namespace net_instaweb { |
| |
| namespace { |
| |
| class ThreadSynchronizerTest : public testing::Test { |
| public: |
| ThreadSynchronizerTest() |
| : thread_system_(Platform::CreateThreadSystem()), |
| synchronizer_(thread_system_.get()), |
| pool_(1, "thread_synchronizer_test", thread_system_.get()), |
| sequence_(pool_.NewSequence()), |
| sync_point_(new WorkerTestBase::SyncPoint(thread_system_.get())) { |
| } |
| |
| ~ThreadSynchronizerTest() { |
| sync_point_.reset(NULL); // make sure this is destructed first. |
| } |
| |
| protected: |
| void AppendChar(char c) { |
| buffer_ += c; |
| synchronizer_.Signal("Thread:started"); |
| synchronizer_.Wait("Thread:unblock"); |
| } |
| |
| void AppendStringOneCharAtATime(const StringPiece& str) { |
| for (int i = 0, n = str.size(); i < n; ++i) { |
| sequence_->Add(MakeFunction( |
| this, &ThreadSynchronizerTest::AppendChar, str[i])); |
| } |
| } |
| |
| void TestSyncDisabled() { |
| // Queue up a bunch of functions. By default, synchronizer_ |
| // is disabled so they will just execute without delay. That is, |
| // the calls to Wait and Signal in AppendChar will be no-ops. |
| AppendStringOneCharAtATime("135"); |
| sequence_->Add(new WorkerTestBase::NotifyRunFunction(sync_point_.get())); |
| sync_point_->Wait(); |
| EXPECT_EQ("135", buffer_); |
| } |
| |
| scoped_ptr<ThreadSystem> thread_system_; |
| ThreadSynchronizer synchronizer_; |
| QueuedWorkerPool pool_; |
| QueuedWorkerPool::Sequence* sequence_; |
| scoped_ptr<WorkerTestBase::SyncPoint> sync_point_; |
| GoogleString buffer_; |
| }; |
| |
| TEST_F(ThreadSynchronizerTest, SyncDisabled) { |
| TestSyncDisabled(); |
| } |
| |
| TEST_F(ThreadSynchronizerTest, SyncWrongPrefix) { |
| synchronizer_.EnableForPrefix("WrongPrefix_"); |
| |
| // Despite having enabled the synchronizer, the prefix supplied does |
| // not match the prefix we use in AppendChar above. Thus the |
| // testcase will behave exactly as if there were no sync-points, as |
| // in SyncDisabled. The sync-points will be no-ops. |
| TestSyncDisabled(); |
| } |
| |
| TEST_F(ThreadSynchronizerTest, SyncEnabled) { |
| synchronizer_.EnableForPrefix("Thread:"); |
| AppendStringOneCharAtATime("135"); |
| sequence_->Add(new WorkerTestBase::NotifyRunFunction(sync_point_.get())); |
| |
| // Wait for the thread to initiate, then signal it so it can complete the |
| // first character. |
| synchronizer_.Wait("Thread:started"); |
| EXPECT_EQ("1", buffer_); |
| buffer_ += "2"; |
| synchronizer_.Signal("Thread:unblock"); |
| synchronizer_.Wait("Thread:started"); |
| EXPECT_EQ("123", buffer_); |
| buffer_ += "4"; |
| synchronizer_.Signal("Thread:unblock"); |
| synchronizer_.Wait("Thread:started"); |
| EXPECT_EQ("12345", buffer_); |
| synchronizer_.Signal("Thread:unblock"); |
| sync_point_->Wait(); |
| EXPECT_EQ("12345", buffer_); |
| } |
| |
| TEST_F(ThreadSynchronizerTest, SignalInAdvance) { |
| synchronizer_.EnableForPrefix("Thread:"); |
| synchronizer_.Signal("Thread:unblock"); |
| synchronizer_.Signal("Thread:unblock"); |
| synchronizer_.Signal("Thread:unblock"); |
| AppendStringOneCharAtATime("135"); |
| sequence_->Add(new WorkerTestBase::NotifyRunFunction(sync_point_.get())); |
| sync_point_->Wait(); |
| |
| // It's an error to let the 3 pending "Thread:started" signals go unwaited |
| // on exit, so "wait" for them now -- it won't actually even block. |
| synchronizer_.Wait("Thread:started"); |
| synchronizer_.Wait("Thread:started"); |
| synchronizer_.Wait("Thread:started"); |
| |
| EXPECT_EQ("135", buffer_); |
| } |
| |
| TEST_F(ThreadSynchronizerTest, TimedWait) { |
| synchronizer_.EnableForPrefix("Thread:"); |
| synchronizer_.TimedWait("Thread:NeverComing", 10 /* ms */); |
| synchronizer_.Signal("Thread:NeverComing"); |
| } |
| |
| TEST_F(ThreadSynchronizerTest, AllowSloppyNeverSignaled) { |
| synchronizer_.EnableForPrefix("Thread:"); |
| synchronizer_.TimedWait("Thread:NeverComing", 10 /* ms */); |
| synchronizer_.AllowSloppyTermination("Thread:NeverComing"); |
| } |
| |
| TEST_F(ThreadSynchronizerTest, AllowSloppyNeverWaited) { |
| synchronizer_.EnableForPrefix("Thread:"); |
| synchronizer_.Signal("Thread:NeverWaited"); |
| synchronizer_.AllowSloppyTermination("Thread:NeverWaited"); |
| } |
| |
| } // namespace |
| |
| } // namespace net_instaweb |