Serenity Operating System
at master 844 lines 32 kB view raw
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}