cargo_test_filter/
runner.rs1use anyhow::{Context, Result};
2use std::collections::HashMap;
3use std::process::{Command, Stdio};
4use crate::cli::TestFilterArgs;
5use crate::discovery::{TestFunction, TestTarget, TestType};
6
7pub struct TestRunner<'a> {
8 args: &'a TestFilterArgs,
9}
10
11impl<'a> TestRunner<'a> {
12 pub fn new(args: &'a TestFilterArgs) -> Self {
13 Self { args }
14 }
15
16 pub fn list_test_functions(&self, functions: &[TestFunction]) {
18 if functions.is_empty() {
19 println!("No tests match the filter criteria.");
20 return;
21 }
22
23 println!("Found {} test function(s):\n", functions.len());
24
25 let mut integration_tests: HashMap<String, Vec<&TestFunction>> = HashMap::new();
27 let mut unit_tests: Vec<&TestFunction> = Vec::new();
28
29 for func in functions {
30 match func.test_type {
31 TestType::Integration => {
32 integration_tests
33 .entry(func.target_name.clone())
34 .or_default()
35 .push(func);
36 }
37 TestType::Unit => {
38 unit_tests.push(func);
39 }
40 TestType::Doc => {}
41 }
42 }
43
44 if !integration_tests.is_empty() {
46 println!("Integration Tests:");
47 for (target, funcs) in &integration_tests {
48 println!(" tests/{}.rs:", target);
49 for func in funcs {
50 if func.tags.is_empty() {
51 println!(" - {}", func.name);
52 } else {
53 println!(" - {} [{}]", func.name, func.tags.join(", "));
54 }
55 }
56 }
57 println!();
58 }
59
60 if !unit_tests.is_empty() {
62 println!("Unit Tests:");
63 for func in &unit_tests {
64 if func.tags.is_empty() {
65 println!(" - {}", func.name);
66 } else {
67 println!(" - {} [{}]", func.name, func.tags.join(", "));
68 }
69 }
70 println!();
71 }
72 }
73
74 pub fn run_test_functions(&self, functions: &[TestFunction]) -> Result<()> {
77 if functions.is_empty() {
78 println!("No tests match the filter criteria.");
79 return Ok(());
80 }
81
82 println!("Running {} test function(s)...", functions.len());
83 if self.args.verbose {
84 for func in functions {
85 println!(" - {}::{} (tags: {:?})", func.target_name, func.name, func.tags);
86 }
87 println!();
88 }
89
90 let mut integration_tests: HashMap<String, Vec<&TestFunction>> = HashMap::new();
92 let mut unit_tests: Vec<&TestFunction> = Vec::new();
93
94 for func in functions {
95 match func.test_type {
96 TestType::Integration => {
97 integration_tests
98 .entry(func.target_name.clone())
99 .or_default()
100 .push(func);
101 }
102 TestType::Unit => {
103 unit_tests.push(func);
104 }
105 TestType::Doc => {
106 }
108 }
109 }
110
111 for (target_name, funcs) in &integration_tests {
113 self.run_integration_test_functions(target_name, funcs)?;
114 }
115
116 if !unit_tests.is_empty() {
118 self.run_unit_test_functions(&unit_tests)?;
119 }
120
121 Ok(())
122 }
123
124 fn run_integration_test_functions(&self, target_name: &str, functions: &[&TestFunction]) -> Result<()> {
126 for func in functions {
128 let mut cmd = Command::new("cargo");
129 cmd.arg("test");
130 cmd.arg("--test");
131 cmd.arg(target_name);
132 cmd.arg("--");
133 cmd.arg("--exact");
134 cmd.arg(&func.name);
135
136 if self.args.verbose {
138 cmd.arg("--nocapture");
139 }
140
141 for arg in &self.args.test_args {
143 cmd.arg(arg);
144 }
145
146 if self.args.verbose {
147 println!("Executing: {:?}\n", cmd);
148 }
149
150 let status = cmd
151 .stdin(Stdio::inherit())
152 .stdout(Stdio::inherit())
153 .stderr(Stdio::inherit())
154 .status()
155 .context("Failed to execute cargo test")?;
156
157 if !status.success() {
158 anyhow::bail!("Tests failed with exit code: {:?}", status.code());
159 }
160 }
161
162 Ok(())
163 }
164
165 fn run_unit_test_functions(&self, functions: &[&TestFunction]) -> Result<()> {
167 for func in functions {
170 let mut cmd = Command::new("cargo");
171 cmd.arg("test");
172 cmd.arg("--lib");
173 cmd.arg(format!("::{}", func.name));
175 cmd.arg("--");
176
177 if self.args.verbose {
179 cmd.arg("--nocapture");
180 }
181
182 for arg in &self.args.test_args {
184 cmd.arg(arg);
185 }
186
187 if self.args.verbose {
188 println!("Executing: {:?}\n", cmd);
189 }
190
191 let status = cmd
192 .stdin(Stdio::inherit())
193 .stdout(Stdio::inherit())
194 .stderr(Stdio::inherit())
195 .status()
196 .context("Failed to execute cargo test")?;
197
198 if !status.success() {
199 anyhow::bail!("Tests failed with exit code: {:?}", status.code());
200 }
201 }
202
203 Ok(())
204 }
205
206 pub fn run_tests(&self, targets: &[TestTarget]) -> Result<()> {
208 if targets.is_empty() {
209 println!("No tests match the filter criteria.");
210 return Ok(());
211 }
212
213 println!("Running {} test target(s)...", targets.len());
214 if self.args.verbose {
215 for target in targets {
216 println!(" - {} ({:?})", target.name, target.test_type);
217 }
218 }
219
220 if self.args.integration {
222 self.run_integration_tests(targets)
224 } else if self.args.unit {
225 self.run_unit_tests(targets)
227 } else {
228 self.run_general_tests(targets)
230 }
231 }
232
233 fn run_integration_tests(&self, targets: &[TestTarget]) -> Result<()> {
235 for target in targets {
236 let mut cmd = Command::new("cargo");
237 cmd.arg("test");
238 cmd.arg("--test");
239 cmd.arg(&target.name);
240
241 if let Some(ref name) = self.args.name {
243 cmd.arg(name);
244 }
245
246 self.add_test_args(&mut cmd);
247
248 if self.args.verbose {
249 println!("\nExecuting: {:?}\n", cmd);
250 }
251
252 let status = cmd
253 .stdin(Stdio::inherit())
254 .stdout(Stdio::inherit())
255 .stderr(Stdio::inherit())
256 .status()
257 .context("Failed to execute cargo test")?;
258
259 if !status.success() {
260 anyhow::bail!("Tests failed with exit code: {:?}", status.code());
261 }
262 }
263 Ok(())
264 }
265
266 fn run_unit_tests(&self, _targets: &[TestTarget]) -> Result<()> {
268 let mut cmd = Command::new("cargo");
269 cmd.arg("test");
270 cmd.arg("--lib");
271
272 if let Some(ref name) = self.args.name {
274 cmd.arg(name);
275 }
276
277 self.add_test_args(&mut cmd);
278
279 if self.args.verbose {
280 println!("\nExecuting: {:?}\n", cmd);
281 }
282
283 let status = cmd
284 .stdin(Stdio::inherit())
285 .stdout(Stdio::inherit())
286 .stderr(Stdio::inherit())
287 .status()
288 .context("Failed to execute cargo test")?;
289
290 if !status.success() {
291 anyhow::bail!("Tests failed with exit code: {:?}", status.code());
292 }
293
294 Ok(())
295 }
296
297 fn run_general_tests(&self, _targets: &[TestTarget]) -> Result<()> {
299 let mut cmd = Command::new("cargo");
300 cmd.arg("test");
301
302 if let Some(ref name) = self.args.name {
304 cmd.arg(name);
305 }
306
307 self.add_test_args(&mut cmd);
308
309 if self.args.verbose {
310 println!("\nExecuting: {:?}\n", cmd);
311 }
312
313 let status = cmd
314 .stdin(Stdio::inherit())
315 .stdout(Stdio::inherit())
316 .stderr(Stdio::inherit())
317 .status()
318 .context("Failed to execute cargo test")?;
319
320 if !status.success() {
321 anyhow::bail!("Tests failed with exit code: {:?}", status.code());
322 }
323
324 Ok(())
325 }
326
327 fn add_test_args(&self, cmd: &mut Command) {
329 let mut needs_separator = true;
330
331 if self.args.verbose {
333 if needs_separator {
334 cmd.arg("--");
335 needs_separator = false;
336 }
337 cmd.arg("--nocapture");
338 }
339
340 if !self.args.test_args.is_empty() {
342 if needs_separator {
343 cmd.arg("--");
344 }
345 for arg in &self.args.test_args {
346 cmd.arg(arg);
347 }
348 }
349 }
350
351 pub fn run_all_tests(&self) -> Result<()> {
353 println!("Running all tests...");
354
355 let mut cmd = Command::new("cargo");
356 cmd.arg("test");
357
358 self.add_test_args(&mut cmd);
359
360 let status = cmd
361 .stdin(Stdio::inherit())
362 .stdout(Stdio::inherit())
363 .stderr(Stdio::inherit())
364 .status()
365 .context("Failed to execute cargo test")?;
366
367 if !status.success() {
368 anyhow::bail!("Tests failed with exit code: {:?}", status.code());
369 }
370
371 Ok(())
372 }
373}