mirror of
https://github.com/gfx-rs/wgpu.git
synced 2025-12-08 21:26:17 +00:00
276 lines
8.5 KiB
Rust
276 lines
8.5 KiB
Rust
//! Interface for running the WebGPU CTS (Conformance Test Suite) against wgpu.
|
|
//!
|
|
//! To run the default set of tests from `cts_runner/test.lst`:
|
|
//!
|
|
//! ```sh
|
|
//! cargo xtask cts
|
|
//! ```
|
|
//!
|
|
//! To run a specific test selector:
|
|
//!
|
|
//! ```sh
|
|
//! cargo xtask cts 'webgpu:api,operation,command_buffer,basic:*'
|
|
//! ```
|
|
//!
|
|
//! You can also supply your own test list in a file:
|
|
//!
|
|
//! ```sh
|
|
//! cargo xtask cts -f your_tests.lst
|
|
//! ```
|
|
//!
|
|
//! Each line in a test list file is a test selector that will be passed to the
|
|
//! CTS's own command line runner. Note that wildcards may only be used to specify
|
|
//! running all tests in a file, or all subtests in a test.
|
|
//!
|
|
//! A test line may optionally contain a `fails-if(backend)` clause. This
|
|
//! indicates that the test should be skipped on that backend, however, the
|
|
//! runner will only do so if the `--backend` flag is passed to tell it where
|
|
//! it is running.
|
|
//!
|
|
//! Lines starting with `//` or `#` in the test list are treated as comments and
|
|
//! ignored.
|
|
|
|
use anyhow::{bail, Context};
|
|
use pico_args::Arguments;
|
|
use regex_lite::{Regex, RegexBuilder};
|
|
use std::{ffi::OsString, sync::LazyLock};
|
|
use xshell::Shell;
|
|
|
|
use crate::util::git_version_at_least;
|
|
|
|
/// Path within the repository where the CTS will be checked out.
|
|
const CTS_CHECKOUT_PATH: &str = "cts";
|
|
|
|
/// Path within the repository to a file containing the git revision of the CTS to check out.
|
|
const CTS_REVISION_PATH: &str = "cts_runner/revision.txt";
|
|
|
|
/// URL of the CTS git repository.
|
|
const CTS_GIT_URL: &str = "https://github.com/gpuweb/cts.git";
|
|
|
|
/// Path to default CTS test list.
|
|
const CTS_DEFAULT_TEST_LIST: &str = "cts_runner/test.lst";
|
|
|
|
#[derive(Default)]
|
|
struct TestLine {
|
|
pub selector: OsString,
|
|
pub fails_if: Option<String>,
|
|
}
|
|
|
|
pub fn run_cts(
|
|
shell: Shell,
|
|
mut args: Arguments,
|
|
passthrough_args: Option<Vec<OsString>>,
|
|
) -> anyhow::Result<()> {
|
|
let skip_checkout = args.contains("--skip-checkout");
|
|
let llvm_cov = args.contains("--llvm-cov");
|
|
let release = args.contains("--release");
|
|
let running_on_backend = args.opt_value_from_str::<_, String>("--backend")?;
|
|
|
|
if running_on_backend.is_none() {
|
|
log::warn!(
|
|
"fails-if conditions are only evaluated if a backend is specified with --backend"
|
|
);
|
|
}
|
|
|
|
let mut list_files = Vec::<OsString>::new();
|
|
while let Some(file) = args.opt_value_from_str("-f")? {
|
|
list_files.push(file);
|
|
}
|
|
|
|
let mut tests = args
|
|
.finish()
|
|
.into_iter()
|
|
.map(|selector| TestLine {
|
|
selector,
|
|
..Default::default()
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
if tests.is_empty() && list_files.is_empty() {
|
|
if passthrough_args.is_none() {
|
|
log::info!("Reading default test list from {CTS_DEFAULT_TEST_LIST}");
|
|
list_files.push(OsString::from(CTS_DEFAULT_TEST_LIST));
|
|
}
|
|
} else if passthrough_args.is_some() {
|
|
bail!("Test(s) and test list(s) are incompatible with passthrough arguments.");
|
|
}
|
|
|
|
for file in list_files {
|
|
tests.extend(shell.read_file(file)?.lines().filter_map(|line| {
|
|
static TEST_LINE_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
|
RegexBuilder::new(r#"(?:fails-if\s*\(\s*(?<fails_if>\w+)\s*\)\s+)?(?<selector>.*)"#)
|
|
.build()
|
|
.unwrap()
|
|
});
|
|
|
|
let trimmed = line.trim();
|
|
let is_comment = trimmed.starts_with("//") || trimmed.starts_with("#");
|
|
let captures = TEST_LINE_REGEX
|
|
.captures(trimmed)
|
|
.expect("Invalid test line: {trimmed}");
|
|
(!trimmed.is_empty() && !is_comment).then(|| TestLine {
|
|
selector: OsString::from(&captures["selector"]),
|
|
fails_if: captures.name("fails_if").map(|m| m.as_str().to_string()),
|
|
})
|
|
}))
|
|
}
|
|
|
|
let wgpu_cargo_toml = std::path::absolute(shell.current_dir().join("Cargo.toml"))
|
|
.context("Failed to get path to Cargo.toml")?;
|
|
|
|
let cts_revision = shell
|
|
.read_file(CTS_REVISION_PATH)
|
|
.context(format!(
|
|
"Failed to read CTS git SHA from {CTS_REVISION_PATH}"
|
|
))?
|
|
.trim()
|
|
.to_string();
|
|
|
|
if !shell.path_exists(CTS_CHECKOUT_PATH) {
|
|
if skip_checkout {
|
|
bail!("Skipping CTS checkout doesn't make sense when CTS is not present");
|
|
}
|
|
let mut cmd = shell
|
|
.cmd("git")
|
|
.args(["clone", CTS_GIT_URL, CTS_CHECKOUT_PATH])
|
|
.quiet();
|
|
|
|
if git_version_at_least(&shell, [2, 49, 0])? {
|
|
log::info!("Cloning CTS shallowly with revision {cts_revision}");
|
|
cmd = cmd.args(["--depth=1", "--revision", &cts_revision]);
|
|
cmd = cmd.args([
|
|
"-c",
|
|
"remote.origin.fetch=+refs/heads/gh-pages:refs/remotes/origin/gh-pages",
|
|
]);
|
|
} else {
|
|
log::info!("Cloning full checkout of CTS with revision {cts_revision}");
|
|
cmd = cmd.args(["-b", "gh-pages", "--single-branch"]);
|
|
}
|
|
|
|
cmd.run().context("Failed to clone CTS")?;
|
|
|
|
shell.change_dir(CTS_CHECKOUT_PATH);
|
|
} else if !skip_checkout {
|
|
shell.change_dir(CTS_CHECKOUT_PATH);
|
|
|
|
// For new clones, this is set by the cloning commands above, but older
|
|
// clones may not have it. Eventually this can be removed.
|
|
if shell
|
|
.cmd("git")
|
|
.args(["config", "--get", "remote.origin.fetch"])
|
|
.quiet()
|
|
.ignore_stdout()
|
|
.ignore_stderr()
|
|
.run()
|
|
.is_err()
|
|
{
|
|
shell
|
|
.cmd("git")
|
|
.args([
|
|
"config",
|
|
"remote.origin.fetch",
|
|
"+refs/heads/gh-pages:refs/remotes/origin/gh-pages",
|
|
])
|
|
.quiet()
|
|
.run()
|
|
.context("Failed setting git config")?;
|
|
}
|
|
|
|
// If we don't have the CTS commit we want, try to fetch it.
|
|
if shell
|
|
.cmd("git")
|
|
.args(["cat-file", "commit", &cts_revision])
|
|
.quiet()
|
|
.ignore_stdout()
|
|
.ignore_stderr()
|
|
.run()
|
|
.is_err()
|
|
{
|
|
log::info!("Fetching CTS");
|
|
shell
|
|
.cmd("git")
|
|
.args(["fetch", "--quiet"])
|
|
.quiet()
|
|
.run()
|
|
.context("Failed to fetch CTS")?;
|
|
}
|
|
} else {
|
|
shell.change_dir(CTS_CHECKOUT_PATH);
|
|
}
|
|
|
|
if !skip_checkout {
|
|
log::info!("Checking out CTS");
|
|
shell
|
|
.cmd("git")
|
|
.args(["checkout", "--quiet", &cts_revision])
|
|
.quiet()
|
|
.run()
|
|
.context("Failed to check out CTS")?;
|
|
} else {
|
|
log::info!("Skipping CTS checkout because --skip-checkout was specified");
|
|
}
|
|
|
|
let run_flags = if llvm_cov {
|
|
&["llvm-cov", "--no-cfg-coverage", "--no-report", "run"][..]
|
|
} else {
|
|
&["run"][..]
|
|
};
|
|
|
|
if let Some(passthrough_args) = passthrough_args {
|
|
let mut cmd = shell
|
|
.cmd("cargo")
|
|
.args(run_flags)
|
|
.args(["--manifest-path".as_ref(), wgpu_cargo_toml.as_os_str()])
|
|
.args(["-p", "cts_runner"])
|
|
.args(["--bin", "cts_runner"]);
|
|
|
|
if release {
|
|
cmd = cmd.arg("--release")
|
|
}
|
|
|
|
cmd.args(["--", "./tools/run_deno", "--verbose"])
|
|
.args(&passthrough_args)
|
|
.run()?;
|
|
|
|
return Ok(());
|
|
}
|
|
|
|
log::info!("Running CTS");
|
|
for test in &tests {
|
|
match (&test.fails_if, &running_on_backend) {
|
|
(Some(backend), Some(running_on_backend)) if backend == running_on_backend => {
|
|
log::info!(
|
|
"Skipping {} on {} backend",
|
|
test.selector.to_string_lossy(),
|
|
running_on_backend,
|
|
);
|
|
continue;
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
log::info!("Running {}", test.selector.to_string_lossy());
|
|
let mut cmd = shell
|
|
.cmd("cargo")
|
|
.args(run_flags)
|
|
.args(["--manifest-path".as_ref(), wgpu_cargo_toml.as_os_str()])
|
|
.args(["-p", "cts_runner"])
|
|
.args(["--bin", "cts_runner"]);
|
|
|
|
if release {
|
|
cmd = cmd.arg("--release")
|
|
}
|
|
|
|
cmd.args(["--", "./tools/run_deno", "--verbose"])
|
|
.args([&test.selector])
|
|
.run()
|
|
.context("CTS failed")?;
|
|
}
|
|
|
|
if tests.len() > 1 {
|
|
log::info!("Summary reflects only tests from the last selector, not the entire run.");
|
|
}
|
|
|
|
Ok(())
|
|
}
|