use {
crate::*,
rustc_hash::FxHashMap,
};
pub fn build_report<L: LineAnalyzer>(
cmd_lines: &[CommandOutputLine],
mut line_analyzer: L,
) -> Report {
build_report_from_analysis(
cmd_lines
.iter()
.map(|line| (line_analyzer.analyze_line(line), line.content.clone())),
)
}
pub fn build_report_from_analysis(lines: impl Iterator<Item = (LineAnalysis, TLine)>) -> Report {
#[derive(Debug, Default)]
struct Test {
passed: bool,
has_title: bool,
}
let mut warnings = Vec::new();
let mut errors = Vec::new();
let mut fails = Vec::new();
let mut tests: FxHashMap<String, Test> = Default::default();
let mut passed_tests = 0;
let mut cur_err_kind = None; let mut is_in_out_fail = false;
let mut suggest_backtrace = false;
for (line_analysis, content) in lines {
let line_type = line_analysis.line_type;
let mut line = Line {
item_idx: 0, line_type,
content,
};
match (line_type, line_analysis.key) {
(LineType::TestResult(r), Some(key)) => {
if r {
passed_tests += 1;
tests.insert(
key,
Test {
passed: true,
has_title: false,
},
);
} else if !tests.contains_key(&key) {
tests.insert(key, Test::default());
}
}
(LineType::Title(Kind::TestOutput | Kind::TestFail), Some(key)) => {
let test = tests.entry(key.clone()).or_default();
is_in_out_fail = true;
cur_err_kind = Some(Kind::TestFail);
if test.has_title {
continue;
}
test.has_title = true;
line.content = if test.passed {
TLine::passed(&key)
} else {
TLine::failed(&key)
};
fails.push(line);
}
(LineType::Normal, None) => {
if line.content.is_blank() {
if cur_err_kind == Some(Kind::TestFail) {
if let Some(last) = fails.last() {
if last.line_type != LineType::Normal || last.content.is_blank() {
continue;
}
}
} else {
is_in_out_fail = false;
}
}
if is_in_out_fail {
fails.push(line);
} else {
match cur_err_kind {
Some(Kind::Warning) => warnings.push(line),
Some(Kind::Error) => errors.push(line),
_ => {}
}
}
}
(LineType::Title(Kind::Sum), None) => {
cur_err_kind = None;
is_in_out_fail = false;
}
(LineType::SectionEnd, None) => {
cur_err_kind = None;
is_in_out_fail = false;
}
(LineType::Title(kind), _) => {
cur_err_kind = Some(kind);
match cur_err_kind {
Some(Kind::Warning) => warnings.push(line),
Some(Kind::Error) => errors.push(line),
_ => {} }
}
(LineType::BacktraceSuggestion, _) => {
suggest_backtrace = true;
}
(LineType::Location, _) => {
match cur_err_kind {
Some(Kind::Warning) => warnings.push(line),
Some(Kind::Error) => errors.push(line),
Some(Kind::TestFail) => fails.push(line),
_ => {} }
}
_ => {}
}
}
for (key, test) in &tests {
if test.has_title || test.passed {
continue;
}
fails.push(Line {
item_idx: 0, line_type: LineType::Title(Kind::TestFail),
content: TLine::failed(key),
});
fails.push(Line {
item_idx: 0,
line_type: LineType::Normal,
content: TLine::italic("no output".to_string()),
});
}
let mut lines = errors;
lines.append(&mut fails);
lines.append(&mut warnings);
let mut item_idx = 0;
for line in &mut lines {
if matches!(line.line_type, LineType::Title(_)) {
item_idx += 1;
}
line.item_idx = item_idx;
}
let mut report = Report::new(lines);
report.suggest_backtrace = suggest_backtrace;
report.has_passed_tests = passed_tests > 0;
report.failure_keys = tests
.drain()
.filter_map(|(key, test)| if !test.passed { Some(key) } else { None })
.collect::<Vec<_>>();
report
}