mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 20:27:11 +01:00 
			
		
		
		
	Add SonarQube Generic Test Data reporter
It outputs reports in the `Generic Execution Test Data` format, see https://docs.sonarqube.org/latest/analysis/generic-test/, specifically https://docs.sonarqube.org/latest/analysis/generic-test/#header-2 Close #1738 (this is a cherry-pick and fixup of that PR)
This commit is contained in:
		 sp-dani-garcia
					sp-dani-garcia
				
			
				
					committed by
					
						 Martin Hořeňovský
						Martin Hořeňovský
					
				
			
			
				
	
			
			
			 Martin Hořeňovský
						Martin Hořeňovský
					
				
			
						parent
						
							9a558171d8
						
					
				
				
					commit
					51b29ced1a
				
			
							
								
								
									
										181
									
								
								include/reporters/catch_reporter_sonarqube.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								include/reporters/catch_reporter_sonarqube.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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(), false); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         XmlWriter xml; | ||||
|     }; | ||||
|  | ||||
| #ifdef CATCH_IMPL | ||||
|     SonarQubeReporter::~SonarQubeReporter() {} | ||||
| #endif | ||||
|  | ||||
|     CATCH_REGISTER_REPORTER( "sonarqube", SonarQubeReporter ) | ||||
|  | ||||
| } // end namespace Catch | ||||
|  | ||||
| #endif // CATCH_REPORTER_SONARQUBE_HPP_INCLUDED | ||||
		Reference in New Issue
	
	Block a user