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