xref: /freebsd/contrib/kyua/engine/googletest.cpp (revision eb8f69902f7de8012a31a85db2f88a54584f81af)
1*eb8f6990SEnji Cooper // Copyright 2024 The Kyua Authors.
2*eb8f6990SEnji Cooper // All rights reserved.
3*eb8f6990SEnji Cooper //
4*eb8f6990SEnji Cooper // Redistribution and use in source and binary forms, with or without
5*eb8f6990SEnji Cooper // modification, are permitted provided that the following conditions are
6*eb8f6990SEnji Cooper // met:
7*eb8f6990SEnji Cooper //
8*eb8f6990SEnji Cooper // * Redistributions of source code must retain the above copyright
9*eb8f6990SEnji Cooper //   notice, this list of conditions and the following disclaimer.
10*eb8f6990SEnji Cooper // * Redistributions in binary form must reproduce the above copyright
11*eb8f6990SEnji Cooper //   notice, this list of conditions and the following disclaimer in the
12*eb8f6990SEnji Cooper //   documentation and/or other materials provided with the distribution.
13*eb8f6990SEnji Cooper // * Neither the name of Google Inc. nor the names of its contributors
14*eb8f6990SEnji Cooper //   may be used to endorse or promote products derived from this software
15*eb8f6990SEnji Cooper //   without specific prior written permission.
16*eb8f6990SEnji Cooper //
17*eb8f6990SEnji Cooper // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18*eb8f6990SEnji Cooper // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19*eb8f6990SEnji Cooper // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20*eb8f6990SEnji Cooper // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21*eb8f6990SEnji Cooper // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22*eb8f6990SEnji Cooper // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23*eb8f6990SEnji Cooper // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24*eb8f6990SEnji Cooper // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25*eb8f6990SEnji Cooper // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26*eb8f6990SEnji Cooper // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27*eb8f6990SEnji Cooper // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*eb8f6990SEnji Cooper 
29*eb8f6990SEnji Cooper #include "engine/googletest.hpp"
30*eb8f6990SEnji Cooper 
31*eb8f6990SEnji Cooper extern "C" {
32*eb8f6990SEnji Cooper #include <unistd.h>
33*eb8f6990SEnji Cooper }
34*eb8f6990SEnji Cooper 
35*eb8f6990SEnji Cooper #include <cerrno>
36*eb8f6990SEnji Cooper #include <cstdlib>
37*eb8f6990SEnji Cooper #include <fstream>
38*eb8f6990SEnji Cooper 
39*eb8f6990SEnji Cooper #include "engine/googletest_list.hpp"
40*eb8f6990SEnji Cooper #include "engine/googletest_result.hpp"
41*eb8f6990SEnji Cooper #include "engine/exceptions.hpp"
42*eb8f6990SEnji Cooper #include "model/test_case.hpp"
43*eb8f6990SEnji Cooper #include "model/test_program.hpp"
44*eb8f6990SEnji Cooper #include "model/test_result.hpp"
45*eb8f6990SEnji Cooper #include "utils/defs.hpp"
46*eb8f6990SEnji Cooper #include "utils/env.hpp"
47*eb8f6990SEnji Cooper #include "utils/format/macros.hpp"
48*eb8f6990SEnji Cooper #include "utils/fs/path.hpp"
49*eb8f6990SEnji Cooper #include "utils/logging/macros.hpp"
50*eb8f6990SEnji Cooper #include "utils/optional.ipp"
51*eb8f6990SEnji Cooper #include "utils/process/exceptions.hpp"
52*eb8f6990SEnji Cooper #include "utils/process/operations.hpp"
53*eb8f6990SEnji Cooper #include "utils/process/status.hpp"
54*eb8f6990SEnji Cooper #include "utils/stream.hpp"
55*eb8f6990SEnji Cooper 
56*eb8f6990SEnji Cooper namespace config = utils::config;
57*eb8f6990SEnji Cooper namespace fs = utils::fs;
58*eb8f6990SEnji Cooper namespace process = utils::process;
59*eb8f6990SEnji Cooper 
60*eb8f6990SEnji Cooper using utils::optional;
61*eb8f6990SEnji Cooper 
62*eb8f6990SEnji Cooper 
63*eb8f6990SEnji Cooper namespace {
64*eb8f6990SEnji Cooper 
65*eb8f6990SEnji Cooper 
66*eb8f6990SEnji Cooper /// Basename of the file containing the result written by the googletest
67*eb8f6990SEnji Cooper /// testcase.
68*eb8f6990SEnji Cooper ///
69*eb8f6990SEnji Cooper /// TODO: Use more structured output format someday, like the googletest's JSON
70*eb8f6990SEnji Cooper /// or XML format to avoid dealing with upstream formatting changes, as there's
71*eb8f6990SEnji Cooper /// no guarantee that the output format (which is more or less custom/freeform)
72*eb8f6990SEnji Cooper /// won't change in the future, making compatibility with all versions
73*eb8f6990SEnji Cooper /// potentially difficult to scrape for.
74*eb8f6990SEnji Cooper ///
75*eb8f6990SEnji Cooper /// Using either format will require pulling in a third party library and
76*eb8f6990SEnji Cooper /// understanding the schema of the format. As it stands in 1.9.0,
77*eb8f6990SEnji Cooper /// googletest doesn't document this expectation very well and instead seems
78*eb8f6990SEnji Cooper /// to rely on third-party solutions for doing structured output via the
79*eb8f6990SEnji Cooper /// listener interfaces.
80*eb8f6990SEnji Cooper 
81*eb8f6990SEnji Cooper 
82*eb8f6990SEnji Cooper /// Magic numbers returned by exec_list when exec(2) fails.
83*eb8f6990SEnji Cooper enum list_exit_code {
84*eb8f6990SEnji Cooper     exit_eacces = 90,
85*eb8f6990SEnji Cooper     exit_enoent,
86*eb8f6990SEnji Cooper     exit_enoexec,
87*eb8f6990SEnji Cooper };
88*eb8f6990SEnji Cooper 
89*eb8f6990SEnji Cooper 
90*eb8f6990SEnji Cooper }  // anonymous namespace
91*eb8f6990SEnji Cooper 
92*eb8f6990SEnji Cooper 
93*eb8f6990SEnji Cooper /// Executes a test program's list operation.
94*eb8f6990SEnji Cooper ///
95*eb8f6990SEnji Cooper /// This method is intended to be called within a subprocess and is expected
96*eb8f6990SEnji Cooper /// to terminate execution either by exec(2)ing the test program or by
97*eb8f6990SEnji Cooper /// exiting with a failure.
98*eb8f6990SEnji Cooper ///
99*eb8f6990SEnji Cooper /// \param test_program The test program to execute.
100*eb8f6990SEnji Cooper void
101*eb8f6990SEnji Cooper engine::googletest_interface::exec_list(
102*eb8f6990SEnji Cooper     const model::test_program& test_program,
103*eb8f6990SEnji Cooper     const config::properties_map& /*vars*/) const
104*eb8f6990SEnji Cooper {
105*eb8f6990SEnji Cooper     const process::args_vector args{
106*eb8f6990SEnji Cooper         "--gtest_color=no",
107*eb8f6990SEnji Cooper         "--gtest_list_tests",
108*eb8f6990SEnji Cooper     };
109*eb8f6990SEnji Cooper 
110*eb8f6990SEnji Cooper     try {
111*eb8f6990SEnji Cooper         process::exec_unsafe(test_program.absolute_path(), args);
112*eb8f6990SEnji Cooper     } catch (const process::system_error& e) {
113*eb8f6990SEnji Cooper         if (e.original_errno() == EACCES)
114*eb8f6990SEnji Cooper             ::_exit(exit_eacces);
115*eb8f6990SEnji Cooper         else if (e.original_errno() == ENOENT)
116*eb8f6990SEnji Cooper             ::_exit(exit_enoent);
117*eb8f6990SEnji Cooper         else if (e.original_errno() == ENOEXEC)
118*eb8f6990SEnji Cooper             ::_exit(exit_enoexec);
119*eb8f6990SEnji Cooper         throw;
120*eb8f6990SEnji Cooper     }
121*eb8f6990SEnji Cooper }
122*eb8f6990SEnji Cooper 
123*eb8f6990SEnji Cooper 
124*eb8f6990SEnji Cooper /// Computes the test cases list of a test program.
125*eb8f6990SEnji Cooper ///
126*eb8f6990SEnji Cooper /// \param status The termination status of the subprocess used to execute
127*eb8f6990SEnji Cooper ///     the exec_test() method or none if the test timed out.
128*eb8f6990SEnji Cooper /// \param stdout_path Path to the file containing the stdout of the test.
129*eb8f6990SEnji Cooper ///
130*eb8f6990SEnji Cooper /// \return A list of test cases.
131*eb8f6990SEnji Cooper ///
132*eb8f6990SEnji Cooper /// \throw error If there is a problem parsing the test case list.
133*eb8f6990SEnji Cooper model::test_cases_map
134*eb8f6990SEnji Cooper engine::googletest_interface::parse_list(
135*eb8f6990SEnji Cooper     const optional< process::status >& status,
136*eb8f6990SEnji Cooper     const fs::path& stdout_path,
137*eb8f6990SEnji Cooper     const fs::path& /* stderr_path */) const
138*eb8f6990SEnji Cooper {
139*eb8f6990SEnji Cooper     if (!status)
140*eb8f6990SEnji Cooper         throw engine::error("Test case list timed out");
141*eb8f6990SEnji Cooper     if (status.get().exited()) {
142*eb8f6990SEnji Cooper         const int exitstatus = status.get().exitstatus();
143*eb8f6990SEnji Cooper         if (exitstatus == EXIT_SUCCESS) {
144*eb8f6990SEnji Cooper             // Nothing to do; fall through.
145*eb8f6990SEnji Cooper         } else if (exitstatus == exit_eacces) {
146*eb8f6990SEnji Cooper             throw engine::error("Permission denied to run test program");
147*eb8f6990SEnji Cooper         } else if (exitstatus == exit_enoent) {
148*eb8f6990SEnji Cooper             throw engine::error("Cannot find test program");
149*eb8f6990SEnji Cooper         } else if (exitstatus == exit_enoexec) {
150*eb8f6990SEnji Cooper             throw engine::error("Invalid test program format");
151*eb8f6990SEnji Cooper         } else {
152*eb8f6990SEnji Cooper             throw engine::error("Test program did not exit cleanly; exited "
153*eb8f6990SEnji Cooper                                 "with status: " + std::to_string(exitstatus));
154*eb8f6990SEnji Cooper         }
155*eb8f6990SEnji Cooper     } else {
156*eb8f6990SEnji Cooper         throw engine::error("Test program received signal");
157*eb8f6990SEnji Cooper     }
158*eb8f6990SEnji Cooper 
159*eb8f6990SEnji Cooper     std::ifstream input(stdout_path.c_str());
160*eb8f6990SEnji Cooper     if (!input)
161*eb8f6990SEnji Cooper         throw engine::load_error(stdout_path, "Cannot open file for read");
162*eb8f6990SEnji Cooper     const model::test_cases_map test_cases = parse_googletest_list(input);
163*eb8f6990SEnji Cooper 
164*eb8f6990SEnji Cooper     return test_cases;
165*eb8f6990SEnji Cooper }
166*eb8f6990SEnji Cooper 
167*eb8f6990SEnji Cooper 
168*eb8f6990SEnji Cooper /// Executes a test case of the test program.
169*eb8f6990SEnji Cooper ///
170*eb8f6990SEnji Cooper /// This method is intended to be called within a subprocess and is expected
171*eb8f6990SEnji Cooper /// to terminate execution either by exec(2)ing the test program or by
172*eb8f6990SEnji Cooper /// exiting with a failure.
173*eb8f6990SEnji Cooper ///
174*eb8f6990SEnji Cooper /// \param test_program The test program to execute.
175*eb8f6990SEnji Cooper /// \param test_case_name Name of the test case to invoke.
176*eb8f6990SEnji Cooper /// \param vars User-provided variables to pass to the test program.
177*eb8f6990SEnji Cooper void
178*eb8f6990SEnji Cooper engine::googletest_interface::exec_test(
179*eb8f6990SEnji Cooper     const model::test_program& test_program,
180*eb8f6990SEnji Cooper     const std::string& test_case_name,
181*eb8f6990SEnji Cooper     const config::properties_map& vars,
182*eb8f6990SEnji Cooper     const fs::path& /* control_directory */) const
183*eb8f6990SEnji Cooper {
184*eb8f6990SEnji Cooper     for (config::properties_map::const_iterator iter = vars.begin();
185*eb8f6990SEnji Cooper          iter != vars.end(); ++iter) {
186*eb8f6990SEnji Cooper         utils::setenv(F("TEST_ENV_%s") % (*iter).first, (*iter).second);
187*eb8f6990SEnji Cooper     }
188*eb8f6990SEnji Cooper 
189*eb8f6990SEnji Cooper     process::args_vector args{
190*eb8f6990SEnji Cooper         "--gtest_color=no",
191*eb8f6990SEnji Cooper         F("--gtest_filter=%s") % (test_case_name)
192*eb8f6990SEnji Cooper     };
193*eb8f6990SEnji Cooper     process::exec(test_program.absolute_path(), args);
194*eb8f6990SEnji Cooper     __builtin_unreachable();
195*eb8f6990SEnji Cooper }
196*eb8f6990SEnji Cooper 
197*eb8f6990SEnji Cooper 
198*eb8f6990SEnji Cooper /// Computes the result of a test case based on its termination status.
199*eb8f6990SEnji Cooper ///
200*eb8f6990SEnji Cooper /// \param status The termination status of the subprocess used to execute
201*eb8f6990SEnji Cooper ///     the exec_test() method or none if the test timed out.
202*eb8f6990SEnji Cooper /// \param stdout_path Path to the file containing the stdout of the
203*eb8f6990SEnji Cooper ///     test.
204*eb8f6990SEnji Cooper ///
205*eb8f6990SEnji Cooper /// \return A test result.
206*eb8f6990SEnji Cooper model::test_result
207*eb8f6990SEnji Cooper engine::googletest_interface::compute_result(
208*eb8f6990SEnji Cooper     const optional< process::status >& status,
209*eb8f6990SEnji Cooper     const fs::path& /* control_directory */,
210*eb8f6990SEnji Cooper     const fs::path& stdout_path,
211*eb8f6990SEnji Cooper     const fs::path& /* stderr_path */) const
212*eb8f6990SEnji Cooper {
213*eb8f6990SEnji Cooper     if (!status) {
214*eb8f6990SEnji Cooper         return model::test_result(model::test_result_broken,
215*eb8f6990SEnji Cooper                                   "Test case body timed out");
216*eb8f6990SEnji Cooper     }
217*eb8f6990SEnji Cooper 
218*eb8f6990SEnji Cooper     return calculate_googletest_result(status, stdout_path);
219*eb8f6990SEnji Cooper }
220