1 | use regex::bytes::Regex; |
2 | |
3 | use crate::{dependencies::build_dependencies, CommandBuilder, Filter, Match, Mode, RustfixMode}; |
4 | pub use color_eyre; |
5 | use color_eyre::eyre::Result; |
6 | use std::{ |
7 | ffi::OsString, |
8 | num::NonZeroUsize, |
9 | path::{Path, PathBuf}, |
10 | }; |
11 | |
12 | mod args; |
13 | pub use args::{Args, Format}; |
14 | |
15 | #[derive (Debug, Clone)] |
16 | /// Central datastructure containing all information to run the tests. |
17 | pub struct Config { |
18 | /// Host triple; usually will be auto-detected. |
19 | pub host: Option<String>, |
20 | /// `None` to run on the host, otherwise a target triple |
21 | pub target: Option<String>, |
22 | /// Filters applied to stderr output before processing it. |
23 | /// By default contains a filter for replacing backslashes in paths with |
24 | /// regular slashes. |
25 | /// On windows, contains a filter to remove `\r`. |
26 | pub stderr_filters: Filter, |
27 | /// Filters applied to stdout output before processing it. |
28 | /// On windows, contains a filter to remove `\r`. |
29 | pub stdout_filters: Filter, |
30 | /// The folder in which to start searching for .rs files |
31 | pub root_dir: PathBuf, |
32 | /// The mode in which to run the tests. |
33 | pub mode: Mode, |
34 | /// The binary to actually execute. |
35 | pub program: CommandBuilder, |
36 | /// The command to run to obtain the cfgs that the output is supposed to |
37 | pub cfgs: CommandBuilder, |
38 | /// What to do in case the stdout/stderr output differs from the expected one. |
39 | pub output_conflict_handling: OutputConflictHandling, |
40 | /// Path to a `Cargo.toml` that describes which dependencies the tests can access. |
41 | pub dependencies_crate_manifest_path: Option<PathBuf>, |
42 | /// The command to run can be changed from `cargo` to any custom command to build the |
43 | /// dependencies in `dependencies_crate_manifest_path`. |
44 | pub dependency_builder: CommandBuilder, |
45 | /// Where to dump files like the binaries compiled from tests. |
46 | /// Defaults to `target/ui` in the current directory. |
47 | pub out_dir: PathBuf, |
48 | /// The default edition to use on all tests. |
49 | pub edition: Option<String>, |
50 | /// Skip test files whose names contain any of these entries. |
51 | pub skip_files: Vec<String>, |
52 | /// Only test files whose names contain any of these entries. |
53 | pub filter_files: Vec<String>, |
54 | /// Override the number of threads to use. |
55 | pub threads: Option<NonZeroUsize>, |
56 | /// Nextest emulation: only list the test itself, not its components. |
57 | pub list: bool, |
58 | /// Only run the tests that are ignored. |
59 | pub run_only_ignored: bool, |
60 | /// Filters must match exactly instead of just checking for substrings. |
61 | pub filter_exact: bool, |
62 | } |
63 | |
64 | impl Config { |
65 | /// Create a configuration for testing the output of running |
66 | /// `rustc` on the test files. |
67 | pub fn rustc(root_dir: impl Into<PathBuf>) -> Self { |
68 | Self { |
69 | host: None, |
70 | target: None, |
71 | stderr_filters: vec![ |
72 | (Match::PathBackslash, b"/" ), |
73 | #[cfg (windows)] |
74 | (Match::Exact(vec![b' \r' ]), b"" ), |
75 | #[cfg (windows)] |
76 | (Match::Exact(br"\\?\" .to_vec()), b"" ), |
77 | ], |
78 | stdout_filters: vec![ |
79 | (Match::PathBackslash, b"/" ), |
80 | #[cfg (windows)] |
81 | (Match::Exact(vec![b' \r' ]), b"" ), |
82 | #[cfg (windows)] |
83 | (Match::Exact(br"\\?\" .to_vec()), b"" ), |
84 | ], |
85 | root_dir: root_dir.into(), |
86 | mode: Mode::Fail { |
87 | require_patterns: true, |
88 | rustfix: RustfixMode::MachineApplicable, |
89 | }, |
90 | program: CommandBuilder::rustc(), |
91 | cfgs: CommandBuilder::cfgs(), |
92 | output_conflict_handling: OutputConflictHandling::Bless, |
93 | dependencies_crate_manifest_path: None, |
94 | dependency_builder: CommandBuilder::cargo(), |
95 | out_dir: std::env::var_os("CARGO_TARGET_DIR" ) |
96 | .map(PathBuf::from) |
97 | .unwrap_or_else(|| std::env::current_dir().unwrap().join("target" )) |
98 | .join("ui" ), |
99 | edition: Some("2021" .into()), |
100 | skip_files: Vec::new(), |
101 | filter_files: Vec::new(), |
102 | threads: None, |
103 | list: false, |
104 | run_only_ignored: false, |
105 | filter_exact: false, |
106 | } |
107 | } |
108 | |
109 | /// Create a configuration for testing the output of running |
110 | /// `cargo` on the test `Cargo.toml` files. |
111 | pub fn cargo(root_dir: impl Into<PathBuf>) -> Self { |
112 | Self { |
113 | program: CommandBuilder::cargo(), |
114 | edition: None, |
115 | mode: Mode::Fail { |
116 | require_patterns: true, |
117 | rustfix: RustfixMode::Disabled, |
118 | }, |
119 | ..Self::rustc(root_dir) |
120 | } |
121 | } |
122 | |
123 | /// Populate the config with the values from parsed command line arguments. |
124 | /// If neither `--bless` or `--check` are provided `default_bless` is used. |
125 | /// |
126 | /// The default output conflict handling command suggests adding `--bless` |
127 | /// to the end of the current command. |
128 | pub fn with_args(&mut self, args: &Args, default_bless: bool) { |
129 | let Args { |
130 | ref filters, |
131 | check, |
132 | bless, |
133 | list, |
134 | exact, |
135 | ignored, |
136 | format: _, |
137 | threads, |
138 | ref skip, |
139 | } = *args; |
140 | |
141 | self.threads = threads.or(self.threads); |
142 | |
143 | self.filter_files.extend_from_slice(filters); |
144 | self.skip_files.extend_from_slice(skip); |
145 | self.run_only_ignored = ignored; |
146 | self.filter_exact = exact; |
147 | |
148 | self.list = list; |
149 | |
150 | let bless = match (bless, check) { |
151 | (_, true) => false, |
152 | (true, _) => true, |
153 | _ => default_bless, |
154 | }; |
155 | self.output_conflict_handling = if bless { |
156 | OutputConflictHandling::Bless |
157 | } else { |
158 | OutputConflictHandling::Error(format!( |
159 | " {} --bless" , |
160 | std::env::args() |
161 | .map(|s| format!(" {s:?}" )) |
162 | .collect::<Vec<_>>() |
163 | .join(" " ) |
164 | )) |
165 | }; |
166 | } |
167 | |
168 | /// Replace all occurrences of a path in stderr/stdout with a byte string. |
169 | #[track_caller ] |
170 | pub fn path_filter(&mut self, path: &Path, replacement: &'static (impl AsRef<[u8]> + ?Sized)) { |
171 | self.path_stderr_filter(path, replacement); |
172 | self.path_stdout_filter(path, replacement); |
173 | } |
174 | |
175 | /// Replace all occurrences of a path in stderr with a byte string. |
176 | #[track_caller ] |
177 | pub fn path_stderr_filter( |
178 | &mut self, |
179 | path: &Path, |
180 | replacement: &'static (impl AsRef<[u8]> + ?Sized), |
181 | ) { |
182 | let pattern = path.canonicalize().unwrap(); |
183 | self.stderr_filters |
184 | .push((pattern.parent().unwrap().into(), replacement.as_ref())); |
185 | } |
186 | |
187 | /// Replace all occurrences of a path in stdout with a byte string. |
188 | #[track_caller ] |
189 | pub fn path_stdout_filter( |
190 | &mut self, |
191 | path: &Path, |
192 | replacement: &'static (impl AsRef<[u8]> + ?Sized), |
193 | ) { |
194 | let pattern = path.canonicalize().unwrap(); |
195 | self.stdout_filters |
196 | .push((pattern.parent().unwrap().into(), replacement.as_ref())); |
197 | } |
198 | |
199 | /// Replace all occurrences of a regex pattern in stderr/stdout with a byte string. |
200 | #[track_caller ] |
201 | pub fn filter(&mut self, pattern: &str, replacement: &'static (impl AsRef<[u8]> + ?Sized)) { |
202 | self.stderr_filter(pattern, replacement); |
203 | self.stdout_filter(pattern, replacement); |
204 | } |
205 | |
206 | /// Replace all occurrences of a regex pattern in stderr with a byte string. |
207 | #[track_caller ] |
208 | pub fn stderr_filter( |
209 | &mut self, |
210 | pattern: &str, |
211 | replacement: &'static (impl AsRef<[u8]> + ?Sized), |
212 | ) { |
213 | self.stderr_filters |
214 | .push((Regex::new(pattern).unwrap().into(), replacement.as_ref())); |
215 | } |
216 | |
217 | /// Replace all occurrences of a regex pattern in stdout with a byte string. |
218 | #[track_caller ] |
219 | pub fn stdout_filter( |
220 | &mut self, |
221 | pattern: &str, |
222 | replacement: &'static (impl AsRef<[u8]> + ?Sized), |
223 | ) { |
224 | self.stdout_filters |
225 | .push((Regex::new(pattern).unwrap().into(), replacement.as_ref())); |
226 | } |
227 | |
228 | /// Compile dependencies and return the right flags |
229 | /// to find the dependencies. |
230 | pub fn build_dependencies(&self) -> Result<Vec<OsString>> { |
231 | let dependencies = build_dependencies(self)?; |
232 | let mut args = vec![]; |
233 | for (name, artifacts) in dependencies.dependencies { |
234 | for dependency in artifacts { |
235 | args.push("--extern" .into()); |
236 | let mut dep = OsString::from(&name); |
237 | dep.push("=" ); |
238 | dep.push(dependency); |
239 | args.push(dep); |
240 | } |
241 | } |
242 | for import_path in dependencies.import_paths { |
243 | args.push("-L" .into()); |
244 | args.push(import_path.into()); |
245 | } |
246 | Ok(args) |
247 | } |
248 | |
249 | /// Make sure we have the host and target triples. |
250 | pub fn fill_host_and_target(&mut self) -> Result<()> { |
251 | if self.host.is_none() { |
252 | self.host = Some( |
253 | rustc_version::VersionMeta::for_command(std::process::Command::new( |
254 | &self.program.program, |
255 | )) |
256 | .map_err(|err| { |
257 | color_eyre::eyre::Report::new(err).wrap_err(format!( |
258 | "failed to parse rustc version info: {}" , |
259 | self.program.display() |
260 | )) |
261 | })? |
262 | .host, |
263 | ); |
264 | } |
265 | if self.target.is_none() { |
266 | self.target = Some(self.host.clone().unwrap()); |
267 | } |
268 | Ok(()) |
269 | } |
270 | |
271 | pub(crate) fn has_asm_support(&self) -> bool { |
272 | static ASM_SUPPORTED_ARCHS: &[&str] = &[ |
273 | "x86" , "x86_64" , "arm" , "aarch64" , "riscv32" , |
274 | "riscv64" , |
275 | // These targets require an additional asm_experimental_arch feature. |
276 | // "nvptx64", "hexagon", "mips", "mips64", "spirv", "wasm32", |
277 | ]; |
278 | ASM_SUPPORTED_ARCHS |
279 | .iter() |
280 | .any(|arch| self.target.as_ref().unwrap().contains(arch)) |
281 | } |
282 | } |
283 | |
284 | #[derive (Debug, Clone)] |
285 | /// The different options for what to do when stdout/stderr files differ from the actual output. |
286 | pub enum OutputConflictHandling { |
287 | /// The string should be a command that can be executed to bless all tests. |
288 | Error(String), |
289 | /// Ignore mismatches in the stderr/stdout files. |
290 | Ignore, |
291 | /// Instead of erroring if the stderr/stdout differs from the expected |
292 | /// automatically replace it with the found output (after applying filters). |
293 | Bless, |
294 | } |
295 | |