Skip to content
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
36 changes: 19 additions & 17 deletions Cargo.lock

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

43 changes: 41 additions & 2 deletions crates/common/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,23 @@ fn all_sources<E: private::ErrorChain + ?Sized>(err: &E) -> Vec<String> {
pub fn convert_solar_errors(dcx: &solar::interface::diagnostics::DiagCtxt) -> eyre::Result<()> {
match dcx.emitted_errors() {
Some(Ok(())) => Ok(()),
Some(Err(e)) if !e.is_empty() => eyre::bail!("solar run failed:\n\n{e}"),
_ if dcx.has_errors().is_err() => eyre::bail!("solar run failed"),
Some(Err(e)) if !e.is_empty() => eyre::bail!("solar reported errors:\n\n{e}"),
_ if dcx.has_errors().is_err() => {
// Non-buffer emitter: diagnostics already went to stderr; include the count.
let n = dcx.err_count();
let plural = if n == 1 { "" } else { "s" };
eyre::bail!(
"solar reported {n} error{plural}; see the diagnostic{plural} printed above"
)
}
_ => Ok(()),
}
}

#[cfg(test)]
mod tests {
use super::*;
use solar::interface::diagnostics::{DiagCtxt, SilentEmitter};

#[test]
fn dedups_contained() {
Expand All @@ -72,4 +80,35 @@ mod tests {
let chained = display_chain(&ee);
assert_eq!(chained, "my error: hello");
}

/// Regression test for the "non-buffer emitter" branch of [`convert_solar_errors`].
///
/// Simulates an unhandled solar edge case: the linter installs a non-buffer (stderr-style)
/// emitter, errors are emitted to it, and only the count is recoverable afterwards. The
/// returned eyre error must reference the count and direct the user to the diagnostics that
/// were already printed above.
#[test]
fn solar_non_buffer_emitter_singular() {
let dcx = DiagCtxt::new(Box::new(SilentEmitter::new_silent()));
dcx.err("boom").emit();

let err = convert_solar_errors(&dcx).unwrap_err();
assert_eq!(err.to_string(), "solar reported 1 error; see the diagnostic printed above");
}

#[test]
fn solar_non_buffer_emitter_plural() {
let dcx = DiagCtxt::new(Box::new(SilentEmitter::new_silent()));
dcx.err("boom 1").emit();
dcx.err("boom 2").emit();

let err = convert_solar_errors(&dcx).unwrap_err();
assert_eq!(err.to_string(), "solar reported 2 errors; see the diagnostics printed above");
}

#[test]
fn solar_no_errors_is_ok() {
let dcx = DiagCtxt::new(Box::new(SilentEmitter::new_silent()));
assert!(convert_solar_errors(&dcx).is_ok());
}
}
5 changes: 5 additions & 0 deletions crates/config/src/invariant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ pub struct InvariantConfig {
///
/// Example: `check_interval = 10` means assert after calls 10, 20, 30, ... and the last call.
pub check_interval: u32,
/// Assert every invariant declared in the current test suite, continuing the campaign after
/// the first failure until all invariants have been broken (or normal limits are hit).
/// When `false`, the campaign aborts on the first broken invariant (legacy behavior).
pub assert_all: bool,
}

impl Default for InvariantConfig {
Expand All @@ -70,6 +74,7 @@ impl Default for InvariantConfig {
max_time_delay: None,
max_block_delay: None,
check_interval: 1,
assert_all: true,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/evm/evm/src/executors/corpus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ pub(crate) struct CorpusMetrics {
impl fmt::Display for CorpusMetrics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f)?;
writeln!(f, " Edge coverage metrics:")?;
writeln!(f, " - cumulative edges seen: {}", self.cumulative_edges_seen)?;
writeln!(f, " - cumulative features seen: {}", self.cumulative_features_seen)?;
writeln!(f, " - corpus count: {}", self.corpus_count)?;
Expand Down
Loading