integration_tests/
nextest_cli.rs1use camino::Utf8PathBuf;
5use color_eyre::{
6 Result,
7 eyre::{Context, bail},
8};
9use nextest_metadata::TestListSummary;
10use std::{
11 borrow::Cow,
12 collections::HashMap,
13 ffi::OsString,
14 fmt,
15 process::{Command, ExitStatus},
16};
17
18pub fn cargo_bin() -> String {
19 match std::env::var("CARGO") {
20 Ok(v) => v,
21 Err(std::env::VarError::NotPresent) => "cargo".to_owned(),
22 Err(err) => panic!("error obtaining CARGO env var: {err}"),
23 }
24}
25
26#[derive(Clone, Debug)]
27pub struct CargoNextestCli {
28 bin: Utf8PathBuf,
29 args: Vec<String>,
30 envs: HashMap<OsString, OsString>,
31 unchecked: bool,
32}
33
34impl CargoNextestCli {
35 pub fn for_test() -> Self {
36 let bin = std::env::var("NEXTEST_BIN_EXE_cargo-nextest-dup")
37 .expect("unable to find cargo-nextest-dup");
38 Self {
39 bin: bin.into(),
40 args: vec!["nextest".to_owned()],
41 envs: HashMap::new(),
42 unchecked: false,
43 }
44 }
45
46 pub fn for_script() -> Result<Self> {
51 let cargo_bin = cargo_bin();
52 let mut command = std::process::Command::new(&cargo_bin);
53 command.args([
54 "run",
55 "--bin",
56 "cargo-nextest-dup",
57 "--",
58 "nextest",
59 "debug",
60 "current-exe",
61 ]);
62 let output = command.output().wrap_err("failed to get current exe")?;
63
64 let output = CargoNextestOutput {
65 command: Box::new(command),
66 exit_status: output.status,
67 stdout: output.stdout,
68 stderr: output.stderr,
69 };
70
71 if !output.exit_status.success() {
72 bail!("failed to get current exe:\n\n{output:?}");
73 }
74
75 let exe =
77 String::from_utf8(output.stdout).wrap_err("current exe output isn't valid UTF-8")?;
78
79 Ok(Self {
80 bin: Utf8PathBuf::from(exe.trim_end()),
81 args: vec!["nextest".to_owned()],
82 envs: HashMap::new(),
83 unchecked: false,
84 })
85 }
86
87 pub fn arg(&mut self, arg: impl Into<String>) -> &mut Self {
88 self.args.push(arg.into());
89 self
90 }
91
92 pub fn args(&mut self, arg: impl IntoIterator<Item = impl Into<String>>) -> &mut Self {
93 self.args.extend(arg.into_iter().map(Into::into));
94 self
95 }
96
97 pub fn env(&mut self, k: impl Into<OsString>, v: impl Into<OsString>) -> &mut Self {
98 self.envs.insert(k.into(), v.into());
99 self
100 }
101
102 pub fn envs(
103 &mut self,
104 envs: impl IntoIterator<Item = (impl Into<OsString>, impl Into<OsString>)>,
105 ) -> &mut Self {
106 self.envs
107 .extend(envs.into_iter().map(|(k, v)| (k.into(), v.into())));
108 self
109 }
110
111 pub fn unchecked(&mut self, unchecked: bool) -> &mut Self {
112 self.unchecked = unchecked;
113 self
114 }
115
116 pub fn output(&self) -> CargoNextestOutput {
117 let mut command = std::process::Command::new(&self.bin);
118 command.args(&self.args);
119 command.envs(&self.envs);
120 let output = command.output().expect("failed to execute");
121
122 let ret = CargoNextestOutput {
123 command: Box::new(command),
124 exit_status: output.status,
125 stdout: output.stdout,
126 stderr: output.stderr,
127 };
128
129 if !self.unchecked && !output.status.success() {
130 panic!("command failed:\n\n{ret}");
131 }
132
133 ret
134 }
135}
136
137pub struct CargoNextestOutput {
138 pub command: Box<Command>,
139 pub exit_status: ExitStatus,
140 pub stdout: Vec<u8>,
141 pub stderr: Vec<u8>,
142}
143
144impl CargoNextestOutput {
145 pub fn stdout_as_str(&self) -> Cow<'_, str> {
146 String::from_utf8_lossy(&self.stdout)
147 }
148
149 pub fn stderr_as_str(&self) -> Cow<'_, str> {
150 String::from_utf8_lossy(&self.stderr)
151 }
152
153 pub fn decode_test_list_json(&self) -> Result<TestListSummary> {
154 Ok(serde_json::from_slice(&self.stdout)?)
155 }
156
157 pub fn to_snapshot(&self) -> String {
160 let output = format!(
163 "exit code: {:?}\n\
164 --- stdout ---\n{}\n\n--- stderr ---\n{}\n",
165 self.exit_status.code(),
166 String::from_utf8_lossy(&self.stdout),
167 String::from_utf8_lossy(&self.stderr),
168 );
169
170 let output = output.replace("exit status: ", "exit status|code: ");
172 output.replace("exit code: ", "exit status|code: ")
173 }
174}
175
176impl fmt::Display for CargoNextestOutput {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 write!(
179 f,
180 "command: {:?}\nexit code: {:?}\n\
181 --- stdout ---\n{}\n\n--- stderr ---\n{}\n\n",
182 self.command,
183 self.exit_status.code(),
184 String::from_utf8_lossy(&self.stdout),
185 String::from_utf8_lossy(&self.stderr)
186 )
187 }
188}
189
190impl fmt::Debug for CargoNextestOutput {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 fmt::Display::fmt(self, f)
194 }
195}