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>
125 lines
5.5 KiB
C++
125 lines
5.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;
|
|
|
|
// POSIX env helper — std::setenv is non-standard. Linux-only is fine here;
|
|
// project.cpp gates this test on x86_64-pc-linux-gnu.
|
|
extern "C" int setenv(const char* name, const char* value, int overwrite);
|
|
|
|
// EnableWasiBrowserRuntime stamps runtime.js, a generated index.html, and a
|
|
// files.json manifest onto cfg.files so the build step copies them next to
|
|
// the .wasm. We don't run the wasm build here (avoids requiring wasi-sdk +
|
|
// the wasi-libc++ PCM rebuild on top of every test); we just verify the
|
|
// helper produced the right artifacts and registered them on the config.
|
|
int main() {
|
|
Configuration cfg;
|
|
cfg.path = fs::temp_directory_path() / "crafter-build-wasi-runtime-test";
|
|
cfg.name = "wasi-helper";
|
|
cfg.outputName = "wasi-helper";
|
|
cfg.target = "wasm32-wasip1";
|
|
cfg.type = ConfigurationType::Executable;
|
|
|
|
std::error_code ec;
|
|
fs::remove_all(cfg.path, ec);
|
|
fs::create_directories(cfg.path);
|
|
|
|
// EnableWasiBrowserRuntime looks up runtime.js + index.html.in under
|
|
// GetCrafterBuildHome()/wasi-runtime. The test binary lives in
|
|
// bin/<variant>/, so the auto-detect walks parents and won't find a
|
|
// share/crafter-build/Crafter.Build.cppm anchor — point CRAFTER_BUILD_HOME
|
|
// at the repo root (cwd when crafter-build test runs) so it picks up
|
|
// ./wasi-runtime/. Probe both repo root and the system-installed share
|
|
// dir to also work from a packaged checkout.
|
|
fs::path repoWasi = fs::current_path() / "wasi-runtime";
|
|
fs::path systemHome = "/usr/local/share/crafter-build";
|
|
if (fs::exists(repoWasi / "runtime.js")) {
|
|
setenv("CRAFTER_BUILD_HOME", fs::current_path().c_str(), 1);
|
|
} else if (fs::exists(systemHome / "wasi-runtime" / "runtime.js")) {
|
|
setenv("CRAFTER_BUILD_HOME", systemHome.c_str(), 1);
|
|
} else {
|
|
std::println(std::cerr, "wasi-runtime assets not found near {} or {}", repoWasi.string(), systemHome.string());
|
|
return 77; // skipped — environmental, not a code defect
|
|
}
|
|
|
|
EnableWasiBrowserRuntime(cfg);
|
|
|
|
// Three files registered: runtime.js, generated index.html, files.json.
|
|
if (cfg.files.size() != 4) {
|
|
std::println(std::cerr, "expected 4 cfg.files entries, got {}", cfg.files.size());
|
|
for (const auto& f : cfg.files) std::println(std::cerr, " - {}", f.string());
|
|
return 1;
|
|
}
|
|
|
|
auto findByName = [&](std::string_view name) -> const fs::path* {
|
|
for (const fs::path& f : cfg.files) {
|
|
if (f.filename() == name) return &f;
|
|
}
|
|
return nullptr;
|
|
};
|
|
|
|
const fs::path* runtimeJs = findByName("runtime.js");
|
|
const fs::path* indexHtml = findByName("index.html");
|
|
const fs::path* manifest = findByName("files.json");
|
|
const fs::path* variants = findByName("variants.json");
|
|
|
|
if (!runtimeJs) { std::println(std::cerr, "runtime.js not registered"); return 1; }
|
|
if (!indexHtml) { std::println(std::cerr, "index.html not registered"); return 1; }
|
|
if (!manifest) { std::println(std::cerr, "files.json not registered"); return 1; }
|
|
if (!variants) { std::println(std::cerr, "variants.json not registered"); return 1; }
|
|
|
|
// The generated index.html and manifest live under the project's build/
|
|
// wasi-runtime/<name>/ directory and must actually exist on disk.
|
|
if (!fs::exists(*indexHtml)) {
|
|
std::println(std::cerr, "index.html not produced at {}", indexHtml->string());
|
|
return 1;
|
|
}
|
|
if (!fs::exists(*manifest)) {
|
|
std::println(std::cerr, "files.json not produced at {}", manifest->string());
|
|
return 1;
|
|
}
|
|
if (!fs::exists(*variants)) {
|
|
std::println(std::cerr, "variants.json not produced at {}", variants->string());
|
|
return 1;
|
|
}
|
|
|
|
// With no declared variants, variants.json is a single baseline entry
|
|
// pointing at outputName.wasm, with only the probe for what every wasm
|
|
// build needs: exception handling, so that runtime.js can turn an engine
|
|
// without it away with a message rather than a CompileError.
|
|
{
|
|
std::ifstream vin(*variants);
|
|
std::string vjson{std::istreambuf_iterator<char>(vin), std::istreambuf_iterator<char>()};
|
|
if (vjson.find("\"wasi-helper.wasm\"") == std::string::npos) {
|
|
std::println(std::cerr, "variants.json missing baseline url: {}", vjson);
|
|
return 1;
|
|
}
|
|
if (vjson.find("\"probes\":[\"exception-handling\"]") == std::string::npos) {
|
|
std::println(std::cerr, "variants.json baseline does not probe for exception handling: {}", vjson);
|
|
return 1;
|
|
}
|
|
if (vjson.find("relaxed-simd") != std::string::npos) {
|
|
std::println(std::cerr, "variants.json unexpectedly mentions relaxed-simd: {}", vjson);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// The template's {{WASM}} placeholder should have been replaced with
|
|
// the configured outputName.
|
|
std::ifstream in(*indexHtml);
|
|
std::string html{std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>()};
|
|
if (html.find("wasi-helper.wasm") == std::string::npos) {
|
|
std::println(std::cerr, "index.html missing wasi-helper.wasm reference");
|
|
return 1;
|
|
}
|
|
if (html.find("{{WASM}}") != std::string::npos) {
|
|
std::println(std::cerr, "index.html still contains unreplaced {{WASM}} placeholder");
|
|
return 1;
|
|
}
|
|
|
|
fs::remove_all(cfg.path, ec);
|
|
return 0;
|
|
}
|