From 581c46249acf8389e90b37f2d920ba522038a190 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 --- include/reporters/catch_reporter_junit.cpp | 16 ++++++++++++++-- scripts/approvalTests.py | 7 +++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/include/reporters/catch_reporter_junit.cpp b/include/reporters/catch_reporter_junit.cpp index 31f0802f..e793fbc2 100644 --- a/include/reporters/catch_reporter_junit.cpp +++ b/include/reporters/catch_reporter_junit.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace Catch { @@ -56,6 +57,17 @@ namespace Catch { return it->substr(1); 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 ) @@ -125,7 +137,7 @@ namespace Catch { if( m_config->showDurations() == ShowDurations::Never ) xml.writeAttribute( "time", "" ); else - xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "time", formatDuration( suiteTime ) ); xml.writeAttribute( "timestamp", getCurrentTimestamp() ); // Write properties if there are any @@ -192,7 +204,7 @@ namespace Catch { xml.writeAttribute( "classname", className ); xml.writeAttribute( "name", name ); } - xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); + xml.writeAttribute( "time", 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/scripts/approvalTests.py b/scripts/approvalTests.py index 838a1a78..9b781911 100755 --- a/scripts/approvalTests.py +++ b/scripts/approvalTests.py @@ -28,7 +28,10 @@ filelocParser = re.compile(r''' ''', re.VERBOSE) 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]*"') +# 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}"') sonarqubeDurationParser = 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]+(-develop\.[0-9]+)?') @@ -138,7 +141,7 @@ def filterLine(line, isCompact): line = hexParser.sub("0x", line) # strip durations and timestamps - line = durationsParser.sub(' time="{duration}"', line) + line = junitDurationsParser.sub(' time="{duration}"', line) line = sonarqubeDurationParser.sub(' duration="{duration}"', line) line = timestampsParser.sub('{iso8601-timestamp}', line) line = specialCaseParser.sub('file:\g<1>', line)