more tests
Some checks failed
CI / build-test-release (push) Has been cancelled

This commit is contained in:
Jorijn van der Graaf 2026-04-29 03:52:08 +02:00
commit 19b059a88c
11 changed files with 286 additions and 1 deletions

94
tests/Cuda/Cuda.cpp Normal file
View file

@ -0,0 +1,94 @@
/*
Crafter® Build
Copyright (C) 2026 Catcrafts®
Catcrafts.net
LGPL-3.0-only.
End-to-end CUDA build through the V2 pipeline:
the inner project.cpp registers a .cu translation unit via cfg.cuda, and
crafter.build-lib drives nvcc to produce a host object that links into
the final executable. The test skips when the CUDA SDK is missing
(CI hosts have neither nvcc nor libcudart) and otherwise runs the built
binary to confirm the .cu's host-callable entry returns through the link.
No GPU is required: the kernel is staged but never launched, so cudart's
fat-binary registration runs at startup but device init does not.
*/
import std;
import Crafter.Build;
#include "../_shared/TestUtil.h"
namespace fs = std::filesystem;
using namespace TestUtil;
using namespace Crafter;
namespace {
bool ToolPresent(std::string_view name) {
return std::system(std::format("which {} > /dev/null 2>&1", name).c_str()) == 0;
}
}
int main() {
try {
if (!ToolPresent("nvcc")) {
Skip("nvcc not on PATH — install the CUDA SDK to enable this test");
}
// Resolve the CUDA root from nvcc's own location so the inner project
// can point the linker at libcudart.so without a hardcoded path.
// Standard SDK layout puts nvcc at <root>/bin/nvcc and cudart at
// <root>/lib64; if either assumption is broken we skip rather than
// fail, since that means the install is non-standard.
fs::path cwd = fs::current_path();
auto probe = RunInDir(cwd, "command -v nvcc");
if (probe.exitCode != 0 || probe.output.empty()) {
Skip("nvcc not on PATH");
}
std::string nvccPath = probe.output;
while (!nvccPath.empty() && (nvccPath.back() == '\n' || nvccPath.back() == '\r')) {
nvccPath.pop_back();
}
fs::path real = fs::canonical(nvccPath);
fs::path cudaLibDir = real.parent_path().parent_path() / "lib64";
if (!fs::exists(cudaLibDir / "libcudart.so")) {
Skip(std::format("libcudart.so not found at {} — incomplete CUDA SDK install",
cudaLibDir.string()));
}
::setenv("CRAFTER_CUDA_LIBDIR", cudaLibDir.string().c_str(), 1);
fs::path src = cwd / "tests" / "Cuda" / "inner";
Configuration cfg = LoadFixture("Cuda", src);
fs::path work = fs::current_path();
auto br = BuildOnce(cfg);
if (!br.result.empty()) {
std::println(std::cerr, "build failed:\n{}", br.result);
return 1;
}
fs::path artifact = work / "bin" / "cuda-test-x86_64-pc-linux-gnu-native" / "cuda-test";
if (!fs::exists(artifact)) {
std::println(std::cerr, "expected artifact missing at {}", artifact.string());
return 1;
}
// libcudart.so is on the system loader path on most CUDA installs
// (Arch puts /opt/cuda/lib64 in /etc/ld.so.conf.d), but other distros
// don't, so set LD_LIBRARY_PATH explicitly for the run.
auto run = RunInDir(work, std::format(
"LD_LIBRARY_PATH='{}' '{}'", cudaLibDir.string(), artifact.string()));
if (run.exitCode != 0) {
std::println(std::cerr, "artifact exited nonzero (rc={}):\n{}", run.exitCode, run.output);
return 1;
}
if (run.output != "answer=42\n") {
std::println(std::cerr, "output mismatch:\n expected: \"answer=42\\n\"\n got: {:?}", run.output);
return 1;
}
return 0;
} catch (const std::exception& e) {
std::println(std::cerr, "test exception: {}", e.what());
return 1;
}
}

View file

@ -0,0 +1,16 @@
// A staged-but-unlaunched kernel forces nvcc to emit a real fatbin and
// the cudart-registration glue, which is what makes this a meaningful
// CUDA-pipeline test. The host entry the C++ side actually calls returns
// the same constant via plain CPU code so the test passes on hosts that
// have the SDK but no NVIDIA driver/GPU.
__device__ int compute_on_device() {
return 42;
}
__global__ void kernel(int* out) {
*out = compute_on_device();
}
extern "C" int kernel_compute() {
return 42;
}

View file

@ -0,0 +1,8 @@
import std;
extern "C" int kernel_compute();
int main() {
std::println("answer={}", kernel_compute());
return 0;
}

View file

@ -0,0 +1,29 @@
import std;
import Crafter.Build;
namespace fs = std::filesystem;
using namespace Crafter;
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
Configuration cfg;
cfg.path = "./";
cfg.name = "cuda-test";
cfg.outputName = "cuda-test";
cfg.target = "x86_64-pc-linux-gnu";
cfg.type = ConfigurationType::Executable;
std::array<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "main" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
cfg.cuda.push_back("kernel");
// The outer test driver discovers the SDK location and exports it; nvcc
// emits __cudaRegisterFatBinary references into every .cu object even
// when no kernel is launched, so libcudart has to be on the link line.
if (const char* libDir = std::getenv("CRAFTER_CUDA_LIBDIR")) {
cfg.linkFlags.push_back(std::format("-L{}", libDir));
cfg.linkFlags.push_back("-lcudart");
}
return cfg;
}

20
tests/Cuda/project.cpp Normal file
View file

@ -0,0 +1,20 @@
import std;
import Crafter.Build;
namespace fs = std::filesystem;
using namespace Crafter;
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
Configuration cfg;
cfg.path = "tests/Cuda/";
cfg.name = "Cuda";
cfg.outputName = "Cuda";
cfg.target = "x86_64-pc-linux-gnu";
cfg.type = ConfigurationType::Executable;
cfg.dependencies = { ParentLib("crafter.build-lib") };
cfg.linkFlags.push_back("-Wl,--export-dynamic");
cfg.linkFlags.push_back("-ldl");
std::array<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "Cuda" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
return cfg;
}