diff options
| -rw-r--r-- | mingling_ci/src/reporter.rs | 50 | ||||
| -rw-r--r-- | mingling_ci/src/res/collect_logs.rs | 82 |
2 files changed, 72 insertions, 60 deletions
diff --git a/mingling_ci/src/reporter.rs b/mingling_ci/src/reporter.rs index 1cd5b2b..ef626d9 100644 --- a/mingling_ci/src/reporter.rs +++ b/mingling_ci/src/reporter.rs @@ -104,7 +104,8 @@ pub fn export_on(package: &str, platform: ReportPlatform, result: ReportResult) } } -/// Writes buffered successes to `{task}/{platform}/ok`, one package per line. +/// Writes buffered successes to `collect/{task}.{platform}.ok`, one package per +/// line. /// /// # Panics /// @@ -116,38 +117,43 @@ pub fn flush() { }; let buffered = std::mem::take(&mut *OK_BUFFER.lock().unwrap()); + if buffered.is_empty() { + return; + } + + if let Err(e) = fs::create_dir_all(COLLECT_DIR) { + eprintln!("reporter: failed to create {COLLECT_DIR}: {e}"); + return; + } + for (platform, packages) in buffered { - let dir = Path::new(COLLECT_DIR).join(&task).join(platform.dir_name()); - if let Err(e) = fs::create_dir_all(&dir) { - eprintln!("reporter: failed to create {}: {e}", dir.display()); - continue; - } let content = if packages.is_empty() { String::new() } else { packages.join("\n") + "\n" }; - let path = dir.join("ok"); + let platform_name = platform.dir_name(); + let path = Path::new(COLLECT_DIR).join(format!("{task}.{platform_name}.ok")); if let Err(e) = fs::write(&path, content) { eprintln!("reporter: failed to write {}: {e}", path.display()); } } } -/// Writes a failure entry to `{task}/{platform}/{package}.err`. +/// Writes a failure entry to `collect/{task}.{platform}.{package}.err`. fn write_err(package: &str, platform: ReportPlatform, output: String) { let Some(task) = CURRENT_TASK.lock().unwrap().clone() else { eprintln!("reporter: no current task; call reporter::set_task first"); return; }; - let dir = Path::new(COLLECT_DIR).join(task).join(platform.dir_name()); - if let Err(e) = fs::create_dir_all(&dir) { - eprintln!("reporter: failed to create {}: {e}", dir.display()); + if let Err(e) = fs::create_dir_all(COLLECT_DIR) { + eprintln!("reporter: failed to create {COLLECT_DIR}: {e}"); return; } - let path = dir.join(format!("{package}.err")); + let platform_name = platform.dir_name(); + let path = Path::new(COLLECT_DIR).join(format!("{task}.{platform_name}.{package}.err")); if let Err(e) = fs::write(&path, output) { eprintln!("reporter: failed to write {}: {e}", path.display()); } @@ -160,19 +166,23 @@ mod tests { #[test] fn export_writes_ok_and_err_files() { set_task("reporter-test"); - let task_root = Path::new(COLLECT_DIR).join("reporter-test"); - let dir = task_root.join(current_platform().dir_name()); - fs::remove_dir_all(&task_root).ok(); + let platform_name = current_platform().dir_name(); + let ok_path = Path::new(COLLECT_DIR).join(format!("reporter-test.{platform_name}.ok")); + let err_path = + Path::new(COLLECT_DIR).join(format!("reporter-test.{platform_name}.pkg-b.err")); + fs::remove_file(&ok_path).ok(); + fs::remove_file(&err_path).ok(); export("pkg-a", ReportResult::Ok); export("pkg-b", ReportResult::Error("boom".to_string())); flush(); - assert!(dir.join("ok").is_file()); - assert_eq!(fs::read_to_string(dir.join("ok")).unwrap(), "pkg-a\n"); - assert!(dir.join("pkg-b.err").is_file()); - assert_eq!(fs::read_to_string(dir.join("pkg-b.err")).unwrap(), "boom"); + assert!(ok_path.is_file()); + assert_eq!(fs::read_to_string(&ok_path).unwrap(), "pkg-a\n"); + assert!(err_path.is_file()); + assert_eq!(fs::read_to_string(&err_path).unwrap(), "boom"); - fs::remove_dir_all(&task_root).ok(); + fs::remove_file(ok_path).ok(); + fs::remove_file(err_path).ok(); } } diff --git a/mingling_ci/src/res/collect_logs.rs b/mingling_ci/src/res/collect_logs.rs index df0af5b..7c5b375 100644 --- a/mingling_ci/src/res/collect_logs.rs +++ b/mingling_ci/src/res/collect_logs.rs @@ -26,54 +26,34 @@ pub struct ResCollectLogs { } impl ResCollectLogs { - /// Reads `collect/{task}/{os}/` — the aggregate `ok` file (one package per - /// line) and per-package `{package}.err` files — plus the git info. + /// Reads the flat `collect/` directory — aggregate `{task}.{os}.ok` files + /// (one package per line) and per-package `{task}.{os}.{package}.err` + /// files — plus the git info. #[must_use] pub fn read() -> Self { let mut logs = Self::default(); - if let Ok(task_entries) = std::fs::read_dir(COLLECT_DIR) { - for task_entry in task_entries.flatten() { - if !task_entry.file_type().is_ok_and(|t| t.is_dir()) { - continue; - } - let task = task_entry.file_name().to_string_lossy().into_owned(); - - let Ok(os_entries) = std::fs::read_dir(task_entry.path()) else { - continue; - }; - for os_entry in os_entries.flatten() { - if !os_entry.file_type().is_ok_and(|t| t.is_dir()) { - continue; - } - let os = os_entry.file_name().to_string_lossy().into_owned(); - - let Ok(files) = std::fs::read_dir(os_entry.path()) else { - continue; - }; - for file in files.flatten() { - let file_name = file.file_name().to_string_lossy().into_owned(); - if file_name == "ok" { - // Aggregate success file: one package name per line. - if let Ok(content) = std::fs::read_to_string(file.path()) { - for package in content.lines().filter(|l| !l.is_empty()) { - logs.statuses - .entry((task.clone(), package.to_string())) - .or_default() - .insert(os.clone(), true); - } - } - } else if let Some(package) = file_name.strip_suffix(".err") { - let package = package.to_string(); + if let Ok(entries) = std::fs::read_dir(COLLECT_DIR) { + for entry in entries.flatten() { + let file_name = entry.file_name().to_string_lossy().into_owned(); + if let Some((task, os)) = parse_ok_name(&file_name) { + // Aggregate success file: one package name per line. + if let Ok(content) = std::fs::read_to_string(entry.path()) { + for package in content.lines().filter(|l| !l.is_empty()) { logs.statuses - .entry((task.clone(), package.clone())) + .entry((task.clone(), package.to_string())) .or_default() - .insert(os.clone(), false); - let err = std::fs::read_to_string(file.path()).unwrap_or_default(); - logs.err_outputs - .insert((task.clone(), os.clone(), package), strip_ansi(&err)); + .insert(os.clone(), true); } } + } else if let Some((task, os, package)) = parse_err_name(&file_name) { + logs.statuses + .entry((task.clone(), package.clone())) + .or_default() + .insert(os.clone(), false); + let err = std::fs::read_to_string(entry.path()).unwrap_or_default(); + logs.err_outputs + .insert((task, os, package), strip_ansi(&err)); } } } @@ -83,6 +63,28 @@ impl ResCollectLogs { } } +/// Parses a `{task}.{os}.ok` file name. +fn parse_ok_name(file_name: &str) -> Option<(String, String)> { + let name = file_name.strip_suffix(".ok")?; + let mut parts = name.rsplitn(2, '.'); + let os = parts.next()?.to_string(); + let task = parts.next()?.to_string(); + Some((task, os)) +} + +/// Parses a `{task}.{os}.{package}.err` file name. +/// +/// Split from the right: package names cannot contain dots (cargo forbids +/// them), while task names may. +fn parse_err_name(file_name: &str) -> Option<(String, String, String)> { + let name = file_name.strip_suffix(".err")?; + let mut parts = name.rsplitn(3, '.'); + let package = parts.next()?.to_string(); + let os = parts.next()?.to_string(); + let task = parts.next()?.to_string(); + Some((task, os, package)) +} + #[program_setup] pub fn report_setup(p: &mut Program<ThisProgram>) { p.with_resource(ResCollectLogs::read()); |
