Skip to content

Commit a86c11e

Browse files
committed
Auto merge of #528 - Zeegomo:md, r=pietroalbini
Add markdown report Adds markdown report. Minicrater files for html have changed because json serialization is sorted by key (`ResultName` changes side effect).
2 parents 942318f + 25b6c59 commit a86c11e

19 files changed

+624
-40
lines changed

src/report/display.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::results::{BrokenReason, FailureReason, TestResult};
44

55
pub trait ResultName {
66
fn name(&self) -> String;
7+
fn long_name(&self) -> String;
78
}
89

910
impl ResultName for FailureReason {
@@ -17,6 +18,16 @@ impl ResultName for FailureReason {
1718
FailureReason::DependsOn(_) => "faulty deps".into(),
1819
}
1920
}
21+
22+
fn long_name(&self) -> String {
23+
match self {
24+
FailureReason::CompilerError(_) | FailureReason::DependsOn(_) => self.to_string(),
25+
FailureReason::Unknown
26+
| FailureReason::Timeout
27+
| FailureReason::OOM
28+
| FailureReason::ICE => self.name(),
29+
}
30+
}
2031
}
2132

2233
impl ResultName for BrokenReason {
@@ -28,6 +39,10 @@ impl ResultName for BrokenReason {
2839
BrokenReason::MissingGitRepository => "missing repo".into(),
2940
}
3041
}
42+
43+
fn long_name(&self) -> String {
44+
self.name()
45+
}
3146
}
3247

3348
impl ResultName for TestResult {
@@ -42,6 +57,18 @@ impl ResultName for TestResult {
4257
TestResult::Skipped => "skipped".into(),
4358
}
4459
}
60+
61+
fn long_name(&self) -> String {
62+
match self {
63+
TestResult::BuildFail(reason) => format!("build {}", reason.long_name()),
64+
TestResult::TestFail(reason) => format!("test {}", reason.long_name()),
65+
TestResult::BrokenCrate(reason) => reason.long_name(),
66+
TestResult::TestSkipped
67+
| TestResult::TestPass
68+
| TestResult::Error
69+
| TestResult::Skipped => self.name(),
70+
}
71+
}
4572
}
4673

4774
#[derive(Serialize)]

src/report/html.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::report::{
55
analyzer::ReportCrates, archives::Archive, Color, Comparison, CrateResult, ReportWriter,
66
ResultColor, ResultName, TestResults,
77
};
8-
use crate::results::{EncodingType, FailureReason, TestResult};
8+
use crate::results::EncodingType;
99
use indexmap::IndexMap;
1010

1111
#[derive(Serialize)]
@@ -170,12 +170,7 @@ fn write_report<W: ReportWriter>(
170170
.into_iter()
171171
.map(|(res, krates)| {
172172
(
173-
if let TestResult::BuildFail(FailureReason::CompilerError(_)) = res
174-
{
175-
res.to_string()
176-
} else {
177-
res.name()
178-
},
173+
res.long_name(),
179174
krates
180175
.into_iter()
181176
.map(|result| to_html_crate_result(result))

src/report/markdown.rs

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
use crate::crates::Crate;
2+
use crate::experiments::Experiment;
3+
use crate::prelude::*;
4+
use crate::report::analyzer::{ReportConfig, ReportCrates, ToolchainSelect};
5+
use crate::report::{
6+
crate_to_url, BuildTestResult, Comparison, CrateResult, ReportWriter, ResultName, TestResults,
7+
};
8+
use crate::utils::serialize::to_vec;
9+
use indexmap::{IndexMap, IndexSet};
10+
use std::fmt::Write;
11+
12+
#[derive(Serialize)]
13+
enum ReportCratesMD {
14+
Plain(Vec<CrateResult>),
15+
Complete {
16+
// only string keys are allowed in JSON maps
17+
#[serde(serialize_with = "to_vec")]
18+
res: IndexMap<CrateResult, Vec<CrateResult>>,
19+
#[serde(serialize_with = "to_vec")]
20+
orphans: IndexMap<Crate, Vec<CrateResult>>,
21+
},
22+
}
23+
24+
#[derive(Serialize)]
25+
struct ResultsContext<'a> {
26+
ex: &'a Experiment,
27+
categories: Vec<(Comparison, ReportCratesMD)>,
28+
info: IndexMap<Comparison, u32>,
29+
full: bool,
30+
crates_count: usize,
31+
}
32+
33+
fn write_crate(
34+
mut rendered: &mut String,
35+
krate: &CrateResult,
36+
comparison: Comparison,
37+
is_child: bool,
38+
) -> Fallible<()> {
39+
let get_run_name = |run: &BuildTestResult| {
40+
if !is_child {
41+
run.res.long_name()
42+
} else {
43+
run.res.name()
44+
}
45+
};
46+
47+
let runs = [
48+
krate.runs[0]
49+
.as_ref()
50+
.map(get_run_name)
51+
.unwrap_or_else(|| "unavailable".into()),
52+
krate.runs[0]
53+
.as_ref()
54+
.map(|run| run.log.to_owned())
55+
.unwrap_or_else(|| "#".into()),
56+
krate.runs[1]
57+
.as_ref()
58+
.map(get_run_name)
59+
.unwrap_or_else(|| "unavailable".into()),
60+
krate.runs[1]
61+
.as_ref()
62+
.map(|run| run.log.to_owned())
63+
.unwrap_or_else(|| "#".into()),
64+
];
65+
66+
let prefix = if is_child { " * " } else { "* " };
67+
68+
if let ReportConfig::Complete(toolchain) = comparison.report_config() {
69+
let (conj, run) = match toolchain {
70+
ToolchainSelect::Start => ("from", 0),
71+
ToolchainSelect::End => ("due to", 2),
72+
};
73+
74+
writeln!(
75+
&mut rendered,
76+
"{}[{}]({}) {} {} **{}** [start]({}/log.txt) | [end]({}/log.txt)",
77+
prefix,
78+
krate.name,
79+
krate.url,
80+
comparison.to_string(),
81+
conj,
82+
runs[run],
83+
runs[1],
84+
runs[3]
85+
)?;
86+
} else {
87+
writeln!(
88+
&mut rendered,
89+
"{}[{}]({}) {} [start]({}/log.txt) | [end]({}/log.txt)",
90+
prefix,
91+
krate.name,
92+
krate.url,
93+
comparison.to_string(),
94+
runs[1],
95+
runs[3]
96+
)?;
97+
};
98+
99+
Ok(())
100+
}
101+
102+
fn render_markdown(context: &ResultsContext) -> Fallible<String> {
103+
let mut rendered = String::new();
104+
105+
//add title
106+
writeln!(&mut rendered, "# Crater report for {}\n\n", context.ex.name)?;
107+
108+
for (comparison, results) in context.categories.iter() {
109+
writeln!(&mut rendered, "\n### {}", comparison.to_string())?;
110+
match results {
111+
ReportCratesMD::Plain(crates) => {
112+
for krate in crates {
113+
write_crate(&mut rendered, krate, *comparison, false)?;
114+
}
115+
}
116+
ReportCratesMD::Complete { res, orphans } => {
117+
for (root, deps) in res {
118+
write_crate(&mut rendered, root, *comparison, false)?;
119+
for krate in deps {
120+
write_crate(&mut rendered, krate, *comparison, true)?;
121+
}
122+
}
123+
124+
for (krate, deps) in orphans {
125+
writeln!(
126+
&mut rendered,
127+
"* [{}]({}) (not covered in crater testing)",
128+
krate,
129+
crate_to_url(&krate)?
130+
)?;
131+
for krate in deps {
132+
write_crate(&mut rendered, krate, *comparison, true)?;
133+
}
134+
}
135+
}
136+
}
137+
}
138+
139+
Ok(rendered)
140+
}
141+
142+
fn write_report<W: ReportWriter>(
143+
ex: &Experiment,
144+
crates_count: usize,
145+
res: &TestResults,
146+
full: bool,
147+
to: &str,
148+
dest: &W,
149+
output_templates: bool,
150+
) -> Fallible<()> {
151+
let categories = res
152+
.categories
153+
.iter()
154+
.filter(|(category, _)| full || category.show_in_summary())
155+
.map(|(&category, crates)| (category, crates.to_owned()))
156+
.map(|(category, crates)| match crates {
157+
ReportCrates::Plain(crates) => (
158+
category,
159+
ReportCratesMD::Plain(crates.into_iter().collect::<Vec<_>>()),
160+
),
161+
ReportCrates::Complete { mut tree, results } => {
162+
let res = results
163+
.into_iter()
164+
.flat_map(|(_key, values)| values.into_iter())
165+
.collect::<IndexSet<_>>() // remove duplicates
166+
.into_iter()
167+
.map(|krate| {
168+
// done here to avoid cloning krate
169+
let deps = tree.remove(&krate.krate).unwrap_or_default();
170+
(krate, deps)
171+
})
172+
.collect::<IndexMap<_, _>>();
173+
174+
(category, ReportCratesMD::Complete { res, orphans: tree })
175+
}
176+
})
177+
.collect();
178+
179+
let context = ResultsContext {
180+
ex,
181+
categories,
182+
info: res.info.clone(),
183+
full,
184+
crates_count,
185+
};
186+
187+
let markdown = render_markdown(&context)?;
188+
info!("generating {}", to);
189+
dest.write_string(to, markdown.into(), &mime::TEXT_PLAIN)?;
190+
191+
if output_templates {
192+
dest.write_string(
193+
[to, ".context.json"].concat(),
194+
serde_json::to_string(&context)?.into(),
195+
&mime::TEXT_PLAIN,
196+
)?;
197+
}
198+
199+
Ok(())
200+
}
201+
202+
pub fn write_markdown_report<W: ReportWriter>(
203+
ex: &Experiment,
204+
crates_count: usize,
205+
res: &TestResults,
206+
dest: &W,
207+
output_templates: bool,
208+
) -> Fallible<()> {
209+
write_report(
210+
ex,
211+
crates_count,
212+
res,
213+
false,
214+
"markdown.md",
215+
dest,
216+
output_templates,
217+
)?;
218+
Ok(())
219+
}

src/report/mod.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod analyzer;
2323
mod archives;
2424
mod display;
2525
mod html;
26+
mod markdown;
2627
mod s3;
2728

2829
pub use self::display::{Color, ResultColor, ResultName};
@@ -46,8 +47,8 @@ pub struct RawTestResults {
4647
pub crates: Vec<CrateResult>,
4748
}
4849

49-
#[cfg_attr(test, derive(Debug, PartialEq))]
50-
#[derive(Serialize, Deserialize, Clone)]
50+
#[cfg_attr(test, derive(Debug))]
51+
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
5152
pub struct CrateResult {
5253
name: String,
5354
url: String,
@@ -107,8 +108,8 @@ impl Comparison {
107108
}
108109
}
109110

110-
#[cfg_attr(test, derive(Debug, PartialEq))]
111-
#[derive(Serialize, Deserialize, Clone)]
111+
#[cfg_attr(test, derive(Debug))]
112+
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
112113
struct BuildTestResult {
113114
res: TestResult,
114115
log: String,
@@ -309,6 +310,8 @@ pub fn gen<DB: ReadResults, W: ReportWriter + Display>(
309310
dest,
310311
output_templates,
311312
)?;
313+
info!("writing markdown files");
314+
markdown::write_markdown_report(ex, crates.len(), &res, dest, output_templates)?;
312315
info!("writing logs");
313316
write_logs(db, ex, crates, dest, config)?;
314317

src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub(crate) mod http;
1010
#[macro_use]
1111
mod macros;
1212
pub(crate) mod path;
13+
pub(crate) mod serialize;
1314
pub mod size;
1415
pub(crate) mod string;
1516

src/utils/serialize.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use serde::ser::{Serialize, SerializeSeq, Serializer};
2+
use std::iter::IntoIterator;
3+
4+
pub fn to_vec<S, T>(data: T, serializer: S) -> Result<S::Ok, S::Error>
5+
where
6+
S: Serializer,
7+
T: IntoIterator,
8+
T::Item: Serialize,
9+
T::IntoIter: std::iter::ExactSizeIterator,
10+
{
11+
let data = IntoIterator::into_iter(data);
12+
let mut seq = serializer.serialize_seq(Some(data.len()))?;
13+
for element in data {
14+
seq.serialize_element(&element)?;
15+
}
16+
seq.end()
17+
}

templates/report/downloads.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
<a href="retry-regressed-list.txt">Regressed crates as list</a>
4444
<span><a href="retry-regressed-list.txt">Download</a></span>
4545
</div>
46+
<div class="crate">
47+
<a href="markdown.md">Markdown report</a>
48+
<span><a href="markdown.md">Download</a></span>
49+
</div>
4650
</div>
4751
</div>
4852
{% endblock %}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"categories": [],
3+
"crates_count": 3,
4+
"full": false,
5+
"info": {
6+
"skipped": 1,
7+
"test-pass": 1,
8+
"test-skipped": 1
9+
}
10+
}

tests/minicrater/clippy/full.html.context.expected.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"RootResults": {
3838
"count": 1,
3939
"results": {
40-
"build-fail:compiler-error(clippy::print_with_newline)": [
40+
"build compiler-error(clippy::print_with_newline)": [
4141
{
4242
"name": "clippy-warn (local)",
4343
"res": "regressed",

0 commit comments

Comments
 (0)