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.
This commit is contained in:
Ali Mohammad Pur 2021-05-04 07:36:59 +04:30 committed by Linus Groh
parent ba2fce14d3
commit b3c13c3e8a
Notes: sideshowbarker 2024-07-18 17:39:59 +09:00
11 changed files with 207 additions and 1 deletions

2
.gitignore vendored
View file

@ -25,3 +25,5 @@ compile_commands.json
cmake-build-debug/
sync-local.sh
.vim/
Userland/Libraries/LibWasm/Tests/Fixtures/SpecTests

View file

@ -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()

View file

@ -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 `<component_name>_DEBUG` macros, which can be enabled individually at build time. They are listed in [this file](../Meta/CMake/all_the_debug_macros.cmake).

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -0,0 +1,2 @@
serenity_testjs_test(test-wasm.cpp test-wasm LIBS LibWasm)
install(TARGETS test-wasm RUNTIME DESTINATION bin)

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/File.h>
#include <LibTest/JavaScriptTestRunner.h>
#include <LibWasm/AbstractMachine/Interpreter.h>
#include <LibWasm/Types.h>
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<JS::TypeError>(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<JS::Uint8Array>(object)) {
vm.throw_exception<JS::TypeError>(global_object, "Expected a Uint8Array argument to parse_webassembly_module");
return {};
}
auto& array = static_cast<JS::Uint8Array&>(*object);
InputMemoryStream stream { array.data() };
auto result = Wasm::Module::parse(stream);
if (result.is_error()) {
vm.throw_exception<JS::SyntaxError>(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<JS::TypedArrayBase>(lhs)) {
vm.throw_exception<JS::TypeError>(global_object, "Expected a TypedArray");
return {};
}
auto& lhs_array = static_cast<JS::TypedArrayBase&>(*lhs);
auto rhs = vm.argument(1).to_object(global_object);
if (vm.exception())
return {};
if (!is<JS::TypedArrayBase>(rhs)) {
vm.throw_exception<JS::TypeError>(global_object, "Expected a TypedArray");
return {};
}
auto& rhs_array = static_cast<JS::TypedArrayBase&>(*rhs);
return JS::Value(lhs_array.viewed_array_buffer()->buffer() == rhs_array.viewed_array_buffer()->buffer());
}

View file

@ -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);
});
}

View file

@ -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"
);
});