xref: /freebsd/contrib/kyua/engine/googletest_helpers.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 extern "C" {
30*eb8f6990SEnji Cooper #include <sys/stat.h>
31*eb8f6990SEnji Cooper 
32*eb8f6990SEnji Cooper #include <getopt.h>
33*eb8f6990SEnji Cooper #include <signal.h>
34*eb8f6990SEnji Cooper #include <unistd.h>
35*eb8f6990SEnji Cooper 
36*eb8f6990SEnji Cooper extern char** environ;
37*eb8f6990SEnji Cooper }
38*eb8f6990SEnji Cooper 
39*eb8f6990SEnji Cooper #include <cstdlib>
40*eb8f6990SEnji Cooper #include <cstring>
41*eb8f6990SEnji Cooper #include <fstream>
42*eb8f6990SEnji Cooper #include <iostream>
43*eb8f6990SEnji Cooper 
44*eb8f6990SEnji Cooper #include "utils/env.hpp"
45*eb8f6990SEnji Cooper #include "utils/cmdline/options.hpp"
46*eb8f6990SEnji Cooper #include "utils/cmdline/parser.ipp"
47*eb8f6990SEnji Cooper #include "utils/format/containers.ipp"
48*eb8f6990SEnji Cooper #include "utils/format/macros.hpp"
49*eb8f6990SEnji Cooper #include "utils/fs/path.hpp"
50*eb8f6990SEnji Cooper #include "utils/optional.ipp"
51*eb8f6990SEnji Cooper #include "utils/test_utils.ipp"
52*eb8f6990SEnji Cooper 
53*eb8f6990SEnji Cooper namespace cmdline = utils::cmdline;
54*eb8f6990SEnji Cooper namespace fs = utils::fs;
55*eb8f6990SEnji Cooper 
56*eb8f6990SEnji Cooper using cmdline::base_option;
57*eb8f6990SEnji Cooper using cmdline::bool_option;
58*eb8f6990SEnji Cooper using cmdline::parse;
59*eb8f6990SEnji Cooper using cmdline::parsed_cmdline;
60*eb8f6990SEnji Cooper using cmdline::string_option;
61*eb8f6990SEnji Cooper 
62*eb8f6990SEnji Cooper namespace fs = utils::fs;
63*eb8f6990SEnji Cooper 
64*eb8f6990SEnji Cooper namespace {
65*eb8f6990SEnji Cooper 
66*eb8f6990SEnji Cooper 
67*eb8f6990SEnji Cooper /// Prefix for all testcases.
68*eb8f6990SEnji Cooper const char *test_suite = "Suite.";
69*eb8f6990SEnji Cooper 
70*eb8f6990SEnji Cooper 
71*eb8f6990SEnji Cooper /// Logs an error message and exits the test with an error code.
72*eb8f6990SEnji Cooper ///
73*eb8f6990SEnji Cooper /// \param str The error message to log.
74*eb8f6990SEnji Cooper static void
75*eb8f6990SEnji Cooper fail(const std::string& str)
76*eb8f6990SEnji Cooper {
77*eb8f6990SEnji Cooper     std::cerr << str << '\n';
78*eb8f6990SEnji Cooper     std::exit(EXIT_FAILURE);
79*eb8f6990SEnji Cooper }
80*eb8f6990SEnji Cooper 
81*eb8f6990SEnji Cooper 
82*eb8f6990SEnji Cooper /// A test scenario that validates the TEST_ENV_* variables.
83*eb8f6990SEnji Cooper static void
84*eb8f6990SEnji Cooper test_check_configuration_variables(void)
85*eb8f6990SEnji Cooper {
86*eb8f6990SEnji Cooper     std::set< std::string > vars;
87*eb8f6990SEnji Cooper     char** iter;
88*eb8f6990SEnji Cooper     for (iter = environ; *iter != NULL; ++iter) {
89*eb8f6990SEnji Cooper         if (std::strstr(*iter, "TEST_ENV_") == *iter) {
90*eb8f6990SEnji Cooper             vars.insert(*iter);
91*eb8f6990SEnji Cooper         }
92*eb8f6990SEnji Cooper     }
93*eb8f6990SEnji Cooper 
94*eb8f6990SEnji Cooper     std::set< std::string > exp_vars{
95*eb8f6990SEnji Cooper         "TEST_ENV_first=some value",
96*eb8f6990SEnji Cooper         "TEST_ENV_second=some other value"
97*eb8f6990SEnji Cooper     };
98*eb8f6990SEnji Cooper 
99*eb8f6990SEnji Cooper     if (vars == exp_vars) {
100*eb8f6990SEnji Cooper         std::cout <<
101*eb8f6990SEnji Cooper "Note: Google Test filter = Suite.check_configuration_variables\n"
102*eb8f6990SEnji Cooper "[==========] Running 1 test from 1 test case.\n"
103*eb8f6990SEnji Cooper "[----------] Global test environment set-up.\n"
104*eb8f6990SEnji Cooper "[----------] 1 test from Suite\n"
105*eb8f6990SEnji Cooper "[ RUN      ] Suite.check_configuration_variables\n"
106*eb8f6990SEnji Cooper "[       OK ] Suite.check_configuration_variables (0 ms)\n"
107*eb8f6990SEnji Cooper "[----------] 1 test from PassFailTest (0 ms total)\n"
108*eb8f6990SEnji Cooper "\n"
109*eb8f6990SEnji Cooper "[----------] Global test environment tear-down\n"
110*eb8f6990SEnji Cooper "[==========] 1 test from 1 test case ran. (1 ms total)\n"
111*eb8f6990SEnji Cooper "[  PASSED  ] 1 test.\n"
112*eb8f6990SEnji Cooper         ;
113*eb8f6990SEnji Cooper     } else {
114*eb8f6990SEnji Cooper         std::cout <<
115*eb8f6990SEnji Cooper "Note: Google Test filter = Suite.Fails\n"
116*eb8f6990SEnji Cooper "[==========] Running 1 test from 1 test case.\n"
117*eb8f6990SEnji Cooper "[----------] Global test environment set-up.\n"
118*eb8f6990SEnji Cooper "[----------] 1 test from PassFailTest\n"
119*eb8f6990SEnji Cooper "[ RUN      ] Suite.check_configuration_variables\n"
120*eb8f6990SEnji Cooper "pass_fail_demo.cc:12: Failure\n"
121*eb8f6990SEnji Cooper "Expected equality of these values:\n"
122*eb8f6990SEnji Cooper "  false\n"
123*eb8f6990SEnji Cooper "  true\n"
124*eb8f6990SEnji Cooper "[  FAILED  ] Suite.check_configuration_variables (0 ms)\n"
125*eb8f6990SEnji Cooper "[----------] 1 test from Suite (0 ms total)\n"
126*eb8f6990SEnji Cooper "\n"
127*eb8f6990SEnji Cooper "[----------] Global test environment tear-down\n"
128*eb8f6990SEnji Cooper "[==========] 1 test from 1 test case ran. (0 ms total)\n"
129*eb8f6990SEnji Cooper "[  PASSED  ] 0 tests.\n"
130*eb8f6990SEnji Cooper "[  FAILED  ] 1 test, listed below:\n"
131*eb8f6990SEnji Cooper "[  FAILED  ] Suite.check_configuration_variables\n"
132*eb8f6990SEnji Cooper "\n"
133*eb8f6990SEnji Cooper " 1 FAILED TEST\n"
134*eb8f6990SEnji Cooper         << F("    Expected: %s\nFound: %s\n") % exp_vars % vars;
135*eb8f6990SEnji Cooper         std::exit(EXIT_FAILURE);
136*eb8f6990SEnji Cooper     }
137*eb8f6990SEnji Cooper }
138*eb8f6990SEnji Cooper 
139*eb8f6990SEnji Cooper 
140*eb8f6990SEnji Cooper /// A test scenario that triggers a crash via abort in order to generate a
141*eb8f6990SEnji Cooper /// core dump.
142*eb8f6990SEnji Cooper static void
143*eb8f6990SEnji Cooper test_crash(void)
144*eb8f6990SEnji Cooper {
145*eb8f6990SEnji Cooper     std::abort();
146*eb8f6990SEnji Cooper }
147*eb8f6990SEnji Cooper 
148*eb8f6990SEnji Cooper 
149*eb8f6990SEnji Cooper /// A test scenario that reports some tests as failed.
150*eb8f6990SEnji Cooper static void
151*eb8f6990SEnji Cooper test_fail(void)
152*eb8f6990SEnji Cooper {
153*eb8f6990SEnji Cooper     std::cout << (
154*eb8f6990SEnji Cooper "Note: Google Test filter = Suite.fail\n"
155*eb8f6990SEnji Cooper "[==========] Running 1 test from 1 test suite.\n"
156*eb8f6990SEnji Cooper "[----------] Global test environment set-up.\n"
157*eb8f6990SEnji Cooper "[----------] 1 test from Suite\n"
158*eb8f6990SEnji Cooper "[ RUN      ] Suite.fail\n"
159*eb8f6990SEnji Cooper "gtest_macros_demo.cc:4: Failure\n"
160*eb8f6990SEnji Cooper "Failed\n"
161*eb8f6990SEnji Cooper "with a reason\n"
162*eb8f6990SEnji Cooper "[  FAILED  ] Suite.fail (0 ms)\n"
163*eb8f6990SEnji Cooper "[----------] 1 test from Suite (0 ms total)\n"
164*eb8f6990SEnji Cooper "\n"
165*eb8f6990SEnji Cooper "[----------] Global test environment tear-down\n"
166*eb8f6990SEnji Cooper "[==========] 1 test from 1 test suite ran. (0 ms total)\n"
167*eb8f6990SEnji Cooper "[  PASSED  ] 0 tests.\n"
168*eb8f6990SEnji Cooper "[  FAILED  ] 1 test, listed below:\n"
169*eb8f6990SEnji Cooper "[  FAILED  ] Suite.fail\n"
170*eb8f6990SEnji Cooper "\n"
171*eb8f6990SEnji Cooper " 1 FAILED TEST\n"
172*eb8f6990SEnji Cooper     );
173*eb8f6990SEnji Cooper     std::exit(EXIT_FAILURE);
174*eb8f6990SEnji Cooper }
175*eb8f6990SEnji Cooper 
176*eb8f6990SEnji Cooper 
177*eb8f6990SEnji Cooper /// A test scenario that passes.
178*eb8f6990SEnji Cooper static void
179*eb8f6990SEnji Cooper test_pass(void)
180*eb8f6990SEnji Cooper {
181*eb8f6990SEnji Cooper     std::cout << (
182*eb8f6990SEnji Cooper "Note: Google Test filter = Suite.pass\n"
183*eb8f6990SEnji Cooper "[==========] Running 1 test from 1 test suite.\n"
184*eb8f6990SEnji Cooper "[----------] Global test environment set-up.\n"
185*eb8f6990SEnji Cooper "[----------] 1 test from Suite\n"
186*eb8f6990SEnji Cooper "[ RUN      ] Suite.pass\n"
187*eb8f6990SEnji Cooper "[       OK ] Suite.pass (0 ms)\n"
188*eb8f6990SEnji Cooper "[----------] 1 test from Suite (0 ms total)\n"
189*eb8f6990SEnji Cooper "\n"
190*eb8f6990SEnji Cooper "[----------] Global test environment tear-down\n"
191*eb8f6990SEnji Cooper "[==========] 1 test from 1 test suite ran. (0 ms total)\n"
192*eb8f6990SEnji Cooper "[  PASSED  ] 1 test.\n"
193*eb8f6990SEnji Cooper     );
194*eb8f6990SEnji Cooper }
195*eb8f6990SEnji Cooper 
196*eb8f6990SEnji Cooper 
197*eb8f6990SEnji Cooper /// A test scenario that passes but then exits with non-zero.
198*eb8f6990SEnji Cooper static void
199*eb8f6990SEnji Cooper test_pass_but_exit_failure(void)
200*eb8f6990SEnji Cooper {
201*eb8f6990SEnji Cooper     std::cout << (
202*eb8f6990SEnji Cooper "Note: Google Test filter = Suite.pass_but_exit_failure\n"
203*eb8f6990SEnji Cooper "[==========] Running 1 test from 1 test suite.\n"
204*eb8f6990SEnji Cooper "[----------] Global test environment set-up.\n"
205*eb8f6990SEnji Cooper "[----------] 1 test from Suite\n"
206*eb8f6990SEnji Cooper "[ RUN      ] Suite.pass_but_exit_failure\n"
207*eb8f6990SEnji Cooper "[       OK ] Suite.pass_but_exit_failure (0 ms)\n"
208*eb8f6990SEnji Cooper "[----------] 1 test from Suite (0 ms total)\n"
209*eb8f6990SEnji Cooper "\n"
210*eb8f6990SEnji Cooper "[----------] Global test environment tear-down\n"
211*eb8f6990SEnji Cooper "[==========] 1 test from 1 test suite ran. (0 ms total)\n"
212*eb8f6990SEnji Cooper "[  PASSED  ] 1 test.\n"
213*eb8f6990SEnji Cooper     );
214*eb8f6990SEnji Cooper 
215*eb8f6990SEnji Cooper     std::exit(70);
216*eb8f6990SEnji Cooper }
217*eb8f6990SEnji Cooper 
218*eb8f6990SEnji Cooper 
219*eb8f6990SEnji Cooper /// A test scenario that times out.
220*eb8f6990SEnji Cooper ///
221*eb8f6990SEnji Cooper /// Note that the timeout is defined in the Kyuafile, as the TAP interface has
222*eb8f6990SEnji Cooper /// no means for test programs to specify this by themselves.
223*eb8f6990SEnji Cooper static void
224*eb8f6990SEnji Cooper test_timeout(void)
225*eb8f6990SEnji Cooper {
226*eb8f6990SEnji Cooper     std::cout << (
227*eb8f6990SEnji Cooper "Note: Google Test filter = Suite.incomplete\n"
228*eb8f6990SEnji Cooper "[==========] Running 1 test from 1 test suite.\n"
229*eb8f6990SEnji Cooper "[----------] Global test environment set-up.\n"
230*eb8f6990SEnji Cooper "[----------] 1 test from Suite\n"
231*eb8f6990SEnji Cooper "[ RUN      ] Suite.incomplete\n"
232*eb8f6990SEnji Cooper     );
233*eb8f6990SEnji Cooper 
234*eb8f6990SEnji Cooper     ::sleep(10);
235*eb8f6990SEnji Cooper     const fs::path control_dir = fs::path(utils::getenv("CONTROL_DIR").get());
236*eb8f6990SEnji Cooper     std::ofstream file((control_dir / "cookie").c_str());
237*eb8f6990SEnji Cooper     if (!file)
238*eb8f6990SEnji Cooper         fail("Failed to create the control cookie");
239*eb8f6990SEnji Cooper     file.close();
240*eb8f6990SEnji Cooper }
241*eb8f6990SEnji Cooper 
242*eb8f6990SEnji Cooper 
243*eb8f6990SEnji Cooper /// Prints out program usage and exits with a non-zero exit code.
244*eb8f6990SEnji Cooper static void
245*eb8f6990SEnji Cooper usage(const char* argv0) {
246*eb8f6990SEnji Cooper     std::cout << "usage: " << argv0 << " "
247*eb8f6990SEnji Cooper               << "[--gtest_color=(auto|yes|no)] "
248*eb8f6990SEnji Cooper               << "[--gtest_filter=POSITIVE_PATTERNS] "
249*eb8f6990SEnji Cooper               << "[--gtest_list_tests]"
250*eb8f6990SEnji Cooper               << "\n\n"
251*eb8f6990SEnji Cooper               << "This program mocks a googletest test program."
252*eb8f6990SEnji Cooper               << "\n";
253*eb8f6990SEnji Cooper     std::exit(EXIT_FAILURE);
254*eb8f6990SEnji Cooper }
255*eb8f6990SEnji Cooper 
256*eb8f6990SEnji Cooper 
257*eb8f6990SEnji Cooper }  // anonymous namespace
258*eb8f6990SEnji Cooper 
259*eb8f6990SEnji Cooper 
260*eb8f6990SEnji Cooper /// Entry point to the test program.
261*eb8f6990SEnji Cooper ///
262*eb8f6990SEnji Cooper /// The caller can select which test scenario to run by modifying the program's
263*eb8f6990SEnji Cooper /// basename on disk (either by a copy or by a hard link).
264*eb8f6990SEnji Cooper ///
265*eb8f6990SEnji Cooper /// \todo It may be worth to split this binary into separate, smaller binaries,
266*eb8f6990SEnji Cooper /// one for every "test scenario".  We use this program as a dispatcher for
267*eb8f6990SEnji Cooper /// different "main"s, the only reason being to keep the amount of helper test
268*eb8f6990SEnji Cooper /// programs to a minimum.  However, putting this each function in its own
269*eb8f6990SEnji Cooper /// binary could simplify many other things.
270*eb8f6990SEnji Cooper ///
271*eb8f6990SEnji Cooper /// \param argc The number of CLI arguments.
272*eb8f6990SEnji Cooper /// \param argv The CLI arguments themselves.  These are not used because
273*eb8f6990SEnji Cooper ///     Kyua will not pass any arguments to the plain test program.
274*eb8f6990SEnji Cooper int
275*eb8f6990SEnji Cooper main(int argc, char** argv)
276*eb8f6990SEnji Cooper {
277*eb8f6990SEnji Cooper     using scenario_fn_t = void (*)(void);
278*eb8f6990SEnji Cooper 
279*eb8f6990SEnji Cooper     char *argv0 = argv[0];
280*eb8f6990SEnji Cooper 
281*eb8f6990SEnji Cooper     std::map<std::string, scenario_fn_t> scenarios{
282*eb8f6990SEnji Cooper         { "check_configuration_variables", test_check_configuration_variables },
283*eb8f6990SEnji Cooper         { "crash", test_crash },
284*eb8f6990SEnji Cooper         { "fail", test_fail },
285*eb8f6990SEnji Cooper         { "pass", test_pass },
286*eb8f6990SEnji Cooper         { "pass_but_exit_failure", test_pass_but_exit_failure },
287*eb8f6990SEnji Cooper         { "timeout", test_timeout }
288*eb8f6990SEnji Cooper     };
289*eb8f6990SEnji Cooper 
290*eb8f6990SEnji Cooper     const bool_option gtest_list_tests_opt(
291*eb8f6990SEnji Cooper         "gtest_list_tests", "List tests");
292*eb8f6990SEnji Cooper     const string_option gtest_color_opt(
293*eb8f6990SEnji Cooper         "gtest_color", "Enable/disable color support", "auto");
294*eb8f6990SEnji Cooper     const string_option gtest_filter_opt(
295*eb8f6990SEnji Cooper         "gtest_filter", "", "POSITIVE_PATTERNS");
296*eb8f6990SEnji Cooper 
297*eb8f6990SEnji Cooper     std::vector<const base_option*> options;
298*eb8f6990SEnji Cooper     /// Ignore `--gtest_color=*`.
299*eb8f6990SEnji Cooper     options.push_back(&gtest_color_opt);
300*eb8f6990SEnji Cooper     options.push_back(&gtest_filter_opt);
301*eb8f6990SEnji Cooper     options.push_back(&gtest_list_tests_opt);
302*eb8f6990SEnji Cooper 
303*eb8f6990SEnji Cooper     const parsed_cmdline cmdline = parse(argc, argv, options);
304*eb8f6990SEnji Cooper     INV(cmdline.arguments().empty());
305*eb8f6990SEnji Cooper 
306*eb8f6990SEnji Cooper     if (cmdline.has_option("gtest_list_tests")) {
307*eb8f6990SEnji Cooper         std::cout << test_suite << "\n";
308*eb8f6990SEnji Cooper         for (auto it = scenarios.begin(); it != scenarios.end(); it++) {
309*eb8f6990SEnji Cooper             std::cout << "  " << it->first << "\n";
310*eb8f6990SEnji Cooper         }
311*eb8f6990SEnji Cooper         return EXIT_SUCCESS;
312*eb8f6990SEnji Cooper     }
313*eb8f6990SEnji Cooper 
314*eb8f6990SEnji Cooper     INV(cmdline.has_option("gtest_filter"));
315*eb8f6990SEnji Cooper 
316*eb8f6990SEnji Cooper     auto gtest_filter_arg = cmdline.get_option<string_option>("gtest_filter");
317*eb8f6990SEnji Cooper     INV(gtest_filter_arg.find_first_of(test_suite) == 0);
318*eb8f6990SEnji Cooper     auto testcase = gtest_filter_arg.erase(0, strlen(test_suite));
319*eb8f6990SEnji Cooper 
320*eb8f6990SEnji Cooper     auto scenario = scenarios.find(testcase);
321*eb8f6990SEnji Cooper     if (scenario == scenarios.end()) {
322*eb8f6990SEnji Cooper         /// Mimic googletest test programs by printing out a usage message when
323*eb8f6990SEnji Cooper         /// a test cannot be found.
324*eb8f6990SEnji Cooper         usage(argv0);
325*eb8f6990SEnji Cooper     }
326*eb8f6990SEnji Cooper 
327*eb8f6990SEnji Cooper     scenario->second();
328*eb8f6990SEnji Cooper 
329*eb8f6990SEnji Cooper     return EXIT_SUCCESS;
330*eb8f6990SEnji Cooper }
331