diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index 58f60dbb0a..3daf145601 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -604,8 +604,9 @@ if (BUILD_LAGOM) ) set_tests_properties(JS PROPERTIES ENVIRONMENT SERENITY_SOURCE_DIR=${SERENITY_PROJECT_ROOT}) - # test-invalid-unicode-js + # Extra tests from Tests/LibJS lagom_test(../../Tests/LibJS/test-invalid-unicode-js.cpp LIBS LagomJS) + lagom_test(../../Tests/LibJS/test-bytecode-js.cpp LIBS LagomJS) # Markdown include(commonmark_spec) diff --git a/Tests/LibJS/CMakeLists.txt b/Tests/LibJS/CMakeLists.txt index 210440c6f6..5ed112091d 100644 --- a/Tests/LibJS/CMakeLists.txt +++ b/Tests/LibJS/CMakeLists.txt @@ -5,3 +5,6 @@ link_with_unicode_data(test-js) serenity_test(test-invalid-unicode-js.cpp LibJS LIBS LibJS) link_with_unicode_data(test-invalid-unicode-js) + +serenity_test(test-bytecode-js.cpp LibJS LIBS LibJS) +link_with_unicode_data(test-bytecode-js) diff --git a/Tests/LibJS/test-bytecode-js.cpp b/Tests/LibJS/test-bytecode-js.cpp new file mode 100644 index 0000000000..e2dad31beb --- /dev/null +++ b/Tests/LibJS/test-bytecode-js.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2022, David Tuin + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +#define SETUP_AND_PARSE(source) \ + auto vm = JS::VM::create(); \ + auto ast_interpreter = JS::Interpreter::create(*vm); \ + \ + auto script_or_error = JS::Script::parse(source, ast_interpreter->realm()); \ + EXPECT(!script_or_error.is_error()); \ + \ + auto script = script_or_error.release_value(); \ + auto const& program = script->parse_node(); \ + JS::Bytecode::Interpreter bytecode_interpreter(ast_interpreter->global_object(), ast_interpreter->realm()); + +#define EXPECT_NO_EXCEPTION(executable) \ + auto executable = JS::Bytecode::Generator::generate(program); \ + auto result = bytecode_interpreter.run(executable); \ + EXPECT(!result.is_error()); \ + EXPECT(!vm->exception()); + +#define EXPECT_NO_EXCEPTION_WITH_OPTIMIZATIONS(executable) \ + auto& passes = JS::Bytecode::Interpreter::optimization_pipeline(); \ + passes.perform(executable); \ + \ + auto result_with_optimizations = bytecode_interpreter.run(executable); \ + \ + EXPECT(!result_with_optimizations.is_error()); \ + EXPECT(!vm->exception()) + +#define EXPECT_NO_EXCEPTION_ALL(source) \ + SETUP_AND_PARSE(source) \ + EXPECT_NO_EXCEPTION(executable) \ + EXPECT_NO_EXCEPTION_WITH_OPTIMIZATIONS(executable) + +TEST_CASE(empty_program) +{ + EXPECT_NO_EXCEPTION_ALL(""); +} + +TEST_CASE(if_statement_pass) +{ + EXPECT_NO_EXCEPTION_ALL("if (false) throw new Exception('failed');"); +} + +TEST_CASE(if_statement_fail) +{ + SETUP_AND_PARSE("if (true) throw new Exception('failed');"); + + auto executable = JS::Bytecode::Generator::generate(program); + auto result = bytecode_interpreter.run(executable); + EXPECT(result.is_error()); +} + +TEST_CASE(trivial_program) +{ + EXPECT_NO_EXCEPTION_ALL("if (1 + 1 !== 2) throw new Exception('failed');"); +} + +TEST_CASE(variables) +{ + EXPECT_NO_EXCEPTION_ALL("var a = 1; \n" + "if (a + 1 !== 2) throw new Exception('failed'); "); +} + +TEST_CASE(function_call) +{ + EXPECT_NO_EXCEPTION_ALL("if (!isNaN(NaN)) throw new Exception('failed'); "); +} + +TEST_CASE(function_delcaration_and_call) +{ + EXPECT_NO_EXCEPTION_ALL("var passed = false; \n" + "function f() { passed = true; return 1; }\n" + "if (f() !== 1) throw new Exception('failed');\n" + // The passed !== true is needed as otherwise UBSAN + // complains about unaligned access, until that + // is fixed or ignored care must be taken to prevent such cases in tests. + "if (passed !== true) throw new Exception('failed');"); +} + +TEST_CASE(generator_function_call) +{ + EXPECT_NO_EXCEPTION_ALL("function *g() { yield 2; }\n" + "var gen = g();\n" + "var result = gen.next();\n" + "if (result.value !== 2) throw new Exception('failed');"); +} + +TEST_CASE(loading_multiple_files) +{ + // This is a testcase which is very much like test-js and test262 + // which load some common files first and only then the actual test file. + + SETUP_AND_PARSE("function f() { return 'hello'; }"); + + { + EXPECT_NO_EXCEPTION(common_file_executable); + } + + { + auto test_file_script_or_error = JS::Script::parse("if (f() !== 'hello') throw new Exception('failed'); ", ast_interpreter->realm()); + EXPECT(!test_file_script_or_error.is_error()); + + auto test_file_script = test_file_script_or_error.release_value(); + auto const& test_file_program = test_file_script->parse_node(); + + auto executable = JS::Bytecode::Generator::generate(test_file_program); + auto result = bytecode_interpreter.run(executable); + EXPECT(!result.is_error()); + EXPECT(!vm->exception()); + } +}