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