xref: /freebsd-src/contrib/kyua/engine/tap.cpp (revision 47fb5d2b13c5ca8e1a3b05ed223d62ea4b43b3e8)
1b0d29bc4SBrooks Davis // Copyright 2015 The Kyua Authors.
2b0d29bc4SBrooks Davis // All rights reserved.
3b0d29bc4SBrooks Davis //
4b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without
5b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are
6b0d29bc4SBrooks Davis // met:
7b0d29bc4SBrooks Davis //
8b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright
9b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer.
10b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
11b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer in the
12b0d29bc4SBrooks Davis //   documentation and/or other materials provided with the distribution.
13b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
14b0d29bc4SBrooks Davis //   may be used to endorse or promote products derived from this software
15b0d29bc4SBrooks Davis //   without specific prior written permission.
16b0d29bc4SBrooks Davis //
17b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28b0d29bc4SBrooks Davis 
29b0d29bc4SBrooks Davis #include "engine/tap.hpp"
30b0d29bc4SBrooks Davis 
31b0d29bc4SBrooks Davis extern "C" {
32b0d29bc4SBrooks Davis #include <unistd.h>
33b0d29bc4SBrooks Davis }
34b0d29bc4SBrooks Davis 
35b0d29bc4SBrooks Davis #include <cstdlib>
36b0d29bc4SBrooks Davis 
37b0d29bc4SBrooks Davis #include "engine/exceptions.hpp"
38257e70f1SIgor Ostapenko #include "engine/execenv/execenv.hpp"
39b0d29bc4SBrooks Davis #include "engine/tap_parser.hpp"
40b0d29bc4SBrooks Davis #include "model/test_case.hpp"
41b0d29bc4SBrooks Davis #include "model/test_program.hpp"
42b0d29bc4SBrooks Davis #include "model/test_result.hpp"
43b0d29bc4SBrooks Davis #include "utils/defs.hpp"
44b0d29bc4SBrooks Davis #include "utils/env.hpp"
45b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
46b0d29bc4SBrooks Davis #include "utils/optional.ipp"
47b0d29bc4SBrooks Davis #include "utils/process/operations.hpp"
48b0d29bc4SBrooks Davis #include "utils/process/status.hpp"
49b0d29bc4SBrooks Davis #include "utils/sanity.hpp"
50b0d29bc4SBrooks Davis 
51b0d29bc4SBrooks Davis namespace config = utils::config;
52257e70f1SIgor Ostapenko namespace execenv = engine::execenv;
53b0d29bc4SBrooks Davis namespace fs = utils::fs;
54b0d29bc4SBrooks Davis namespace process = utils::process;
55b0d29bc4SBrooks Davis 
56b0d29bc4SBrooks Davis using utils::optional;
57b0d29bc4SBrooks Davis 
58b0d29bc4SBrooks Davis 
59b0d29bc4SBrooks Davis namespace {
60b0d29bc4SBrooks Davis 
61b0d29bc4SBrooks Davis 
62b0d29bc4SBrooks Davis /// Computes the result of a TAP test program termination.
63b0d29bc4SBrooks Davis ///
64b0d29bc4SBrooks Davis /// Timeouts and bad TAP data must be handled by the caller.  Here we assume
65b0d29bc4SBrooks Davis /// that we have been able to successfully parse the TAP output.
66b0d29bc4SBrooks Davis ///
67b0d29bc4SBrooks Davis /// \param summary Parsed TAP data for the test program.
68b0d29bc4SBrooks Davis /// \param status Exit status of the test program.
69b0d29bc4SBrooks Davis ///
70b0d29bc4SBrooks Davis /// \return A test result.
71b0d29bc4SBrooks Davis static model::test_result
72b0d29bc4SBrooks Davis tap_to_result(const engine::tap_summary& summary,
73b0d29bc4SBrooks Davis               const process::status& status)
74b0d29bc4SBrooks Davis {
75b0d29bc4SBrooks Davis     if (summary.bailed_out()) {
76b0d29bc4SBrooks Davis         return model::test_result(model::test_result_failed, "Bailed out");
77b0d29bc4SBrooks Davis     }
78b0d29bc4SBrooks Davis 
79b0d29bc4SBrooks Davis     if (summary.plan() == engine::all_skipped_plan) {
80b0d29bc4SBrooks Davis         return model::test_result(model::test_result_skipped,
81b0d29bc4SBrooks Davis                                   summary.all_skipped_reason());
82b0d29bc4SBrooks Davis     }
83b0d29bc4SBrooks Davis 
84b0d29bc4SBrooks Davis     if (summary.not_ok_count() == 0) {
85b0d29bc4SBrooks Davis         if (status.exitstatus() == EXIT_SUCCESS) {
86b0d29bc4SBrooks Davis             return model::test_result(model::test_result_passed);
87b0d29bc4SBrooks Davis         } else {
88b0d29bc4SBrooks Davis             return model::test_result(
89b0d29bc4SBrooks Davis                 model::test_result_broken,
90b0d29bc4SBrooks Davis                 F("Dubious test program: reported all tests as passed "
91b0d29bc4SBrooks Davis                   "but returned exit code %s") % status.exitstatus());
92b0d29bc4SBrooks Davis         }
93b0d29bc4SBrooks Davis     } else {
94b0d29bc4SBrooks Davis         const std::size_t total = summary.ok_count() + summary.not_ok_count();
95b0d29bc4SBrooks Davis         return model::test_result(model::test_result_failed,
96b0d29bc4SBrooks Davis                                   F("%s of %s tests failed") %
97b0d29bc4SBrooks Davis                                   summary.not_ok_count() % total);
98b0d29bc4SBrooks Davis     }
99b0d29bc4SBrooks Davis }
100b0d29bc4SBrooks Davis 
101b0d29bc4SBrooks Davis 
102b0d29bc4SBrooks Davis }  // anonymous namespace
103b0d29bc4SBrooks Davis 
104b0d29bc4SBrooks Davis 
105b0d29bc4SBrooks Davis /// Executes a test program's list operation.
106b0d29bc4SBrooks Davis ///
107b0d29bc4SBrooks Davis /// This method is intended to be called within a subprocess and is expected
108b0d29bc4SBrooks Davis /// to terminate execution either by exec(2)ing the test program or by
109b0d29bc4SBrooks Davis /// exiting with a failure.
110b0d29bc4SBrooks Davis void
111b0d29bc4SBrooks Davis engine::tap_interface::exec_list(
112b0d29bc4SBrooks Davis     const model::test_program& /* test_program */,
113b0d29bc4SBrooks Davis     const config::properties_map& /* vars */) const
114b0d29bc4SBrooks Davis {
115b0d29bc4SBrooks Davis     ::_exit(EXIT_SUCCESS);
116b0d29bc4SBrooks Davis }
117b0d29bc4SBrooks Davis 
118b0d29bc4SBrooks Davis 
119b0d29bc4SBrooks Davis /// Computes the test cases list of a test program.
120b0d29bc4SBrooks Davis ///
121b0d29bc4SBrooks Davis /// \return A list of test cases.
122b0d29bc4SBrooks Davis model::test_cases_map
123b0d29bc4SBrooks Davis engine::tap_interface::parse_list(
124b0d29bc4SBrooks Davis     const optional< process::status >& /* status */,
125b0d29bc4SBrooks Davis     const fs::path& /* stdout_path */,
126b0d29bc4SBrooks Davis     const fs::path& /* stderr_path */) const
127b0d29bc4SBrooks Davis {
128b0d29bc4SBrooks Davis     return model::test_cases_map_builder().add("main").build();
129b0d29bc4SBrooks Davis }
130b0d29bc4SBrooks Davis 
131b0d29bc4SBrooks Davis 
132b0d29bc4SBrooks Davis /// Executes a test case of the test program.
133b0d29bc4SBrooks Davis ///
134b0d29bc4SBrooks Davis /// This method is intended to be called within a subprocess and is expected
135b0d29bc4SBrooks Davis /// to terminate execution either by exec(2)ing the test program or by
136b0d29bc4SBrooks Davis /// exiting with a failure.
137b0d29bc4SBrooks Davis ///
138b0d29bc4SBrooks Davis /// \param test_program The test program to execute.
139b0d29bc4SBrooks Davis /// \param test_case_name Name of the test case to invoke.
140b0d29bc4SBrooks Davis /// \param vars User-provided variables to pass to the test program.
141b0d29bc4SBrooks Davis void
142b0d29bc4SBrooks Davis engine::tap_interface::exec_test(
143b0d29bc4SBrooks Davis     const model::test_program& test_program,
144b0d29bc4SBrooks Davis     const std::string& test_case_name,
145b0d29bc4SBrooks Davis     const config::properties_map& vars,
146b0d29bc4SBrooks Davis     const fs::path& /* control_directory */) const
147b0d29bc4SBrooks Davis {
148b0d29bc4SBrooks Davis     PRE(test_case_name == "main");
149b0d29bc4SBrooks Davis 
150b0d29bc4SBrooks Davis     for (config::properties_map::const_iterator iter = vars.begin();
151b0d29bc4SBrooks Davis          iter != vars.end(); ++iter) {
152b0d29bc4SBrooks Davis         utils::setenv(F("TEST_ENV_%s") % (*iter).first, (*iter).second);
153b0d29bc4SBrooks Davis     }
154b0d29bc4SBrooks Davis 
155b0d29bc4SBrooks Davis     process::args_vector args;
156257e70f1SIgor Ostapenko 
157257e70f1SIgor Ostapenko     auto e = execenv::get(test_program, test_case_name);
158257e70f1SIgor Ostapenko     e->init();
159257e70f1SIgor Ostapenko     e->exec(args);
160*47fb5d2bSBrooks Davis     __builtin_unreachable();
161b0d29bc4SBrooks Davis }
162b0d29bc4SBrooks Davis 
163b0d29bc4SBrooks Davis 
164b0d29bc4SBrooks Davis /// Computes the result of a test case based on its termination status.
165b0d29bc4SBrooks Davis ///
166b0d29bc4SBrooks Davis /// \param status The termination status of the subprocess used to execute
167b0d29bc4SBrooks Davis ///     the exec_test() method or none if the test timed out.
168b0d29bc4SBrooks Davis /// \param stdout_path Path to the file containing the stdout of the test.
169b0d29bc4SBrooks Davis ///
170b0d29bc4SBrooks Davis /// \return A test result.
171b0d29bc4SBrooks Davis model::test_result
172b0d29bc4SBrooks Davis engine::tap_interface::compute_result(
173b0d29bc4SBrooks Davis     const optional< process::status >& status,
174b0d29bc4SBrooks Davis     const fs::path& /* control_directory */,
175b0d29bc4SBrooks Davis     const fs::path& stdout_path,
176b0d29bc4SBrooks Davis     const fs::path& /* stderr_path */) const
177b0d29bc4SBrooks Davis {
178b0d29bc4SBrooks Davis     if (!status) {
179b0d29bc4SBrooks Davis         return model::test_result(model::test_result_broken,
180b0d29bc4SBrooks Davis                                   "Test case timed out");
181b0d29bc4SBrooks Davis     } else {
182b0d29bc4SBrooks Davis         if (status.get().signaled()) {
183b0d29bc4SBrooks Davis             return model::test_result(
184b0d29bc4SBrooks Davis                 model::test_result_broken,
185b0d29bc4SBrooks Davis                 F("Received signal %s") % status.get().termsig());
186b0d29bc4SBrooks Davis         } else {
187b0d29bc4SBrooks Davis             try {
188b0d29bc4SBrooks Davis                 const tap_summary summary = parse_tap_output(stdout_path);
189b0d29bc4SBrooks Davis                 return tap_to_result(summary, status.get());
190b0d29bc4SBrooks Davis             } catch (const load_error& e) {
191b0d29bc4SBrooks Davis                 return model::test_result(
192b0d29bc4SBrooks Davis                     model::test_result_broken,
193b0d29bc4SBrooks Davis                     F("TAP test program yielded invalid data: %s") % e.what());
194b0d29bc4SBrooks Davis             }
195b0d29bc4SBrooks Davis         }
196b0d29bc4SBrooks Davis     }
197b0d29bc4SBrooks Davis }
198