1use std::{collections::HashMap, path::Path, time::SystemTime};
4
5use html_page::{Element, HtmlPage, Tag};
6use log::trace;
7
8use crate::{
9 run::commit_dir,
10 {runlog::RunLog, spec::Spec, timestamp},
11};
12
13const REFRESH_INTERVAL: &str = "300";
14
15pub struct Report {
17 description: String,
18 commits: Vec<(String, bool)>,
19}
20
21impl Report {
22 pub fn new(description: &str, stats_txt: &Path) -> anyhow::Result<Self> {
24 let text = std::fs::read(stats_txt)?;
25 let text = String::from_utf8_lossy(&text);
26 let mut commits = vec![];
27 for line in text.lines() {
28 let mut words = line.split_whitespace();
29 let commit = words.next().unwrap();
30 let result = words.next().unwrap();
31 commits.push((commit.to_string(), result == "SUCCESS"));
32 }
33 trace!("loaded {} commits", commits.len());
34 Ok(Self {
35 description: description.into(),
36 commits,
37 })
38 }
39
40 fn count(&self) -> HashMap<String, (usize, usize)> {
41 let mut counts = HashMap::new();
42 for (commit, success) in self.commits.iter() {
43 let (mut s, mut f) = counts.get(commit).copied().unwrap_or((0, 0));
44 if *success {
45 s += 1;
46 } else {
47 f += 1;
48 }
49 counts.insert(commit.into(), (s, f));
50 }
51 counts
52 }
53
54 fn counts_with_date(
55 spec: &Spec,
56 working_dir: &Path,
57 counts: HashMap<String, (usize, usize)>,
58 ) -> HashMap<String, (String, usize, usize)> {
59 let mut map = HashMap::new();
60 let mut run_log = RunLog::default();
61 for (commit, (successes, failures)) in counts.iter() {
62 let date = spec.git_commit_date(working_dir, commit, &mut run_log);
63 map.insert(commit.to_string(), (date, *successes, *failures));
64 }
65 map
66 }
67
68 pub fn as_html(&self, spec: &Spec, working_dir: &Path) -> HtmlPage {
70 let counts = self.count();
71 let counts = Self::counts_with_date(spec, working_dir, counts);
72
73 let now = timestamp(&SystemTime::now());
74
75 let mut table = Element::new(Tag::Table).with_child(
76 Element::new(Tag::Tr)
77 .with_child(Element::new(Tag::Th).with_text("date"))
78 .with_child(Element::new(Tag::Th).with_text("commit"))
79 .with_child(
80 Element::new(Tag::Th)
81 .with_class("numeric")
82 .with_text("successes"),
83 )
84 .with_child(
85 Element::new(Tag::Th)
86 .with_class("numeric")
87 .with_text("failures"),
88 ),
89 );
90
91 let mut counts: Vec<_> = counts.iter().collect();
92 counts.sort_by_cached_key(|(_, (date, _, _))| date);
93 counts.reverse();
94
95 for (commit, (date, successes, failures)) in counts.iter() {
96 let successes = format!("{successes}");
97 let failures = format!("{failures}");
98
99 table.push_child(
100 Element::new(Tag::Tr)
101 .with_child(Element::new(Tag::Td).with_class("date").with_text(date))
102 .with_child(
103 Element::new(Tag::Td).with_child(
104 Element::new(Tag::A)
105 .with_attribute("href", &commit_dir(commit))
106 .with_class("commit")
107 .with_text(commit),
108 ),
109 )
110 .with_child(
111 Element::new(Tag::Td)
112 .with_class("numeric")
113 .with_text(&successes),
114 )
115 .with_child(
116 Element::new(Tag::Td)
117 .with_class("numeric")
118 .with_text(&failures),
119 ),
120 );
121 }
122
123 let total = self.commits.len();
124 HtmlPage::default()
125 .with_head_element(Element::new(Tag::Title).with_text("Wumpus hunter"))
126 .with_head_element(Element::new(Tag::Style).with_text(crate::CSS))
127 .with_head_element(
128 Element::new(Tag::Meta)
129 .with_attribute("http-equiv", "refresh")
130 .with_attribute("content", REFRESH_INTERVAL),
131 )
132 .with_body_element(Element::new(Tag::H1).with_text("Wumpus hunter"))
133 .with_body_element(Element::new(Tag::P).with_text(&self.description))
134 .with_body_element(
135 Element::new(Tag::P)
136 .with_text(&format!("Total of {total} test runs. Last updated "))
137 .with_text(&now),
138 )
139 .with_body_element(table)
140 }
141}