summaryrefslogtreecommitdiff
path: root/single_include/catch2/catch_reporter_sonarqube.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'single_include/catch2/catch_reporter_sonarqube.hpp')
-rw-r--r--single_include/catch2/catch_reporter_sonarqube.hpp181
1 files changed, 181 insertions, 0 deletions
diff --git a/single_include/catch2/catch_reporter_sonarqube.hpp b/single_include/catch2/catch_reporter_sonarqube.hpp
new file mode 100644
index 00000000..bf7d9299
--- /dev/null
+++ b/single_include/catch2/catch_reporter_sonarqube.hpp
@@ -0,0 +1,181 @@
+/*
+ * 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 <map>
+
+namespace Catch {
+
+ struct SonarQubeReporter : CumulativeReporterBase<SonarQubeReporter> {
+
+ 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<Verbosity> 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<std::string, TestGroupNode::ChildNodes> 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<long>(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 \ No newline at end of file