From b3c13c3e8ae4e65310714283980dd42b2da30be8 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Tue, 4 May 2021 07:36:59 +0430 Subject: [PATCH] LibWasm+Meta: Add test-wasm and optionally test the conformance tests This only tests "can it be parsed", but the goal of this commit is to provide a test framework that can be built upon :) The conformance tests are downloaded, compiled* and installed only if the INCLUDE_WASM_SPEC_TESTS cmake option is enabled. (*) Since we do not yet have a wast parser, the compilation is delegated to an external tool from binaryen, `wasm-as`, which is required for the test suite download/install to succeed. This *does* run the tests in CI, but it currently does not include the spec conformance tests. --- .gitignore | 2 + CMakeLists.txt | 31 ++++++++ Documentation/BuildInstructions.md | 1 + Meta/Lagom/CMakeLists.txt | 15 ++++ Meta/build-root-filesystem.sh | 4 +- Tests/CMakeLists.txt | 1 + Tests/LibWasm/CMakeLists.txt | 2 + Tests/LibWasm/test-wasm.cpp | 70 ++++++++++++++++++ .../Tests/Fixtures/Modules/empty-module.wasm | Bin 0 -> 50 bytes .../LibWasm/Tests/Parser/spec-testsuite.js | 23 ++++++ .../LibWasm/Tests/Parser/test-basic-load.js | 59 +++++++++++++++ 11 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWasm/CMakeLists.txt create mode 100644 Tests/LibWasm/test-wasm.cpp create mode 100644 Userland/Libraries/LibWasm/Tests/Fixtures/Modules/empty-module.wasm create mode 100644 Userland/Libraries/LibWasm/Tests/Parser/spec-testsuite.js create mode 100644 Userland/Libraries/LibWasm/Tests/Parser/test-basic-load.js diff --git a/.gitignore b/.gitignore index cbe6b504ec..1830b6e0e1 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ compile_commands.json cmake-build-debug/ sync-local.sh .vim/ + +Userland/Libraries/LibWasm/Tests/Fixtures/SpecTests diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c591256f2..d5d3fc4e89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ option(ENABLE_COMPILETIME_FORMAT_CHECK "Enable compiletime format string checks" option(ENABLE_PCI_IDS_DOWNLOAD "Enable download of the pci.ids database at build time" ON) option(BUILD_LAGOM "Build parts of the system targeting the host OS for fuzzing/testing" OFF) option(ENABLE_KERNEL_LTO "Build the kernel with link-time optimization" OFF) +option(INCLUDE_WASM_SPEC_TESTS "Download and include the WebAssembly spec testsuite" OFF) add_custom_target(run COMMAND ${CMAKE_SOURCE_DIR}/Meta/run.sh @@ -245,3 +246,33 @@ if(EXISTS ${PCI_IDS_GZ_PATH} AND NOT EXISTS ${PCI_IDS_INSTALL_PATH}) file(MAKE_DIRECTORY ${CMAKE_INSTALL_DATAROOTDIR}) file(RENAME ${PCI_IDS_PATH} ${PCI_IDS_INSTALL_PATH}) endif() + +if(INCLUDE_WASM_SPEC_TESTS) + set(WASM_SPEC_TEST_GZ_URL https://github.com/WebAssembly/testsuite/archive/refs/heads/master.tar.gz) + set(WASM_SPEC_TEST_GZ_PATH ${CMAKE_BINARY_DIR}/wasm-spec-testsuite.tar.gz) + set(WASM_SPEC_TEST_TAR_PATH ${CMAKE_BINARY_DIR}/wasm-spec-testsuite.tar) + set(WASM_SPEC_TEST_PATH ${CMAKE_SOURCE_DIR}/Userland/Libraries/LibWasm/Tests/Fixtures/SpecTests) + + if(NOT EXISTS ${WASM_SPEC_TEST_GZ_PATH}) + message(STATUS "Downloading the WebAssembly testsuite from ${WASM_SPEC_TEST_GZ_URL}...") + file(DOWNLOAD ${WASM_SPEC_TEST_GZ_URL} ${WASM_SPEC_TEST_GZ_PATH} INACTIVITY_TIMEOUT 10) + endif() + + if(EXISTS ${WASM_SPEC_TEST_GZ_PATH} AND NOT EXISTS ${WASM_SPEC_TEST_PATH}) + message(STATUS "Extracting the WebAssembly testsuite from ${WASM_SPEC_TEST_GZ_PATH}...") + file(MAKE_DIRECTORY ${WASM_SPEC_TEST_PATH}) + execute_process(COMMAND gzip -k -d ${WASM_SPEC_TEST_GZ_PATH}) + execute_process(COMMAND tar -xf ${WASM_SPEC_TEST_TAR_PATH}) + execute_process(COMMAND rm ${WASM_SPEC_TEST_TAR_PATH}) + file(GLOB WASM_TESTS "${CMAKE_BINARY_DIR}/testsuite-master/*.wast") + foreach(PATH ${WASM_TESTS}) + get_filename_component(NAME ${PATH} NAME_WLE) + message(STATUS "Compiling WebAssembly test ${NAME}...") + execute_process( + COMMAND wasm-as -n ${PATH} -o "${WASM_SPEC_TEST_PATH}/${NAME}.wasm" + OUTPUT_QUIET + ERROR_QUIET) + endforeach() + file(REMOVE testsuite-master) + endif() +endif() diff --git a/Documentation/BuildInstructions.md b/Documentation/BuildInstructions.md index 8eb398607a..dd5c70a2cc 100644 --- a/Documentation/BuildInstructions.md +++ b/Documentation/BuildInstructions.md @@ -252,6 +252,7 @@ There are some optional features that can be enabled during compilation that are - `BUILD_LAGOM`: builds [Lagom](../Meta/Lagom/ReadMe.md), which makes various SerenityOS libraries and programs available on the host system. - `PRECOMPILE_COMMON_HEADERS`: precompiles some common headers to speedup compilation. - `ENABLE_KERNEL_LTO`: builds the kernel with link-time optimization. +- `INCLUDE_WASM_SPEC_TESTS`: downloads and includes the WebAssembly spec testsuite tests Many parts of the SerenityOS codebase have debug functionality, mostly consisting of additional messages printed to the debug console. This is done via the `_DEBUG` macros, which can be enabled individually at build time. They are listed in [this file](../Meta/CMake/all_the_debug_macros.cmake). diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index b071468b2f..13568dd8d4 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -155,6 +155,21 @@ if (BUILD_LAGOM) WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) + add_executable(test-wasm_lagom + ../../Tests/LibWasm/test-wasm.cpp + ../../Userland/Libraries/LibTest/JavaScriptTestRunnerMain.cpp) + set_target_properties(test-wasm_lagom PROPERTIES OUTPUT_NAME test-wasm) + target_link_libraries(test-wasm_lagom Lagom) + target_link_libraries(test-wasm_lagom stdc++) + target_link_libraries(test-wasm_lagom pthread) + add_test( + NAME WasmParser + COMMAND test-wasm_lagom --show-progress=false + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + set_tests_properties(WasmParser PROPERTIES + ENVIRONMENT SERENITY_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/../..) + add_executable(disasm_lagom ../../Userland/Utilities/disasm.cpp) set_target_properties(disasm_lagom PROPERTIES OUTPUT_NAME disasm) target_link_libraries(disasm_lagom Lagom) diff --git a/Meta/build-root-filesystem.sh b/Meta/build-root-filesystem.sh index 38c5152850..3a31671481 100755 --- a/Meta/build-root-filesystem.sh +++ b/Meta/build-root-filesystem.sh @@ -104,13 +104,15 @@ mkdir -p mnt/home/anon mkdir -p mnt/home/anon/Desktop mkdir -p mnt/home/anon/Downloads mkdir -p mnt/home/nona -rm -fr mnt/home/anon/js-tests mnt/home/anon/web-tests mnt/home/anon/cpp-tests +rm -fr mnt/home/anon/js-tests mnt/home/anon/web-tests mnt/home/anon/cpp-tests mnt/home/anon/wasm-tests mkdir -p mnt/home/anon/cpp-tests/ cp "$SERENITY_SOURCE_DIR"/README.md mnt/home/anon/ cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibJS/Tests mnt/home/anon/js-tests cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibWeb/Tests mnt/home/anon/web-tests cp -r "$SERENITY_SOURCE_DIR"/Userland/DevTools/HackStudio/LanguageServers/Cpp/Tests mnt/home/anon/cpp-tests/comprehension cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibCpp/Tests mnt/home/anon/cpp-tests/parser +cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibWasm/Tests mnt/home/anon/wasm-tests +cp -r "$SERENITY_SOURCE_DIR"/Userland/Libraries/LibJS/Tests/test-common.js mnt/home/anon/wasm-tests chmod 700 mnt/root chmod 700 mnt/home/anon chmod 700 mnt/home/nona diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 514bfb1a68..855bb5d531 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -11,5 +11,6 @@ add_subdirectory(LibM) add_subdirectory(LibPthread) add_subdirectory(LibRegex) add_subdirectory(LibSQL) +add_subdirectory(LibWasm) add_subdirectory(LibWeb) add_subdirectory(UserspaceEmulator) diff --git a/Tests/LibWasm/CMakeLists.txt b/Tests/LibWasm/CMakeLists.txt new file mode 100644 index 0000000000..0f16647d2e --- /dev/null +++ b/Tests/LibWasm/CMakeLists.txt @@ -0,0 +1,2 @@ +serenity_testjs_test(test-wasm.cpp test-wasm LIBS LibWasm) +install(TARGETS test-wasm RUNTIME DESTINATION bin) diff --git a/Tests/LibWasm/test-wasm.cpp b/Tests/LibWasm/test-wasm.cpp new file mode 100644 index 0000000000..c8f48a3002 --- /dev/null +++ b/Tests/LibWasm/test-wasm.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +TEST_ROOT("Userland/Libraries/LibWasm/Tests"); + +TESTJS_GLOBAL_FUNCTION(read_binary_wasm_file, readBinaryWasmFile) +{ + auto filename = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + auto file = Core::File::open(filename, Core::OpenMode::ReadOnly); + if (file.is_error()) { + vm.throw_exception(global_object, file.error()); + return {}; + } + auto contents = file.value()->read_all(); + auto array = JS::Uint8Array::create(global_object, contents.size()); + contents.span().copy_to(array->data()); + return array; +} + +TESTJS_GLOBAL_FUNCTION(parse_webassembly_module, parseWebAssemblyModule) +{ + auto object = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + if (!is(object)) { + vm.throw_exception(global_object, "Expected a Uint8Array argument to parse_webassembly_module"); + return {}; + } + auto& array = static_cast(*object); + InputMemoryStream stream { array.data() }; + auto result = Wasm::Module::parse(stream); + if (result.is_error()) { + vm.throw_exception(global_object, Wasm::parse_error_to_string(result.error())); + return {}; + } + if (stream.handle_any_error()) + return JS::js_undefined(); + return JS::js_null(); +} + +TESTJS_GLOBAL_FUNCTION(compare_typed_arrays, compareTypedArrays) +{ + auto lhs = vm.argument(0).to_object(global_object); + if (vm.exception()) + return {}; + if (!is(lhs)) { + vm.throw_exception(global_object, "Expected a TypedArray"); + return {}; + } + auto& lhs_array = static_cast(*lhs); + auto rhs = vm.argument(1).to_object(global_object); + if (vm.exception()) + return {}; + if (!is(rhs)) { + vm.throw_exception(global_object, "Expected a TypedArray"); + return {}; + } + auto& rhs_array = static_cast(*rhs); + return JS::Value(lhs_array.viewed_array_buffer()->buffer() == rhs_array.viewed_array_buffer()->buffer()); +} diff --git a/Userland/Libraries/LibWasm/Tests/Fixtures/Modules/empty-module.wasm b/Userland/Libraries/LibWasm/Tests/Fixtures/Modules/empty-module.wasm new file mode 100644 index 0000000000000000000000000000000000000000..589357f2a57a19386d7ee5b9a23ce169bee135a1 GIT binary patch literal 50 zcmZQbEY4+QU|?Wn29oStjBL57x%owvObiU-0>$~IMaikYi3J6jdFi1+KCEepImM{} D_%;lN literal 0 HcmV?d00001 diff --git a/Userland/Libraries/LibWasm/Tests/Parser/spec-testsuite.js b/Userland/Libraries/LibWasm/Tests/Parser/spec-testsuite.js new file mode 100644 index 0000000000..f1ec9b76e6 --- /dev/null +++ b/Userland/Libraries/LibWasm/Tests/Parser/spec-testsuite.js @@ -0,0 +1,23 @@ +let haveSpecTestSuite = false; +try { + readBinaryWasmFile("Fixtures/SpecTests/address.wasm"); + haveSpecTestSuite = true; +} catch {} + +let testFunction = haveSpecTestSuite ? test : test.skip; + +// prettier-ignore +const tests = [ + "address", "align", "binary", "binary-leb128", "br_table", "comments", "endianness", "exports", + "f32", "f32_bitwise", "f32_cmp", "f64", "f64_bitwise", "f64_cmp", "float_exprs", "float_literals", + "float_memory", "float_misc", "forward", "func_ptrs", "int_exprs", "int_literals", "labels", + "left-to-right", "linking", "load", "local_get", "memory", "memory_grow", "memory_redundancy", + "memory_size", "memory_trap", "names", "return", "switch", "table", "traps", "type" +]; + +for (let testName of tests) { + testFunction(`parse ${testName}`, () => { + const contents = readBinaryWasmFile(`Fixtures/SpecTests/${testName}.wasm`); + parseWebAssemblyModule(contents); + }); +} diff --git a/Userland/Libraries/LibWasm/Tests/Parser/test-basic-load.js b/Userland/Libraries/LibWasm/Tests/Parser/test-basic-load.js new file mode 100644 index 0000000000..6e8dc253ae --- /dev/null +++ b/Userland/Libraries/LibWasm/Tests/Parser/test-basic-load.js @@ -0,0 +1,59 @@ +test("test harness test", () => { + expect(parseWebAssemblyModule).not.toBeUndefined(); + expect(readBinaryWasmFile).not.toBeUndefined(); + expect(compareTypedArrays).not.toBeUndefined(); +}); + +test("parsing can pass", () => { + // prettier-ignore + let binary = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x86, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60, + 0x01, 0x7f, 0x01, 0x7f, 0x02, 0xba, 0x80, 0x80, 0x80, 0x00, 0x02, 0x03, 0x65, 0x6e, 0x76, 0x0f, + 0x5f, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, + 0x00, 0x00, 0x03, 0x65, 0x6e, 0x76, 0x19, 0x5f, 0x5f, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x01, 0x70, 0x00, 0x00, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x0a, 0xcd, 0x80, 0x80, + 0x80, 0x00, 0x01, 0x4b, 0x01, 0x03, 0x7f, 0x41, 0x00, 0x21, 0x01, 0x02, 0x40, 0x02, 0x40, 0x20, + 0x00, 0x41, 0x02, 0x4e, 0x0d, 0x00, 0x20, 0x00, 0x21, 0x02, 0x0c, 0x01, 0x0b, 0x41, 0x00, 0x21, + 0x01, 0x03, 0x40, 0x20, 0x00, 0x41, 0x7f, 0x6a, 0x10, 0x80, 0x80, 0x80, 0x80, 0x00, 0x20, 0x01, + 0x6a, 0x21, 0x01, 0x20, 0x00, 0x41, 0x03, 0x4a, 0x21, 0x03, 0x20, 0x00, 0x41, 0x7e, 0x6a, 0x22, + 0x02, 0x21, 0x00, 0x20, 0x03, 0x0d, 0x00, 0x0b, 0x0b, 0x20, 0x02, 0x20, 0x01, 0x6a, 0x0b, 0x00, + 0x97, 0x80, 0x80, 0x80, 0x00, 0x07, 0x6c, 0x69, 0x6e, 0x6b, 0x69, 0x6e, 0x67, 0x02, 0x08, 0x88, + 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x66, 0x69, 0x62, 0x00, 0x90, 0x80, 0x80, + 0x80, 0x00, 0x0a, 0x72, 0x65, 0x6c, 0x6f, 0x63, 0x2e, 0x43, 0x4f, 0x44, 0x45, 0x03, 0x01, 0x00, + 0x27, 0x00, 0x00, 0xa6, 0x80, 0x80, 0x80, 0x00, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, + 0x72, 0x73, 0x01, 0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x2d, 0x62, 0x79, + 0x01, 0x05, 0x63, 0x6c, 0x61, 0x6e, 0x67, 0x06, 0x31, 0x31, 0x2e, 0x31, 0x2e, 0x30, + ]); + // This just checks that the function actually works + parseWebAssemblyModule(binary); +}); + +test("parsing can fail", () => { + let binary = new Uint8Array([0, 0x32, 0x73, 0x6d]); + expect(() => parseWebAssemblyModule(binary)).toThrow( + SyntaxError, + "Incorrect module magic (did not match \\0asm)" + ); +}); + +test("file reading can pass", () => { + // prettier-ignore + let referenceContents = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x05, 0x03, 0x01, 0x00, + 0x00, 0x07, 0x0a, 0x01, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, + 0x00, 0x00, 0x17, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x61, + 0x70, 0x70, 0x69, 0x6e, 0x67, 0x55, 0x52, 0x4c, 0x05, 0x66, 0x61, 0x6c, + 0x73, 0x65 + ]); + + let contents = readBinaryWasmFile("Fixtures/Modules/empty-module.wasm"); + expect(compareTypedArrays(contents, referenceContents)).toBe(true); +}); + +test("file reading can fail", () => { + expect(() => readBinaryWasmFile("Fixtures/this-file-must-not-exist.wasm")).toThrow( + TypeError, + "No such file or directory" + ); +});