diff --git a/src/catch2/reporters/catch_reporter_junit.cpp b/src/catch2/reporters/catch_reporter_junit.cpp index 8e8da3ca..71a93051 100644 --- a/src/catch2/reporters/catch_reporter_junit.cpp +++ b/src/catch2/reporters/catch_reporter_junit.cpp @@ -17,6 +17,7 @@ #include #include #include +#include namespace Catch { @@ -54,6 +55,17 @@ namespace Catch { } return std::string(); } + + // Formats the duration in seconds to 3 decimal places. + // This is done because some genius defined Maven Surefire schema + // in a way that only accepts 3 decimal places, and tools like + // Jenkins use that schema for validation JUnit reporter output. + std::string formatDuration( double seconds ) { + ReusableStringStream rss; + rss << std::fixed << std::setprecision( 3 ) << seconds; + return rss.str(); + } + } // anonymous namespace JunitReporter::JunitReporter( ReporterConfig const& _config ) @@ -111,7 +123,7 @@ namespace Catch { if( m_config->showDurations() == ShowDurations::Never ) xml.writeAttribute( "time"_sr, ""_sr ); else - xml.writeAttribute( "time"_sr, suiteTime ); + xml.writeAttribute( "time"_sr, formatDuration( suiteTime ) ); xml.writeAttribute( "timestamp"_sr, getCurrentTimestamp() ); // Write properties @@ -178,7 +190,7 @@ namespace Catch { xml.writeAttribute( "classname"_sr, className ); xml.writeAttribute( "name"_sr, name ); } - xml.writeAttribute( "time"_sr, ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + xml.writeAttribute( "time"_sr, formatDuration( sectionNode.stats.durationInSeconds ) ); // This is not ideal, but it should be enough to mimic gtest's // junit output. // Ideally the JUnit reporter would also handle `skipTest` diff --git a/tools/scripts/approvalTests.py b/tools/scripts/approvalTests.py index 73e9b832..1bef3e52 100755 --- a/tools/scripts/approvalTests.py +++ b/tools/scripts/approvalTests.py @@ -29,7 +29,11 @@ filelocParser = re.compile(r''' lineNumberParser = re.compile(r' line="[0-9]*"') hexParser = re.compile(r'\b(0[xX][0-9a-fA-F]+)\b') durationsParser = re.compile(r' time="[0-9]*\.[0-9]*"') -sonarqubeDurationParser = re.compile(r''' duration=["'][0-9]+["']''') +# Note: junit must serialize time with 3 (or or less) decimal places +# before generalizing this parser, make sure that this is checked +# in other places too. +junitDurationsParser = re.compile(r' time="[0-9]+\.[0-9]{3}"') +durationParser = re.compile(r''' duration=['"][0-9]+['"]''') timestampsParser = re.compile(r'\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}Z') versionParser = re.compile(r'Catch v[0-9]+\.[0-9]+\.[0-9]+(-\w*\.[0-9]+)?') nullParser = re.compile(r'\b(__null|nullptr)\b') @@ -143,8 +147,8 @@ def filterLine(line, isCompact): line = hexParser.sub("0x", line) # strip durations and timestamps - line = durationsParser.sub(' time="{duration}"', line) - line = sonarqubeDurationParser.sub(' duration="{duration}"', line) + line = junitDurationsParser.sub(' time="{duration}"', line) + line = durationParser.sub(' duration="{duration}"', line) line = timestampsParser.sub('{iso8601-timestamp}', line) line = specialCaseParser.sub('file:\g<1>', line) line = errnoParser.sub('errno', line)