Commit | Line | Data |
---|---|---|
970ed795 | 1 | /////////////////////////////////////////////////////////////////////////////// |
3abe9331 | 2 | // Copyright (c) 2000-2015 Ericsson Telecom AB |
970ed795 EL |
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 "JUnitLogger.hh" | |
9 | ||
10 | #ifndef TITAN_RUNTIME_2 | |
11 | #include "RT1/TitanLoggerApi.hh" | |
12 | #else | |
13 | #include "RT2/TitanLoggerApi.hh" | |
14 | #endif | |
15 | ||
16 | #include <unistd.h> | |
17 | #include <sys/types.h> | |
18 | ||
19 | #include <sys/time.h> | |
20 | ||
21 | extern "C" | |
22 | { | |
23 | // It's a static plug-in, destruction is done in the destructor. We need | |
24 | // `extern "C"' for some reason. | |
25 | ILoggerPlugin *create_junit_logger() { return new JUnitLogger(); } | |
26 | } | |
27 | ||
28 | extern "C" { | |
29 | ILoggerPlugin *create_plugin() { return new JUnitLogger(); } | |
30 | void destroy_plugin(ILoggerPlugin *plugin) { delete plugin; } | |
31 | } | |
32 | ||
33 | JUnitLogger::JUnitLogger() | |
34 | : filename_stem_(NULL), testsuite_name_(mcopystr("Titan")) | |
35 | , filename_(NULL), file_stream_(NULL) | |
36 | { | |
37 | // Overwrite values set by the base class constructor | |
38 | major_version_ = 1; | |
39 | minor_version_ = 0; | |
40 | name_ = mcopystr("JUnitLogger"); | |
41 | help_ = mcopystr("JUnitLogger writes JUnit-compatible XML"); | |
42 | //printf("%5lu:constructed\n", (unsigned long)getpid()); | |
43 | } | |
44 | ||
45 | JUnitLogger::~JUnitLogger() | |
46 | { | |
47 | //printf("%5lu:desstructed\n", (unsigned long)getpid()); | |
48 | close_file(); | |
49 | ||
50 | Free(name_); | |
51 | Free(help_); | |
52 | Free(filename_); | |
53 | Free(testsuite_name_); | |
54 | Free(filename_stem_); | |
55 | name_ = help_ = filename_ = filename_stem_ = NULL; | |
56 | file_stream_ = NULL; | |
57 | } | |
58 | ||
59 | void JUnitLogger::init(const char */*options*/) | |
60 | { | |
61 | //printf("%5lu:init\n", (unsigned long)getpid()); | |
62 | fprintf(stderr, "Initializing `%s' (v%u.%u): %s\n", name_, | |
63 | major_version_, minor_version_, help_); | |
64 | } | |
65 | ||
66 | void JUnitLogger::fini() | |
67 | { | |
68 | //puts("fini"); | |
69 | //fprintf(stderr, "JUnitLogger finished logging for PID: `%lu'\n", (unsigned long)getpid()); | |
70 | } | |
71 | ||
72 | void JUnitLogger::set_parameter( | |
73 | const char *parameter_name, const char *parameter_value) | |
74 | { | |
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 JUnitLogger::open_file(bool is_first) | |
91 | { | |
92 | if (is_first) { | |
93 | if (filename_stem_ == NULL) { | |
94 | filename_stem_ = mcopystr("junit-xml"); | |
95 | } | |
96 | } | |
97 | ||
98 | if (file_stream_ != NULL) return; // already open | |
99 | ||
100 | if (!TTCN_Runtime::is_single() | |
101 | && !TTCN_Runtime::is_mtc()) return; // don't bother, only MTC has testcases | |
102 | ||
103 | filename_ = mprintf("%s-%lu.log", filename_stem_, (unsigned long)getpid()); | |
104 | //printf("\n%5lu:open [%s]\n", (unsigned long)getpid(), filename_); | |
105 | ||
106 | file_stream_ = fopen(filename_, "w"); | |
107 | if (!file_stream_) { | |
108 | fprintf(stderr, "%s was unable to open log file `%s', reinitialization " | |
109 | "may help\n", plugin_name(), filename_); | |
110 | return; | |
111 | } | |
112 | ||
113 | is_configured_ = true; | |
114 | ||
115 | fprintf(file_stream_, | |
116 | "<?xml version=\"1.0\"?>\n" | |
117 | "<testsuite name='%s'>" | |
118 | // We don't know the number of tests yet; leave out the tests=... attrib | |
119 | "<!-- logger name=\"%s\" version=\"v%u.%u\" -->\n", | |
120 | testsuite_name_, | |
121 | plugin_name(), major_version(), minor_version()); | |
122 | fflush(file_stream_); | |
123 | } | |
124 | ||
125 | void JUnitLogger::close_file() | |
126 | { | |
127 | //printf("%5lu:close%c[%s]\n", (unsigned long)getpid(), file_stream_ ? ' ' : 'd', filename_ ? filename_ : "<none>"); | |
128 | if (file_stream_ != NULL) { | |
129 | fputs("</testsuite>\n", file_stream_); | |
130 | fflush(file_stream_); | |
131 | fclose(file_stream_); | |
132 | file_stream_ = NULL; | |
133 | } | |
134 | if (filename_) { | |
135 | Free(filename_); | |
136 | filename_ = NULL; | |
137 | } | |
138 | } | |
139 | ||
140 | void JUnitLogger::log(const TitanLoggerApi::TitanLogEvent& event, | |
141 | bool /*log_buffered*/, bool /*separate_file*/, | |
142 | bool /*use_emergency_mask*/) | |
143 | { | |
144 | //puts("log"); | |
145 | if (file_stream_ == NULL) return; | |
146 | static RInt seconds, microseconds; | |
147 | ||
148 | const TitanLoggerApi::LogEventType_choice& choice = event.logEvent().choice(); | |
149 | ||
150 | switch (choice.get_selection()) { | |
151 | case TitanLoggerApi::LogEventType_choice::ALT_testcaseOp: { | |
152 | const TitanLoggerApi::TestcaseEvent_choice& tcev = choice.testcaseOp().choice(); | |
153 | switch (tcev.get_selection()) { | |
154 | case TitanLoggerApi::TestcaseEvent_choice::ALT_testcaseStarted: { | |
155 | fprintf(file_stream_, "<!-- Testcase %s started -->\n", | |
156 | (const char*)tcev.testcaseStarted().testcase__name()); | |
157 | const TitanLoggerApi::TimestampType& ts = event.timestamp(); | |
158 | // remember the start time | |
159 | seconds = ts.seconds(); | |
160 | microseconds = ts.microSeconds(); | |
161 | break; } | |
162 | ||
163 | case TitanLoggerApi::TestcaseEvent_choice::ALT_testcaseFinished: { | |
164 | const TitanLoggerApi::TestcaseType& tct = tcev.testcaseFinished(); | |
165 | const TitanLoggerApi::TimestampType& ts = event.timestamp(); | |
166 | long long now = 1000000LL * (long long)ts.seconds() + (long long)ts.microSeconds(); | |
167 | long long then= 1000000LL * (long long) seconds + (long long) microseconds ; | |
168 | fprintf(file_stream_, "<!-- Testcase %s finished in %f, verdict: %s%s%s -->\n", | |
169 | (const char*)tct.name().testcase__name(), | |
170 | (now - then) / 1000000.0, | |
171 | verdict_name[tct.verdict()], | |
172 | (tct.reason().lengthof() > 0 ? ", reason: " : ""), | |
173 | (const char*)tct.reason()); | |
174 | ||
175 | fprintf(file_stream_, " <testcase classname='%s' name='%s' time='%f'>\n", | |
176 | (const char*)tct.name().module__name(), | |
177 | (const char*)tct.name().testcase__name(), | |
178 | (now - then) / 1000000.0); | |
179 | ||
180 | switch (tct.verdict()) { | |
181 | case TitanLoggerApi::Verdict::UNBOUND_VALUE: | |
182 | case TitanLoggerApi::Verdict::UNKNOWN_VALUE: | |
183 | // should not happen | |
184 | break; | |
185 | ||
186 | case TitanLoggerApi::Verdict::v0none: | |
187 | fprintf(file_stream_, " <skipped>no verdict</skipped>\n"); | |
188 | break; | |
189 | ||
190 | case TitanLoggerApi::Verdict::v1pass: | |
191 | // do nothing; this counts as success | |
192 | break; | |
193 | ||
194 | case TitanLoggerApi::Verdict::v2inconc: | |
195 | // JUnit doesn't seem to have the concept of "inconclusive" | |
196 | // do nothing; this will appear as success | |
197 | break; | |
198 | ||
199 | case TitanLoggerApi::Verdict::v3fail: { | |
200 | fprintf(file_stream_, " <failure type='fail-verdict'>%s\n", | |
201 | (const char*)escape_xml_element(tct.reason())); | |
202 | ||
203 | // Add a stack trace | |
204 | const TitanLoggerApi::TitanLogEvent_sourceInfo__list& stack = | |
205 | event.sourceInfo__list(); | |
206 | int stack_depth = stack.size_of(); | |
207 | for (int i=0; i < stack_depth; ++i) { | |
208 | const TitanLoggerApi::LocationInfo& location = stack[i]; | |
209 | fprintf(file_stream_, "\n %s:%d %s ", | |
210 | (const char*)location.filename(), (int)location.line(), | |
211 | (const char*)location.ent__name()); | |
212 | ||
213 | // print the location type | |
214 | switch (location.ent__type()) { | |
215 | case TitanLoggerApi::LocationInfo_ent__type:: UNKNOWN_VALUE: | |
216 | case TitanLoggerApi::LocationInfo_ent__type:: UNBOUND_VALUE: | |
217 | // can't happen | |
218 | break; | |
219 | case TitanLoggerApi::LocationInfo_ent__type::unknown: | |
220 | // do nothing | |
221 | break; | |
222 | case TitanLoggerApi::LocationInfo_ent__type::controlpart: | |
223 | fputs("control part", file_stream_); | |
224 | break; | |
225 | case TitanLoggerApi::LocationInfo_ent__type::testcase__: | |
226 | fputs("testcase", file_stream_); | |
227 | break; | |
228 | case TitanLoggerApi::LocationInfo_ent__type::altstep__: | |
229 | fputs("altstep", file_stream_); | |
230 | break; | |
231 | case TitanLoggerApi::LocationInfo_ent__type::function__: | |
232 | fputs("function", file_stream_); | |
233 | break; | |
234 | case TitanLoggerApi::LocationInfo_ent__type::external__function: | |
235 | fputs("external function", file_stream_); | |
236 | break; | |
237 | case TitanLoggerApi::LocationInfo_ent__type::template__: | |
238 | fputs("template", file_stream_); | |
239 | break; | |
240 | } | |
241 | } | |
242 | fputs("\n </failure>\n", file_stream_); | |
243 | break; } | |
244 | ||
245 | case TitanLoggerApi::Verdict::v4error: | |
246 | fprintf(file_stream_, " <error type='DTE'>%s</error>\n", | |
247 | (const char*)escape_xml_element(tct.reason())); | |
248 | break; | |
249 | } | |
250 | // error or skip based on verdict | |
251 | fputs(" </testcase>\n", file_stream_); | |
252 | break; } | |
253 | ||
254 | case TitanLoggerApi::TestcaseEvent_choice::UNBOUND_VALUE: | |
255 | fputs("<!-- Unbound testcaseOp.choice !! -->\n", file_stream_); | |
256 | break; | |
257 | } // switch testcaseOp().choice.get_selection() | |
258 | ||
259 | break; } // testcaseOp | |
260 | ||
261 | case TitanLoggerApi::LogEventType_choice::ALT_errorLog: { | |
262 | // A DTE is about to be thrown | |
263 | const TitanLoggerApi::Categorized& cat = choice.errorLog(); | |
264 | fprintf(file_stream_, " <error type='DTE'>%s</error>\n", | |
265 | (const char*)escape_xml_element(cat.text())); | |
266 | break; } | |
267 | ||
268 | default: | |
269 | break; // NOP | |
270 | } // switch event.logEvent().choice().get_selection() | |
271 | ||
272 | fflush(file_stream_); | |
273 | } | |
274 | ||
275 | CHARSTRING JUnitLogger::escape_xml(const CHARSTRING& xml_str, int escape_chars) | |
276 | { | |
277 | expstring_t escaped = NULL; | |
278 | int len = xml_str.lengthof(); | |
279 | for (int i=0; i<len; i++) { | |
280 | char c = *(((const char*)xml_str)+i); | |
281 | switch (c) { | |
282 | case '<': | |
283 | if (escape_chars<) escaped = mputstr(escaped, "<"); | |
284 | else escaped = mputc(escaped, c); | |
285 | break; | |
286 | case '>': | |
287 | if (escape_chars>) escaped = mputstr(escaped, ">"); | |
288 | else escaped = mputc(escaped, c); | |
289 | break; | |
290 | case '"': | |
291 | if (escape_chars") escaped = mputstr(escaped, """); | |
292 | else escaped = mputc(escaped, c); | |
293 | break; | |
294 | case '\'': | |
295 | if (escape_chars&APOS) escaped = mputstr(escaped, "'"); | |
296 | else escaped = mputc(escaped, c); | |
297 | break; | |
298 | case '&': | |
299 | if (escape_chars&) escaped = mputstr(escaped, "&"); | |
300 | else escaped = mputc(escaped, c); | |
301 | break; | |
302 | default: | |
303 | escaped = mputc(escaped, c); | |
304 | } | |
305 | } | |
306 | CHARSTRING ret_val(escaped); | |
307 | Free(escaped); | |
308 | return ret_val; | |
309 | } |