1 // Copyright 2010 The Kyua Authors.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 // may be used to endorse or promote products derived from this software
15 // without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 #include "engine/atf_result.hpp"
30
31 #include <cstdlib>
32 #include <fstream>
33 #include <utility>
34
35 #include "engine/exceptions.hpp"
36 #include "model/test_result.hpp"
37 #include "utils/fs/path.hpp"
38 #include "utils/format/macros.hpp"
39 #include "utils/optional.ipp"
40 #include "utils/process/status.hpp"
41 #include "utils/sanity.hpp"
42 #include "utils/text/exceptions.hpp"
43 #include "utils/text/operations.ipp"
44
45 namespace fs = utils::fs;
46 namespace process = utils::process;
47 namespace text = utils::text;
48
49 using utils::none;
50 using utils::optional;
51
52
53 namespace {
54
55
56 /// Reads a file and flattens its lines.
57 ///
58 /// The main purpose of this function is to simplify the parsing of a file
59 /// containing the result of a test. Therefore, the return value carries
60 /// several assumptions.
61 ///
62 /// \param input The stream to read from.
63 ///
64 /// \return A pair (line count, contents) detailing how many lines where read
65 /// and their contents. If the file contains a single line with no newline
66 /// character, the line count is 0. If the file includes more than one line,
67 /// the lines are merged together and separated by the magic string
68 /// '<<NEWLINE>>'.
69 static std::pair< size_t, std::string >
read_lines(std::istream & input)70 read_lines(std::istream& input)
71 {
72 std::pair< size_t, std::string > ret = std::make_pair(0, "");
73
74 do {
75 std::string line;
76 std::getline(input, line);
77 if (input.eof() && !line.empty()) {
78 if (ret.first == 0)
79 ret.second = line;
80 else {
81 ret.second += "<<NEWLINE>>" + line;
82 ret.first++;
83 }
84 } else if (input.good()) {
85 if (ret.first == 0)
86 ret.second = line;
87 else
88 ret.second += "<<NEWLINE>>" + line;
89 ret.first++;
90 }
91 } while (input.good());
92
93 return ret;
94 }
95
96
97 /// Parses a test result that does not accept a reason.
98 ///
99 /// \param status The result status name.
100 /// \param rest The rest of the line after the status name.
101 ///
102 /// \return An object representing the test result.
103 ///
104 /// \throw format_error If the result is invalid (i.e. rest is invalid).
105 ///
106 /// \pre status must be "passed".
107 static engine::atf_result
parse_without_reason(const std::string & status,const std::string & rest)108 parse_without_reason(const std::string& status, const std::string& rest)
109 {
110 if (!rest.empty())
111 throw engine::format_error(F("%s cannot have a reason") % status);
112 PRE(status == "passed");
113 return engine::atf_result(engine::atf_result::passed);
114 }
115
116
117 /// Parses a test result that needs a reason.
118 ///
119 /// \param status The result status name.
120 /// \param rest The rest of the line after the status name.
121 ///
122 /// \return An object representing the test result.
123 ///
124 /// \throw format_error If the result is invalid (i.e. rest is invalid).
125 ///
126 /// \pre status must be one of "broken", "expected_death", "expected_failure",
127 /// "expected_timeout", "failed" or "skipped".
128 static engine::atf_result
parse_with_reason(const std::string & status,const std::string & rest)129 parse_with_reason(const std::string& status, const std::string& rest)
130 {
131 using engine::atf_result;
132
133 if (rest.length() < 3 || rest.substr(0, 2) != ": ")
134 throw engine::format_error(F("%s must be followed by ': <reason>'") %
135 status);
136 const std::string reason = rest.substr(2);
137 INV(!reason.empty());
138
139 if (status == "broken")
140 return atf_result(atf_result::broken, reason);
141 else if (status == "expected_death")
142 return atf_result(atf_result::expected_death, reason);
143 else if (status == "expected_failure")
144 return atf_result(atf_result::expected_failure, reason);
145 else if (status == "expected_timeout")
146 return atf_result(atf_result::expected_timeout, reason);
147 else if (status == "failed")
148 return atf_result(atf_result::failed, reason);
149 else if (status == "skipped")
150 return atf_result(atf_result::skipped, reason);
151 else
152 PRE_MSG(false, "Unexpected status");
153 }
154
155
156 /// Converts a string to an integer.
157 ///
158 /// \param str The string containing the integer to convert.
159 ///
160 /// \return The converted integer; none if the parsing fails.
161 static optional< int >
parse_int(const std::string & str)162 parse_int(const std::string& str)
163 {
164 try {
165 return utils::make_optional(text::to_type< int >(str));
166 } catch (const text::value_error& e) {
167 return none;
168 }
169 }
170
171
172 /// Parses a test result that needs a reason and accepts an optional integer.
173 ///
174 /// \param status The result status name.
175 /// \param rest The rest of the line after the status name.
176 ///
177 /// \return The parsed test result if the data is valid, or a broken result if
178 /// the parsing failed.
179 ///
180 /// \pre status must be one of "expected_exit" or "expected_signal".
181 static engine::atf_result
parse_with_reason_and_arg(const std::string & status,const std::string & rest)182 parse_with_reason_and_arg(const std::string& status, const std::string& rest)
183 {
184 using engine::atf_result;
185
186 std::string::size_type delim = rest.find_first_of(":(");
187 if (delim == std::string::npos)
188 throw engine::format_error(F("Invalid format for '%s' test case "
189 "result; must be followed by '[(num)]: "
190 "<reason>' but found '%s'") %
191 status % rest);
192
193 optional< int > arg;
194 if (rest[delim] == '(') {
195 const std::string::size_type delim2 = rest.find("):", delim);
196 if (delim == std::string::npos)
197 throw engine::format_error(F("Mismatched '(' in %s") % rest);
198
199 const std::string argstr = rest.substr(delim + 1, delim2 - delim - 1);
200 arg = parse_int(argstr);
201 if (!arg)
202 throw engine::format_error(F("Invalid integer argument '%s' to "
203 "'%s' test case result") %
204 argstr % status);
205 delim = delim2 + 1;
206 }
207
208 const std::string reason = rest.substr(delim + 2);
209
210 if (status == "expected_exit")
211 return atf_result(atf_result::expected_exit, arg, reason);
212 else if (status == "expected_signal")
213 return atf_result(atf_result::expected_signal, arg, reason);
214 else
215 PRE_MSG(false, "Unexpected status");
216 }
217
218
219 /// Formats the termination status of a process to be used with validate_result.
220 ///
221 /// \param status The status to format.
222 ///
223 /// \return A string describing the status.
224 static std::string
format_status(const process::status & status)225 format_status(const process::status& status)
226 {
227 if (status.exited())
228 return F("exited with code %s") % status.exitstatus();
229 else if (status.signaled())
230 return F("received signal %s%s") % status.termsig() %
231 (status.coredump() ? " (core dumped)" : "");
232 else
233 return F("terminated in an unknown manner");
234 }
235
236
237 } // anonymous namespace
238
239
240 /// Constructs a raw result with a type.
241 ///
242 /// The reason and the argument are left uninitialized.
243 ///
244 /// \param type_ The type of the result.
atf_result(const types type_)245 engine::atf_result::atf_result(const types type_) :
246 _type(type_)
247 {
248 }
249
250
251 /// Constructs a raw result with a type and a reason.
252 ///
253 /// The argument is left uninitialized.
254 ///
255 /// \param type_ The type of the result.
256 /// \param reason_ The reason for the result.
atf_result(const types type_,const std::string & reason_)257 engine::atf_result::atf_result(const types type_, const std::string& reason_) :
258 _type(type_), _reason(reason_)
259 {
260 }
261
262
263 /// Constructs a raw result with a type, an optional argument and a reason.
264 ///
265 /// \param type_ The type of the result.
266 /// \param argument_ The optional argument for the result.
267 /// \param reason_ The reason for the result.
atf_result(const types type_,const utils::optional<int> & argument_,const std::string & reason_)268 engine::atf_result::atf_result(const types type_,
269 const utils::optional< int >& argument_,
270 const std::string& reason_) :
271 _type(type_), _argument(argument_), _reason(reason_)
272 {
273 }
274
275
276 /// Parses an input stream to extract a test result.
277 ///
278 /// If the parsing fails for any reason, the test result is 'broken' and it
279 /// contains the reason for the parsing failure. Test cases that report results
280 /// in an inconsistent state cannot be trusted (e.g. the test program code may
281 /// have a bug), and thus why they are reported as broken instead of just failed
282 /// (which is a legitimate result for a test case).
283 ///
284 /// \param input The stream to read from.
285 ///
286 /// \return A generic representation of the result of the test case.
287 ///
288 /// \throw format_error If the input is invalid.
289 engine::atf_result
parse(std::istream & input)290 engine::atf_result::parse(std::istream& input)
291 {
292 const std::pair< size_t, std::string > data = read_lines(input);
293 if (data.first == 0)
294 throw format_error("Empty test result or no new line");
295 else if (data.first > 1)
296 throw format_error("Test result contains multiple lines: " +
297 data.second);
298 else {
299 const std::string::size_type delim = data.second.find_first_not_of(
300 "abcdefghijklmnopqrstuvwxyz_");
301 const std::string status = data.second.substr(0, delim);
302 const std::string rest = data.second.substr(status.length());
303
304 if (status == "broken")
305 return parse_with_reason(status, rest);
306 else if (status == "expected_death")
307 return parse_with_reason(status, rest);
308 else if (status == "expected_exit")
309 return parse_with_reason_and_arg(status, rest);
310 else if (status == "expected_failure")
311 return parse_with_reason(status, rest);
312 else if (status == "expected_signal")
313 return parse_with_reason_and_arg(status, rest);
314 else if (status == "expected_timeout")
315 return parse_with_reason(status, rest);
316 else if (status == "failed")
317 return parse_with_reason(status, rest);
318 else if (status == "passed")
319 return parse_without_reason(status, rest);
320 else if (status == "skipped")
321 return parse_with_reason(status, rest);
322 else
323 throw format_error(F("Unknown test result '%s'") % status);
324 }
325 }
326
327
328 /// Loads a test case result from a file.
329 ///
330 /// \param file The file to parse.
331 ///
332 /// \return The parsed test case result if all goes well.
333 ///
334 /// \throw std::runtime_error If the file does not exist.
335 /// \throw engine::format_error If the contents of the file are bogus.
336 engine::atf_result
load(const fs::path & file)337 engine::atf_result::load(const fs::path& file)
338 {
339 std::ifstream input(file.c_str());
340 if (!input)
341 throw std::runtime_error("Cannot open results file");
342 else
343 return parse(input);
344 }
345
346
347 /// Gets the type of the result.
348 ///
349 /// \return A result type.
350 engine::atf_result::types
type(void) const351 engine::atf_result::type(void) const
352 {
353 return _type;
354 }
355
356
357 /// Gets the optional argument of the result.
358 ///
359 /// \return The argument of the result if present; none otherwise.
360 const optional< int >&
argument(void) const361 engine::atf_result::argument(void) const
362 {
363 return _argument;
364 }
365
366
367 /// Gets the optional reason of the result.
368 ///
369 /// \return The reason of the result if present; none otherwise.
370 const optional< std::string >&
reason(void) const371 engine::atf_result::reason(void) const
372 {
373 return _reason;
374 }
375
376
377 /// Checks whether the result should be reported as good or not.
378 ///
379 /// \return True if the result can be considered "good", false otherwise.
380 bool
good(void) const381 engine::atf_result::good(void) const
382 {
383 switch (_type) {
384 case atf_result::expected_death:
385 case atf_result::expected_exit:
386 case atf_result::expected_failure:
387 case atf_result::expected_signal:
388 case atf_result::expected_timeout:
389 case atf_result::passed:
390 case atf_result::skipped:
391 return true;
392
393 case atf_result::broken:
394 case atf_result::failed:
395 return false;
396
397 default:
398 UNREACHABLE;
399 }
400 }
401
402
403 /// Reinterprets a raw result based on the termination status of the test case.
404 ///
405 /// This reinterpretation ensures that the termination conditions of the program
406 /// match what is expected of the paticular result reported by the test program.
407 /// If such conditions do not match, the test program is considered bogus and is
408 /// thus reported as broken.
409 ///
410 /// This is just a helper function for calculate_result(); the real result of
411 /// the test case cannot be inferred from apply() only.
412 ///
413 /// \param status The exit status of the test program, or none if the test
414 /// program timed out.
415 ///
416 /// \result The adjusted result. The original result is transformed into broken
417 /// if the exit status of the program does not match our expectations.
418 engine::atf_result
apply(const optional<process::status> & status) const419 engine::atf_result::apply(const optional< process::status >& status)
420 const
421 {
422 if (!status) {
423 if (_type != atf_result::expected_timeout)
424 return atf_result(atf_result::broken, "Test case body timed out");
425 else
426 return *this;
427 }
428
429 INV(status);
430 switch (_type) {
431 case atf_result::broken:
432 return *this;
433
434 case atf_result::expected_death:
435 return *this;
436
437 case atf_result::expected_exit:
438 if (status.get().exited()) {
439 if (_argument) {
440 if (_argument.get() == status.get().exitstatus())
441 return *this;
442 else
443 return atf_result(
444 atf_result::failed,
445 F("Test case expected to exit with code %s but got "
446 "code %s") %
447 _argument.get() % status.get().exitstatus());
448 } else
449 return *this;
450 } else
451 return atf_result(atf_result::broken, "Expected clean exit but " +
452 format_status(status.get()));
453
454 case atf_result::expected_failure:
455 if (status.get().exited() && status.get().exitstatus() == EXIT_SUCCESS)
456 return *this;
457 else
458 return atf_result(atf_result::broken, "Expected failure should "
459 "have reported success but " +
460 format_status(status.get()));
461
462 case atf_result::expected_signal:
463 if (status.get().signaled()) {
464 if (_argument) {
465 if (_argument.get() == status.get().termsig())
466 return *this;
467 else
468 return atf_result(
469 atf_result::failed,
470 F("Test case expected to receive signal %s but "
471 "got %s") %
472 _argument.get() % status.get().termsig());
473 } else
474 return *this;
475 } else
476 return atf_result(atf_result::broken, "Expected signal but " +
477 format_status(status.get()));
478
479 case atf_result::expected_timeout:
480 return atf_result(atf_result::broken, "Expected timeout but " +
481 format_status(status.get()));
482
483 case atf_result::failed:
484 if (status.get().exited() && status.get().exitstatus() == EXIT_FAILURE)
485 return *this;
486 else
487 return atf_result(atf_result::broken, "Failed test case should "
488 "have reported failure but " +
489 format_status(status.get()));
490
491 case atf_result::passed:
492 if (status.get().exited() && status.get().exitstatus() == EXIT_SUCCESS)
493 return *this;
494 else
495 return atf_result(atf_result::broken, "Passed test case should "
496 "have reported success but " +
497 format_status(status.get()));
498
499 case atf_result::skipped:
500 if (status.get().exited() && status.get().exitstatus() == EXIT_SUCCESS)
501 return *this;
502 else
503 return atf_result(atf_result::broken, "Skipped test case should "
504 "have reported success but " +
505 format_status(status.get()));
506 }
507
508 UNREACHABLE;
509 }
510
511
512 /// Converts an internal result to the interface-agnostic representation.
513 ///
514 /// \return A generic result instance representing this result.
515 model::test_result
externalize(void) const516 engine::atf_result::externalize(void) const
517 {
518 switch (_type) {
519 case atf_result::broken:
520 return model::test_result(model::test_result_broken, _reason.get());
521
522 case atf_result::expected_death:
523 case atf_result::expected_exit:
524 case atf_result::expected_failure:
525 case atf_result::expected_signal:
526 case atf_result::expected_timeout:
527 return model::test_result(model::test_result_expected_failure,
528 _reason.get());
529
530 case atf_result::failed:
531 return model::test_result(model::test_result_failed, _reason.get());
532
533 case atf_result::passed:
534 return model::test_result(model::test_result_passed);
535
536 case atf_result::skipped:
537 return model::test_result(model::test_result_skipped, _reason.get());
538
539 default:
540 UNREACHABLE;
541 }
542 }
543
544
545 /// Compares two raw results for equality.
546 ///
547 /// \param other The result to compare to.
548 ///
549 /// \return True if the two raw results are equal; false otherwise.
550 bool
operator ==(const atf_result & other) const551 engine::atf_result::operator==(const atf_result& other) const
552 {
553 return _type == other._type && _argument == other._argument &&
554 _reason == other._reason;
555 }
556
557
558 /// Compares two raw results for inequality.
559 ///
560 /// \param other The result to compare to.
561 ///
562 /// \return True if the two raw results are different; false otherwise.
563 bool
operator !=(const atf_result & other) const564 engine::atf_result::operator!=(const atf_result& other) const
565 {
566 return !(*this == other);
567 }
568
569
570 /// Injects the object into a stream.
571 ///
572 /// \param output The stream into which to inject the object.
573 /// \param object The object to format.
574 ///
575 /// \return The output stream.
576 std::ostream&
operator <<(std::ostream & output,const atf_result & object)577 engine::operator<<(std::ostream& output, const atf_result& object)
578 {
579 std::string result_name;
580 switch (object.type()) {
581 case atf_result::broken: result_name = "broken"; break;
582 case atf_result::expected_death: result_name = "expected_death"; break;
583 case atf_result::expected_exit: result_name = "expected_exit"; break;
584 case atf_result::expected_failure: result_name = "expected_failure"; break;
585 case atf_result::expected_signal: result_name = "expected_signal"; break;
586 case atf_result::expected_timeout: result_name = "expected_timeout"; break;
587 case atf_result::failed: result_name = "failed"; break;
588 case atf_result::passed: result_name = "passed"; break;
589 case atf_result::skipped: result_name = "skipped"; break;
590 }
591
592 const optional< int >& argument = object.argument();
593
594 const optional< std::string >& reason = object.reason();
595
596 output << F("model::test_result{type=%s, argument=%s, reason=%s}")
597 % text::quote(result_name, '\'')
598 % (argument ? (F("%s") % argument.get()).str() : "none")
599 % (reason ? text::quote(reason.get(), '\'') : "none");
600
601 return output;
602 }
603
604
605 /// Calculates the user-visible result of a test case.
606 ///
607 /// This function needs to perform magic to ensure that what the test case
608 /// reports as its result is what the user should really see: i.e. it adjusts
609 /// the reported status of the test to the exit conditions of its body and
610 /// cleanup parts.
611 ///
612 /// \param body_status The termination status of the process that executed
613 /// the body of the test. None if the body timed out.
614 /// \param results_file The path to the results file that the test case body is
615 /// supposed to have created.
616 ///
617 /// \return The calculated test case result.
618 model::test_result
calculate_atf_result(const optional<process::status> & body_status,const fs::path & results_file)619 engine::calculate_atf_result(const optional< process::status >& body_status,
620 const fs::path& results_file)
621 {
622 using engine::atf_result;
623
624 atf_result result(atf_result::broken, "Unknown result");
625 try {
626 result = atf_result::load(results_file);
627 } catch (const engine::format_error& error) {
628 result = atf_result(atf_result::broken, error.what());
629 } catch (const std::runtime_error& error) {
630 if (body_status)
631 result = atf_result(
632 atf_result::broken, F("Premature exit; test case %s") %
633 format_status(body_status.get()));
634 else {
635 // The test case timed out. apply() handles this case later.
636 }
637 }
638
639 result = result.apply(body_status);
640
641 return result.externalize();
642 }
643