mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 20:27:11 +01:00 
			
		
		
		
	First draft of Junit reporter
This commit is contained in:
		| @@ -33,7 +33,8 @@ | ||||
| /* Begin PBXFileReference section */ | ||||
| 		4A3BFFB8128DCF06005609E3 /* TestMain.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TestMain.cpp; sourceTree = "<group>"; }; | ||||
| 		4A3BFFF0128DD23C005609E3 /* catch_runnerconfig.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = catch_runnerconfig.hpp; path = ../internal/catch_runnerconfig.hpp; sourceTree = SOURCE_ROOT; }; | ||||
| 		4AA7E968129FA1DF005A0B97 /* catch_reporter_junit.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = catch_reporter_junit.hpp; path = ../../../Lib/Catch/catch_reporter_junit.hpp; sourceTree = SOURCE_ROOT; }; | ||||
| 		4A992A6512B2156C002B7B66 /* catch_xmlwriter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = catch_xmlwriter.hpp; path = ../internal/catch_xmlwriter.hpp; sourceTree = SOURCE_ROOT; }; | ||||
| 		4A992A6612B21582002B7B66 /* catch_reporter_junit.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = catch_reporter_junit.hpp; path = ../catch_reporter_junit.hpp; sourceTree = SOURCE_ROOT; }; | ||||
| 		4AA7EA9112A438C7005A0B97 /* MiscTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MiscTests.cpp; sourceTree = "<group>"; }; | ||||
| 		4AFC341512809A36003A0C29 /* catch_capture.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = catch_capture.hpp; path = ../internal/catch_capture.hpp; sourceTree = SOURCE_ROOT; }; | ||||
| 		4AFC341612809A36003A0C29 /* catch_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = catch_common.h; path = ../internal/catch_common.h; sourceTree = SOURCE_ROOT; }; | ||||
| @@ -101,9 +102,9 @@ | ||||
| 		4AA7E96B129FA282005A0B97 /* Reporters */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				4A992A6612B21582002B7B66 /* catch_reporter_junit.hpp */, | ||||
| 				4AFC341D12809A45003A0C29 /* catch_reporter_basic.hpp */, | ||||
| 				4AFC341E12809A45003A0C29 /* catch_reporter_xml.hpp */, | ||||
| 				4AA7E968129FA1DF005A0B97 /* catch_reporter_junit.hpp */, | ||||
| 			); | ||||
| 			name = Reporters; | ||||
| 			sourceTree = "<group>"; | ||||
| @@ -136,6 +137,7 @@ | ||||
| 		4AFC341412809A1B003A0C29 /* Internal */ = { | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				4A992A6512B2156C002B7B66 /* catch_xmlwriter.hpp */, | ||||
| 				4A3BFFF0128DD23C005609E3 /* catch_runnerconfig.hpp */, | ||||
| 				4AFC341F12809A45003A0C29 /* catch_list.hpp */, | ||||
| 				4AFC359B1281F00B003A0C29 /* catch_section.hpp */, | ||||
|   | ||||
							
								
								
									
										286
									
								
								catch_reporter_junit.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								catch_reporter_junit.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,286 @@ | ||||
| /* | ||||
|  *  catch_reporter_junit.hpp | ||||
|  *  Catch | ||||
|  * | ||||
|  *  Created by Phil on 26/11/2010. | ||||
|  *  Copyright 2010 Two Blue Cubes Ltd. 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 TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED | ||||
| #define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED | ||||
|  | ||||
| #include "internal/catch_capture.hpp" | ||||
| #include "internal/catch_reporter_registry.hpp" | ||||
| #include "internal/catch_xmlwriter.hpp" | ||||
| #include <iostream> | ||||
|  | ||||
| namespace Catch | ||||
| { | ||||
|     class JunitReporter : public Catch::ITestReporter | ||||
|     { | ||||
|         struct Indenter | ||||
|         { | ||||
|             Indenter& operator ++() | ||||
|             { | ||||
|                 m_indent += "\t"; | ||||
|                 return *this; | ||||
|             } | ||||
|             Indenter& operator --() | ||||
|             { | ||||
|                 m_indent = m_indent.substr( 0, m_indent.length()-1 ); | ||||
|                 return *this; | ||||
|             } | ||||
|              | ||||
|             friend std::ostream& operator << ( std::ostream& os, const Indenter& indent ) | ||||
|             { | ||||
|                 os << indent.m_indent; | ||||
|                 return os; | ||||
|             } | ||||
|              | ||||
|             std::string m_indent; | ||||
|         }; | ||||
|          | ||||
|         struct TestStats | ||||
|         { | ||||
|             std::string element; | ||||
|             std::string resultType; | ||||
|             std::string message; | ||||
|             std::string content; | ||||
|         }; | ||||
|          | ||||
|         struct TestCaseStats | ||||
|         { | ||||
|             TestCaseStats( const std::string& name = std::string() ) | ||||
|             :   name( name ) | ||||
|             { | ||||
|             } | ||||
|              | ||||
|             double      timeInSeconds; | ||||
|             std::string status; | ||||
|             std::string className; | ||||
|             std::string name; | ||||
|             std::vector<TestStats> testStats; | ||||
|         }; | ||||
|          | ||||
|         struct Stats | ||||
|         { | ||||
|             Stats( const std::string& name = std::string() ) | ||||
|             :   testsCount( 0 ), | ||||
|                 failuresCount( 0 ), | ||||
|                 disabledCount( 0 ), | ||||
|                 errorsCount( 0 ), | ||||
|                 timeInSeconds( 0 ), | ||||
|                 name( name ) | ||||
|             { | ||||
|             } | ||||
|              | ||||
|             std::size_t testsCount; | ||||
|             std::size_t failuresCount; | ||||
|             std::size_t disabledCount; | ||||
|             std::size_t errorsCount; | ||||
|             double      timeInSeconds; | ||||
|             std::string name; | ||||
|              | ||||
|             std::vector<TestCaseStats> testCaseStats; | ||||
|         }; | ||||
|          | ||||
|     public: | ||||
|         /////////////////////////////////////////////////////////////////////////// | ||||
|         JunitReporter( const ReporterConfig& config = ReporterConfig() ) | ||||
|         :   m_config( config ), | ||||
|             m_testSuiteStats( "AllTests" ), | ||||
|             m_currentStats( &m_testSuiteStats ) | ||||
|         { | ||||
|         }         | ||||
|          | ||||
|         /////////////////////////////////////////////////////////////////////////// | ||||
|         static std::string getDescription() | ||||
|         { | ||||
|             return "Reports test results in an XML format that looks like Ant's junitreport target"; | ||||
|         } | ||||
|          | ||||
|     private: // ITestReporter | ||||
|          | ||||
|         /////////////////////////////////////////////////////////////////////////// | ||||
|         virtual void StartTesting() | ||||
|         { | ||||
|         } | ||||
|          | ||||
|         /////////////////////////////////////////////////////////////////////////// | ||||
|         virtual void StartGroup( const std::string& groupName ) | ||||
|         { | ||||
|              | ||||
| //            m_config.stream() << "\t<testsuite>\n"; | ||||
| //            if( !groupName.empty() ) | ||||
|             { | ||||
|                 m_statsForSuites.push_back( Stats( groupName ) ); | ||||
|                 m_currentStats = &m_statsForSuites.back(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /////////////////////////////////////////////////////////////////////////// | ||||
|         virtual void EndGroup( const std::string& groupName, std::size_t succeeded, std::size_t failed ) | ||||
|         { | ||||
| //            m_config.stream() << "\t</testsuite>\n"; | ||||
|             (groupName, succeeded, failed); | ||||
|             m_currentStats = &m_testSuiteStats; | ||||
|         } | ||||
|          | ||||
|         virtual void StartSection( const std::string& sectionName, const std::string description ){(sectionName,description);} | ||||
|         virtual void EndSection( const std::string& sectionName, std::size_t succeeded, std::size_t failed ){(sectionName, succeeded, failed);} | ||||
|          | ||||
|         /////////////////////////////////////////////////////////////////////////// | ||||
|         virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) | ||||
|         { | ||||
| //            m_config.stream() << "\t\t<testcase name='" << testInfo.getName() << "'>\n"; | ||||
|   //          m_currentTestSuccess = true; | ||||
|             m_currentStats->testCaseStats.push_back( TestCaseStats( testInfo.getName() ) ); | ||||
|              | ||||
|         } | ||||
|          | ||||
|         /////////////////////////////////////////////////////////////////////////// | ||||
|         virtual void Result( const Catch::ResultInfo& resultInfo ) | ||||
|         { | ||||
|             if( !resultInfo.ok() || m_config.includeSuccessfulResults() ) | ||||
|             { | ||||
|                 TestCaseStats& testCaseStats = m_currentStats->testCaseStats.back(); | ||||
|                 TestStats stats; | ||||
|                 std::ostringstream oss; | ||||
|                 if( !resultInfo.getMessage().empty() ) | ||||
|                 { | ||||
|                     oss << resultInfo.getMessage() << " at "; | ||||
|                 } | ||||
|                 oss << resultInfo.getFilename() << ":" << resultInfo.getLine(); | ||||
|                 stats.content = oss.str(); | ||||
|                 stats.message = resultInfo.getExpandedExpression(); | ||||
|                 stats.resultType = resultInfo.getTestMacroName(); | ||||
|                 switch( resultInfo.getResultType() ) | ||||
|                 { | ||||
|                     case ResultWas::ThrewException: | ||||
|                         stats.element = "error"; | ||||
|                         break; | ||||
|                     case ResultWas::Info: | ||||
|                         stats.element = "info"; // !TBD ? | ||||
|                         break; | ||||
|                     case ResultWas::Warning: | ||||
|                         stats.element = "warning"; // !TBD ? | ||||
|                         break; | ||||
|                     case ResultWas::ExplicitFailure: | ||||
|                         stats.element = "failure"; | ||||
|                         break; | ||||
|                     case ResultWas::ExpressionFailed: | ||||
|                         stats.element = "failure"; | ||||
|                         break; | ||||
|                     case ResultWas::Ok: | ||||
|                         stats.element = "success"; | ||||
|                         break; | ||||
|                     default: | ||||
|                         stats.element = "unknown"; | ||||
|                         break; | ||||
|                 } | ||||
|                 testCaseStats.testStats.push_back( stats ); | ||||
|                  | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /////////////////////////////////////////////////////////////////////////// | ||||
|         virtual void EndTestCase( const Catch::TestCaseInfo&, const std::string& stdOut, const std::string& stdErr ) | ||||
|         { | ||||
|             if( !stdOut.empty() ) | ||||
|                 m_stdOut << stdOut << "\n"; | ||||
|             if( !stdErr.empty() ) | ||||
|                 m_stdErr << stdErr << "\n"; | ||||
|         }     | ||||
|  | ||||
|         static std::string trim( const std::string& str ) | ||||
|         { | ||||
|             std::string::size_type start = str.find_first_not_of( "\n\r\t " ); | ||||
|             std::string::size_type end = str.find_last_not_of( "\n\r\t " ); | ||||
|              | ||||
|             return start < end ? str.substr( start, 1+end-start ) : ""; | ||||
|         } | ||||
|          | ||||
|         /////////////////////////////////////////////////////////////////////////// | ||||
|         virtual void EndTesting( std::size_t /* succeeded */, std::size_t /* failed */ ) | ||||
|         { | ||||
|             std::ostream& str = m_config.stream(); | ||||
|             { | ||||
|                 XmlWriter xml( str ); | ||||
|                  | ||||
|                 if( m_statsForSuites.size() > 0 ) | ||||
|                     xml.startElement( "testsuites" ); | ||||
|                              | ||||
|                 std::vector<Stats>::const_iterator it = m_statsForSuites.begin(); | ||||
|                 std::vector<Stats>::const_iterator itEnd = m_statsForSuites.end(); | ||||
|                  | ||||
|                 for(; it != itEnd; ++it ) | ||||
|                 { | ||||
|                     XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); | ||||
|                     xml.writeAttribute( "name", it->name ); | ||||
|                      | ||||
|                     OutputTestCases( xml, *it ); | ||||
|                 } | ||||
|        | ||||
|                 xml.scopedElement( "system-out" ).writeText( trim( m_stdOut.str() ) );                 | ||||
|                 xml.scopedElement( "system-err" ).writeText( trim( m_stdOut.str() ) ); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /////////////////////////////////////////////////////////////////////////// | ||||
|         void OutputTestCases( XmlWriter& xml, const Stats& stats ) | ||||
|         { | ||||
|             std::vector<TestCaseStats>::const_iterator it = stats.testCaseStats.begin(); | ||||
|             std::vector<TestCaseStats>::const_iterator itEnd = stats.testCaseStats.end(); | ||||
|             for(; it != itEnd; ++it ) | ||||
|             { | ||||
|                 xml.writeBlankLine(); | ||||
|                 xml.writeComment( "Test case" ); | ||||
|                  | ||||
|                 XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); | ||||
|                 xml.writeAttribute( "classname", it->className ); | ||||
|                 xml.writeAttribute( "name", it->name ); | ||||
|                 xml.writeAttribute( "time", "tbd" ); | ||||
|  | ||||
|                 OutputTestResult( xml, *it ); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|          | ||||
|         /////////////////////////////////////////////////////////////////////////// | ||||
|         void OutputTestResult( XmlWriter& xml, const TestCaseStats& stats ) | ||||
|         { | ||||
|             std::vector<TestStats>::const_iterator it = stats.testStats.begin(); | ||||
|             std::vector<TestStats>::const_iterator itEnd = stats.testStats.end(); | ||||
|             for(; it != itEnd; ++it ) | ||||
|             { | ||||
|                 if( it->element != "success" ) | ||||
|                 { | ||||
|                     XmlWriter::ScopedElement e = xml.scopedElement( it->element ); | ||||
|                      | ||||
|                     xml.writeAttribute( "message", it->message ); | ||||
|                     xml.writeAttribute( "type", it->resultType ); | ||||
|                     if( !it->content.empty() ) | ||||
|                         xml.writeText( it->content ); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|          | ||||
|     private: | ||||
|         const ReporterConfig& m_config; | ||||
|         bool m_currentTestSuccess; | ||||
|          | ||||
|         Stats m_testSuiteStats; | ||||
|         Stats* m_currentStats; | ||||
|         std::vector<Stats> m_statsForSuites; | ||||
|         std::ostringstream m_stdOut; | ||||
|         std::ostringstream m_stdErr; | ||||
|  | ||||
|         Indenter m_indent; | ||||
|     }; | ||||
|      | ||||
| } // end namespace Catch | ||||
|  | ||||
| #endif // TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED | ||||
| @@ -82,6 +82,9 @@ namespace Catch | ||||
|         } | ||||
|         std::string getExpandedExpression() const | ||||
|         { | ||||
|             if( !hasExpression() ) | ||||
|                 return ""; | ||||
|              | ||||
|             return m_expressionIncomplete | ||||
|                 ? getExpandedExpressionInternal() + " {can't expand the rest of the expression - consider rewriting it}" | ||||
|                 : getExpandedExpressionInternal(); | ||||
|   | ||||
							
								
								
									
										191
									
								
								internal/catch_xmlwriter.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								internal/catch_xmlwriter.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | ||||
| /* | ||||
|  *  catch_xmlwriter.hpp | ||||
|  *  Catch | ||||
|  * | ||||
|  *  Created by Phil on 09/12/2010. | ||||
|  *  Copyright 2010 Two Blue Cubes Ltd. 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 TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED | ||||
| #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED | ||||
|  | ||||
| namespace Catch | ||||
| { | ||||
|     class XmlWriter | ||||
|     { | ||||
|     public: | ||||
|          | ||||
|         class ScopedElement | ||||
|         { | ||||
|         public: | ||||
|             ScopedElement( XmlWriter* writer ) | ||||
|             :   m_writer( writer ) | ||||
|             { | ||||
|             } | ||||
|              | ||||
|             ScopedElement( const ScopedElement& other ) | ||||
|             :   m_writer( other.m_writer ) | ||||
|             { | ||||
|                 other.m_writer = NULL; | ||||
|             } | ||||
|              | ||||
|             ~ScopedElement() | ||||
|             { | ||||
|                 if( m_writer ) | ||||
|                     m_writer->endElement(); | ||||
|             } | ||||
|  | ||||
|             ScopedElement& writeText( const std::string& text ) | ||||
|             { | ||||
|                 m_writer->writeText( text ); | ||||
|                 return *this; | ||||
|             } | ||||
|              | ||||
|         private: | ||||
|             mutable XmlWriter* m_writer; | ||||
|         }; | ||||
|          | ||||
|         XmlWriter( std::ostream& os) | ||||
|         :   m_tagIsOpen( false ), | ||||
|             m_needsNewline( false ), | ||||
|             m_os( os ) | ||||
|         { | ||||
|         } | ||||
|          | ||||
|         ~XmlWriter() | ||||
|         { | ||||
|             while( !m_tags.empty() ) | ||||
|             { | ||||
|                 endElement(); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         XmlWriter& startElement( const std::string& name ) | ||||
|         { | ||||
|             ensureTagClosed(); | ||||
|             newlineIfNecessary(); | ||||
|             m_os << m_indent << "<" << name; | ||||
|             m_tags.push_back( name ); | ||||
|             m_indent += "  "; | ||||
|             m_tagIsOpen = true; | ||||
|             return *this; | ||||
|         } | ||||
|  | ||||
|         ScopedElement scopedElement( const std::string& name ) | ||||
|         { | ||||
|             ScopedElement scoped( this ); | ||||
|             startElement( name ); | ||||
|             return scoped; | ||||
|         } | ||||
|  | ||||
|         XmlWriter& endElement() | ||||
|         { | ||||
|             newlineIfNecessary(); | ||||
|             m_indent = m_indent.substr( 0, m_indent.size()-2 ); | ||||
|             if( m_tagIsOpen ) | ||||
|             { | ||||
|                 m_os << "/>\n"; | ||||
|                 m_tagIsOpen = false; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 m_os << m_indent << "</" << m_tags.back() << ">\n"; | ||||
|             }  | ||||
|             m_tags.pop_back(); | ||||
|             return *this; | ||||
|         } | ||||
|          | ||||
|         XmlWriter& writeAttribute( const std::string& name, const std::string& attribute ) | ||||
|         { | ||||
|             if( !name.empty() && !attribute.empty() ) | ||||
|             { | ||||
|                 m_os << " " << name << "=\""; | ||||
|                 writeEncodedText( attribute ); | ||||
|                 m_os << "\""; | ||||
|             } | ||||
|             return *this; | ||||
|         } | ||||
|          | ||||
|         XmlWriter& writeText( const std::string& text ) | ||||
|         { | ||||
|             if( !text.empty() ) | ||||
|             { | ||||
|                 bool tagWasOpen = m_tagIsOpen; | ||||
|                 ensureTagClosed(); | ||||
|                 if( tagWasOpen ) | ||||
|                     m_os << m_indent; | ||||
|                 writeEncodedText( text ); | ||||
|                 m_needsNewline = true; | ||||
|             } | ||||
|             return *this; | ||||
|         } | ||||
|          | ||||
|         XmlWriter& writeComment( const std::string& text ) | ||||
|         { | ||||
|             ensureTagClosed(); | ||||
|             m_os << m_indent << "<!--" << text << "-->"; | ||||
|             m_needsNewline = true; | ||||
|             return *this; | ||||
|         } | ||||
|          | ||||
|         XmlWriter& writeBlankLine() | ||||
|         { | ||||
|             ensureTagClosed(); | ||||
|             m_os << "\n"; | ||||
|             return *this; | ||||
|         } | ||||
|          | ||||
|     private: | ||||
|          | ||||
|         void ensureTagClosed() | ||||
|         { | ||||
|             if( m_tagIsOpen ) | ||||
|             { | ||||
|                 m_os << ">\n"; | ||||
|                 m_tagIsOpen = false; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         void newlineIfNecessary() | ||||
|         { | ||||
|             if( m_needsNewline ) | ||||
|             { | ||||
|                 m_os << "\n"; | ||||
|                 m_needsNewline = false; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         void writeEncodedText( const std::string& text ) | ||||
|         { | ||||
|             // !TBD finish this | ||||
|             if( !findReplaceableString( text, "<", "<" ) && | ||||
|                !findReplaceableString( text, "&", "&" ) && | ||||
|                !findReplaceableString( text, "\"", ""e;" ) ) | ||||
|             { | ||||
|                 m_os << text; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         bool findReplaceableString( const std::string& text, const std::string& replaceWhat, const std::string& replaceWith ) | ||||
|         { | ||||
|             std::string::size_type pos = text.find_first_of( replaceWhat ); | ||||
|             if( pos != std::string::npos ) | ||||
|             { | ||||
|                 m_os << text.substr( 0, pos ) << replaceWith; | ||||
|                 writeEncodedText( text.substr( pos+1 ) ); | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         bool m_tagIsOpen; | ||||
|         bool m_needsNewline; | ||||
|         std::vector<std::string> m_tags; | ||||
|         std::string m_indent; | ||||
|         std::ostream& m_os; | ||||
|     }; | ||||
|      | ||||
| } | ||||
| #endif | ||||
		Reference in New Issue
	
	Block a user
	 Phil Nash
					Phil Nash