wgpu_test/
params.rs

1use arrayvec::ArrayVec;
2use wgpu::{DownlevelCapabilities, DownlevelFlags, Features, InstanceFlags, Limits};
3
4use crate::{
5    report::AdapterReport, FailureApplicationReasons, FailureBehavior, FailureCase,
6    GpuTestConfiguration,
7};
8
9const LOWEST_DOWNLEVEL_PROPERTIES: wgpu::DownlevelCapabilities = DownlevelCapabilities {
10    flags: wgpu::DownlevelFlags::empty(),
11    limits: wgpu::DownlevelLimits {},
12    shader_model: wgpu::ShaderModel::Sm2,
13};
14
15/// This information determines if a test should run.
16#[derive(Clone)]
17pub struct TestParameters {
18    pub required_features: Features,
19    pub required_downlevel_caps: DownlevelCapabilities,
20    pub required_limits: Limits,
21
22    pub required_instance_flags: InstanceFlags,
23
24    /// On Dx12, specifically test against the Fxc compiler.
25    ///
26    /// For testing workarounds to Fxc bugs.
27    pub force_fxc: bool,
28
29    /// Conditions under which this test should be skipped.
30    pub skips: Vec<FailureCase>,
31
32    /// Conditions under which this test should be run, but is expected to fail.
33    pub failures: Vec<FailureCase>,
34
35    /// For certain features (ray tracing), metal shader validation is completely
36    /// broken
37    pub disable_mtl_shader_validation: bool,
38}
39
40impl Default for TestParameters {
41    fn default() -> Self {
42        Self {
43            required_features: Features::empty(),
44            required_downlevel_caps: LOWEST_DOWNLEVEL_PROPERTIES,
45            required_limits: Limits::downlevel_webgl2_defaults(),
46            required_instance_flags: InstanceFlags::empty(),
47            force_fxc: false,
48            // By default we skip the noop backend, and enable it if the test
49            // parameters ask us to remove it.
50            skips: vec![FailureCase::backend(wgpu::Backends::NOOP)],
51            failures: Vec::new(),
52            disable_mtl_shader_validation: false,
53        }
54    }
55}
56
57// Builder pattern to make it easier
58impl TestParameters {
59    /// Set of common features that most internal tests require for compute and readback.
60    pub fn test_features_limits(self) -> Self {
61        self.downlevel_flags(DownlevelFlags::COMPUTE_SHADERS)
62            .limits(wgpu::Limits::downlevel_defaults())
63    }
64
65    /// Set the list of features this test requires.
66    pub fn features(mut self, features: Features) -> Self {
67        self.required_features |= features;
68        self
69    }
70
71    pub fn downlevel_flags(mut self, downlevel_flags: DownlevelFlags) -> Self {
72        self.required_downlevel_caps.flags |= downlevel_flags;
73        self
74    }
75
76    /// Set the limits needed for the test.
77    pub fn limits(mut self, limits: Limits) -> Self {
78        self.required_limits = limits;
79        self
80    }
81
82    /// Sets the instance flags that the test requires.
83    pub fn instance_flags(mut self, instance_flags: InstanceFlags) -> Self {
84        self.required_instance_flags |= instance_flags;
85        self
86    }
87
88    pub fn force_fxc(mut self, force_fxc: bool) -> Self {
89        self.force_fxc = force_fxc;
90        self
91    }
92
93    /// Mark the test as always failing, but not to be skipped.
94    pub fn expect_fail(mut self, when: FailureCase) -> Self {
95        self.failures.push(when);
96        self
97    }
98
99    /// Mark the test as always failing, and needing to be skipped.
100    pub fn skip(mut self, when: FailureCase) -> Self {
101        self.skips.push(when);
102        self
103    }
104
105    /// Enable testing against the noop backend and miri.
106    ///
107    /// The noop backend does not execute any operations, but allows us to test
108    /// validation and memory safety.
109    pub fn enable_noop(mut self) -> Self {
110        self.skips
111            .retain(|case| *case != FailureCase::backend(wgpu::Backends::NOOP));
112        self
113    }
114
115    /// Disable metal shader validation.
116    ///
117    /// Metal shader validation can cause features (ray tracing specifically)
118    /// to break. This disables it so it can be tested.
119    pub fn disable_mtl_shader_validation(mut self) -> Self {
120        self.disable_mtl_shader_validation = true;
121        self
122    }
123}
124
125/// Information about a test, including if if it should be skipped.
126pub struct TestInfo {
127    pub skip: bool,
128    pub failure_application_reasons: FailureApplicationReasons,
129    pub failures: Vec<FailureCase>,
130    pub running_msg: String,
131}
132
133impl TestInfo {
134    pub(crate) fn from_configuration(test: &GpuTestConfiguration, adapter: &AdapterReport) -> Self {
135        // Figure out if a test is unsupported, and why.
136        let mut unsupported_reasons: ArrayVec<_, 4> = ArrayVec::new();
137        let missing_features = test.params.required_features - adapter.features;
138        if !missing_features.is_empty() {
139            unsupported_reasons.push("Features");
140        }
141
142        if !test.params.required_limits.check_limits(&adapter.limits) {
143            unsupported_reasons.push("Limits");
144        }
145
146        let missing_downlevel_flags =
147            test.params.required_downlevel_caps.flags - adapter.downlevel_caps.flags;
148        if !missing_downlevel_flags.is_empty() {
149            unsupported_reasons.push("Downlevel Flags");
150        }
151
152        if test.params.required_downlevel_caps.shader_model > adapter.downlevel_caps.shader_model {
153            unsupported_reasons.push("Shader Model");
154        }
155
156        // Produce a lower-case version of the adapter info, for comparison against
157        // `parameters.skips` and `parameters.failures`.
158        let adapter_lowercase_info = wgpu::AdapterInfo {
159            name: adapter.info.name.to_lowercase(),
160            driver: adapter.info.driver.to_lowercase(),
161            ..adapter.info.clone()
162        };
163
164        // Check if we should skip the test altogether.
165        let skip_application_reason = test
166            .params
167            .skips
168            .iter()
169            .find_map(|case| case.applies_to_adapter(&adapter_lowercase_info));
170
171        let mut applicable_cases = Vec::with_capacity(test.params.failures.len());
172        let mut failure_application_reasons = FailureApplicationReasons::empty();
173        let mut flaky = false;
174        for failure in &test.params.failures {
175            if let Some(reasons) = failure.applies_to_adapter(&adapter_lowercase_info) {
176                failure_application_reasons.insert(reasons);
177                applicable_cases.push(failure.clone());
178                flaky |= matches!(failure.behavior, FailureBehavior::Ignore);
179            }
180        }
181
182        let mut skip = false;
183        let running_msg = if let Some(reasons) = skip_application_reason {
184            skip = true;
185
186            let names: ArrayVec<_, 4> = reasons.iter_names().map(|(name, _)| name).collect();
187            let names_text = names.join(" | ");
188
189            format!("Skipped Failure: {names_text}")
190        } else if !unsupported_reasons.is_empty() {
191            skip = true;
192            format!("Unsupported: {}", unsupported_reasons.join(" | "))
193        } else if !failure_application_reasons.is_empty() {
194            if cfg!(target_arch = "wasm32") {
195                skip = true;
196            }
197
198            let names: ArrayVec<_, 4> = failure_application_reasons
199                .iter_names()
200                .map(|(name, _)| name)
201                .collect();
202            let names_text = names.join(" & ");
203            let flaky_text = if flaky { " Flaky " } else { " " };
204
205            format!("Executed{flaky_text}Failure: {names_text}")
206        } else {
207            String::from("Executed")
208        };
209
210        Self {
211            skip,
212            failure_application_reasons,
213            failures: applicable_cases,
214            running_msg,
215        }
216    }
217}