Implements the timestamp attribute for the testsuites element in the output XML (external contribution by Dirk Meister).
This commit is contained in:
		| @@ -1152,6 +1152,10 @@ class GTEST_API_ UnitTest { | |||||||
|   // Gets the number of tests that should run. |   // Gets the number of tests that should run. | ||||||
|   int test_to_run_count() const; |   int test_to_run_count() const; | ||||||
|  |  | ||||||
|  |   // Gets the time of the test program start, in ms from the start of the | ||||||
|  |   // UNIX epoch. | ||||||
|  |   TimeInMillis start_timestamp() const; | ||||||
|  |  | ||||||
|   // Gets the elapsed time, in milliseconds. |   // Gets the elapsed time, in milliseconds. | ||||||
|   TimeInMillis elapsed_time() const; |   TimeInMillis elapsed_time() const; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -112,6 +112,12 @@ GTEST_API_ bool ShouldUseColor(bool stdout_is_tty); | |||||||
| // Formats the given time in milliseconds as seconds. | // Formats the given time in milliseconds as seconds. | ||||||
| GTEST_API_ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms); | GTEST_API_ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms); | ||||||
|  |  | ||||||
|  | // Converts the given time in milliseconds to a date string in the ISO 8601 | ||||||
|  | // format, without the timezone information.  N.B.: due to the use the | ||||||
|  | // non-reentrant localtime() function, this function is not thread safe.  Do | ||||||
|  | // not use it in any code that can be called from multiple threads. | ||||||
|  | GTEST_API_ std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms); | ||||||
|  |  | ||||||
| // Parses a string for an Int32 flag, in the form of "--flag=value". | // Parses a string for an Int32 flag, in the form of "--flag=value". | ||||||
| // | // | ||||||
| // On success, stores the value of the flag in *value, and returns | // On success, stores the value of the flag in *value, and returns | ||||||
| @@ -548,6 +554,10 @@ class GTEST_API_ UnitTestImpl { | |||||||
|   // Gets the number of tests that should run. |   // Gets the number of tests that should run. | ||||||
|   int test_to_run_count() const; |   int test_to_run_count() const; | ||||||
|  |  | ||||||
|  |   // Gets the time of the test program start, in ms from the start of the | ||||||
|  |   // UNIX epoch. | ||||||
|  |   TimeInMillis start_timestamp() const { return start_timestamp_; } | ||||||
|  |  | ||||||
|   // Gets the elapsed time, in milliseconds. |   // Gets the elapsed time, in milliseconds. | ||||||
|   TimeInMillis elapsed_time() const { return elapsed_time_; } |   TimeInMillis elapsed_time() const { return elapsed_time_; } | ||||||
|  |  | ||||||
| @@ -880,6 +890,10 @@ class GTEST_API_ UnitTestImpl { | |||||||
|   // Our random number generator. |   // Our random number generator. | ||||||
|   internal::Random random_; |   internal::Random random_; | ||||||
|  |  | ||||||
|  |   // The time of the test program start, in ms from the start of the | ||||||
|  |   // UNIX epoch. | ||||||
|  |   TimeInMillis start_timestamp_; | ||||||
|  |  | ||||||
|   // How long the test took to run, in milliseconds. |   // How long the test took to run, in milliseconds. | ||||||
|   TimeInMillis elapsed_time_; |   TimeInMillis elapsed_time_; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								src/gtest.cc
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								src/gtest.cc
									
									
									
									
									
								
							| @@ -39,6 +39,7 @@ | |||||||
| #include <stdarg.h> | #include <stdarg.h> | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
| #include <stdlib.h> | #include <stdlib.h> | ||||||
|  | #include <time.h> | ||||||
| #include <wchar.h> | #include <wchar.h> | ||||||
| #include <wctype.h> | #include <wctype.h> | ||||||
|  |  | ||||||
| @@ -3195,6 +3196,32 @@ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms) { | |||||||
|   return ss.str(); |   return ss.str(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Converts the given epoch time in milliseconds to a date string in the ISO | ||||||
|  | // 8601 format, without the timezone information. | ||||||
|  | std::string FormatEpochTimeInMillisAsIso8601(TimeInMillis ms) { | ||||||
|  |   // Using non-reentrant version as localtime_r is not portable. | ||||||
|  |   time_t seconds = static_cast<time_t>(ms / 1000); | ||||||
|  | #ifdef _MSC_VER | ||||||
|  | # pragma warning(push)          // Saves the current warning state. | ||||||
|  | # pragma warning(disable:4996)  // Temporarily disables warning 4996 | ||||||
|  |                                 // (function or variable may be unsafe). | ||||||
|  |   const struct tm* const time_struct = localtime(&seconds);  // NOLINT | ||||||
|  | # pragma warning(pop)           // Restores the warning state again. | ||||||
|  | #else | ||||||
|  |   const struct tm* const time_struct = localtime(&seconds);  // NOLINT | ||||||
|  | #endif | ||||||
|  |   if (time_struct == NULL) | ||||||
|  |     return "";  // Invalid ms value | ||||||
|  |  | ||||||
|  |   return String::Format("%d-%02d-%02dT%02d:%02d:%02d",  // YYYY-MM-DDThh:mm:ss | ||||||
|  |                         time_struct->tm_year + 1900, | ||||||
|  |                         time_struct->tm_mon + 1, | ||||||
|  |                         time_struct->tm_mday, | ||||||
|  |                         time_struct->tm_hour, | ||||||
|  |                         time_struct->tm_min, | ||||||
|  |                         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. | ||||||
| void XmlUnitTestResultPrinter::OutputXmlCDataSection(::std::ostream* stream, | void XmlUnitTestResultPrinter::OutputXmlCDataSection(::std::ostream* stream, | ||||||
|                                                      const char* data) { |                                                      const char* data) { | ||||||
| @@ -3291,10 +3318,11 @@ void XmlUnitTestResultPrinter::PrintXmlUnitTest(FILE* out, | |||||||
|   fprintf(out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); |   fprintf(out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); | ||||||
|   fprintf(out, |   fprintf(out, | ||||||
|           "<testsuites tests=\"%d\" failures=\"%d\" disabled=\"%d\" " |           "<testsuites tests=\"%d\" failures=\"%d\" disabled=\"%d\" " | ||||||
|           "errors=\"0\" time=\"%s\" ", |           "errors=\"0\" timestamp=\"%s\" time=\"%s\" ", | ||||||
|           unit_test.total_test_count(), |           unit_test.total_test_count(), | ||||||
|           unit_test.failed_test_count(), |           unit_test.failed_test_count(), | ||||||
|           unit_test.disabled_test_count(), |           unit_test.disabled_test_count(), | ||||||
|  |           FormatEpochTimeInMillisAsIso8601(unit_test.start_timestamp()).c_str(), | ||||||
|           FormatTimeInMillisAsSeconds(unit_test.elapsed_time()).c_str()); |           FormatTimeInMillisAsSeconds(unit_test.elapsed_time()).c_str()); | ||||||
|   if (GTEST_FLAG(shuffle)) { |   if (GTEST_FLAG(shuffle)) { | ||||||
|     fprintf(out, "random_seed=\"%d\" ", unit_test.random_seed()); |     fprintf(out, "random_seed=\"%d\" ", unit_test.random_seed()); | ||||||
| @@ -3687,6 +3715,12 @@ int UnitTest::total_test_count() const { return impl()->total_test_count(); } | |||||||
| // Gets the number of tests that should run. | // Gets the number of tests that should run. | ||||||
| int UnitTest::test_to_run_count() const { return impl()->test_to_run_count(); } | int UnitTest::test_to_run_count() const { return impl()->test_to_run_count(); } | ||||||
|  |  | ||||||
|  | // Gets the time of the test program start, in ms from the start of the | ||||||
|  | // UNIX epoch. | ||||||
|  | internal::TimeInMillis UnitTest::start_timestamp() const { | ||||||
|  |     return impl()->start_timestamp(); | ||||||
|  | } | ||||||
|  |  | ||||||
| // Gets the elapsed time, in milliseconds. | // Gets the elapsed time, in milliseconds. | ||||||
| internal::TimeInMillis UnitTest::elapsed_time() const { | internal::TimeInMillis UnitTest::elapsed_time() const { | ||||||
|   return impl()->elapsed_time(); |   return impl()->elapsed_time(); | ||||||
| @@ -3961,6 +3995,7 @@ UnitTestImpl::UnitTestImpl(UnitTest* parent) | |||||||
|       post_flag_parse_init_performed_(false), |       post_flag_parse_init_performed_(false), | ||||||
|       random_seed_(0),  // Will be overridden by the flag before first use. |       random_seed_(0),  // Will be overridden by the flag before first use. | ||||||
|       random_(0),  // Will be reseeded before first use. |       random_(0),  // Will be reseeded before first use. | ||||||
|  |       start_timestamp_(0), | ||||||
|       elapsed_time_(0), |       elapsed_time_(0), | ||||||
| #if GTEST_HAS_DEATH_TEST | #if GTEST_HAS_DEATH_TEST | ||||||
|       internal_run_death_test_flag_(NULL), |       internal_run_death_test_flag_(NULL), | ||||||
| @@ -4192,6 +4227,7 @@ bool UnitTestImpl::RunAllTests() { | |||||||
|  |  | ||||||
|   TestEventListener* repeater = listeners()->repeater(); |   TestEventListener* repeater = listeners()->repeater(); | ||||||
|  |  | ||||||
|  |   start_timestamp_ = GetTimeInMillis(); | ||||||
|   repeater->OnTestProgramStart(*parent_); |   repeater->OnTestProgramStart(*parent_); | ||||||
|  |  | ||||||
|   // How many times to repeat the tests?  We don't want to repeat them |   // How many times to repeat the tests?  We don't want to repeat them | ||||||
|   | |||||||
| @@ -71,6 +71,7 @@ TEST(CommandLineFlagsTest, CanBeAccessedInCodeOnceGTestHIsIncluded) { | |||||||
|  |  | ||||||
| #include <limits.h>  // For INT_MAX. | #include <limits.h>  // For INT_MAX. | ||||||
| #include <stdlib.h> | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
| #include <time.h> | #include <time.h> | ||||||
|  |  | ||||||
| #include <map> | #include <map> | ||||||
| @@ -141,6 +142,7 @@ using testing::TestPartResult; | |||||||
| using testing::TestPartResultArray; | using testing::TestPartResultArray; | ||||||
| using testing::TestProperty; | using testing::TestProperty; | ||||||
| using testing::TestResult; | using testing::TestResult; | ||||||
|  | using testing::TimeInMillis; | ||||||
| using testing::UnitTest; | using testing::UnitTest; | ||||||
| using testing::kMaxStackTraceDepth; | using testing::kMaxStackTraceDepth; | ||||||
| using testing::internal::AddReference; | using testing::internal::AddReference; | ||||||
| @@ -156,6 +158,7 @@ using testing::internal::CountIf; | |||||||
| using testing::internal::EqFailure; | using testing::internal::EqFailure; | ||||||
| using testing::internal::FloatingPoint; | using testing::internal::FloatingPoint; | ||||||
| using testing::internal::ForEach; | using testing::internal::ForEach; | ||||||
|  | using testing::internal::FormatEpochTimeInMillisAsIso8601; | ||||||
| using testing::internal::FormatTimeInMillisAsSeconds; | using testing::internal::FormatTimeInMillisAsSeconds; | ||||||
| using testing::internal::GTestFlagSaver; | using testing::internal::GTestFlagSaver; | ||||||
| using testing::internal::GetCurrentOsStackTraceExceptTop; | using testing::internal::GetCurrentOsStackTraceExceptTop; | ||||||
| @@ -163,6 +166,7 @@ using testing::internal::GetElementOr; | |||||||
| using testing::internal::GetNextRandomSeed; | using testing::internal::GetNextRandomSeed; | ||||||
| using testing::internal::GetRandomSeedFromFlag; | using testing::internal::GetRandomSeedFromFlag; | ||||||
| using testing::internal::GetTestTypeId; | using testing::internal::GetTestTypeId; | ||||||
|  | using testing::internal::GetTimeInMillis; | ||||||
| using testing::internal::GetTypeId; | using testing::internal::GetTypeId; | ||||||
| using testing::internal::GetUnitTestImpl; | using testing::internal::GetUnitTestImpl; | ||||||
| using testing::internal::ImplicitlyConvertible; | using testing::internal::ImplicitlyConvertible; | ||||||
| @@ -308,6 +312,103 @@ TEST(FormatTimeInMillisAsSecondsTest, FormatsNegativeNumber) { | |||||||
|   EXPECT_EQ("-3", FormatTimeInMillisAsSeconds(-3000)); |   EXPECT_EQ("-3", FormatTimeInMillisAsSeconds(-3000)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Tests FormatEpochTimeInMillisAsIso8601().  The correctness of conversion | ||||||
|  | // for particular dates below was verified in Python using | ||||||
|  | // datetime.datetime.fromutctimestamp(<timetamp>/1000). | ||||||
|  |  | ||||||
|  | // FormatEpochTimeInMillisAsIso8601 depends on the current timezone, so we | ||||||
|  | // have to set up a particular timezone to obtain predictable results. | ||||||
|  | class FormatEpochTimeInMillisAsIso8601Test : public Test { | ||||||
|  |  public: | ||||||
|  |   // On Cygwin, GCC doesn't allow unqualified integer literals to exceed | ||||||
|  |   // 32 bits, even when 64-bit integer types are available.  We have to | ||||||
|  |   // force the constants to have a 64-bit type here. | ||||||
|  |   static const TimeInMillis kMillisPerSec = 1000; | ||||||
|  |  | ||||||
|  |  private: | ||||||
|  |   virtual void SetUp() { | ||||||
|  |     saved_tz_ = NULL; | ||||||
|  | #if _MSC_VER | ||||||
|  | # pragma warning(push)          // Saves the current warning state. | ||||||
|  | # pragma warning(disable:4996)  // Temporarily disables warning 4996 | ||||||
|  |                                 // (function or variable may be unsafe | ||||||
|  |                                 // for getenv, function is deprecated for | ||||||
|  |                                 // strdup). | ||||||
|  |     if (getenv("TZ")) | ||||||
|  |       saved_tz_ = strdup(getenv("TZ")); | ||||||
|  | # pragma warning(pop)           // Restores the warning state again. | ||||||
|  | #else | ||||||
|  |     if (getenv("TZ")) | ||||||
|  |       saved_tz_ = strdup(getenv("TZ")); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |     // Set up the time zone for FormatEpochTimeInMillisAsIso8601 to use.  We | ||||||
|  |     // cannot use the local time zone because the function's output depends | ||||||
|  |     // on the time zone. | ||||||
|  |     SetTimeZone("UTC+00"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   virtual void TearDown() { | ||||||
|  |     SetTimeZone(saved_tz_); | ||||||
|  |     free(const_cast<char*>(saved_tz_)); | ||||||
|  |     saved_tz_ = NULL; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static void SetTimeZone(const char* time_zone) { | ||||||
|  |     // tzset() distinguishes between the TZ variable being present and empty | ||||||
|  |     // and not being present, so we have to consider the case of time_zone | ||||||
|  |     // being NULL. | ||||||
|  | #if _MSC_VER | ||||||
|  |     // ...Unless it's MSVC, whose standard library's _putenv doesn't | ||||||
|  |     // distinguish between an empty and a missing variable. | ||||||
|  |     const std::string env_var = | ||||||
|  |         std::string("TZ=") + (time_zone ? time_zone : ""); | ||||||
|  |     _putenv(env_var.c_str()); | ||||||
|  | # pragma warning(push)          // Saves the current warning state. | ||||||
|  | # pragma warning(disable:4996)  // Temporarily disables warning 4996 | ||||||
|  |                                 // (function is deprecated). | ||||||
|  |     tzset(); | ||||||
|  | # pragma warning(pop)           // Restores the warning state again. | ||||||
|  | #else | ||||||
|  |     if (time_zone) { | ||||||
|  |       setenv(("TZ"), time_zone, 1); | ||||||
|  |     } else { | ||||||
|  |       unsetenv("TZ"); | ||||||
|  |     } | ||||||
|  |     tzset(); | ||||||
|  | #endif | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const char* saved_tz_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const TimeInMillis FormatEpochTimeInMillisAsIso8601Test::kMillisPerSec; | ||||||
|  |  | ||||||
|  | TEST_F(FormatEpochTimeInMillisAsIso8601Test, PrintsTwoDigitSegments) { | ||||||
|  |   EXPECT_EQ("2011-10-31T18:52:42", | ||||||
|  |             FormatEpochTimeInMillisAsIso8601(1320087162 * kMillisPerSec)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TEST_F(FormatEpochTimeInMillisAsIso8601Test, MillisecondsDoNotAffectResult) { | ||||||
|  |   EXPECT_EQ( | ||||||
|  |       "2011-10-31T18:52:42", | ||||||
|  |       FormatEpochTimeInMillisAsIso8601(1320087162 * kMillisPerSec + 234)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TEST_F(FormatEpochTimeInMillisAsIso8601Test, PrintsLeadingZeroes) { | ||||||
|  |   EXPECT_EQ("2011-09-03T05:07:02", | ||||||
|  |             FormatEpochTimeInMillisAsIso8601(1315026422 * kMillisPerSec)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TEST_F(FormatEpochTimeInMillisAsIso8601Test, Prints24HourTime) { | ||||||
|  |   EXPECT_EQ("2011-09-28T17:08:22", | ||||||
|  |             FormatEpochTimeInMillisAsIso8601(1317229702 * kMillisPerSec)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TEST_F(FormatEpochTimeInMillisAsIso8601Test, PrintsEpochStart) { | ||||||
|  |   EXPECT_EQ("1970-01-01T00:00:00", FormatEpochTimeInMillisAsIso8601(0)); | ||||||
|  | } | ||||||
|  |  | ||||||
| #if GTEST_CAN_COMPARE_NULL | #if GTEST_CAN_COMPARE_NULL | ||||||
|  |  | ||||||
| # ifdef __BORLANDC__ | # ifdef __BORLANDC__ | ||||||
| @@ -2130,6 +2231,11 @@ TEST(UnitTestTest, CanGetOriginalWorkingDir) { | |||||||
|   EXPECT_STRNE(UnitTest::GetInstance()->original_working_dir(), ""); |   EXPECT_STRNE(UnitTest::GetInstance()->original_working_dir(), ""); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | TEST(UnitTestTest, ReturnsPlausibleTimestamp) { | ||||||
|  |   EXPECT_LT(0, UnitTest::GetInstance()->start_timestamp()); | ||||||
|  |   EXPECT_LE(UnitTest::GetInstance()->start_timestamp(), GetTimeInMillis()); | ||||||
|  | } | ||||||
|  |  | ||||||
| // This group of tests is for predicate assertions (ASSERT_PRED*, etc) | // This group of tests is for predicate assertions (ASSERT_PRED*, etc) | ||||||
| // of various arities.  They do not attempt to be exhaustive.  Rather, | // of various arities.  They do not attempt to be exhaustive.  Rather, | ||||||
| // view them as smoke tests that can be easily reviewed and verified. | // view them as smoke tests that can be easily reviewed and verified. | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ GTEST_OUTPUT_1_TEST = "gtest_xml_outfile1_test_" | |||||||
| GTEST_OUTPUT_2_TEST = "gtest_xml_outfile2_test_" | GTEST_OUTPUT_2_TEST = "gtest_xml_outfile2_test_" | ||||||
|  |  | ||||||
| EXPECTED_XML_1 = """<?xml version="1.0" encoding="UTF-8"?> | EXPECTED_XML_1 = """<?xml version="1.0" encoding="UTF-8"?> | ||||||
| <testsuites tests="1" failures="0" disabled="0" errors="0" time="*" name="AllTests"> | <testsuites tests="1" failures="0" disabled="0" errors="0" time="*" timestamp="*" name="AllTests"> | ||||||
|   <testsuite name="PropertyOne" tests="1" failures="0" disabled="0" errors="0" time="*"> |   <testsuite name="PropertyOne" tests="1" failures="0" disabled="0" errors="0" time="*"> | ||||||
|     <testcase name="TestSomeProperties" status="run" time="*" classname="PropertyOne" SetUpProp="1" TestSomeProperty="1" TearDownProp="1" /> |     <testcase name="TestSomeProperties" status="run" time="*" classname="PropertyOne" SetUpProp="1" TestSomeProperty="1" TearDownProp="1" /> | ||||||
|   </testsuite> |   </testsuite> | ||||||
| @@ -53,7 +53,7 @@ EXPECTED_XML_1 = """<?xml version="1.0" encoding="UTF-8"?> | |||||||
| """ | """ | ||||||
|  |  | ||||||
| EXPECTED_XML_2 = """<?xml version="1.0" encoding="UTF-8"?> | EXPECTED_XML_2 = """<?xml version="1.0" encoding="UTF-8"?> | ||||||
| <testsuites tests="1" failures="0" disabled="0" errors="0" time="*" name="AllTests"> | <testsuites tests="1" failures="0" disabled="0" errors="0" time="*" timestamp="*" name="AllTests"> | ||||||
|   <testsuite name="PropertyTwo" tests="1" failures="0" disabled="0" errors="0" time="*"> |   <testsuite name="PropertyTwo" tests="1" failures="0" disabled="0" errors="0" time="*"> | ||||||
|     <testcase name="TestSomeProperties" status="run" time="*" classname="PropertyTwo" SetUpProp="2" TestSomeProperty="2" TearDownProp="2" /> |     <testcase name="TestSomeProperties" status="run" time="*" classname="PropertyTwo" SetUpProp="2" TestSomeProperty="2" TearDownProp="2" /> | ||||||
|   </testsuite> |   </testsuite> | ||||||
|   | |||||||
| @@ -33,8 +33,10 @@ | |||||||
|  |  | ||||||
| __author__ = 'eefacm@gmail.com (Sean Mcafee)' | __author__ = 'eefacm@gmail.com (Sean Mcafee)' | ||||||
|  |  | ||||||
|  | import datetime | ||||||
| import errno | import errno | ||||||
| import os | import os | ||||||
|  | import re | ||||||
| import sys | import sys | ||||||
| from xml.dom import minidom, Node | from xml.dom import minidom, Node | ||||||
|  |  | ||||||
| @@ -55,7 +57,7 @@ else: | |||||||
|   STACK_TRACE_TEMPLATE = '' |   STACK_TRACE_TEMPLATE = '' | ||||||
|  |  | ||||||
| EXPECTED_NON_EMPTY_XML = """<?xml version="1.0" encoding="UTF-8"?> | EXPECTED_NON_EMPTY_XML = """<?xml version="1.0" encoding="UTF-8"?> | ||||||
| <testsuites tests="23" failures="4" disabled="2" errors="0" time="*" name="AllTests"> | <testsuites tests="23" failures="4" disabled="2" errors="0" time="*" timestamp="*" name="AllTests"> | ||||||
|   <testsuite name="SuccessfulTest" tests="1" failures="0" disabled="0" errors="0" time="*"> |   <testsuite name="SuccessfulTest" tests="1" failures="0" disabled="0" errors="0" time="*"> | ||||||
|     <testcase name="Succeeds" status="run" time="*" classname="SuccessfulTest"/> |     <testcase name="Succeeds" status="run" time="*" classname="SuccessfulTest"/> | ||||||
|   </testsuite> |   </testsuite> | ||||||
| @@ -128,7 +130,7 @@ Invalid characters in brackets []%(stack)s]]></failure> | |||||||
|  |  | ||||||
|  |  | ||||||
| EXPECTED_EMPTY_XML = """<?xml version="1.0" encoding="UTF-8"?> | EXPECTED_EMPTY_XML = """<?xml version="1.0" encoding="UTF-8"?> | ||||||
| <testsuites tests="0" failures="0" disabled="0" errors="0" time="*" name="AllTests"> | <testsuites tests="0" failures="0" disabled="0" errors="0" time="*" timestamp="*" name="AllTests"> | ||||||
| </testsuites>""" | </testsuites>""" | ||||||
|  |  | ||||||
| GTEST_PROGRAM_PATH = gtest_test_utils.GetTestExecutablePath(GTEST_PROGRAM_NAME) | GTEST_PROGRAM_PATH = gtest_test_utils.GetTestExecutablePath(GTEST_PROGRAM_NAME) | ||||||
| @@ -153,13 +155,39 @@ class GTestXMLOutputUnitTest(gtest_xml_test_utils.GTestXMLTestCase): | |||||||
|       self._TestXmlOutput(GTEST_PROGRAM_NAME, EXPECTED_NON_EMPTY_XML, 1) |       self._TestXmlOutput(GTEST_PROGRAM_NAME, EXPECTED_NON_EMPTY_XML, 1) | ||||||
|  |  | ||||||
|   def testEmptyXmlOutput(self): |   def testEmptyXmlOutput(self): | ||||||
|     """ |     """Verifies XML output for a Google Test binary without actual tests. | ||||||
|  |  | ||||||
|     Runs a test program that generates an empty XML output, and |     Runs a test program that generates an empty XML output, and | ||||||
|     tests that the XML output is expected. |     tests that the XML output is expected. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     self._TestXmlOutput('gtest_no_test_unittest', EXPECTED_EMPTY_XML, 0) |     self._TestXmlOutput('gtest_no_test_unittest', EXPECTED_EMPTY_XML, 0) | ||||||
|  |  | ||||||
|  |   def testTimestampValue(self): | ||||||
|  |     """Checks whether the timestamp attribute in the XML output is valid. | ||||||
|  |  | ||||||
|  |     Runs a test program that generates an empty XML output, and checks if | ||||||
|  |     the timestamp attribute in the testsuites tag is valid. | ||||||
|  |     """ | ||||||
|  |     actual = self._GetXmlOutput('gtest_no_test_unittest', 0) | ||||||
|  |     date_time_str = actual.documentElement.getAttributeNode('timestamp').value | ||||||
|  |     # datetime.strptime() is only available in Python 2.5+ so we have to | ||||||
|  |     # parse the expected datetime manually. | ||||||
|  |     match = re.match(r'(\d+)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)', date_time_str) | ||||||
|  |     self.assertTrue( | ||||||
|  |         re.match, | ||||||
|  |         'XML datettime string %s has incorrect format' % date_time_str) | ||||||
|  |     date_time_from_xml = datetime.datetime( | ||||||
|  |         year=int(match.group(1)), month=int(match.group(2)), | ||||||
|  |         day=int(match.group(3)), hour=int(match.group(4)), | ||||||
|  |         minute=int(match.group(5)), second=int(match.group(6))) | ||||||
|  |  | ||||||
|  |     time_delta = abs(datetime.datetime.now() - date_time_from_xml) | ||||||
|  |     # timestamp value should be near the current local time | ||||||
|  |     self.assertTrue(time_delta < datetime.timedelta(seconds=600), | ||||||
|  |                     'time_delta is %s' % time_delta) | ||||||
|  |     actual.unlink() | ||||||
|  |  | ||||||
|   def testDefaultOutputFile(self): |   def testDefaultOutputFile(self): | ||||||
|     """ |     """ | ||||||
|     Confirms that Google Test produces an XML output file with the expected |     Confirms that Google Test produces an XML output file with the expected | ||||||
| @@ -198,8 +226,10 @@ class GTestXMLOutputUnitTest(gtest_xml_test_utils.GTestXMLTestCase): | |||||||
|                '--shut_down_xml'] |                '--shut_down_xml'] | ||||||
|     p = gtest_test_utils.Subprocess(command) |     p = gtest_test_utils.Subprocess(command) | ||||||
|     if p.terminated_by_signal: |     if p.terminated_by_signal: | ||||||
|       self.assert_(False, |       # p.signal is avalable only if p.terminated_by_signal is True. | ||||||
|                    '%s was killed by signal %d' % (gtest_prog_name, p.signal)) |       self.assertFalse( | ||||||
|  |           p.terminated_by_signal, | ||||||
|  |           '%s was killed by signal %d' % (GTEST_PROGRAM_NAME, p.signal)) | ||||||
|     else: |     else: | ||||||
|       self.assert_(p.exited) |       self.assert_(p.exited) | ||||||
|       self.assertEquals(1, p.exit_code, |       self.assertEquals(1, p.exit_code, | ||||||
| @@ -209,13 +239,10 @@ class GTestXMLOutputUnitTest(gtest_xml_test_utils.GTestXMLTestCase): | |||||||
|  |  | ||||||
|     self.assert_(not os.path.isfile(xml_path)) |     self.assert_(not os.path.isfile(xml_path)) | ||||||
|  |  | ||||||
|  |   def _GetXmlOutput(self, gtest_prog_name, expected_exit_code): | ||||||
|   def _TestXmlOutput(self, gtest_prog_name, expected_xml, expected_exit_code): |  | ||||||
|     """ |     """ | ||||||
|     Asserts that the XML document generated by running the program |     Returns the xml output generated by running the program gtest_prog_name. | ||||||
|     gtest_prog_name matches expected_xml, a string containing another |     Furthermore, the program's exit code must be expected_exit_code. | ||||||
|     XML document.  Furthermore, the program's exit code must be |  | ||||||
|     expected_exit_code. |  | ||||||
|     """ |     """ | ||||||
|     xml_path = os.path.join(gtest_test_utils.GetTempDir(), |     xml_path = os.path.join(gtest_test_utils.GetTempDir(), | ||||||
|                             gtest_prog_name + 'out.xml') |                             gtest_prog_name + 'out.xml') | ||||||
| @@ -232,15 +259,24 @@ class GTestXMLOutputUnitTest(gtest_xml_test_utils.GTestXMLTestCase): | |||||||
|                         "'%s' exited with code %s, which doesn't match " |                         "'%s' exited with code %s, which doesn't match " | ||||||
|                         'the expected exit code %s.' |                         'the expected exit code %s.' | ||||||
|                         % (command, p.exit_code, expected_exit_code)) |                         % (command, p.exit_code, expected_exit_code)) | ||||||
|  |     actual = minidom.parse(xml_path) | ||||||
|  |     return actual | ||||||
|  |  | ||||||
|  |   def _TestXmlOutput(self, gtest_prog_name, expected_xml, expected_exit_code): | ||||||
|  |     """ | ||||||
|  |     Asserts that the XML document generated by running the program | ||||||
|  |     gtest_prog_name matches expected_xml, a string containing another | ||||||
|  |     XML document.  Furthermore, the program's exit code must be | ||||||
|  |     expected_exit_code. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     actual = self._GetXmlOutput(gtest_prog_name, expected_exit_code) | ||||||
|     expected = minidom.parseString(expected_xml) |     expected = minidom.parseString(expected_xml) | ||||||
|     actual   = minidom.parse(xml_path) |  | ||||||
|     self.NormalizeXml(actual.documentElement) |     self.NormalizeXml(actual.documentElement) | ||||||
|     self.AssertEquivalentNodes(expected.documentElement, |     self.AssertEquivalentNodes(expected.documentElement, | ||||||
|                                actual.documentElement) |                                actual.documentElement) | ||||||
|     expected.unlink() |     expected.unlink() | ||||||
|     actual  .unlink() |     actual.unlink() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|   | |||||||
| @@ -39,8 +39,8 @@ from xml.dom import minidom, Node | |||||||
| import gtest_test_utils | import gtest_test_utils | ||||||
|  |  | ||||||
|  |  | ||||||
| GTEST_OUTPUT_FLAG         = "--gtest_output" | GTEST_OUTPUT_FLAG         = '--gtest_output' | ||||||
| GTEST_DEFAULT_OUTPUT_FILE = "test_detail.xml" | GTEST_DEFAULT_OUTPUT_FILE = 'test_detail.xml' | ||||||
|  |  | ||||||
| class GTestXMLTestCase(gtest_test_utils.TestCase): | class GTestXMLTestCase(gtest_test_utils.TestCase): | ||||||
|   """ |   """ | ||||||
| @@ -80,23 +80,23 @@ class GTestXMLTestCase(gtest_test_utils.TestCase): | |||||||
|     actual_attributes   = actual_node  .attributes |     actual_attributes   = actual_node  .attributes | ||||||
|     self.assertEquals( |     self.assertEquals( | ||||||
|         expected_attributes.length, actual_attributes.length, |         expected_attributes.length, actual_attributes.length, | ||||||
|         "attribute numbers differ in element " + actual_node.tagName) |         'attribute numbers differ in element ' + actual_node.tagName) | ||||||
|     for i in range(expected_attributes.length): |     for i in range(expected_attributes.length): | ||||||
|       expected_attr = expected_attributes.item(i) |       expected_attr = expected_attributes.item(i) | ||||||
|       actual_attr   = actual_attributes.get(expected_attr.name) |       actual_attr   = actual_attributes.get(expected_attr.name) | ||||||
|       self.assert_( |       self.assert_( | ||||||
|           actual_attr is not None, |           actual_attr is not None, | ||||||
|           "expected attribute %s not found in element %s" % |           'expected attribute %s not found in element %s' % | ||||||
|           (expected_attr.name, actual_node.tagName)) |           (expected_attr.name, actual_node.tagName)) | ||||||
|       self.assertEquals(expected_attr.value, actual_attr.value, |       self.assertEquals(expected_attr.value, actual_attr.value, | ||||||
|                         " values of attribute %s in element %s differ" % |                         ' values of attribute %s in element %s differ' % | ||||||
|                         (expected_attr.name, actual_node.tagName)) |                         (expected_attr.name, actual_node.tagName)) | ||||||
|  |  | ||||||
|     expected_children = self._GetChildren(expected_node) |     expected_children = self._GetChildren(expected_node) | ||||||
|     actual_children = self._GetChildren(actual_node) |     actual_children = self._GetChildren(actual_node) | ||||||
|     self.assertEquals( |     self.assertEquals( | ||||||
|         len(expected_children), len(actual_children), |         len(expected_children), len(actual_children), | ||||||
|         "number of child elements differ in element " + actual_node.tagName) |         'number of child elements differ in element ' + actual_node.tagName) | ||||||
|     for child_id, child in expected_children.iteritems(): |     for child_id, child in expected_children.iteritems(): | ||||||
|       self.assert_(child_id in actual_children, |       self.assert_(child_id in actual_children, | ||||||
|                    '<%s> is not in <%s> (in element %s)' % |                    '<%s> is not in <%s> (in element %s)' % | ||||||
| @@ -104,10 +104,10 @@ class GTestXMLTestCase(gtest_test_utils.TestCase): | |||||||
|       self.AssertEquivalentNodes(child, actual_children[child_id]) |       self.AssertEquivalentNodes(child, actual_children[child_id]) | ||||||
|  |  | ||||||
|   identifying_attribute = { |   identifying_attribute = { | ||||||
|     "testsuites": "name", |     'testsuites': 'name', | ||||||
|     "testsuite": "name", |     'testsuite': 'name', | ||||||
|     "testcase":  "name", |     'testcase':  'name', | ||||||
|     "failure":   "message", |     'failure':   'message', | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   def _GetChildren(self, element): |   def _GetChildren(self, element): | ||||||
| @@ -127,20 +127,20 @@ class GTestXMLTestCase(gtest_test_utils.TestCase): | |||||||
|     for child in element.childNodes: |     for child in element.childNodes: | ||||||
|       if child.nodeType == Node.ELEMENT_NODE: |       if child.nodeType == Node.ELEMENT_NODE: | ||||||
|         self.assert_(child.tagName in self.identifying_attribute, |         self.assert_(child.tagName in self.identifying_attribute, | ||||||
|                      "Encountered unknown element <%s>" % child.tagName) |                      'Encountered unknown element <%s>' % child.tagName) | ||||||
|         childID = child.getAttribute(self.identifying_attribute[child.tagName]) |         childID = child.getAttribute(self.identifying_attribute[child.tagName]) | ||||||
|         self.assert_(childID not in children) |         self.assert_(childID not in children) | ||||||
|         children[childID] = child |         children[childID] = child | ||||||
|       elif child.nodeType in [Node.TEXT_NODE, Node.CDATA_SECTION_NODE]: |       elif child.nodeType in [Node.TEXT_NODE, Node.CDATA_SECTION_NODE]: | ||||||
|         if "detail" not in children: |         if 'detail' not in children: | ||||||
|           if (child.nodeType == Node.CDATA_SECTION_NODE or |           if (child.nodeType == Node.CDATA_SECTION_NODE or | ||||||
|               not child.nodeValue.isspace()): |               not child.nodeValue.isspace()): | ||||||
|             children["detail"] = child.ownerDocument.createCDATASection( |             children['detail'] = child.ownerDocument.createCDATASection( | ||||||
|                 child.nodeValue) |                 child.nodeValue) | ||||||
|         else: |         else: | ||||||
|           children["detail"].nodeValue += child.nodeValue |           children['detail'].nodeValue += child.nodeValue | ||||||
|       else: |       else: | ||||||
|         self.fail("Encountered unexpected node type %d" % child.nodeType) |         self.fail('Encountered unexpected node type %d' % child.nodeType) | ||||||
|     return children |     return children | ||||||
|  |  | ||||||
|   def NormalizeXml(self, element): |   def NormalizeXml(self, element): | ||||||
| @@ -151,6 +151,8 @@ class GTestXMLTestCase(gtest_test_utils.TestCase): | |||||||
|     *  The "time" attribute of <testsuites>, <testsuite> and <testcase> |     *  The "time" attribute of <testsuites>, <testsuite> and <testcase> | ||||||
|        elements is replaced with a single asterisk, if it contains |        elements is replaced with a single asterisk, if it contains | ||||||
|        only digit characters. |        only digit characters. | ||||||
|  |     *  The "timestamp" attribute of <testsuites> elements is replaced with a | ||||||
|  |        single asterisk, if it contains a valid ISO8601 datetime value. | ||||||
|     *  The "type_param" attribute of <testcase> elements is replaced with a |     *  The "type_param" attribute of <testcase> elements is replaced with a | ||||||
|        single asterisk (if it sn non-empty) as it is the type name returned |        single asterisk (if it sn non-empty) as it is the type name returned | ||||||
|        by the compiler and is platform dependent. |        by the compiler and is platform dependent. | ||||||
| @@ -160,20 +162,24 @@ class GTestXMLTestCase(gtest_test_utils.TestCase): | |||||||
|     *  The stack traces are removed. |     *  The stack traces are removed. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     if element.tagName in ("testsuites", "testsuite", "testcase"): |     if element.tagName == 'testsuites': | ||||||
|       time = element.getAttributeNode("time") |       timestamp = element.getAttributeNode('timestamp') | ||||||
|       time.value = re.sub(r"^\d+(\.\d+)?$", "*", time.value) |       timestamp.value = re.sub(r'^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d$', | ||||||
|       type_param = element.getAttributeNode("type_param") |                                '*', timestamp.value) | ||||||
|  |     if element.tagName in ('testsuites', 'testsuite', 'testcase'): | ||||||
|  |       time = element.getAttributeNode('time') | ||||||
|  |       time.value = re.sub(r'^\d+(\.\d+)?$', '*', time.value) | ||||||
|  |       type_param = element.getAttributeNode('type_param') | ||||||
|       if type_param and type_param.value: |       if type_param and type_param.value: | ||||||
|         type_param.value = "*" |         type_param.value = '*' | ||||||
|     elif element.tagName == "failure": |     elif element.tagName == 'failure': | ||||||
|       for child in element.childNodes: |       for child in element.childNodes: | ||||||
|         if child.nodeType == Node.CDATA_SECTION_NODE: |         if child.nodeType == Node.CDATA_SECTION_NODE: | ||||||
|           # Removes the source line number. |           # Removes the source line number. | ||||||
|           cdata = re.sub(r"^.*[/\\](.*:)\d+\n", "\\1*\n", child.nodeValue) |           cdata = re.sub(r'^.*[/\\](.*:)\d+\n', '\\1*\n', child.nodeValue) | ||||||
|           # Removes the actual stack trace. |           # Removes the actual stack trace. | ||||||
|           child.nodeValue = re.sub(r"\nStack trace:\n(.|\n)*", |           child.nodeValue = re.sub(r'\nStack trace:\n(.|\n)*', | ||||||
|                                    "", cdata) |                                    '', cdata) | ||||||
|     for child in element.childNodes: |     for child in element.childNodes: | ||||||
|       if child.nodeType == Node.ELEMENT_NODE: |       if child.nodeType == Node.ELEMENT_NODE: | ||||||
|         self.NormalizeXml(child) |         self.NormalizeXml(child) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 vladlosev
					vladlosev