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
This commit is contained in:
Martin Hořeňovský 2021-05-22 23:41:58 +02:00
parent 1d9696d22d
commit de67278e14
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
2 changed files with 21 additions and 5 deletions

View File

@ -17,6 +17,7 @@
#include <cassert> #include <cassert>
#include <ctime> #include <ctime>
#include <algorithm> #include <algorithm>
#include <iomanip>
namespace Catch { namespace Catch {
@ -54,6 +55,17 @@ namespace Catch {
} }
return std::string(); 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 } // anonymous namespace
JunitReporter::JunitReporter( ReporterConfig const& _config ) JunitReporter::JunitReporter( ReporterConfig const& _config )
@ -111,7 +123,7 @@ namespace Catch {
if( m_config->showDurations() == ShowDurations::Never ) if( m_config->showDurations() == ShowDurations::Never )
xml.writeAttribute( "time"_sr, ""_sr ); xml.writeAttribute( "time"_sr, ""_sr );
else else
xml.writeAttribute( "time"_sr, suiteTime ); xml.writeAttribute( "time"_sr, formatDuration( suiteTime ) );
xml.writeAttribute( "timestamp"_sr, getCurrentTimestamp() ); xml.writeAttribute( "timestamp"_sr, getCurrentTimestamp() );
// Write properties // Write properties
@ -178,7 +190,7 @@ namespace Catch {
xml.writeAttribute( "classname"_sr, className ); xml.writeAttribute( "classname"_sr, className );
xml.writeAttribute( "name"_sr, name ); 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 // This is not ideal, but it should be enough to mimic gtest's
// junit output. // junit output.
// Ideally the JUnit reporter would also handle `skipTest` // Ideally the JUnit reporter would also handle `skipTest`

View File

@ -29,7 +29,11 @@ filelocParser = re.compile(r'''
lineNumberParser = re.compile(r' line="[0-9]*"') lineNumberParser = re.compile(r' line="[0-9]*"')
hexParser = re.compile(r'\b(0[xX][0-9a-fA-F]+)\b') hexParser = re.compile(r'\b(0[xX][0-9a-fA-F]+)\b')
durationsParser = re.compile(r' time="[0-9]*\.[0-9]*"') 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') 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]+)?') versionParser = re.compile(r'Catch v[0-9]+\.[0-9]+\.[0-9]+(-\w*\.[0-9]+)?')
nullParser = re.compile(r'\b(__null|nullptr)\b') nullParser = re.compile(r'\b(__null|nullptr)\b')
@ -143,8 +147,8 @@ def filterLine(line, isCompact):
line = hexParser.sub("0x<hex digits>", line) line = hexParser.sub("0x<hex digits>", line)
# strip durations and timestamps # strip durations and timestamps
line = durationsParser.sub(' time="{duration}"', line) line = junitDurationsParser.sub(' time="{duration}"', line)
line = sonarqubeDurationParser.sub(' duration="{duration}"', line) line = durationParser.sub(' duration="{duration}"', line)
line = timestampsParser.sub('{iso8601-timestamp}', line) line = timestampsParser.sub('{iso8601-timestamp}', line)
line = specialCaseParser.sub('file:\g<1>', line) line = specialCaseParser.sub('file:\g<1>', line)
line = errnoParser.sub('errno', line) line = errnoParser.sub('errno', line)