| 1 | //! Types used for running tests after they pass compilation | 
| 2 |  | 
|---|
| 3 | use super::Flag; | 
|---|
| 4 | use crate::{ | 
|---|
| 5 | build_manager::BuildManager, display, per_test_config::TestConfig, | 
|---|
| 6 | status_emitter::RevisionStyle, CommandBuilder, Error, Errored, OutputConflictHandling, TestOk, | 
|---|
| 7 | }; | 
|---|
| 8 | use bstr::ByteSlice; | 
|---|
| 9 | use spanned::Spanned; | 
|---|
| 10 | use std::{path::Path, process::Output}; | 
|---|
| 11 |  | 
|---|
| 12 | #[ derive(Debug, Copy, Clone)] | 
|---|
| 13 | /// Run a test after successfully compiling it | 
|---|
| 14 | pub struct Run { | 
|---|
| 15 | /// The exit code that the test is expected to emit. | 
|---|
| 16 | pub exit_code: i32, | 
|---|
| 17 | /// How to handle output conflicts | 
|---|
| 18 | pub output_conflict_handling: Option<OutputConflictHandling>, | 
|---|
| 19 | } | 
|---|
| 20 |  | 
|---|
| 21 | impl Flag for Run { | 
|---|
| 22 | fn must_be_unique(&self) -> bool { | 
|---|
| 23 | true | 
|---|
| 24 | } | 
|---|
| 25 | fn clone_inner(&self) -> Box<dyn Flag> { | 
|---|
| 26 | Box::new(*self) | 
|---|
| 27 | } | 
|---|
| 28 |  | 
|---|
| 29 | fn post_test_action( | 
|---|
| 30 | &self, | 
|---|
| 31 | config: &TestConfig, | 
|---|
| 32 | _output: &Output, | 
|---|
| 33 | build_manager: &BuildManager, | 
|---|
| 34 | ) -> Result<(), Errored> { | 
|---|
| 35 | let mut cmd = config.build_command(build_manager)?; | 
|---|
| 36 | let exit_code = self.exit_code; | 
|---|
| 37 | let revision = config.extension( "run"); | 
|---|
| 38 | let mut config = TestConfig { | 
|---|
| 39 | config: config.config.clone(), | 
|---|
| 40 | comments: config.comments.clone(), | 
|---|
| 41 | aux_dir: config.aux_dir.clone(), | 
|---|
| 42 | status: config.status.for_revision(&revision, RevisionStyle::Show), | 
|---|
| 43 | }; | 
|---|
| 44 | if let Some(och) = self.output_conflict_handling { | 
|---|
| 45 | config.config.output_conflict_handling = och; | 
|---|
| 46 | } | 
|---|
| 47 | build_manager.add_new_job(config, move |config| { | 
|---|
| 48 | cmd.arg( "--print").arg( "file-names"); | 
|---|
| 49 | let output = cmd.output().unwrap(); | 
|---|
| 50 | config.aborted()?; | 
|---|
| 51 | assert!(output.status.success(), "{cmd:#?} : {output:#?} "); | 
|---|
| 52 |  | 
|---|
| 53 | let mut files = output.stdout.lines(); | 
|---|
| 54 | let file = files.next().unwrap(); | 
|---|
| 55 | assert_eq!(files.next(), None); | 
|---|
| 56 | let file = std::str::from_utf8(file).unwrap(); | 
|---|
| 57 | let mut envs = std::mem::take(&mut config.config.program.envs); | 
|---|
| 58 | config.config.program = CommandBuilder::cmd(config.config.out_dir.join(file)); | 
|---|
| 59 | envs.extend(config.envs().map(|(k, v)| (k.into(), Some(v.into())))); | 
|---|
| 60 | config.config.program.envs = envs; | 
|---|
| 61 |  | 
|---|
| 62 | let mut exe = config.config.program.build(Path::new( "")); | 
|---|
| 63 | let stdin = config | 
|---|
| 64 | .status | 
|---|
| 65 | .path() | 
|---|
| 66 | .with_extension(format!( "{revision} .stdin")); | 
|---|
| 67 | if stdin.exists() { | 
|---|
| 68 | exe.stdin(std::fs::File::open(stdin).unwrap()); | 
|---|
| 69 | } | 
|---|
| 70 | let output = exe.output().unwrap_or_else(|err| { | 
|---|
| 71 | panic!( | 
|---|
| 72 | "exe file: {} : {err} ", | 
|---|
| 73 | display(&config.config.program.program) | 
|---|
| 74 | ) | 
|---|
| 75 | }); | 
|---|
| 76 |  | 
|---|
| 77 | config.aborted()?; | 
|---|
| 78 |  | 
|---|
| 79 | let mut errors = vec![]; | 
|---|
| 80 |  | 
|---|
| 81 | config.check_test_output(&mut errors, &output.stdout, &output.stderr); | 
|---|
| 82 |  | 
|---|
| 83 | let status = output.status; | 
|---|
| 84 | if status.code() != Some(exit_code) { | 
|---|
| 85 | errors.push(Error::ExitStatus { | 
|---|
| 86 | status, | 
|---|
| 87 | expected: exit_code, | 
|---|
| 88 | reason: match (exit_code, status.code()) { | 
|---|
| 89 | (_, Some(101)) => get_panic_span(&output.stderr), | 
|---|
| 90 | (0, _) => { | 
|---|
| 91 | Spanned::dummy( "the test was expected to run successfully".into()) | 
|---|
| 92 | } | 
|---|
| 93 | (101, _) => Spanned::dummy( "the test was expected to panic".into()), | 
|---|
| 94 | _ => Spanned::dummy(String::new()), | 
|---|
| 95 | }, | 
|---|
| 96 | }) | 
|---|
| 97 | } | 
|---|
| 98 | if errors.is_empty() { | 
|---|
| 99 | Ok(TestOk::Ok) | 
|---|
| 100 | } else { | 
|---|
| 101 | Err(Errored { | 
|---|
| 102 | command: format!( "{exe:?} "), | 
|---|
| 103 | errors, | 
|---|
| 104 | stderr: output.stderr, | 
|---|
| 105 | stdout: output.stdout, | 
|---|
| 106 | }) | 
|---|
| 107 | } | 
|---|
| 108 | }); | 
|---|
| 109 | Ok(()) | 
|---|
| 110 | } | 
|---|
| 111 | } | 
|---|
| 112 |  | 
|---|
| 113 | fn get_panic_span(stderr: &[u8]) -> Spanned<String> { | 
|---|
| 114 | let mut lines = stderr.lines(); | 
|---|
| 115 | while let Some(line) = lines.next() { | 
|---|
| 116 | if let Some((_, location)) = line.split_once_str( b"panicked at ") { | 
|---|
| 117 | let mut parts = location.split(|&c| c == b':'); | 
|---|
| 118 | let Some(filename) = parts.next() else { | 
|---|
| 119 | continue; | 
|---|
| 120 | }; | 
|---|
| 121 | let Some(line) = parts.next() else { continue }; | 
|---|
| 122 | let Some(col) = parts.next() else { continue }; | 
|---|
| 123 | let message = lines | 
|---|
| 124 | .next() | 
|---|
| 125 | .and_then(|msg| msg.to_str().ok()) | 
|---|
| 126 | .unwrap_or( "the test panicked during execution"); | 
|---|
| 127 | let Ok(line) = line.to_str() else { continue }; | 
|---|
| 128 | let Ok(col) = col.to_str() else { continue }; | 
|---|
| 129 | let Ok(filename) = filename.to_str() else { | 
|---|
| 130 | continue; | 
|---|
| 131 | }; | 
|---|
| 132 | let Ok(line) = line.parse::<usize>() else { | 
|---|
| 133 | continue; | 
|---|
| 134 | }; | 
|---|
| 135 | let Ok(col) = col.parse::<usize>() else { | 
|---|
| 136 | continue; | 
|---|
| 137 | }; | 
|---|
| 138 | let Ok(file) = Spanned::read_from_file(filename) else { | 
|---|
| 139 | continue; | 
|---|
| 140 | }; | 
|---|
| 141 | let Some(line) = line.checked_sub(1) else { | 
|---|
| 142 | continue; | 
|---|
| 143 | }; | 
|---|
| 144 | let Some(line) = file.lines().nth(line) else { | 
|---|
| 145 | continue; | 
|---|
| 146 | }; | 
|---|
| 147 | let Some(col) = col.checked_sub(1) else { | 
|---|
| 148 | continue; | 
|---|
| 149 | }; | 
|---|
| 150 | let span = line.span.inc_col_start(col); | 
|---|
| 151 | return Spanned::new(message.into(), span); | 
|---|
| 152 | } | 
|---|
| 153 | } | 
|---|
| 154 | Spanned::dummy( "".into()) | 
|---|
| 155 | } | 
|---|
| 156 |  | 
|---|