wgpu_test/
native.rs

1#![cfg(not(target_arch = "wasm32"))]
2//! Infrastructure for the native, `cargo-nextest` based harness.
3//!
4//! This is largly used by [`gpu_test_main`](crate::gpu_test_main) and [`gpu_test`](crate::gpu_test).
5
6use std::{future::Future, pin::Pin};
7
8use parking_lot::Mutex;
9
10use crate::{
11    config::GpuTestConfiguration, params::TestInfo, report::AdapterReport, run::execute_test,
12    GpuTestInitializer,
13};
14
15type NativeTestFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
16
17struct NativeTest {
18    name: String,
19    future: NativeTestFuture,
20}
21
22impl NativeTest {
23    /// Adapter index is only used for naming the test, the adapters are matched based on the adapter info.
24    fn from_configuration(
25        config: GpuTestConfiguration,
26        adapter_report: AdapterReport,
27        adapter_index: usize,
28    ) -> Self {
29        let device_name = &adapter_report.info.name;
30        let backend = adapter_report.info.backend;
31        let driver = &adapter_report.info.driver;
32        let report_driver_as_backend = [
33            (wgpu::Backend::Vulkan, "MoltenVK"),
34            (wgpu::Backend::Vulkan, "KosmicKrisp"),
35        ];
36        let backend_str = if report_driver_as_backend.contains(&(backend, driver.as_ref())) {
37            driver.clone()
38        } else {
39            format!("{backend:?}")
40        };
41
42        let test_info = TestInfo::from_configuration(&config, &adapter_report);
43
44        let full_name = format!(
45            "[{running_msg}] [{backend_str}/{device_name}/{adapter_index}] {base_name}",
46            running_msg = test_info.running_msg,
47            base_name = config.name,
48        );
49        Self {
50            name: full_name,
51            future: Box::pin(async move {
52                // Enable metal validation layers if we're running on metal.
53                //
54                // This is a process-wide setting as it's via environment variable, but all
55                // tests are run in separate processes.
56                //
57                // We don't do this in the instance initializer as we don't want to enable
58                // validation layers for the entire process, or other instances.
59                //
60                // We do not enable metal validation when running on moltenvk.
61                let metal_validation = backend == wgpu::Backend::Metal;
62
63                let env_value = if metal_validation { "1" } else { "0" };
64                std::env::set_var("MTL_DEBUG_LAYER", env_value);
65                if std::env::var("GITHUB_ACTIONS").as_deref() != Ok("true")
66                    && !config.params.disable_mtl_shader_validation
67                {
68                    // Metal Shader Validation is entirely broken in the paravirtualized CI environment.
69                    std::env::set_var("MTL_SHADER_VALIDATION", env_value);
70                } else if config.params.disable_mtl_shader_validation {
71                    // For ray tracing, where MTL_SHADER_VALIDATION causes acceleration structure ids to be
72                    // completely incorrect.
73                    std::env::set_var("MTL_SHADER_VALIDATION", "0");
74                }
75
76                execute_test(Some(&adapter_report), config, Some(test_info)).await;
77            }),
78        }
79    }
80
81    pub fn into_trial(self) -> libtest_mimic::Trial {
82        libtest_mimic::Trial::test(self.name, || {
83            pollster::block_on(self.future);
84            Ok(())
85        })
86    }
87}
88
89#[doc(hidden)]
90pub static TEST_LIST: Mutex<Vec<crate::GpuTestConfiguration>> = Mutex::new(Vec::new());
91
92/// Return value for the main function.
93pub type MainResult = anyhow::Result<()>;
94
95/// Main function that runs every gpu function once for every adapter on the system.
96pub fn main(tests: Vec<GpuTestInitializer>) -> MainResult {
97    use anyhow::Context;
98
99    use crate::report::GpuReport;
100
101    // If this environment variable is set, we will only enumerate the noop backend. The
102    // main use case is running tests with miri, where we can't even enumerate adapters,
103    // as we cannot load DLLs or make any external calls.
104    let use_noop = std::env::var("WGPU_GPU_TESTS_USE_NOOP_BACKEND").as_deref() == Ok("1");
105
106    let report = if use_noop {
107        GpuReport::noop_only()
108    } else {
109        let config_text = {
110            profiling::scope!("Reading .gpuconfig");
111            &std::fs::read_to_string(format!("{}/../.gpuconfig", env!("CARGO_MANIFEST_DIR")))
112                .context(
113                    "Failed to read .gpuconfig, did you run the tests via `cargo xtask test`?",
114                )?
115        };
116        let mut report =
117            GpuReport::from_json(config_text).context("Could not parse .gpuconfig JSON")?;
118
119        // Filter out the adapters that are not part of WGPU_BACKEND.
120        let wgpu_backends = wgpu::Backends::from_env().unwrap_or_default();
121        report
122            .devices
123            .retain(|report| wgpu_backends.contains(wgpu::Backends::from(report.info.backend)));
124
125        report
126    };
127
128    // Iterate through all the tests. Creating a test per adapter.
129    execute_native(tests.into_iter().flat_map(|initializer| {
130        let test = initializer();
131        report
132            .devices
133            .iter()
134            .enumerate()
135            .map(move |(adapter_index, adapter_report)| {
136                NativeTest::from_configuration(test.clone(), adapter_report.clone(), adapter_index)
137            })
138    }));
139
140    Ok(())
141}
142
143fn execute_native(tests: impl IntoIterator<Item = NativeTest>) {
144    let args = libtest_mimic::Arguments::from_args();
145    let trials = {
146        profiling::scope!("collecting tests");
147        tests.into_iter().map(NativeTest::into_trial).collect()
148    };
149
150    libtest_mimic::run(&args, trials).exit_if_failed();
151}