Serenity Operating System
at master 202 lines 7.1 kB view raw
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 9#include <LibCore/ArgsParser.h> 10#include <LibCore/DeprecatedFile.h> 11#include <LibTest/JavaScriptTestRunner.h> 12#include <signal.h> 13#include <stdio.h> 14 15namespace Test { 16 17TestRunner* ::Test::TestRunner::s_the = nullptr; 18 19namespace JS { 20 21RefPtr<::JS::VM> g_vm; 22bool g_collect_on_every_allocation = false; 23bool g_run_bytecode = false; 24DeprecatedString g_currently_running_test; 25HashMap<DeprecatedString, FunctionWithLength> s_exposed_global_functions; 26Function<void()> g_main_hook; 27Function<NonnullOwnPtr<JS::Interpreter>()> g_create_interpreter_hook; 28HashMap<bool*, Tuple<DeprecatedString, DeprecatedString, char>> g_extra_args; 29IntermediateRunFileResult (*g_run_file)(DeprecatedString const&, JS::Interpreter&, JS::ExecutionContext&) = nullptr; 30DeprecatedString g_test_root; 31int g_test_argc; 32char** g_test_argv; 33 34} // namespace JS 35} // namespace Test 36 37using namespace Test::JS; 38 39static StringView g_program_name { "test-js"sv }; 40 41static void handle_sigabrt(int) 42{ 43 dbgln("{}: SIGABRT received, cleaning up.", g_program_name); 44 Test::cleanup(); 45 struct sigaction act; 46 memset(&act, 0, sizeof(act)); 47 act.sa_flags = SA_NOCLDWAIT; 48 act.sa_handler = SIG_DFL; 49 int rc = sigaction(SIGABRT, &act, nullptr); 50 if (rc < 0) { 51 perror("sigaction"); 52 exit(1); 53 } 54 abort(); 55} 56 57int main(int argc, char** argv) 58{ 59 Vector<StringView> arguments; 60 arguments.ensure_capacity(argc); 61 for (auto i = 0; i < argc; ++i) 62 arguments.append({ argv[i], strlen(argv[i]) }); 63 64 g_test_argc = argc; 65 g_test_argv = argv; 66 auto program_name = LexicalPath::basename(argv[0]); 67 g_program_name = program_name; 68 69 struct sigaction act; 70 memset(&act, 0, sizeof(act)); 71 act.sa_flags = SA_NOCLDWAIT; 72 act.sa_handler = handle_sigabrt; 73 int rc = sigaction(SIGABRT, &act, nullptr); 74 if (rc < 0) { 75 perror("sigaction"); 76 return 1; 77 } 78 79#ifdef SIGINFO 80 signal(SIGINFO, [](int) { 81 static char buffer[4096]; 82 auto& counts = ::Test::TestRunner::the()->counts(); 83 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()); 84 write(STDOUT_FILENO, buffer, len); 85 }); 86#endif 87 88 bool print_times = false; 89 bool print_progress = 90#ifdef AK_OS_SERENITY 91 true; // Use OSC 9 to print progress 92#else 93 false; 94#endif 95 bool print_json = false; 96 bool per_file = false; 97 StringView specified_test_root; 98 DeprecatedString common_path; 99 DeprecatedString test_glob; 100 101 Core::ArgsParser args_parser; 102 args_parser.add_option(print_times, "Show duration of each test", "show-time", 't'); 103 args_parser.add_option(Core::ArgsParser::Option { 104 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required, 105 .help_string = "Show progress with OSC 9 (true, false)", 106 .long_name = "show-progress", 107 .short_name = 'p', 108 .accept_value = [&](StringView str) { 109 if ("true"sv == str) 110 print_progress = true; 111 else if ("false"sv == str) 112 print_progress = false; 113 else 114 return false; 115 return true; 116 }, 117 }); 118 args_parser.add_option(print_json, "Show results as JSON", "json", 'j'); 119 args_parser.add_option(per_file, "Show detailed per-file results as JSON (implies -j)", "per-file", 0); 120 args_parser.add_option(g_collect_on_every_allocation, "Collect garbage after every allocation", "collect-often", 'g'); 121 args_parser.add_option(g_run_bytecode, "Use the bytecode interpreter", "run-bytecode", 'b'); 122 args_parser.add_option(JS::Bytecode::g_dump_bytecode, "Dump the bytecode", "dump-bytecode", 'd'); 123 args_parser.add_option(test_glob, "Only run tests matching the given glob", "filter", 'f', "glob"); 124 for (auto& entry : g_extra_args) 125 args_parser.add_option(*entry.key, entry.value.get<0>().characters(), entry.value.get<1>().characters(), entry.value.get<2>()); 126 args_parser.add_positional_argument(specified_test_root, "Tests root directory", "path", Core::ArgsParser::Required::No); 127 args_parser.add_positional_argument(common_path, "Path to tests-common.js", "common-path", Core::ArgsParser::Required::No); 128 args_parser.parse(arguments); 129 130 if (per_file) 131 print_json = true; 132 133 test_glob = DeprecatedString::formatted("*{}*", test_glob); 134 135 if (getenv("DISABLE_DBG_OUTPUT")) { 136 AK::set_debug_enabled(false); 137 } 138 139 if (JS::Bytecode::g_dump_bytecode && !g_run_bytecode) { 140 warnln("--dump-bytecode can only be used when --run-bytecode is specified."); 141 return 1; 142 } 143 144 DeprecatedString test_root; 145 146 if (!specified_test_root.is_empty()) { 147 test_root = DeprecatedString { specified_test_root }; 148 } else { 149#ifdef AK_OS_SERENITY 150 test_root = LexicalPath::join("/home/anon/Tests"sv, DeprecatedString::formatted("{}-tests", program_name.split_view('-').last())).string(); 151#else 152 char* serenity_source_dir = getenv("SERENITY_SOURCE_DIR"); 153 if (!serenity_source_dir) { 154 warnln("No test root given, {} requires the SERENITY_SOURCE_DIR environment variable to be set", g_program_name); 155 return 1; 156 } 157 test_root = DeprecatedString::formatted("{}/{}", serenity_source_dir, g_test_root_fragment); 158 common_path = DeprecatedString::formatted("{}/Userland/Libraries/LibJS/Tests/test-common.js", serenity_source_dir); 159#endif 160 } 161 if (!Core::DeprecatedFile::is_directory(test_root)) { 162 warnln("Test root is not a directory: {}", test_root); 163 return 1; 164 } 165 166 if (common_path.is_empty()) { 167#ifdef AK_OS_SERENITY 168 common_path = "/home/anon/Tests/js-tests/test-common.js"; 169#else 170 char* serenity_source_dir = getenv("SERENITY_SOURCE_DIR"); 171 if (!serenity_source_dir) { 172 warnln("No test root given, {} requires the SERENITY_SOURCE_DIR environment variable to be set", g_program_name); 173 return 1; 174 } 175 common_path = DeprecatedString::formatted("{}/Userland/Libraries/LibJS/Tests/test-common.js", serenity_source_dir); 176#endif 177 } 178 179 test_root = Core::DeprecatedFile::real_path_for(test_root); 180 common_path = Core::DeprecatedFile::real_path_for(common_path); 181 182 if (chdir(test_root.characters()) < 0) { 183 auto saved_errno = errno; 184 warnln("chdir failed: {}", strerror(saved_errno)); 185 return 1; 186 } 187 188 if (g_main_hook) 189 g_main_hook(); 190 191 if (!g_vm) { 192 g_vm = JS::VM::create(); 193 g_vm->enable_default_host_import_module_dynamically_hook(); 194 } 195 196 Test::JS::TestRunner test_runner(test_root, common_path, print_times, print_progress, print_json, per_file); 197 test_runner.run(test_glob); 198 199 g_vm = nullptr; 200 201 return test_runner.counts().tests_failed > 0 ? 1 : 0; 202}