1use std::{
2 path::{Path, PathBuf},
3 process::{Command, ExitStatus, Stdio},
4};
5
6use thiserror::Error;
7
8use crate::{Config, ConfigError, GenerationError, refresh_generated_fixtures};
9
10#[derive(Clone, Debug)]
11pub struct TestOptions {
12 pub project_root: PathBuf,
13 pub filter: Option<String>,
14}
15
16impl TestOptions {
17 #[must_use]
18 pub fn new(project_root: impl Into<PathBuf>) -> Self {
19 Self {
20 project_root: project_root.into(),
21 filter: None,
22 }
23 }
24
25 #[must_use]
26 pub fn with_filter(mut self, filter: impl Into<String>) -> Self {
27 self.filter = Some(filter.into());
28 self
29 }
30}
31
32pub fn run_tests(options: &TestOptions) -> Result<(), TestError> {
37 let project_root = canonical_project_root(&options.project_root)?;
38 let config = Config::load(&project_root)?;
39 let generated = refresh_generated_fixtures(&project_root, &config)?;
40 println!(
41 "Testing {} Hblank fixture files with Cargo",
42 generated.fixture_files.len()
43 );
44
45 let manifest = project_root.join(".hblank/Cargo.toml");
46 let target = project_root.join(".hblank/target");
47 let mut command = Command::new("cargo");
48 command
49 .arg("test")
50 .arg("--manifest-path")
51 .arg(&manifest)
52 .arg("--target-dir")
53 .arg(&target)
54 .stdin(Stdio::null());
55 if let Some(filter) = &options.filter {
56 command.arg(filter);
57 }
58 let status = command.status().map_err(TestError::Process)?;
59 if status.success() {
60 Ok(())
61 } else {
62 Err(TestError::TestsFailed(status))
63 }
64}
65
66fn canonical_project_root(path: &Path) -> Result<PathBuf, TestError> {
67 path.canonicalize()
68 .map_err(|source| TestError::ProjectRoot {
69 path: path.to_path_buf(),
70 source,
71 })
72}
73
74#[derive(Debug, Error)]
75pub enum TestError {
76 #[error("could not resolve Hblank project root {path}: {source}")]
77 ProjectRoot {
78 path: PathBuf,
79 source: std::io::Error,
80 },
81 #[error(transparent)]
82 Config(#[from] ConfigError),
83 #[error(transparent)]
84 Generation(#[from] GenerationError),
85 #[error("could not run Hblank tests: {0}")]
86 Process(std::io::Error),
87 #[error("Hblank tests exited unsuccessfully: {0}")]
88 TestsFailed(ExitStatus),
89}