| 1 | //! This module allows you to configure the default settings for all tests. | 
| 2 | //! | 
|---|
| 3 | //! All data structures here are normally parsed from `@` comments | 
|---|
| 4 | //! in the files. These comments still overwrite the defaults, although | 
|---|
| 5 | //! some boolean settings have no way to disable them. | 
|---|
| 6 |  | 
|---|
| 7 | use crate::build_manager::BuildManager; | 
|---|
| 8 | use crate::custom_flags::Flag; | 
|---|
| 9 | pub use crate::diagnostics::Level; | 
|---|
| 10 | use crate::diagnostics::{Diagnostics, Message}; | 
|---|
| 11 | pub use crate::parser::{Comments, Condition, Revisioned}; | 
|---|
| 12 | use crate::parser::{ErrorMatch, ErrorMatchKind, OptWithLine}; | 
|---|
| 13 | use crate::status_emitter::{SilentStatus, TestStatus}; | 
|---|
| 14 | use crate::test_result::{Errored, TestOk, TestResult}; | 
|---|
| 15 | use crate::{core::strip_path_prefix, Config, Error, Errors}; | 
|---|
| 16 | use spanned::Spanned; | 
|---|
| 17 | use std::collections::btree_map::Entry; | 
|---|
| 18 | use std::collections::BTreeMap; | 
|---|
| 19 | use std::num::NonZeroUsize; | 
|---|
| 20 | use std::path::PathBuf; | 
|---|
| 21 | use std::process::{Command, Output}; | 
|---|
| 22 | use std::sync::Arc; | 
|---|
| 23 |  | 
|---|
| 24 | /// All information needed to run a single test | 
|---|
| 25 | pub struct TestConfig { | 
|---|
| 26 | /// The generic config for all tests | 
|---|
| 27 | pub config: Config, | 
|---|
| 28 | pub(crate) comments: Arc<Comments>, | 
|---|
| 29 | /// The path to the folder where to look for aux files | 
|---|
| 30 | pub aux_dir: PathBuf, | 
|---|
| 31 | /// When doing long-running operations, you can inform the user about it here. | 
|---|
| 32 | pub status: Box<dyn TestStatus>, | 
|---|
| 33 | } | 
|---|
| 34 |  | 
|---|
| 35 | impl TestConfig { | 
|---|
| 36 | /// Create a config for running one file. | 
|---|
| 37 | pub fn one_off_runner(config: Config, path: PathBuf) -> Self { | 
|---|
| 38 | Self { | 
|---|
| 39 | comments: Arc::new(config.comment_defaults.clone()), | 
|---|
| 40 | config, | 
|---|
| 41 | aux_dir: PathBuf::new(), | 
|---|
| 42 | status: Box::new(SilentStatus { | 
|---|
| 43 | revision: "".into(), | 
|---|
| 44 | path, | 
|---|
| 45 | }), | 
|---|
| 46 | } | 
|---|
| 47 | } | 
|---|
| 48 |  | 
|---|
| 49 | pub(crate) fn patch_out_dir(&mut self) { | 
|---|
| 50 | // Put aux builds into a separate directory per path so that multiple aux files | 
|---|
| 51 | // from different directories (but with the same file name) don't collide. | 
|---|
| 52 | let relative = | 
|---|
| 53 | strip_path_prefix(self.status.path().parent().unwrap(), &self.config.out_dir); | 
|---|
| 54 |  | 
|---|
| 55 | self.config.out_dir.extend(relative); | 
|---|
| 56 | } | 
|---|
| 57 |  | 
|---|
| 58 | /// Create a file extension that includes the current revision if necessary. | 
|---|
| 59 | pub fn extension(&self, extension: &str) -> String { | 
|---|
| 60 | if self.status.revision().is_empty() { | 
|---|
| 61 | extension.to_string() | 
|---|
| 62 | } else { | 
|---|
| 63 | format!( "{} .{extension} ", self.status.revision()) | 
|---|
| 64 | } | 
|---|
| 65 | } | 
|---|
| 66 |  | 
|---|
| 67 | /// The test's expected exit status after applying all comments | 
|---|
| 68 | pub fn exit_status(&self) -> Result<Option<Spanned<i32>>, Errored> { | 
|---|
| 69 | self.comments.exit_status(self.status.revision()) | 
|---|
| 70 | } | 
|---|
| 71 |  | 
|---|
| 72 | /// Whether compiler messages require annotations | 
|---|
| 73 | pub fn require_annotations(&self) -> Option<Spanned<bool>> { | 
|---|
| 74 | self.comments.require_annotations(self.status.revision()) | 
|---|
| 75 | } | 
|---|
| 76 |  | 
|---|
| 77 | pub(crate) fn find_one<'a, T: 'a>( | 
|---|
| 78 | &'a self, | 
|---|
| 79 | kind: &str, | 
|---|
| 80 | f: impl Fn(&'a Revisioned) -> OptWithLine<T>, | 
|---|
| 81 | ) -> Result<OptWithLine<T>, Errored> { | 
|---|
| 82 | self.comments | 
|---|
| 83 | .find_one_for_revision(self.status.revision(), kind, f) | 
|---|
| 84 | } | 
|---|
| 85 |  | 
|---|
| 86 | /// All comments that apply to the current test. | 
|---|
| 87 | pub fn comments(&self) -> impl Iterator<Item = &'_ Revisioned> { | 
|---|
| 88 | self.comments.for_revision(self.status.revision()) | 
|---|
| 89 | } | 
|---|
| 90 |  | 
|---|
| 91 | pub(crate) fn collect<'a, T, I: Iterator<Item = T>, R: FromIterator<T>>( | 
|---|
| 92 | &'a self, | 
|---|
| 93 | f: impl Fn(&'a Revisioned) -> I, | 
|---|
| 94 | ) -> R { | 
|---|
| 95 | self.comments().flat_map(f).collect() | 
|---|
| 96 | } | 
|---|
| 97 |  | 
|---|
| 98 | /// Apply custom flags (aux builds, dependencies, ...) | 
|---|
| 99 | pub fn apply_custom( | 
|---|
| 100 | &self, | 
|---|
| 101 | cmd: &mut Command, | 
|---|
| 102 | build_manager: &BuildManager, | 
|---|
| 103 | ) -> Result<(), Errored> { | 
|---|
| 104 | let mut all = BTreeMap::new(); | 
|---|
| 105 | for rev in self.comments.for_revision(self.status.revision()) { | 
|---|
| 106 | for (&k, flags) in &rev.custom { | 
|---|
| 107 | for flag in &flags.content { | 
|---|
| 108 | match all.entry(k) { | 
|---|
| 109 | Entry::Vacant(v) => _ = v.insert(vec![flag]), | 
|---|
| 110 | Entry::Occupied(mut o) => { | 
|---|
| 111 | if o.get().last().unwrap().must_be_unique() { | 
|---|
| 112 | // Overwrite previous value so that revisions overwrite default settings | 
|---|
| 113 | // FIXME: report an error if multiple revisions conflict | 
|---|
| 114 | assert_eq!(o.get().len(), 1); | 
|---|
| 115 | o.get_mut()[0] = flag; | 
|---|
| 116 | } else { | 
|---|
| 117 | o.get_mut().push(flag); | 
|---|
| 118 | } | 
|---|
| 119 | } | 
|---|
| 120 | } | 
|---|
| 121 | } | 
|---|
| 122 | } | 
|---|
| 123 | } | 
|---|
| 124 | for flags in all.values() { | 
|---|
| 125 | for flag in flags { | 
|---|
| 126 | flag.apply(cmd, self, build_manager)?; | 
|---|
| 127 | } | 
|---|
| 128 | } | 
|---|
| 129 | Ok(()) | 
|---|
| 130 | } | 
|---|
| 131 |  | 
|---|
| 132 | /// Produce the command that will be executed to run the test. | 
|---|
| 133 | pub fn build_command(&self, build_manager: &BuildManager) -> Result<Command, Errored> { | 
|---|
| 134 | let mut cmd = self.config.program.build(&self.config.out_dir); | 
|---|
| 135 | cmd.arg(self.status.path()); | 
|---|
| 136 | for r in self.comments() { | 
|---|
| 137 | cmd.args(&r.compile_flags); | 
|---|
| 138 | } | 
|---|
| 139 |  | 
|---|
| 140 | self.apply_custom(&mut cmd, build_manager)?; | 
|---|
| 141 |  | 
|---|
| 142 | if let Some(target) = &self.config.target { | 
|---|
| 143 | // Adding a `--target` arg to calls to Cargo will cause target folders | 
|---|
| 144 | // to create a target-specific sub-folder. We can avoid that by just | 
|---|
| 145 | // not passing a `--target` arg if its the same as the host. | 
|---|
| 146 | if !self.config.host_matches_target() { | 
|---|
| 147 | cmd.arg( "--target").arg(target); | 
|---|
| 148 | } | 
|---|
| 149 | } | 
|---|
| 150 |  | 
|---|
| 151 | cmd.envs(self.envs()); | 
|---|
| 152 |  | 
|---|
| 153 | Ok(cmd) | 
|---|
| 154 | } | 
|---|
| 155 |  | 
|---|
| 156 | pub(crate) fn output_path(&self, kind: &str) -> PathBuf { | 
|---|
| 157 | let ext = self.extension(kind); | 
|---|
| 158 | if self.comments().any(|r| r.stderr_per_bitwidth) { | 
|---|
| 159 | return self | 
|---|
| 160 | .status | 
|---|
| 161 | .path() | 
|---|
| 162 | .with_extension(format!( "{} bit.{ext} ", self.config.get_pointer_width())); | 
|---|
| 163 | } | 
|---|
| 164 | self.status.path().with_extension(ext) | 
|---|
| 165 | } | 
|---|
| 166 |  | 
|---|
| 167 | pub(crate) fn normalize(&self, text: &[u8], kind: &str) -> Vec<u8> { | 
|---|
| 168 | let mut text = text.to_owned(); | 
|---|
| 169 |  | 
|---|
| 170 | for (from, to) in self.comments().flat_map(|r| match kind { | 
|---|
| 171 | _ if kind.ends_with( "fixed") => &[] as &[_], | 
|---|
| 172 | "stderr"=> &r.normalize_stderr, | 
|---|
| 173 | "stdout"=> &r.normalize_stdout, | 
|---|
| 174 | _ => unreachable!(), | 
|---|
| 175 | }) { | 
|---|
| 176 | text = from.replace_all(&text, to).into_owned(); | 
|---|
| 177 | } | 
|---|
| 178 | text | 
|---|
| 179 | } | 
|---|
| 180 |  | 
|---|
| 181 | pub(crate) fn check_test_output(&self, errors: &mut Errors, stdout: &[u8], stderr: &[u8]) { | 
|---|
| 182 | // Check output files (if any) | 
|---|
| 183 | // Check output files against actual output | 
|---|
| 184 | self.check_output(stderr, errors, "stderr"); | 
|---|
| 185 | self.check_output(stdout, errors, "stdout"); | 
|---|
| 186 | } | 
|---|
| 187 |  | 
|---|
| 188 | pub(crate) fn check_output(&self, output: &[u8], errors: &mut Errors, kind: &str) -> PathBuf { | 
|---|
| 189 | let path = self.output_path(kind); | 
|---|
| 190 | (self.config.output_conflict_handling)(&path, output, errors, self); | 
|---|
| 191 | path | 
|---|
| 192 | } | 
|---|
| 193 |  | 
|---|
| 194 | /// Read diagnostics from a test's output. | 
|---|
| 195 | pub fn process(&self, stderr: &[u8]) -> Diagnostics { | 
|---|
| 196 | (self.config.diagnostic_extractor)(self.status.path(), stderr) | 
|---|
| 197 | } | 
|---|
| 198 |  | 
|---|
| 199 | fn check_test_result(&self, command: &Command, output: Output) -> Result<Output, Errored> { | 
|---|
| 200 | let mut errors = vec![]; | 
|---|
| 201 | errors.extend(self.ok(output.status)?); | 
|---|
| 202 | // Always remove annotation comments from stderr. | 
|---|
| 203 | let diagnostics = self.process(&output.stderr); | 
|---|
| 204 | self.check_test_output(&mut errors, &output.stdout, &diagnostics.rendered); | 
|---|
| 205 | // Check error annotations in the source against output | 
|---|
| 206 | self.check_annotations( | 
|---|
| 207 | diagnostics.messages, | 
|---|
| 208 | diagnostics.messages_from_unknown_file_or_line, | 
|---|
| 209 | &mut errors, | 
|---|
| 210 | )?; | 
|---|
| 211 | if errors.is_empty() { | 
|---|
| 212 | Ok(output) | 
|---|
| 213 | } else { | 
|---|
| 214 | Err(Errored { | 
|---|
| 215 | command: format!( "{command:?} "), | 
|---|
| 216 | errors, | 
|---|
| 217 | stderr: diagnostics.rendered, | 
|---|
| 218 | stdout: output.stdout, | 
|---|
| 219 | }) | 
|---|
| 220 | } | 
|---|
| 221 | } | 
|---|
| 222 |  | 
|---|
| 223 | pub(crate) fn check_annotations( | 
|---|
| 224 | &self, | 
|---|
| 225 | mut messages: Vec<Vec<Message>>, | 
|---|
| 226 | mut messages_from_unknown_file_or_line: Vec<Message>, | 
|---|
| 227 | errors: &mut Errors, | 
|---|
| 228 | ) -> Result<(), Errored> { | 
|---|
| 229 | let error_patterns = self.comments().flat_map(|r| r.error_in_other_files.iter()); | 
|---|
| 230 |  | 
|---|
| 231 | let mut seen_error_match = None; | 
|---|
| 232 | for error_pattern in error_patterns { | 
|---|
| 233 | seen_error_match = Some(error_pattern.span()); | 
|---|
| 234 | // first check the diagnostics messages outside of our file. We check this first, so that | 
|---|
| 235 | // you can mix in-file annotations with //@error-in-other-file annotations, even if there is overlap | 
|---|
| 236 | // in the messages. | 
|---|
| 237 | if let Some(i) = messages_from_unknown_file_or_line | 
|---|
| 238 | .iter() | 
|---|
| 239 | .position(|msg| error_pattern.matches(&msg.message)) | 
|---|
| 240 | { | 
|---|
| 241 | messages_from_unknown_file_or_line.remove(i); | 
|---|
| 242 | } else { | 
|---|
| 243 | errors.push(Error::PatternNotFound { | 
|---|
| 244 | pattern: error_pattern.clone(), | 
|---|
| 245 | expected_line: None, | 
|---|
| 246 | }); | 
|---|
| 247 | } | 
|---|
| 248 | } | 
|---|
| 249 | let diagnostic_code_prefix = self | 
|---|
| 250 | .find_one( "diagnostic_code_prefix", |r| { | 
|---|
| 251 | r.diagnostic_code_prefix.clone() | 
|---|
| 252 | })? | 
|---|
| 253 | .into_inner() | 
|---|
| 254 | .map(|s| s.content) | 
|---|
| 255 | .unwrap_or_default(); | 
|---|
| 256 |  | 
|---|
| 257 | // The order on `Level` is such that `Error` is the highest level. | 
|---|
| 258 | // We will ensure that *all* diagnostics of level at least `lowest_annotation_level` | 
|---|
| 259 | // are matched. | 
|---|
| 260 | let mut lowest_annotation_level = Level::Error; | 
|---|
| 261 | 'err: for &ErrorMatch { ref kind, line } in | 
|---|
| 262 | self.comments().flat_map(|r| r.error_matches.iter()) | 
|---|
| 263 | { | 
|---|
| 264 | match kind { | 
|---|
| 265 | ErrorMatchKind::Code(code) => { | 
|---|
| 266 | seen_error_match = Some(code.span()); | 
|---|
| 267 | } | 
|---|
| 268 | &ErrorMatchKind::Pattern { ref pattern, level } => { | 
|---|
| 269 | seen_error_match = Some(pattern.span()); | 
|---|
| 270 | // If we found a diagnostic with a level annotation, make sure that all | 
|---|
| 271 | // diagnostics of that level have annotations, even if we don't end up finding a matching diagnostic | 
|---|
| 272 | // for this pattern. | 
|---|
| 273 | if lowest_annotation_level > level { | 
|---|
| 274 | lowest_annotation_level = level; | 
|---|
| 275 | } | 
|---|
| 276 | } | 
|---|
| 277 | } | 
|---|
| 278 |  | 
|---|
| 279 | if let Some(msgs) = messages.get_mut(line.get()) { | 
|---|
| 280 | match kind { | 
|---|
| 281 | &ErrorMatchKind::Pattern { ref pattern, level } => { | 
|---|
| 282 | let found = msgs | 
|---|
| 283 | .iter() | 
|---|
| 284 | .position(|msg| pattern.matches(&msg.message) && msg.level == level); | 
|---|
| 285 | if let Some(found) = found { | 
|---|
| 286 | msgs.remove(found); | 
|---|
| 287 | continue; | 
|---|
| 288 | } | 
|---|
| 289 | } | 
|---|
| 290 | ErrorMatchKind::Code(code) => { | 
|---|
| 291 | for (i, msg) in msgs.iter().enumerate() { | 
|---|
| 292 | if msg.level != Level::Error { | 
|---|
| 293 | continue; | 
|---|
| 294 | } | 
|---|
| 295 | let Some(msg_code) = &msg.code else { continue }; | 
|---|
| 296 | let Some(msg) = msg_code.strip_prefix(&diagnostic_code_prefix) else { | 
|---|
| 297 | continue; | 
|---|
| 298 | }; | 
|---|
| 299 | if msg == **code { | 
|---|
| 300 | msgs.remove(i); | 
|---|
| 301 | continue 'err; | 
|---|
| 302 | } | 
|---|
| 303 | } | 
|---|
| 304 | } | 
|---|
| 305 | } | 
|---|
| 306 | } | 
|---|
| 307 |  | 
|---|
| 308 | errors.push(match kind { | 
|---|
| 309 | ErrorMatchKind::Pattern { pattern, .. } => Error::PatternNotFound { | 
|---|
| 310 | pattern: pattern.clone(), | 
|---|
| 311 | expected_line: Some(line), | 
|---|
| 312 | }, | 
|---|
| 313 | ErrorMatchKind::Code(code) => Error::CodeNotFound { | 
|---|
| 314 | code: Spanned::new( | 
|---|
| 315 | format!( "{}{} ", diagnostic_code_prefix, **code), | 
|---|
| 316 | code.span(), | 
|---|
| 317 | ), | 
|---|
| 318 | expected_line: Some(line), | 
|---|
| 319 | }, | 
|---|
| 320 | }); | 
|---|
| 321 | } | 
|---|
| 322 |  | 
|---|
| 323 | let required_annotation_level = self | 
|---|
| 324 | .find_one( "`require_annotations_for_level` annotations", |r| { | 
|---|
| 325 | r.require_annotations_for_level.clone() | 
|---|
| 326 | })?; | 
|---|
| 327 |  | 
|---|
| 328 | let required_annotation_level = required_annotation_level | 
|---|
| 329 | .into_inner() | 
|---|
| 330 | .map_or(lowest_annotation_level, |l| *l); | 
|---|
| 331 | let filter = |mut msgs: Vec<Message>| -> Vec<_> { | 
|---|
| 332 | msgs.retain(|msg| msg.level >= required_annotation_level); | 
|---|
| 333 | msgs | 
|---|
| 334 | }; | 
|---|
| 335 |  | 
|---|
| 336 | let require_annotations = self.require_annotations(); | 
|---|
| 337 |  | 
|---|
| 338 | if let Some(Spanned { content: true, .. }) = require_annotations { | 
|---|
| 339 | let messages_from_unknown_file_or_line = filter(messages_from_unknown_file_or_line); | 
|---|
| 340 | if !messages_from_unknown_file_or_line.is_empty() { | 
|---|
| 341 | errors.push(Error::ErrorsWithoutPattern { | 
|---|
| 342 | path: None, | 
|---|
| 343 | msgs: messages_from_unknown_file_or_line, | 
|---|
| 344 | }); | 
|---|
| 345 | } | 
|---|
| 346 |  | 
|---|
| 347 | for (line, msgs) in messages.into_iter().enumerate() { | 
|---|
| 348 | let msgs = filter(msgs); | 
|---|
| 349 | if !msgs.is_empty() { | 
|---|
| 350 | let line = NonZeroUsize::new(line).expect( "line 0 is always empty"); | 
|---|
| 351 | errors.push(Error::ErrorsWithoutPattern { | 
|---|
| 352 | path: Some((self.status.path().to_path_buf(), line)), | 
|---|
| 353 | msgs, | 
|---|
| 354 | }); | 
|---|
| 355 | } | 
|---|
| 356 | } | 
|---|
| 357 | } | 
|---|
| 358 |  | 
|---|
| 359 | match (require_annotations, seen_error_match) { | 
|---|
| 360 | ( | 
|---|
| 361 | Some(Spanned { | 
|---|
| 362 | content: false, | 
|---|
| 363 | span: mode, | 
|---|
| 364 | }), | 
|---|
| 365 | Some(span), | 
|---|
| 366 | ) => errors.push(Error::PatternFoundInPassTest { mode, span }), | 
|---|
| 367 | (Some(Spanned { content: true, .. }), None) => errors.push(Error::NoPatternsFound), | 
|---|
| 368 | _ => {} | 
|---|
| 369 | } | 
|---|
| 370 | Ok(()) | 
|---|
| 371 | } | 
|---|
| 372 |  | 
|---|
| 373 | pub(crate) fn run_test(&mut self, build_manager: &Arc<BuildManager>) -> TestResult { | 
|---|
| 374 | self.patch_out_dir(); | 
|---|
| 375 |  | 
|---|
| 376 | let mut cmd = self.build_command(build_manager)?; | 
|---|
| 377 | let stdin = self.status.path().with_extension(self.extension( "stdin")); | 
|---|
| 378 | if stdin.exists() { | 
|---|
| 379 | cmd.stdin(std::fs::File::open(stdin).unwrap()); | 
|---|
| 380 | } | 
|---|
| 381 |  | 
|---|
| 382 | let output = build_manager.config.run_command(&mut cmd)?; | 
|---|
| 383 |  | 
|---|
| 384 | let output = self.check_test_result(&cmd, output)?; | 
|---|
| 385 |  | 
|---|
| 386 | for rev in self.comments() { | 
|---|
| 387 | for custom in rev.custom.values() { | 
|---|
| 388 | for flag in &custom.content { | 
|---|
| 389 | flag.post_test_action(self, &output, build_manager)?; | 
|---|
| 390 | } | 
|---|
| 391 | } | 
|---|
| 392 | } | 
|---|
| 393 | Ok(TestOk::Ok) | 
|---|
| 394 | } | 
|---|
| 395 |  | 
|---|
| 396 | pub(crate) fn find_one_custom(&self, arg: &str) -> Result<OptWithLine<&dyn Flag>, Errored> { | 
|---|
| 397 | self.find_one(arg, |r| { | 
|---|
| 398 | r.custom | 
|---|
| 399 | .get(arg) | 
|---|
| 400 | .map(|s| { | 
|---|
| 401 | assert_eq!(s.len(), 1); | 
|---|
| 402 | Spanned::new(&*s[0], s.span()) | 
|---|
| 403 | }) | 
|---|
| 404 | .into() | 
|---|
| 405 | }) | 
|---|
| 406 | } | 
|---|
| 407 |  | 
|---|
| 408 | pub(crate) fn aborted(&self) -> Result<(), Errored> { | 
|---|
| 409 | self.config.aborted() | 
|---|
| 410 | } | 
|---|
| 411 |  | 
|---|
| 412 | /// All the environment variables set for the given revision | 
|---|
| 413 | pub fn envs(&self) -> impl Iterator<Item = (&str, &str)> { | 
|---|
| 414 | self.comments() | 
|---|
| 415 | .flat_map(|r| r.env_vars.iter()) | 
|---|
| 416 | .map(|(k, v)| (k.as_ref(), v.as_ref())) | 
|---|
| 417 | } | 
|---|
| 418 | } | 
|---|
| 419 |  | 
|---|