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