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.
pull/496/merge
shiqian 16 years ago
parent e79c3ccb73
commit 64cdcb69b2
  1. 9
      include/gtest/gtest-spi.h
  2. 40
      include/gtest/internal/gtest-filepath.h
  3. 52
      include/gtest/internal/gtest-port.h
  4. 6
      include/gtest/internal/gtest-type-util.h
  5. 6
      include/gtest/internal/gtest-type-util.h.pump
  6. 1
      src/gtest-death-test.cc
  7. 54
      src/gtest-filepath.cc
  8. 46
      src/gtest.cc
  9. 106
      test/gtest-filepath_test.cc
  10. 24
      test/gtest_unittest.cc
  11. 6
      test/gtest_xml_outfiles_test.py
  12. 20
      test/gtest_xml_output_unittest.py
  13. 100
      test/gtest_xml_test_utils.py

@ -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.
};

@ -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

@ -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 <strstream> // 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 {

@ -71,6 +71,8 @@ struct AssertTypeEq<T, T> {
// GetTypeName<T>() returns a human-readable name of type T.
template <typename T>
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 "<type>";
#endif // GTEST_HAS_RTTI
}
// A unique type used as the default value for the arguments of class

@ -71,6 +71,8 @@ struct AssertTypeEq<T, T> {
// GetTypeName<T>() returns a human-readable name of type T.
template <typename T>
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 "<type>";
#endif // GTEST_HAS_RTTI
}
// A unique type used as the default value for the arguments of class

@ -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");

@ -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_<number>.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

@ -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,
// <testsuite name="AllTests"> <-- corresponds to a UnitTest object
// <testsuite name="testcase-name"> <-- corresponds to a TestCase object
// <testcase name="test-name"> <-- corresponds to a TestInfo object
// <failure message="..." />
// <failure message="..." /> <-- individual assertion failures
// <failure message="..." />
// <failure message="...">...</failure>
// <failure message="...">...</failure>
// <failure message="...">...</failure>
// <-- individual assertion failures
// </testcase>
// </testsuite>
// </testsuite>
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,
" <failure message=\"%s\" type=\"\"/>\n",
EscapeXmlAttribute(message.c_str()).c_str());
" <failure message=\"%s\" type=\"\"><![CDATA[%s]]>"
"</failure>\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<TestInfo*>* 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<TestCase*>* 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 =

@ -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:

@ -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.

@ -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()

@ -54,14 +54,20 @@ EXPECTED_NON_EMPTY_XML = """<?xml version="1.0" encoding="UTF-8"?>
</testsuite>
<testsuite name="FailedTest" tests="1" failures="1" disabled="0" errors="0" time="*">
<testcase name="Fails" status="run" time="*" classname="FailedTest">
<failure message="gtest_xml_output_unittest_.cc:*&#x0A;Value of: 2&#x0A;Expected: 1" type=""/>
<failure message="Value of: 2&#x0A;Expected: 1" type=""><![CDATA[gtest_xml_output_unittest_.cc:*
Value of: 2
Expected: 1]]></failure>
</testcase>
</testsuite>
<testsuite name="MixedResultTest" tests="3" failures="1" disabled="1" errors="0" time="*">
<testcase name="Succeeds" status="run" time="*" classname="MixedResultTest"/>
<testcase name="Fails" status="run" time="*" classname="MixedResultTest">
<failure message="gtest_xml_output_unittest_.cc:*&#x0A;Value of: 2&#x0A;Expected: 1" type=""/>
<failure message="gtest_xml_output_unittest_.cc:*&#x0A;Value of: 3&#x0A;Expected: 2" type=""/>
<failure message="Value of: 2&#x0A;Expected: 1" type=""><![CDATA[gtest_xml_output_unittest_.cc:*
Value of: 2
Expected: 1]]></failure>
<failure message="Value of: 3&#x0A;Expected: 2" type=""><![CDATA[gtest_xml_output_unittest_.cc:*
Value of: 3
Expected: 2]]></failure>
</testcase>
<testcase name="DISABLED_test" status="notrun" time="*" classname="MixedResultTest"/>
</testsuite>
@ -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()

@ -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
<testsuite> and <testcase> elements, "message" for <failure>
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 <testsuite> and
<testcase> elements, the identifying attribute is "name"; for
<failure> 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 <testsuite> and <testcase> elements, the ID is the
value of their "name" attribute; for <failure> 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 <failure> 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)

Loading…
Cancel
Save