Commit a6340420 by kosak

Implement threading support for gtest on Windows.

Also, stop using localtime(). Instead, use localtime_r() on most systems, localtime_s() on Windows.
parent ffea2d60
...@@ -968,32 +968,6 @@ GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv); ...@@ -968,32 +968,6 @@ GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv);
// platform. // platform.
GTEST_API_ std::string GetLastErrnoDescription(); GTEST_API_ std::string GetLastErrnoDescription();
# if GTEST_OS_WINDOWS
// Provides leak-safe Windows kernel handle ownership.
class AutoHandle {
public:
AutoHandle() : handle_(INVALID_HANDLE_VALUE) {}
explicit AutoHandle(HANDLE handle) : handle_(handle) {}
~AutoHandle() { Reset(); }
HANDLE Get() const { return handle_; }
void Reset() { Reset(INVALID_HANDLE_VALUE); }
void Reset(HANDLE handle) {
if (handle != handle_) {
if (handle_ != INVALID_HANDLE_VALUE)
::CloseHandle(handle_);
handle_ = handle;
}
}
private:
HANDLE handle_;
GTEST_DISALLOW_COPY_AND_ASSIGN_(AutoHandle);
};
# endif // GTEST_OS_WINDOWS
// Attempts to parse a string into a positive integer pointed to by the // Attempts to parse a string into a positive integer pointed to by the
// number parameter. Returns true if that is possible. // number parameter. Returns true if that is possible.
// GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we can use // GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we can use
......
...@@ -3219,27 +3219,23 @@ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms) { ...@@ -3219,27 +3219,23 @@ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms) {
// Converts the given epoch time in milliseconds to a date string in the ISO // Converts the given epoch time in milliseconds to a date string in the ISO
// 8601 format, without the timezone information. // 8601 format, without the timezone information.
std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms) { std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms) {
// Using non-reentrant version as localtime_r is not portable.
time_t seconds = static_cast<time_t>(ms / 1000); time_t seconds = static_cast<time_t>(ms / 1000);
struct tm time_struct;
#ifdef _MSC_VER #ifdef _MSC_VER
# pragma warning(push) // Saves the current warning state. if (localtime_s(&time_struct, &seconds) != 0)
# pragma warning(disable:4996) // Temporarily disables warning 4996 return ""; // Invalid ms value
// (function or variable may be unsafe).
const struct tm* const time_struct = localtime(&seconds); // NOLINT
# pragma warning(pop) // Restores the warning state again.
#else #else
const struct tm* const time_struct = localtime(&seconds); // NOLINT if (localtime_r(&seconds, &time_struct) == NULL)
#endif
if (time_struct == NULL)
return ""; // Invalid ms value return ""; // Invalid ms value
#endif
// YYYY-MM-DDThh:mm:ss // YYYY-MM-DDThh:mm:ss
return StreamableToString(time_struct->tm_year + 1900) + "-" + return StreamableToString(time_struct.tm_year + 1900) + "-" +
String::FormatIntWidth2(time_struct->tm_mon + 1) + "-" + String::FormatIntWidth2(time_struct.tm_mon + 1) + "-" +
String::FormatIntWidth2(time_struct->tm_mday) + "T" + String::FormatIntWidth2(time_struct.tm_mday) + "T" +
String::FormatIntWidth2(time_struct->tm_hour) + ":" + String::FormatIntWidth2(time_struct.tm_hour) + ":" +
String::FormatIntWidth2(time_struct->tm_min) + ":" + String::FormatIntWidth2(time_struct.tm_min) + ":" +
String::FormatIntWidth2(time_struct->tm_sec); String::FormatIntWidth2(time_struct.tm_sec);
} }
// Streams an XML CDATA section, escaping invalid CDATA sequences as needed. // Streams an XML CDATA section, escaping invalid CDATA sequences as needed.
......
...@@ -699,7 +699,10 @@ TEST_F(TestForDeathTest, ExpectDebugDeathDoesNotAbort) { ...@@ -699,7 +699,10 @@ TEST_F(TestForDeathTest, ExpectDebugDeathDoesNotAbort) {
void AssertDebugDeathHelper(bool* aborted) { void AssertDebugDeathHelper(bool* aborted) {
*aborted = true; *aborted = true;
ASSERT_DEBUG_DEATH(return, "") << "This is expected to fail."; GTEST_LOG_(INFO) << "Before ASSERT_DEBUG_DEATH";
ASSERT_DEBUG_DEATH(GTEST_LOG_(INFO) << "In ASSERT_DEBUG_DEATH"; return, "")
<< "This is expected to fail.";
GTEST_LOG_(INFO) << "After ASSERT_DEBUG_DEATH";
*aborted = false; *aborted = false;
} }
...@@ -712,6 +715,69 @@ TEST_F(TestForDeathTest, AssertDebugDeathAborts) { ...@@ -712,6 +715,69 @@ TEST_F(TestForDeathTest, AssertDebugDeathAborts) {
EXPECT_TRUE(aborted); EXPECT_TRUE(aborted);
} }
TEST_F(TestForDeathTest, AssertDebugDeathAborts2) {
static bool aborted;
aborted = false;
EXPECT_FATAL_FAILURE(AssertDebugDeathHelper(&aborted), "");
EXPECT_TRUE(aborted);
}
TEST_F(TestForDeathTest, AssertDebugDeathAborts3) {
static bool aborted;
aborted = false;
EXPECT_FATAL_FAILURE(AssertDebugDeathHelper(&aborted), "");
EXPECT_TRUE(aborted);
}
TEST_F(TestForDeathTest, AssertDebugDeathAborts4) {
static bool aborted;
aborted = false;
EXPECT_FATAL_FAILURE(AssertDebugDeathHelper(&aborted), "");
EXPECT_TRUE(aborted);
}
TEST_F(TestForDeathTest, AssertDebugDeathAborts5) {
static bool aborted;
aborted = false;
EXPECT_FATAL_FAILURE(AssertDebugDeathHelper(&aborted), "");
EXPECT_TRUE(aborted);
}
TEST_F(TestForDeathTest, AssertDebugDeathAborts6) {
static bool aborted;
aborted = false;
EXPECT_FATAL_FAILURE(AssertDebugDeathHelper(&aborted), "");
EXPECT_TRUE(aborted);
}
TEST_F(TestForDeathTest, AssertDebugDeathAborts7) {
static bool aborted;
aborted = false;
EXPECT_FATAL_FAILURE(AssertDebugDeathHelper(&aborted), "");
EXPECT_TRUE(aborted);
}
TEST_F(TestForDeathTest, AssertDebugDeathAborts8) {
static bool aborted;
aborted = false;
EXPECT_FATAL_FAILURE(AssertDebugDeathHelper(&aborted), "");
EXPECT_TRUE(aborted);
}
TEST_F(TestForDeathTest, AssertDebugDeathAborts9) {
static bool aborted;
aborted = false;
EXPECT_FATAL_FAILURE(AssertDebugDeathHelper(&aborted), "");
EXPECT_TRUE(aborted);
}
TEST_F(TestForDeathTest, AssertDebugDeathAborts10) {
static bool aborted;
aborted = false;
EXPECT_FATAL_FAILURE(AssertDebugDeathHelper(&aborted), "");
EXPECT_TRUE(aborted);
}
# endif // _NDEBUG # endif // _NDEBUG
// Tests the *_EXIT family of macros, using a variety of predicates. // Tests the *_EXIT family of macros, using a variety of predicates.
......
...@@ -1062,11 +1062,13 @@ class AtomicCounterWithMutex { ...@@ -1062,11 +1062,13 @@ class AtomicCounterWithMutex {
MutexLock lock(mutex_); MutexLock lock(mutex_);
int temp = value_; int temp = value_;
{ {
// Locking a mutex puts up a memory barrier, preventing reads and // We need to put up a memory barrier to prevent reads and writes to
// writes to value_ rearranged when observed from other threads. // value_ rearranged with the call to SleepMilliseconds when observed
// // from other threads.
// We cannot use Mutex and MutexLock here or rely on their memory #if GTEST_HAS_PTHREAD
// barrier functionality as we are testing them here. // 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; pthread_mutex_t memory_barrier_mutex;
GTEST_CHECK_POSIX_SUCCESS_( GTEST_CHECK_POSIX_SUCCESS_(
pthread_mutex_init(&memory_barrier_mutex, NULL)); pthread_mutex_init(&memory_barrier_mutex, NULL));
...@@ -1076,6 +1078,15 @@ class AtomicCounterWithMutex { ...@@ -1076,6 +1078,15 @@ class AtomicCounterWithMutex {
GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_unlock(&memory_barrier_mutex)); GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_unlock(&memory_barrier_mutex));
GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_destroy(&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; value_ = temp + 1;
} }
...@@ -1145,27 +1156,76 @@ TEST(ThreadLocalTest, ParameterizedConstructorSetsDefault) { ...@@ -1145,27 +1156,76 @@ TEST(ThreadLocalTest, ParameterizedConstructorSetsDefault) {
EXPECT_STREQ("foo", result.c_str()); 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 // DestructorTracker keeps track of whether its instances have been
// destroyed. // destroyed.
static std::vector<bool> g_destroyed;
class DestructorTracker { class DestructorTracker {
public: public:
DestructorTracker() : index_(GetNewIndex()) {} DestructorTracker() : index_(GetNewIndex()) {}
DestructorTracker(const DestructorTracker& /* rhs */) DestructorTracker(const DestructorTracker& /* rhs */)
: index_(GetNewIndex()) {} : index_(GetNewIndex()) {}
~DestructorTracker() { ~DestructorTracker() {
// We never access g_destroyed concurrently, so we don't need to // We never access DestructorCall::List() concurrently, so we don't need
// protect the write operation under a mutex. // to protect this acccess with a mutex.
g_destroyed[index_] = true; DestructorCall::List()[index_]->ReportDestroyed();
} }
private: private:
static int GetNewIndex() { static int GetNewIndex() {
g_destroyed.push_back(false); DestructorCall::List().push_back(new DestructorCall);
return g_destroyed.size() - 1; return DestructorCall::List().size() - 1;
} }
const int index_; const int index_;
GTEST_DISALLOW_ASSIGN_(DestructorTracker);
}; };
typedef ThreadLocal<DestructorTracker>* ThreadParam; typedef ThreadLocal<DestructorTracker>* ThreadParam;
...@@ -1177,63 +1237,63 @@ void CallThreadLocalGet(ThreadParam thread_local_param) { ...@@ -1177,63 +1237,63 @@ void CallThreadLocalGet(ThreadParam thread_local_param) {
// Tests that when a ThreadLocal object dies in a thread, it destroys // Tests that when a ThreadLocal object dies in a thread, it destroys
// the managed object for that thread. // the managed object for that thread.
TEST(ThreadLocalTest, DestroysManagedObjectForOwnThreadWhenDying) { TEST(ThreadLocalTest, DestroysManagedObjectForOwnThreadWhenDying) {
g_destroyed.clear(); DestructorCall::ResetList();
{ {
// The next line default constructs a DestructorTracker object as // The next line default constructs a DestructorTracker object as
// the default value of objects managed by thread_local_tracker. // the default value of objects managed by thread_local_tracker.
ThreadLocal<DestructorTracker> thread_local_tracker; ThreadLocal<DestructorTracker> thread_local_tracker;
ASSERT_EQ(1U, g_destroyed.size()); ASSERT_EQ(1U, DestructorCall::List().size());
ASSERT_FALSE(g_destroyed[0]); ASSERT_FALSE(DestructorCall::List()[0]->CheckDestroyed());
// This creates another DestructorTracker object for the main thread. // This creates another DestructorTracker object for the main thread.
thread_local_tracker.get(); thread_local_tracker.get();
ASSERT_EQ(2U, g_destroyed.size()); ASSERT_EQ(2U, DestructorCall::List().size());
ASSERT_FALSE(g_destroyed[0]); ASSERT_FALSE(DestructorCall::List()[0]->CheckDestroyed());
ASSERT_FALSE(g_destroyed[1]); ASSERT_FALSE(DestructorCall::List()[1]->CheckDestroyed());
} }
// Now thread_local_tracker has died. It should have destroyed both the // Now thread_local_tracker has died. It should have destroyed both the
// default value shared by all threads and the value for the main // default value shared by all threads and the value for the main
// thread. // thread.
ASSERT_EQ(2U, g_destroyed.size()); ASSERT_EQ(2U, DestructorCall::List().size());
EXPECT_TRUE(g_destroyed[0]); EXPECT_TRUE(DestructorCall::List()[0]->CheckDestroyed());
EXPECT_TRUE(g_destroyed[1]); EXPECT_TRUE(DestructorCall::List()[1]->CheckDestroyed());
g_destroyed.clear(); DestructorCall::ResetList();
} }
// Tests that when a thread exits, the thread-local object for that // Tests that when a thread exits, the thread-local object for that
// thread is destroyed. // thread is destroyed.
TEST(ThreadLocalTest, DestroysManagedObjectAtThreadExit) { TEST(ThreadLocalTest, DestroysManagedObjectAtThreadExit) {
g_destroyed.clear(); DestructorCall::ResetList();
{ {
// The next line default constructs a DestructorTracker object as // The next line default constructs a DestructorTracker object as
// the default value of objects managed by thread_local_tracker. // the default value of objects managed by thread_local_tracker.
ThreadLocal<DestructorTracker> thread_local_tracker; ThreadLocal<DestructorTracker> thread_local_tracker;
ASSERT_EQ(1U, g_destroyed.size()); ASSERT_EQ(1U, DestructorCall::List().size());
ASSERT_FALSE(g_destroyed[0]); ASSERT_FALSE(DestructorCall::List()[0]->CheckDestroyed());
// This creates another DestructorTracker object in the new thread. // This creates another DestructorTracker object in the new thread.
ThreadWithParam<ThreadParam> thread( ThreadWithParam<ThreadParam> thread(
&CallThreadLocalGet, &thread_local_tracker, NULL); &CallThreadLocalGet, &thread_local_tracker, NULL);
thread.Join(); thread.Join();
// Now the new thread has exited. The per-thread object for it // The thread has exited, and we should have another DestroyedTracker
// should have been destroyed. // instance created for it. But it may not have been destroyed yet.
ASSERT_EQ(2U, g_destroyed.size()); // The instance for the main thread should still persist.
ASSERT_FALSE(g_destroyed[0]); ASSERT_EQ(2U, DestructorCall::List().size());
ASSERT_TRUE(g_destroyed[1]); ASSERT_FALSE(DestructorCall::List()[0]->CheckDestroyed());
} }
// Now thread_local_tracker has died. The default value should have been // The thread has exited and thread_local_tracker has died. The default
// destroyed too. // value should have been destroyed too.
ASSERT_EQ(2U, g_destroyed.size()); ASSERT_EQ(2U, DestructorCall::List().size());
EXPECT_TRUE(g_destroyed[0]); EXPECT_TRUE(DestructorCall::List()[0]->CheckDestroyed());
EXPECT_TRUE(g_destroyed[1]); EXPECT_TRUE(DestructorCall::List()[1]->CheckDestroyed());
g_destroyed.clear(); DestructorCall::ResetList();
} }
TEST(ThreadLocalTest, ThreadLocalMutationsAffectOnlyCurrentThread) { TEST(ThreadLocalTest, ThreadLocalMutationsAffectOnlyCurrentThread) {
...@@ -1249,5 +1309,15 @@ TEST(ThreadLocalTest, ThreadLocalMutationsAffectOnlyCurrentThread) { ...@@ -1249,5 +1309,15 @@ TEST(ThreadLocalTest, ThreadLocalMutationsAffectOnlyCurrentThread) {
#endif // GTEST_IS_THREADSAFE #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 internal
} // namespace testing } // namespace testing
...@@ -252,8 +252,8 @@ SUPPORTS_STACK_TRACES = False ...@@ -252,8 +252,8 @@ SUPPORTS_STACK_TRACES = False
CAN_GENERATE_GOLDEN_FILE = (SUPPORTS_DEATH_TESTS and CAN_GENERATE_GOLDEN_FILE = (SUPPORTS_DEATH_TESTS and
SUPPORTS_TYPED_TESTS and SUPPORTS_TYPED_TESTS and
SUPPORTS_THREADS) SUPPORTS_THREADS and
not IS_WINDOWS)
class GTestOutputTest(gtest_test_utils.TestCase): class GTestOutputTest(gtest_test_utils.TestCase):
def RemoveUnsupportedTests(self, test_output): def RemoveUnsupportedTests(self, test_output):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment