mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-26 01:35:08 +02:00
The bytecode interpreter only needed the running execution context, but still threaded a separate Interpreter object through both the C++ and asm entry points. Move that state and the bytecode execution helpers onto VM instead, and teach the asm generator and slow paths to use VM directly.
223 lines
7.6 KiB
C++
223 lines
7.6 KiB
C++
/*
|
|
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
|
|
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
|
|
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/LexicalPath.h>
|
|
#include <LibCore/ArgsParser.h>
|
|
#include <LibCore/Environment.h>
|
|
#include <LibCore/System.h>
|
|
#include <LibFileSystem/FileSystem.h>
|
|
#include <LibJS/Bytecode/Debug.h>
|
|
#include <LibTest/JavaScriptTestRunner.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
|
|
namespace Test {
|
|
|
|
TestRunner* ::Test::TestRunner::s_the = nullptr;
|
|
|
|
namespace JS {
|
|
|
|
GC_DEFINE_ALLOCATOR(TestRunnerGlobalObject);
|
|
|
|
RefPtr<::JS::VM> g_vm;
|
|
bool g_collect_on_every_allocation = false;
|
|
ByteString g_currently_running_test;
|
|
HashMap<Utf16String, FunctionWithLength> s_exposed_global_functions;
|
|
Function<void()> g_main_hook;
|
|
HashMap<bool*, Tuple<ByteString, ByteString, char>> g_extra_args;
|
|
IntermediateRunFileResult (*g_run_file)(ByteString const&, JS::Realm&, JS::ExecutionContext&) = nullptr;
|
|
ByteString g_test_root;
|
|
int g_test_argc;
|
|
char** g_test_argv;
|
|
|
|
} // namespace JS
|
|
} // namespace Test
|
|
|
|
using namespace Test::JS;
|
|
|
|
static StringView g_program_name { "test-js"sv };
|
|
|
|
static bool set_abort_action(void (*function)(int))
|
|
{
|
|
#if defined(AK_OS_WINDOWS)
|
|
auto rc = signal(SIGABRT, function);
|
|
if (rc == SIG_ERR) {
|
|
perror("sigaction");
|
|
return false;
|
|
}
|
|
return true;
|
|
#else
|
|
struct sigaction act;
|
|
memset(&act, 0, sizeof(act));
|
|
act.sa_flags = 0;
|
|
act.sa_handler = function;
|
|
int rc = sigaction(SIGABRT, &act, nullptr);
|
|
if (rc < 0) {
|
|
perror("sigaction");
|
|
return false;
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
static void handle_sigabrt(int)
|
|
{
|
|
dbgln("{}: SIGABRT received, cleaning up.", g_program_name);
|
|
Test::cleanup();
|
|
if (!set_abort_action(SIG_DFL))
|
|
exit(1);
|
|
abort();
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
Vector<StringView> arguments;
|
|
arguments.ensure_capacity(argc);
|
|
for (auto i = 0; i < argc; ++i)
|
|
arguments.append({ argv[i], strlen(argv[i]) });
|
|
|
|
g_test_argc = argc;
|
|
g_test_argv = argv;
|
|
auto program_name = LexicalPath::basename(argv[0]);
|
|
g_program_name = program_name;
|
|
|
|
if (!set_abort_action(handle_sigabrt))
|
|
return 1;
|
|
|
|
#ifdef SIGINFO
|
|
signal(SIGINFO, [](int) {
|
|
static char buffer[4096];
|
|
auto& counts = ::Test::TestRunner::the()->counts();
|
|
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());
|
|
write(STDOUT_FILENO, buffer, len);
|
|
});
|
|
#endif
|
|
|
|
bool print_times = false;
|
|
bool print_progress = false;
|
|
bool print_json = false;
|
|
bool per_file = false;
|
|
StringView specified_test_root;
|
|
ByteString common_path;
|
|
Vector<ByteString> test_globs;
|
|
|
|
Core::ArgsParser args_parser;
|
|
args_parser.add_option(print_times, "Show duration of each test", "show-time", 't');
|
|
args_parser.add_option(Core::ArgsParser::Option {
|
|
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
|
|
.help_string = "Show progress with OSC 9 (true, false)",
|
|
.long_name = "show-progress",
|
|
.short_name = 'p',
|
|
.accept_value = [&](StringView str) {
|
|
if ("true"sv == str)
|
|
print_progress = true;
|
|
else if ("false"sv == str)
|
|
print_progress = false;
|
|
else
|
|
return false;
|
|
return true;
|
|
},
|
|
});
|
|
|
|
args_parser.add_option(print_json, "Show results as JSON", "json", 'j');
|
|
args_parser.add_option(per_file, "Show detailed per-file results as JSON (implies -j)", "per-file");
|
|
args_parser.add_option(g_collect_on_every_allocation, "Collect garbage after every allocation", "collect-often", 'g');
|
|
args_parser.add_option(JS::Bytecode::g_dump_bytecode, "Dump the bytecode", "dump-bytecode", 'd');
|
|
args_parser.add_option(test_globs, "Only run tests matching the given glob", "filter", 'f', "glob");
|
|
for (auto& entry : g_extra_args)
|
|
args_parser.add_option(*entry.key, entry.value.get<0>().characters(), entry.value.get<1>().characters(), entry.value.get<2>());
|
|
args_parser.add_positional_argument(specified_test_root, "Tests root directory", "path", Core::ArgsParser::Required::No);
|
|
args_parser.add_positional_argument(common_path, "Path to tests-common.js", "common-path", Core::ArgsParser::Required::No);
|
|
args_parser.parse(arguments);
|
|
|
|
if (per_file)
|
|
print_json = true;
|
|
|
|
for (auto& glob : test_globs)
|
|
glob = ByteString::formatted("*{}*", glob);
|
|
if (test_globs.is_empty())
|
|
test_globs.append("*"sv);
|
|
|
|
if (Core::Environment::has("DISABLE_DBG_OUTPUT"sv)) {
|
|
AK::set_debug_enabled(false);
|
|
}
|
|
|
|
ByteString test_root;
|
|
|
|
if (!specified_test_root.is_empty()) {
|
|
test_root = ByteString { specified_test_root };
|
|
} else {
|
|
auto ladybird_source_dir = Core::Environment::get("LADYBIRD_SOURCE_DIR"sv);
|
|
if (!ladybird_source_dir.has_value()) {
|
|
warnln("No test root given, {} requires the LADYBIRD_SOURCE_DIR environment variable to be set", g_program_name);
|
|
return 1;
|
|
}
|
|
test_root = LexicalPath::join(*ladybird_source_dir, g_test_root_fragment).string();
|
|
common_path = LexicalPath::join(*ladybird_source_dir, "Tests"sv, "LibJS"sv, "Runtime"sv, "test-common.js"sv).string();
|
|
}
|
|
if (!FileSystem::is_directory(test_root)) {
|
|
warnln("Test root is not a directory: {}", test_root);
|
|
return 1;
|
|
}
|
|
|
|
if (common_path.is_empty()) {
|
|
auto ladybird_source_dir = Core::Environment::get("LADYBIRD_SOURCE_DIR"sv);
|
|
if (!ladybird_source_dir.has_value()) {
|
|
warnln("No test root given, {} requires the LADYBIRD_SOURCE_DIR environment variable to be set", g_program_name);
|
|
return 1;
|
|
}
|
|
common_path = LexicalPath::join(*ladybird_source_dir, "Tests"sv, "LibJS"sv, "Runtime"sv, "test-common.js"sv).string();
|
|
}
|
|
|
|
auto test_root_or_error = FileSystem::real_path(test_root);
|
|
if (test_root_or_error.is_error()) {
|
|
warnln("Failed to resolve test root: {}", test_root_or_error.error());
|
|
return 1;
|
|
}
|
|
test_root = test_root_or_error.release_value();
|
|
|
|
auto common_path_or_error = FileSystem::real_path(common_path);
|
|
if (common_path_or_error.is_error()) {
|
|
warnln("Failed to resolve common path: {}", common_path_or_error.error());
|
|
return 1;
|
|
}
|
|
common_path = common_path_or_error.release_value();
|
|
|
|
if (auto err = Core::System::chdir(test_root); err.is_error()) {
|
|
warnln("chdir failed: {}", err.error());
|
|
return 1;
|
|
}
|
|
|
|
if (g_main_hook)
|
|
g_main_hook();
|
|
|
|
if (!g_vm) {
|
|
g_vm = JS::VM::create();
|
|
g_vm->set_dynamic_imports_allowed(true);
|
|
|
|
// Configure the test VM to support additional import attributes
|
|
// This allows tests to use import attributes beyond just "type"
|
|
Test::JS::g_vm->host_get_supported_import_attributes = []() -> Vector<Utf16String> {
|
|
return {
|
|
"type"_utf16,
|
|
"key"_utf16, // Used in modules/import-with-attributes.mjs test
|
|
"key1"_utf16, // Used in modules/basic-modules.js
|
|
"key2"_utf16, // Used in modules/import-with-attributes.mjs test
|
|
"default"_utf16, // Used in modules/import-with-attributes.mjs test
|
|
};
|
|
};
|
|
}
|
|
|
|
Test::JS::TestRunner test_runner(test_root, common_path, print_times, print_progress, print_json, per_file);
|
|
test_runner.run(test_globs);
|
|
|
|
g_vm = nullptr;
|
|
|
|
return test_runner.counts().tests_failed > 0 ? 1 : 0;
|
|
}
|