test(wasm): the cases the review found
WasmBuildRefusals checks that -flto in compileFlags and in linkFlags, -fno-exceptions and a shared library are each refused with their cause, and that none of them leaves bin/ or build/ behind. With the check disabled, each of them failed on a missing std.cppm instead, after the build directories had been made. WasiSysrootWithoutExceptions also takes its sysroot through missing and without the target's directory, each with a message of its own, checks that a refused build leaves nothing behind, and that a static library passes without libunwind. WasmExceptions catches an exception thrown inside libc++.a (stoi). vector::at throws from a header compiled into the fixture, so the fixture also passed against the libc++ built without exceptions. Its C source uses setjmp, which the old C flags did not compile. An exception that escapes main has to end the run as wasmtime's uncaught exception, where any nonzero exit used to pass, a trap or a timeout included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
45b9f5cbb7
commit
2a05b8e6fb
7 changed files with 171 additions and 20 deletions
85
tests/WasmBuildRefusals/main.cpp
Normal file
85
tests/WasmBuildRefusals/main.cpp
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
// 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;
|
||||
|
||||
// What a wasm32 build refuses before it starts, now that every wasm unit has
|
||||
// C++ exceptions, each with a message naming the cause:
|
||||
// - -flto in compileFlags or linkFlags. LTO code generation drops the
|
||||
// exception handling, and the build used to succeed with every throw
|
||||
// escaping its catch (WebAssembly/wasi-sdk#629).
|
||||
// - -fno-exceptions. The std module is built with exceptions, and every
|
||||
// `import std;` failed on a BMI configuration mismatch.
|
||||
// - A shared library. The exception-enabled libc++ is static archives
|
||||
// only, and the link failed on a relocation that wants -fPIC.
|
||||
// None of them leaves bin/ or build/ behind, and none needs a working wasm
|
||||
// toolchain. The sysroot given does not exist, so a refusal that did not
|
||||
// fire would show up as the missing sysroot instead.
|
||||
namespace {
|
||||
std::int32_t failures = 0;
|
||||
|
||||
void Check(bool cond, std::string_view msg) {
|
||||
if (!cond) {
|
||||
std::println(std::cerr, "FAIL: {}", msg);
|
||||
++failures;
|
||||
}
|
||||
}
|
||||
|
||||
struct Refusal {
|
||||
std::string_view what;
|
||||
std::vector<std::string> compileFlags;
|
||||
std::vector<std::string> linkFlags;
|
||||
ConfigurationType type;
|
||||
std::string_view expected;
|
||||
};
|
||||
}
|
||||
|
||||
int main() {
|
||||
// Build() reports a missing compiler before it gets to any of this.
|
||||
if (const Toolchain& tc = GetToolchain(); !tc.error.empty()) {
|
||||
std::println(std::cerr, "SKIP: {}", tc.error);
|
||||
return 77;
|
||||
}
|
||||
|
||||
const fs::path scratch = fs::temp_directory_path() / "crafter-build-wasm-build-refusals";
|
||||
fs::remove_all(scratch);
|
||||
fs::create_directories(scratch);
|
||||
const fs::path project = scratch / "project";
|
||||
fs::copy(fs::current_path() / "tests" / "WasmBuildRefusals" / "fixture", project, fs::copy_options::recursive);
|
||||
|
||||
const std::array refusals = {
|
||||
Refusal{ "-flto in compileFlags", { "-flto=thin" }, {}, ConfigurationType::Executable, "-flto=thin is not supported on wasm32: LTO code generation drops C++ exception handling" },
|
||||
Refusal{ "-flto in linkFlags", {}, { "-flto" }, ConfigurationType::Executable, "-flto is not supported on wasm32" },
|
||||
Refusal{ "-fno-exceptions", { "-fno-exceptions" }, {}, ConfigurationType::Executable, "-fno-exceptions is not supported on wasm32" },
|
||||
Refusal{ "a shared library", {}, {}, ConfigurationType::LibraryDynamic, "wasm32 shared libraries are not supported" },
|
||||
};
|
||||
for (const Refusal& refusal : refusals) {
|
||||
Configuration app;
|
||||
app.path = project;
|
||||
app.name = "hello";
|
||||
app.outputName = "hello";
|
||||
app.target = "wasm32-wasip1";
|
||||
app.sysroot = (scratch / "no-sysroot").string();
|
||||
app.type = refusal.type;
|
||||
app.compileFlags = refusal.compileFlags;
|
||||
app.linkFlags = refusal.linkFlags;
|
||||
std::array<fs::path, 0> ifaces = {};
|
||||
std::array<fs::path, 1> impls = { "main" };
|
||||
app.GetInterfacesAndImplementations(ifaces, impls);
|
||||
std::unordered_map<fs::path, std::shared_future<BuildResult>> depResults;
|
||||
std::mutex depMutex;
|
||||
const BuildResult r = Build(app, depResults, depMutex);
|
||||
Check(r.result.contains(refusal.expected), std::format("{} is refused with its cause: {}", refusal.what, r.result));
|
||||
Check(!fs::exists(project / "bin") && !fs::exists(project / "build"), std::format("refusing {} leaves no bin/ or build/ behind", refusal.what));
|
||||
}
|
||||
|
||||
fs::remove_all(scratch);
|
||||
if (failures > 0) {
|
||||
std::println(std::cerr, "{} assertions failed", failures);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in a new issue