1use crate::cli::types::{CliTestResults, ACCEPTED_EXTENSIONS};
6use crate::config::load_config_from_file;
7use crate::error::{CleanroomError, Result};
8use std::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: &PathBuf) -> 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> {
104 use junit_report::{Duration, OffsetDateTime, Report, TestCase, TestSuite};
105
106 let mut test_suite = TestSuite::new("cleanroom_tests");
107 test_suite.set_timestamp(OffsetDateTime::now_utc());
108
109 for test in &results.tests {
110 let duration_secs = test.duration_ms as f64 / 1000.0;
111 let test_case = if !test.passed {
112 if let Some(error) = &test.error {
113 TestCase::failure(
114 &test.name,
115 Duration::seconds(duration_secs as i64),
116 "test_failure",
117 error,
118 )
119 } else {
120 TestCase::failure(
121 &test.name,
122 Duration::seconds(duration_secs as i64),
123 "test_failure",
124 "Test failed without error message",
125 )
126 }
127 } else {
128 TestCase::success(&test.name, Duration::seconds(duration_secs as i64))
129 };
130
131 test_suite.add_testcase(test_case);
132 }
133
134 let mut report = Report::new();
135 report.add_testsuite(test_suite);
136
137 let mut xml_output = Vec::new();
138 report.write_xml(&mut xml_output).map_err(|e| {
139 CleanroomError::internal_error("JUnit XML generation failed")
140 .with_context("Failed to serialize test results to JUnit XML")
141 .with_source(e.to_string())
142 })?;
143
144 String::from_utf8(xml_output).map_err(|e| {
145 CleanroomError::internal_error("JUnit XML encoding failed")
146 .with_context("Failed to convert JUnit XML to UTF-8 string")
147 .with_source(e.to_string())
148 })
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use std::fs;
155 use tempfile::TempDir;
156
157 #[test]
158 fn test_discover_test_files_single_file_valid() -> Result<()> {
159 let temp_dir = TempDir::new().map_err(|e| {
161 CleanroomError::internal_error("Failed to create temp dir").with_source(e.to_string())
162 })?;
163 let test_file = temp_dir.path().join("test.toml");
164 fs::write(&test_file, "test content").map_err(|e| {
165 CleanroomError::internal_error("Failed to write test file").with_source(e.to_string())
166 })?;
167
168 let result = discover_test_files(&test_file)?;
170
171 assert_eq!(result.len(), 1);
173 assert_eq!(result[0], test_file);
174 Ok(())
175 }
176
177 #[test]
178 fn test_discover_test_files_single_file_clnrm_toml() -> Result<()> {
179 let temp_dir = TempDir::new().map_err(|e| {
181 CleanroomError::internal_error("Failed to create temp dir").with_source(e.to_string())
182 })?;
183 let test_file = temp_dir.path().join("test.clnrm.toml");
184 fs::write(&test_file, "test content").map_err(|e| {
185 CleanroomError::internal_error("Failed to write test file").with_source(e.to_string())
186 })?;
187
188 let result = discover_test_files(&test_file)?;
190
191 assert_eq!(result.len(), 1);
193 assert_eq!(result[0], test_file);
194 Ok(())
195 }
196
197 #[test]
198 #[ignore = "Incomplete test data or implementation"]
199 fn test_discover_test_files_single_file_invalid_extension() -> Result<()> {
200 let temp_dir = TempDir::new().map_err(|e| {
202 CleanroomError::internal_error("Failed to create temp dir").with_source(e.to_string())
203 })?;
204 let test_file = temp_dir.path().join("test.txt");
205 fs::write(&test_file, "test content").map_err(|e| {
206 CleanroomError::internal_error("Failed to write test file").with_source(e.to_string())
207 })?;
208
209 let result = discover_test_files(&test_file);
211
212 assert!(result.is_err());
214 assert!(result
215 .unwrap_err()
216 .message
217 .contains("File must have .clnrm.toml extension"));
218 Ok(())
219 }
220
221 #[test]
222 fn test_discover_test_files_directory() -> Result<()> {
223 let temp_dir = TempDir::new().map_err(|e| {
225 CleanroomError::internal_error("Failed to create temp dir").with_source(e.to_string())
226 })?;
227
228 let test_file1 = temp_dir.path().join("test1.clnrm.toml");
230 let test_file2 = temp_dir.path().join("test2.clnrm.toml");
231 let ignored_file = temp_dir.path().join("ignored.txt");
232
233 fs::write(&test_file1, "test content 1").map_err(|e| {
234 CleanroomError::internal_error("Failed to write test file 1").with_source(e.to_string())
235 })?;
236 fs::write(&test_file2, "test content 2").map_err(|e| {
237 CleanroomError::internal_error("Failed to write test file 2").with_source(e.to_string())
238 })?;
239 fs::write(&ignored_file, "ignored content").map_err(|e| {
240 CleanroomError::internal_error("Failed to write ignored file")
241 .with_source(e.to_string())
242 })?;
243
244 let result = discover_test_files(&temp_dir.path().to_path_buf())?;
246
247 assert_eq!(result.len(), 2);
249 assert!(result
250 .iter()
251 .any(|p| p.file_name().unwrap_or_default() == "test1.clnrm.toml"));
252 assert!(result
253 .iter()
254 .any(|p| p.file_name().unwrap_or_default() == "test2.clnrm.toml"));
255 Ok(())
256 }
257
258 #[test]
259 #[ignore = "Incomplete test data or implementation"]
260 fn test_discover_test_files_directory_no_test_files() -> Result<()> {
261 let temp_dir = TempDir::new().map_err(|e| {
263 CleanroomError::internal_error("Failed to create temp dir").with_source(e.to_string())
264 })?;
265
266 let ignored_file = temp_dir.path().join("ignored.txt");
268 fs::write(&ignored_file, "ignored content").map_err(|e| {
269 CleanroomError::internal_error("Failed to write ignored file")
270 .with_source(e.to_string())
271 })?;
272
273 let result = discover_test_files(&temp_dir.path().to_path_buf());
275
276 assert!(result.is_err());
278 assert!(result
279 .unwrap_err()
280 .message
281 .contains("No test files (.clnrm.toml) found"));
282 Ok(())
283 }
284
285 #[test]
286 fn test_discover_test_files_nonexistent_path() -> Result<()> {
287 let nonexistent_path = PathBuf::from("nonexistent_path");
289
290 let result = discover_test_files(&nonexistent_path);
292
293 assert!(result.is_err());
295 assert!(result
296 .unwrap_err()
297 .message
298 .contains("Path is neither a file nor a directory"));
299 Ok(())
300 }
301
302 #[test]
303 fn test_setup_logging() -> Result<()> {
304 let result = setup_logging(0);
306
307 assert!(result.is_ok());
309 Ok(())
310 }
311
312 #[test]
313 #[ignore = "Incomplete test data or implementation"]
314 fn test_setup_logging_different_verbosity_levels() -> Result<()> {
315 assert!(setup_logging(0).is_ok());
317 assert!(setup_logging(1).is_ok());
318 assert!(setup_logging(2).is_ok());
319 assert!(setup_logging(5).is_ok());
320 Ok(())
321 }
322
323 #[test]
324 fn test_generate_junit_xml_success() -> Result<()> {
325 let results = CliTestResults {
327 tests: vec![
328 crate::cli::types::CliTestResult {
329 name: "test1".to_string(),
330 passed: true,
331 duration_ms: 1000,
332 error: None,
333 },
334 crate::cli::types::CliTestResult {
335 name: "test2".to_string(),
336 passed: false,
337 duration_ms: 500,
338 error: Some("Test failed".to_string()),
339 },
340 ],
341 total_duration_ms: 1500,
342 };
343
344 let xml = generate_junit_xml(&results)?;
346
347 assert!(xml.contains("cleanroom_tests"));
349 assert!(xml.contains("test1"));
350 assert!(xml.contains("test2"));
351 assert!(xml.contains("Test failed"));
352 Ok(())
353 }
354
355 #[test]
356 fn test_generate_junit_xml_empty_results() -> Result<()> {
357 let results = CliTestResults {
359 tests: vec![],
360 total_duration_ms: 0,
361 };
362
363 let xml = generate_junit_xml(&results)?;
365
366 assert!(xml.contains("cleanroom_tests"));
368 assert!(xml.contains("<testsuite"));
369 Ok(())
370 }
371
372 #[test]
373 #[ignore = "Incomplete test data or implementation"]
374 fn test_parse_toml_test_valid() -> Result<()> {
375 let temp_dir = TempDir::new().map_err(|e| {
377 CleanroomError::internal_error("Failed to create temp dir").with_source(e.to_string())
378 })?;
379 let test_file = temp_dir.path().join("test.toml");
380
381 let toml_content = r#"
382name = "test_example"
383
384[[scenarios]]
385name = "basic_test"
386steps = [
387 { name = "test_step", cmd = ["echo", "hello world"] }
388]
389"#;
390
391 fs::write(&test_file, toml_content).map_err(|e| {
392 CleanroomError::internal_error("Failed to write test file").with_source(e.to_string())
393 })?;
394
395 let config = parse_toml_test(&test_file)?;
397
398 assert_eq!(config.test.metadata.name, "test_example");
400 assert_eq!(config.steps.len(), 1);
401 assert_eq!(config.steps[0].name, "test_step");
402 assert_eq!(config.steps[0].command, vec!["echo", "hello world"]);
403
404 Ok(())
405 }
406
407 #[test]
408 fn test_parse_toml_test_invalid_toml() -> Result<()> {
409 let temp_dir = TempDir::new().map_err(|e| {
411 CleanroomError::internal_error("Failed to create temp dir").with_source(e.to_string())
412 })?;
413 let test_file = temp_dir.path().join("invalid.toml");
414
415 let invalid_toml = r#"
416[test
417name = "invalid"
418"#;
419
420 fs::write(&test_file, invalid_toml).map_err(|e| {
421 CleanroomError::internal_error("Failed to write test file").with_source(e.to_string())
422 })?;
423
424 let result = parse_toml_test(&test_file);
426 assert!(result.is_err());
427
428 Ok(())
429 }
430
431 #[test]
432 fn test_parse_toml_test_file_not_found() -> Result<()> {
433 let non_existent_file = PathBuf::from("non_existent.toml");
435
436 let result = parse_toml_test(&non_existent_file);
438 assert!(result.is_err());
439
440 Ok(())
441 }
442}