Skip to content

Commit

Permalink
framework for different analyzers, nextest support (#212)
Browse files Browse the repository at this point in the history
Fix #196

To try it, define a job like this:

```TOML
[jobs.nextest]
command = ["cargo", "nextest", "run", "--color", "always", "--hide-progress-bar", "--failure-output", "final"]
need_stdout = true
analyzer = "nextest"
```
  • Loading branch information
Canop authored Oct 3, 2024
1 parent bde8a0d commit b0e5b66
Show file tree
Hide file tree
Showing 26 changed files with 2,134 additions and 328 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
### next
- support for nextest output & default nextest job, bound by default to the 'n' key - Fix #196
- new `exports` structure in configuration. New `analysis` export bound by default to `ctrl-e`. The old syntax defining locations export is still supported but won't appear in documentations anymore.
- recognize panic location in test - Fix #208

Expand Down
9 changes: 8 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bacon"
version = "2.21.0"
version = "2.22.0-nextest"
authors = ["dystroy <[email protected]>"]
repository = "https://github.com/Canop/bacon"
description = "background rust compiler"
Expand All @@ -10,7 +10,7 @@ license = "AGPL-3.0"
categories = ["command-line-utilities", "development-tools"]
readme = "README.md"
rust-version = "1.76"

[dependencies]
anyhow = "1.0.88"
cargo_metadata = "0.18"
Expand All @@ -23,6 +23,7 @@ directories-next = "2.0.0"
gix = "0.58"
lazy-regex = "3.3"
notify = "6.1"
rustc-hash = "2"
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0"
termimad = "0.30"
Expand Down
5 changes: 3 additions & 2 deletions bacon.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ command = [
"-A", "clippy::collapsible_else_if",
"-A", "clippy::collapsible_if",
"-A", "clippy::derive_partial_eq_without_eq",
"-A", "clippy::len_without_is_empty",
"-A", "clippy::get_first",
"-A", "clippy::while_let_on_iterator",
"-A", "clippy::if_same_then_else",
"-A", "clippy::len_without_is_empty",
"-A", "clippy::map_entry",
"-A", "clippy::while_let_on_iterator",
]
need_stdout = false

Expand Down
5 changes: 5 additions & 0 deletions defaults/default-bacon.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ command = [
]
need_stdout = true

[jobs.nextest]
command = ["cargo", "nextest", "run", "--color", "always", "--hide-progress-bar", "--failure-output", "final"]
need_stdout = true
analyzer = "nextest"

[jobs.doc]
command = ["cargo", "doc", "--color", "always", "--no-deps"]
need_stdout = false
Expand Down
178 changes: 178 additions & 0 deletions src/analysis/analyzer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use {
super::{
nextest_analyzer,
standard_analyzer,
},
crate::*,
rustc_hash::FxHashMap,
serde::{
Deserialize,
Serialize,
},
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Analyzer {
#[default]
Standard,
Nextest,
}

impl Analyzer {
pub fn analyze_line(
self,
line: &CommandOutputLine,
) -> LineAnalysis {
match self {
Self::Standard => standard_analyzer::analyze_line(line),
Self::Nextest => nextest_analyzer::analyze_line(line),
}
}
pub fn build_report(
&self,
cmd_lines: &[CommandOutputLine],
) -> anyhow::Result<Report> {
#[derive(Debug, Default)]
struct Failure {
has_title: bool,
}
// we first accumulate warnings, test fails and errors in separate vectors
let mut warnings = Vec::new();
let mut errors = Vec::new();
let mut fails = Vec::new();
let mut failures: FxHashMap<String, Failure> = Default::default();
let mut passed_tests = 0;
let mut cur_err_kind = None; // the current kind among stderr lines
let mut is_in_out_fail = false;
let mut suggest_backtrace = false;
for cmd_line in cmd_lines {
let line_analysis = self.analyze_line(cmd_line);
let line_type = line_analysis.line_type;
let mut line = Line {
item_idx: 0, // will be filled later
line_type,
content: cmd_line.content.clone(),
};
match (line_type, line_analysis.key) {
(LineType::Garbage, _) => {
continue;
}
(LineType::TestResult(r), Some(key)) => {
if r {
passed_tests += 1;
} else if !failures.contains_key(&key) {
// we should receive the test failure section later,
// right now we just whitelist it
failures.insert(key, Failure::default());
}
}
(LineType::Title(Kind::TestFail), Some(key)) => {
let failure = failures.entry(key.clone()).or_default();
is_in_out_fail = true;
cur_err_kind = Some(Kind::TestFail);
if failure.has_title {
// we already have a title for this failure
// (for nextest, we have a title for stdout and one for stderr)
continue;
}
failure.has_title = true;
line.content = TLine::failed(&key);
fails.push(line);
}
(LineType::Normal, None) => {
if line.content.is_blank() {
if cur_err_kind == Some(Kind::TestFail) {
// beautification: we remove some blank lines
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) => {
// we're not interested in this section
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),
_ => {} // before warnings and errors, or in a sum
}
}
(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),
_ => {} // before warnings and errors, or in a sum
}
}
_ => {}
}
}
for (key, failure) in failures.drain() {
// if we know of a failure but there was no content, we add some
if failure.has_title {
continue;
}
fails.push(Line {
item_idx: 0, // will be filled later
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()),
});
}
// we now build a common vector, with errors first
let mut lines = errors;
lines.append(&mut fails);
lines.append(&mut warnings);
// and we assign the indexes
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;
}
// we compute the stats at end because some lines may
// have been read but not added (at start or end)
let mut stats = Stats::from(&lines);
stats.passed_tests = passed_tests;
debug!("stats: {:#?}", &stats);
let report = Report {
lines,
stats,
suggest_backtrace,
output: Default::default(),
};
Ok(report)
}
}
5 changes: 5 additions & 0 deletions src/analysis/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod analyzer;
mod nextest_analyzer;
mod standard_analyzer;

pub use analyzer::*;
Loading

0 comments on commit b0e5b66

Please sign in to comment.