1 ///////////////////////////////////////////////////////////////////////////////
2 // Copyright (c) 2000-2015 Ericsson Telecom AB
3 // All rights reserved. This program and the accompanying materials
4 // are made available under the terms of the Eclipse Public License v1.0
5 // which accompanies this distribution, and is available at
6 // http://www.eclipse.org/legal/epl-v10.html
7 ///////////////////////////////////////////////////////////////////////////////
8 #include "JUnitLogger2.hh"
11 #include <sys/types.h>
17 // It's a static plug-in, destruction is done in the destructor. We need
18 // `extern "C"' for some reason.
19 ILoggerPlugin
*create_junit_logger() { return new JUnitLogger2(); }
23 ILoggerPlugin
*create_plugin() { return new JUnitLogger2(); }
24 void destroy_plugin(ILoggerPlugin
*plugin
) { delete plugin
; }
27 TestSuite::~TestSuite()
29 for (TestCases::const_iterator it
= testcases
.begin(); it
!= testcases
.end(); ++it
) {
34 JUnitLogger2::JUnitLogger2()
35 : filename_stem_(NULL
), testsuite_name_(mcopystr("Titan")), filename_(NULL
), file_stream_(NULL
)
37 // Overwrite values set by the base class constructor
39 fprintf(stderr
, "construct junitlogger\n");
42 name_
= mcopystr("JUnitLogger");
43 help_
= mcopystr("JUnitLogger writes JUnit-compatible XML");
45 //printf("%5lu:constructed\n", (unsigned long)getpid());
48 JUnitLogger2::~JUnitLogger2()
50 //printf("%5lu:desstructed\n", (unsigned long)getpid());
56 Free(testsuite_name_
);
58 name_
= help_
= filename_
= filename_stem_
= NULL
;
62 void JUnitLogger2::init(const char */
*options*/
)
64 //printf("%5lu:init\n", (unsigned long)getpid());
65 fprintf(stderr
, "Initializing `%s' (v%u.%u): %s\n", name_
, major_version_
, minor_version_
, help_
);
68 void JUnitLogger2::fini()
71 //fprintf(stderr, "JUnitLogger2 finished logging for PID: `%lu'\n", (unsigned long)getpid());
74 void JUnitLogger2::set_parameter(const char *parameter_name
, const char *parameter_value
) {
76 if (!strcmp("filename_stem", parameter_name
)) {
77 if (filename_stem_
!= NULL
)
79 filename_stem_
= mcopystr(parameter_value
);
80 } else if (!strcmp("testsuite_name", parameter_name
)) {
81 if (filename_stem_
!= NULL
)
82 Free(testsuite_name_
);
83 testsuite_name_
= mcopystr(parameter_value
);
85 fprintf(stderr
, "Unsupported parameter: `%s' with value: `%s'\n",
86 parameter_name
, parameter_value
);
90 void JUnitLogger2::open_file(bool is_first
) {
92 if (filename_stem_
== NULL
) {
93 filename_stem_
= mcopystr("junit-xml");
97 if (file_stream_
!= NULL
) return; // already open
99 if (!TTCN_Runtime::is_single() && !TTCN_Runtime::is_mtc()) return; // don't bother, only MTC has testcases
101 filename_
= mprintf("%s-%lu.log", filename_stem_
, (unsigned long)getpid());
103 file_stream_
= fopen(filename_
, "w");
105 fprintf(stderr
, "%s was unable to open log file `%s', reinitialization "
106 "may help\n", plugin_name(), filename_
);
110 is_configured_
= true;
111 time(&(testsuite
.start_ts
));
112 testsuite
.ts_name
= testsuite_name_
;
115 void JUnitLogger2::close_file() {
116 if (file_stream_
!= NULL
) {
117 time(&(testsuite
.end_ts
));
118 testsuite
.write(file_stream_
);
119 fclose(file_stream_
);
128 void JUnitLogger2::log(const TitanLoggerApi::TitanLogEvent
& event
,
129 bool /*log_buffered*/, bool /*separate_file*/,
130 bool /*use_emergency_mask*/)
132 if (file_stream_
== NULL
) return;
134 const TitanLoggerApi::LogEventType_choice
& choice
= event
.logEvent().choice();
136 switch (choice
.get_selection()) {
137 case TitanLoggerApi::LogEventType_choice::ALT_testcaseOp
: {
138 const TitanLoggerApi::TestcaseEvent_choice
& tcev
= choice
.testcaseOp().choice();
140 switch (tcev
.get_selection()) {
141 case TitanLoggerApi::TestcaseEvent_choice::ALT_testcaseStarted
: {
142 testcase
.tc_name
= tcev
.testcaseStarted().testcase__name();
143 // remember the start time
144 testcase
.tc_start
= 1000000LL * (long long)event
.timestamp().seconds() + (long long)event
.timestamp().microSeconds();
147 case TitanLoggerApi::TestcaseEvent_choice::ALT_testcaseFinished
: {
148 const TitanLoggerApi::TestcaseType
& tct
= tcev
.testcaseFinished();
149 testcase
.reason
= tct
.reason();
150 testcase
.module_name
= tct
.name().module__name();
152 const TitanLoggerApi::TimestampType
& ts
= event
.timestamp();
153 long long tc_end
= 1000000LL * (long long)ts
.seconds() + (long long)ts
.microSeconds();
154 testcase
.time
= (tc_end
- testcase
.tc_start
) / 1000000.0;
156 testcase
.setTCVerdict(event
);
157 testcase
.dte_reason
= dte_reason
.data();
159 testsuite
.addTestCase(testcase
);
163 case TitanLoggerApi::TestcaseEvent_choice::UNBOUND_VALUE
:
164 testcase
.verdict
= TestCase::Unbound
;
165 break; } // switch testcaseOp().choice.get_selection()
167 break; } // testcaseOp
169 case TitanLoggerApi::LogEventType_choice::ALT_errorLog
: {// A DTE is about to be thrown
170 const TitanLoggerApi::Categorized
& cat
= choice
.errorLog();
171 dte_reason
= escape_xml_element(cat
.text());
175 } // switch event.logEvent().choice().get_selection()
177 fflush(file_stream_
);
180 CHARSTRING
JUnitLogger2::escape_xml(const CHARSTRING
& xml_str
, int escape_chars
) {
181 expstring_t escaped
= NULL
;
182 int len
= xml_str
.lengthof();
183 for (int i
=0; i
<len
; i
++) {
184 char c
= *(((const char*)xml_str
)+i
);
187 if (escape_chars
<
) escaped
= mputstr(escaped
, "<");
188 else escaped
= mputc(escaped
, c
);
191 if (escape_chars
>
) escaped
= mputstr(escaped
, ">");
192 else escaped
= mputc(escaped
, c
);
195 if (escape_chars
"
) escaped
= mputstr(escaped
, """);
196 else escaped
= mputc(escaped
, c
);
199 if (escape_chars
&APOS
) escaped
= mputstr(escaped
, "'");
200 else escaped
= mputc(escaped
, c
);
203 if (escape_chars
&
) escaped
= mputstr(escaped
, "&");
204 else escaped
= mputc(escaped
, c
);
207 escaped
= mputc(escaped
, c
);
210 CHARSTRING
ret_val(escaped
);
215 void TestCase::writeTestCase(FILE* file_stream_
) const{
218 fprintf(file_stream_
, " <testcase classname='%s' name='%s' time='%f'>\n", module_name
.data(), tc_name
.data(), time
);
219 fprintf(file_stream_
, " <skipped>no verdict</skipped>\n");
220 fprintf(file_stream_
, " </testcase>\n");
223 fprintf(file_stream_
, " <testcase classname='%s' name='%s' time='%f'>\n", module_name
.data(), tc_name
.data(), time
);
224 fprintf(file_stream_
, " <failure type='fail-verdict'>%s\n", reason
.data());
225 fprintf(file_stream_
, "%s\n", stack_trace
.data());
226 fprintf(file_stream_
, " </failure>\n");
227 fprintf(file_stream_
, " </testcase>\n");
230 fprintf(file_stream_
, " <testcase classname='%s' name='%s' time='%f'>\n", module_name
.data(), tc_name
.data(), time
);
231 fprintf(file_stream_
, " <error type='DTE'>%s</error>\n", dte_reason
.data());
232 fprintf(file_stream_
, " </testcase>\n");
235 fprintf(file_stream_
, " <testcase classname='%s' name='%s' time='%f'/>\n", module_name
.data(), tc_name
.data(), time
);
238 fflush(file_stream_
);
241 void TestSuite::addTestCase(const TestCase
& testcase
) {
242 testcases
.push_back(new TestCase(testcase
));
244 switch(testcase
.verdict
) {
245 case TestCase::Fail
: failed
++; break;
246 case TestCase::None
: skipped
++; break;
247 case TestCase::Error
: error
++; break;
248 case TestCase::Inconc
: inconc
++; break;
253 void TestSuite::write(FILE* file_stream_
) {
254 double difftime_
= difftime(end_ts
, start_ts
);
255 fprintf(file_stream_
, "<?xml version=\"1.0\"?>\n"
256 "<testsuite name='%s' tests='%d' failures='%d' errors='%d' skipped='%d' inconc='%d' time='%.2f'>\n",
257 ts_name
.data(), all
, failed
, error
, skipped
, inconc
, difftime_
);
258 fflush(file_stream_
);
260 for (TestCases::const_iterator it
= testcases
.begin(); it
!= testcases
.end(); ++it
) {
261 (*it
)->writeTestCase(file_stream_
);
264 fprintf(file_stream_
, "</testsuite>\n");
265 fflush(file_stream_
);
268 void TestCase::setTCVerdict(const TitanLoggerApi::TitanLogEvent
& event
){
269 TitanLoggerApi::Verdict tc_verdict
= event
.logEvent().choice().testcaseOp().choice().testcaseFinished().verdict();
270 switch (tc_verdict
) {
271 case TitanLoggerApi::Verdict::UNBOUND_VALUE
:
272 case TitanLoggerApi::Verdict::UNKNOWN_VALUE
:
276 case TitanLoggerApi::Verdict::v0none
:
277 verdict
= TestCase::None
;
280 case TitanLoggerApi::Verdict::v1pass
:
281 verdict
= TestCase::Pass
;
284 case TitanLoggerApi::Verdict::v2inconc
:
285 verdict
= TestCase::Inconc
;
288 case TitanLoggerApi::Verdict::v3fail
: {
289 verdict
= TestCase::Fail
;
290 addStackTrace(event
);
293 case TitanLoggerApi::Verdict::v4error
:
294 verdict
= TestCase::Error
;
299 void TestCase::addStackTrace(const TitanLoggerApi::TitanLogEvent
& event
){
301 const TitanLoggerApi::TitanLogEvent_sourceInfo__list
& stack
= event
.sourceInfo__list();
303 int stack_depth
= stack
.size_of();
304 for (int i
=0; i
< stack_depth
; ++i
) {
305 const TitanLoggerApi::LocationInfo
& location
= stack
[i
];
308 stack_trace
+= location
.filename();
310 stack_trace
+= int2str(location
.line());
312 stack_trace
+= location
.ent__name();
315 // print the location type
316 switch (location
.ent__type()) {
317 case TitanLoggerApi::LocationInfo_ent__type:: UNKNOWN_VALUE
:
318 case TitanLoggerApi::LocationInfo_ent__type:: UNBOUND_VALUE
:
321 case TitanLoggerApi::LocationInfo_ent__type::unknown
:
324 case TitanLoggerApi::LocationInfo_ent__type::controlpart
:
325 stack_trace
+= "control part";
327 case TitanLoggerApi::LocationInfo_ent__type::testcase__
:
328 stack_trace
+= "testcase";
330 case TitanLoggerApi::LocationInfo_ent__type::altstep__
:
331 stack_trace
+= "altstep";
333 case TitanLoggerApi::LocationInfo_ent__type::function__
:
334 stack_trace
+= "function";
336 case TitanLoggerApi::LocationInfo_ent__type::external__function
:
337 stack_trace
+= "external function";
339 case TitanLoggerApi::LocationInfo_ent__type::template__
:
340 stack_trace
+= "template";
344 if(i
<stack_depth
-1) stack_trace
+= "\n";