ui_test/custom_flags/
run.rs1use super::Flag;
4use crate::{
5 build_manager::BuildManager, display, per_test_config::TestConfig,
6 status_emitter::RevisionStyle, CommandBuilder, Error, Errored, OutputConflictHandling, TestOk,
7};
8use bstr::ByteSlice;
9use spanned::Spanned;
10use std::{path::Path, process::Output};
11
12#[derive(Debug, Copy, Clone)]
13pub struct Run {
15 pub exit_code: i32,
17 pub output_conflict_handling: Option<OutputConflictHandling>,
19}
20
21impl 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
113fn 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}