[cts_runner] Print uncaptured errors to stderr (#8263)

This is useful when running test snippets. Update READMEs including to
mention how to do that.
This commit is contained in:
Andy Leiserson 2025-10-15 16:13:04 -04:00 committed by GitHub
parent f935660d54
commit cb13fbe4a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 125 additions and 38 deletions

14
Cargo.lock generated
View File

@ -976,6 +976,7 @@ dependencies = [
"deno_webgpu",
"deno_webidl",
"env_logger",
"tempfile",
"termcolor",
"tokio",
]
@ -3990,6 +3991,19 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790"
[[package]]
name = "tempfile"
version = "3.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [
"fastrand",
"getrandom 0.3.3",
"once_cell",
"rustix 1.0.8",
"windows-sys 0.61.0",
]
[[package]]
name = "termcolor"
version = "1.4.1"

View File

@ -187,6 +187,7 @@ spirv = "0.3"
static_assertions = "1.1"
strum = { version = "0.27.1", default-features = false, features = ["derive"] }
syn = "2.0.98"
tempfile = "3"
toml = "0.9.0"
trybuild = "1"
tracy-client = "0.18"

View File

@ -22,3 +22,6 @@ deno_webidl.workspace = true
deno_webgpu.workspace = true
tokio = { workspace = true, features = ["full"] }
termcolor.workspace = true
[dev-dependencies]
tempfile.workspace = true

17
cts_runner/README.md Normal file
View File

@ -0,0 +1,17 @@
# cts_runner
This crate contains infrastructure for running the WebGPU conformance tests on
Deno's `wgpu`-based implementation of WebGPU.
Instructions for running the tests via the CTS `xtask` are in the
[top-level README](https://github.com/gfx-rs/wgpu/blob/trunk/README.md#webgpu-conformance-test-suite).
The file [revision.txt](./revision.txt) specifies the version of the CTS that
will be used by default.
`cts_runner` is somewhat misnamed at this point, in that it is useful for
things other than just running the CTS:
- The [tests](./tests) directory contains a few directed tests for
Deno's bindings to `wgpu`.
- Standalone JavaScript snippets that use WebGPU can be run
with a command like: `cargo run -p cts_runner -- test.js`.

View File

@ -224,6 +224,25 @@ const windowOrWorkerGlobalScope = {
windowOrWorkerGlobalScope.console.enumerable = false;
// Print uncaptured WebGPU errors to stderr. This is useful when running
// standalone JavaScript test snippets. It isn't needed for the CTS, because the
// CTS uses error scopes. (The CTS also installs its own error handler with
// `addEventListener`, so having this here may result in printing duplicate
// errors from the CTS in some cases.) Printing uncaptured errors to stderr
// isn't desired as built-in behavior in Deno, because the console is reserved
// for the application.
//
// Note that catching an error here _does not_ result in a non-zero exit status.
const requestDevice = webgpu.GPUAdapter.prototype.requestDevice;
webgpu.GPUAdapter.prototype.requestDevice = function(desc) {
return requestDevice.call(this, desc).then((device) => {
device.onuncapturederror = (event) => {
core.print("cts_runner caught WebGPU error:" + event.error.message, true);
};
return device;
})
};
const mainRuntimeGlobalProperties = {
Window: globalInterfaces.windowConstructorDescriptor,
window: util.readOnly(globalThis),

View File

@ -1,5 +0,0 @@
const adapter = await navigator.gpu.requestAdapter();
if (adapter.features.has("mappable-primary-buffers")) {
throw new TypeError("Adapter should not report support for wgpu native-only features");
}

View File

@ -3,12 +3,15 @@
// As of June 2025, these tests are not run in CI.
use std::{
fmt::{self, Debug, Display},
ffi::OsStr,
io::Write,
path::PathBuf,
process::Command,
process::{Command, Output},
str,
};
use tempfile::NamedTempFile;
pub fn target_dir() -> PathBuf {
let current_exe = std::env::current_exe().unwrap();
let target_dir = current_exe.parent().unwrap().parent().unwrap();
@ -24,38 +27,71 @@ pub fn cts_runner_exe_path() -> PathBuf {
p
}
pub struct JsError;
impl Display for JsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "JavaScript test returned an error")
}
}
impl Debug for JsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
type JsResult = Result<(), JsError>;
fn exec_js_test(script: &str) -> JsResult {
let output = Command::new(cts_runner_exe_path())
.arg(script)
fn exec_cts_runner(script_file: impl AsRef<OsStr>) -> Output {
Command::new(cts_runner_exe_path())
.arg(script_file)
.output()
.unwrap();
.unwrap()
}
fn exec_js_file(script_file: impl AsRef<OsStr>) {
let output = exec_cts_runner(script_file);
println!("{}", str::from_utf8(&output.stdout).unwrap());
eprintln!("{}", str::from_utf8(&output.stderr).unwrap());
output.status.success().then_some(()).ok_or(JsError)
assert!(output.status.success());
}
fn check_js_stderr(script: &str, expected: &str) {
let mut tempfile = NamedTempFile::new().unwrap();
tempfile.write_all(script.as_bytes()).unwrap();
tempfile.flush().unwrap();
let output = exec_cts_runner(tempfile.path());
assert!(
output.stdout.is_empty(),
"unexpected output on stdout: {}",
str::from_utf8(&output.stdout).unwrap(),
);
assert_eq!(str::from_utf8(&output.stderr).unwrap(), expected);
assert!(output.status.success());
}
fn exec_js(script: &str) {
check_js_stderr(script, "");
}
#[test]
fn hello_compute_example() -> JsResult {
exec_js_test("examples/hello-compute.js")
fn hello_compute_example() {
exec_js_file("examples/hello-compute.js");
}
#[test]
fn features() -> JsResult {
exec_js_test("tests/features.js")
fn features() {
exec_js(
r#"
const adapter = await navigator.gpu.requestAdapter();
if (adapter.features.has("mappable-primary-buffers")) {
throw new TypeError("Adapter should not report support for wgpu native-only features");
}
"#,
);
}
#[test]
fn uncaptured_error() {
check_js_stderr(
r#"
const code = `const val: u32 = 1.1;`;
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
device.createShaderModule({ code })
"#,
"cts_runner caught WebGPU error:
Shader '' parsing error: the type of `val` is expected to be `u32`, but got `{AbstractFloat}`
wgsl:1:7
1 const val: u32 = 1.1;
^^^ definition of `val`\n\n",
);
}

View File

@ -14,13 +14,15 @@ a
[wgpu trace](https://github.com/gfx-rs/wgpu/wiki/Debugging-wgpu-Applications#tracing-infrastructure)
to the specified directory.
For testing this op crate will make use of the WebGPU conformance tests suite,
running through our WPT runner. This will be used to validate implementation
conformance.
This op crate is tested primarily by running the
[WebGPU conformance test suite](https://github.com/gpuweb/cts) using `wgpu`'s
[`cts_runner`](https://github.com/gfx-rs/wgpu/blob/trunk/README.md#webgpu-conformance-test-suite).
`cts_runner` also has a few
[directed tests](https://github.com/gfx-rs/wgpu/tree/trunk/cts_runner/tests)
to fill in missing coverage.
GitHub CI doesn't run with GPUs, so testing relies on software like DX WARP &
Vulkan lavapipe. Currently, only using DX WARP works, so tests are only run on
Windows.
GPU availability in GitHub CI is limited, so some configurations rely on
software like DX WARP & Vulkan lavapipe.
## Links