catch_reporter_xml.cpp
1 2 // Copyright Catch2 Authors 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE.txt or copy at 5 // https://www.boost.org/LICENSE_1_0.txt) 6 7 // SPDX-License-Identifier: BSL-1.0 8 #include <catch2/reporters/catch_reporter_xml.hpp> 9 10 #include <catch2/reporters/catch_reporter_helpers.hpp> 11 #include <catch2/interfaces/catch_interfaces_config.hpp> 12 #include <catch2/catch_test_spec.hpp> 13 #include <catch2/internal/catch_string_manip.hpp> 14 #include <catch2/internal/catch_list.hpp> 15 #include <catch2/catch_test_case_info.hpp> 16 #include <catch2/internal/catch_move_and_forward.hpp> 17 #include <catch2/catch_version.hpp> 18 19 #if defined(_MSC_VER) 20 #pragma warning(push) 21 #pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch 22 // Note that 4062 (not all labels are handled 23 // and default is missing) is enabled 24 #endif 25 26 namespace Catch { 27 XmlReporter::XmlReporter( ReporterConfig&& _config ) 28 : StreamingReporterBase( CATCH_MOVE(_config) ), 29 m_xml(m_stream) 30 { 31 m_preferences.shouldRedirectStdOut = true; 32 m_preferences.shouldReportAllAssertions = true; 33 } 34 35 XmlReporter::~XmlReporter() = default; 36 37 std::string XmlReporter::getDescription() { 38 return "Reports test results as an XML document"; 39 } 40 41 std::string XmlReporter::getStylesheetRef() const { 42 return std::string(); 43 } 44 45 void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { 46 m_xml 47 .writeAttribute( "filename"_sr, sourceInfo.file ) 48 .writeAttribute( "line"_sr, sourceInfo.line ); 49 } 50 51 void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { 52 StreamingReporterBase::testRunStarting( testInfo ); 53 std::string stylesheetRef = getStylesheetRef(); 54 if( !stylesheetRef.empty() ) 55 m_xml.writeStylesheetRef( stylesheetRef ); 56 m_xml.startElement("Catch2TestRun") 57 .writeAttribute("name"_sr, m_config->name()) 58 .writeAttribute("rng-seed"_sr, m_config->rngSeed()) 59 .writeAttribute("xml-format-version"_sr, 3) 60 .writeAttribute("catch2-version"_sr, libraryVersion()); 61 if ( m_config->testSpec().hasFilters() ) { 62 m_xml.writeAttribute( "filters"_sr, m_config->testSpec() ); 63 } 64 } 65 66 void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { 67 StreamingReporterBase::testCaseStarting(testInfo); 68 m_xml.startElement( "TestCase" ) 69 .writeAttribute( "name"_sr, trim( StringRef(testInfo.name) ) ) 70 .writeAttribute( "tags"_sr, testInfo.tagsAsString() ); 71 72 writeSourceInfo( testInfo.lineInfo ); 73 74 if ( m_config->showDurations() == ShowDurations::Always ) 75 m_testCaseTimer.start(); 76 m_xml.ensureTagClosed(); 77 } 78 79 void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { 80 StreamingReporterBase::sectionStarting( sectionInfo ); 81 if( m_sectionDepth++ > 0 ) { 82 m_xml.startElement( "Section" ) 83 .writeAttribute( "name"_sr, trim( StringRef(sectionInfo.name) ) ); 84 writeSourceInfo( sectionInfo.lineInfo ); 85 m_xml.ensureTagClosed(); 86 } 87 } 88 89 void XmlReporter::assertionStarting( AssertionInfo const& ) { } 90 91 void XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { 92 93 AssertionResult const& result = assertionStats.assertionResult; 94 95 bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); 96 97 if( includeResults || result.getResultType() == ResultWas::Warning ) { 98 // Print any info messages in <Info> tags. 99 for( auto const& msg : assertionStats.infoMessages ) { 100 if( msg.type == ResultWas::Info && includeResults ) { 101 auto t = m_xml.scopedElement( "Info" ); 102 writeSourceInfo( msg.lineInfo ); 103 t.writeText( msg.message ); 104 } else if ( msg.type == ResultWas::Warning ) { 105 auto t = m_xml.scopedElement( "Warning" ); 106 writeSourceInfo( msg.lineInfo ); 107 t.writeText( msg.message ); 108 } 109 } 110 } 111 112 // Drop out if result was successful but we're not printing them. 113 if ( !includeResults && result.getResultType() != ResultWas::Warning && 114 result.getResultType() != ResultWas::ExplicitSkip ) { 115 return; 116 } 117 118 // Print the expression if there is one. 119 if( result.hasExpression() ) { 120 m_xml.startElement( "Expression" ) 121 .writeAttribute( "success"_sr, result.succeeded() ) 122 .writeAttribute( "type"_sr, result.getTestMacroName() ); 123 124 writeSourceInfo( result.getSourceInfo() ); 125 126 m_xml.scopedElement( "Original" ) 127 .writeText( result.getExpression() ); 128 m_xml.scopedElement( "Expanded" ) 129 .writeText( result.getExpandedExpression() ); 130 } 131 132 // And... Print a result applicable to each result type. 133 switch( result.getResultType() ) { 134 case ResultWas::ThrewException: 135 m_xml.startElement( "Exception" ); 136 writeSourceInfo( result.getSourceInfo() ); 137 m_xml.writeText( result.getMessage() ); 138 m_xml.endElement(); 139 break; 140 case ResultWas::FatalErrorCondition: 141 m_xml.startElement( "FatalErrorCondition" ); 142 writeSourceInfo( result.getSourceInfo() ); 143 m_xml.writeText( result.getMessage() ); 144 m_xml.endElement(); 145 break; 146 case ResultWas::Info: 147 m_xml.scopedElement( "Info" ) 148 .writeText( result.getMessage() ); 149 break; 150 case ResultWas::Warning: 151 // Warning will already have been written 152 break; 153 case ResultWas::ExplicitFailure: 154 m_xml.startElement( "Failure" ); 155 writeSourceInfo( result.getSourceInfo() ); 156 m_xml.writeText( result.getMessage() ); 157 m_xml.endElement(); 158 break; 159 case ResultWas::ExplicitSkip: 160 m_xml.startElement( "Skip" ); 161 writeSourceInfo( result.getSourceInfo() ); 162 m_xml.writeText( result.getMessage() ); 163 m_xml.endElement(); 164 break; 165 default: 166 break; 167 } 168 169 if( result.hasExpression() ) 170 m_xml.endElement(); 171 } 172 173 void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { 174 StreamingReporterBase::sectionEnded( sectionStats ); 175 if ( --m_sectionDepth > 0 ) { 176 { 177 XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); 178 e.writeAttribute( "successes"_sr, sectionStats.assertions.passed ); 179 e.writeAttribute( "failures"_sr, sectionStats.assertions.failed ); 180 e.writeAttribute( "expectedFailures"_sr, sectionStats.assertions.failedButOk ); 181 e.writeAttribute( "skipped"_sr, sectionStats.assertions.skipped > 0 ); 182 183 if ( m_config->showDurations() == ShowDurations::Always ) 184 e.writeAttribute( "durationInSeconds"_sr, sectionStats.durationInSeconds ); 185 } 186 // Ends assertion tag 187 m_xml.endElement(); 188 } 189 } 190 191 void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { 192 StreamingReporterBase::testCaseEnded( testCaseStats ); 193 XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); 194 e.writeAttribute( "success"_sr, testCaseStats.totals.assertions.allOk() ); 195 e.writeAttribute( "skips"_sr, testCaseStats.totals.assertions.skipped ); 196 197 if ( m_config->showDurations() == ShowDurations::Always ) 198 e.writeAttribute( "durationInSeconds"_sr, m_testCaseTimer.getElapsedSeconds() ); 199 if( !testCaseStats.stdOut.empty() ) 200 m_xml.scopedElement( "StdOut" ).writeText( trim( StringRef(testCaseStats.stdOut) ), XmlFormatting::Newline ); 201 if( !testCaseStats.stdErr.empty() ) 202 m_xml.scopedElement( "StdErr" ).writeText( trim( StringRef(testCaseStats.stdErr) ), XmlFormatting::Newline ); 203 204 m_xml.endElement(); 205 } 206 207 void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { 208 StreamingReporterBase::testRunEnded( testRunStats ); 209 m_xml.scopedElement( "OverallResults" ) 210 .writeAttribute( "successes"_sr, testRunStats.totals.assertions.passed ) 211 .writeAttribute( "failures"_sr, testRunStats.totals.assertions.failed ) 212 .writeAttribute( "expectedFailures"_sr, testRunStats.totals.assertions.failedButOk ) 213 .writeAttribute( "skips"_sr, testRunStats.totals.assertions.skipped ); 214 m_xml.scopedElement( "OverallResultsCases") 215 .writeAttribute( "successes"_sr, testRunStats.totals.testCases.passed ) 216 .writeAttribute( "failures"_sr, testRunStats.totals.testCases.failed ) 217 .writeAttribute( "expectedFailures"_sr, testRunStats.totals.testCases.failedButOk ) 218 .writeAttribute( "skips"_sr, testRunStats.totals.testCases.skipped ); 219 m_xml.endElement(); 220 } 221 222 void XmlReporter::benchmarkPreparing( StringRef name ) { 223 m_xml.startElement("BenchmarkResults") 224 .writeAttribute("name"_sr, name); 225 } 226 227 void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) { 228 m_xml.writeAttribute("samples"_sr, info.samples) 229 .writeAttribute("resamples"_sr, info.resamples) 230 .writeAttribute("iterations"_sr, info.iterations) 231 .writeAttribute("clockResolution"_sr, info.clockResolution) 232 .writeAttribute("estimatedDuration"_sr, info.estimatedDuration) 233 .writeComment("All values in nano seconds"_sr); 234 } 235 236 void XmlReporter::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) { 237 m_xml.scopedElement("mean") 238 .writeAttribute("value"_sr, benchmarkStats.mean.point.count()) 239 .writeAttribute("lowerBound"_sr, benchmarkStats.mean.lower_bound.count()) 240 .writeAttribute("upperBound"_sr, benchmarkStats.mean.upper_bound.count()) 241 .writeAttribute("ci"_sr, benchmarkStats.mean.confidence_interval); 242 m_xml.scopedElement("standardDeviation") 243 .writeAttribute("value"_sr, benchmarkStats.standardDeviation.point.count()) 244 .writeAttribute("lowerBound"_sr, benchmarkStats.standardDeviation.lower_bound.count()) 245 .writeAttribute("upperBound"_sr, benchmarkStats.standardDeviation.upper_bound.count()) 246 .writeAttribute("ci"_sr, benchmarkStats.standardDeviation.confidence_interval); 247 m_xml.scopedElement("outliers") 248 .writeAttribute("variance"_sr, benchmarkStats.outlierVariance) 249 .writeAttribute("lowMild"_sr, benchmarkStats.outliers.low_mild) 250 .writeAttribute("lowSevere"_sr, benchmarkStats.outliers.low_severe) 251 .writeAttribute("highMild"_sr, benchmarkStats.outliers.high_mild) 252 .writeAttribute("highSevere"_sr, benchmarkStats.outliers.high_severe); 253 m_xml.endElement(); 254 } 255 256 void XmlReporter::benchmarkFailed(StringRef error) { 257 m_xml.scopedElement("failed"). 258 writeAttribute("message"_sr, error); 259 m_xml.endElement(); 260 } 261 262 void XmlReporter::listReporters(std::vector<ReporterDescription> const& descriptions) { 263 auto outerTag = m_xml.scopedElement("AvailableReporters"); 264 for (auto const& reporter : descriptions) { 265 auto inner = m_xml.scopedElement("Reporter"); 266 m_xml.startElement("Name", XmlFormatting::Indent) 267 .writeText(reporter.name, XmlFormatting::None) 268 .endElement(XmlFormatting::Newline); 269 m_xml.startElement("Description", XmlFormatting::Indent) 270 .writeText(reporter.description, XmlFormatting::None) 271 .endElement(XmlFormatting::Newline); 272 } 273 } 274 275 void XmlReporter::listListeners(std::vector<ListenerDescription> const& descriptions) { 276 auto outerTag = m_xml.scopedElement( "RegisteredListeners" ); 277 for ( auto const& listener : descriptions ) { 278 auto inner = m_xml.scopedElement( "Listener" ); 279 m_xml.startElement( "Name", XmlFormatting::Indent ) 280 .writeText( listener.name, XmlFormatting::None ) 281 .endElement( XmlFormatting::Newline ); 282 m_xml.startElement( "Description", XmlFormatting::Indent ) 283 .writeText( listener.description, XmlFormatting::None ) 284 .endElement( XmlFormatting::Newline ); 285 } 286 } 287 288 void XmlReporter::listTests(std::vector<TestCaseHandle> const& tests) { 289 auto outerTag = m_xml.scopedElement("MatchingTests"); 290 for (auto const& test : tests) { 291 auto innerTag = m_xml.scopedElement("TestCase"); 292 auto const& testInfo = test.getTestCaseInfo(); 293 m_xml.startElement("Name", XmlFormatting::Indent) 294 .writeText(testInfo.name, XmlFormatting::None) 295 .endElement(XmlFormatting::Newline); 296 m_xml.startElement("ClassName", XmlFormatting::Indent) 297 .writeText(testInfo.className, XmlFormatting::None) 298 .endElement(XmlFormatting::Newline); 299 m_xml.startElement("Tags", XmlFormatting::Indent) 300 .writeText(testInfo.tagsAsString(), XmlFormatting::None) 301 .endElement(XmlFormatting::Newline); 302 303 auto sourceTag = m_xml.scopedElement("SourceInfo"); 304 m_xml.startElement("File", XmlFormatting::Indent) 305 .writeText(testInfo.lineInfo.file, XmlFormatting::None) 306 .endElement(XmlFormatting::Newline); 307 m_xml.startElement("Line", XmlFormatting::Indent) 308 .writeText(std::to_string(testInfo.lineInfo.line), XmlFormatting::None) 309 .endElement(XmlFormatting::Newline); 310 } 311 } 312 313 void XmlReporter::listTags(std::vector<TagInfo> const& tags) { 314 auto outerTag = m_xml.scopedElement("TagsFromMatchingTests"); 315 for (auto const& tag : tags) { 316 auto innerTag = m_xml.scopedElement("Tag"); 317 m_xml.startElement("Count", XmlFormatting::Indent) 318 .writeText(std::to_string(tag.count), XmlFormatting::None) 319 .endElement(XmlFormatting::Newline); 320 auto aliasTag = m_xml.scopedElement("Aliases"); 321 for (auto const& alias : tag.spellings) { 322 m_xml.startElement("Alias", XmlFormatting::Indent) 323 .writeText(alias, XmlFormatting::None) 324 .endElement(XmlFormatting::Newline); 325 } 326 } 327 } 328 329 } // end namespace Catch 330 331 #if defined(_MSC_VER) 332 #pragma warning(pop) 333 #endif