5
5
// https://www.boost.org/LICENSE_1_0.txt)
6
6
7
7
// 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>
12
9
#include < catch2/internal/catch_enforce.hpp>
10
+ #include < catch2/internal/catch_string_manip.hpp>
13
11
#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>
15
14
15
+ #include < algorithm>
16
16
#include < cassert>
17
17
#include < ostream>
18
18
19
19
namespace Catch {
20
20
21
21
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, " ]" , " |]" );
43
31
return escaped;
44
32
}
45
33
} // end anonymous namespace
46
34
35
+ TeamCityReporter::TeamCityReporter ( ReporterConfig&& _config ):
36
+ StreamingReporterBase ( CATCH_MOVE( _config ) ) {
37
+ m_preferences.shouldRedirectStdOut = true ;
38
+
39
+ parseCustomOptions ();
40
+ }
47
41
48
42
TeamCityReporter::~TeamCityReporter () = default ;
49
43
50
44
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 " ;
53
48
}
54
49
55
50
void TeamCityReporter::testRunEnded ( TestRunStats const & runStats ) {
51
+ StreamingReporterBase::testRunEnded ( runStats );
52
+
56
53
m_stream << " ##teamcity[testSuiteFinished name='"
57
- << escape ( runStats.runInfo .name ) << " ']\n " ;
54
+ << escape ( runStats.runInfo .name ) << " ']\n " ;
58
55
}
59
56
60
- void TeamCityReporter::assertionEnded (AssertionStats const & assertionStats) {
57
+ void TeamCityReporter::assertionEnded ( AssertionStats const & assertionStats ) {
61
58
AssertionResult const & result = assertionStats.assertionResult ;
62
59
if ( !result.isOk () ||
63
60
result.getResultType () == ResultWas::ExplicitSkip ) {
64
61
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
+ }
69
66
67
+ ReusableStringStream msg;
70
68
msg << result.getSourceInfo () << ' \n ' ;
71
69
72
70
switch (result.getResultType ()) {
@@ -107,12 +105,12 @@ namespace Catch {
107
105
for (auto const & messageInfo : assertionStats.infoMessages )
108
106
msg << " \n \" " << messageInfo.message << ' "' ;
109
107
110
-
111
108
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 ' ;
116
114
}
117
115
118
116
if ( result.getResultType () == ResultWas::ExplicitSkip ) {
@@ -123,55 +121,103 @@ namespace Catch {
123
121
} else {
124
122
m_stream << " ##teamcity[testFailed" ;
125
123
}
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 () );
128
151
}
129
- m_stream.flush ();
130
152
}
131
153
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 ;
138
173
}
139
174
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 );
155
187
}
156
188
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
+ }
159
195
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 );
162
200
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 ) );
169
205
}
170
206
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
+ }
172
216
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
+ }
175
221
}
176
222
177
223
} // end namespace Catch
0 commit comments