a38c6d4c |
1 | /////////////////////////////////////////////////////////////////////////////// |
3abe9331 |
2 | // Copyright (c) 2000-2015 Ericsson Telecom AB |
a38c6d4c |
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" |
9 | |
10 | #include <unistd.h> |
11 | #include <sys/types.h> |
12 | |
13 | #include <sys/time.h> |
14 | |
15 | extern "C" |
16 | { |
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(); } |
20 | } |
21 | |
22 | extern "C" { |
23 | ILoggerPlugin *create_plugin() { return new JUnitLogger2(); } |
24 | void destroy_plugin(ILoggerPlugin *plugin) { delete plugin; } |
25 | } |
26 | |
27 | TestSuite::~TestSuite() |
28 | { |
29 | for (TestCases::const_iterator it = testcases.begin(); it != testcases.end(); ++it) { |
30 | delete (*it); |
31 | } |
32 | } |
33 | |
34 | JUnitLogger2::JUnitLogger2() |
35 | : filename_stem_(NULL), testsuite_name_(mcopystr("Titan")), filename_(NULL), file_stream_(NULL) |
36 | { |
37 | // Overwrite values set by the base class constructor |
38 | |
39 | fprintf(stderr, "construct junitlogger\n"); |
40 | major_version_ = 2; |
41 | minor_version_ = 0; |
42 | name_ = mcopystr("JUnitLogger"); |
43 | help_ = mcopystr("JUnitLogger writes JUnit-compatible XML"); |
44 | dte_reason = ""; |
45 | //printf("%5lu:constructed\n", (unsigned long)getpid()); |
46 | } |
47 | |
48 | JUnitLogger2::~JUnitLogger2() |
49 | { |
50 | //printf("%5lu:desstructed\n", (unsigned long)getpid()); |
51 | close_file(); |
52 | |
53 | Free(name_); |
54 | Free(help_); |
55 | Free(filename_); |
56 | Free(testsuite_name_); |
57 | Free(filename_stem_); |
58 | name_ = help_ = filename_ = filename_stem_ = NULL; |
59 | file_stream_ = NULL; |
60 | } |
61 | |
62 | void JUnitLogger2::init(const char */*options*/) |
63 | { |
64 | //printf("%5lu:init\n", (unsigned long)getpid()); |
65 | fprintf(stderr, "Initializing `%s' (v%u.%u): %s\n", name_, major_version_, minor_version_, help_); |
66 | } |
67 | |
68 | void JUnitLogger2::fini() |
69 | { |
70 | //puts("fini"); |
71 | //fprintf(stderr, "JUnitLogger2 finished logging for PID: `%lu'\n", (unsigned long)getpid()); |
72 | } |
73 | |
74 | void JUnitLogger2::set_parameter(const char *parameter_name, const char *parameter_value) { |
75 | //puts("set_par"); |
76 | if (!strcmp("filename_stem", parameter_name)) { |
77 | if (filename_stem_ != NULL) |
78 | Free(filename_stem_); |
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); |
84 | } else { |
85 | fprintf(stderr, "Unsupported parameter: `%s' with value: `%s'\n", |
86 | parameter_name, parameter_value); |
87 | } |
88 | } |
89 | |
90 | void JUnitLogger2::open_file(bool is_first) { |
91 | if (is_first) { |
92 | if (filename_stem_ == NULL) { |
93 | filename_stem_ = mcopystr("junit-xml"); |
94 | } |
95 | } |
96 | |
97 | if (file_stream_ != NULL) return; // already open |
98 | |
99 | if (!TTCN_Runtime::is_single() && !TTCN_Runtime::is_mtc()) return; // don't bother, only MTC has testcases |
100 | |
101 | filename_ = mprintf("%s-%lu.log", filename_stem_, (unsigned long)getpid()); |
102 | |
103 | file_stream_ = fopen(filename_, "w"); |
104 | if (!file_stream_) { |
105 | fprintf(stderr, "%s was unable to open log file `%s', reinitialization " |
106 | "may help\n", plugin_name(), filename_); |
107 | return; |
108 | } |
109 | |
110 | is_configured_ = true; |
111 | time(&(testsuite.start_ts)); |
112 | testsuite.ts_name = testsuite_name_; |
113 | } |
114 | |
115 | void JUnitLogger2::close_file() { |
116 | if (file_stream_ != NULL) { |
117 | time(&(testsuite.end_ts)); |
118 | testsuite.write(file_stream_); |
119 | fclose(file_stream_); |
120 | file_stream_ = NULL; |
121 | } |
122 | if (filename_) { |
123 | Free(filename_); |
124 | filename_ = NULL; |
125 | } |
126 | } |
127 | |
128 | void JUnitLogger2::log(const TitanLoggerApi::TitanLogEvent& event, |
129 | bool /*log_buffered*/, bool /*separate_file*/, |
130 | bool /*use_emergency_mask*/) |
131 | { |
132 | if (file_stream_ == NULL) return; |
133 | |
134 | const TitanLoggerApi::LogEventType_choice& choice = event.logEvent().choice(); |
135 | |
136 | switch (choice.get_selection()) { |
137 | case TitanLoggerApi::LogEventType_choice::ALT_testcaseOp: { |
138 | const TitanLoggerApi::TestcaseEvent_choice& tcev = choice.testcaseOp().choice(); |
139 | |
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(); |
145 | break; } |
146 | |
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(); |
151 | |
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; |
155 | |
156 | testcase.setTCVerdict(event); |
157 | testcase.dte_reason = dte_reason.data(); |
158 | dte_reason = ""; |
159 | testsuite.addTestCase(testcase); |
160 | testcase.reset(); |
161 | break; } |
162 | |
163 | case TitanLoggerApi::TestcaseEvent_choice::UNBOUND_VALUE: |
164 | testcase.verdict = TestCase::Unbound; |
165 | break; } // switch testcaseOp().choice.get_selection() |
166 | |
167 | break; } // testcaseOp |
168 | |
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()); |
172 | break; } |
173 | |
174 | default: break; |
175 | } // switch event.logEvent().choice().get_selection() |
176 | |
177 | fflush(file_stream_); |
178 | } |
179 | |
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); |
185 | switch (c) { |
186 | case '<': |
187 | if (escape_chars<) escaped = mputstr(escaped, "<"); |
188 | else escaped = mputc(escaped, c); |
189 | break; |
190 | case '>': |
191 | if (escape_chars>) escaped = mputstr(escaped, ">"); |
192 | else escaped = mputc(escaped, c); |
193 | break; |
194 | case '"': |
195 | if (escape_chars") escaped = mputstr(escaped, """); |
196 | else escaped = mputc(escaped, c); |
197 | break; |
198 | case '\'': |
199 | if (escape_chars&APOS) escaped = mputstr(escaped, "'"); |
200 | else escaped = mputc(escaped, c); |
201 | break; |
202 | case '&': |
203 | if (escape_chars&) escaped = mputstr(escaped, "&"); |
204 | else escaped = mputc(escaped, c); |
205 | break; |
206 | default: |
207 | escaped = mputc(escaped, c); |
208 | } |
209 | } |
210 | CHARSTRING ret_val(escaped); |
211 | Free(escaped); |
212 | return ret_val; |
213 | } |
214 | |
215 | void TestCase::writeTestCase(FILE* file_stream_) const{ |
216 | switch(verdict){ |
217 | case None: { |
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"); |
221 | break; } |
222 | case Fail: { |
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"); |
228 | break; } |
229 | case Error: { |
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"); |
233 | break; } |
234 | default: |
235 | fprintf(file_stream_, " <testcase classname='%s' name='%s' time='%f'/>\n", module_name.data(), tc_name.data(), time); |
236 | break; |
237 | } |
238 | fflush(file_stream_); |
239 | } |
240 | |
241 | void TestSuite::addTestCase(const TestCase& testcase) { |
242 | testcases.push_back(new TestCase(testcase)); |
243 | all++; |
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; |
249 | default: break; |
250 | } |
251 | } |
252 | |
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_); |
259 | |
260 | for (TestCases::const_iterator it = testcases.begin(); it != testcases.end(); ++it) { |
261 | (*it)->writeTestCase(file_stream_); |
262 | } |
263 | |
264 | fprintf(file_stream_, "</testsuite>\n"); |
265 | fflush(file_stream_); |
266 | } |
267 | |
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: |
273 | // should not happen |
274 | break; |
275 | |
276 | case TitanLoggerApi::Verdict::v0none: |
277 | verdict = TestCase::None; |
278 | break; |
279 | |
280 | case TitanLoggerApi::Verdict::v1pass: |
281 | verdict = TestCase::Pass; |
282 | break; |
283 | |
284 | case TitanLoggerApi::Verdict::v2inconc: |
285 | verdict = TestCase::Inconc; |
286 | break; |
287 | |
288 | case TitanLoggerApi::Verdict::v3fail: { |
289 | verdict = TestCase::Fail; |
290 | addStackTrace(event); |
291 | break; } |
292 | |
293 | case TitanLoggerApi::Verdict::v4error: |
294 | verdict = TestCase::Error; |
295 | break; |
296 | } |
297 | } |
298 | |
299 | void TestCase::addStackTrace(const TitanLoggerApi::TitanLogEvent& event){ |
300 | // Add a stack trace |
301 | const TitanLoggerApi::TitanLogEvent_sourceInfo__list& stack = event.sourceInfo__list(); |
302 | |
303 | int stack_depth = stack.size_of(); |
304 | for (int i=0; i < stack_depth; ++i) { |
305 | const TitanLoggerApi::LocationInfo& location = stack[i]; |
306 | |
307 | stack_trace += " "; |
308 | stack_trace += location.filename(); |
309 | stack_trace += ":"; |
310 | stack_trace += int2str(location.line()); |
311 | stack_trace += " "; |
312 | stack_trace += location.ent__name(); |
313 | stack_trace += " "; |
314 | |
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: |
319 | // can't happen |
320 | break; |
321 | case TitanLoggerApi::LocationInfo_ent__type::unknown: |
322 | // do nothing |
323 | break; |
324 | case TitanLoggerApi::LocationInfo_ent__type::controlpart: |
325 | stack_trace += "control part"; |
326 | break; |
327 | case TitanLoggerApi::LocationInfo_ent__type::testcase__: |
328 | stack_trace += "testcase"; |
329 | break; |
330 | case TitanLoggerApi::LocationInfo_ent__type::altstep__: |
331 | stack_trace += "altstep"; |
332 | break; |
333 | case TitanLoggerApi::LocationInfo_ent__type::function__: |
334 | stack_trace += "function"; |
335 | break; |
336 | case TitanLoggerApi::LocationInfo_ent__type::external__function: |
337 | stack_trace += "external function"; |
338 | break; |
339 | case TitanLoggerApi::LocationInfo_ent__type::template__: |
340 | stack_trace += "template"; |
341 | break; |
342 | } |
343 | |
344 | if(i<stack_depth-1) stack_trace += "\n"; |
345 | } |
346 | } |