diff --git a/.cargo/config.toml b/.cargo/config.toml index 5a1b17391b2..e0072bb0c5e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,7 +2,7 @@ xtask = "run --package xtask --" build-test-shader = "xtask test-build" compiletest = "run --release -p compiletests --" -difftest = "run --release -p difftests --" +difftest = "nextest run --release -P difftests -p difftests" run-wasm = ["run", "--release", "-p", "run-wasm", "--"] [target.'cfg(target_arch = "wasm32")'] diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 00000000000..4edcc1c7f37 --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,10 @@ +[profile.default] +default-filter = '!package(difftest*) & !package(compiletest*) & !package(example-runner-*)' +fail-fast = false + +[profile.difftests] +default-filter = 'package(difftests)' +slow-timeout = "2m" + +[profile.difftest-runner] +default-filter = 'package(difftest-runner) | package(difftest-types)' diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 50f2b12abda..5c9cfd27776 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -41,6 +41,8 @@ jobs: # figure out native target triple while we're at it - name: install rust-toolchain run: echo "TARGET=$(rustc --print host-tuple)" >> "$GITHUB_ENV" + - name: install nextest + uses: taiki-e/install-action@nextest # Fetch dependencies in a separate step to clearly show how long each part # of the testing takes - name: cargo fetch --locked @@ -49,13 +51,13 @@ jobs: # Core crates # Compiled in --release because cargo compiletest would otherwise compile in release again. - name: rustc_codegen_spirv build - run: cargo test -p rustc_codegen_spirv --release --no-default-features --features "use-installed-tools" --no-run + run: cargo nextest run -p rustc_codegen_spirv --release --no-default-features --features "use-installed-tools" --no-run - name: rustc_codegen_spirv test - run: cargo test -p rustc_codegen_spirv --release --no-default-features --features "use-installed-tools" + run: cargo nextest run -p rustc_codegen_spirv --release --no-default-features --features "use-installed-tools" - name: workspace test (excluding examples) - run: cargo test --release --workspace --exclude "example-runner-*" --exclude "cargo-gpu*" --no-default-features --features "use-installed-tools,clap" + run: cargo nextest run --release --workspace --exclude "example-runner-*" --exclude "cargo-gpu*" --no-default-features --features "use-installed-tools,clap" # Examples - name: cargo check examples @@ -173,12 +175,16 @@ jobs: sudo apt install -y xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - name: install rust-toolchain run: echo "TARGET=$(rustc --print host-tuple)" >> "$GITHUB_ENV" + - name: install nextest + uses: taiki-e/install-action@nextest - name: cargo fetch --locked run: cargo fetch --locked --target $TARGET - name: cargo fetch --locked difftests run: cargo fetch --locked --manifest-path=tests/difftests/tests/Cargo.toml --target $TARGET + - name: build difftests (without shaders) + run: cargo nextest run -P difftests -p difftests --release --no-default-features --features "use-installed-tools" --no-run - name: difftests - run: cargo run -p difftests --release --no-default-features --features "use-installed-tools" + run: cargo nextest run -P difftests -p difftests --release --no-default-features --features "use-installed-tools" cargo-gpu-os: strategy: diff --git a/Cargo.lock b/Cargo.lock index 64ee62a4795..670cbf5b5d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -871,37 +871,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] -name = "difftest" +name = "difftest-runner" version = "0.0.0" dependencies = [ "anyhow", - "ash", "bytemuck", - "futures", + "bytesize", + "difftest-types", + "libtest-mimic", "serde", "serde_json", - "spirv-builder", + "tabled", "tempfile", - "wgpu", + "thiserror 2.0.18", + "toml 0.9.11+spec-1.1.0", + "tracing", + "tracing-subscriber", ] [[package]] -name = "difftests" -version = "0.0.0" +name = "difftest-types" +version = "0.9.0" dependencies = [ "anyhow", "bytemuck", - "bytesize", - "difftest", "serde", "serde_json", - "tabled", - "tempfile", - "tester", - "thiserror 2.0.18", - "toml 0.9.11+spec-1.1.0", - "tracing", - "tracing-subscriber", +] + +[[package]] +name = "difftests" +version = "0.9.0" +dependencies = [ + "anyhow", + "difftest-runner", ] [[package]] @@ -1071,6 +1074,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + [[package]] name = "example-runner-ash" version = "0.0.0" @@ -1914,6 +1923,18 @@ dependencies = [ "redox_syscall 0.7.0", ] +[[package]] +name = "libtest-mimic" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5297962ef19edda4ce33aaa484386e0a5b3d7f2f4e037cbeee00503ef6b29d33" +dependencies = [ + "anstream", + "anstyle", + "clap", + "escape8259", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" diff --git a/Cargo.toml b/Cargo.toml index 88a788506d0..6d46237d779 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,8 @@ members = [ "tests/compiletests", "tests/compiletests/deps-helper", "tests/difftests/bin", - "tests/difftests/lib", + "tests/difftests/runner", + "tests/difftests/types", ] exclude = [ @@ -84,7 +85,9 @@ expect-test = "1.5.1" regex-lite = "0.1.9" # difftest libraries mirrored from difftest workspace -difftest = { path = "tests/difftests/lib" } +difftest = { path = "tests/difftests/tests/lib" } +difftest-runner = { path = "tests/difftests/runner", default-features = false } +difftest-types = { path = "tests/difftests/types" } # External dependencies that need to be mentioned more than once. tracing = "0.1" diff --git a/crates/rustc_codegen_spirv/Cargo.toml b/crates/rustc_codegen_spirv/Cargo.toml index ed72fbd2800..10a95dafe54 100644 --- a/crates/rustc_codegen_spirv/Cargo.toml +++ b/crates/rustc_codegen_spirv/Cargo.toml @@ -34,7 +34,7 @@ skip-toolchain-check = [] # HACK(eddyb) these only exist to unify features across dependency trees, # in order to avoid multiple separate instances of `rustc_codegen_spirv`. ahash = { version = "0.8.11", features = ["no-rng"] } -bytemuck = { version = "1.20.0", features = ["aarch64_simd", "derive"] } +bytemuck = { workspace = true, features = ["aarch64_simd"] } log = { version = "0.4.22", features = ["std"] } regex = { version = "1", features = ["perf"] } rustix = { version = "1.0.8", features = ["all-apis"] } diff --git a/crates/spirv-std/Cargo.toml b/crates/spirv-std/Cargo.toml index e5d69a08e3c..c847a2edf46 100644 --- a/crates/spirv-std/Cargo.toml +++ b/crates/spirv-std/Cargo.toml @@ -14,7 +14,7 @@ workspace = true spirv-std-types.workspace = true spirv-std-macros.workspace = true bitflags = "1.3.2" -bytemuck = { version = "1.18.0", features = ["derive"], optional = true } +bytemuck = { workspace = true, optional = true } [target.'cfg(target_arch = "spirv")'.dependencies] num-traits = { workspace = true, features = ["libm"] } diff --git a/default.nix b/default.nix index 3194b5f017f..4b907d8dd0e 100644 --- a/default.nix +++ b/default.nix @@ -12,6 +12,7 @@ in with pkgs; stdenv.mkDerivation rec { SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt"; nativeBuildInputs = [ rustup ]; + buildInputs = [ cargo-nextest ]; # Runtime dependencies (for the example runners). LD_LIBRARY_PATH = with xorg; lib.makeLibraryPath [ diff --git a/tests/difftests/README.md b/tests/difftests/README.md index 8edb58f1441..8c71bf242a6 100644 --- a/tests/difftests/README.md +++ b/tests/difftests/README.md @@ -160,6 +160,12 @@ config.write_metadata(&metadata)?; ## Running Tests +### Install cargo-nextest: + +```sh +cargo install cargo-nextest +``` + ### Run all difftests: ```sh diff --git a/tests/difftests/bin/Cargo.toml b/tests/difftests/bin/Cargo.toml index 23d47c4f27a..b40435fc1f5 100644 --- a/tests/difftests/bin/Cargo.toml +++ b/tests/difftests/bin/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "difftests" -version = "0.0.0" -publish = false +version.workspace = true authors.workspace = true edition.workspace = true license.workspace = true @@ -10,26 +9,17 @@ repository.workspace = true # See rustc_codegen_spirv/Cargo.toml for details on these features [features] default = ["use-compiled-tools"] -use-installed-tools = [] -use-compiled-tools = [] +use-installed-tools = ["difftest-runner/use-installed-tools"] +use-compiled-tools = ["difftest-runner/use-compiled-tools"] [dependencies] anyhow = "1.0" -tracing = "0.1" -tracing-subscriber = { version = "0.3.20", features = ["fmt", "env-filter"] } -tempfile = "3.5" -tester = "0.9.1" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "2.0.12" -toml = { version = "0.9.2", default-features = false, features = ["parse", "serde"] } -bytesize = "2.0.1" -bytemuck = "1.21.0" -difftest = { path = "../lib" } -tabled = { version = "0.20.0", default-features = false, features = ["std"] } +difftest-runner.workspace = true [lints] workspace = true -[package.metadata.release] -release = false +[[test]] +name = "difftests" +path = "src/test.rs" +harness = false diff --git a/tests/difftests/bin/src/main.rs b/tests/difftests/bin/src/main.rs deleted file mode 100644 index 58614d5a8b7..00000000000 --- a/tests/difftests/bin/src/main.rs +++ /dev/null @@ -1,140 +0,0 @@ -#![allow(clippy::exit)] - -use anyhow::Result; -use std::{ - env, - process::{self, Command}, -}; -use tester::{ - ColorConfig, DynTestName, OutputFormat, RunIgnored, ShouldPanic, TestDesc, TestDescAndFn, - TestFn, TestType, run_tests_console, - test::{TestOpts, parse_opts}, -}; -use tracing_subscriber::FmtSubscriber; - -mod differ; -mod runner; -use runner::Runner; - -fn main() -> Result<()> { - let subscriber = FmtSubscriber::builder() - .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) - .finish(); - tracing::subscriber::set_global_default(subscriber).expect("Failed to set global subscriber"); - - let args: Vec = env::args().collect(); - let opts: TestOpts = match parse_opts(&args) { - Some(Ok(o)) => TestOpts { - test_threads: Some(1), - ..o - }, - Some(Err(e)) => { - eprintln!("Error parsing test options: {e}"); - process::exit(1); - } - None => TestOpts { - list: false, - filters: vec![], - filter_exact: false, - force_run_in_process: false, - exclude_should_panic: false, - run_ignored: RunIgnored::No, - run_tests: true, - bench_benchmarks: false, - logfile: None, - nocapture: false, - color: ColorConfig::AutoColor, - format: OutputFormat::Pretty, - test_threads: Some(1), - skip: vec![], - time_options: None, - options: tester::Options { - display_output: true, - panic_abort: true, - }, - }, - }; - - // Find the manifest directory at compile time and locate tests in ../tests. - let manifest_dir = env!("CARGO_MANIFEST_DIR"); - let base = std::path::Path::new(manifest_dir) - .join("../tests") - .canonicalize() - .expect("Failed to canonicalize tests directory"); - tracing::debug!("Using tests directory: {}", base.display()); - - let runner = Runner::new(base.clone()); - - let test_cases = - Runner::collect_test_dirs(&base).expect("Failed to collect test case directories"); - if test_cases.is_empty() { - eprintln!("No valid tests found in {}", base.display()); - process::exit(1); - } - - // We build first to ensure that the tests are compiled before running them and to - // passthrough stdout and stderr from cargo to help debugging. - let mut cmd = Command::new("cargo"); - let cmd = cmd.arg("build").arg("--release"); - runner::forward_features(cmd); - cmd.current_dir(&base) - .stderr(process::Stdio::inherit()) - .stdout(process::Stdio::inherit()); - tracing::debug!("Running cargo command: {:?}", cmd); - - let output = cmd.output().expect("build output"); - let exit_code = output.status.code().unwrap_or(-1); - tracing::debug!("Cargo build exited with code {}", exit_code); - if !output.status.success() { - tracing::error!("Cargo build failed"); - process::exit(exit_code); - } - - let tests: Vec = test_cases - .into_iter() - .map(|case| { - let test_name = Runner::format_test_name(&case, &base); - TestDescAndFn { - desc: TestDesc { - name: DynTestName(test_name.clone()), - ignore: false, - should_panic: ShouldPanic::No, - allow_fail: false, - test_type: TestType::IntegrationTest, - }, - testfn: TestFn::DynTestFn(Box::new({ - let runner = runner.clone(); - move || { - runner - .run_test_case(&case) - .unwrap_or_else(|e| panic!("{}", e)); - } - })), - } - }) - .collect(); - - // If filters are provided that look like paths (contain '/'), convert them to test names - let opts = if opts.filters.iter().any(|f| f.contains('/')) { - let mut new_opts = opts; - new_opts.filters = new_opts - .filters - .into_iter() - .map(|filter| { - if filter.contains('/') { - // Convert path-like filter to test name format - filter.replace('/', "::") - } else { - filter - } - }) - .collect(); - new_opts - } else { - opts - }; - - let passed = run_tests_console(&opts, tests).expect("Failed to run tests"); - - process::exit(if passed { 0 } else { 1 }); -} diff --git a/tests/difftests/bin/src/test.rs b/tests/difftests/bin/src/test.rs new file mode 100644 index 00000000000..afa31a9c9ca --- /dev/null +++ b/tests/difftests/bin/src/test.rs @@ -0,0 +1,6 @@ +use anyhow::Result; +use std::process::ExitCode; + +pub fn main() -> Result { + difftest_runner::run() +} diff --git a/tests/difftests/lib/src/scaffold/compute/mod.rs b/tests/difftests/lib/src/scaffold/compute/mod.rs deleted file mode 100644 index 0c89c51837f..00000000000 --- a/tests/difftests/lib/src/scaffold/compute/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -mod ash; -mod backend; -mod wgpu; - -pub use crate::scaffold::shader::*; -pub use ash::AshBackend; -pub use backend::{BufferConfig, BufferUsage, ComputeBackend, ComputeShaderTest, ComputeTest}; -pub use wgpu::{ - WgpuBackend, WgpuComputeTest, WgpuComputeTestMultiBuffer, WgpuComputeTestPushConstants, -}; diff --git a/tests/difftests/lib/src/scaffold/compute/wgpu.rs b/tests/difftests/lib/src/scaffold/compute/wgpu.rs deleted file mode 100644 index 4996e061160..00000000000 --- a/tests/difftests/lib/src/scaffold/compute/wgpu.rs +++ /dev/null @@ -1,840 +0,0 @@ -use super::backend::{self, ComputeBackend}; -use crate::config::Config; -use crate::scaffold::shader::RustComputeShader; -use crate::scaffold::shader::WgpuShader; -use crate::scaffold::shader::WgslComputeShader; -use anyhow::Context; -use bytemuck::Pod; -use futures::executor::block_on; -use std::{borrow::Cow, sync::Arc}; -use wgpu::{ExperimentalFeatures, PipelineCompilationOptions, util::DeviceExt}; - -pub type BufferConfig = backend::BufferConfig; -pub type BufferUsage = backend::BufferUsage; - -/// Compute test that is generic over the shader type. -pub struct WgpuComputeTest { - shader: S, - dispatch: [u32; 3], - output_bytes: u64, -} - -/// More flexible compute test that supports multiple buffers. -pub struct WgpuComputeTestMultiBuffer { - shader: S, - dispatch: [u32; 3], - buffers: Vec, -} - -/// Compute test that supports push constants. -pub struct WgpuComputeTestPushConstants { - shader: S, - dispatch: [u32; 3], - buffers: Vec, - push_constants_size: u32, - push_constants_data: Vec, -} - -impl WgpuComputeTest -where - S: WgpuShader, -{ - pub fn new(shader: S, dispatch: [u32; 3], output_bytes: u64) -> Self { - Self { - shader, - dispatch, - output_bytes, - } - } - - fn init() -> anyhow::Result<(wgpu::Device, wgpu::Queue)> { - Self::init_with_features(wgpu::Features::empty()) - } - - fn init_with_features(features: wgpu::Features) -> anyhow::Result<(wgpu::Device, wgpu::Queue)> { - block_on(async { - let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { - #[cfg(target_os = "linux")] - backends: wgpu::Backends::VULKAN, - #[cfg(not(target_os = "linux"))] - backends: wgpu::Backends::PRIMARY, - flags: Default::default(), - memory_budget_thresholds: Default::default(), - backend_options: wgpu::BackendOptions { - #[cfg(target_os = "windows")] - dx12: wgpu::Dx12BackendOptions { - shader_compiler: wgpu::Dx12Compiler::StaticDxc, - ..Default::default() - }, - ..Default::default() - }, - }); - let adapter = instance - .request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::HighPerformance, - compatible_surface: None, - force_fallback_adapter: false, - }) - .await - .context("Failed to find a suitable GPU adapter")?; - let (device, queue) = adapter - .request_device(&wgpu::DeviceDescriptor { - label: Some("wgpu Device"), - #[cfg(target_os = "linux")] - required_features: wgpu::Features::EXPERIMENTAL_PASSTHROUGH_SHADERS | features, - #[cfg(not(target_os = "linux"))] - required_features: features, - required_limits: wgpu::Limits { - max_push_constant_size: 128, - ..wgpu::Limits::default() - }, - experimental_features: unsafe { ExperimentalFeatures::enabled() }, - memory_hints: Default::default(), - trace: Default::default(), - }) - .await - .context("Failed to create device")?; - Ok((device, queue)) - }) - } - - fn run_internal(self, input: Option) -> anyhow::Result> - where - I: Sized + Pod, - { - let (device, queue) = Self::init()?; - let (module, entrypoint) = self.shader.create_module(&device)?; - let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: Some("Compute Pipeline"), - layout: None, - module: &module, - entry_point: entrypoint.as_deref(), - compilation_options: PipelineCompilationOptions::default(), - cache: None, - }); - - // Create the output buffer. - let output_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Output Buffer"), - size: self.output_bytes, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, - mapped_at_creation: true, - }); - { - // Zero the buffer. - let initial_data = vec![0u8; self.output_bytes as usize]; - let mut mapping = output_buffer.slice(..).get_mapped_range_mut(); - mapping.copy_from_slice(&initial_data); - } - output_buffer.unmap(); - - // Build the bind group. - let bind_group = if let Some(input_val) = input { - let input_bytes = bytemuck::bytes_of(&input_val); - let input_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Input Buffer"), - contents: input_bytes, - usage: wgpu::BufferUsages::UNIFORM, - }); - device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &pipeline.get_bind_group_layout(0), - entries: &[ - // Binding 0: uniform input. - wgpu::BindGroupEntry { - binding: 0, - resource: input_buffer.as_entire_binding(), - }, - // Binding 1: storage output. - wgpu::BindGroupEntry { - binding: 1, - resource: output_buffer.as_entire_binding(), - }, - ], - label: Some("Compute Bind Group (with input)"), - }) - } else { - device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &pipeline.get_bind_group_layout(0), - entries: &[ - // Binding 0: storage output. - wgpu::BindGroupEntry { - binding: 0, - resource: output_buffer.as_entire_binding(), - }, - ], - label: Some("Compute Bind Group (no input)"), - }) - }; - - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Compute Encoder"), - }); - { - let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("Compute Pass"), - timestamp_writes: Default::default(), - }); - pass.set_pipeline(&pipeline); - pass.set_bind_group(0, &bind_group, &[]); - pass.dispatch_workgroups(self.dispatch[0], self.dispatch[1], self.dispatch[2]); - } - - // Create a staging buffer. - let staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Staging Buffer"), - size: self.output_bytes, - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - encoder.copy_buffer_to_buffer(&output_buffer, 0, &staging_buffer, 0, self.output_bytes); - queue.submit(Some(encoder.finish())); - - let buffer_slice = staging_buffer.slice(..); - let (sender, receiver) = futures::channel::oneshot::channel(); - buffer_slice.map_async(wgpu::MapMode::Read, move |res| { - let _ = sender.send(res); - }); - device.poll(wgpu::PollType::wait_indefinitely())?; - block_on(receiver) - .context("mapping canceled")? - .context("mapping failed")?; - let data = buffer_slice.get_mapped_range().to_vec(); - staging_buffer.unmap(); - Ok(data) - } - - /// Runs the compute shader with no input. - pub fn run(self) -> anyhow::Result> { - self.run_internal::<()>(None) - } - - /// Runs the compute shader with provided input. - pub fn run_with_input(self, input: I) -> anyhow::Result> - where - I: Sized + Pod, - { - self.run_internal(Some(input)) - } - - /// Runs the compute shader with no input and writes the output to a file. - pub fn run_test(self, config: &Config) -> anyhow::Result<()> { - let output = self.run()?; - config.write_result(&output)?; - Ok(()) - } - - /// Runs the compute shader with provided input and writes the output to a file. - pub fn run_test_with_input(self, config: &Config, input: I) -> anyhow::Result<()> - where - I: Sized + Pod, - { - let output = self.run_with_input(input)?; - config.write_result(&output)?; - Ok(()) - } -} - -/// wgpu backend implementation for the generic `ComputeBackend` trait -pub struct WgpuBackend { - device: Arc, - queue: Arc, -} - -impl ComputeBackend for WgpuBackend { - fn init() -> anyhow::Result { - let (device, queue) = WgpuComputeTest::::init()?; - Ok(Self { - device: Arc::new(device), - queue: Arc::new(queue), - }) - } - - fn run_compute( - &self, - spirv_bytes: &[u8], - entry_point: &str, - dispatch: [u32; 3], - buffers: Vec, - ) -> anyhow::Result>> { - // Convert bytes to u32 words - if !spirv_bytes.len().is_multiple_of(4) { - anyhow::bail!("SPIR-V binary length is not a multiple of 4"); - } - let spirv_words: Vec = bytemuck::cast_slice(spirv_bytes).to_vec(); - - // Create shader module - let module = self - .device - .create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Compute Shader"), - source: wgpu::ShaderSource::SpirV(Cow::Owned(spirv_words)), - }); - - // Create pipeline - let pipeline = self - .device - .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: Some("Compute Pipeline"), - layout: None, - module: &module, - entry_point: Some(entry_point), - compilation_options: PipelineCompilationOptions::default(), - cache: None, - }); - - // Create buffers - let mut gpu_buffers = Vec::new(); - for (i, buffer_config) in buffers.iter().enumerate() { - let usage = match buffer_config.usage { - BufferUsage::Storage => wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, - BufferUsage::StorageReadOnly => { - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC - } - BufferUsage::Uniform => wgpu::BufferUsages::UNIFORM, - }; - - let buffer = if let Some(initial_data) = &buffer_config.initial_data { - self.device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("Buffer {i}")), - contents: initial_data, - usage, - }) - } else { - let buffer = self.device.create_buffer(&wgpu::BufferDescriptor { - label: Some(&format!("Buffer {i}")), - size: buffer_config.size, - usage, - mapped_at_creation: true, - }); - { - // Zero the buffer - let initial_data = vec![0u8; buffer_config.size as usize]; - let mut mapping = buffer.slice(..).get_mapped_range_mut(); - mapping.copy_from_slice(&initial_data); - } - buffer.unmap(); - buffer - }; - gpu_buffers.push(buffer); - } - - // Create bind entries - let bind_entries: Vec<_> = gpu_buffers - .iter() - .enumerate() - .map(|(i, buffer)| wgpu::BindGroupEntry { - binding: i as u32, - resource: buffer.as_entire_binding(), - }) - .collect(); - - let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &pipeline.get_bind_group_layout(0), - entries: &bind_entries, - label: Some("Compute Bind Group"), - }); - - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Compute Encoder"), - }); - - { - let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("Compute Pass"), - timestamp_writes: Default::default(), - }); - pass.set_pipeline(&pipeline); - pass.set_bind_group(0, &bind_group, &[]); - pass.dispatch_workgroups(dispatch[0], dispatch[1], dispatch[2]); - } - - // Create staging buffers and copy results - let mut staging_buffers = Vec::new(); - for (i, buffer_config) in buffers.iter().enumerate() { - if matches!( - buffer_config.usage, - BufferUsage::Storage | BufferUsage::StorageReadOnly - ) { - let staging_buffer = self.device.create_buffer(&wgpu::BufferDescriptor { - label: Some(&format!("Staging Buffer {i}")), - size: buffer_config.size, - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - encoder.copy_buffer_to_buffer( - &gpu_buffers[i], - 0, - &staging_buffer, - 0, - buffer_config.size, - ); - staging_buffers.push(Some(staging_buffer)); - } else { - staging_buffers.push(None); - } - } - - self.queue.submit(Some(encoder.finish())); - - // Read back results - let mut results = Vec::new(); - for staging_buffer in staging_buffers { - if let Some(buffer) = staging_buffer { - let buffer_slice = buffer.slice(..); - let (sender, receiver) = futures::channel::oneshot::channel(); - buffer_slice.map_async(wgpu::MapMode::Read, move |res| { - let _ = sender.send(res); - }); - self.device.poll(wgpu::PollType::wait_indefinitely())?; - block_on(receiver) - .context("mapping canceled")? - .context("mapping failed")?; - let data = buffer_slice.get_mapped_range().to_vec(); - buffer.unmap(); - results.push(data); - } else { - results.push(Vec::new()); - } - } - - Ok(results) - } -} - -impl WgpuComputeTestMultiBuffer -where - S: WgpuShader, -{ - pub fn new(shader: S, dispatch: [u32; 3], buffers: Vec) -> Self { - Self { - shader, - dispatch, - buffers, - } - } - - pub fn new_with_sizes(shader: S, dispatch: [u32; 3], sizes: &[u64]) -> Self { - let buffers = sizes - .iter() - .map(|&size| BufferConfig { - size, - usage: BufferUsage::Storage, - initial_data: None, - }) - .collect(); - Self::new(shader, dispatch, buffers) - } - - pub fn run(self) -> anyhow::Result>> { - let (device, queue) = WgpuComputeTest::::init()?; - let (module, entrypoint) = self.shader.create_module(&device)?; - let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: Some("Compute Pipeline"), - layout: None, - module: &module, - entry_point: entrypoint.as_deref(), - compilation_options: PipelineCompilationOptions::default(), - cache: None, - }); - - // Create buffers. - let mut gpu_buffers = Vec::new(); - - for (i, buffer_config) in self.buffers.iter().enumerate() { - let usage = match buffer_config.usage { - BufferUsage::Storage => wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, - BufferUsage::StorageReadOnly => { - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC - } - BufferUsage::Uniform => wgpu::BufferUsages::UNIFORM, - }; - - let buffer = if let Some(initial_data) = &buffer_config.initial_data { - device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("Buffer {i}")), - contents: initial_data, - usage, - }) - } else { - let buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some(&format!("Buffer {i}")), - size: buffer_config.size, - usage, - mapped_at_creation: true, - }); - { - // Zero the buffer. - let initial_data = vec![0u8; buffer_config.size as usize]; - let mut mapping = buffer.slice(..).get_mapped_range_mut(); - mapping.copy_from_slice(&initial_data); - } - buffer.unmap(); - buffer - }; - - gpu_buffers.push(buffer); - } - - // Create bind entries after all buffers are created - let bind_entries: Vec<_> = gpu_buffers - .iter() - .enumerate() - .map(|(i, buffer)| wgpu::BindGroupEntry { - binding: i as u32, - resource: buffer.as_entire_binding(), - }) - .collect(); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &pipeline.get_bind_group_layout(0), - entries: &bind_entries, - label: Some("Compute Bind Group"), - }); - - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Compute Encoder"), - }); - { - let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("Compute Pass"), - timestamp_writes: Default::default(), - }); - pass.set_pipeline(&pipeline); - pass.set_bind_group(0, &bind_group, &[]); - pass.dispatch_workgroups(self.dispatch[0], self.dispatch[1], self.dispatch[2]); - } - - // Create staging buffers and copy results. - let mut staging_buffers = Vec::new(); - for (i, buffer_config) in self.buffers.iter().enumerate() { - if matches!( - buffer_config.usage, - BufferUsage::Storage | BufferUsage::StorageReadOnly - ) { - let staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some(&format!("Staging Buffer {i}")), - size: buffer_config.size, - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - encoder.copy_buffer_to_buffer( - &gpu_buffers[i], - 0, - &staging_buffer, - 0, - buffer_config.size, - ); - staging_buffers.push(Some(staging_buffer)); - } else { - staging_buffers.push(None); - } - } - - queue.submit(Some(encoder.finish())); - - // Read back results. - let mut results = Vec::new(); - for staging_buffer in staging_buffers { - if let Some(buffer) = staging_buffer { - let buffer_slice = buffer.slice(..); - let (sender, receiver) = futures::channel::oneshot::channel(); - buffer_slice.map_async(wgpu::MapMode::Read, move |res| { - let _ = sender.send(res); - }); - device.poll(wgpu::PollType::wait_indefinitely())?; - block_on(receiver) - .context("mapping canceled")? - .context("mapping failed")?; - let data = buffer_slice.get_mapped_range().to_vec(); - buffer.unmap(); - results.push(data); - } else { - results.push(Vec::new()); - } - } - - Ok(results) - } - - pub fn run_test(self, config: &Config) -> anyhow::Result<()> { - let buffers = self.buffers.clone(); - let outputs = self.run()?; - // Write the first storage buffer output to the file. - for (output, buffer_config) in outputs.iter().zip(&buffers) { - if matches!(buffer_config.usage, BufferUsage::Storage) && !output.is_empty() { - config.write_result(output)?; - return Ok(()); - } - } - anyhow::bail!("No storage buffer output found") - } -} - -impl WgpuComputeTestPushConstants -where - S: WgpuShader, -{ - pub fn new( - shader: S, - dispatch: [u32; 3], - buffers: Vec, - push_constants_size: u32, - push_constants_data: Vec, - ) -> Self { - Self { - shader, - dispatch, - buffers, - push_constants_size, - push_constants_data, - } - } - - pub fn run(self) -> anyhow::Result>> { - let (device, queue) = - WgpuComputeTest::::init_with_features(wgpu::Features::PUSH_CONSTANTS)?; - let (module, entrypoint) = self.shader.create_module(&device)?; - - // Create pipeline layout with push constants - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Bind Group Layout"), - entries: &self - .buffers - .iter() - .enumerate() - .map(|(i, buffer_config)| wgpu::BindGroupLayoutEntry { - binding: i as u32, - visibility: wgpu::ShaderStages::COMPUTE, - ty: match buffer_config.usage { - BufferUsage::Storage => wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: false }, - has_dynamic_offset: false, - min_binding_size: None, - }, - BufferUsage::StorageReadOnly => wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - BufferUsage::Uniform => wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - }, - count: None, - }) - .collect::>(), - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Pipeline Layout"), - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[wgpu::PushConstantRange { - stages: wgpu::ShaderStages::COMPUTE, - range: 0..self.push_constants_size, - }], - }); - - let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: Some("Compute Pipeline"), - layout: Some(&pipeline_layout), - module: &module, - entry_point: entrypoint.as_deref(), - compilation_options: PipelineCompilationOptions::default(), - cache: None, - }); - - // Create buffers. - let mut gpu_buffers = Vec::new(); - - for (i, buffer_config) in self.buffers.iter().enumerate() { - let usage = match buffer_config.usage { - BufferUsage::Storage => wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, - BufferUsage::StorageReadOnly => { - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC - } - BufferUsage::Uniform => wgpu::BufferUsages::UNIFORM, - }; - - let buffer = if let Some(initial_data) = &buffer_config.initial_data { - device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("Buffer {i}")), - contents: initial_data, - usage, - }) - } else { - let buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some(&format!("Buffer {i}")), - size: buffer_config.size, - usage, - mapped_at_creation: true, - }); - { - // Zero the buffer. - let initial_data = vec![0u8; buffer_config.size as usize]; - let mut mapping = buffer.slice(..).get_mapped_range_mut(); - mapping.copy_from_slice(&initial_data); - } - buffer.unmap(); - buffer - }; - - gpu_buffers.push(buffer); - } - - // Create bind entries after all buffers are created - let bind_entries: Vec<_> = gpu_buffers - .iter() - .enumerate() - .map(|(i, buffer)| wgpu::BindGroupEntry { - binding: i as u32, - resource: buffer.as_entire_binding(), - }) - .collect(); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &bind_group_layout, - entries: &bind_entries, - label: Some("Compute Bind Group"), - }); - - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Compute Encoder"), - }); - { - let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("Compute Pass"), - timestamp_writes: Default::default(), - }); - pass.set_pipeline(&pipeline); - pass.set_bind_group(0, &bind_group, &[]); - pass.set_push_constants(0, &self.push_constants_data); - pass.dispatch_workgroups(self.dispatch[0], self.dispatch[1], self.dispatch[2]); - } - - // Create staging buffers and copy results. - let mut staging_buffers = Vec::new(); - for (i, buffer_config) in self.buffers.iter().enumerate() { - if matches!( - buffer_config.usage, - BufferUsage::Storage | BufferUsage::StorageReadOnly - ) { - let staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some(&format!("Staging Buffer {i}")), - size: buffer_config.size, - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - encoder.copy_buffer_to_buffer( - &gpu_buffers[i], - 0, - &staging_buffer, - 0, - buffer_config.size, - ); - staging_buffers.push(Some(staging_buffer)); - } else { - staging_buffers.push(None); - } - } - - queue.submit(Some(encoder.finish())); - - // Read back results. - let mut results = Vec::new(); - for staging_buffer in staging_buffers { - if let Some(buffer) = staging_buffer { - let buffer_slice = buffer.slice(..); - let (sender, receiver) = futures::channel::oneshot::channel(); - buffer_slice.map_async(wgpu::MapMode::Read, move |res| { - let _ = sender.send(res); - }); - device.poll(wgpu::PollType::wait_indefinitely())?; - block_on(receiver) - .context("mapping canceled")? - .context("mapping failed")?; - let data = buffer_slice.get_mapped_range().to_vec(); - buffer.unmap(); - results.push(data); - } else { - results.push(Vec::new()); - } - } - - Ok(results) - } - - pub fn run_test(self, config: &Config) -> anyhow::Result<()> { - let buffers = self.buffers.clone(); - let results = self.run()?; - // Write first storage buffer output to file. - for (data, buffer_config) in results.iter().zip(&buffers) { - if buffer_config.usage == BufferUsage::Storage && !data.is_empty() { - config.write_result(data)?; - return Ok(()); - } - } - anyhow::bail!("No storage buffer output found") - } -} - -// Convenience implementation for WgpuComputeTestPushConstants -impl WgpuComputeTestPushConstants { - pub fn new_with_data( - shader: RustComputeShader, - dispatch: [u32; 3], - sizes: &[u64], - push_constant_data: &T, - ) -> Self { - let buffers = sizes - .iter() - .map(|&size| BufferConfig { - size, - usage: BufferUsage::Storage, - initial_data: None, - }) - .collect(); - let push_constants_data = bytemuck::bytes_of(push_constant_data).to_vec(); - let push_constants_size = push_constants_data.len() as u32; - - Self::new( - shader, - dispatch, - buffers, - push_constants_size, - push_constants_data, - ) - } -} - -impl WgpuComputeTestPushConstants { - pub fn new_with_data( - shader: WgslComputeShader, - dispatch: [u32; 3], - sizes: &[u64], - push_constant_data: &T, - ) -> Self { - let buffers = sizes - .iter() - .map(|&size| BufferConfig { - size, - usage: BufferUsage::Storage, - initial_data: None, - }) - .collect(); - let push_constants_data = bytemuck::bytes_of(push_constant_data).to_vec(); - let push_constants_size = push_constants_data.len() as u32; - - Self::new( - shader, - dispatch, - buffers, - push_constants_size, - push_constants_data, - ) - } -} diff --git a/tests/difftests/runner/Cargo.toml b/tests/difftests/runner/Cargo.toml new file mode 100644 index 00000000000..0697af18209 --- /dev/null +++ b/tests/difftests/runner/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "difftest-runner" +version = "0.0.0" +publish = false +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +# See rustc_codegen_spirv/Cargo.toml for details on these features +[features] +default = ["use-compiled-tools"] +use-installed-tools = [] +use-compiled-tools = [] + +[dependencies] +anyhow = "1.0" +tracing = "0.1" +tracing-subscriber = { version = "0.3.20", features = ["fmt", "env-filter"] } +libtest-mimic = "0.8.1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "2.0.12" +toml = { version = "0.9.2", default-features = false, features = ["parse", "serde"] } +bytesize = "2.0.1" +bytemuck = "1.21.0" +difftest-types = { path = "../types" } +tabled = { version = "0.20.0", default-features = false, features = ["std"] } + +[dev-dependencies] +tempfile = "3.5" + +[lints] +workspace = true + +[package.metadata.release] +release = false diff --git a/tests/difftests/bin/src/differ.rs b/tests/difftests/runner/src/differ.rs similarity index 99% rename from tests/difftests/bin/src/differ.rs rename to tests/difftests/runner/src/differ.rs index 62de8983f90..592046dd175 100644 --- a/tests/difftests/bin/src/differ.rs +++ b/tests/difftests/runner/src/differ.rs @@ -1,6 +1,6 @@ #![allow(clippy::unimplemented)] -use difftest::config::OutputType; +use difftest_types::config::OutputType; use std::marker::PhantomData; /// Represents the magnitude of a difference between two values diff --git a/tests/difftests/runner/src/lib.rs b/tests/difftests/runner/src/lib.rs new file mode 100644 index 00000000000..dab1cae295d --- /dev/null +++ b/tests/difftests/runner/src/lib.rs @@ -0,0 +1,75 @@ +#![allow(clippy::exit)] + +use crate::testcase::collect_test_dirs; +use anyhow::Result; +use libtest_mimic::{Arguments, Trial}; +use runner::Runner; +use std::process::ExitCode; +use std::sync::Arc; +use std::{ + env, fs, + process::{self}, +}; +use tracing_subscriber::FmtSubscriber; + +mod differ; +mod runner; +mod testcase; + +pub fn run() -> Result { + let mut args = Arguments::from_args(); + + // list must not print anything else to stdout + if !args.list { + let subscriber = FmtSubscriber::builder() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .finish(); + tracing::subscriber::set_global_default(subscriber) + .expect("Failed to set global subscriber"); + } + + // If filters are provided that look like paths (contain '/'), convert them to test names + if let Some(filter) = &mut args.filter { + *filter = filter.replace('/', "::"); + } + + let tests = collect_tests()?; + Ok(libtest_mimic::run(&args, tests).exit_code()) +} + +fn collect_tests() -> Result> { + // Find the manifest directory at compile time and locate tests in ../tests. + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let base = std::path::Path::new(manifest_dir) + .join("../tests") + .canonicalize() + .expect("Failed to canonicalize tests directory"); + tracing::debug!("Using tests directory: {}", base.display()); + + let output_dir = std::path::Path::new(manifest_dir).join("../tests/target/difftest"); + fs::create_dir_all(&output_dir)?; + let output_dir = output_dir + .canonicalize() + .expect("Failed to canonicalize tests directory"); + tracing::debug!("Using output directory: {}", output_dir.display()); + + let runner = Arc::new(Runner { + base_dir: base.clone(), + output_dir, + }); + + let test_cases = collect_test_dirs(&base).expect("Failed to collect test case directories"); + if test_cases.is_empty() { + eprintln!("No valid tests found in {}", base.display()); + process::exit(1); + } + + let trails = test_cases + .into_iter() + .map(|case| { + let runner = runner.clone(); + Trial::test(case.to_string(), move || Ok(runner.run_test_case(&case)?)) + }) + .collect(); + Ok(trails) +} diff --git a/tests/difftests/bin/src/runner.rs b/tests/difftests/runner/src/runner.rs similarity index 80% rename from tests/difftests/bin/src/runner.rs rename to tests/difftests/runner/src/runner.rs index 350f75d6194..25711e28a6c 100644 --- a/tests/difftests/bin/src/runner.rs +++ b/tests/difftests/runner/src/runner.rs @@ -1,18 +1,17 @@ use bytesize::ByteSize; -use difftest::config::{OutputType, TestMetadata}; +use difftest_types::config::{OutputType, TestMetadata}; use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, fs, - io::Write, - path::{Component, Path, PathBuf}, + path::{Path, PathBuf}, process::Command, }; -use tempfile::NamedTempFile; use thiserror::Error; use tracing::{debug, error, info, trace}; use crate::differ::{DiffMagnitude, Difference, DifferenceDisplay, OutputDiffer}; +use crate::testcase::TestCase; #[derive(Debug, Error)] pub enum RunnerError { @@ -214,65 +213,54 @@ impl ErrorReport { #[derive(Clone)] pub struct Runner { pub base_dir: PathBuf, + pub output_dir: PathBuf, } impl Runner { - pub fn new(base_dir: PathBuf) -> Self { - Self { base_dir } - } - - pub fn run_test_case(&self, test_case: &Path) -> RunnerResult<()> { - trace!("Starting test case: {}", test_case.display()); - let packages = self.collect_packages(test_case)?; + pub fn run_test_case(&self, test_case: &TestCase) -> RunnerResult<()> { + trace!("Starting test case: {}", test_case); debug!( "Found {} package(s) in test case {}", - packages.len(), - test_case.display() + test_case.test_binaries.len(), + test_case ); - if packages.len() < 2 { - error!("Insufficient packages in test case {}", test_case.display()); + if test_case.test_binaries.len() < 2 { + error!("Insufficient packages in test case {}", test_case); return Err(RunnerError::InsufficientPackages { - count: packages.len(), + count: test_case.test_binaries.len(), }); } // Pre-check that package names are globally unique. let mut names_seen = HashSet::new(); - for package in &packages { - let manifest_path = package.join("Cargo.toml"); + for package in &test_case.test_binaries { + let manifest_path = package.absolute_path.join("Cargo.toml"); let pkg_name = self.get_package_name(&manifest_path)?; if !names_seen.insert(pkg_name.clone()) { return Err(RunnerError::DuplicatePackageName { pkg_name }); } } - let mut temp_files: Vec = Vec::with_capacity(packages.len()); - let mut pkg_outputs: Vec = Vec::with_capacity(packages.len()); + let mut pkg_outputs: Vec = Vec::with_capacity(test_case.test_binaries.len()); let mut epsilon: Option = None; let mut output_type = None; - for package in packages { - trace!("Processing package at {}", package.display()); - let manifest_path = package.join("Cargo.toml"); + for package in &test_case.test_binaries { + trace!( + "Processing package '{}' at '{}'", + package, + package.absolute_path.display() + ); + let manifest_path = package.absolute_path.join("Cargo.toml"); let pkg_name = self.get_package_name(&manifest_path)?; debug!("Package '{}' detected", pkg_name); - let output_file = NamedTempFile::new()?; - let temp_output_path = output_file.path().to_path_buf(); - temp_files.push(output_file); - - let metadata_file = NamedTempFile::new()?; - let temp_metadata_path = metadata_file.path().to_path_buf(); - temp_files.push(metadata_file); - - trace!( - "Temporary output file created at {}", - temp_output_path.display() - ); - trace!( - "Temporary metadata file created at {}", - temp_metadata_path.display() - ); + let package_out = self.output_dir.join(&package.relative_path); + fs::create_dir_all(&package_out)?; + debug!("Writing output to '{}'", package_out.display()); + let config_file = package_out.join("config.json"); + let temp_output_path = package_out.join("out.bin"); + let temp_metadata_path = package_out.join("metadata.json"); let config = HarnessConfig { output_path: temp_output_path.clone(), @@ -280,9 +268,11 @@ impl Runner { }; let config_json = serde_json::to_string(&config) .map_err(|e| RunnerError::Config { msg: e.to_string() })?; - let mut config_file = NamedTempFile::new()?; - write!(config_file, "{config_json}").map_err(|e| RunnerError::Io { source: e })?; - trace!("Config file created at {}", config_file.path().display()); + fs::write(&config_file, &config_json)?; + trace!("Config file created at {}", config_file.display()); + + fs::write(&temp_output_path, [])?; + trace!("Output file created at {}", temp_output_path.display()); let mut cmd = Command::new("cargo"); cmd.arg("run").arg("--release").arg("--manifest-path").arg( @@ -293,18 +283,14 @@ impl Runner { })?, ); forward_features(&mut cmd); - cmd.arg("--").arg( - config_file - .path() - .to_str() - .ok_or_else(|| RunnerError::Config { - msg: "Invalid config file path".into(), - })?, - ); + cmd.arg("--") + .arg(config_file.to_str().ok_or_else(|| RunnerError::Config { + msg: "Invalid config file path".into(), + })?); debug!("Running cargo command: {:?}", cmd); let output = cmd - .current_dir(&package) + .current_dir(&package.absolute_path) .output() .map_err(|e| RunnerError::Io { source: e })?; let exit_code = output.status.code().unwrap_or(-1); @@ -317,7 +303,7 @@ impl Runner { error!("Cargo execution failed for package '{}'", pkg_name); return Err(RunnerError::CargoExecutionFailed { pkg_name, - package_path: package, + package_path: package.absolute_path.clone(), exit_status: exit_code, stderr: stderr_str, }); @@ -388,7 +374,7 @@ impl Runner { pkg_outputs.push(PackageOutput { pkg_name, - package_path: package, + package_path: package.absolute_path.clone(), output: output_bytes, temp_path: temp_output_path, }); @@ -424,30 +410,12 @@ impl Runner { // Generate detailed error report let details = self.format_error(&pkg_outputs, epsilon, output_type, &*differ, &*display); - self.keep_temp_files(&mut temp_files); return Err(RunnerError::DifferingOutput(details)); } - info!( - "Test case '{}' passed.", - Runner::format_test_name(test_case, test_case.parent().unwrap_or(test_case)) - ); + info!("Test case '{test_case}' passed."); Ok(()) } - #[allow(clippy::unused_self)] - fn collect_packages(&self, test_case: &Path) -> RunnerResult> { - let mut packages = Vec::new(); - for entry in fs::read_dir(test_case)? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() && path.join("Cargo.toml").exists() { - debug!("Found package candidate: {}", path.display()); - packages.push(path); - } - } - Ok(packages) - } - #[allow(clippy::unused_self)] fn group_outputs<'a>( &self, @@ -665,64 +633,14 @@ impl Runner { debug!("Package name '{}' found in manifest", manifest.package.name); Ok(manifest.package.name) } - - #[allow(clippy::unused_self)] - fn keep_temp_files(&self, temp_files: &mut Vec) { - for file in temp_files.drain(..) { - let _ = file.into_temp_path().keep(); - } - } - - pub fn format_test_name(test_case: &Path, base: &Path) -> String { - let name = test_case.strip_prefix(base).map_or_else( - |_| test_case.to_string_lossy().into_owned(), - |relative| { - relative - .components() - .filter_map(|comp| match comp { - Component::Normal(os_str) => Some(os_str.to_string_lossy()), - _ => None, - }) - .collect::>() - .join("::") - }, - ); - format!("difftests::{name}") - } - - pub fn collect_test_dirs(root: &Path) -> RunnerResult> { - let mut test_cases = Vec::new(); - for entry in fs::read_dir(root)? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() { - if Runner::collect_test_binaries(&path)?.len() >= 2 { - debug!("Test case found: {}", path.display()); - test_cases.push(path.clone()); - } - let mut subdirs = Runner::collect_test_dirs(&path)?; - test_cases.append(&mut subdirs); - } - } - Ok(test_cases) - } - - fn collect_test_binaries(test_case: &Path) -> RunnerResult> { - let mut packages = Vec::new(); - for entry in fs::read_dir(test_case)? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() && path.join("Cargo.toml").exists() { - debug!("Found binary package candidate: {}", path.display()); - packages.push(path); - } - } - Ok(packages) - } } pub fn forward_features(cmd: &mut Command) { cmd.arg("--features"); + #[cfg(all(feature = "use-compiled-tools", feature = "use-installed-tools"))] + compile_error!( + "Features `use-compiled-tools` and `use-installed-tools` are mutually exclusive" + ); #[cfg(feature = "use-compiled-tools")] { cmd.arg("difftest/use-compiled-tools"); @@ -736,10 +654,17 @@ pub fn forward_features(cmd: &mut Command) { #[cfg(test)] mod tests { use super::*; - use difftest::config::OutputType; + use difftest_types::config::OutputType; use std::{fs, io::Write, path::Path, path::PathBuf}; use tempfile::{NamedTempFile, tempdir}; + fn dummy_runner() -> Runner { + Runner { + base_dir: PathBuf::from("dummy_base"), + output_dir: PathBuf::from("dummy_out"), + } + } + fn dummy_package_output(name: &str, path: &str, output: &[u8], temp: &str) -> PackageOutput { PackageOutput { pkg_name: name.to_string(), @@ -755,19 +680,11 @@ mod tests { let pkg2 = dummy_package_output("bar", "/path/to/bar", b"world", "tmp2"); let pkg3 = dummy_package_output("baz", "/path/to/baz", b"hello", "tmp3"); let outputs = vec![pkg1, pkg2, pkg3]; - let runner = Runner::new(PathBuf::from("dummy_base")); + let runner = dummy_runner(); let groups = runner.group_outputs(&outputs, None, OutputType::Raw); assert_eq!(groups.len(), 2); } - #[test] - fn test_format_test_name() { - let base = Path::new("/home/user/tests"); - let test_case = base.join("group1/testcase1"); - let formatted = Runner::format_test_name(&test_case, base); - assert_eq!(formatted, "difftests::group1::testcase1"); - } - #[test] fn test_get_package_name() { let mut temp = NamedTempFile::new().expect("failed to create temp file"); @@ -778,47 +695,13 @@ mod tests { edition = "2021" "#; write!(temp, "{cargo_toml}").expect("failed to write to temp file"); - let runner = Runner::new(PathBuf::from("dummy_base")); + let runner = dummy_runner(); let pkg_name = runner .get_package_name(temp.path()) .expect("failed to get package name"); assert_eq!(pkg_name, "dummy_package"); } - #[test] - fn test_collect_packages() { - let temp_dir = tempdir().expect("failed to create temp dir"); - let dir_path = temp_dir.path(); - let pkg_dir = dir_path.join("pkg1"); - fs::create_dir(&pkg_dir).expect("failed to create pkg1 dir"); - fs::write(pkg_dir.join("Cargo.toml"), "[package]\nname = \"pkg1\"") - .expect("failed to write Cargo.toml"); - let runner = Runner::new(PathBuf::from("dummy_base")); - let packages = runner - .collect_packages(dir_path) - .expect("failed to collect packages"); - assert_eq!(packages.len(), 1); - assert_eq!(packages[0], pkg_dir); - } - - #[test] - fn test_collect_test_dirs() { - let temp_dir = tempdir().expect("failed to create temp dir"); - let base = temp_dir.path(); - let test_case_dir = base.join("test_case"); - fs::create_dir(&test_case_dir).expect("failed to create test_case dir"); - let pkg1_dir = test_case_dir.join("pkg1"); - fs::create_dir(&pkg1_dir).expect("failed to create pkg1"); - fs::write(pkg1_dir.join("Cargo.toml"), "[package]\nname = \"pkg1\"") - .expect("failed to write Cargo.toml for pkg1"); - let pkg2_dir = test_case_dir.join("pkg2"); - fs::create_dir(&pkg2_dir).expect("failed to create pkg2"); - fs::write(pkg2_dir.join("Cargo.toml"), "[package]\nname = \"pkg2\"") - .expect("failed to write Cargo.toml for pkg2"); - let test_dirs = Runner::collect_test_dirs(base).expect("failed to collect test dirs"); - assert!(test_dirs.contains(&test_case_dir)); - } - #[test] fn test_run_test_case_insufficient_packages() { let temp_dir = tempdir().expect("failed to create temp dir"); @@ -828,8 +711,10 @@ mod tests { fs::create_dir(&pkg_dir).expect("failed to create pkg1"); fs::write(pkg_dir.join("Cargo.toml"), "[package]\nname = \"pkg1\"") .expect("failed to write Cargo.toml for pkg1"); - let runner = Runner::new(PathBuf::from("dummy_base")); - let result = runner.run_test_case(&test_case_dir); + let test_case = TestCase::try_new(temp_dir.path(), Path::new("single_pkg")) + .unwrap() + .unwrap(); + let result = dummy_runner().run_test_case(&test_case); match result { Err(RunnerError::InsufficientPackages { count }) => assert_eq!(count, 1), _ => panic!("Expected InsufficientPackages error"), @@ -849,8 +734,11 @@ mod tests { fs::create_dir(&pkg2_dir).expect("failed to create pkg2"); fs::write(pkg2_dir.join("Cargo.toml"), "[package]\nname = \"dup_pkg\"") .expect("failed to write Cargo.toml for pkg2"); - let runner = Runner::new(PathBuf::from("dummy_base")); - let result = runner.run_test_case(&test_case_dir); + let runner = dummy_runner(); + let test_case = TestCase::try_new(temp_dir.path(), Path::new("dup_pkg")) + .unwrap() + .unwrap(); + let result = runner.run_test_case(&test_case); match result { Err(RunnerError::DuplicatePackageName { pkg_name }) => assert_eq!(pkg_name, "dup_pkg"), _ => panic!("Expected DuplicatePackageName error"), @@ -948,7 +836,7 @@ mod tests { #[test] fn test_group_outputs_with_epsilon() { - let runner = Runner::new(PathBuf::from("dummy_base")); + let runner = dummy_runner(); // Create float outputs with small differences let val1: f32 = 1.0; @@ -977,8 +865,7 @@ mod tests { fn test_invalid_metadata_json() { // Test that invalid JSON in metadata file causes proper error let metadata_content = "{ invalid json }"; - let result: Result = - serde_json::from_str(metadata_content); + let result: Result = serde_json::from_str(metadata_content); assert!(result.is_err()); // Just check that it's an error, don't check the specific message } diff --git a/tests/difftests/runner/src/testcase.rs b/tests/difftests/runner/src/testcase.rs new file mode 100644 index 00000000000..fb5b4eb9e88 --- /dev/null +++ b/tests/difftests/runner/src/testcase.rs @@ -0,0 +1,192 @@ +use crate::runner::RunnerResult; +use std::ffi::OsStr; +use std::fmt::{Display, Formatter}; +use std::fs; +use std::path::{Path, PathBuf}; +use tracing::debug; + +/// A test case containing multiple test binaries that should produce the same output +pub struct TestCase { + /// The name of the testcase, as a rust mod path + pub name: String, + /// The relative path from the base `difftest` dir + pub relative_path: PathBuf, + /// The absolute path + pub absolute_path: PathBuf, + /// All the test binaries of this single test case. + pub test_binaries: Vec, +} + +fn path_to_test_name(path: &Path) -> String { + path.to_string_lossy() + .replace("/", "::") + .replace("\\\\", "::") +} + +impl TestCase { + pub fn new_empty(root: &Path, relative_path: &Path) -> Self { + TestCase { + name: format!("difftests::{}", path_to_test_name(relative_path)), + absolute_path: root.join(relative_path), + relative_path: relative_path.to_path_buf(), + test_binaries: Vec::new(), + } + } + + pub fn try_new(root: &Path, relative_path: &Path) -> RunnerResult> { + let mut test_case = Self::new_empty(root, relative_path); + test_case.collect_test_binaries()?; + if !test_case.test_binaries.is_empty() { + debug!("Test case found: {}", relative_path.display()); + Ok(Some(test_case)) + } else { + Ok(None) + } + } + + fn collect_test_binaries(&mut self) -> RunnerResult<()> { + for entry in fs::read_dir(&self.absolute_path)? { + let entry = entry?; + let path = entry.path(); + let relative_path = self.relative_path.join(entry.file_name()); + if IGNORE_DIR_LIST + .iter() + .any(|dir| relative_path.as_os_str() == OsStr::new(dir)) + { + debug!("Ignoring test binary: {}", path.display()); + return Ok(()); + } + if path.is_dir() && path.join("Cargo.toml").exists() { + debug!("Found binary package candidate: {}", path.display()); + self.test_binaries + .push(TestBinary::new(self, Path::new(&entry.file_name()))); + } + } + Ok(()) + } +} + +impl Display for TestCase { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.name) + } +} + +/// A test binary that can be executed +pub struct TestBinary { + /// The name of the testcase, as a rust mod path + pub name: String, + /// the relative path from the base `difftest` dir + pub relative_path: PathBuf, + /// the absolute path + pub absolute_path: PathBuf, +} + +impl TestBinary { + pub fn new(test_case: &TestCase, relative_to_test_case: &Path) -> Self { + Self { + name: format!( + "{}::{}", + test_case.name, + path_to_test_name(relative_to_test_case) + ), + relative_path: test_case.relative_path.join(relative_to_test_case), + absolute_path: test_case.absolute_path.join(relative_to_test_case), + } + } +} + +impl Display for TestBinary { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.name) + } +} + +/// List of paths relative to `./tests/difftests/tests/` to ignore +pub const IGNORE_DIR_LIST: &[&str] = &["target", "lib"]; + +pub fn collect_test_dirs(root: &Path) -> RunnerResult> { + fn recurse(root: &Path, traverse: &Path, test_cases: &mut Vec) -> RunnerResult<()> { + let absolute_path = root.join(traverse); + if IGNORE_DIR_LIST + .iter() + .any(|dir| traverse.as_os_str() == OsStr::new(dir)) + { + debug!("Ignoring path: {}", absolute_path.display()); + return Ok(()); + } + + if let Some(test_case) = TestCase::try_new(root, traverse)? { + test_cases.push(test_case); + } + for entry in fs::read_dir(absolute_path)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + let relative_path = traverse.join(entry.file_name()); + recurse(root, &relative_path, test_cases)?; + } + } + Ok(()) + } + + let mut test_cases = Vec::new(); + recurse(root, Path::new(""), &mut test_cases)?; + Ok(test_cases) +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + + #[test] + fn test_format_test_name() { + let mut test_case = + TestCase::new_empty(Path::new("/home/user/tests"), Path::new("core/group1")); + test_case + .test_binaries + .push(TestBinary::new(&test_case, Path::new("testcase1"))); + assert_eq!(test_case.to_string(), "difftests::core::group1"); + assert_eq!( + test_case.test_binaries[0].to_string(), + "difftests::core::group1::testcase1" + ); + } + + #[test] + fn test_collect_test_dirs() { + let temp_dir = tempdir().expect("failed to create temp dir"); + let base = temp_dir.path(); + let test_case_dir = base.join("test_case"); + fs::create_dir(&test_case_dir).expect("failed to create test_case dir"); + let pkg1_dir = test_case_dir.join("pkg1"); + fs::create_dir(&pkg1_dir).expect("failed to create pkg1"); + fs::write(pkg1_dir.join("Cargo.toml"), "[package]\nname = \"pkg1\"") + .expect("failed to write Cargo.toml for pkg1"); + let pkg2_dir = test_case_dir.join("pkg2"); + fs::create_dir(&pkg2_dir).expect("failed to create pkg2"); + fs::write(pkg2_dir.join("Cargo.toml"), "[package]\nname = \"pkg2\"") + .expect("failed to write Cargo.toml for pkg2"); + let mut test_dirs = collect_test_dirs(base).expect("failed to collect test dirs"); + + assert_eq!(test_dirs.len(), 1); + let test_case = &mut test_dirs[0]; + assert_eq!(test_case.relative_path.to_string_lossy(), "test_case"); + assert_eq!(test_case.absolute_path, test_case_dir); + + test_case + .test_binaries + .sort_by(|a, b| a.relative_path.cmp(&b.relative_path)); + assert_eq!( + test_case.test_binaries[0].relative_path, + Path::new("test_case/pkg1") + ); + assert_eq!(test_case.test_binaries[0].absolute_path, pkg1_dir); + assert_eq!( + test_case.test_binaries[1].relative_path, + Path::new("test_case/pkg2") + ); + assert_eq!(test_case.test_binaries[1].absolute_path, pkg2_dir); + } +} diff --git a/tests/difftests/tests/Cargo.lock b/tests/difftests/tests/Cargo.lock index a97fdaebd8e..d5a7f050482 100644 --- a/tests/difftests/tests/Cargo.lock +++ b/tests/difftests/tests/Cargo.lock @@ -52,6 +52,40 @@ dependencies = [ "difftest", ] +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -67,6 +101,12 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "ar" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69" + [[package]] name = "array_access-rust" version = "0.0.0" @@ -264,6 +304,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cc" +version = "1.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -374,6 +426,12 @@ dependencies = [ "difftest", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.10.1" @@ -401,12 +459,34 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crunchy" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "difftest" version = "0.0.0" @@ -414,6 +494,7 @@ dependencies = [ "anyhow", "ash", "bytemuck", + "difftest-types", "futures", "serde", "serde_json", @@ -422,6 +503,16 @@ dependencies = [ "wgpu", ] +[[package]] +name = "difftest-types" +version = "0.9.0" +dependencies = [ + "anyhow", + "bytemuck", + "serde", + "serde_json", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -442,6 +533,22 @@ dependencies = [ "litrs", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elsa" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9abf33c656a7256451ebb7d0082c5a471820c31269e49d807c538c252352186e" +dependencies = [ + "indexmap", + "stable_deref_trait", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -469,18 +576,40 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + [[package]] name = "fixedbitset" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "foldhash" version = "0.1.5" @@ -630,6 +759,17 @@ dependencies = [ "wasip2", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + [[package]] name = "glam" version = "0.30.9" @@ -696,6 +836,8 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ + "allocator-api2", + "equivalent", "foldhash 0.1.5", ] @@ -826,12 +968,46 @@ dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "internal-iterator" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969ee3fc68ec2e88eb21434ce4d9b7e1600d1ce92ff974560a6c4a304f5124b9" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + [[package]] name = "js-sys" version = "0.3.83" @@ -842,6 +1018,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.178" @@ -897,6 +1079,12 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "longest-increasing-subsequence" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86" + [[package]] name = "malloc_buf" version = "0.0.6" @@ -906,6 +1094,15 @@ dependencies = [ "libc", ] +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "math_ops-rust" version = "0.0.0" @@ -976,6 +1173,16 @@ dependencies = [ "paste", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "naga" version = "27.0.3" @@ -1003,6 +1210,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1043,6 +1259,33 @@ dependencies = [ "malloc_buf", ] +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "crc32fast", + "flate2", + "hashbrown 0.15.5", + "indexmap", + "memchr", + "ruzstd", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "crc32fast", + "hashbrown 0.15.5", + "indexmap", + "memchr", + "wasmparser", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1217,6 +1460,35 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -1233,12 +1505,48 @@ dependencies = [ "spirv", ] +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_codegen_spirv" +version = "0.9.0" +dependencies = [ + "ahash", + "ar", + "bytemuck", + "either", + "indexmap", + "itertools 0.14.0", + "lazy_static", + "libc", + "log", + "object 0.37.3", + "regex", + "rspirv", + "rustc-demangle", + "rustc_codegen_spirv-types", + "rustix", + "sanitize-filename", + "smallvec", + "spirt", + "spirv-std-types", + "spirv-tools", + "thorin-dwp", + "tracing", + "tracing-subscriber", + "tracing-tree", +] + [[package]] name = "rustc_codegen_spirv-types" version = "0.9.0" @@ -1251,6 +1559,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.2" @@ -1270,12 +1587,30 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ruzstd" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f" +dependencies = [ + "twox-hash", +] + [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "sanitize-filename" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" +dependencies = [ + "regex", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1366,6 +1701,27 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "simple-compute-rust" version = "0.0.0" @@ -1392,6 +1748,30 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "spirt" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d5968bd2a36466468aac637b355776f080edfb0c6f769b2b99b9708260c42a" +dependencies = [ + "arrayvec", + "bytemuck", + "derive_more", + "elsa", + "indexmap", + "internal-iterator", + "itertools 0.10.5", + "lazy_static", + "longest-increasing-subsequence", + "rustc-hash", + "serde", + "serde_json", + "smallvec", +] [[package]] name = "spirv" @@ -1411,6 +1791,7 @@ dependencies = [ "log", "memchr", "raw-string", + "rustc_codegen_spirv", "rustc_codegen_spirv-types", "semver", "serde", @@ -1444,6 +1825,26 @@ dependencies = [ name = "spirv-std-types" version = "0.9.0" +[[package]] +name = "spirv-tools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863f14733a4ecb68c0bde9ea9b93bc58085a4172e76c8cefe7c810c940f02131" +dependencies = [ + "memchr", + "spirv-tools-sys", + "tempfile", +] + +[[package]] +name = "spirv-tools-sys" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219df977b2dd5a34a3529a7f7d2be12727abd87e4545abd0d54edd4fa2cfe5a8" +dependencies = [ + "cc", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -1520,6 +1921,27 @@ dependencies = [ "syn", ] +[[package]] +name = "thorin-dwp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9c1e705f82a260173f3eec93f2ff6d7807f23ad5a8cc2e7316a891733ea7a1" +dependencies = [ + "gimli", + "hashbrown 0.15.5", + "object 0.36.7", + "tracing", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -1571,6 +1993,92 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "tracing-tree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac87aa03b6a4d5a7e4810d1a80c19601dbe0f8a837e9177f23af721c7ba7beec" +dependencies = [ + "nu-ansi-term", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + [[package]] name = "trailing_zeros_64-cpu" version = "0.0.0" @@ -1603,6 +2111,16 @@ dependencies = [ "difftest", ] +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + [[package]] name = "typeid" version = "1.0.3" @@ -1645,6 +2163,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vector_extract_insert-rust" version = "0.0.0" @@ -1694,6 +2218,12 @@ dependencies = [ "difftest", ] +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -1748,6 +2278,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasmparser" +version = "0.236.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b1e81f3eb254cf7404a82cee6926a4a3ccc5aad80cc3d43608a070c67aa1d7" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "web-sys" version = "0.3.83" diff --git a/tests/difftests/tests/Cargo.toml b/tests/difftests/tests/Cargo.toml index a6a7dedd10f..95c5c484740 100644 --- a/tests/difftests/tests/Cargo.toml +++ b/tests/difftests/tests/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "lib", "simple-compute/simple-compute-rust", "simple-compute/simple-compute-wgsl", "arch/atomic_ops/atomic_ops-rust", @@ -60,13 +61,18 @@ unexpected_cfgs = { level = "allow", check-cfg = [ 'cfg(target_arch, values("spirv"))', ] } +# Cargo Windows bug: workspace dependencies of crates.io dependencies are resolved +# incorrectly in the root workspace instead of this difftest workspace. So all +# workspace dependencies here must be mirrored into the root workspace as well. [workspace.dependencies] +spirv-builder = { path = "../../../crates/spirv-builder", default-features = false } spirv-std = { path = "../../../crates/spirv-std", version = "=0.9.0" } -difftest = { path = "../../../tests/difftests/lib" } +difftest = { path = "lib" } +difftest-types = { path = "../types" } # External dependencies that need to be mentioned more than once. num-traits = { version = "0.2.15", default-features = false } glam = { version = ">=0.30.8", default-features = false } -bytemuck = { version = "1.14", features = ["derive"] } +bytemuck = { version = "1.23", features = ["derive"] } # Enable incremental by default in release mode. [profile.release] diff --git a/tests/difftests/tests/arch/push_constants/push_constants-rust/src/main.rs b/tests/difftests/tests/arch/push_constants/push_constants-rust/src/main.rs index 671f0a4974c..b30c57839e7 100644 --- a/tests/difftests/tests/arch/push_constants/push_constants-rust/src/main.rs +++ b/tests/difftests/tests/arch/push_constants/push_constants-rust/src/main.rs @@ -1,6 +1,7 @@ use difftest::config::Config; +use difftest::scaffold::compute::wgpu::Features; use difftest::scaffold::compute::{ - BufferConfig, BufferUsage, RustComputeShader, WgpuComputeTestPushConstants, + BufferConfig, BufferUsage, RustComputeShader, WgpuComputeTestMultiBuffer, }; #[repr(C)] @@ -30,7 +31,7 @@ fn main() { count: num_elements as u32, }; - let test = WgpuComputeTestPushConstants::new( + let test = WgpuComputeTestMultiBuffer::new( RustComputeShader::default(), [4, 1, 1], // 256 / 64 = 4 workgroups vec![ @@ -45,9 +46,9 @@ fn main() { initial_data: None, }, ], - std::mem::size_of::() as u32, - bytemuck::bytes_of(&push_constants).to_vec(), - ); + ) + .with_feature(Features::PUSH_CONSTANTS) + .with_push_constant(&push_constants); test.run_test(&config).unwrap(); } diff --git a/tests/difftests/tests/arch/push_constants/push_constants-wgsl/src/main.rs b/tests/difftests/tests/arch/push_constants/push_constants-wgsl/src/main.rs index 04f8b0ab79f..d077fa0ca55 100644 --- a/tests/difftests/tests/arch/push_constants/push_constants-wgsl/src/main.rs +++ b/tests/difftests/tests/arch/push_constants/push_constants-wgsl/src/main.rs @@ -1,6 +1,7 @@ use difftest::config::Config; +use difftest::scaffold::compute::wgpu::Features; use difftest::scaffold::compute::{ - BufferConfig, BufferUsage, WgpuComputeTestPushConstants, WgslComputeShader, + BufferConfig, BufferUsage, WgpuComputeTestMultiBuffer, WgslComputeShader, }; #[repr(C)] @@ -30,7 +31,7 @@ fn main() { count: num_elements as u32, }; - let test = WgpuComputeTestPushConstants::new( + let test = WgpuComputeTestMultiBuffer::new( WgslComputeShader::default(), [4, 1, 1], // 256 / 64 = 4 workgroups vec![ @@ -45,9 +46,9 @@ fn main() { initial_data: None, }, ], - std::mem::size_of::() as u32, - bytemuck::bytes_of(&push_constants).to_vec(), - ); + ) + .with_feature(Features::PUSH_CONSTANTS) + .with_push_constant(&push_constants); test.run_test(&config).unwrap(); } diff --git a/tests/difftests/tests/arch/vector_extract_insert/vector_extract_insert-wgsl/shader.wgsl b/tests/difftests/tests/arch/vector_extract_insert/vector_extract_insert-wgsl/shader.wgsl index 43199e558f8..94f237ba164 100644 --- a/tests/difftests/tests/arch/vector_extract_insert/vector_extract_insert-wgsl/shader.wgsl +++ b/tests/difftests/tests/arch/vector_extract_insert/vector_extract_insert-wgsl/shader.wgsl @@ -1,5 +1,5 @@ -@group(0) @binding(0) var input: array>; -@group(0) @binding(1) var indices: array; +@group(0) @binding(0) var input: array>; +@group(0) @binding(1) var indices: array; @group(0) @binding(2) var output: array>; @compute @workgroup_size(64) diff --git a/tests/difftests/tests/lang/control_flow_complex/control_flow_complex-wgsl/shader.wgsl b/tests/difftests/tests/lang/control_flow_complex/control_flow_complex-wgsl/shader.wgsl index c04bcb3880d..5ddcb86a1d5 100644 --- a/tests/difftests/tests/lang/control_flow_complex/control_flow_complex-wgsl/shader.wgsl +++ b/tests/difftests/tests/lang/control_flow_complex/control_flow_complex-wgsl/shader.wgsl @@ -1,4 +1,4 @@ -@group(0) @binding(0) var input: array; +@group(0) @binding(0) var input: array; @group(0) @binding(1) var output: array; @compute @workgroup_size(64) diff --git a/tests/difftests/tests/lang/core/intrinsics/black_box_noop/with-black-box/src/main.rs b/tests/difftests/tests/lang/core/intrinsics/black_box_noop/with-black-box/src/main.rs index e6ea8d4b46f..9597e318dbb 100644 --- a/tests/difftests/tests/lang/core/intrinsics/black_box_noop/with-black-box/src/main.rs +++ b/tests/difftests/tests/lang/core/intrinsics/black_box_noop/with-black-box/src/main.rs @@ -1,9 +1,13 @@ use difftest::config::{Config, TestMetadata}; -use difftest::scaffold::compute::{RustComputeShader, WgpuComputeTest}; +use difftest::scaffold::compute::{RustComputeShader, WgpuComputeTestMultiBuffer}; fn main() { let config = Config::from_path(std::env::args().nth(1).unwrap()).unwrap(); - let test = WgpuComputeTest::new(RustComputeShader::default(), [1, 1, 1], 12 * 4); + let test = WgpuComputeTestMultiBuffer::new_single_buffer( + RustComputeShader::default(), + [1, 1, 1], + 12 * 4, + ); test.run_test(&config).unwrap(); config .write_metadata(&TestMetadata::u32()) diff --git a/tests/difftests/tests/lang/core/intrinsics/black_box_noop/without-black-box/src/main.rs b/tests/difftests/tests/lang/core/intrinsics/black_box_noop/without-black-box/src/main.rs index e6ea8d4b46f..9597e318dbb 100644 --- a/tests/difftests/tests/lang/core/intrinsics/black_box_noop/without-black-box/src/main.rs +++ b/tests/difftests/tests/lang/core/intrinsics/black_box_noop/without-black-box/src/main.rs @@ -1,9 +1,13 @@ use difftest::config::{Config, TestMetadata}; -use difftest::scaffold::compute::{RustComputeShader, WgpuComputeTest}; +use difftest::scaffold::compute::{RustComputeShader, WgpuComputeTestMultiBuffer}; fn main() { let config = Config::from_path(std::env::args().nth(1).unwrap()).unwrap(); - let test = WgpuComputeTest::new(RustComputeShader::default(), [1, 1, 1], 12 * 4); + let test = WgpuComputeTestMultiBuffer::new_single_buffer( + RustComputeShader::default(), + [1, 1, 1], + 12 * 4, + ); test.run_test(&config).unwrap(); config .write_metadata(&TestMetadata::u32()) diff --git a/tests/difftests/tests/lang/core/ops/bitwise_ops/bitwise_ops-wgsl/shader.wgsl b/tests/difftests/tests/lang/core/ops/bitwise_ops/bitwise_ops-wgsl/shader.wgsl index 9950f1fa10d..a6fb4f686d2 100644 --- a/tests/difftests/tests/lang/core/ops/bitwise_ops/bitwise_ops-wgsl/shader.wgsl +++ b/tests/difftests/tests/lang/core/ops/bitwise_ops/bitwise_ops-wgsl/shader.wgsl @@ -1,5 +1,5 @@ -@group(0) @binding(0) var input_a: array; -@group(0) @binding(1) var input_b: array; +@group(0) @binding(0) var input_a: array; +@group(0) @binding(1) var input_b: array; @group(0) @binding(2) var output: array; @compute @workgroup_size(64) diff --git a/tests/difftests/tests/lang/core/ops/trig_ops/trig_ops-wgsl/shader.wgsl b/tests/difftests/tests/lang/core/ops/trig_ops/trig_ops-wgsl/shader.wgsl index 2a43f9a3b1b..d618981e5e1 100644 --- a/tests/difftests/tests/lang/core/ops/trig_ops/trig_ops-wgsl/shader.wgsl +++ b/tests/difftests/tests/lang/core/ops/trig_ops/trig_ops-wgsl/shader.wgsl @@ -1,4 +1,4 @@ -@group(0) @binding(0) var input: array; +@group(0) @binding(0) var input: array; @group(0) @binding(1) var output: array; @compute @workgroup_size(64) diff --git a/tests/difftests/tests/lang/core/ops/vector_swizzle/vector_swizzle-wgsl/shader.wgsl b/tests/difftests/tests/lang/core/ops/vector_swizzle/vector_swizzle-wgsl/shader.wgsl index e811918b238..ae921be518f 100644 --- a/tests/difftests/tests/lang/core/ops/vector_swizzle/vector_swizzle-wgsl/shader.wgsl +++ b/tests/difftests/tests/lang/core/ops/vector_swizzle/vector_swizzle-wgsl/shader.wgsl @@ -1,4 +1,4 @@ -@group(0) @binding(0) var input: array>; +@group(0) @binding(0) var input: array>; @group(0) @binding(1) var output: array>; @compute @workgroup_size(64) diff --git a/tests/difftests/lib/Cargo.toml b/tests/difftests/tests/lib/Cargo.toml similarity index 81% rename from tests/difftests/lib/Cargo.toml rename to tests/difftests/tests/lib/Cargo.toml index 0fd85562928..f011cd44f6d 100644 --- a/tests/difftests/lib/Cargo.toml +++ b/tests/difftests/tests/lib/Cargo.toml @@ -1,11 +1,6 @@ [package] name = "difftest" -version = "0.0.0" -publish = false -authors.workspace = true edition.workspace = true -license.workspace = true -repository.workspace = true # See rustc_codegen_spirv/Cargo.toml for details on these features [features] @@ -18,6 +13,7 @@ use-compiled-tools = [ [target.'cfg(not(target_arch = "spirv"))'.dependencies] spirv-builder.workspace = true +difftest-types.workspace = true serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" wgpu = { version = "27.0.1", default-features = false, features = ["std", "parking_lot", "spirv", "wgsl", "vulkan", "vulkan-portability", "metal"] } @@ -29,6 +25,3 @@ anyhow = "1.0.98" [lints] workspace = true - -[package.metadata.release] -release = false diff --git a/tests/difftests/lib/src/lib.rs b/tests/difftests/tests/lib/src/lib.rs similarity index 97% rename from tests/difftests/lib/src/lib.rs rename to tests/difftests/tests/lib/src/lib.rs index 2f66926766b..7491ec61f08 100644 --- a/tests/difftests/lib/src/lib.rs +++ b/tests/difftests/tests/lib/src/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(target_arch = "spirv", no_std)] #[cfg(not(target_arch = "spirv"))] -pub mod config; +pub use difftest_types::config; #[cfg(not(target_arch = "spirv"))] pub mod scaffold; diff --git a/tests/difftests/lib/src/scaffold/compute/ash.rs b/tests/difftests/tests/lib/src/scaffold/compute/ash_runner.rs similarity index 100% rename from tests/difftests/lib/src/scaffold/compute/ash.rs rename to tests/difftests/tests/lib/src/scaffold/compute/ash_runner.rs diff --git a/tests/difftests/lib/src/scaffold/compute/backend.rs b/tests/difftests/tests/lib/src/scaffold/compute/backend.rs similarity index 100% rename from tests/difftests/lib/src/scaffold/compute/backend.rs rename to tests/difftests/tests/lib/src/scaffold/compute/backend.rs diff --git a/tests/difftests/tests/lib/src/scaffold/compute/mod.rs b/tests/difftests/tests/lib/src/scaffold/compute/mod.rs new file mode 100644 index 00000000000..645c818873e --- /dev/null +++ b/tests/difftests/tests/lib/src/scaffold/compute/mod.rs @@ -0,0 +1,10 @@ +mod ash_runner; +mod backend; +mod wgpu_runner; + +pub use crate::scaffold::shader::*; +pub use ash; +pub use ash_runner::AshBackend; +pub use backend::{BufferConfig, BufferUsage, ComputeBackend, ComputeShaderTest, ComputeTest}; +pub use wgpu; +pub use wgpu_runner::WgpuComputeTestMultiBuffer; diff --git a/tests/difftests/tests/lib/src/scaffold/compute/wgpu_runner.rs b/tests/difftests/tests/lib/src/scaffold/compute/wgpu_runner.rs new file mode 100644 index 00000000000..9893a49cf82 --- /dev/null +++ b/tests/difftests/tests/lib/src/scaffold/compute/wgpu_runner.rs @@ -0,0 +1,275 @@ +use crate::config::Config; +use crate::scaffold::compute::{BufferConfig, BufferUsage}; +use crate::scaffold::shader::WgpuShader; +use anyhow::Context; +use bytemuck::NoUninit; +use futures::executor::block_on; +use wgpu::{ExperimentalFeatures, util::DeviceExt}; + +/// More flexible compute test that supports multiple buffers. +pub struct WgpuComputeTestMultiBuffer { + pub features: wgpu::Features, + pub shader: S, + pub dispatch: [u32; 3], + pub buffers: Vec, + pub push_constant: Option>, +} + +impl WgpuComputeTestMultiBuffer +where + S: WgpuShader, +{ + pub fn new(shader: S, dispatch: [u32; 3], buffers: Vec) -> Self { + Self { + features: wgpu::Features::empty(), + shader, + dispatch, + buffers, + push_constant: None, + } + } + + pub fn new_with_sizes(shader: S, dispatch: [u32; 3], sizes: &[u64]) -> Self { + let buffers = sizes + .iter() + .map(|&size| BufferConfig::writeback(size as usize)) + .collect(); + Self::new(shader, dispatch, buffers) + } + + pub fn new_single_buffer(shader: S, dispatch: [u32; 3], size: u64) -> Self { + Self::new_with_sizes(shader, dispatch, &[size]) + } + + pub fn with_feature(self, feature: wgpu::Features) -> Self { + Self { + features: self.features | feature, + ..self + } + } + + pub fn with_push_constant(self, data: &T) -> Self { + Self { + push_constant: Some(bytemuck::bytes_of(data).to_vec()), + ..self + } + } + + pub fn run(self) -> anyhow::Result>> { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + #[cfg(target_os = "linux")] + backends: wgpu::Backends::VULKAN, + #[cfg(not(target_os = "linux"))] + backends: wgpu::Backends::PRIMARY, + flags: Default::default(), + memory_budget_thresholds: Default::default(), + backend_options: wgpu::BackendOptions { + #[cfg(target_os = "windows")] + dx12: wgpu::Dx12BackendOptions { + shader_compiler: wgpu::Dx12Compiler::StaticDxc, + ..Default::default() + }, + ..Default::default() + }, + }); + let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::HighPerformance, + compatible_surface: None, + force_fallback_adapter: false, + })) + .context("Failed to find a suitable GPU adapter")?; + let (device, queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor { + label: Some("wgpu Device"), + required_features: wgpu::Features::EXPERIMENTAL_PASSTHROUGH_SHADERS | self.features, + required_limits: wgpu::Limits { + max_push_constant_size: 128, + ..wgpu::Limits::default() + }, + experimental_features: unsafe { ExperimentalFeatures::enabled() }, + memory_hints: Default::default(), + trace: Default::default(), + })) + .context("Failed to create device")?; + + let buffers = &self.buffers; + let pipeline = self.shader.create_pipeline( + &device, + &device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Pipeline Layout"), + bind_group_layouts: &[&device.create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + label: Some("Bind Group Layout"), + entries: &buffers + .iter() + .enumerate() + .map(|(i, buffer_config)| wgpu::BindGroupLayoutEntry { + binding: i as u32, + visibility: wgpu::ShaderStages::COMPUTE, + ty: match buffer_config.usage { + BufferUsage::Storage => wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + BufferUsage::StorageReadOnly => wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + BufferUsage::Uniform => wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + }, + count: None, + }) + .collect::>(), + }, + )], + push_constant_ranges: self + .push_constant + .as_ref() + .map(|data| wgpu::PushConstantRange { + stages: wgpu::ShaderStages::COMPUTE, + range: 0..data.len() as u32, + }) + .as_slice(), + }), + )?; + + // Create buffers. + let mut gpu_buffers = Vec::new(); + + for (i, buffer_config) in self.buffers.iter().enumerate() { + let usage = match buffer_config.usage { + BufferUsage::Storage => wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + BufferUsage::StorageReadOnly => { + wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC + } + BufferUsage::Uniform => wgpu::BufferUsages::UNIFORM, + }; + + let buffer = if let Some(initial_data) = &buffer_config.initial_data { + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("Buffer {i}")), + contents: initial_data, + usage, + }) + } else { + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(&format!("Buffer {i}")), + size: buffer_config.size, + usage, + mapped_at_creation: true, + }); + { + // Zero the buffer. + let initial_data = vec![0u8; buffer_config.size as usize]; + let mut mapping = buffer.slice(..).get_mapped_range_mut(); + mapping.copy_from_slice(&initial_data); + } + buffer.unmap(); + buffer + }; + + gpu_buffers.push(buffer); + } + + // Create bind entries after all buffers are created + let bind_entries: Vec<_> = gpu_buffers + .iter() + .enumerate() + .map(|(i, buffer)| wgpu::BindGroupEntry { + binding: i as u32, + resource: buffer.as_entire_binding(), + }) + .collect(); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &pipeline.get_bind_group_layout(0), + entries: &bind_entries, + label: Some("Compute Bind Group"), + }); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Compute Encoder"), + }); + { + let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("Compute Pass"), + timestamp_writes: Default::default(), + }); + pass.set_pipeline(&pipeline); + pass.set_bind_group(0, &bind_group, &[]); + if let Some(push_constant) = self.push_constant { + pass.set_push_constants(0, &push_constant); + } + pass.dispatch_workgroups(self.dispatch[0], self.dispatch[1], self.dispatch[2]); + } + + // Create staging buffers and copy results. + let mut staging_buffers = Vec::new(); + for (i, buffer_config) in self.buffers.iter().enumerate() { + if matches!( + buffer_config.usage, + BufferUsage::Storage | BufferUsage::StorageReadOnly + ) { + let staging_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some(&format!("Staging Buffer {i}")), + size: buffer_config.size, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + encoder.copy_buffer_to_buffer( + &gpu_buffers[i], + 0, + &staging_buffer, + 0, + buffer_config.size, + ); + staging_buffers.push(Some(staging_buffer)); + } else { + staging_buffers.push(None); + } + } + + queue.submit(Some(encoder.finish())); + + // Read back results. + let mut results = Vec::new(); + for staging_buffer in staging_buffers { + if let Some(buffer) = staging_buffer { + let buffer_slice = buffer.slice(..); + let (sender, receiver) = futures::channel::oneshot::channel(); + buffer_slice.map_async(wgpu::MapMode::Read, move |res| { + let _ = sender.send(res); + }); + device.poll(wgpu::PollType::wait_indefinitely())?; + block_on(receiver) + .context("mapping canceled")? + .context("mapping failed")?; + let data = buffer_slice.get_mapped_range().to_vec(); + buffer.unmap(); + results.push(data); + } else { + results.push(Vec::new()); + } + } + + Ok(results) + } + + pub fn run_test(self, config: &Config) -> anyhow::Result<()> { + let buffers = self.buffers.clone(); + let outputs = self.run()?; + // Write the first storage buffer output to the file. + for (output, buffer_config) in outputs.iter().zip(&buffers) { + if matches!(buffer_config.usage, BufferUsage::Storage) && !output.is_empty() { + config.write_result(output)?; + return Ok(()); + } + } + anyhow::bail!("No storage buffer output found") + } +} diff --git a/tests/difftests/lib/src/scaffold/mod.rs b/tests/difftests/tests/lib/src/scaffold/mod.rs similarity index 100% rename from tests/difftests/lib/src/scaffold/mod.rs rename to tests/difftests/tests/lib/src/scaffold/mod.rs diff --git a/tests/difftests/lib/src/scaffold/shader/mod.rs b/tests/difftests/tests/lib/src/scaffold/shader/mod.rs similarity index 80% rename from tests/difftests/lib/src/scaffold/shader/mod.rs rename to tests/difftests/tests/lib/src/scaffold/shader/mod.rs index cef42c40faf..53c0c8ac207 100644 --- a/tests/difftests/lib/src/scaffold/shader/mod.rs +++ b/tests/difftests/tests/lib/src/scaffold/shader/mod.rs @@ -13,8 +13,9 @@ pub trait SpirvShader { /// Trait for shaders that can create wgpu modules. pub trait WgpuShader { /// Creates a wgpu shader module. - fn create_module( + fn create_pipeline( &self, device: &wgpu::Device, - ) -> anyhow::Result<(wgpu::ShaderModule, Option)>; + pipeline_layout: &wgpu::PipelineLayout, + ) -> anyhow::Result; } diff --git a/tests/difftests/lib/src/scaffold/shader/rust_gpu_shader.rs b/tests/difftests/tests/lib/src/scaffold/shader/rust_gpu_shader.rs similarity index 74% rename from tests/difftests/lib/src/scaffold/shader/rust_gpu_shader.rs rename to tests/difftests/tests/lib/src/scaffold/shader/rust_gpu_shader.rs index e92f02a8474..eac40d66f21 100644 --- a/tests/difftests/lib/src/scaffold/shader/rust_gpu_shader.rs +++ b/tests/difftests/tests/lib/src/scaffold/shader/rust_gpu_shader.rs @@ -70,21 +70,40 @@ impl SpirvShader for RustComputeShader { } impl WgpuShader for RustComputeShader { - fn create_module( + fn create_pipeline( &self, device: &wgpu::Device, - ) -> anyhow::Result<(wgpu::ShaderModule, Option)> { + layout: &wgpu::PipelineLayout, + ) -> anyhow::Result { let (shader_bytes, entry_point) = self.spirv_bytes()?; if !shader_bytes.len().is_multiple_of(4) { anyhow::bail!("SPIR-V binary length is not a multiple of 4"); } let shader_words: Vec = bytemuck::cast_slice(&shader_bytes).to_vec(); - let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Compute Shader"), - source: wgpu::ShaderSource::SpirV(Cow::Owned(shader_words)), + let module = unsafe { + device.create_shader_module_passthrough(wgpu::ShaderModuleDescriptorPassthrough { + entry_point: entry_point.clone(), + label: Some("Compute Shader"), + num_workgroups: (0, 0, 0), + runtime_checks: Default::default(), + spirv: Some(Cow::Owned(shader_words)), + dxil: None, + msl: None, + hlsl: None, + glsl: None, + wgsl: None, + }) + }; + let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("Compute Pipeline"), + layout: Some(layout), + module: &module, + entry_point: Some(&entry_point), + compilation_options: wgpu::PipelineCompilationOptions::default(), + cache: None, }); - Ok((module, Some(entry_point))) + Ok(pipeline) } } diff --git a/tests/difftests/lib/src/scaffold/shader/wgsl_shader.rs b/tests/difftests/tests/lib/src/scaffold/shader/wgsl_shader.rs similarity index 80% rename from tests/difftests/lib/src/scaffold/shader/wgsl_shader.rs rename to tests/difftests/tests/lib/src/scaffold/shader/wgsl_shader.rs index a7e3f354f97..8b7c248d7fe 100644 --- a/tests/difftests/lib/src/scaffold/shader/wgsl_shader.rs +++ b/tests/difftests/tests/lib/src/scaffold/shader/wgsl_shader.rs @@ -20,17 +20,27 @@ impl WgslComputeShader { } impl WgpuShader for WgslComputeShader { - fn create_module( + fn create_pipeline( &self, device: &wgpu::Device, - ) -> anyhow::Result<(wgpu::ShaderModule, Option)> { + layout: &wgpu::PipelineLayout, + ) -> anyhow::Result { let shader_source = fs::read_to_string(&self.path) .with_context(|| format!("reading wgsl source file '{}'", &self.path.display()))?; let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Compute Shader"), source: wgpu::ShaderSource::Wgsl(Cow::Owned(shader_source)), }); - Ok((module, self.entry_point.clone())) + Ok( + device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("Wgsl compute pipeline"), + layout: Some(layout), + module: &module, + entry_point: self.entry_point.as_ref().map(|s| s.as_str()), + compilation_options: Default::default(), + cache: None, + }), + ) } } diff --git a/tests/difftests/lib/src/scaffold/skip.rs b/tests/difftests/tests/lib/src/scaffold/skip.rs similarity index 100% rename from tests/difftests/lib/src/scaffold/skip.rs rename to tests/difftests/tests/lib/src/scaffold/skip.rs diff --git a/tests/difftests/tests/simple-compute/simple-compute-rust/src/main.rs b/tests/difftests/tests/simple-compute/simple-compute-rust/src/main.rs index b98488302a2..3b6e4db3d77 100644 --- a/tests/difftests/tests/simple-compute/simple-compute-rust/src/main.rs +++ b/tests/difftests/tests/simple-compute/simple-compute-rust/src/main.rs @@ -1,12 +1,16 @@ use difftest::config::Config; -use difftest::scaffold::compute::{RustComputeShader, WgpuComputeTest}; +use difftest::scaffold::compute::{RustComputeShader, WgpuComputeTestMultiBuffer}; fn main() { // Load the config from the harness. let config = Config::from_path(std::env::args().nth(1).unwrap()).unwrap(); // Define test parameters, loading the rust shader from the current crate. - let test = WgpuComputeTest::new(RustComputeShader::default(), [1, 1, 1], 1024); + let test = WgpuComputeTestMultiBuffer::new_single_buffer( + RustComputeShader::default(), + [1, 1, 1], + 1024, + ); // Run the test and write the output to a file. test.run_test(&config).unwrap(); diff --git a/tests/difftests/tests/simple-compute/simple-compute-wgsl/src/main.rs b/tests/difftests/tests/simple-compute/simple-compute-wgsl/src/main.rs index cdb733fd3b3..4b64d3e48e8 100644 --- a/tests/difftests/tests/simple-compute/simple-compute-wgsl/src/main.rs +++ b/tests/difftests/tests/simple-compute/simple-compute-wgsl/src/main.rs @@ -1,12 +1,16 @@ use difftest::config::Config; -use difftest::scaffold::compute::{WgpuComputeTest, WgslComputeShader}; +use difftest::scaffold::compute::{WgpuComputeTestMultiBuffer, WgslComputeShader}; fn main() { // Load the config from the harness. let config = Config::from_path(std::env::args().nth(1).unwrap()).unwrap(); // Define test parameters, loading the wgsl shader from the crate directory. - let test = WgpuComputeTest::new(WgslComputeShader::default(), [1, 1, 1], 1024); + let test = WgpuComputeTestMultiBuffer::new_single_buffer( + WgslComputeShader::default(), + [1, 1, 1], + 1024, + ); // Run the test and write the output to a file. test.run_test(&config).unwrap(); diff --git a/tests/difftests/types/Cargo.toml b/tests/difftests/types/Cargo.toml new file mode 100644 index 00000000000..25847165e61 --- /dev/null +++ b/tests/difftests/types/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "difftest-types" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +anyhow = "1.0" +bytemuck = "1.21.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/tests/difftests/lib/src/config.rs b/tests/difftests/types/src/config.rs similarity index 73% rename from tests/difftests/lib/src/config.rs rename to tests/difftests/types/src/config.rs index 81071046686..78cfdd3daef 100644 --- a/tests/difftests/lib/src/config.rs +++ b/tests/difftests/types/src/config.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use serde::{Deserialize, Serialize}; use std::fs::File; use std::io::Write; @@ -11,8 +12,18 @@ pub struct Config { impl Config { pub fn write_result(&self, output: &[A]) -> anyhow::Result<()> { - let mut f = File::create(&self.output_path)?; - f.write_all(bytemuck::cast_slice(output))?; + let mut f = File::create(&self.output_path).with_context(|| { + format!( + "failed to create output file '{}'", + self.output_path.display() + ) + })?; + f.write_all(bytemuck::cast_slice(output)).with_context(|| { + format!( + "failed to write to output file '{}'", + self.output_path.display() + ) + })?; Ok(()) } } @@ -106,15 +117,28 @@ impl TestMetadata { impl Config { pub fn from_path>(path: P) -> anyhow::Result { - let content = fs::read_to_string(path)?; - let config = serde_json::from_str(&content)?; + let path = path.as_ref(); + let content = fs::read_to_string(path) + .with_context(|| format!("Could not read file '{}'", path.display()))?; + let config = serde_json::from_str(&content).with_context(|| { + format!( + "Could not parse json in file '{}':\n{content}", + path.display() + ) + })?; Ok(config) } /// Write test metadata to the configured metadata path pub fn write_metadata(&self, metadata: &TestMetadata) -> anyhow::Result<()> { - let metadata_json = serde_json::to_string(metadata)?; - fs::write(&self.metadata_path, metadata_json)?; + let metadata_json = + serde_json::to_string(metadata).context("Could not serialize TestMetadata")?; + fs::write(&self.metadata_path, metadata_json).with_context(|| { + format!( + "Could not write TestMetadata to file at '{}'", + self.metadata_path.display() + ) + })?; Ok(()) } } diff --git a/tests/difftests/types/src/lib.rs b/tests/difftests/types/src/lib.rs new file mode 100644 index 00000000000..ef68c36943d --- /dev/null +++ b/tests/difftests/types/src/lib.rs @@ -0,0 +1 @@ +pub mod config;