xref: /freebsd/contrib/kyua/engine/atf_result.cpp (revision bf2fa8d9d11c9f2ceff09bacc406876fa37096be)
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 /// '&lt;&lt;NEWLINE&gt;&gt;'.
69 static std::pair< size_t, std::string >
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
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
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 >
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
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
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.
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.
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.
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
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
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
351 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 >&
361 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 >&
371 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
381 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
419 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
516 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
551 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
564 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&
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
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