From 64cdcb69b28fc26e78d95c574187f7dd9830c84c Mon Sep 17 00:00:00 2001 From: shiqian Date: Fri, 26 Sep 2008 16:08:30 +0000 Subject: Lots of changes: * changes the XML report format to match JUnit/Ant's. * improves file path handling. * allows the user to disable RTTI using the GTEST_HAS_RTTI macro. * makes the code compile with -Wswitch-enum. --- include/gtest/gtest-spi.h | 9 +++ include/gtest/internal/gtest-filepath.h | 40 ++++++++-- include/gtest/internal/gtest-port.h | 52 ++++++++++++- include/gtest/internal/gtest-type-util.h | 6 ++ include/gtest/internal/gtest-type-util.h.pump | 6 ++ src/gtest-death-test.cc | 1 + src/gtest-filepath.cc | 54 +++++++++++-- src/gtest.cc | 46 ++++++++--- test/gtest-filepath_test.cc | 106 +++++++++++++++++++++++++- test/gtest_unittest.cc | 24 ++++++ test/gtest_xml_outfiles_test.py | 6 +- test/gtest_xml_output_unittest.py | 20 +++-- test/gtest_xml_test_utils.py | 100 ++++++++++++++---------- 13 files changed, 395 insertions(+), 75 deletions(-) diff --git a/include/gtest/gtest-spi.h b/include/gtest/gtest-spi.h index 75d0dcf..5315b97 100644 --- a/include/gtest/gtest-spi.h +++ b/include/gtest/gtest-spi.h @@ -55,6 +55,7 @@ class TestPartResult { : type_(type), file_name_(file_name), line_number_(line_number), + summary_(ExtractSummary(message)), message_(message) { } @@ -69,6 +70,9 @@ class TestPartResult { // or -1 if it's unknown. int line_number() const { return line_number_; } + // Gets the summary of the failure message. + const char* summary() const { return summary_.c_str(); } + // Gets the message associated with the test part. const char* message() const { return message_.c_str(); } @@ -86,12 +90,17 @@ class TestPartResult { private: TestPartResultType type_; + // Gets the summary of the failure message by omitting the stack + // trace in it. + static internal::String ExtractSummary(const char* message); + // The name of the source file where the test part took place, or // NULL if the source file is unknown. internal::String file_name_; // The line in the source file where the test part took place, or -1 // if the line number is unknown. int line_number_; + internal::String summary_; // The test failure summary. internal::String message_; // The test failure message. }; diff --git a/include/gtest/internal/gtest-filepath.h b/include/gtest/internal/gtest-filepath.h index fecdddc..9a0682a 100644 --- a/include/gtest/internal/gtest-filepath.h +++ b/include/gtest/internal/gtest-filepath.h @@ -60,8 +60,19 @@ class FilePath { public: FilePath() : pathname_("") { } FilePath(const FilePath& rhs) : pathname_(rhs.pathname_) { } - explicit FilePath(const char* pathname) : pathname_(pathname) { } - explicit FilePath(const String& pathname) : pathname_(pathname) { } + + explicit FilePath(const char* pathname) : pathname_(pathname) { + Normalize(); + } + + explicit FilePath(const String& pathname) : pathname_(pathname) { + Normalize(); + } + + FilePath& operator=(const FilePath& rhs) { + Set(rhs); + return *this; + } void Set(const FilePath& rhs) { pathname_ = rhs.pathname_; @@ -149,11 +160,30 @@ class FilePath { // This does NOT check that a directory (or file) actually exists. bool IsDirectory() const; + // Returns true if pathname describes a root directory. (Windows has one + // root directory per disk drive.) + bool IsRootDirectory() const; + private: - String pathname_; + // Replaces multiple consecutive separators with a single separator. + // For example, "bar///foo" becomes "bar/foo". Does not eliminate other + // redundancies that might be in a pathname involving "." or "..". + // + // A pathname with multiple consecutive separators may occur either through + // user error or as a result of some scripts or APIs that generate a pathname + // with a trailing separator. On other platforms the same API or script + // may NOT generate a pathname with a trailing "/". Then elsewhere that + // pathname may have another "/" and pathname components added to it, + // without checking for the separator already being there. + // The script language and operating system may allow paths like "foo//bar" + // but some of the functions in FilePath will not handle that correctly. In + // particular, RemoveTrailingPathSeparator() only removes one separator, and + // it is called in CreateDirectoriesRecursively() assuming that it will change + // a pathname from directory syntax (trailing separator) to filename syntax. + + void Normalize(); - // Don't implement operator= because it is banned by the style guide. - FilePath& operator=(const FilePath& rhs); + String pathname_; }; // class FilePath } // namespace internal diff --git a/include/gtest/internal/gtest-port.h b/include/gtest/internal/gtest-port.h index 75429b2..022e670 100644 --- a/include/gtest/internal/gtest-port.h +++ b/include/gtest/internal/gtest-port.h @@ -55,6 +55,9 @@ // is/isn't available (some systems define // ::wstring, which is different to std::wstring). // Leave it undefined to let Google Test define it. +// GTEST_HAS_RTTI - Define it to 1/0 to indicate that RTTI is/isn't +// enabled. Leave it undefined to let Google +// Test define it. // This header defines the following utilities: // @@ -135,6 +138,13 @@ #define GTEST_FLAG_PREFIX "gtest_" #define GTEST_FLAG_PREFIX_UPPER "GTEST_" +// Determines the version of gcc that is used to compile this. +#ifdef __GNUC__ +// 40302 means version 4.3.2. +#define GTEST_GCC_VER_ \ + (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__) +#endif // __GNUC__ + // Determines the platform on which Google Test is compiled. #ifdef __CYGWIN__ #define GTEST_OS_CYGWIN @@ -215,6 +225,42 @@ #include // NOLINT #endif // GTEST_HAS_STD_STRING +// Determines whether RTTI is available. +#ifndef GTEST_HAS_RTTI +// The user didn't tell us whether RTTI is enabled, so we need to +// figure it out. + +#ifdef _MSC_VER + +#ifdef _CPPRTTI // MSVC defines this macro iff RTTI is enabled. +#define GTEST_HAS_RTTI 1 +#else +#define GTEST_HAS_RTTI 0 +#endif // _CPPRTTI + +#elif defined(__GNUC__) + +// Starting with version 4.3.2, gcc defines __GXX_RTTI iff RTTI is enabled. +#if GTEST_GCC_VER_ >= 40302 +#ifdef __GXX_RTTI +#define GTEST_HAS_RTTI 1 +#else +#define GTEST_HAS_RTTI 0 +#endif // __GXX_RTTI +#else +// For gcc versions smaller than 4.3.2, we assume RTTI is enabled. +#define GTEST_HAS_RTTI 1 +#endif // GTEST_GCC_VER >= 40302 + +#else + +// Unknown compiler - assume RTTI is enabled. +#define GTEST_HAS_RTTI 1 + +#endif // _MSC_VER + +#endif // GTEST_HAS_RTTI + // Determines whether to support death tests. #if GTEST_HAS_STD_STRING && defined(GTEST_OS_LINUX) #define GTEST_HAS_DEATH_TEST @@ -284,13 +330,11 @@ // following the argument list: // // Sprocket* AllocateSprocket() GTEST_MUST_USE_RESULT; -#if defined(__GNUC__) \ - && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) \ - && !defined(COMPILER_ICC) +#if defined(__GNUC__) && (GTEST_GCC_VER_ >= 30400) && !defined(COMPILER_ICC) #define GTEST_MUST_USE_RESULT __attribute__ ((warn_unused_result)) #else #define GTEST_MUST_USE_RESULT -#endif // (__GNUC__ > 3 || __GNUC__ == 3 && __GNUC_MINOR__ >= 4) +#endif // __GNUC__ && (GTEST_GCC_VER_ >= 30400) && !COMPILER_ICC namespace testing { diff --git a/include/gtest/internal/gtest-type-util.h b/include/gtest/internal/gtest-type-util.h index 8b56082..815da4b 100644 --- a/include/gtest/internal/gtest-type-util.h +++ b/include/gtest/internal/gtest-type-util.h @@ -71,6 +71,8 @@ struct AssertTypeEq { // GetTypeName() returns a human-readable name of type T. template String GetTypeName() { +#if GTEST_HAS_RTTI + const char* const name = typeid(T).name(); #ifdef __GNUC__ int status = 0; @@ -83,6 +85,10 @@ String GetTypeName() { #else return name; #endif // __GNUC__ + +#else + return ""; +#endif // GTEST_HAS_RTTI } // A unique type used as the default value for the arguments of class diff --git a/include/gtest/internal/gtest-type-util.h.pump b/include/gtest/internal/gtest-type-util.h.pump index f43be5e..4858c7d 100644 --- a/include/gtest/internal/gtest-type-util.h.pump +++ b/include/gtest/internal/gtest-type-util.h.pump @@ -71,6 +71,8 @@ struct AssertTypeEq { // GetTypeName() returns a human-readable name of type T. template String GetTypeName() { +#if GTEST_HAS_RTTI + const char* const name = typeid(T).name(); #ifdef __GNUC__ int status = 0; @@ -83,6 +85,10 @@ String GetTypeName() { #else return name; #endif // __GNUC__ + +#else + return ""; +#endif // GTEST_HAS_RTTI } // A unique type used as the default value for the arguments of class diff --git a/src/gtest-death-test.cc b/src/gtest-death-test.cc index 971c300..fa80087 100644 --- a/src/gtest-death-test.cc +++ b/src/gtest-death-test.cc @@ -416,6 +416,7 @@ bool ForkingDeathTest::Passed(bool status_ok) { << " " << ExitSummary(status_) << "\n"; } break; + case IN_PROGRESS: default: GTEST_LOG(FATAL, "DeathTest::Passed somehow called before conclusion of test"); diff --git a/src/gtest-filepath.cc b/src/gtest-filepath.cc index 2a5be8c..dc0d78f 100644 --- a/src/gtest-filepath.cc +++ b/src/gtest-filepath.cc @@ -161,10 +161,13 @@ bool FilePath::FileOrDirectoryExists() const { // that exists. bool FilePath::DirectoryExists() const { bool result = false; -#ifdef _WIN32 - FilePath removed_sep(this->RemoveTrailingPathSeparator()); +#ifdef GTEST_OS_WINDOWS + // Don't strip off trailing separator if path is a root directory on + // Windows (like "C:\\"). + const FilePath& path(IsRootDirectory() ? *this : + RemoveTrailingPathSeparator()); #ifdef _WIN32_WCE - LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str()); + LPCWSTR unicode = String::AnsiToUtf16(path.c_str()); const DWORD attributes = GetFileAttributes(unicode); delete [] unicode; if ((attributes != kInvalidFileAttributes) && @@ -173,17 +176,32 @@ bool FilePath::DirectoryExists() const { } #else struct _stat file_stat = {}; - result = _stat(removed_sep.c_str(), &file_stat) == 0 && + result = _stat(path.c_str(), &file_stat) == 0 && (_S_IFDIR & file_stat.st_mode) != 0; #endif // _WIN32_WCE #else struct stat file_stat = {}; result = stat(pathname_.c_str(), &file_stat) == 0 && S_ISDIR(file_stat.st_mode); -#endif // _WIN32 +#endif // GTEST_OS_WINDOWS return result; } +// Returns true if pathname describes a root directory. (Windows has one +// root directory per disk drive.) +bool FilePath::IsRootDirectory() const { +#ifdef GTEST_OS_WINDOWS + const char* const name = pathname_.c_str(); + return pathname_.GetLength() == 3 && + ((name[0] >= 'a' && name[0] <= 'z') || + (name[0] >= 'A' && name[0] <= 'Z')) && + name[1] == ':' && + name[2] == kPathSeparator; +#else + return pathname_ == kPathSeparatorString; +#endif +} + // Returns a pathname for a file that does not currently exist. The pathname // will be directory/base_name.extension or // directory/base_name_.extension if directory/base_name.extension @@ -258,5 +276,31 @@ FilePath FilePath::RemoveTrailingPathSeparator() const { : *this; } +// Normalize removes any redundant separators that might be in the pathname. +// For example, "bar///foo" becomes "bar/foo". Does not eliminate other +// redundancies that might be in a pathname involving "." or "..". +void FilePath::Normalize() { + if (pathname_.c_str() == NULL) { + pathname_ = ""; + return; + } + const char* src = pathname_.c_str(); + char* const dest = new char[pathname_.GetLength() + 1]; + char* dest_ptr = dest; + memset(dest_ptr, 0, pathname_.GetLength() + 1); + + while (*src != '\0') { + *dest_ptr++ = *src; + if (*src != kPathSeparator) + src++; + else + while (*src == kPathSeparator) + src++; + } + *dest_ptr = '\0'; + pathname_ = dest; + delete[] dest; +} + } // namespace internal } // namespace testing diff --git a/src/gtest.cc b/src/gtest.cc index 8d2d2a2..f8c1199 100644 --- a/src/gtest.cc +++ b/src/gtest.cc @@ -134,6 +134,10 @@ static const char kUniversalFilter[] = "*"; // The default output file for XML output. static const char kDefaultOutputFile[] = "test_detail.xml"; +// The text used in failure messages to indicate the start of the +// stack trace. +static const char kStackTraceMarker[] = "\nStack trace:\n"; + GTEST_DEFINE_bool( break_on_failure, internal::BoolFromGTestEnv("break_on_failure", false), @@ -200,6 +204,14 @@ GTEST_DEFINE_bool( "True iff " GTEST_NAME " should include internal stack frames when " "printing test failure stack traces."); +// Gets the summary of the failure message by omitting the stack trace +// in it. +internal::String TestPartResult::ExtractSummary(const char* message) { + const char* const stack_trace = strstr(message, kStackTraceMarker); + return stack_trace == NULL ? internal::String(message) : + internal::String(message, stack_trace - message); +} + namespace internal { // GTestIsInitialized() returns true iff the user has initialized @@ -2923,13 +2935,28 @@ internal::String XmlUnitTestResultPrinter::EscapeXml(const char* str, // <-- corresponds to a UnitTest object // <-- corresponds to a TestCase object // <-- corresponds to a TestInfo object -// -// <-- individual assertion failures -// +// ... +// ... +// ... +// <-- individual assertion failures // // // +namespace internal { + +// Formats the given time in milliseconds as seconds. The returned +// C-string is owned by this function and cannot be released by the +// caller. Calling the function again invalidates the previous +// result. +const char* FormatTimeInMillisAsSeconds(TimeInMillis ms) { + static String str; + str = (Message() << (ms/1000.0)).GetString(); + return str.c_str(); +} + +} // namespace internal + // Prints an XML representation of a TestInfo object. // TODO(wan): There is also value in printing properties with the plain printer. void XmlUnitTestResultPrinter::PrintXmlTestInfo(FILE* out, @@ -2942,7 +2969,7 @@ void XmlUnitTestResultPrinter::PrintXmlTestInfo(FILE* out, "classname=\"%s\"%s", EscapeXmlAttribute(test_info->name()).c_str(), test_info->should_run() ? "run" : "notrun", - internal::StreamableToString(result->elapsed_time()).c_str(), + internal::FormatTimeInMillisAsSeconds(result->elapsed_time()), EscapeXmlAttribute(test_case_name).c_str(), TestPropertiesAsXmlAttributes(result).c_str()); @@ -2958,8 +2985,9 @@ void XmlUnitTestResultPrinter::PrintXmlTestInfo(FILE* out, if (++failures == 1) fprintf(out, ">\n"); fprintf(out, - " \n", - EscapeXmlAttribute(message.c_str()).c_str()); + " " + "\n", + EscapeXmlAttribute(part.summary()).c_str(), message.c_str()); } } @@ -2981,7 +3009,7 @@ void XmlUnitTestResultPrinter::PrintXmlTestCase(FILE* out, test_case->disabled_test_count()); fprintf(out, "errors=\"0\" time=\"%s\">\n", - internal::StreamableToString(test_case->elapsed_time()).c_str()); + internal::FormatTimeInMillisAsSeconds(test_case->elapsed_time())); for (const internal::ListNode* info_node = test_case->test_info_list().Head(); info_node != NULL; @@ -3002,7 +3030,7 @@ void XmlUnitTestResultPrinter::PrintXmlUnitTest(FILE* out, impl->total_test_count(), impl->failed_test_count(), impl->disabled_test_count(), - internal::StreamableToString(impl->elapsed_time()).c_str()); + internal::FormatTimeInMillisAsSeconds(impl->elapsed_time())); fprintf(out, "name=\"AllTests\">\n"); for (const internal::ListNode* case_node = impl->test_cases()->Head(); @@ -3153,7 +3181,7 @@ void UnitTest::AddTestPartResult(TestPartResultType result_type, } if (os_stack_trace.c_str() != NULL && !os_stack_trace.empty()) { - msg << "\nStack trace:\n" << os_stack_trace; + msg << kStackTraceMarker << os_stack_trace; } const TestPartResult result = diff --git a/test/gtest-filepath_test.cc b/test/gtest-filepath_test.cc index 603427c..ae5e3fb 100644 --- a/test/gtest-filepath_test.cc +++ b/test/gtest-filepath_test.cc @@ -123,8 +123,6 @@ TEST(IsEmptyTest, ReturnsFalseForNonEmptyPath) { EXPECT_FALSE(FilePath("a\\b\\").IsEmpty()); } -// FilePath's functions used by UnitTestOptions::GetOutputFile. - // RemoveDirectoryName "" -> "" TEST(RemoveDirectoryNameTest, WhenEmptyName) { EXPECT_STREQ("", FilePath("").RemoveDirectoryName().c_str()); @@ -257,6 +255,110 @@ TEST(RemoveTrailingPathSeparatorTest, ShouldReturnUnmodified) { FilePath("foo" PATH_SEP "bar").RemoveTrailingPathSeparator().c_str()); } +TEST(DirectoryTest, RootDirectoryExists) { +#ifdef GTEST_OS_WINDOWS // We are on Windows. + char current_drive[_MAX_PATH]; + current_drive[0] = _getdrive() + 'A' - 1; + current_drive[1] = ':'; + current_drive[2] = '\\'; + current_drive[3] = '\0'; + EXPECT_TRUE(FilePath(current_drive).DirectoryExists()); +#else + EXPECT_TRUE(FilePath("/").DirectoryExists()); +#endif // GTEST_OS_WINDOWS +} + +#ifdef GTEST_OS_WINDOWS +TEST(DirectoryTest, RootOfWrongDriveDoesNotExists) { + const int saved_drive_ = _getdrive(); + // Find a drive that doesn't exist. Start with 'Z' to avoid common ones. + for (char drive = 'Z'; drive >= 'A'; drive--) + if (_chdrive(drive - 'A' + 1) == -1) { + char non_drive[_MAX_PATH]; + non_drive[0] = drive; + non_drive[1] = ':'; + non_drive[2] = '\\'; + non_drive[3] = '\0'; + EXPECT_FALSE(FilePath(non_drive).DirectoryExists()); + break; + } + _chdrive(saved_drive_); +} +#endif // GTEST_OS_WINDOWS + +TEST(DirectoryTest, EmptyPathDirectoryDoesNotExist) { + EXPECT_FALSE(FilePath("").DirectoryExists()); +} + +TEST(DirectoryTest, CurrentDirectoryExists) { +#ifdef GTEST_OS_WINDOWS // We are on Windows. +#ifndef _WIN32_CE // Windows CE doesn't have a current directory. + EXPECT_TRUE(FilePath(".").DirectoryExists()); + EXPECT_TRUE(FilePath(".\\").DirectoryExists()); +#endif // _WIN32_CE +#else + EXPECT_TRUE(FilePath(".").DirectoryExists()); + EXPECT_TRUE(FilePath("./").DirectoryExists()); +#endif // GTEST_OS_WINDOWS +} + +TEST(NormalizeTest, NullStringsEqualEmptyDirectory) { + EXPECT_STREQ("", FilePath(NULL).c_str()); + EXPECT_STREQ("", FilePath(String(NULL)).c_str()); +} + +// "foo/bar" == foo//bar" == "foo///bar" +TEST(NormalizeTest, MultipleConsecutiveSepaparatorsInMidstring) { + EXPECT_STREQ("foo" PATH_SEP "bar", + FilePath("foo" PATH_SEP "bar").c_str()); + EXPECT_STREQ("foo" PATH_SEP "bar", + FilePath("foo" PATH_SEP PATH_SEP "bar").c_str()); + EXPECT_STREQ("foo" PATH_SEP "bar", + FilePath("foo" PATH_SEP PATH_SEP PATH_SEP "bar").c_str()); +} + +// "/bar" == //bar" == "///bar" +TEST(NormalizeTest, MultipleConsecutiveSepaparatorsAtStringStart) { + EXPECT_STREQ(PATH_SEP "bar", + FilePath(PATH_SEP "bar").c_str()); + EXPECT_STREQ(PATH_SEP "bar", + FilePath(PATH_SEP PATH_SEP "bar").c_str()); + EXPECT_STREQ(PATH_SEP "bar", + FilePath(PATH_SEP PATH_SEP PATH_SEP "bar").c_str()); +} + +// "foo/" == foo//" == "foo///" +TEST(NormalizeTest, MultipleConsecutiveSepaparatorsAtStringEnd) { + EXPECT_STREQ("foo" PATH_SEP, + FilePath("foo" PATH_SEP).c_str()); + EXPECT_STREQ("foo" PATH_SEP, + FilePath("foo" PATH_SEP PATH_SEP).c_str()); + EXPECT_STREQ("foo" PATH_SEP, + FilePath("foo" PATH_SEP PATH_SEP PATH_SEP).c_str()); +} + +TEST(AssignmentOperatorTest, DefaultAssignedToNonDefault) { + FilePath default_path; + FilePath non_default_path("path"); + non_default_path = default_path; + EXPECT_STREQ("", non_default_path.c_str()); + EXPECT_STREQ("", default_path.c_str()); // RHS var is unchanged. +} + +TEST(AssignmentOperatorTest, NonDefaultAssignedToDefault) { + FilePath non_default_path("path"); + FilePath default_path; + default_path = non_default_path; + EXPECT_STREQ("path", default_path.c_str()); + EXPECT_STREQ("path", non_default_path.c_str()); // RHS var is unchanged. +} + +TEST(AssignmentOperatorTest, ConstAssignedToNonConst) { + const FilePath const_default_path("const_path"); + FilePath non_default_path("path"); + non_default_path = const_default_path; + EXPECT_STREQ("const_path", non_default_path.c_str()); +} class DirectoryCreationTest : public Test { protected: diff --git a/test/gtest_unittest.cc b/test/gtest_unittest.cc index abc337f..a9281ce 100644 --- a/test/gtest_unittest.cc +++ b/test/gtest_unittest.cc @@ -58,10 +58,12 @@ namespace testing { namespace internal { +const char* FormatTimeInMillisAsSeconds(TimeInMillis ms); bool ParseInt32Flag(const char* str, const char* flag, Int32* value); } // namespace internal } // namespace testing +using testing::internal::FormatTimeInMillisAsSeconds; using testing::internal::ParseInt32Flag; namespace testing { @@ -118,6 +120,28 @@ using testing::internal::WideStringToUtf8; // This line tests that we can define tests in an unnamed namespace. namespace { +// Tests FormatTimeInMillisAsSeconds(). + +TEST(FormatTimeInMillisAsSecondsTest, FormatsZero) { + EXPECT_STREQ("0", FormatTimeInMillisAsSeconds(0)); +} + +TEST(FormatTimeInMillisAsSecondsTest, FormatsPositiveNumber) { + EXPECT_STREQ("0.003", FormatTimeInMillisAsSeconds(3)); + EXPECT_STREQ("0.01", FormatTimeInMillisAsSeconds(10)); + EXPECT_STREQ("0.2", FormatTimeInMillisAsSeconds(200)); + EXPECT_STREQ("1.2", FormatTimeInMillisAsSeconds(1200)); + EXPECT_STREQ("3", FormatTimeInMillisAsSeconds(3000)); +} + +TEST(FormatTimeInMillisAsSecondsTest, FormatsNegativeNumber) { + EXPECT_STREQ("-0.003", FormatTimeInMillisAsSeconds(-3)); + EXPECT_STREQ("-0.01", FormatTimeInMillisAsSeconds(-10)); + EXPECT_STREQ("-0.2", FormatTimeInMillisAsSeconds(-200)); + EXPECT_STREQ("-1.2", FormatTimeInMillisAsSeconds(-1200)); + EXPECT_STREQ("-3", FormatTimeInMillisAsSeconds(-3000)); +} + #ifndef __SYMBIAN32__ // NULL testing does not work with Symbian compilers. diff --git a/test/gtest_xml_outfiles_test.py b/test/gtest_xml_outfiles_test.py index c76e1f7..b83df77 100755 --- a/test/gtest_xml_outfiles_test.py +++ b/test/gtest_xml_outfiles_test.py @@ -122,9 +122,9 @@ class GTestXMLOutFilesTest(gtest_xml_test_utils.GTestXMLTestCase): actual = minidom.parse(output_file1) else: actual = minidom.parse(output_file2) - self._NormalizeXml(actual.documentElement) - self._AssertEquivalentElements(expected.documentElement, - actual.documentElement) + self.NormalizeXml(actual.documentElement) + self.AssertEquivalentNodes(expected.documentElement, + actual.documentElement) expected.unlink() actual.unlink() diff --git a/test/gtest_xml_output_unittest.py b/test/gtest_xml_output_unittest.py index 013e739..7206006 100755 --- a/test/gtest_xml_output_unittest.py +++ b/test/gtest_xml_output_unittest.py @@ -54,14 +54,20 @@ EXPECTED_NON_EMPTY_XML = """ - + - - + + @@ -160,14 +166,14 @@ class GTestXMLOutputUnitTest(gtest_xml_test_utils.GTestXMLTestCase): expected = minidom.parseString(expected_xml) actual = minidom.parse(xml_path) - self._NormalizeXml(actual.documentElement) - self._AssertEquivalentElements(expected.documentElement, - actual.documentElement) + self.NormalizeXml(actual.documentElement) + self.AssertEquivalentNodes(expected.documentElement, + actual.documentElement) expected.unlink() actual .unlink() if __name__ == '__main__': - os.environ['GTEST_STACK_TRACE_DEPTH'] = '0' + os.environ['GTEST_STACK_TRACE_DEPTH'] = '1' gtest_test_utils.Main() diff --git a/test/gtest_xml_test_utils.py b/test/gtest_xml_test_utils.py index c2ea9c1..5694dff 100755 --- a/test/gtest_xml_test_utils.py +++ b/test/gtest_xml_test_utils.py @@ -47,27 +47,35 @@ class GTestXMLTestCase(unittest.TestCase): """ - def _AssertEquivalentElements(self, expected_element, actual_element): + def AssertEquivalentNodes(self, expected_node, actual_node): """ - Asserts that actual_element (a DOM element object) is equivalent to - expected_element (another DOM element object), in that it meets all - of the following conditions: - * It has the same tag name as expected_element. - * It has the same set of attributes as expected_element, each with - the same value as the corresponding attribute of expected_element. - An exception is any attribute named "time", which need only be - convertible to a long integer. - * Each child element is equivalent to a child element of - expected_element. Child elements are matched according to an - attribute that varies depending on the element; "name" for - and elements, "message" for - elements. This matching is necessary because child elements are - not guaranteed to be ordered in any particular way. + Asserts that actual_node (a DOM node object) is equivalent to + expected_node (another DOM node object), in that either both of + them are CDATA nodes and have the same value, or both are DOM + elements and actual_node meets all of the following conditions: + + * It has the same tag name as expected_node. + * It has the same set of attributes as expected_node, each with + the same value as the corresponding attribute of expected_node. + An exception is any attribute named "time", which needs only be + convertible to a floating-point number. + * It has an equivalent set of child nodes (including elements and + CDATA sections) as expected_node. Note that we ignore the + order of the children as they are not guaranteed to be in any + particular order. """ - self.assertEquals(expected_element.tagName, actual_element.tagName) - expected_attributes = expected_element.attributes - actual_attributes = actual_element .attributes + if expected_node.nodeType == Node.CDATA_SECTION_NODE: + self.assertEquals(Node.CDATA_SECTION_NODE, actual_node.nodeType) + self.assertEquals(expected_node.nodeValue, actual_node.nodeValue) + return + + self.assertEquals(Node.ELEMENT_NODE, actual_node.nodeType) + self.assertEquals(Node.ELEMENT_NODE, expected_node.nodeType) + self.assertEquals(expected_node.tagName, actual_node.tagName) + + expected_attributes = expected_node.attributes + actual_attributes = actual_node .attributes self.assertEquals(expected_attributes.length, actual_attributes.length) for i in range(expected_attributes.length): expected_attr = expected_attributes.item(i) @@ -75,13 +83,13 @@ class GTestXMLTestCase(unittest.TestCase): self.assert_(actual_attr is not None) self.assertEquals(expected_attr.value, actual_attr.value) - expected_child = self._GetChildElements(expected_element) - actual_child = self._GetChildElements(actual_element) - self.assertEquals(len(expected_child), len(actual_child)) - for child_id, element in expected_child.iteritems(): - self.assert_(child_id in actual_child, - '<%s> is not in <%s>' % (child_id, actual_child)) - self._AssertEquivalentElements(element, actual_child[child_id]) + expected_children = self._GetChildren(expected_node) + actual_children = self._GetChildren(actual_node) + self.assertEquals(len(expected_children), len(actual_children)) + for child_id, child in expected_children.iteritems(): + self.assert_(child_id in actual_children, + '<%s> is not in <%s>' % (child_id, actual_children)) + self.AssertEquivalentNodes(child, actual_children[child_id]) identifying_attribute = { "testsuite": "name", @@ -89,18 +97,20 @@ class GTestXMLTestCase(unittest.TestCase): "failure": "message", } - def _GetChildElements(self, element): + def _GetChildren(self, element): """ - Fetches all of the Element type child nodes of element, a DOM - Element object. Returns them as the values of a dictionary keyed by - the value of one of the node's attributes. For and - elements, the identifying attribute is "name"; for - elements, it is "message". An exception is raised if - any element other than the above three is encountered, if two - child elements with the same identifying attributes are encountered, - or if any other type of node is encountered, other than Text nodes - containing only whitespace. + Fetches all of the child nodes of element, a DOM Element object. + Returns them as the values of a dictionary keyed by the IDs of the + children. For and elements, the ID is the + value of their "name" attribute; for elements, it is the + value of the "message" attribute; for CDATA section node, it is + "detail". An exception is raised if any element other than the + above four is encountered, if two child elements with the same + identifying attributes are encountered, or if any other type of + node is encountered, other than Text nodes containing only + whitespace. """ + children = {} for child in element.childNodes: if child.nodeType == Node.ELEMENT_NODE: @@ -111,11 +121,14 @@ class GTestXMLTestCase(unittest.TestCase): children[childID] = child elif child.nodeType == Node.TEXT_NODE: self.assert_(child.nodeValue.isspace()) + elif child.nodeType == Node.CDATA_SECTION_NODE: + self.assert_("detail" not in children) + children["detail"] = child else: self.fail("Encountered unexpected node type %d" % child.nodeType) return children - def _NormalizeXml(self, element): + def NormalizeXml(self, element): """ Normalizes Google Test's XML output to eliminate references to transient information that may change from run to run. @@ -126,13 +139,20 @@ class GTestXMLTestCase(unittest.TestCase): * The line number reported in the first line of the "message" attribute of elements is replaced with a single asterisk. * The directory names in file paths are removed. + * The stack traces are removed. """ + if element.tagName in ("testsuite", "testcase"): time = element.getAttributeNode("time") - time.value = re.sub(r"^\d+$", "*", time.value) + time.value = re.sub(r"^\d+(\.\d+)?$", "*", time.value) elif element.tagName == "failure": - message = element.getAttributeNode("message") - message.value = re.sub(r"^.*/(.*:)\d+\n", "\\1*\n", message.value) + for child in element.childNodes: + if child.nodeType == Node.CDATA_SECTION_NODE: + # Removes the source line number. + cdata = re.sub(r"^.*/(.*:)\d+\n", "\\1*\n", child.nodeValue) + # Removes the actual stack trace. + child.nodeValue = re.sub(r"\nStack trace:\n(.|\n)*", + "", cdata) for child in element.childNodes: if child.nodeType == Node.ELEMENT_NODE: - self._NormalizeXml(child) + self.NormalizeXml(child) -- cgit v0.12