2026-07-23 01:24:42 +02:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
2026-07-22 18:16:38 +02:00
|
|
|
|
test: broaden self-test coverage beyond the compile smoke test
The suite had only HelloWorld, which built and exited an empty exe. Add
in-process tests covering each public surface area users actually touch:
- StaticLib / ModuleInterface / DependencyLink — Build() against
fixtures for libraries, project-local module interfaces, and
cross-config module deps with link verification (runs the built exe).
- ShaderCompile — drives Shader::Compile directly, validates SPIR-V
magic + Check() idempotency.
- StandardArgs — covers --debug, --target=, --march=, --mtune=,
--lib/--shared promotions, and ArgQuery::Has / Get.
- TestRunnerSpec — FromSpec parse rules, ForTarget routing for host,
wasm32-wasip1, aarch64-linux-gnu (+ sysroot QEMU_LD_PREFIX),
i686 → qemu-i386 rewrite, mingw → wine on Linux hosts, FromEnv.
- VariantId — confirms type / debug / sysroot / defines / compileFlags /
target / march all perturb the cache key, plus PcmDir routing.
- WasiBrowserRuntime — calls EnableWasiBrowserRuntime, asserts the
three cfg.files entries get registered and index.html had its
template placeholder substituted.
- RunSingleTestExit — drives RunSingleTest against tiny sh scripts and
pins the documented exit-code mapping (0/77/non-zero) and the
Cmd-prefix runner path.
Closes #12.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 19:38:18 +00:00
|
|
|
import std;
|
|
|
|
|
import Crafter.Build;
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
using namespace Crafter;
|
|
|
|
|
|
|
|
|
|
// Compile a tiny GLSL fragment shader directly via the Shader API. Verifies
|
|
|
|
|
// the SPIR-V output lands in the target dir and starts with the SPIR-V magic
|
2026-10-06 16:50:41 +02:00
|
|
|
// number, without dragging in the full Build() pipeline. The second half
|
|
|
|
|
// registers one source twice, with and without defines, and checks that the
|
|
|
|
|
// variant gets its own output, its own staleness and the other #ifdef branch
|
|
|
|
|
// (tests/ShaderVariantBuild drives the same thing through Build()).
|
test: broaden self-test coverage beyond the compile smoke test
The suite had only HelloWorld, which built and exited an empty exe. Add
in-process tests covering each public surface area users actually touch:
- StaticLib / ModuleInterface / DependencyLink — Build() against
fixtures for libraries, project-local module interfaces, and
cross-config module deps with link verification (runs the built exe).
- ShaderCompile — drives Shader::Compile directly, validates SPIR-V
magic + Check() idempotency.
- StandardArgs — covers --debug, --target=, --march=, --mtune=,
--lib/--shared promotions, and ArgQuery::Has / Get.
- TestRunnerSpec — FromSpec parse rules, ForTarget routing for host,
wasm32-wasip1, aarch64-linux-gnu (+ sysroot QEMU_LD_PREFIX),
i686 → qemu-i386 rewrite, mingw → wine on Linux hosts, FromEnv.
- VariantId — confirms type / debug / sysroot / defines / compileFlags /
target / march all perturb the cache key, plus PcmDir routing.
- WasiBrowserRuntime — calls EnableWasiBrowserRuntime, asserts the
three cfg.files entries get registered and index.html had its
template placeholder substituted.
- RunSingleTestExit — drives RunSingleTest against tiny sh scripts and
pins the documented exit-code mapping (0/77/non-zero) and the
Cmd-prefix runner path.
Closes #12.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 19:38:18 +00:00
|
|
|
int main() {
|
|
|
|
|
fs::path src = fs::current_path() / "tests" / "ShaderCompile" / "fixture" / "triangle.frag";
|
|
|
|
|
if (!fs::exists(src)) {
|
|
|
|
|
std::println(std::cerr, "fixture missing: {}", src.string());
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fs::path outDir = fs::temp_directory_path() / "crafter-build-shader-test";
|
|
|
|
|
std::error_code ec;
|
|
|
|
|
fs::remove_all(outDir, ec);
|
|
|
|
|
fs::create_directories(outDir);
|
|
|
|
|
|
|
|
|
|
Shader shader(fs::path(src), "main", ShaderType::Fragment);
|
|
|
|
|
|
|
|
|
|
std::array<fs::path, 0> includeDirs = {};
|
|
|
|
|
std::string err = shader.Compile(outDir, includeDirs);
|
|
|
|
|
if (!err.empty()) {
|
|
|
|
|
std::println(std::cerr, "shader compile failed: {}", err);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fs::path spv = outDir / "triangle.spv";
|
|
|
|
|
if (!fs::exists(spv)) {
|
|
|
|
|
std::println(std::cerr, "spv not produced at {}", spv.string());
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SPIR-V binaries start with the magic number 0x07230203 (little-endian
|
|
|
|
|
// when written by glslang on x86). Read the first four bytes to confirm
|
|
|
|
|
// the produced file is real SPIR-V, not a fluke empty file.
|
|
|
|
|
std::ifstream in(spv, std::ios::binary);
|
|
|
|
|
std::uint32_t magic = 0;
|
|
|
|
|
in.read(reinterpret_cast<char*>(&magic), sizeof(magic));
|
|
|
|
|
if (!in || magic != 0x07230203u) {
|
|
|
|
|
std::println(std::cerr, "spv has wrong magic: 0x{:08x}", magic);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compiling again should be a no-op since the source isn't newer.
|
|
|
|
|
if (!shader.Check(outDir)) {
|
|
|
|
|
std::println(std::cerr, "Shader::Check returned false on an up-to-date spv");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2026-10-06 16:50:41 +02:00
|
|
|
|
|
|
|
|
// One source registered twice: the second entry carries defines and a
|
|
|
|
|
// variant name. It must land in its own file instead of overwriting the
|
|
|
|
|
// first, and it must take the other #ifdef branch. Crafter.Graphics ships
|
|
|
|
|
// each UI shader in a descriptor-heap and a descriptor-set flavour this
|
|
|
|
|
// way; if the variant were dropped from the output name, whichever compile
|
|
|
|
|
// finished last would silently win.
|
|
|
|
|
fs::path variantSrc = fs::current_path() / "tests" / "ShaderCompile" / "fixture" / "variant.frag";
|
|
|
|
|
Shader plain(fs::path(variantSrc), "main", ShaderType::Fragment);
|
|
|
|
|
Shader red(fs::path(variantSrc), "main", ShaderType::Fragment);
|
|
|
|
|
red.defines = {"VARIANT_RED", "VARIANT_ALPHA=0.5"};
|
|
|
|
|
red.variant = "red";
|
|
|
|
|
|
|
|
|
|
if (plain.OutputName() != "variant.spv" || red.OutputName() != "variant.red.spv") {
|
|
|
|
|
std::println(std::cerr, "unexpected output names: {} and {}", plain.OutputName().string(), red.OutputName().string());
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
for (const Shader* each : {&plain, &red}) {
|
|
|
|
|
// Staleness is per output, not per source: while only variant.spv
|
|
|
|
|
// exists, the variant is still due. A Check() that ignored the
|
|
|
|
|
// variant would call it up to date here, and Build() would then
|
|
|
|
|
// never compile a variant added to an already built project.
|
|
|
|
|
if (each == &red && red.Check(outDir)) {
|
|
|
|
|
std::println(std::cerr, "Shader::Check called the variant up to date before it was ever compiled");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
std::string variantErr = each->Compile(outDir, includeDirs);
|
|
|
|
|
if (!variantErr.empty()) {
|
|
|
|
|
std::println(std::cerr, "variant shader compile failed: {}", variantErr);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (!each->Check(outDir)) {
|
|
|
|
|
std::println(std::cerr, "Shader::Check returned false for {}", each->OutputName().string());
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Malformed defines and variants are refused by name. Left to glslang, an
|
|
|
|
|
// empty macro name surfaces as "-1:<line>" with no file, and a variant
|
|
|
|
|
// with a separator as a failed open after the whole compile.
|
|
|
|
|
for (const char* bad : {"", "=1", "A\nB"}) {
|
|
|
|
|
Shader malformed(fs::path(variantSrc), "main", ShaderType::Fragment);
|
|
|
|
|
malformed.defines = {bad};
|
|
|
|
|
malformed.variant = "malformed";
|
|
|
|
|
if (!malformed.Compile(outDir, includeDirs).contains("invalid shader define")) {
|
|
|
|
|
std::println(std::cerr, "a malformed define was not rejected by name");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Shader nested(fs::path(variantSrc), "main", ShaderType::Fragment);
|
|
|
|
|
nested.variant = "sub/red";
|
|
|
|
|
if (!nested.Compile(outDir, includeDirs).contains("path separator")) {
|
|
|
|
|
std::println(std::cerr, "a variant with a path separator was not rejected by name");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto readAll = [](const fs::path& file) {
|
|
|
|
|
std::ifstream stream(file, std::ios::binary);
|
|
|
|
|
return std::string(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
|
|
|
|
|
};
|
|
|
|
|
std::string plainSpv = readAll(outDir / "variant.spv");
|
|
|
|
|
std::string redSpv = readAll(outDir / "variant.red.spv");
|
|
|
|
|
if (plainSpv.empty() || redSpv.empty()) {
|
|
|
|
|
std::println(std::cerr, "a variant output is missing or empty");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (plainSpv == redSpv) {
|
|
|
|
|
std::println(std::cerr, "both variants compiled to the same SPIR-V: the defines did not reach the preprocessor");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
test: broaden self-test coverage beyond the compile smoke test
The suite had only HelloWorld, which built and exited an empty exe. Add
in-process tests covering each public surface area users actually touch:
- StaticLib / ModuleInterface / DependencyLink — Build() against
fixtures for libraries, project-local module interfaces, and
cross-config module deps with link verification (runs the built exe).
- ShaderCompile — drives Shader::Compile directly, validates SPIR-V
magic + Check() idempotency.
- StandardArgs — covers --debug, --target=, --march=, --mtune=,
--lib/--shared promotions, and ArgQuery::Has / Get.
- TestRunnerSpec — FromSpec parse rules, ForTarget routing for host,
wasm32-wasip1, aarch64-linux-gnu (+ sysroot QEMU_LD_PREFIX),
i686 → qemu-i386 rewrite, mingw → wine on Linux hosts, FromEnv.
- VariantId — confirms type / debug / sysroot / defines / compileFlags /
target / march all perturb the cache key, plus PcmDir routing.
- WasiBrowserRuntime — calls EnableWasiBrowserRuntime, asserts the
three cfg.files entries get registered and index.html had its
template placeholder substituted.
- RunSingleTestExit — drives RunSingleTest against tiny sh scripts and
pins the documented exit-code mapping (0/77/non-zero) and the
Cmd-prefix runner path.
Closes #12.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 19:38:18 +00:00
|
|
|
return 0;
|
|
|
|
|
}
|