• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Created by Phil on 26/11/2010.
3  *  Copyright 2010 Two Blue Cubes Ltd. 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 
9 #include "catch_reporter_bases.hpp"
10 
11 #include "catch_reporter_junit.h"
12 
13 #include "../internal/catch_tostring.h"
14 #include "../internal/catch_reporter_registrars.hpp"
15 
16 #include <cassert>
17 #include <sstream>
18 #include <ctime>
19 #include <algorithm>
20 
21 namespace Catch {
22 
23     namespace {
getCurrentTimestamp()24         std::string getCurrentTimestamp() {
25             // Beware, this is not reentrant because of backward compatibility issues
26             // Also, UTC only, again because of backward compatibility (%z is C++11)
27             time_t rawtime;
28             std::time(&rawtime);
29             auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
30 
31 #ifdef _MSC_VER
32             std::tm timeInfo = {};
33             gmtime_s(&timeInfo, &rawtime);
34 #else
35             std::tm* timeInfo;
36             timeInfo = std::gmtime(&rawtime);
37 #endif
38 
39             char timeStamp[timeStampSize];
40             const char * const fmt = "%Y-%m-%dT%H:%M:%SZ";
41 
42 #ifdef _MSC_VER
43             std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
44 #else
45             std::strftime(timeStamp, timeStampSize, fmt, timeInfo);
46 #endif
47             return std::string(timeStamp);
48         }
49 
fileNameTag(const std::vector<std::string> & tags)50         std::string fileNameTag(const std::vector<std::string> &tags) {
51             auto it = std::find_if(begin(tags),
52                                    end(tags),
53                                    [] (std::string const& tag) {return tag.front() == '#'; });
54             if (it != tags.end())
55                 return it->substr(1);
56             return std::string();
57         }
58     } // anonymous namespace
59 
JunitReporter(ReporterConfig const & _config)60     JunitReporter::JunitReporter( ReporterConfig const& _config )
61         :   CumulativeReporterBase( _config ),
62             xml( _config.stream() )
63         {
64             m_reporterPrefs.shouldRedirectStdOut = true;
65             m_reporterPrefs.shouldReportAllAssertions = true;
66         }
67 
~JunitReporter()68     JunitReporter::~JunitReporter() {}
69 
getDescription()70     std::string JunitReporter::getDescription() {
71         return "Reports test results in an XML format that looks like Ant's junitreport target";
72     }
73 
noMatchingTestCases(std::string const &)74     void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {}
75 
testRunStarting(TestRunInfo const & runInfo)76     void JunitReporter::testRunStarting( TestRunInfo const& runInfo )  {
77         CumulativeReporterBase::testRunStarting( runInfo );
78         xml.startElement( "testsuites" );
79         if( m_config->rngSeed() != 0 ) {
80             xml.startElement( "properties" );
81             xml.scopedElement( "property" )
82                 .writeAttribute( "name", "random-seed" )
83                 .writeAttribute( "value", m_config->rngSeed() );
84             xml.endElement();
85         }
86     }
87 
testGroupStarting(GroupInfo const & groupInfo)88     void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) {
89         suiteTimer.start();
90         stdOutForSuite.clear();
91         stdErrForSuite.clear();
92         unexpectedExceptions = 0;
93         CumulativeReporterBase::testGroupStarting( groupInfo );
94     }
95 
testCaseStarting(TestCaseInfo const & testCaseInfo)96     void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) {
97         m_okToFail = testCaseInfo.okToFail();
98     }
99 
assertionEnded(AssertionStats const & assertionStats)100     bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) {
101         if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail )
102             unexpectedExceptions++;
103         return CumulativeReporterBase::assertionEnded( assertionStats );
104     }
105 
testCaseEnded(TestCaseStats const & testCaseStats)106     void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {
107         stdOutForSuite += testCaseStats.stdOut;
108         stdErrForSuite += testCaseStats.stdErr;
109         CumulativeReporterBase::testCaseEnded( testCaseStats );
110     }
111 
testGroupEnded(TestGroupStats const & testGroupStats)112     void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {
113         double suiteTime = suiteTimer.getElapsedSeconds();
114         CumulativeReporterBase::testGroupEnded( testGroupStats );
115         writeGroup( *m_testGroups.back(), suiteTime );
116     }
117 
testRunEndedCumulative()118     void JunitReporter::testRunEndedCumulative() {
119         xml.endElement();
120     }
121 
writeGroup(TestGroupNode const & groupNode,double suiteTime)122     void JunitReporter::writeGroup( TestGroupNode const& groupNode, double suiteTime ) {
123         XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
124         TestGroupStats const& stats = groupNode.value;
125         xml.writeAttribute( "name", stats.groupInfo.name );
126         xml.writeAttribute( "errors", unexpectedExceptions );
127         xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions );
128         xml.writeAttribute( "tests", stats.totals.assertions.total() );
129         xml.writeAttribute( "hostname", "tbd" ); // !TBD
130         if( m_config->showDurations() == ShowDurations::Never )
131             xml.writeAttribute( "time", "" );
132         else
133             xml.writeAttribute( "time", suiteTime );
134         xml.writeAttribute( "timestamp", getCurrentTimestamp() );
135 
136         // Write test cases
137         for( auto const& child : groupNode.children )
138             writeTestCase( *child );
139 
140         xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false );
141         xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false );
142     }
143 
writeTestCase(TestCaseNode const & testCaseNode)144     void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) {
145         TestCaseStats const& stats = testCaseNode.value;
146 
147         // All test cases have exactly one section - which represents the
148         // test case itself. That section may have 0-n nested sections
149         assert( testCaseNode.children.size() == 1 );
150         SectionNode const& rootSection = *testCaseNode.children.front();
151 
152         std::string className = stats.testInfo.className;
153 
154         if( className.empty() ) {
155             className = fileNameTag(stats.testInfo.tags);
156             if ( className.empty() )
157                 className = "global";
158         }
159 
160         if ( !m_config->name().empty() )
161             className = m_config->name() + "." + className;
162 
163         writeSection( className, "", rootSection );
164     }
165 
writeSection(std::string const & className,std::string const & rootName,SectionNode const & sectionNode)166     void JunitReporter::writeSection(  std::string const& className,
167                         std::string const& rootName,
168                         SectionNode const& sectionNode ) {
169         std::string name = trim( sectionNode.stats.sectionInfo.name );
170         if( !rootName.empty() )
171             name = rootName + '/' + name;
172 
173         if( !sectionNode.assertions.empty() ||
174             !sectionNode.stdOut.empty() ||
175             !sectionNode.stdErr.empty() ) {
176             XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
177             if( className.empty() ) {
178                 xml.writeAttribute( "classname", name );
179                 xml.writeAttribute( "name", "root" );
180             }
181             else {
182                 xml.writeAttribute( "classname", className );
183                 xml.writeAttribute( "name", name );
184             }
185             xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) );
186 
187             writeAssertions( sectionNode );
188 
189             if( !sectionNode.stdOut.empty() )
190                 xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false );
191             if( !sectionNode.stdErr.empty() )
192                 xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false );
193         }
194         for( auto const& childNode : sectionNode.childSections )
195             if( className.empty() )
196                 writeSection( name, "", *childNode );
197             else
198                 writeSection( className, name, *childNode );
199     }
200 
writeAssertions(SectionNode const & sectionNode)201     void JunitReporter::writeAssertions( SectionNode const& sectionNode ) {
202         for( auto const& assertion : sectionNode.assertions )
203             writeAssertion( assertion );
204     }
205 
writeAssertion(AssertionStats const & stats)206     void JunitReporter::writeAssertion( AssertionStats const& stats ) {
207         AssertionResult const& result = stats.assertionResult;
208         if( !result.isOk() ) {
209             std::string elementName;
210             switch( result.getResultType() ) {
211                 case ResultWas::ThrewException:
212                 case ResultWas::FatalErrorCondition:
213                     elementName = "error";
214                     break;
215                 case ResultWas::ExplicitFailure:
216                     elementName = "failure";
217                     break;
218                 case ResultWas::ExpressionFailed:
219                     elementName = "failure";
220                     break;
221                 case ResultWas::DidntThrowException:
222                     elementName = "failure";
223                     break;
224 
225                 // We should never see these here:
226                 case ResultWas::Info:
227                 case ResultWas::Warning:
228                 case ResultWas::Ok:
229                 case ResultWas::Unknown:
230                 case ResultWas::FailureBit:
231                 case ResultWas::Exception:
232                     elementName = "internalError";
233                     break;
234             }
235 
236             XmlWriter::ScopedElement e = xml.scopedElement( elementName );
237 
238             xml.writeAttribute( "message", result.getExpandedExpression() );
239             xml.writeAttribute( "type", result.getTestMacroName() );
240 
241             ReusableStringStream rss;
242             if( !result.getMessage().empty() )
243                 rss << result.getMessage() << '\n';
244             for( auto const& msg : stats.infoMessages )
245                 if( msg.type == ResultWas::Info )
246                     rss << msg.message << '\n';
247 
248             rss << "at " << result.getSourceInfo();
249             xml.writeText( rss.str(), false );
250         }
251     }
252 
253     CATCH_REGISTER_REPORTER( "junit", JunitReporter )
254 
255 } // end namespace Catch
256