Turn dependency tests from xtask subcommand into #[test]s (#7220)

This commit is contained in:
Connor Fitzgerald 2025-02-27 00:22:08 -05:00 committed by GitHub
parent 6f0c2434a7
commit 99437e7343
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 353 additions and 258 deletions

View File

@ -809,22 +809,3 @@ jobs:
command: check bans licenses sources
arguments: --all-features --workspace
rust-version: ${{ env.REPO_MSRV }}
check-feature-dependencies:
# runtime is normally 1 minute
timeout-minutes: 5
name: "Feature Dependencies"
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Install repo MSRV toolchain
run: |
rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal
rustup override set ${{ env.REPO_MSRV }}
cargo -V
- name: Run `cargo feature-dependencies`
run: cargo xtask check-feature-dependencies

44
Cargo.lock generated
View File

@ -546,6 +546,15 @@ dependencies = [
"wayland-client",
]
[[package]]
name = "camino"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
dependencies = [
"serde",
]
[[package]]
name = "capacity_builder"
version = "0.1.3"
@ -575,6 +584,29 @@ dependencies = [
"syn",
]
[[package]]
name = "cargo-platform"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8769706aad5d996120af43197bf46ef6ad0fda35216b4505f926a365a232d924"
dependencies = [
"camino",
"cargo-platform",
"semver 1.0.25",
"serde",
"serde_json",
"thiserror 2.0.11",
]
[[package]]
name = "cast"
version = "0.3.0"
@ -3329,7 +3361,7 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
"semver 0.9.0",
]
[[package]]
@ -3413,6 +3445,15 @@ dependencies = [
"semver-parser",
]
[[package]]
name = "semver"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
dependencies = [
"serde",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
@ -4738,6 +4779,7 @@ dependencies = [
"arrayvec",
"bitflags 2.8.0",
"bytemuck",
"cargo_metadata",
"cfg-if",
"console_log",
"ctor",

View File

@ -93,6 +93,7 @@ bytemuck = { version = "1.21", features = [
"extern_crate_alloc",
"min_const_generics",
] }
cargo_metadata = "0.19"
cfg_aliases = "0.2.1"
cfg-if = "1"
criterion = "0.5"

View File

@ -37,6 +37,7 @@ This is a table of contents, in the form of the repository's directory structure
- [tests](#player-tests)
- tests
- [compile-tests](#wgpu-compile-tests)
- [dependency-tests](#wgpu-dependency-tests)
- [gpu-tests](#wgpu-gpu-tests)
- [validation-tests](#wgpu-validation-tests)
@ -169,6 +170,18 @@ the `wgpu` crate is expected to fail to compile. This mainly
revolves around ensuring lifetimes are properly handled when
dropping passes, etc.
## `wgpu` Dependency Tests
- Located in: `tests/dependency-tests`
- Run with `cargo nextest run --test wgpu-dependency-test`
- Tests against `cargo tree`.
These tests ensure that the `wgpu` crate has the correct dependency
tree on all platforms. It's super easy to subtly mess up the dependencies
which can cause issues or extra dependencies to be pulled in.
This provides a way to ensure that our `toml` files are correct.
## `wgpu` GPU Tests
- Located in: `tests/gpu-tests`

View File

@ -17,6 +17,11 @@ name = "wgpu-compile-test"
path = "compile-tests/root.rs"
harness = true
[[test]]
name = "wgpu-dependency-test"
path = "dependency-tests/root.rs"
harness = true
[[test]]
name = "wgpu-gpu-test"
path = "gpu-tests/root.rs"
@ -39,6 +44,7 @@ arrayvec.workspace = true
approx.workspace = true
bitflags.workspace = true
bytemuck.workspace = true
cargo_metadata.workspace = true
cfg-if.workspace = true
ctor.workspace = true
futures-lite.workspace = true

View File

@ -0,0 +1,290 @@
use std::process::Command;
#[derive(Debug)]
enum Search<'a> {
Positive(&'a str),
Negative(&'a str),
}
#[derive(Debug)]
struct Requirement<'a> {
human_readable_name: &'a str,
target: &'a str,
packages: &'a [&'a str],
features: &'a [&'a str],
default_features: bool,
search_terms: &'a [Search<'a>],
}
fn check_feature_dependency(requirement: Requirement) {
println!("Checking: {}", requirement.human_readable_name);
let mut args = Vec::new();
args.extend(["tree", "--target", requirement.target]);
for package in requirement.packages {
args.push("--package");
args.push(package);
}
if !requirement.default_features {
args.push("--no-default-features");
}
let features = requirement.features.join(",");
if !requirement.features.is_empty() {
args.push("--features");
args.push(&features);
}
println!("$ cargo {}", args.join(" "));
let output = Command::new("cargo")
.args(&args)
.output()
.expect("Failed to run cargo tree")
.stdout;
let output = String::from_utf8(output).expect("Output is not valid UTF-8");
let mut any_failed = false;
println!("{output}");
for (i, search_term) in requirement.search_terms.iter().enumerate() {
// Add a space and after to make sure we're getting a full match
let found = match search_term {
Search::Positive(search_term) => output.contains(&format!(" {search_term} ")),
Search::Negative(search_term) => !output.contains(&format!(" {search_term} ")),
};
if found {
println!(
"✅ Passed! ({} of {})",
i + 1,
requirement.search_terms.len()
);
} else {
println!(
"❌ Failed! ({} of {})",
i + 1,
requirement.search_terms.len()
);
any_failed = true;
}
}
assert!(!any_failed);
}
fn get_all_wgpu_features() -> Vec<String> {
let metadata = cargo_metadata::MetadataCommand::new()
.no_deps()
.exec()
.unwrap();
metadata
.packages
.iter()
.find(|p| p.name == "wgpu")
.unwrap()
.features
.keys()
.cloned()
.collect()
}
#[test]
fn wasm32_without_webgl_or_noop_does_not_depend_on_wgpu_core() {
let all_features = get_all_wgpu_features();
let removed_features = ["webgl", "noop", "wgpu-core"];
let features_no_webgl: Vec<&str> = all_features
.iter()
.map(String::as_str)
.filter(|&feature| !removed_features.contains(&feature))
.collect();
check_feature_dependency(Requirement {
human_readable_name:
"wasm32 without `webgl` or `noop` feature does not depend on `wgpu-core`",
target: "wasm32-unknown-unknown",
packages: &["wgpu"],
features: &features_no_webgl,
default_features: false,
search_terms: &[Search::Negative("wgpu-core")],
});
}
#[test]
fn wasm32_with_webgpu_and_wgsl_does_not_depend_on_naga() {
check_feature_dependency(Requirement {
human_readable_name: "wasm32 with `webgpu` and `wgsl` feature does not depend on `naga`",
target: "wasm32-unknown-unknown",
packages: &["wgpu"],
features: &["webgpu", "wgsl"],
default_features: false,
search_terms: &[Search::Negative("naga")],
});
}
#[test]
fn wasm32_with_webgl_depends_on_glow() {
check_feature_dependency(Requirement {
human_readable_name: "wasm32 with `webgl` feature depends on `glow`",
target: "wasm32-unknown-unknown",
packages: &["wgpu"],
features: &["webgl"],
default_features: false,
search_terms: &[Search::Positive("glow")],
});
}
#[test]
fn windows_with_webgl_does_not_depend_on_glow() {
check_feature_dependency(Requirement {
human_readable_name: "windows with `webgl` does not depend on `glow`",
target: "x86_64-pc-windows-msvc",
packages: &["wgpu"],
features: &["webgl"],
default_features: false,
search_terms: &[Search::Negative("glow")],
});
}
#[test]
fn apple_with_vulkan_does_not_depend_on_ash() {
check_feature_dependency(Requirement {
human_readable_name: "apple with `vulkan` feature does not depend on `ash`",
target: "aarch64-apple-darwin",
packages: &["wgpu"],
features: &["vulkan"],
default_features: false,
search_terms: &[Search::Negative("ash")],
});
}
#[test]
fn apple_with_vulkan_portability_depends_on_ash_and_renderdoc_sys() {
check_feature_dependency(Requirement {
human_readable_name:
"apple with `vulkan-portability` feature depends on `ash` and `renderdoc-sys`",
target: "aarch64-apple-darwin",
packages: &["wgpu"],
features: &["vulkan-portability"],
default_features: false,
search_terms: &[Search::Positive("ash"), Search::Positive("renderdoc-sys")],
});
}
#[test]
fn apple_with_gles_does_not_depend_on_glow() {
check_feature_dependency(Requirement {
human_readable_name: "apple with 'gles' feature does not depend on 'glow'",
target: "aarch64-apple-darwin",
packages: &["wgpu"],
features: &["gles"],
default_features: false,
search_terms: &[Search::Negative("glow")],
});
}
#[test]
fn apple_with_angle_depends_on_glow_and_renderdoc_sys() {
check_feature_dependency(Requirement {
human_readable_name: "apple with 'angle' feature depends on 'glow' and `renderdoc-sys`",
target: "aarch64-apple-darwin",
packages: &["wgpu"],
features: &["angle"],
default_features: false,
search_terms: &[Search::Positive("glow"), Search::Positive("renderdoc-sys")],
});
}
#[test]
fn apple_with_no_features_does_not_depend_on_renderdoc_sys() {
check_feature_dependency(Requirement {
human_readable_name: "apple with no features does not depend on 'renderdoc-sys'",
target: "aarch64-apple-darwin",
packages: &["wgpu"],
features: &[],
default_features: false,
search_terms: &[Search::Negative("renderdoc-sys")],
});
}
#[test]
fn windows_with_no_features_does_not_depend_on_glow_windows_or_ash() {
check_feature_dependency(Requirement {
human_readable_name:
"windows with no features does not depend on 'glow', `windows`, or `ash`",
target: "x86_64-pc-windows-msvc",
packages: &["wgpu"],
features: &[],
default_features: false,
search_terms: &[
Search::Negative("glow"),
Search::Negative("windows"),
Search::Negative("ash"),
],
});
}
#[test]
fn windows_with_no_features_depends_on_renderdoc_sys() {
check_feature_dependency(Requirement {
human_readable_name: "windows with no features depends on renderdoc-sys",
target: "x86_64-pc-windows-msvc",
packages: &["wgpu"],
features: &[],
default_features: false,
search_terms: &[Search::Positive("renderdoc-sys")],
});
}
#[test]
fn emscripten_with_webgl_does_not_depend_on_glow() {
check_feature_dependency(Requirement {
human_readable_name: "emscripten with webgl feature does not depend on glow",
target: "wasm32-unknown-emscripten",
packages: &["wgpu"],
features: &["webgl"],
default_features: false,
search_terms: &[Search::Negative("glow")],
});
}
#[test]
fn emscripten_with_gles_depends_on_glow() {
check_feature_dependency(Requirement {
human_readable_name: "emscripten with gles feature depends on glow",
target: "wasm32-unknown-emscripten",
packages: &["wgpu"],
features: &["gles"],
default_features: false,
search_terms: &[Search::Positive("glow")],
});
}
#[test]
fn x86_64_does_not_depend_on_portable_atomic() {
check_feature_dependency(Requirement {
human_readable_name: "x86-64 does not depend on portable-atomic",
target: "x86_64-unknown-linux-gnu",
packages: &["wgpu"],
features: &[],
default_features: false,
search_terms: &[Search::Negative("portable-atomic")],
});
}
#[test]
fn ppc32_does_depend_on_portable_atomic() {
check_feature_dependency(Requirement {
human_readable_name: "ppc32 does depend on portable-atomic",
target: "powerpc-unknown-linux-gnu",
packages: &["wgpu"],
features: &[],
default_features: false,
search_terms: &[Search::Positive("portable-atomic")],
});
}

View File

@ -1,231 +0,0 @@
use pico_args::Arguments;
use xshell::Shell;
#[derive(Debug)]
enum Search<'a> {
Positive(&'a str),
Negative(&'a str),
}
#[derive(Debug)]
struct Requirement<'a> {
human_readable_name: &'a str,
target: &'a str,
packages: &'a [&'a str],
features: &'a [&'a str],
default_features: bool,
search_terms: &'a [Search<'a>],
}
const ALL_WGPU_FEATURES: &[&str] = &[
"dx12",
"metal",
"webgpu",
"angle",
"vulkan-portability",
"webgl",
"spirv",
"glsl",
"wgsl",
"naga-ir",
"serde",
"counters",
"fragile-send-sync-non-atomic-wasm",
"static-dxc",
];
pub fn check_feature_dependencies(shell: Shell, arguments: Arguments) -> anyhow::Result<()> {
let mut _args = arguments.finish();
let features_no_webgl: Vec<&str> = ALL_WGPU_FEATURES
.iter()
.copied()
.filter(|feature| *feature != "webgl")
.collect();
let requirements = [
Requirement {
human_readable_name: "wasm32 without `webgl` feature does not depend on `wgpu-core`",
target: "wasm32-unknown-unknown",
packages: &["wgpu"],
features: &features_no_webgl,
default_features: false,
search_terms: &[Search::Negative("wgpu-core")],
},
Requirement {
human_readable_name:
"wasm32 with `webgpu` and `wgsl` feature does not depend on `naga`",
target: "wasm32-unknown-unknown",
packages: &["wgpu"],
features: &["webgpu", "wgsl"],
default_features: false,
search_terms: &[Search::Negative("naga")],
},
Requirement {
human_readable_name: "wasm32 with `webgl` feature depends on `glow`",
target: "wasm32-unknown-unknown",
packages: &["wgpu"],
features: &["webgl"],
default_features: false,
search_terms: &[Search::Positive("glow")],
},
Requirement {
human_readable_name: "windows with `webgl` does not depend on `glow`",
target: "x86_64-pc-windows-msvc",
packages: &["wgpu"],
features: &["webgl"],
default_features: false,
search_terms: &[Search::Negative("glow")],
},
Requirement {
human_readable_name: "apple with `vulkan` feature does not depend on `ash`",
target: "aarch64-apple-darwin",
packages: &["wgpu"],
features: &["vulkan"],
default_features: false,
search_terms: &[Search::Negative("ash")],
},
Requirement {
human_readable_name:
"apple with `vulkan-portability` feature depends on `ash` and `renderdoc-sys`",
target: "aarch64-apple-darwin",
packages: &["wgpu"],
features: &["vulkan-portability"],
default_features: false,
search_terms: &[Search::Positive("ash"), Search::Positive("renderdoc-sys")],
},
Requirement {
human_readable_name: "apple with 'gles' feature does not depend on 'glow'",
target: "aarch64-apple-darwin",
packages: &["wgpu"],
features: &["gles"],
default_features: false,
search_terms: &[Search::Negative("glow")],
},
Requirement {
human_readable_name: "apple with 'angle' feature depends on 'glow' and `renderdoc-sys`",
target: "aarch64-apple-darwin",
packages: &["wgpu"],
features: &["angle"],
default_features: false,
search_terms: &[Search::Positive("glow"), Search::Positive("renderdoc-sys")],
},
Requirement {
human_readable_name: "apple with no features does not depend on 'renderdoc-sys'",
target: "aarch64-apple-darwin",
packages: &["wgpu"],
features: &[],
default_features: false,
search_terms: &[Search::Negative("renderdoc-sys")],
},
Requirement {
human_readable_name:
"windows with no features does not depend on 'glow', `windows`, or `ash`",
target: "x86_64-pc-windows-msvc",
packages: &["wgpu"],
features: &[],
default_features: false,
search_terms: &[
Search::Negative("glow"),
Search::Negative("windows"),
Search::Negative("ash"),
],
},
Requirement {
human_readable_name: "windows with no features depends on renderdoc-sys",
target: "x86_64-pc-windows-msvc",
packages: &["wgpu"],
features: &[],
default_features: false,
search_terms: &[Search::Positive("renderdoc-sys")],
},
Requirement {
human_readable_name: "emscripten with webgl feature does not depend on glow",
target: "wasm32-unknown-emscripten",
packages: &["wgpu"],
features: &["webgl"],
default_features: false,
search_terms: &[Search::Negative("glow")],
},
Requirement {
human_readable_name: "emscripten with gles feature depends on glow",
target: "wasm32-unknown-emscripten",
packages: &["wgpu"],
features: &["gles"],
default_features: false,
search_terms: &[Search::Positive("glow")],
},
Requirement {
human_readable_name: "x86-64 does not depend on portable-atomic",
target: "x86_64-unknown-linux-gnu",
packages: &["wgpu"],
features: &[],
default_features: false,
search_terms: &[Search::Negative("portable-atomic")],
},
Requirement {
human_readable_name: "ppc32 does depend on portable-atomic",
target: "powerpc-unknown-linux-gnu",
packages: &["wgpu"],
features: &[],
default_features: false,
search_terms: &[Search::Positive("portable-atomic")],
},
];
let mut any_failures = false;
for requirement in requirements {
let mut cmd = shell
.cmd("cargo")
.args(["tree", "--target", requirement.target]);
for package in requirement.packages {
cmd = cmd.arg("--package").arg(package);
}
if !requirement.default_features {
cmd = cmd.arg("--no-default-features");
}
if !requirement.features.is_empty() {
cmd = cmd.arg("--features").arg(requirement.features.join(","));
}
log::info!("Checking Requirement: {}", requirement.human_readable_name);
log::debug!("{:#?}", requirement);
log::debug!("$ {cmd}");
let output = cmd.read()?;
log::debug!("{output}");
for (i, search_term) in requirement.search_terms.into_iter().enumerate() {
// Add a space and after to make sure we're getting a full match
let found = match search_term {
Search::Positive(search_term) => output.contains(&format!(" {search_term} ")),
Search::Negative(search_term) => !output.contains(&format!(" {search_term} ")),
};
if found {
log::info!(
"✅ Passed! ({} of {})",
i + 1,
requirement.search_terms.len()
);
} else {
log::info!(
"❌ Failed! ({} of {})",
i + 1,
requirement.search_terms.len()
);
any_failures = true;
}
}
}
if any_failures {
anyhow::bail!("Some feature dependencies are not met");
}
Ok(())
}

View File

@ -3,7 +3,6 @@ use std::process::ExitCode;
use anyhow::Context;
use pico_args::Arguments;
mod check_feature_dependencies;
mod run_wasm;
mod test;
mod util;
@ -13,9 +12,6 @@ const HELP: &str = "\
Usage: xtask <COMMAND>
Commands:
check-feature-dependencies
Check certain dependency invariants are upheld.
run-wasm
Build and run web examples
@ -77,9 +73,6 @@ fn main() -> anyhow::Result<ExitCode> {
shell.change_dir(String::from(env!("CARGO_MANIFEST_DIR")) + "/..");
match subcommand.as_deref() {
Some("check-feature-dependencies") => {
check_feature_dependencies::check_feature_dependencies(shell, args)?
}
Some("run-wasm") => run_wasm::run_wasm(shell, args)?,
Some("test") => test::run_tests(shell, args)?,
Some("vendor-web-sys") => vendor_web_sys::run_vendor_web_sys(shell, args)?,