WasmExceptions builds a fixture for wasm32-wasip1 and runs it under
wasmtime. It throws through four frames whose destructors have to run,
catches the exception, catches one that vector::at throws, and with an
argument lets one escape main, which has to fail the run. With the
previous flags the fixture does not compile ("cannot use 'throw' with
exceptions disabled"), and each part of turning exceptions on fails it
in its own way, from a BMI built without them to the legacy encoding
wasmtime refuses.
WasiSysrootWithoutExceptions takes a scratch sysroot through three
states, no libunwind, libunwind under eh/ and libunwind in the
triple's own directory, and checks that only the first stops the
build, with the message naming the library it looked for. It needs no
working wasm toolchain, so it also runs where the others skip.
WasmHelloRuns and WasmExceptions treat a sysroot without the
exception-enabled libraries as missing and skip. The manifest tests
expect the exception-handling probe on every entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
60 lines
2.5 KiB
C++
60 lines
2.5 KiB
C++
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
import std;
|
|
import Crafter.Build;
|
|
namespace fs = std::filesystem;
|
|
using namespace Crafter;
|
|
|
|
// A wasm build end to end: the fixture is compiled for wasm32-wasip1 against
|
|
// the resolved toolchain's WASI sysroot and the result is run under
|
|
// wasmtime. The other wasm tests only inspect generated files; this is the
|
|
// one that proves the sysroot, the std module for wasm and wasm-ld all work
|
|
// together. It skips when the sysroot or wasmtime is missing, so a
|
|
// development box without them is not a failure; a sysroot without the
|
|
// exception-enabled libraries counts as missing, since wasm builds link them
|
|
// (WasiSysrootWithoutExceptions covers that message). Inside the Flatpak both
|
|
// are bundled, and there the skip would be the failure to look for.
|
|
int main() {
|
|
const Toolchain& tc = GetToolchain();
|
|
const fs::path libDir = tc.wasiSysroot / "lib" / "wasm32-wasip1";
|
|
if (!fs::exists(libDir / "eh" / "libunwind.a") && !fs::exists(libDir / "libunwind.a")) {
|
|
std::println(std::cerr, "SKIP: no exception-enabled WASI sysroot at {}", tc.wasiSysroot.string());
|
|
return 77;
|
|
}
|
|
if (std::system("which wasmtime > /dev/null 2>&1") != 0) {
|
|
std::println(std::cerr, "SKIP: wasmtime not on PATH");
|
|
return 77;
|
|
}
|
|
|
|
Configuration cfg;
|
|
cfg.path = fs::current_path() / "tests" / "WasmHelloRuns" / "fixture";
|
|
cfg.name = "wasm-hello";
|
|
cfg.outputName = "wasm-hello";
|
|
cfg.target = "wasm32-wasip1";
|
|
cfg.type = ConfigurationType::Executable;
|
|
std::array<fs::path, 0> ifaces = {};
|
|
std::array<fs::path, 1> impls = { "main" };
|
|
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
|
|
|
std::unordered_map<fs::path, std::shared_future<BuildResult>> depResults;
|
|
std::mutex depMutex;
|
|
BuildResult r = Build(cfg, depResults, depMutex);
|
|
if (!r.result.empty()) {
|
|
std::println(std::cerr, "build failed: {}", r.result);
|
|
return 1;
|
|
}
|
|
|
|
const fs::path wasm = ExpectedOutput(cfg);
|
|
if (!fs::exists(wasm)) {
|
|
std::println(std::cerr, "no .wasm produced at {}", wasm.string());
|
|
return 1;
|
|
}
|
|
|
|
const auto run = RunCommandWithTimeout(std::format("wasmtime {}", wasm.string()), std::chrono::seconds(60));
|
|
if (run.exitCode != 0 || !run.output.contains("hello from wasm")) {
|
|
std::println(std::cerr, "wasmtime run failed (exit {}): {}", run.exitCode, run.output);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|