run-tests.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /*
  2. * Copyright (c) 2021, Andrew Kaster <akaster@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/LexicalPath.h>
  7. #include <LibCore/ArgsParser.h>
  8. #include <LibCore/ConfigFile.h>
  9. #include <LibCore/File.h>
  10. #include <LibRegex/Regex.h>
  11. #include <LibTest/TestRunner.h>
  12. #include <signal.h>
  13. #include <spawn.h>
  14. #include <sys/wait.h>
  15. #include <unistd.h>
  16. namespace Test {
  17. TestRunner* TestRunner::s_the = nullptr;
  18. }
  19. using Test::get_time_in_ms;
  20. using Test::print_modifiers;
  21. struct FileResult {
  22. LexicalPath file_path;
  23. double time_taken { 0 };
  24. Test::Result result { Test::Result::Pass };
  25. int stdout_err_fd { -1 };
  26. };
  27. String g_currently_running_test;
  28. class TestRunner : public ::Test::TestRunner {
  29. public:
  30. TestRunner(String test_root, Regex<PosixExtended> exclude_regex, NonnullRefPtr<Core::ConfigFile> config, Regex<PosixExtended> skip_regex, bool print_progress, bool print_json, bool print_all_output, bool print_times = true)
  31. : ::Test::TestRunner(move(test_root), print_times, print_progress, print_json)
  32. , m_exclude_regex(move(exclude_regex))
  33. , m_config(move(config))
  34. , m_skip_regex(move(skip_regex))
  35. , m_print_all_output(print_all_output)
  36. {
  37. m_skip_directories = m_config->read_entry("Global", "SkipDirectories", "").split(' ');
  38. m_skip_files = m_config->read_entry("Global", "SkipTests", "").split(' ');
  39. }
  40. virtual ~TestRunner() = default;
  41. protected:
  42. virtual void do_run_single_test(const String& test_path) override;
  43. virtual Vector<String> get_test_paths() const override;
  44. virtual const Vector<String>* get_failed_test_names() const override { return &m_failed_test_names; }
  45. virtual FileResult run_test_file(const String& test_path);
  46. bool should_skip_test(const LexicalPath& test_path);
  47. Regex<PosixExtended> m_exclude_regex;
  48. NonnullRefPtr<Core::ConfigFile> m_config;
  49. Vector<String> m_skip_directories;
  50. Vector<String> m_skip_files;
  51. Vector<String> m_failed_test_names;
  52. Regex<PosixExtended> m_skip_regex;
  53. bool m_print_all_output { false };
  54. };
  55. Vector<String> TestRunner::get_test_paths() const
  56. {
  57. Vector<String> paths;
  58. Test::iterate_directory_recursively(m_test_root, [&](const String& file_path) {
  59. if (access(file_path.characters(), R_OK | X_OK) != 0)
  60. return;
  61. auto result = m_exclude_regex.match(file_path, PosixFlags::Global);
  62. if (!result.success) // must NOT match the regex to be a valid test file
  63. paths.append(file_path);
  64. });
  65. quick_sort(paths);
  66. return paths;
  67. }
  68. bool TestRunner::should_skip_test(const LexicalPath& test_path)
  69. {
  70. for (const String& dir : m_skip_directories) {
  71. if (test_path.dirname().contains(dir))
  72. return true;
  73. }
  74. for (const String& file : m_skip_files) {
  75. if (test_path.basename().contains(file))
  76. return true;
  77. }
  78. auto result = m_skip_regex.match(test_path.basename(), PosixFlags::Global);
  79. if (result.success)
  80. return true;
  81. return false;
  82. }
  83. void TestRunner::do_run_single_test(const String& test_path)
  84. {
  85. g_currently_running_test = test_path;
  86. auto test_result = run_test_file(test_path);
  87. switch (test_result.result) {
  88. case Test::Result::Pass:
  89. ++m_counts.tests_passed;
  90. break;
  91. case Test::Result::Skip:
  92. ++m_counts.tests_skipped;
  93. break;
  94. case Test::Result::Fail:
  95. ++m_counts.tests_failed;
  96. break;
  97. case Test::Result::Crashed:
  98. ++m_counts.tests_failed; // FIXME: tests_crashed
  99. break;
  100. }
  101. if (test_result.result != Test::Result::Skip)
  102. ++m_counts.files_total;
  103. m_total_elapsed_time_in_ms += test_result.time_taken;
  104. bool crashed_or_failed = test_result.result == Test::Result::Fail || test_result.result == Test::Result::Crashed;
  105. bool print_stdout_stderr = crashed_or_failed || m_print_all_output;
  106. if (crashed_or_failed) {
  107. m_failed_test_names.append(test_path);
  108. print_modifiers({ Test::BG_RED, Test::FG_BLACK, Test::FG_BOLD });
  109. out("{}", test_result.result == Test::Result::Fail ? " FAIL " : "CRASHED");
  110. print_modifiers({ Test::CLEAR });
  111. } else {
  112. print_modifiers({ Test::BG_GREEN, Test::FG_BLACK, Test::FG_BOLD });
  113. out(" PASS ");
  114. print_modifiers({ Test::CLEAR });
  115. }
  116. out(" {}", LexicalPath::relative_path(test_path, m_test_root));
  117. print_modifiers({ Test::CLEAR, Test::ITALIC, Test::FG_GRAY });
  118. if (test_result.time_taken < 1000) {
  119. outln(" ({}ms)", static_cast<int>(test_result.time_taken));
  120. } else {
  121. outln(" ({:3}s)", test_result.time_taken / 1000.0);
  122. }
  123. print_modifiers({ Test::CLEAR });
  124. if (test_result.result != Test::Result::Pass) {
  125. print_modifiers({ Test::FG_GRAY, Test::FG_BOLD });
  126. out(" Test: ");
  127. if (crashed_or_failed) {
  128. print_modifiers({ Test::CLEAR, Test::FG_RED });
  129. outln("{} ({})", test_result.file_path.basename(), test_result.result == Test::Result::Fail ? "failed" : "crashed");
  130. } else {
  131. print_modifiers({ Test::CLEAR, Test::FG_ORANGE });
  132. outln("{} (skipped)", test_result.file_path.basename());
  133. }
  134. print_modifiers({ Test::CLEAR });
  135. }
  136. // Make sure our clear modifiers goes through before we dump file output via write(2)
  137. fflush(stdout);
  138. if (print_stdout_stderr && test_result.stdout_err_fd > 0) {
  139. int ret = lseek(test_result.stdout_err_fd, 0, SEEK_SET);
  140. VERIFY(ret == 0);
  141. for (;;) {
  142. char buf[32768];
  143. ssize_t nread = read(test_result.stdout_err_fd, buf, sizeof(buf));
  144. if (nread == 0)
  145. break;
  146. if (nread < 0) {
  147. perror("read");
  148. break;
  149. }
  150. size_t already_written = 0;
  151. while (already_written < (size_t)nread) {
  152. ssize_t nwritten = write(STDOUT_FILENO, buf + already_written, nread - already_written);
  153. if (nwritten < 0) {
  154. perror("write");
  155. break;
  156. }
  157. already_written += nwritten;
  158. }
  159. }
  160. }
  161. close(test_result.stdout_err_fd);
  162. }
  163. FileResult TestRunner::run_test_file(const String& test_path)
  164. {
  165. double start_time = get_time_in_ms();
  166. auto path_for_test = LexicalPath(test_path);
  167. if (should_skip_test(path_for_test)) {
  168. return FileResult { move(path_for_test), 0.0, Test::Result::Skip, -1 };
  169. }
  170. // FIXME: actual error handling, mark test as :yaksplode: if any are bad instead of VERIFY
  171. posix_spawn_file_actions_t file_actions;
  172. posix_spawn_file_actions_init(&file_actions);
  173. char child_out_err_path[] = "/tmp/run-tests.XXXXXX";
  174. int child_out_err_file = mkstemp(child_out_err_path);
  175. VERIFY(child_out_err_file >= 0);
  176. String dirname = path_for_test.dirname();
  177. String basename = path_for_test.basename();
  178. (void)posix_spawn_file_actions_adddup2(&file_actions, child_out_err_file, STDOUT_FILENO);
  179. (void)posix_spawn_file_actions_adddup2(&file_actions, child_out_err_file, STDERR_FILENO);
  180. (void)posix_spawn_file_actions_addchdir(&file_actions, dirname.characters());
  181. Vector<const char*, 4> argv;
  182. argv.append(basename.characters());
  183. auto extra_args = m_config->read_entry(path_for_test.basename(), "Arguments", "").split(' ');
  184. for (auto& arg : extra_args)
  185. argv.append(arg.characters());
  186. argv.append(nullptr);
  187. pid_t child_pid = -1;
  188. // FIXME: Do we really want to copy test runner's entire env?
  189. int ret = posix_spawn(&child_pid, test_path.characters(), &file_actions, nullptr, const_cast<char* const*>(argv.data()), environ);
  190. VERIFY(ret == 0);
  191. VERIFY(child_pid > 0);
  192. int wstatus;
  193. Test::Result test_result = Test::Result::Fail;
  194. for (size_t num_waits = 0; num_waits < 2; ++num_waits) {
  195. ret = waitpid(child_pid, &wstatus, 0); // intentionally not setting WCONTINUED
  196. if (ret != child_pid)
  197. break; // we'll end up with a failure
  198. if (WIFEXITED(wstatus)) {
  199. if (wstatus == 0) {
  200. test_result = Test::Result::Pass;
  201. }
  202. break;
  203. } else if (WIFSIGNALED(wstatus)) {
  204. test_result = Test::Result::Crashed;
  205. break;
  206. } else if (WIFSTOPPED(wstatus)) {
  207. outln("{} was stopped unexpectedly, sending SIGCONT", test_path);
  208. kill(child_pid, SIGCONT);
  209. }
  210. }
  211. // Remove the child's stdout from /tmp. This does cause the temp file to be observable
  212. // while the test is executing, but if it hangs that might even be a bonus :)
  213. ret = unlink(child_out_err_path);
  214. VERIFY(ret == 0);
  215. return FileResult { move(path_for_test), get_time_in_ms() - start_time, test_result, child_out_err_file };
  216. }
  217. int main(int argc, char** argv)
  218. {
  219. auto program_name = LexicalPath::basename(argv[0]);
  220. #ifdef SIGINFO
  221. signal(SIGINFO, [](int) {
  222. static char buffer[4096];
  223. auto& counts = ::Test::TestRunner::the()->counts();
  224. int len = snprintf(buffer, sizeof(buffer), "Pass: %d, Fail: %d, Skip: %d\nCurrent test: %s\n", counts.tests_passed, counts.tests_failed, counts.tests_skipped, g_currently_running_test.characters());
  225. write(STDOUT_FILENO, buffer, len);
  226. });
  227. #endif
  228. bool print_progress =
  229. #ifdef __serenity__
  230. true; // Use OSC 9 to print progress
  231. #else
  232. false;
  233. #endif
  234. bool print_json = false;
  235. bool print_all_output = false;
  236. const char* specified_test_root = nullptr;
  237. String test_glob;
  238. String exclude_pattern;
  239. String config_file;
  240. Core::ArgsParser args_parser;
  241. args_parser.add_option(Core::ArgsParser::Option {
  242. .requires_argument = true,
  243. .help_string = "Show progress with OSC 9 (true, false)",
  244. .long_name = "show-progress",
  245. .short_name = 'p',
  246. .accept_value = [&](auto* str) {
  247. if ("true"sv == str)
  248. print_progress = true;
  249. else if ("false"sv == str)
  250. print_progress = false;
  251. else
  252. return false;
  253. return true;
  254. },
  255. });
  256. args_parser.add_option(print_json, "Show results as JSON", "json", 'j');
  257. args_parser.add_option(print_all_output, "Show all test output", "verbose", 'v');
  258. args_parser.add_option(test_glob, "Only run tests matching the given glob", "filter", 'f', "glob");
  259. args_parser.add_option(exclude_pattern, "Regular expression to use to exclude paths from being considered tests", "exclude-pattern", 'e', "pattern");
  260. args_parser.add_option(config_file, "Configuration file to use", "config-file", 'c', "filename");
  261. args_parser.add_positional_argument(specified_test_root, "Tests root directory", "path", Core::ArgsParser::Required::No);
  262. args_parser.parse(argc, argv);
  263. test_glob = String::formatted("*{}*", test_glob);
  264. if (getenv("DISABLE_DBG_OUTPUT")) {
  265. AK::set_debug_enabled(false);
  266. }
  267. String test_root;
  268. if (specified_test_root) {
  269. test_root = String { specified_test_root };
  270. } else {
  271. test_root = "/usr/Tests";
  272. }
  273. if (!Core::File::is_directory(test_root)) {
  274. warnln("Test root is not a directory: {}", test_root);
  275. return 1;
  276. }
  277. test_root = Core::File::real_path_for(test_root);
  278. if (chdir(test_root.characters()) < 0) {
  279. auto saved_errno = errno;
  280. warnln("chdir failed: {}", strerror(saved_errno));
  281. return 1;
  282. }
  283. auto config = config_file.is_empty() ? Core::ConfigFile::get_for_app("Tests") : Core::ConfigFile::open(config_file);
  284. if (config->num_groups() == 0)
  285. warnln("Empty configuration file ({}) loaded!", config_file.is_empty() ? "User config for Tests" : config_file.characters());
  286. if (exclude_pattern.is_empty())
  287. exclude_pattern = config->read_entry("Global", "NotTestsPattern", "$^"); // default is match nothing (aka match end then beginning)
  288. Regex<PosixExtended> exclude_regex(exclude_pattern, {});
  289. if (exclude_regex.parser_result.error != Error::NoError) {
  290. warnln("Exclude pattern \"{}\" is invalid", exclude_pattern);
  291. return 1;
  292. }
  293. // we need to preconfigure this, because we can't autoinitialize Regex types
  294. // in the Testrunner
  295. auto skip_regex_pattern = config->read_entry("Global", "SkipRegex", "$^");
  296. Regex<PosixExtended> skip_regex { skip_regex_pattern, {} };
  297. if (skip_regex.parser_result.error != Error::NoError) {
  298. warnln("SkipRegex pattern \"{}\" is invalid", skip_regex_pattern);
  299. return 1;
  300. }
  301. TestRunner test_runner(test_root, move(exclude_regex), move(config), move(skip_regex), print_progress, print_json, print_all_output);
  302. test_runner.run(test_glob);
  303. return test_runner.counts().tests_failed;
  304. }