Serenity Operating System
1/*
2 * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
3 * Copyright (c) 2021-2022, David Tuin <davidot@serenityos.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <AK/DeprecatedString.h>
9#include <AK/Format.h>
10#include <AK/JsonObject.h>
11#include <AK/Result.h>
12#include <AK/ScopeGuard.h>
13#include <AK/Vector.h>
14#include <LibCore/ArgsParser.h>
15#include <LibCore/File.h>
16#include <LibJS/Bytecode/BasicBlock.h>
17#include <LibJS/Bytecode/Generator.h>
18#include <LibJS/Bytecode/Interpreter.h>
19#include <LibJS/Bytecode/PassManager.h>
20#include <LibJS/Contrib/Test262/GlobalObject.h>
21#include <LibJS/Interpreter.h>
22#include <LibJS/Parser.h>
23#include <LibJS/Runtime/VM.h>
24#include <LibJS/Script.h>
25#include <fcntl.h>
26#include <signal.h>
27#include <unistd.h>
28
29#if !defined(AK_OS_MACOS) && !defined(AK_OS_EMSCRIPTEN)
30// Only used to disable core dumps
31# include <sys/prctl.h>
32#endif
33
34static DeprecatedString s_current_test = "";
35static bool s_use_bytecode = false;
36static bool s_enable_bytecode_optimizations = false;
37static bool s_parse_only = false;
38static DeprecatedString s_harness_file_directory;
39static bool s_automatic_harness_detection_mode = false;
40
41enum class NegativePhase {
42 ParseOrEarly,
43 Resolution,
44 Runtime,
45 Harness
46};
47
48struct TestError {
49 NegativePhase phase { NegativePhase::ParseOrEarly };
50 DeprecatedString type;
51 DeprecatedString details;
52 DeprecatedString harness_file;
53};
54
55using ScriptOrModuleProgram = Variant<JS::NonnullGCPtr<JS::Script>, JS::NonnullGCPtr<JS::SourceTextModule>>;
56
57template<typename ScriptType>
58static Result<ScriptOrModuleProgram, TestError> parse_program(JS::Realm& realm, StringView source, StringView filepath)
59{
60 auto script_or_error = ScriptType::parse(source, realm, filepath);
61 if (script_or_error.is_error()) {
62 return TestError {
63 NegativePhase::ParseOrEarly,
64 "SyntaxError",
65 script_or_error.error()[0].to_deprecated_string(),
66 ""
67 };
68 }
69 return ScriptOrModuleProgram { script_or_error.release_value() };
70}
71
72static Result<ScriptOrModuleProgram, TestError> parse_program(JS::Realm& realm, StringView source, StringView filepath, JS::Program::Type program_type)
73{
74 if (program_type == JS::Program::Type::Script)
75 return parse_program<JS::Script>(realm, source, filepath);
76 return parse_program<JS::SourceTextModule>(realm, source, filepath);
77}
78
79template<typename InterpreterT>
80static Result<void, TestError> run_program(InterpreterT& interpreter, ScriptOrModuleProgram& program)
81{
82 auto result = JS::ThrowCompletionOr<JS::Value> { JS::js_undefined() };
83 if constexpr (IsSame<InterpreterT, JS::Interpreter>) {
84 result = program.visit(
85 [&](auto& visitor) {
86 return interpreter.run(*visitor);
87 });
88 } else {
89 auto program_node = program.visit(
90 [](auto& visitor) -> NonnullRefPtr<JS::Program const> {
91 return visitor->parse_node();
92 });
93
94 auto& vm = interpreter.vm();
95
96 if (auto unit_result = JS::Bytecode::Generator::generate(program_node); unit_result.is_error()) {
97 if (auto error_string = unit_result.error().to_string(); error_string.is_error())
98 result = vm.template throw_completion<JS::InternalError>(vm.error_message(JS::VM::ErrorMessage::OutOfMemory));
99 else if (error_string = String::formatted("TODO({})", error_string.value()); error_string.is_error())
100 result = vm.template throw_completion<JS::InternalError>(vm.error_message(JS::VM::ErrorMessage::OutOfMemory));
101 else
102 result = JS::throw_completion(JS::InternalError::create(interpreter.realm(), error_string.release_value()));
103 } else {
104 auto unit = unit_result.release_value();
105 auto optimization_level = s_enable_bytecode_optimizations ? JS::Bytecode::Interpreter::OptimizationLevel::Optimize : JS::Bytecode::Interpreter::OptimizationLevel::Default;
106 auto& passes = JS::Bytecode::Interpreter::optimization_pipeline(optimization_level);
107 passes.perform(*unit);
108 result = interpreter.run(*unit);
109 }
110 }
111
112 if (result.is_error()) {
113 auto error_value = *result.throw_completion().value();
114 TestError error;
115 error.phase = NegativePhase::Runtime;
116 if (error_value.is_object()) {
117 auto& object = error_value.as_object();
118
119 auto name = object.get_without_side_effects("name");
120 if (!name.is_empty() && !name.is_accessor()) {
121 error.type = name.to_string_without_side_effects().release_value_but_fixme_should_propagate_errors().to_deprecated_string();
122 } else {
123 auto constructor = object.get_without_side_effects("constructor");
124 if (constructor.is_object()) {
125 name = constructor.as_object().get_without_side_effects("name");
126 if (!name.is_undefined())
127 error.type = name.to_string_without_side_effects().release_value_but_fixme_should_propagate_errors().to_deprecated_string();
128 }
129 }
130
131 auto message = object.get_without_side_effects("message");
132 if (!message.is_empty() && !message.is_accessor())
133 error.details = message.to_string_without_side_effects().release_value_but_fixme_should_propagate_errors().to_deprecated_string();
134 }
135 if (error.type.is_empty())
136 error.type = error_value.to_string_without_side_effects().release_value_but_fixme_should_propagate_errors().to_deprecated_string();
137 return error;
138 }
139 return {};
140}
141
142static HashMap<DeprecatedString, DeprecatedString> s_cached_harness_files;
143
144static Result<StringView, TestError> read_harness_file(StringView harness_file)
145{
146 auto cache = s_cached_harness_files.find(harness_file);
147 if (cache == s_cached_harness_files.end()) {
148 auto file_or_error = Core::File::open(DeprecatedString::formatted("{}{}", s_harness_file_directory, harness_file), Core::File::OpenMode::Read);
149 if (file_or_error.is_error()) {
150 return TestError {
151 NegativePhase::Harness,
152 "filesystem",
153 DeprecatedString::formatted("Could not open file: {}", harness_file),
154 harness_file
155 };
156 }
157
158 auto contents_or_error = file_or_error.value()->read_until_eof();
159 if (contents_or_error.is_error()) {
160 return TestError {
161 NegativePhase::Harness,
162 "filesystem",
163 DeprecatedString::formatted("Could not read file: {}", harness_file),
164 harness_file
165 };
166 }
167
168 StringView contents_view = contents_or_error.value();
169 s_cached_harness_files.set(harness_file, contents_view.to_deprecated_string());
170 cache = s_cached_harness_files.find(harness_file);
171 VERIFY(cache != s_cached_harness_files.end());
172 }
173 return cache->value.view();
174}
175
176static Result<JS::NonnullGCPtr<JS::Script>, TestError> parse_harness_files(JS::Realm& realm, StringView harness_file)
177{
178 auto source_or_error = read_harness_file(harness_file);
179 if (source_or_error.is_error())
180 return source_or_error.release_error();
181 auto program_or_error = parse_program<JS::Script>(realm, source_or_error.value(), harness_file);
182 if (program_or_error.is_error()) {
183 return TestError {
184 NegativePhase::Harness,
185 program_or_error.error().type,
186 program_or_error.error().details,
187 harness_file
188 };
189 }
190 return program_or_error.release_value().get<JS::NonnullGCPtr<JS::Script>>();
191}
192
193enum class StrictMode {
194 Both,
195 NoStrict,
196 OnlyStrict
197};
198
199static constexpr auto sta_harness_file = "sta.js"sv;
200static constexpr auto assert_harness_file = "assert.js"sv;
201static constexpr auto async_include = "doneprintHandle.js"sv;
202
203struct TestMetadata {
204 Vector<StringView> harness_files { sta_harness_file, assert_harness_file };
205
206 StrictMode strict_mode { StrictMode::Both };
207 JS::Program::Type program_type { JS::Program::Type::Script };
208 bool is_async { false };
209
210 bool is_negative { false };
211 NegativePhase phase { NegativePhase::ParseOrEarly };
212 StringView type;
213};
214
215static Result<void, TestError> run_test(StringView source, StringView filepath, TestMetadata const& metadata)
216{
217 if (s_parse_only || (metadata.is_negative && metadata.phase == NegativePhase::ParseOrEarly && metadata.program_type != JS::Program::Type::Module)) {
218 // Creating the vm and interpreter is heavy so we just parse directly here.
219 // We can also skip if we know the test is supposed to fail during parse
220 // time. Unfortunately the phases of modules are not as clear and thus we
221 // only do this for scripts. See also the comment at the end of verify_test.
222 auto parser = JS::Parser(JS::Lexer(source, filepath), metadata.program_type);
223 auto program_or_error = parser.parse_program();
224 if (parser.has_errors()) {
225 return TestError {
226 NegativePhase::ParseOrEarly,
227 "SyntaxError",
228 parser.errors()[0].to_deprecated_string(),
229 ""
230 };
231 }
232 return {};
233 }
234
235 auto vm = JS::VM::create();
236 vm->enable_default_host_import_module_dynamically_hook();
237 auto ast_interpreter = JS::Interpreter::create<JS::Test262::GlobalObject>(*vm);
238 auto& realm = ast_interpreter->realm();
239
240 auto program_or_error = parse_program(realm, source, filepath, metadata.program_type);
241 if (program_or_error.is_error())
242 return program_or_error.release_error();
243
244 OwnPtr<JS::Bytecode::Interpreter> bytecode_interpreter = nullptr;
245 if (s_use_bytecode)
246 bytecode_interpreter = make<JS::Bytecode::Interpreter>(realm);
247
248 auto run_with_interpreter = [&](ScriptOrModuleProgram& program) {
249 if (s_use_bytecode)
250 return run_program(*bytecode_interpreter, program);
251 return run_program(*ast_interpreter, program);
252 };
253
254 for (auto& harness_file : metadata.harness_files) {
255 auto harness_program_or_error = parse_harness_files(realm, harness_file);
256 if (harness_program_or_error.is_error())
257 return harness_program_or_error.release_error();
258 ScriptOrModuleProgram harness_program { harness_program_or_error.release_value() };
259 auto result = run_with_interpreter(harness_program);
260 if (result.is_error()) {
261 return TestError {
262 NegativePhase::Harness,
263 result.error().type,
264 result.error().details,
265 harness_file
266 };
267 }
268 }
269
270 return run_with_interpreter(program_or_error.value());
271}
272
273static Result<TestMetadata, DeprecatedString> extract_metadata(StringView source)
274{
275 auto lines = source.lines();
276
277 TestMetadata metadata;
278
279 bool parsing_negative = false;
280 DeprecatedString failed_message;
281
282 auto parse_list = [&](StringView line) {
283 auto start = line.find('[');
284 if (!start.has_value())
285 return Vector<StringView> {};
286
287 Vector<StringView> items;
288
289 auto end = line.find_last(']');
290 if (!end.has_value() || end.value() <= start.value()) {
291 failed_message = DeprecatedString::formatted("Can't parse list in '{}'", line);
292 return items;
293 }
294
295 auto list = line.substring_view(start.value() + 1, end.value() - start.value() - 1);
296 for (auto const& item : list.split_view(","sv))
297 items.append(item.trim_whitespace(TrimMode::Both));
298 return items;
299 };
300
301 auto second_word = [&](StringView line) {
302 auto separator = line.find(' ');
303 if (!separator.has_value() || separator.value() >= (line.length() - 1u)) {
304 failed_message = DeprecatedString::formatted("Can't parse value after space in '{}'", line);
305 return ""sv;
306 }
307 return line.substring_view(separator.value() + 1);
308 };
309
310 Vector<StringView> include_list;
311 bool parsing_includes_list = false;
312 bool has_phase = false;
313
314 for (auto raw_line : lines) {
315 if (!failed_message.is_empty())
316 break;
317
318 if (raw_line.starts_with("---*/"sv)) {
319 if (parsing_includes_list) {
320 for (auto& file : include_list)
321 metadata.harness_files.append(file);
322 }
323 return metadata;
324 }
325
326 auto line = raw_line.trim_whitespace();
327
328 if (parsing_includes_list) {
329 if (line.starts_with('-')) {
330 include_list.append(second_word(line));
331 continue;
332 } else {
333 if (include_list.is_empty()) {
334 failed_message = "Supposed to parse a list but found no entries";
335 break;
336 }
337
338 for (auto& file : include_list)
339 metadata.harness_files.append(file);
340 include_list.clear();
341
342 parsing_includes_list = false;
343 }
344 }
345
346 if (parsing_negative) {
347 if (line.starts_with("phase:"sv)) {
348 auto phase = second_word(line);
349 has_phase = true;
350 if (phase == "early"sv || phase == "parse"sv) {
351 metadata.phase = NegativePhase::ParseOrEarly;
352 } else if (phase == "resolution"sv) {
353 metadata.phase = NegativePhase::Resolution;
354 } else if (phase == "runtime"sv) {
355 metadata.phase = NegativePhase::Runtime;
356 } else {
357 has_phase = false;
358 failed_message = DeprecatedString::formatted("Unknown negative phase: {}", phase);
359 break;
360 }
361 } else if (line.starts_with("type:"sv)) {
362 metadata.type = second_word(line);
363 } else {
364 if (!has_phase) {
365 failed_message = "Failed to find phase in negative attributes";
366 break;
367 }
368 if (metadata.type.is_null()) {
369 failed_message = "Failed to find type in negative attributes";
370 break;
371 }
372
373 parsing_negative = false;
374 }
375 }
376
377 if (line.starts_with("flags:"sv)) {
378 auto flags = parse_list(line);
379
380 if (flags.is_empty()) {
381 failed_message = DeprecatedString::formatted("Failed to find flags in '{}'", line);
382 break;
383 }
384
385 for (auto flag : flags) {
386 if (flag == "raw"sv) {
387 metadata.strict_mode = StrictMode::NoStrict;
388 metadata.harness_files.clear();
389 } else if (flag == "noStrict"sv) {
390 metadata.strict_mode = StrictMode::NoStrict;
391 } else if (flag == "onlyStrict"sv) {
392 metadata.strict_mode = StrictMode::OnlyStrict;
393 } else if (flag == "module"sv) {
394 VERIFY(metadata.strict_mode == StrictMode::Both);
395 metadata.program_type = JS::Program::Type::Module;
396 metadata.strict_mode = StrictMode::NoStrict;
397 } else if (flag == "async"sv) {
398 metadata.harness_files.append(async_include);
399 metadata.is_async = true;
400 }
401 }
402 } else if (line.starts_with("includes:"sv)) {
403 auto files = parse_list(line);
404 if (files.is_empty()) {
405 parsing_includes_list = true;
406 } else {
407 for (auto& file : files)
408 metadata.harness_files.append(file);
409 }
410 } else if (line.starts_with("negative:"sv)) {
411 metadata.is_negative = true;
412 parsing_negative = true;
413 }
414 }
415
416 if (failed_message.is_empty())
417 failed_message = DeprecatedString::formatted("Never reached end of comment '---*/'");
418
419 return failed_message;
420}
421
422static bool verify_test(Result<void, TestError>& result, TestMetadata const& metadata, JsonObject& output)
423{
424 if (result.is_error()) {
425 if (result.error().phase == NegativePhase::Harness) {
426 output.set("harness_error", true);
427 output.set("harness_file", result.error().harness_file);
428 output.set("result", "harness_error");
429 } else if (result.error().phase == NegativePhase::Runtime) {
430 auto& error_type = result.error().type;
431 auto& error_details = result.error().details;
432 if ((error_type == "InternalError"sv && error_details.starts_with("TODO("sv))
433 || (error_type == "Test262Error"sv && error_details.ends_with(" but got a InternalError"sv))) {
434 output.set("todo_error", true);
435 output.set("result", "todo_error");
436 }
437 }
438 }
439
440 if (metadata.is_async && output.has("output"sv)) {
441 auto output_messages = output.get_deprecated_string("output"sv);
442 VERIFY(output_messages.has_value());
443 if (output_messages->contains("AsyncTestFailure:InternalError: TODO("sv)) {
444 output.set("todo_error", true);
445 output.set("result", "todo_error");
446 }
447 }
448
449 auto phase_to_string = [](NegativePhase phase) {
450 switch (phase) {
451 case NegativePhase::ParseOrEarly:
452 return "parse";
453 case NegativePhase::Resolution:
454 return "resolution";
455 case NegativePhase::Runtime:
456 return "runtime";
457 case NegativePhase::Harness:
458 return "harness";
459 }
460 VERIFY_NOT_REACHED();
461 };
462
463 auto error_to_json = [&phase_to_string](TestError const& error) {
464 JsonObject error_object;
465 error_object.set("phase", phase_to_string(error.phase));
466 error_object.set("type", error.type);
467 error_object.set("details", error.details);
468 return error_object;
469 };
470
471 JsonValue expected_error;
472 JsonValue got_error;
473
474 ScopeGuard set_error = [&] {
475 JsonObject error_object;
476 error_object.set("expected", expected_error);
477 error_object.set("got", got_error);
478
479 output.set("error", error_object);
480 };
481
482 if (!metadata.is_negative) {
483 if (!result.is_error())
484 return true;
485
486 got_error = JsonValue { error_to_json(result.error()) };
487 return false;
488 }
489
490 JsonObject expected_error_object;
491 expected_error_object.set("phase", phase_to_string(metadata.phase));
492 expected_error_object.set("type", metadata.type.to_deprecated_string());
493
494 expected_error = expected_error_object;
495
496 if (!result.is_error()) {
497 if (s_parse_only && metadata.phase != NegativePhase::ParseOrEarly) {
498 // Expected non-parse error but did not get it but we never got to that phase.
499 return true;
500 }
501
502 return false;
503 }
504
505 auto const& error = result.error();
506
507 got_error = JsonValue { error_to_json(error) };
508
509 if (metadata.program_type == JS::Program::Type::Module && metadata.type == "SyntaxError"sv) {
510 // NOTE: Since the "phase" of negative results is both not defined and hard to
511 // track throughout the entire Module life span we will just accept any
512 // SyntaxError as the correct one.
513 // See for example:
514 // - test/language/module-code/instn-star-err-not-found.js
515 // - test/language/module-code/instn-resolve-err-syntax-1.js
516 // - test/language/import/json-invalid.js
517 // The first fails in runtime because there is no 'x' to export
518 // However this is during the linking phase of the upper module.
519 // Whereas the second fails with a SyntaxError because the linked module
520 // has one.
521 // The third test is the same as the second, upper module is fine but
522 // import a module with SyntaxError, however here the phase is runtime.
523 // In conclusion all the test which would cause the initial module to not
524 // be evaluated !should! have '$DONOTEVALUATE();' at the top causing a
525 // Reference error, meaning we just ignore the phase in the SyntaxError case.
526 return error.type == metadata.type;
527 }
528 return error.phase == metadata.phase && error.type == metadata.type;
529}
530
531static bool extract_harness_directory(DeprecatedString const& test_file_path)
532{
533 auto test_directory_index = test_file_path.find("test/"sv);
534 if (!test_directory_index.has_value()) {
535 warnln("Attempted to find harness directory from test file '{}', but did not find 'test/'", test_file_path);
536 return false;
537 }
538
539 s_harness_file_directory = DeprecatedString::formatted("{}harness/", test_file_path.substring_view(0, test_directory_index.value()));
540 return true;
541}
542
543static FILE* saved_stdout_fd;
544static bool g_in_assert = false;
545
546[[noreturn]] static void handle_failed_assert(char const* assert_failed_message)
547{
548 if (!g_in_assert) {
549 // Just in case we trigger an assert while creating the JSON output just
550 // immediately stop if we are already in a failed assert.
551 g_in_assert = true;
552 JsonObject assert_fail_result;
553 assert_fail_result.set("test", s_current_test);
554 assert_fail_result.set("assert_fail", true);
555 assert_fail_result.set("result", "assert_fail");
556 assert_fail_result.set("output", assert_failed_message);
557 outln(saved_stdout_fd, "RESULT {}{}", assert_fail_result.to_deprecated_string(), '\0');
558 // (Attempt to) Ensure that messages are written before quitting.
559 fflush(saved_stdout_fd);
560 fflush(stderr);
561 }
562 exit(12);
563}
564
565// FIXME: Use a SIGABRT handler here instead of overriding internal libc assertion handlers.
566// Fixing this will likely require updating the test driver as well to pull the assertion failure
567// message out of stderr rather than from the json object printed to stdout.
568#ifdef AK_OS_SERENITY
569void __assertion_failed(char const* assertion)
570{
571 handle_failed_assert(assertion);
572}
573#else
574# ifdef ASSERT_FAIL_HAS_INT /* Set by CMake */
575extern "C" __attribute__((__noreturn__)) void __assert_fail(char const* assertion, char const* file, int line, char const* function)
576# else
577extern "C" __attribute__((__noreturn__)) void __assert_fail(char const* assertion, char const* file, unsigned int line, char const* function)
578# endif
579{
580 auto full_message = DeprecatedString::formatted("{}:{}: {}: Assertion `{}' failed.", file, line, function, assertion);
581 handle_failed_assert(full_message.characters());
582}
583#endif
584
585constexpr int exit_wrong_arguments = 2;
586constexpr int exit_stdout_setup_failed = 1;
587constexpr int exit_setup_input_failure = 7;
588constexpr int exit_read_file_failure = 3;
589
590int main(int argc, char** argv)
591{
592 Vector<StringView> arguments;
593 arguments.ensure_capacity(argc);
594 for (auto i = 0; i < argc; ++i)
595 arguments.append({ argv[i], strlen(argv[i]) });
596
597 int timeout = 10;
598 bool enable_debug_printing = false;
599 bool disable_core_dumping = false;
600
601 Core::ArgsParser args_parser;
602 args_parser.set_general_help("LibJS test262 runner for streaming tests");
603 args_parser.add_option(s_harness_file_directory, "Directory containing the harness files", "harness-location", 'l', "harness-files");
604 args_parser.add_option(s_use_bytecode, "Use the bytecode interpreter", "use-bytecode", 'b');
605 args_parser.add_option(s_enable_bytecode_optimizations, "Enable the bytecode optimization passes", "enable-bytecode-optimizations", 'e');
606 args_parser.add_option(s_parse_only, "Only parse the files", "parse-only", 'p');
607 args_parser.add_option(timeout, "Seconds before test should timeout", "timeout", 't', "seconds");
608 args_parser.add_option(enable_debug_printing, "Enable debug printing", "debug", 'd');
609 args_parser.add_option(disable_core_dumping, "Disable core dumping", "disable-core-dump", 0);
610 args_parser.parse(arguments);
611
612#if !defined(AK_OS_MACOS) && !defined(AK_OS_EMSCRIPTEN)
613 if (disable_core_dumping && prctl(PR_SET_DUMPABLE, 0, 0) < 0) {
614 perror("prctl(PR_SET_DUMPABLE)");
615 return exit_wrong_arguments;
616 }
617#endif
618
619 if (s_harness_file_directory.is_empty()) {
620 s_automatic_harness_detection_mode = true;
621 } else if (!s_harness_file_directory.ends_with('/')) {
622 s_harness_file_directory = DeprecatedString::formatted("{}/", s_harness_file_directory);
623 }
624
625 if (timeout <= 0) {
626 warnln("timeout must be at least 1");
627 return exit_wrong_arguments;
628 }
629
630 AK::set_debug_enabled(enable_debug_printing);
631
632 // The piping stuff is based on https://stackoverflow.com/a/956269.
633 constexpr auto BUFFER_SIZE = 1 * KiB;
634 char buffer[BUFFER_SIZE] = {};
635
636 auto saved_stdout = dup(STDOUT_FILENO);
637 if (saved_stdout < 0) {
638 perror("dup");
639 return exit_stdout_setup_failed;
640 }
641
642 saved_stdout_fd = fdopen(saved_stdout, "w");
643 if (!saved_stdout_fd) {
644 perror("fdopen");
645 return exit_stdout_setup_failed;
646 }
647
648 int stdout_pipe[2];
649 if (pipe(stdout_pipe) < 0) {
650 perror("pipe");
651 return exit_stdout_setup_failed;
652 }
653
654 auto flags = fcntl(stdout_pipe[0], F_GETFL);
655 flags |= O_NONBLOCK;
656 fcntl(stdout_pipe[0], F_SETFL, flags);
657
658 auto flags2 = fcntl(stdout_pipe[1], F_GETFL);
659 flags2 |= O_NONBLOCK;
660 fcntl(stdout_pipe[1], F_SETFL, flags2);
661
662 if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0) {
663 perror("dup2");
664 return exit_stdout_setup_failed;
665 }
666
667 if (close(stdout_pipe[1]) < 0) {
668 perror("close");
669 return exit_stdout_setup_failed;
670 }
671
672 auto collect_output = [&] {
673 fflush(stdout);
674 auto nread = read(stdout_pipe[0], buffer, BUFFER_SIZE);
675 DeprecatedString value;
676
677 if (nread > 0) {
678 value = DeprecatedString { buffer, static_cast<size_t>(nread) };
679 while (nread > 0) {
680 nread = read(stdout_pipe[0], buffer, BUFFER_SIZE);
681 }
682 }
683
684 return value;
685 };
686
687#define ARM_TIMER() \
688 alarm(timeout)
689
690#define DISARM_TIMER() \
691 alarm(0)
692
693 auto standard_input_or_error = Core::File::standard_input();
694 if (standard_input_or_error.is_error())
695 return exit_setup_input_failure;
696
697 Array<u8, 1024> input_buffer {};
698 auto buffered_standard_input_or_error = Core::BufferedFile::create(standard_input_or_error.release_value());
699 if (buffered_standard_input_or_error.is_error())
700 return exit_setup_input_failure;
701
702 auto& buffered_input_stream = buffered_standard_input_or_error.value();
703
704 size_t count = 0;
705
706 while (!buffered_input_stream->is_eof()) {
707 auto path_or_error = buffered_input_stream->read_line(input_buffer);
708 if (path_or_error.is_error() || path_or_error.value().is_empty())
709 continue;
710
711 auto& path = path_or_error.value();
712
713 s_current_test = path;
714
715 if (s_automatic_harness_detection_mode) {
716 if (!extract_harness_directory(path))
717 return exit_read_file_failure;
718 s_automatic_harness_detection_mode = false;
719 VERIFY(!s_harness_file_directory.is_empty());
720 }
721
722 auto file_or_error = Core::File::open(path, Core::File::OpenMode::Read);
723 if (file_or_error.is_error()) {
724 warnln("Could not open file: {}", path);
725 return exit_read_file_failure;
726 }
727 auto& file = file_or_error.value();
728
729 count++;
730
731 DeprecatedString source_with_strict;
732 static StringView use_strict = "'use strict';\n"sv;
733 static size_t strict_length = use_strict.length();
734
735 {
736 auto contents_or_error = file->read_until_eof();
737 if (contents_or_error.is_error()) {
738 warnln("Could not read contents of file: {}", path);
739 return exit_read_file_failure;
740 }
741 auto& contents = contents_or_error.value();
742 StringBuilder builder { contents.size() + strict_length };
743 builder.append(use_strict);
744 builder.append(contents);
745 source_with_strict = builder.to_deprecated_string();
746 }
747
748 StringView with_strict = source_with_strict.view();
749 StringView original_contents = source_with_strict.substring_view(strict_length);
750
751 JsonObject result_object;
752 result_object.set("test", path);
753
754 ScopeGuard output_guard = [&] {
755 outln(saved_stdout_fd, "RESULT {}{}", result_object.to_deprecated_string(), '\0');
756 fflush(saved_stdout_fd);
757 };
758
759 auto metadata_or_error = extract_metadata(original_contents);
760 if (metadata_or_error.is_error()) {
761 result_object.set("result", "metadata_error");
762 result_object.set("metadata_error", true);
763 result_object.set("metadata_output", metadata_or_error.error());
764 continue;
765 }
766
767 auto& metadata = metadata_or_error.value();
768
769 bool passed = true;
770
771 if (metadata.strict_mode != StrictMode::OnlyStrict) {
772 result_object.set("strict_mode", false);
773
774 ARM_TIMER();
775 auto result = run_test(original_contents, path, metadata);
776 DISARM_TIMER();
777
778 DeprecatedString first_output = collect_output();
779 if (!first_output.is_null())
780 result_object.set("output", first_output);
781
782 passed = verify_test(result, metadata, result_object);
783 if (metadata.is_async && !s_parse_only) {
784 if (!first_output.contains("Test262:AsyncTestComplete"sv) || first_output.contains("Test262:AsyncTestFailure"sv)) {
785 result_object.set("async_fail", true);
786 if (first_output.is_null())
787 result_object.set("output", JsonValue { AK::JsonValue::Type::Null });
788
789 passed = false;
790 }
791 }
792 }
793
794 if (passed && metadata.strict_mode != StrictMode::NoStrict) {
795 result_object.set("strict_mode", true);
796
797 ARM_TIMER();
798 auto result = run_test(with_strict, path, metadata);
799 DISARM_TIMER();
800
801 DeprecatedString first_output = collect_output();
802 if (!first_output.is_null())
803 result_object.set("strict_output", first_output);
804
805 passed = verify_test(result, metadata, result_object);
806 if (metadata.is_async && !s_parse_only) {
807 if (!first_output.contains("Test262:AsyncTestComplete"sv) || first_output.contains("Test262:AsyncTestFailure"sv)) {
808 result_object.set("async_fail", true);
809 if (first_output.is_null())
810 result_object.set("output", JsonValue { AK::JsonValue::Type::Null });
811
812 passed = false;
813 }
814 }
815 }
816
817 if (passed)
818 result_object.remove("strict_mode"sv);
819
820 if (!result_object.has("result"sv))
821 result_object.set("result"sv, passed ? "passed"sv : "failed"sv);
822 }
823
824 s_current_test = "";
825 outln(saved_stdout_fd, "DONE {}", count);
826
827 // After this point we have already written our output so pretend everything is fine if we get an error.
828 if (dup2(saved_stdout, STDOUT_FILENO) < 0) {
829 perror("dup2");
830 return 0;
831 }
832
833 if (fclose(saved_stdout_fd) < 0) {
834 perror("fclose");
835 return 0;
836 }
837
838 if (close(stdout_pipe[0]) < 0) {
839 perror("close");
840 return 0;
841 }
842
843 return 0;
844}