Commit 431a8be1 authored by vladlosev's avatar vladlosev
Browse files

Implements the timestamp attribute for the testsuites element in the output...

Implements the timestamp attribute for the testsuites element in the output XML (external contribution by Dirk Meister).
parent 69a40b7d
...@@ -1152,6 +1152,10 @@ class GTEST_API_ UnitTest { ...@@ -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); ...@@ -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 { ...@@ -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 { ...@@ -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_;
......
...@@ -39,6 +39,7 @@ ...@@ -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) { ...@@ -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, ...@@ -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(); } ...@@ -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) ...@@ -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() { ...@@ -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) { ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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) { ...@@ -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) { ...@@ -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_" ...@@ -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"?> ...@@ -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 @@ ...@@ -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: ...@@ -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> ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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 ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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)
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