55// https://www.boost.org/LICENSE_1_0.txt)
66
77// SPDX-License-Identifier: BSL-1.0
8- #include < catch2/reporters/catch_reporter_teamcity.hpp>
9-
10- #include < catch2/reporters/catch_reporter_helpers.hpp>
11- #include < catch2/internal/catch_string_manip.hpp>
8+ #include < catch2/catch_test_case_info.hpp>
129#include < catch2/internal/catch_enforce.hpp>
10+ #include < catch2/internal/catch_string_manip.hpp>
1311#include < catch2/internal/catch_textflow.hpp>
14- #include < catch2/catch_test_case_info.hpp>
12+ #include < catch2/reporters/catch_reporter_helpers.hpp>
13+ #include < catch2/reporters/catch_reporter_teamcity.hpp>
1514
15+ #include < algorithm>
1616#include < cassert>
1717#include < ostream>
1818
1919namespace Catch {
2020
2121 namespace {
22- // if string has a : in first line will set indent to follow it on
23- // subsequent lines
24- void printHeaderString (std::ostream& os, std::string const & _string, std::size_t indent = 0 ) {
25- std::size_t i = _string.find (" : " );
26- if (i != std::string::npos)
27- i += 2 ;
28- else
29- i = 0 ;
30- os << TextFlow::Column (_string)
31- .indent (indent + i)
32- .initialIndent (indent) << ' \n ' ;
33- }
34-
35- std::string escape (StringRef str) {
36- std::string escaped = static_cast <std::string>(str);
37- replaceInPlace (escaped, " |" , " ||" );
38- replaceInPlace (escaped, " '" , " |'" );
39- replaceInPlace (escaped, " \n " , " |n" );
40- replaceInPlace (escaped, " \r " , " |r" );
41- replaceInPlace (escaped, " [" , " |[" );
42- replaceInPlace (escaped, " ]" , " |]" );
22+ std::string escape ( StringRef str ) {
23+ std::string escaped = static_cast <std::string>( str );
24+ replaceInPlace ( escaped, " " , " _" );
25+ replaceInPlace ( escaped, " |" , " ||" );
26+ replaceInPlace ( escaped, " '" , " |'" );
27+ replaceInPlace ( escaped, " \n " , " |n" );
28+ replaceInPlace ( escaped, " \r " , " |r" );
29+ replaceInPlace ( escaped, " [" , " |[" );
30+ replaceInPlace ( escaped, " ]" , " |]" );
4331 return escaped;
4432 }
4533 } // end anonymous namespace
4634
35+ TeamCityReporter::TeamCityReporter ( ReporterConfig&& _config ):
36+ StreamingReporterBase ( CATCH_MOVE( _config ) ) {
37+ m_preferences.shouldRedirectStdOut = true ;
38+
39+ parseCustomOptions ();
40+ }
4741
4842 TeamCityReporter::~TeamCityReporter () = default ;
4943
5044 void TeamCityReporter::testRunStarting ( TestRunInfo const & runInfo ) {
51- m_stream << " ##teamcity[testSuiteStarted name='" << escape ( runInfo.name )
52- << " ']\n " ;
45+ StreamingReporterBase::testRunStarting ( runInfo );
46+ m_stream << " ##teamcity[testSuiteStarted name='"
47+ << escape ( runInfo.name ) << " ']\n " ;
5348 }
5449
5550 void TeamCityReporter::testRunEnded ( TestRunStats const & runStats ) {
51+ StreamingReporterBase::testRunEnded ( runStats );
52+
5653 m_stream << " ##teamcity[testSuiteFinished name='"
57- << escape ( runStats.runInfo .name ) << " ']\n " ;
54+ << escape ( runStats.runInfo .name ) << " ']\n " ;
5855 }
5956
60- void TeamCityReporter::assertionEnded (AssertionStats const & assertionStats) {
57+ void TeamCityReporter::assertionEnded ( AssertionStats const & assertionStats ) {
6158 AssertionResult const & result = assertionStats.assertionResult ;
6259 if ( !result.isOk () ||
6360 result.getResultType () == ResultWas::ExplicitSkip ) {
6461
65- ReusableStringStream msg;
66- if (!m_headerPrintedForThisSection)
67- printSectionHeader (msg. get ()) ;
68- m_headerPrintedForThisSection = true ;
62+ if ( m_printSections ) {
63+ m_stream << createTestCaseHeader ( printSectionName () );
64+ m_headerPrintedForThisSection = true ;
65+ }
6966
67+ ReusableStringStream msg;
7068 msg << result.getSourceInfo () << ' \n ' ;
7169
7270 switch (result.getResultType ()) {
@@ -107,12 +105,12 @@ namespace Catch {
107105 for (auto const & messageInfo : assertionStats.infoMessages )
108106 msg << " \n \" " << messageInfo.message << ' "' ;
109107
110-
111108 if (result.hasExpression ()) {
112- msg <<
113- " \n " << result.getExpressionInMacro () << " \n "
114- " with expansion:\n "
115- " " << result.getExpandedExpression () << ' \n ' ;
109+ msg << " \n " << result.getExpressionInMacro ()
110+ << " \n "
111+ " with expansion:\n "
112+ " "
113+ << result.getExpandedExpression () << ' \n ' ;
116114 }
117115
118116 if ( result.getResultType () == ResultWas::ExplicitSkip ) {
@@ -123,55 +121,103 @@ namespace Catch {
123121 } else {
124122 m_stream << " ##teamcity[testFailed" ;
125123 }
126- m_stream << " name='" << escape ( currentTestCaseInfo->name ) << ' \' '
127- << " message='" << escape ( msg.str () ) << ' \' ' << " ]\n " ;
124+
125+ if ( m_printSections ) {
126+ m_stream << " name='" << escape ( printSectionName () ) << ' \' ' ;
127+ } else {
128+ m_stream << " name='" << escape ( currentTestCaseInfo->name )
129+ << ' \' ' ;
130+ }
131+ m_stream << " message='" << escape ( msg.str () ) << " ']\n " ;
132+ }
133+ }
134+
135+ void TeamCityReporter::testCaseStarting ( TestCaseInfo const & testInfo ) {
136+ StreamingReporterBase::testCaseStarting ( testInfo );
137+
138+ if ( not m_printSections ) {
139+ m_testCaseTimer.start ();
140+ m_stream << createTestCaseHeader ( testInfo.name );
141+ }
142+ }
143+
144+ void TeamCityReporter::testCaseEnded ( TestCaseStats const & testCaseStats ) {
145+ StreamingReporterBase::testCaseEnded ( testCaseStats );
146+
147+ if ( not m_printSections ) {
148+ m_stream << createTestCaseFooter (
149+ testCaseStats.testInfo ->name ,
150+ m_testCaseTimer.getElapsedSeconds () );
128151 }
129- m_stream.flush ();
130152 }
131153
132- void TeamCityReporter::testCaseStarting (TestCaseInfo const & testInfo) {
133- m_testTimer.start ();
134- StreamingReporterBase::testCaseStarting (testInfo);
135- m_stream << " ##teamcity[testStarted name='"
136- << escape (testInfo.name ) << " ']\n " ;
137- m_stream.flush ();
154+ std::string TeamCityReporter::printSectionName () {
155+ std::string output;
156+
157+ for ( const auto & entry : m_sectionStack ) {
158+ output += entry.name + m_sectionSeparator;
159+ }
160+
161+ const auto endPos = output.find_last_not_of ( m_sectionSeparator );
162+ if ( endPos != std::string::npos ) { output.resize ( endPos + 1 ); }
163+
164+ return output;
165+ }
166+
167+ void TeamCityReporter::sectionStarting ( SectionInfo const & sectionInfo ) {
168+ StreamingReporterBase::sectionStarting ( sectionInfo );
169+
170+ if ( not m_printSections ) { return ; }
171+
172+ m_headerPrintedForThisSection = false ;
138173 }
139174
140- void TeamCityReporter::testCaseEnded (TestCaseStats const & testCaseStats) {
141- StreamingReporterBase::testCaseEnded (testCaseStats);
142- auto const & testCaseInfo = *testCaseStats.testInfo ;
143- if (!testCaseStats.stdOut .empty ())
144- m_stream << " ##teamcity[testStdOut name='"
145- << escape (testCaseInfo.name )
146- << " ' out='" << escape (testCaseStats.stdOut ) << " ']\n " ;
147- if (!testCaseStats.stdErr .empty ())
148- m_stream << " ##teamcity[testStdErr name='"
149- << escape (testCaseInfo.name )
150- << " ' out='" << escape (testCaseStats.stdErr ) << " ']\n " ;
151- m_stream << " ##teamcity[testFinished name='"
152- << escape (testCaseInfo.name ) << " ' duration='"
153- << m_testTimer.getElapsedMilliseconds () << " ']\n " ;
154- m_stream.flush ();
175+ void TeamCityReporter::sectionEnded ( SectionStats const & sectionStats ) {
176+ if ( not m_printSections ) { return ; }
177+
178+ if ( not m_headerPrintedForThisSection ) {
179+ m_headerPrintedForThisSection = true ;
180+
181+ m_stream << createTestCaseHeader ( printSectionName () );
182+ m_stream << createTestCaseFooter ( printSectionName (),
183+ sectionStats.durationInSeconds );
184+ }
185+
186+ StreamingReporterBase::sectionEnded ( sectionStats );
155187 }
156188
157- void TeamCityReporter::printSectionHeader (std::ostream& os) {
158- assert (!m_sectionStack.empty ());
189+ std::string TeamCityReporter::createTestCaseHeader ( std::string name ) {
190+ std::string result{ " ##teamcity[testStarted name='" };
191+ result += escape ( name );
192+ result += " ']\n " ;
193+ return result;
194+ }
159195
160- if (m_sectionStack.size () > 1 ) {
161- os << lineOfChars (' -' ) << ' \n ' ;
196+ std::string TeamCityReporter::createTestCaseFooter ( std::string name,
197+ double duration ) {
198+ std::string result{ " ##teamcity[testFinished name='" };
199+ result += escape ( name );
162200
163- std::vector<SectionInfo>::const_iterator
164- it = m_sectionStack.begin () + 1 , // Skip first section (test case)
165- itEnd = m_sectionStack.end ();
166- for (; it != itEnd; ++it)
167- printHeaderString (os, it->name );
168- os << lineOfChars (' -' ) << ' \n ' ;
201+ if ( m_config->showDurations () == ShowDurations::Always ) {
202+ result +=
203+ " ' duration='" +
204+ std::to_string ( static_cast <uint32_t >( duration * 1000 ) );
169205 }
170206
171- SourceLineInfo lineInfo = m_sectionStack.front ().lineInfo ;
207+ result += " ']\n " ;
208+ return result;
209+ }
210+
211+ void TeamCityReporter::parseCustomOptions () {
212+ auto result = m_customOptions.find ( " Xsections" );
213+ if ( result != m_customOptions.end () ) {
214+ m_printSections = result->second == " true" ;
215+ }
172216
173- os << lineInfo << ' \n ' ;
174- os << lineOfChars (' .' ) << " \n\n " ;
217+ result = m_customOptions.find ( " Xseparator" );
218+ if ( result != m_customOptions.end () ) {
219+ m_sectionSeparator = result->second ;
220+ }
175221 }
176222
177223} // end namespace Catch
0 commit comments