1use crate::cli::types::{CliTestResults, ACCEPTED_EXTENSIONS};
6use crate::config::load_config_from_file;
7use crate::error::{CleanroomError, Result};
8use std::path::{Path, PathBuf};
9use tracing::{debug, info};
10use walkdir::WalkDir;
11
12pub fn discover_test_files(path: &PathBuf) -> Result<Vec<PathBuf>> {
19 let mut test_files = Vec::new();
20
21 if path.is_file() {
22 let path_str = path.to_str().unwrap_or("");
24 if ACCEPTED_EXTENSIONS
25 .iter()
26 .any(|ext| path_str.ends_with(ext))
27 {
28 test_files.push(path.clone());
29 } else {
30 return Err(CleanroomError::validation_error(format!(
31 "File must have .toml or .clnrm.toml extension: {}",
32 path.display()
33 )));
34 }
35 } else if path.is_dir() {
36 info!("Discovering test files in: {}", path.display());
38
39 for entry in WalkDir::new(path)
40 .follow_links(true)
41 .into_iter()
42 .filter_map(|e| e.ok())
43 {
44 let entry_path = entry.path();
45 let path_str = entry_path.to_str().unwrap_or("");
46
47 if ACCEPTED_EXTENSIONS
49 .iter()
50 .any(|ext| path_str.ends_with(ext))
51 && entry_path.is_file()
52 {
53 test_files.push(entry_path.to_path_buf());
54 debug!("Found test file: {}", entry_path.display());
55 }
56 }
57
58 if test_files.is_empty() {
59 return Err(CleanroomError::validation_error(format!(
60 "No test files (.toml or .clnrm.toml) found in directory: {}",
61 path.display()
62 )));
63 }
64
65 info!("Discovered {} test file(s)", test_files.len());
66 } else {
67 return Err(CleanroomError::validation_error(format!(
68 "Path is neither a file nor a directory: {}",
69 path.display()
70 )));
71 }
72
73 Ok(test_files)
74}
75
76pub fn parse_toml_test(path: &Path) -> Result<crate::config::TestConfig> {
78 load_config_from_file(path)
79}
80
81pub fn setup_logging(verbosity: u8) -> Result<()> {
83 use tracing_subscriber::{fmt, EnvFilter};
84
85 let filter = match verbosity {
86 0 => "info",
87 1 => "debug",
88 _ => "trace",
89 };
90
91 let subscriber = fmt::Subscriber::builder()
92 .with_env_filter(EnvFilter::new(filter))
93 .finish();
94
95 tracing::subscriber::set_global_default(subscriber).map_err(|e| {
96 CleanroomError::internal_error("Failed to set up logging").with_source(e.to_string())
97 })?;
98
99 Ok(())
100}
101
102pub fn generate_junit_xml(results: &CliTestResults) -> Result<String> {
110 use junit_report::{Duration, OffsetDateTime, Report, TestCase, TestSuite};
111
112 let mut test_suite = TestSuite::new("cleanroom_tests");
113 test_suite.set_timestamp(OffsetDateTime::now_utc());
114
115 for test in &results.tests {
116 let duration_secs = test.duration_ms as f64 / 1000.0;
117 let test_case = if !test.passed {
118 if let Some(error) = &test.error {
119 TestCase::failure(
120 &test.name,
121 Duration::seconds(duration_secs as i64),
122 "test_failure",
123 error,
124 )
125 } else {
126 TestCase::failure(
127 &test.name,
128 Duration::seconds(duration_secs as i64),
129 "test_failure",
130 "Test failed without error message",
131 )
132 }
133 } else {
134 TestCase::success(&test.name, Duration::seconds(duration_secs as i64))
135 };
136
137 test_suite.add_testcase(test_case);
138 }
139
140 let mut report = Report::new();
141 report.add_testsuite(test_suite);
142
143 let mut xml_output = Vec::new();
144 report.write_xml(&mut xml_output).map_err(|e| {
145 CleanroomError::internal_error("JUnit XML generation failed")
146 .with_context("Failed to serialize test results to JUnit XML")
147 .with_source(e.to_string())
148 })?;
149
150 String::from_utf8(xml_output).map_err(|e| {
151 CleanroomError::internal_error("JUnit XML encoding failed")
152 .with_context("Failed to convert JUnit XML to UTF-8 string")
153 .with_source(e.to_string())
154 })
155}