/* * Created by Daniel Garcia on 2018-12-04. * Copyright Social Point SL. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef CATCH_REPORTER_SONARQUBE_HPP_INCLUDED #define CATCH_REPORTER_SONARQUBE_HPP_INCLUDED // Don't #include any Catch headers here - we can assume they are already // included before this header. // This is not good practice in general but is necessary in this case so this // file can be distributed as a single header that works with the main // Catch single header. #include namespace Catch { struct SonarQubeReporter : CumulativeReporterBase { SonarQubeReporter(ReporterConfig const& config) : CumulativeReporterBase(config) , xml(config.stream()) { m_reporterPrefs.shouldRedirectStdOut = true; m_reporterPrefs.shouldReportAllAssertions = true; } ~SonarQubeReporter() override; static std::string getDescription() { return "Reports test results in the Generic Test Data SonarQube XML format"; } static std::set getSupportedVerbosities() { return { Verbosity::Normal }; } void noMatchingTestCases(std::string const& /*spec*/) override {} void testRunStarting(TestRunInfo const& testRunInfo) override { CumulativeReporterBase::testRunStarting(testRunInfo); xml.startElement("testExecutions"); xml.writeAttribute("version", "1"); } void testGroupEnded(TestGroupStats const& testGroupStats) override { CumulativeReporterBase::testGroupEnded(testGroupStats); writeGroup(*m_testGroups.back()); } void testRunEndedCumulative() override { xml.endElement(); } void writeGroup(TestGroupNode const& groupNode) { std::map testsPerFile; for(auto const& child : groupNode.children) testsPerFile[child->value.testInfo.lineInfo.file].push_back(child); for(auto const& kv : testsPerFile) writeTestFile(kv.first.c_str(), kv.second); } void writeTestFile(const char* filename, TestGroupNode::ChildNodes const& testCaseNodes) { XmlWriter::ScopedElement e = xml.scopedElement("file"); xml.writeAttribute("path", filename); for(auto const& child : testCaseNodes) writeTestCase(*child); } void writeTestCase(TestCaseNode const& testCaseNode) { // All test cases have exactly one section - which represents the // test case itself. That section may have 0-n nested sections assert(testCaseNode.children.size() == 1); SectionNode const& rootSection = *testCaseNode.children.front(); writeSection("", rootSection, testCaseNode.value.testInfo.okToFail()); } void writeSection(std::string const& rootName, SectionNode const& sectionNode, bool okToFail) { std::string name = trim(sectionNode.stats.sectionInfo.name); if(!rootName.empty()) name = rootName + '/' + name; if(!sectionNode.assertions.empty() || !sectionNode.stdOut.empty() || !sectionNode.stdErr.empty()) { XmlWriter::ScopedElement e = xml.scopedElement("testCase"); xml.writeAttribute("name", name); xml.writeAttribute("duration", static_cast(sectionNode.stats.durationInSeconds * 1000)); writeAssertions(sectionNode, okToFail); } for(auto const& childNode : sectionNode.childSections) writeSection(name, *childNode, okToFail); } void writeAssertions(SectionNode const& sectionNode, bool okToFail) { for(auto const& assertion : sectionNode.assertions) writeAssertion( assertion, okToFail); } void writeAssertion(AssertionStats const& stats, bool okToFail) { AssertionResult const& result = stats.assertionResult; if(!result.isOk()) { std::string elementName; if(okToFail) { elementName = "skipped"; } else { switch(result.getResultType()) { case ResultWas::ThrewException: case ResultWas::FatalErrorCondition: elementName = "error"; break; case ResultWas::ExplicitFailure: elementName = "failure"; break; case ResultWas::ExpressionFailed: elementName = "failure"; break; case ResultWas::DidntThrowException: elementName = "failure"; break; // We should never see these here: case ResultWas::Info: case ResultWas::Warning: case ResultWas::Ok: case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: elementName = "internalError"; break; } } XmlWriter::ScopedElement e = xml.scopedElement(elementName); ReusableStringStream messageRss; messageRss << result.getTestMacroName() << "(" << result.getExpression() << ")"; xml.writeAttribute("message", messageRss.str()); ReusableStringStream textRss; if (stats.totals.assertions.total() > 0) { textRss << "FAILED:\n"; if (result.hasExpression()) { textRss << "\t" << result.getExpressionInMacro() << "\n"; } if (result.hasExpandedExpression()) { textRss << "with expansion:\n\t" << result.getExpandedExpression() << "\n"; } } if(!result.getMessage().empty()) textRss << result.getMessage() << "\n"; for(auto const& msg : stats.infoMessages) if(msg.type == ResultWas::Info) textRss << msg.message << "\n"; textRss << "at " << result.getSourceInfo(); xml.writeText(textRss.str(), XmlFormatting::Newline); } } private: XmlWriter xml; }; #ifdef CATCH_IMPL SonarQubeReporter::~SonarQubeReporter() {} #endif CATCH_REGISTER_REPORTER( "sonarqube", SonarQubeReporter ) } // end namespace Catch #endif // CATCH_REPORTER_SONARQUBE_HPP_INCLUDED