1 /* 2 * Created by Daniel Garcia on 2018-12-04. 3 * Copyright Social Point SL. All rights reserved. 4 * 5 * Distributed under the Boost Software License, Version 1.0. (See accompanying 6 * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7 */ 8 #ifndef CATCH_REPORTER_SONARQUBE_HPP_INCLUDED 9 #define CATCH_REPORTER_SONARQUBE_HPP_INCLUDED 10 11 12 // Don't #include any Catch headers here - we can assume they are already 13 // included before this header. 14 // This is not good practice in general but is necessary in this case so this 15 // file can be distributed as a single header that works with the main 16 // Catch single header. 17 18 #include <map> 19 20 namespace Catch { 21 22 struct SonarQubeReporter : CumulativeReporterBase<SonarQubeReporter> { 23 SonarQubeReporterCatch::SonarQubeReporter24 SonarQubeReporter(ReporterConfig const& config) 25 : CumulativeReporterBase(config) 26 , xml(config.stream()) { 27 m_reporterPrefs.shouldRedirectStdOut = true; 28 m_reporterPrefs.shouldReportAllAssertions = true; 29 } 30 31 ~SonarQubeReporter() override; 32 getDescriptionCatch::SonarQubeReporter33 static std::string getDescription() { 34 return "Reports test results in the Generic Test Data SonarQube XML format"; 35 } 36 getSupportedVerbositiesCatch::SonarQubeReporter37 static std::set<Verbosity> getSupportedVerbosities() { 38 return { Verbosity::Normal }; 39 } 40 noMatchingTestCasesCatch::SonarQubeReporter41 void noMatchingTestCases(std::string const& /*spec*/) override {} 42 testRunStartingCatch::SonarQubeReporter43 void testRunStarting(TestRunInfo const& testRunInfo) override { 44 CumulativeReporterBase::testRunStarting(testRunInfo); 45 xml.startElement("testExecutions"); 46 xml.writeAttribute("version", "1"); 47 } 48 testGroupEndedCatch::SonarQubeReporter49 void testGroupEnded(TestGroupStats const& testGroupStats) override { 50 CumulativeReporterBase::testGroupEnded(testGroupStats); 51 writeGroup(*m_testGroups.back()); 52 } 53 testRunEndedCumulativeCatch::SonarQubeReporter54 void testRunEndedCumulative() override { 55 xml.endElement(); 56 } 57 writeGroupCatch::SonarQubeReporter58 void writeGroup(TestGroupNode const& groupNode) { 59 std::map<std::string, TestGroupNode::ChildNodes> testsPerFile; 60 for(auto const& child : groupNode.children) 61 testsPerFile[child->value.testInfo.lineInfo.file].push_back(child); 62 63 for(auto const& kv : testsPerFile) 64 writeTestFile(kv.first.c_str(), kv.second); 65 } 66 writeTestFileCatch::SonarQubeReporter67 void writeTestFile(const char* filename, TestGroupNode::ChildNodes const& testCaseNodes) { 68 XmlWriter::ScopedElement e = xml.scopedElement("file"); 69 xml.writeAttribute("path", filename); 70 71 for(auto const& child : testCaseNodes) 72 writeTestCase(*child); 73 } 74 writeTestCaseCatch::SonarQubeReporter75 void writeTestCase(TestCaseNode const& testCaseNode) { 76 // All test cases have exactly one section - which represents the 77 // test case itself. That section may have 0-n nested sections 78 assert(testCaseNode.children.size() == 1); 79 SectionNode const& rootSection = *testCaseNode.children.front(); 80 writeSection("", rootSection, testCaseNode.value.testInfo.okToFail()); 81 } 82 writeSectionCatch::SonarQubeReporter83 void writeSection(std::string const& rootName, SectionNode const& sectionNode, bool okToFail) { 84 std::string name = trim(sectionNode.stats.sectionInfo.name); 85 if(!rootName.empty()) 86 name = rootName + '/' + name; 87 88 if(!sectionNode.assertions.empty() || !sectionNode.stdOut.empty() || !sectionNode.stdErr.empty()) { 89 XmlWriter::ScopedElement e = xml.scopedElement("testCase"); 90 xml.writeAttribute("name", name); 91 xml.writeAttribute("duration", static_cast<long>(sectionNode.stats.durationInSeconds * 1000)); 92 93 writeAssertions(sectionNode, okToFail); 94 } 95 96 for(auto const& childNode : sectionNode.childSections) 97 writeSection(name, *childNode, okToFail); 98 } 99 writeAssertionsCatch::SonarQubeReporter100 void writeAssertions(SectionNode const& sectionNode, bool okToFail) { 101 for(auto const& assertion : sectionNode.assertions) 102 writeAssertion( assertion, okToFail); 103 } 104 writeAssertionCatch::SonarQubeReporter105 void writeAssertion(AssertionStats const& stats, bool okToFail) { 106 AssertionResult const& result = stats.assertionResult; 107 if(!result.isOk()) { 108 std::string elementName; 109 if(okToFail) { 110 elementName = "skipped"; 111 } 112 else { 113 switch(result.getResultType()) { 114 case ResultWas::ThrewException: 115 case ResultWas::FatalErrorCondition: 116 elementName = "error"; 117 break; 118 case ResultWas::ExplicitFailure: 119 elementName = "failure"; 120 break; 121 case ResultWas::ExpressionFailed: 122 elementName = "failure"; 123 break; 124 case ResultWas::DidntThrowException: 125 elementName = "failure"; 126 break; 127 128 // We should never see these here: 129 case ResultWas::Info: 130 case ResultWas::Warning: 131 case ResultWas::Ok: 132 case ResultWas::Unknown: 133 case ResultWas::FailureBit: 134 case ResultWas::Exception: 135 elementName = "internalError"; 136 break; 137 } 138 } 139 140 XmlWriter::ScopedElement e = xml.scopedElement(elementName); 141 142 ReusableStringStream messageRss; 143 messageRss << result.getTestMacroName() << "(" << result.getExpression() << ")"; 144 xml.writeAttribute("message", messageRss.str()); 145 146 ReusableStringStream textRss; 147 if (stats.totals.assertions.total() > 0) { 148 textRss << "FAILED:\n"; 149 if (result.hasExpression()) { 150 textRss << "\t" << result.getExpressionInMacro() << "\n"; 151 } 152 if (result.hasExpandedExpression()) { 153 textRss << "with expansion:\n\t" << result.getExpandedExpression() << "\n"; 154 } 155 } 156 157 if(!result.getMessage().empty()) 158 textRss << result.getMessage() << "\n"; 159 160 for(auto const& msg : stats.infoMessages) 161 if(msg.type == ResultWas::Info) 162 textRss << msg.message << "\n"; 163 164 textRss << "at " << result.getSourceInfo(); 165 xml.writeText(textRss.str(), XmlFormatting::Newline); 166 } 167 } 168 169 private: 170 XmlWriter xml; 171 }; 172 173 #ifdef CATCH_IMPL ~SonarQubeReporter()174 SonarQubeReporter::~SonarQubeReporter() {} 175 #endif 176 177 CATCH_REGISTER_REPORTER( "sonarqube", SonarQubeReporter ) 178 179 } // end namespace Catch 180 181 #endif // CATCH_REPORTER_SONARQUBE_HPP_INCLUDED