| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. All rights reserved. |
| // https://developers.google.com/protocol-buffers/ |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| // Author: kenton@google.com (Kenton Varda) |
| |
| #ifdef _WIN32 |
| #include <windows.h> |
| #else |
| #include <unistd.h> |
| #include <pthread.h> |
| #endif |
| |
| #include <google/protobuf/stubs/once.h> |
| #include <google/protobuf/testing/googletest.h> |
| #include <gtest/gtest.h> |
| |
| namespace google { |
| namespace protobuf { |
| using internal::NewCallback; |
| namespace { |
| |
| class OnceInitTest : public testing::Test { |
| protected: |
| void SetUp() { |
| state_ = INIT_NOT_STARTED; |
| current_test_ = this; |
| } |
| |
| // Since ProtobufOnceType is only allowed to be allocated in static storage, |
| // each test must use a different pair of ProtobufOnceType objects which it |
| // must declare itself. |
| void SetOnces(ProtobufOnceType* once, ProtobufOnceType* recursive_once) { |
| once_ = once; |
| recursive_once_ = recursive_once; |
| } |
| |
| void InitOnce() { |
| GoogleOnceInit(once_, &InitStatic); |
| } |
| void InitRecursiveOnce() { |
| GoogleOnceInit(recursive_once_, &InitRecursiveStatic); |
| } |
| |
| void BlockInit() { init_blocker_.Lock(); } |
| void UnblockInit() { init_blocker_.Unlock(); } |
| |
| class TestThread { |
| public: |
| TestThread(Closure* callback) |
| : done_(false), joined_(false), callback_(callback) { |
| #ifdef _WIN32 |
| thread_ = CreateThread(NULL, 0, &Start, this, 0, NULL); |
| #else |
| pthread_create(&thread_, NULL, &Start, this); |
| #endif |
| } |
| ~TestThread() { |
| if (!joined_) Join(); |
| } |
| |
| bool IsDone() { |
| MutexLock lock(&done_mutex_); |
| return done_; |
| } |
| void Join() { |
| joined_ = true; |
| #ifdef _WIN32 |
| WaitForSingleObject(thread_, INFINITE); |
| CloseHandle(thread_); |
| #else |
| pthread_join(thread_, NULL); |
| #endif |
| } |
| |
| private: |
| #ifdef _WIN32 |
| HANDLE thread_; |
| #else |
| pthread_t thread_; |
| #endif |
| |
| Mutex done_mutex_; |
| bool done_; |
| bool joined_; |
| Closure* callback_; |
| |
| #ifdef _WIN32 |
| static DWORD WINAPI Start(LPVOID arg) { |
| #else |
| static void* Start(void* arg) { |
| #endif |
| reinterpret_cast<TestThread*>(arg)->Run(); |
| return 0; |
| } |
| |
| void Run() { |
| callback_->Run(); |
| MutexLock lock(&done_mutex_); |
| done_ = true; |
| } |
| }; |
| |
| TestThread* RunInitOnceInNewThread() { |
| return new TestThread(internal::NewCallback(this, &OnceInitTest::InitOnce)); |
| } |
| TestThread* RunInitRecursiveOnceInNewThread() { |
| return new TestThread( |
| internal::NewCallback(this, &OnceInitTest::InitRecursiveOnce)); |
| } |
| |
| enum State { |
| INIT_NOT_STARTED, |
| INIT_STARTED, |
| INIT_DONE |
| }; |
| State CurrentState() { |
| MutexLock lock(&mutex_); |
| return state_; |
| } |
| |
| void WaitABit() { |
| #ifdef _WIN32 |
| Sleep(1000); |
| #else |
| sleep(1); |
| #endif |
| } |
| |
| private: |
| Mutex mutex_; |
| Mutex init_blocker_; |
| State state_; |
| ProtobufOnceType* once_; |
| ProtobufOnceType* recursive_once_; |
| |
| void Init() { |
| MutexLock lock(&mutex_); |
| EXPECT_EQ(INIT_NOT_STARTED, state_); |
| state_ = INIT_STARTED; |
| mutex_.Unlock(); |
| init_blocker_.Lock(); |
| init_blocker_.Unlock(); |
| mutex_.Lock(); |
| state_ = INIT_DONE; |
| } |
| |
| static OnceInitTest* current_test_; |
| static void InitStatic() { current_test_->Init(); } |
| static void InitRecursiveStatic() { current_test_->InitOnce(); } |
| }; |
| |
| OnceInitTest* OnceInitTest::current_test_ = NULL; |
| |
| GOOGLE_PROTOBUF_DECLARE_ONCE(simple_once); |
| |
| TEST_F(OnceInitTest, Simple) { |
| SetOnces(&simple_once, NULL); |
| |
| EXPECT_EQ(INIT_NOT_STARTED, CurrentState()); |
| InitOnce(); |
| EXPECT_EQ(INIT_DONE, CurrentState()); |
| |
| // Calling again has no effect. |
| InitOnce(); |
| EXPECT_EQ(INIT_DONE, CurrentState()); |
| } |
| |
| GOOGLE_PROTOBUF_DECLARE_ONCE(recursive_once1); |
| GOOGLE_PROTOBUF_DECLARE_ONCE(recursive_once2); |
| |
| TEST_F(OnceInitTest, Recursive) { |
| SetOnces(&recursive_once1, &recursive_once2); |
| |
| EXPECT_EQ(INIT_NOT_STARTED, CurrentState()); |
| InitRecursiveOnce(); |
| EXPECT_EQ(INIT_DONE, CurrentState()); |
| } |
| |
| GOOGLE_PROTOBUF_DECLARE_ONCE(multiple_threads_once); |
| |
| TEST_F(OnceInitTest, MultipleThreads) { |
| SetOnces(&multiple_threads_once, NULL); |
| |
| scoped_ptr<TestThread> threads[4]; |
| EXPECT_EQ(INIT_NOT_STARTED, CurrentState()); |
| for (int i = 0; i < 4; i++) { |
| threads[i].reset(RunInitOnceInNewThread()); |
| } |
| for (int i = 0; i < 4; i++) { |
| threads[i]->Join(); |
| } |
| EXPECT_EQ(INIT_DONE, CurrentState()); |
| } |
| |
| GOOGLE_PROTOBUF_DECLARE_ONCE(multiple_threads_blocked_once1); |
| GOOGLE_PROTOBUF_DECLARE_ONCE(multiple_threads_blocked_once2); |
| |
| TEST_F(OnceInitTest, MultipleThreadsBlocked) { |
| SetOnces(&multiple_threads_blocked_once1, &multiple_threads_blocked_once2); |
| |
| scoped_ptr<TestThread> threads[8]; |
| EXPECT_EQ(INIT_NOT_STARTED, CurrentState()); |
| |
| BlockInit(); |
| for (int i = 0; i < 4; i++) { |
| threads[i].reset(RunInitOnceInNewThread()); |
| } |
| for (int i = 4; i < 8; i++) { |
| threads[i].reset(RunInitRecursiveOnceInNewThread()); |
| } |
| |
| WaitABit(); |
| |
| // We should now have one thread blocked inside Init(), four blocked waiting |
| // for Init() to complete, and three blocked waiting for InitRecursive() to |
| // complete. |
| EXPECT_EQ(INIT_STARTED, CurrentState()); |
| UnblockInit(); |
| |
| for (int i = 0; i < 8; i++) { |
| threads[i]->Join(); |
| } |
| EXPECT_EQ(INIT_DONE, CurrentState()); |
| } |
| |
| } // anonymous namespace |
| } // namespace protobuf |
| } // namespace google |