From de67278e145bf0d5d4ceb3e2bdcf42fda5b4876d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Sat, 22 May 2021 23:41:58 +0200 Subject: [PATCH] JUnit reporter uses only 3 decimal places when reporting durations We used to use whatever precision we ended up having from C++'s stdlib. However, some relatively popular tools, like Jenkins, use Maven SureFire XML schema to validate JUnit test reports, and Maven SureFire schema requires the duration to have at most 3 decimal places. For compatibility, the JUnit reporter will now respect this limitation. Closes #2221 --- src/catch2/reporters/catch_reporter_junit.cpp | 16 ++++++++++++++-- tools/scripts/approvalTests.py | 10 +++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) 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)