aboutsummaryrefslogtreecommitdiff
path: root/mingling_ci/src/cmd/cmd_report_collect.rs
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_ci/src/cmd/cmd_report_collect.rs')
-rw-r--r--mingling_ci/src/cmd/cmd_report_collect.rs150
1 files changed, 150 insertions, 0 deletions
diff --git a/mingling_ci/src/cmd/cmd_report_collect.rs b/mingling_ci/src/cmd/cmd_report_collect.rs
new file mode 100644
index 0000000..2eff074
--- /dev/null
+++ b/mingling_ci/src/cmd/cmd_report_collect.rs
@@ -0,0 +1,150 @@
+use std::collections::{BTreeMap, HashMap};
+use std::path::PathBuf;
+
+use just_template::Template;
+use mingling::{
+ Grouped, RenderResult, Routable,
+ macros::{buffer, command, r_println, renderer},
+};
+
+use crate::Next;
+use crate::reporter::{COLLECT_DIR, REPORT_PATH};
+use crate::res::{CargoError, MessagePrinter, ResCollectLogs};
+
+const REPORT_TEMPLATE: &str = include_str!("../../tmpls/report.md");
+const TASK_SECTION_TEMPLATE: &str = include_str!("../../tmpls/task_section.md");
+
+/// Maps a package to its per-OS pass/fail status.
+type OsStatuses = BTreeMap<String, bool>;
+
+/// A row in a task section: item name and its per-OS statuses.
+type TaskRow<'a> = (&'a String, &'a OsStatuses);
+
+/// Rows grouped by task name.
+type RowsByTask<'a> = BTreeMap<&'a String, Vec<TaskRow<'a>>>;
+
+#[command(node = "report-collect")]
+pub fn report_collect(logs: &ResCollectLogs) -> Next {
+ if !PathBuf::from(COLLECT_DIR).is_dir() {
+ return ErrorNoCollectDir.to_chain();
+ }
+
+ // Group rows by task: task -> [(item, os_statuses)].
+ let by_task: RowsByTask =
+ logs.statuses
+ .iter()
+ .fold(BTreeMap::new(), |mut acc, ((task, item), os_statuses)| {
+ acc.entry(task).or_default().push((item, os_statuses));
+ acc
+ });
+
+ // Render one section per task (table rows + this task's failures).
+ let mut fail_count = 0;
+ let mut sections: Vec<HashMap<String, String>> = Vec::new();
+ for (task, rows) in by_task {
+ let mut row_arms = Vec::new();
+ let mut fail_arms = Vec::new();
+ for (item, os_statuses) in rows {
+ let location = logs
+ .locations
+ .get(&(task.clone(), item.clone()))
+ .cloned()
+ .unwrap_or_default();
+ row_arms.push(HashMap::from([
+ ("item_name".to_string(), item.clone()),
+ ("location".to_string(), location),
+ (
+ "pass_win".to_string(),
+ pass_cell(os_statuses.get("Windows")),
+ ),
+ (
+ "pass_linux".to_string(),
+ pass_cell(os_statuses.get("Linux")),
+ ),
+ ("pass_mac".to_string(), pass_cell(os_statuses.get("MacOS"))),
+ ]));
+
+ for (os, ok) in os_statuses {
+ if !ok {
+ let stdout = logs
+ .err_outputs
+ .get(&(task.clone(), os.clone(), item.clone()))
+ .cloned()
+ .unwrap_or_default();
+ fail_arms.push(HashMap::from([
+ ("item_name".to_string(), item.clone()),
+ ("stdout".to_string(), stdout),
+ ]));
+ fail_count += 1;
+ }
+ }
+ }
+
+ let mut section = Template::from(TASK_SECTION_TEMPLATE);
+ section.insert_param("task_name".to_string(), task.clone());
+ *section.add_impl("rows".to_string()) = row_arms;
+ *section.add_impl("fails".to_string()) = fail_arms;
+ sections.push(HashMap::from([(
+ "section".to_string(),
+ section.expand().unwrap_or_default(),
+ )]));
+ }
+
+ let mut template = Template::from(REPORT_TEMPLATE);
+
+ template.insert_param("date".to_string(), logs.git.date.clone());
+ template.insert_param("commit_hash".to_string(), logs.git.commit_hash.clone());
+ *template.add_impl("task_sections".to_string()) = sections;
+
+ let expanded = template.expand().unwrap_or_default();
+ let output = PathBuf::from(REPORT_PATH);
+ let parent = output.parent().expect("output path has a parent");
+
+ if let Err(e) = std::fs::create_dir_all(parent).and_then(|()| std::fs::write(&output, expanded))
+ {
+ return ErrorReportWrite(format!("failed to write {}: {e}", output.display())).to_chain();
+ }
+
+ ResultCollectResults { output, fail_count }.to_chain()
+}
+
+fn pass_cell(status: Option<&bool>) -> String {
+ match status {
+ Some(true) => "✅".to_string(),
+ Some(false) => "❌".to_string(),
+ None => "—".to_string(),
+ }
+}
+
+/// The generated report.
+#[derive(Grouped)]
+pub struct ResultCollectResults {
+ pub output: PathBuf,
+ pub fail_count: usize,
+}
+
+#[derive(Grouped, Default)]
+pub struct ErrorNoCollectDir;
+
+#[derive(Grouped, Default)]
+pub struct ErrorReportWrite(pub String);
+
+#[renderer(buffer)]
+pub fn render_collect_results(r: ResultCollectResults) {
+ r_println!("Collected {} failing logs", r.fail_count);
+ r_println!("Report generated at {}", r.output.display());
+}
+
+#[renderer]
+pub fn render_error_no_collect_dir(_: ErrorNoCollectDir, error: &CargoError) -> RenderResult {
+ let render_result = RenderResult::new();
+ error.println(vec![format!("No collect directory: {COLLECT_DIR}")]);
+ render_result
+}
+
+#[renderer]
+pub fn render_error_report_write(e: ErrorReportWrite, error: &CargoError) -> RenderResult {
+ let render_result = RenderResult::new();
+ error.println(vec![format!("Report: {}", e.0)]);
+ render_result
+}