Files
aiongg f3bb7a9f30 CI: one test binary per crate, line tables only, one WASM check (#2710)
* CI: dev profile keeps line tables for workspace crates and no debug info for dependencies

* Control server tests: persistent_requests_and_slow_replies_outlive_the_idle_read_timeout no longer races the idle timeout

Each request (and the closing half-shutdown) reaches the socket before the
worker reads, so on a slow machine only the reply waits outlast the 100 ms
idle timeout, as the test intends. The idle test sends its input the same
way, and the no-frames test allows 5 s instead of 2 s to give up.

* CI: one integration-test binary per crate

Each crate's tests/*.rs become modules of tests/it/main.rs (51 test files
into 13 binaries, plus 6 that stay their own). Inner #![cfg] attributes
move to the mod lines. Files that change state for the whole process keep
a binary of their own: codecs jxl_tail_heap and raw sr2_heap (dhat global
allocator), gpu shutdown, shutdown_first and memory, ui-egui shutdown.
Docs, comments and the GPU probe name the new paths and targets
(--test it <module>::); the CI report names an it binary's skips after
the crate.

* CI: tests under nextest, doctests apart, one wasm check

xtask ci runs the tests with cargo nextest run --lib --bins --tests when
cargo-nextest is installed, else cargo test with the same targets and a
one-line notice; doctests run after them with cargo test --workspace --doc.
The heif and hardware GPU steps use the same runner. Examples stay out of
the test build (clippy --all-targets still checks them). Concurrent tests
under nextest follow the RUST_TEST_THREADS cap (NEXTEST_TEST_THREADS).
The wasm check is one cargo check --keep-going over the same crates, and
clippy runs with --keep-going.

* CI: tests run under cargo test by default, nextest with LIGHTCRAFT_NEXTEST=1 (per-process scene cache)
2026-10-23 13:34:30 -04:00

87 lines
3.7 KiB
Rust

//! What GPU this machine offers the develop pipeline, as one JSON line: the capability probe of
//! `cargo xtask ci`, which is std + serde_json and has no wgpu of its own. Ignored in normal runs;
//! xtask runs it after the workspace tests, so it costs no extra build:
//! `cargo test --workspace --test it -- --ignored --exact gpu_probe::gpu_probe --nocapture`.
//!
//! - `LIGHTCRAFT_GPU_PROBE` unset: the compute device the pipeline picks (`available()`,
//! `adapter_name()`, `unavailable_reason()`), the backends it may use and the backends this build
//! contains.
//! - `LIGHTCRAFT_GPU_PROBE=<backend>` (`vulkan`, `metal`, `dx12`, `gl`): every adapter that
//! backend lists, software ones (lavapipe, llvmpipe, WARP) included. One backend per process, so
//! a driver that crashes while it is enumerated (issue #136) costs only its own line.
//!
//! Only reports: which adapters the pipeline accepts is `ctx.rs`'s policy.
use lightcraft_gpu::backend::{self, Backends};
const NAMES: [(&str, Backends); 4] = [("vulkan", Backends::VULKAN), ("metal", Backends::METAL), ("dx12", Backends::DX12), ("gl", Backends::GL)];
#[test]
#[ignore = "the capability probe of `cargo xtask ci`"]
fn gpu_probe() {
let line = match std::env::var("LIGHTCRAFT_GPU_PROBE").ok().filter(|b| !b.is_empty()) {
None => pipeline(),
Some(name) => adapters(&name),
};
// its own line, prefixed: libtest may have printed `test gpu_probe ... ` on this one
println!("\ngpu_probe: {line}");
}
fn names(set: Backends) -> String {
let list: Vec<String> = NAMES.iter().filter(|(_, b)| set.contains(*b)).map(|(n, _)| json_str(n)).collect();
format!("[{}]", list.join(","))
}
fn pipeline() -> String {
let available = lightcraft_gpu::available();
let opt = |s: Option<String>| s.map_or_else(|| "null".to_owned(), |s| json_str(&s));
let compiled = wgpu::Instance::enabled_backend_features();
format!(
"{{\"pipeline\":{{\"available\":{available},\"adapter\":{},\"reason\":{},\"backends\":{},\"compiled\":{}}}}}",
opt(lightcraft_gpu::adapter_name()),
opt(lightcraft_gpu::unavailable_reason()),
names(backend::compute_backends().unwrap_or(Backends::empty()) & compiled),
names(compiled),
)
}
fn adapters(name: &str) -> String {
let (_, set) = NAMES.iter().find(|(n, _)| *n == name).unwrap_or_else(|| panic!("unknown backend `{name}` (vulkan, metal, dx12, gl)"));
let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
desc.backends = *set;
desc.backend_options = backend::backend_options();
desc.flags = backend::instance_flags_from_env(desc.flags);
let instance = wgpu::Instance::new(desc);
let adapters: Vec<String> = pollster::block_on(instance.enumerate_adapters(*set))
.iter()
.map(|a| {
let i = a.get_info();
format!(
"{{\"name\":{},\"type\":{},\"vendor\":\"0x{:04x}\",\"device\":\"0x{:04x}\",\"driver\":{},\"driver_info\":{}}}",
json_str(&i.name),
json_str(&format!("{:?}", i.device_type)),
i.vendor,
i.device,
json_str(&i.driver),
json_str(&i.driver_info),
)
})
.collect();
format!("{{\"backend\":{},\"adapters\":[{}]}}", json_str(name), adapters.join(","))
}
/// A JSON string literal.
fn json_str(s: &str) -> String {
let mut out = String::from("\"");
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
c if u32::from(c) < 0x20 => out.push_str(&format!("\\u{:04x}", u32::from(c))),
c => out.push(c),
}
}
out.push('"');
out
}