JavaScriptTestRunnerMain.cpp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /*
  2. * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
  3. * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
  4. * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include <LibTest/JavaScriptTestRunner.h>
  9. #include <signal.h>
  10. #include <stdio.h>
  11. namespace Test {
  12. TestRunner* ::Test::TestRunner::s_the = nullptr;
  13. namespace JS {
  14. RefPtr<::JS::VM> g_vm;
  15. bool g_collect_on_every_allocation = false;
  16. #ifdef JS_TRACK_ZOMBIE_CELLS
  17. bool g_zombify_dead_cells = false;
  18. #endif
  19. bool g_run_bytecode = false;
  20. bool g_dump_bytecode = false;
  21. String g_currently_running_test;
  22. HashMap<String, FunctionWithLength> s_exposed_global_functions;
  23. Function<void()> g_main_hook;
  24. HashMap<bool*, Tuple<String, String, char>> g_extra_args;
  25. IntermediateRunFileResult (*g_run_file)(const String&, JS::Interpreter&) = nullptr;
  26. String g_test_root;
  27. int g_test_argc;
  28. char** g_test_argv;
  29. } // namespace JS
  30. } // namespace Test
  31. using namespace Test::JS;
  32. static StringView g_program_name { "test-js"sv };
  33. static void handle_sigabrt(int)
  34. {
  35. dbgln("{}: SIGABRT received, cleaning up.", g_program_name);
  36. Test::cleanup();
  37. struct sigaction act;
  38. memset(&act, 0, sizeof(act));
  39. act.sa_flags = SA_NOCLDWAIT;
  40. act.sa_handler = SIG_DFL;
  41. int rc = sigaction(SIGABRT, &act, nullptr);
  42. if (rc < 0) {
  43. perror("sigaction");
  44. exit(1);
  45. }
  46. abort();
  47. }
  48. int main(int argc, char** argv)
  49. {
  50. g_test_argc = argc;
  51. g_test_argv = argv;
  52. auto program_name = LexicalPath::basename(argv[0]);
  53. g_program_name = program_name;
  54. struct sigaction act;
  55. memset(&act, 0, sizeof(act));
  56. act.sa_flags = SA_NOCLDWAIT;
  57. act.sa_handler = handle_sigabrt;
  58. int rc = sigaction(SIGABRT, &act, nullptr);
  59. if (rc < 0) {
  60. perror("sigaction");
  61. return 1;
  62. }
  63. #ifdef SIGINFO
  64. signal(SIGINFO, [](int) {
  65. static char buffer[4096];
  66. auto& counts = ::Test::TestRunner::the()->counts();
  67. 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());
  68. write(STDOUT_FILENO, buffer, len);
  69. });
  70. #endif
  71. bool print_times = false;
  72. bool print_progress =
  73. #ifdef __serenity__
  74. true; // Use OSC 9 to print progress
  75. #else
  76. false;
  77. #endif
  78. bool print_json = false;
  79. const char* specified_test_root = nullptr;
  80. String common_path;
  81. String test_glob;
  82. Core::ArgsParser args_parser;
  83. args_parser.add_option(print_times, "Show duration of each test", "show-time", 't');
  84. args_parser.add_option(Core::ArgsParser::Option {
  85. .requires_argument = true,
  86. .help_string = "Show progress with OSC 9 (true, false)",
  87. .long_name = "show-progress",
  88. .short_name = 'p',
  89. .accept_value = [&](auto* str) {
  90. if ("true"sv == str)
  91. print_progress = true;
  92. else if ("false"sv == str)
  93. print_progress = false;
  94. else
  95. return false;
  96. return true;
  97. },
  98. });
  99. args_parser.add_option(print_json, "Show results as JSON", "json", 'j');
  100. args_parser.add_option(g_collect_on_every_allocation, "Collect garbage after every allocation", "collect-often", 'g');
  101. #ifdef JS_TRACK_ZOMBIE_CELLS
  102. args_parser.add_option(g_zombify_dead_cells, "Zombify dead cells (to catch missing GC marks)", "zombify-dead-cells", 'z');
  103. #endif
  104. args_parser.add_option(g_run_bytecode, "Use the bytecode interpreter", "run-bytecode", 'b');
  105. args_parser.add_option(g_dump_bytecode, "Dump the bytecode", "dump-bytecode", 'd');
  106. args_parser.add_option(test_glob, "Only run tests matching the given glob", "filter", 'f', "glob");
  107. for (auto& entry : g_extra_args)
  108. args_parser.add_option(*entry.key, entry.value.get<0>().characters(), entry.value.get<1>().characters(), entry.value.get<2>());
  109. args_parser.add_positional_argument(specified_test_root, "Tests root directory", "path", Core::ArgsParser::Required::No);
  110. args_parser.add_positional_argument(common_path, "Path to tests-common.js", "common-path", Core::ArgsParser::Required::No);
  111. args_parser.parse(argc, argv);
  112. test_glob = String::formatted("*{}*", test_glob);
  113. if (getenv("DISABLE_DBG_OUTPUT")) {
  114. AK::set_debug_enabled(false);
  115. }
  116. if (g_dump_bytecode && !g_run_bytecode) {
  117. warnln("--dump-bytecode can only be used when --run-bytecode is specified.");
  118. return 1;
  119. }
  120. String test_root;
  121. if (specified_test_root) {
  122. test_root = String { specified_test_root };
  123. } else {
  124. #ifdef __serenity__
  125. test_root = LexicalPath::join("/home/anon", String::formatted("{}-tests", program_name.split_view('-').last())).string();
  126. #else
  127. char* serenity_source_dir = getenv("SERENITY_SOURCE_DIR");
  128. if (!serenity_source_dir) {
  129. warnln("No test root given, {} requires the SERENITY_SOURCE_DIR environment variable to be set", g_program_name);
  130. return 1;
  131. }
  132. test_root = String::formatted("{}/{}", serenity_source_dir, g_test_root_fragment);
  133. common_path = String::formatted("{}/Userland/Libraries/LibJS/Tests/test-common.js", serenity_source_dir);
  134. #endif
  135. }
  136. if (!Core::File::is_directory(test_root)) {
  137. warnln("Test root is not a directory: {}", test_root);
  138. return 1;
  139. }
  140. if (common_path.is_empty()) {
  141. #ifdef __serenity__
  142. common_path = "/home/anon/js-tests/test-common.js";
  143. #else
  144. char* serenity_source_dir = getenv("SERENITY_SOURCE_DIR");
  145. if (!serenity_source_dir) {
  146. warnln("No test root given, {} requires the SERENITY_SOURCE_DIR environment variable to be set", g_program_name);
  147. return 1;
  148. }
  149. common_path = String::formatted("{}/Userland/Libraries/LibJS/Tests/test-common.js", serenity_source_dir);
  150. #endif
  151. }
  152. test_root = Core::File::real_path_for(test_root);
  153. common_path = Core::File::real_path_for(common_path);
  154. if (chdir(test_root.characters()) < 0) {
  155. auto saved_errno = errno;
  156. warnln("chdir failed: {}", strerror(saved_errno));
  157. return 1;
  158. }
  159. if (g_main_hook)
  160. g_main_hook();
  161. if (!g_vm)
  162. g_vm = JS::VM::create();
  163. Test::JS::TestRunner test_runner(test_root, common_path, print_times, print_progress, print_json);
  164. test_runner.run(test_glob);
  165. g_vm = nullptr;
  166. return test_runner.counts().tests_failed > 0 ? 1 : 0;
  167. }