Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

parse more largely from stdout and not jus stderr #140

Merged
merged 1 commit into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/command_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ impl CommandResult {
let lines = &output.lines;
let error_code = exit_status.and_then(|s| s.code()).filter(|&c| c != 0);
let mut report = Report::from_lines(lines)?;
debug!("report stats: {:?}", &report.stats);
if let Some(error_code) = error_code {
if report.stats.errors + report.stats.test_fails == 0 {
// report shows no error while the command exe reported
Expand Down
145 changes: 85 additions & 60 deletions src/line_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,68 +19,72 @@ impl From<&CommandOutputLine> for LineAnalysis {
fn from(cmd_line: &CommandOutputLine) -> Self {
let content = &cmd_line.content;
let mut key = None;
let line_type = match cmd_line.origin {
CommandStream::StdOut => {
// there's currently a not understood bug preventing us from getting
// style information in stdout.
if cmd_line.content.is_blank() {
LineType::Normal
} else if let Some(content) = cmd_line.content.if_unstyled() {
if let Some((k, r)) = as_test_result(content) {
key = Some(k.to_string());
LineType::TestResult(r)
} else if let Some(k) = as_fail_result_title(content) {
key = Some(k.to_string());
LineType::Title(Kind::TestFail)
} else if regex_is_match!("^failures:$", content) {
// this isn't very discriminant...
let line_type = if cmd_line.content.is_blank() {
LineType::Normal
} else if let Some(content) = cmd_line.content.if_unstyled() {
if let Some((k, r)) = as_test_result(content) {
key = Some(k.to_string());
LineType::TestResult(r)
} else if let Some(k) = as_fail_result_title(content) {
key = Some(k.to_string());
LineType::Title(Kind::TestFail)
} else if regex_is_match!("^failures:$", content) {
// this isn't very discriminant...
LineType::Title(Kind::Sum)
} else if regex_is_match!("^note: run with `RUST_BACKTRACE=", content) {
LineType::BacktraceSuggestion
} else {
LineType::Normal
}
} else {
if let (Some(title), Some(body)) = (content.strings.get(0), content.strings.get(1)) {
match (
title.csi.as_ref(),
title.raw.as_ref(),
body.csi.as_ref(),
body.raw.as_ref(),
) {
(CSI_BOLD_RED, "error", CSI_ERROR_BODY, body_raw)
if body_raw.starts_with(": aborting due to") =>
{
LineType::Title(Kind::Sum)
} else if regex_is_match!("^note: run with `RUST_BACKTRACE=", content) {
LineType::BacktraceSuggestion
} else {
LineType::Normal
}
} else {
warn!("unexpected styled stdout: {:#?}", &cmd_line);
LineType::Normal // unexpected styled content
}
}
CommandStream::StdErr => {
if let (Some(title), Some(body)) = (content.strings.get(0), content.strings.get(1))
{
match (
title.csi.as_ref(),
title.raw.as_ref(),
body.csi.as_ref(),
body.raw.as_ref(),
) {
(CSI_BOLD_RED, "error", CSI_ERROR_BODY, body_raw)
if body_raw.starts_with(": aborting due to") =>
{
LineType::Title(Kind::Sum)
}
(CSI_BOLD_RED, title_raw, CSI_ERROR_BODY, _)
if title_raw.starts_with("error") =>
{
LineType::Title(Kind::Error)
}
#[cfg(not(windows))]
(CSI_BOLD_YELLOW, "warning", _, body_raw) => {
determine_warning_type(body_raw, content)
}
#[cfg(windows)]
(CSI_BOLD_YELLOW | CSI_BOLD_4BIT_YELLOW, "warning", _, body_raw) => {
determine_warning_type(body_raw, content)
(CSI_BOLD_RED, title_raw, CSI_ERROR_BODY, _)
if title_raw.starts_with("error") =>
{
LineType::Title(Kind::Error)
}
#[cfg(not(windows))]
(CSI_BOLD_YELLOW, "warning", _, body_raw) => {
determine_warning_type(body_raw, content)
}
#[cfg(windows)]
(CSI_BOLD_YELLOW | CSI_BOLD_4BIT_YELLOW, "warning", _, body_raw) => {
determine_warning_type(body_raw, content)
}
("", title_raw, CSI_BOLD_BLUE, "--> ") if is_spaces(title_raw) => {
LineType::Location
}
("", k, CSI_BOLD_RED|CSI_RED, "FAILED") if content.strings.len() == 2 => {
if let Some(k) = as_test_name(k) {
key = Some(k.to_string());
LineType::TestResult(false)
} else {
LineType::Normal
}
("", title_raw, CSI_BOLD_BLUE, "--> ") if is_spaces(title_raw) => {
debug!("LOCATION {:#?}", &content);
LineType::Location
}
("", k, CSI_GREEN, "ok") => {
if let Some(k) = as_test_name(k) {
key = Some(k.to_string());
LineType::TestResult(true)
} else {
LineType::Normal
}
_ => LineType::Normal,
}
} else {
LineType::Normal // empty line
_ => LineType::Normal,
}
} else {
LineType::Normal // empty line
}
};
LineAnalysis { line_type, key }
Expand All @@ -91,7 +95,11 @@ fn determine_warning_type(
body_raw: &str,
content: &TLine,
) -> LineType {
if is_n_warnings_emitted(body_raw) || is_generated_n_warnings(content.strings.get(2)) {
info!("DETER WT {:?}", &content);
if is_n_warnings_emitted(body_raw)
|| is_generated_n_warnings(&content.strings)
|| is_build_failed(content.strings.get(2))
{
LineType::Title(Kind::Sum)
} else {
LineType::Title(Kind::Warning)
Expand All @@ -106,12 +114,29 @@ fn is_spaces(s: &str) -> bool {
fn is_n_warnings_emitted(s: &str) -> bool {
regex_is_match!(r#"^: \d+ warnings? emitted"#, s)
}

fn is_generated_n_warnings(ts: Option<&TString>) -> bool {
ts.map_or(false, |ts| {
fn is_generated_n_warnings(ts: &[TString]) -> bool {
ts.iter().any(|ts| {
regex_is_match!(r#"generated \d+ warnings?$"#, &ts.raw)
})
}
fn is_build_failed(ts: Option<&TString>) -> bool {
ts.map_or(false, |ts| {
regex_is_match!(r#"^\s*build failed"#, &ts.raw)
})
}


/// similar to as_test_result but without the FAILED|ok part
/// This is used in case of styled output (because the FAILED|ok
/// part is in another TString)
fn as_test_name(s: &str) -> Option<&str> {
regex_captures!(
r#"^test\s+(.+?)(?: - should panic\s*)?(?: - compile\s*)?\s+...\s*$"#,
s
)
.map(|(_, key)| key)
}

/// return Some when the line is the non detailled
/// result of a test, for example
///
Expand Down
112 changes: 56 additions & 56 deletions src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,79 +53,78 @@ impl Report {
let mut is_in_out_fail = false;
let mut suggest_backtrace = false;
for cmd_line in cmd_lines {
debug!("cmd_line={:?}", &cmd_line);
let line_analysis = LineAnalysis::from(cmd_line);
debug!("line_analysis={:?}", &line_analysis);
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 cmd_line.origin {
CommandStream::StdErr => {
match line_type {
LineType::Title(Kind::Sum) => {
// we're not interested in this section
cur_err_kind = None;
}
LineType::Title(kind) => {
cur_err_kind = Some(kind);
debug!("{:?}> [{line_type:?}][{:?}]", cmd_line.origin, line_analysis.key);
match (line_type, line_analysis.key) {
(LineType::TestResult(r), Some(key)) => {
if r {
passed_tests += 1;
} else {
// we should receive the test failure section later,
// right now we just whitelist it
failure_names.insert(key);
}
}
(LineType::Title(Kind::TestFail), Some(key)) => {
if failure_names.contains(&key) {
failure_names.remove(&key);
line.content = TLine::failed(&key);
fails.push(line);
is_in_out_fail = true;
cur_err_kind = Some(Kind::TestFail);
} else {
warn!(
"unexpected test result failure_names={:?}, key={:?}",
&failure_names, &key,
);
}
}
(LineType::Normal, None) => {
if line.content.is_blank() && cur_err_kind != Some(Kind::TestFail) {
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::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
}
}
CommandStream::StdOut => {
match (line_type, line_analysis.key) {
(LineType::TestResult(r), Some(key)) => {
if r {
passed_tests += 1;
} else {
// we should receive the test failure section later,
// right now we just whitelist it
failure_names.insert(key);
}
}
(LineType::Title(Kind::TestFail), Some(key)) => {
if failure_names.contains(&key) {
failure_names.remove(&key);
line.content = TLine::failed(&key);
fails.push(line);
is_in_out_fail = true;
cur_err_kind = Some(Kind::TestFail);
} else {
warn!(
"unexpected test result failure_names={:?}, key={:?}",
&failure_names, &key,
);
}
}
(LineType::Normal, None) => {
if line.content.is_blank() && cur_err_kind != Some(Kind::TestFail) {
is_in_out_fail = false;
} else if is_in_out_fail {
fails.push(line);
}
}
(LineType::Title(Kind::Sum), None) => {
// we're not interested in this section
cur_err_kind = None;
is_in_out_fail = false;
}
(LineType::BacktraceSuggestion, _) => {
suggest_backtrace = true;
}
_ => {
// TODO add normal if not broken with blank line
warn!("unexpected line: {:#?}", &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),
_ => {} // before warnings and errors, or in a sum
}
suggest_backtrace = true;
}
_ => {}
}
}
// for now, we only added the test failures for which there was an output.
Expand Down Expand Up @@ -158,6 +157,7 @@ impl Report {
// have been read but not added (at start or end)
let mut stats = Stats::from(&lines);
stats.passed_tests = passed_tests;
debug!("stats: {:#?}", &stats);
Ok(Report {
lines,
stats,
Expand Down
8 changes: 8 additions & 0 deletions src/tty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub const CSI_RESET: &str = "\u{1b}[0m\u{1b}[0m";
pub const CSI_BOLD: &str = "\u{1b}[1m";
pub const CSI_ITALIC: &str = "\u{1b}[3m";

pub const CSI_GREEN: &str = "\u{1b}[32m";

pub const CSI_RED: &str = "\u{1b}[31m";
pub const CSI_BOLD_RED: &str = "\u{1b}[1m\u{1b}[38;5;9m";
pub const CSI_BOLD_ORANGE: &str = "\u{1b}[1m\u{1b}[38;5;208m";

Expand Down Expand Up @@ -247,6 +250,11 @@ impl TLine {
None
}
}
pub fn has(&self, part: &str) -> bool {
self.strings
.iter()
.any(|s| s.raw.contains(part))
}
}

#[derive(Debug, Default)]
Expand Down