use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("the vulkane crate always has a parent directory")
.to_path_buf()
}
fn in_repository_checkout() -> bool {
std::fs::read_to_string(repo_root().join("Cargo.toml")).is_ok_and(|s| s.contains("[workspace]"))
}
fn workflow() -> Option<String> {
let path = repo_root().join(".github/workflows/ci.yml");
match std::fs::read_to_string(&path) {
Ok(text) => Some(text),
Err(e) => {
assert!(
!in_repository_checkout(),
"cannot read {} ({e}), but this *is* a workspace checkout. The \
CI-coverage guard has nothing to read, so it would pass while \
checking nothing. Restore the workflow or fix this path — do \
not let the guard go quiet.",
path.display()
);
eprintln!(
"SKIP: {} not present and this is not a workspace checkout \
(packaged or vendored crate) — the CI-coverage guard is a \
property of the repository and does not apply here",
path.display()
);
None
}
}
}
struct TestLeg {
features: BTreeSet<String>,
only: BTreeSet<String>,
}
impl TestLeg {
fn builds(&self, feature: &str, stem: &str) -> bool {
self.features.contains(feature) && (self.only.is_empty() || self.only.contains(stem))
}
}
fn test_legs() -> Option<Vec<TestLeg>> {
let text = workflow()?;
let mut legs = Vec::new();
for line in text.lines() {
if !line.contains("cargo test") {
continue;
}
let mut features = BTreeSet::new();
let mut only = BTreeSet::new();
let words: Vec<&str> = line.split_whitespace().collect();
for (i, word) in words.iter().enumerate() {
match *word {
"--features" => {
if let Some(list) = words.get(i + 1) {
features.extend(
list.split(',')
.map(str::trim)
.filter(|f| !f.is_empty())
.map(str::to_string),
);
}
}
"--test" => {
if let Some(name) = words.get(i + 1) {
only.insert((*name).to_string());
}
}
_ => {}
}
}
legs.push(TestLeg { features, only });
}
assert!(
!legs.is_empty(),
"parsed no `cargo test` invocations out of ci.yml — the workflow format \
changed and this guard is now vacuous, which is the exact failure it \
exists to prevent. Fix the parser, do not delete the test."
);
Some(legs)
}
fn features_enabled_in_ci() -> BTreeSet<String> {
test_legs()
.into_iter()
.flatten()
.flat_map(|leg| leg.features)
.collect()
}
fn rust_files_in(dir: &Path) -> Vec<PathBuf> {
let entries =
std::fs::read_dir(dir).unwrap_or_else(|e| panic!("cannot list {}: {e}", dir.display()));
let mut files: Vec<PathBuf> = entries
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|x| x == "rs"))
.collect();
files.sort();
files
}
fn file_level_feature_gate(path: &Path) -> Option<String> {
let src = std::fs::read_to_string(path).ok()?;
let marker = "#![cfg(feature = \"";
for line in src.lines() {
let line = line.trim_start();
if line.starts_with("//") {
continue;
}
if let Some(rest) = line.strip_prefix(marker) {
let end = rest.find('"')?;
return Some(rest[..end].to_string());
}
}
None
}
#[test]
fn every_feature_gated_test_file_is_built_by_some_ci_leg() {
let Some(legs) = test_legs() else { return };
let enabled = features_enabled_in_ci();
let mut missing = Vec::new();
for dir in ["vulkane/tests", "kiss-vulkan-vocab/tests"] {
let dir = repo_root().join(dir);
if !dir.is_dir() {
continue;
}
for file in rust_files_in(&dir) {
let Some(feature) = file_level_feature_gate(&file) else {
continue; };
let stem = file
.file_stem()
.expect("a .rs file has a stem")
.to_string_lossy();
if !legs.iter().any(|leg| leg.builds(&feature, &stem)) {
missing.push(format!(
" {} is gated on `{feature}`, which no `cargo test` leg \
enables for this target",
file.strip_prefix(repo_root()).unwrap_or(&file).display()
));
}
}
}
assert!(
missing.is_empty(),
"these test files compile to nothing in CI and report `running 0 tests ... ok`:\n\
{}\n\n\
Features CI does enable: {enabled:?}\n\n\
Add the feature to a `--features` list in .github/workflows/ci.yml. If it \
genuinely cannot run there — a native toolchain CI does not have, say — \
that is a real gap and it belongs in the workflow as a comment stating \
why, not silently absent from it. A file no leg builds is not covered by \
anything, and the summary will not tell you.",
missing.join("\n")
);
}
#[test]
fn every_example_is_built_by_ci() {
let Some(text) = workflow() else { return };
let dir = repo_root().join("vulkane/examples");
let mut missing = Vec::new();
for file in rust_files_in(&dir) {
let name = file
.file_stem()
.expect("a .rs file has a stem")
.to_string_lossy()
.to_string();
if !text.contains(&format!("--example {name}")) {
missing.push(name);
}
}
assert!(
missing.is_empty(),
"these examples are never compiled by CI: {missing:?}\n\n\
An example that does not build is a broken artifact shipped to users, \
and nothing in the repository would notice. Add each to the \"Build \
examples\" step in .github/workflows/ci.yml with whatever features it \
needs."
);
}
#[test]
fn the_parser_actually_finds_the_features_it_claims_to() {
let Some(legs) = test_legs() else { return };
let enabled = features_enabled_in_ci();
assert!(
enabled.contains("fetch-spec"),
"parsed features {enabled:?} without `fetch-spec`, which every CI leg \
enables — the parser is not reading what it thinks it is"
);
assert!(
!enabled.contains("definitely-not-a-real-feature"),
"the parser reports a feature that does not exist"
);
assert!(
legs.iter().any(|leg| leg.only.is_empty()),
"no `cargo test` leg compiles all test targets — every leg names \
`--test <target>`, so a newly added test file would be built by none \
of them while this guard still reported its feature as covered"
);
let restricted: Vec<&TestLeg> = legs.iter().filter(|leg| !leg.only.is_empty()).collect();
assert!(
!restricted.is_empty(),
"expected at least one `--test <target>` leg (the shaderc and slang \
jobs are both restricted); the `--test` parser is not populating"
);
for leg in restricted {
assert!(
!leg.builds("fetch-spec", "definitely_not_a_test_file"),
"a `--test`-restricted leg claimed to build a target it does not name"
);
}
let gated = repo_root().join("vulkane/tests/kiss_target_live.rs");
assert_eq!(
file_level_feature_gate(&gated).as_deref(),
Some("kiss-target"),
"the file-level gate detector stopped recognising a known gate"
);
let ungated = repo_root().join("vulkane/tests/ci_coverage.rs");
assert_eq!(
file_level_feature_gate(&ungated),
None,
"this file is not feature-gated; if the detector says otherwise it is \
matching something it should not"
);
}