diff options
Diffstat (limited to 'test/gtest-port_test.cc')
-rw-r--r-- | test/gtest-port_test.cc | 142 |
1 files changed, 106 insertions, 36 deletions
diff --git a/test/gtest-port_test.cc b/test/gtest-port_test.cc index 43f1f201..370c952b 100644 --- a/test/gtest-port_test.cc +++ b/test/gtest-port_test.cc @@ -1062,11 +1062,13 @@ class AtomicCounterWithMutex { MutexLock lock(mutex_); int temp = value_; { - // Locking a mutex puts up a memory barrier, preventing reads and - // writes to value_ rearranged when observed from other threads. - // - // We cannot use Mutex and MutexLock here or rely on their memory - // barrier functionality as we are testing them here. + // We need to put up a memory barrier to prevent reads and writes to + // value_ rearranged with the call to SleepMilliseconds when observed + // from other threads. +#if GTEST_HAS_PTHREAD + // On POSIX, locking a mutex puts up a memory barrier. We cannot use + // Mutex and MutexLock here or rely on their memory barrier + // functionality as we are testing them here. pthread_mutex_t memory_barrier_mutex; GTEST_CHECK_POSIX_SUCCESS_( pthread_mutex_init(&memory_barrier_mutex, NULL)); @@ -1076,6 +1078,15 @@ class AtomicCounterWithMutex { GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_unlock(&memory_barrier_mutex)); GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_destroy(&memory_barrier_mutex)); +#elif GTEST_OS_WINDOWS + // On Windows, performing an interlocked access puts up a memory barrier. + volatile LONG dummy = 0; + ::InterlockedIncrement(&dummy); + SleepMilliseconds(random_.Generate(30)); + ::InterlockedIncrement(&dummy); +#else +# error "Memory barrier not implemented on this platform." +#endif // GTEST_HAS_PTHREAD } value_ = temp + 1; } @@ -1145,27 +1156,76 @@ TEST(ThreadLocalTest, ParameterizedConstructorSetsDefault) { EXPECT_STREQ("foo", result.c_str()); } +// Keeps track of whether of destructors being called on instances of +// DestructorTracker. On Windows, waits for the destructor call reports. +class DestructorCall { + public: + DestructorCall() { + invoked_ = false; +#if GTEST_OS_WINDOWS + wait_event_.Reset(::CreateEvent(NULL, TRUE, FALSE, NULL)); + GTEST_CHECK_(wait_event_.Get() != NULL); +#endif + } + + bool CheckDestroyed() const { +#if GTEST_OS_WINDOWS + if (::WaitForSingleObject(wait_event_.Get(), 1000) != WAIT_OBJECT_0) + return false; +#endif + return invoked_; + } + + void ReportDestroyed() { + invoked_ = true; +#if GTEST_OS_WINDOWS + ::SetEvent(wait_event_.Get()); +#endif + } + + static std::vector<DestructorCall*>& List() { return *list_; } + + static void ResetList() { + for (size_t i = 0; i < list_->size(); ++i) { + delete list_->at(i); + } + list_->clear(); + } + + private: + bool invoked_; +#if GTEST_OS_WINDOWS + AutoHandle wait_event_; +#endif + static std::vector<DestructorCall*>* const list_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DestructorCall); +}; + +std::vector<DestructorCall*>* const DestructorCall::list_ = + new std::vector<DestructorCall*>; + // DestructorTracker keeps track of whether its instances have been // destroyed. -static std::vector<bool> g_destroyed; - class DestructorTracker { public: DestructorTracker() : index_(GetNewIndex()) {} DestructorTracker(const DestructorTracker& /* rhs */) : index_(GetNewIndex()) {} ~DestructorTracker() { - // We never access g_destroyed concurrently, so we don't need to - // protect the write operation under a mutex. - g_destroyed[index_] = true; + // We never access DestructorCall::List() concurrently, so we don't need + // to protect this acccess with a mutex. + DestructorCall::List()[index_]->ReportDestroyed(); } private: static int GetNewIndex() { - g_destroyed.push_back(false); - return g_destroyed.size() - 1; + DestructorCall::List().push_back(new DestructorCall); + return DestructorCall::List().size() - 1; } const int index_; + + GTEST_DISALLOW_ASSIGN_(DestructorTracker); }; typedef ThreadLocal<DestructorTracker>* ThreadParam; @@ -1177,63 +1237,63 @@ void CallThreadLocalGet(ThreadParam thread_local_param) { // Tests that when a ThreadLocal object dies in a thread, it destroys // the managed object for that thread. TEST(ThreadLocalTest, DestroysManagedObjectForOwnThreadWhenDying) { - g_destroyed.clear(); + DestructorCall::ResetList(); { // The next line default constructs a DestructorTracker object as // the default value of objects managed by thread_local_tracker. ThreadLocal<DestructorTracker> thread_local_tracker; - ASSERT_EQ(1U, g_destroyed.size()); - ASSERT_FALSE(g_destroyed[0]); + ASSERT_EQ(1U, DestructorCall::List().size()); + ASSERT_FALSE(DestructorCall::List()[0]->CheckDestroyed()); // This creates another DestructorTracker object for the main thread. thread_local_tracker.get(); - ASSERT_EQ(2U, g_destroyed.size()); - ASSERT_FALSE(g_destroyed[0]); - ASSERT_FALSE(g_destroyed[1]); + ASSERT_EQ(2U, DestructorCall::List().size()); + ASSERT_FALSE(DestructorCall::List()[0]->CheckDestroyed()); + ASSERT_FALSE(DestructorCall::List()[1]->CheckDestroyed()); } // Now thread_local_tracker has died. It should have destroyed both the // default value shared by all threads and the value for the main // thread. - ASSERT_EQ(2U, g_destroyed.size()); - EXPECT_TRUE(g_destroyed[0]); - EXPECT_TRUE(g_destroyed[1]); + ASSERT_EQ(2U, DestructorCall::List().size()); + EXPECT_TRUE(DestructorCall::List()[0]->CheckDestroyed()); + EXPECT_TRUE(DestructorCall::List()[1]->CheckDestroyed()); - g_destroyed.clear(); + DestructorCall::ResetList(); } // Tests that when a thread exits, the thread-local object for that // thread is destroyed. TEST(ThreadLocalTest, DestroysManagedObjectAtThreadExit) { - g_destroyed.clear(); + DestructorCall::ResetList(); { // The next line default constructs a DestructorTracker object as // the default value of objects managed by thread_local_tracker. ThreadLocal<DestructorTracker> thread_local_tracker; - ASSERT_EQ(1U, g_destroyed.size()); - ASSERT_FALSE(g_destroyed[0]); + ASSERT_EQ(1U, DestructorCall::List().size()); + ASSERT_FALSE(DestructorCall::List()[0]->CheckDestroyed()); // This creates another DestructorTracker object in the new thread. ThreadWithParam<ThreadParam> thread( &CallThreadLocalGet, &thread_local_tracker, NULL); thread.Join(); - // Now the new thread has exited. The per-thread object for it - // should have been destroyed. - ASSERT_EQ(2U, g_destroyed.size()); - ASSERT_FALSE(g_destroyed[0]); - ASSERT_TRUE(g_destroyed[1]); + // The thread has exited, and we should have another DestroyedTracker + // instance created for it. But it may not have been destroyed yet. + // The instance for the main thread should still persist. + ASSERT_EQ(2U, DestructorCall::List().size()); + ASSERT_FALSE(DestructorCall::List()[0]->CheckDestroyed()); } - // Now thread_local_tracker has died. The default value should have been - // destroyed too. - ASSERT_EQ(2U, g_destroyed.size()); - EXPECT_TRUE(g_destroyed[0]); - EXPECT_TRUE(g_destroyed[1]); + // The thread has exited and thread_local_tracker has died. The default + // value should have been destroyed too. + ASSERT_EQ(2U, DestructorCall::List().size()); + EXPECT_TRUE(DestructorCall::List()[0]->CheckDestroyed()); + EXPECT_TRUE(DestructorCall::List()[1]->CheckDestroyed()); - g_destroyed.clear(); + DestructorCall::ResetList(); } TEST(ThreadLocalTest, ThreadLocalMutationsAffectOnlyCurrentThread) { @@ -1249,5 +1309,15 @@ TEST(ThreadLocalTest, ThreadLocalMutationsAffectOnlyCurrentThread) { #endif // GTEST_IS_THREADSAFE +#if GTEST_OS_WINDOWS +TEST(WindowsTypesTest, HANDLEIsVoidStar) { + StaticAssertTypeEq<HANDLE, void*>(); +} + +TEST(WindowsTypesTest, CRITICAL_SECTIONIs_RTL_CRITICAL_SECTION) { + StaticAssertTypeEq<CRITICAL_SECTION, _RTL_CRITICAL_SECTION>(); +} +#endif // GTEST_OS_WINDOWS + } // namespace internal } // namespace testing |