1use regex::bytes::Regex;
2
3use crate::{dependencies::build_dependencies, CommandBuilder, Filter, Match, Mode, RustfixMode};
4pub use color_eyre;
5use color_eyre::eyre::Result;
6use std::{
7 ffi::OsString,
8 num::NonZeroUsize,
9 path::{Path, PathBuf},
10};
11
12mod args;
13pub use args::{Args, Format};
14
15#[derive(Debug, Clone)]
16/// Central datastructure containing all information to run the tests.
17pub 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
64impl 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.
286pub 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