mirror of
https://github.com/pezkuwichain/revive-differential-tests.git
synced 2026-06-14 12:11:08 +00:00
Integrate the reporting infrastructure with the
CLI reporter used by the program.
This commit is contained in:
+55
-57
@@ -18,9 +18,11 @@ use futures::stream;
|
|||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use revive_dt_node_interaction::EthereumNode;
|
use revive_dt_node_interaction::EthereumNode;
|
||||||
use revive_dt_report::{ReportAggregator, Reporter, TestSpecificReporter, TestSpecifier};
|
use revive_dt_report::{
|
||||||
|
ReportAggregator, Reporter, ReporterEvent, TestCaseStatus, TestSpecificReporter, TestSpecifier,
|
||||||
|
};
|
||||||
use temp_dir::TempDir;
|
use temp_dir::TempDir;
|
||||||
use tokio::{join, sync::mpsc, try_join};
|
use tokio::{join, sync::broadcast::Receiver, try_join};
|
||||||
use tracing::{debug, info, info_span, instrument};
|
use tracing::{debug, info, info_span, instrument};
|
||||||
use tracing_appender::non_blocking::WorkerGuard;
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||||
@@ -56,9 +58,6 @@ struct Test<'a> {
|
|||||||
reporter: TestSpecificReporter,
|
reporter: TestSpecificReporter,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This represents the results that we gather from running test cases.
|
|
||||||
type CaseResult = Result<usize, anyhow::Error>;
|
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let (args, _guard) = init_cli()?;
|
let (args, _guard) = init_cli()?;
|
||||||
info!(
|
info!(
|
||||||
@@ -182,13 +181,13 @@ where
|
|||||||
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
L::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
F::Blockchain: revive_dt_node::Node + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let (report_tx, report_rx) = mpsc::unbounded_channel::<(Test<'_>, CaseResult)>();
|
let reporter_events_rx = reporter.subscribe().await?;
|
||||||
|
|
||||||
let tests = prepare_tests::<L, F>(args, metadata_files, reporter);
|
let tests = prepare_tests::<L, F>(args, metadata_files, reporter);
|
||||||
let driver_task = start_driver_task::<L, F>(args, tests, report_tx).await?;
|
let driver_task = start_driver_task::<L, F>(args, tests).await?;
|
||||||
let status_reporter_task = start_reporter_task(report_rx);
|
let cli_reporting_task = start_cli_reporting_task(reporter_events_rx);
|
||||||
|
|
||||||
let (_, _, rtn) = tokio::join!(status_reporter_task, driver_task, report_aggregator_task,);
|
let (_, _, rtn) = tokio::join!(cli_reporting_task, driver_task, report_aggregator_task,);
|
||||||
rtn?;
|
rtn?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -435,7 +434,6 @@ async fn does_compiler_support_mode<P: Platform>(
|
|||||||
async fn start_driver_task<'a, L, F>(
|
async fn start_driver_task<'a, L, F>(
|
||||||
args: &Arguments,
|
args: &Arguments,
|
||||||
tests: impl Stream<Item = Test<'a>>,
|
tests: impl Stream<Item = Test<'a>>,
|
||||||
_: mpsc::UnboundedSender<(Test<'a>, CaseResult)>,
|
|
||||||
) -> anyhow::Result<impl Future<Output = ()>>
|
) -> anyhow::Result<impl Future<Output = ()>>
|
||||||
where
|
where
|
||||||
L: Platform,
|
L: Platform,
|
||||||
@@ -510,74 +508,74 @@ where
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start_reporter_task(mut report_rx: mpsc::UnboundedReceiver<(Test<'_>, CaseResult)>) {
|
#[allow(clippy::uninlined_format_args)]
|
||||||
|
#[allow(irrefutable_let_patterns)]
|
||||||
|
async fn start_cli_reporting_task(mut aggregator_events_rx: Receiver<ReporterEvent>) {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
const GREEN: &str = "\x1B[32m";
|
const GREEN: &str = "\x1B[32m";
|
||||||
const RED: &str = "\x1B[31m";
|
const RED: &str = "\x1B[31m";
|
||||||
|
const GREY: &str = "\x1B[90m";
|
||||||
const COLOR_RESET: &str = "\x1B[0m";
|
const COLOR_RESET: &str = "\x1B[0m";
|
||||||
const BOLD: &str = "\x1B[1m";
|
const BOLD: &str = "\x1B[1m";
|
||||||
const BOLD_RESET: &str = "\x1B[22m";
|
const BOLD_RESET: &str = "\x1B[22m";
|
||||||
|
|
||||||
let mut number_of_successes = 0;
|
let mut number_of_successes = 0;
|
||||||
let mut number_of_failures = 0;
|
let mut number_of_failures = 0;
|
||||||
let mut failures = vec![];
|
|
||||||
|
|
||||||
// Wait for reports to come from our test runner. When the channel closes, this ends.
|
|
||||||
let mut buf = BufWriter::new(stderr());
|
let mut buf = BufWriter::new(stderr());
|
||||||
while let Some((test, case_result)) = report_rx.recv().await {
|
while let Ok(event) = aggregator_events_rx.recv().await {
|
||||||
let case_name = test.case.name.as_deref().unwrap_or("unnamed_case");
|
let ReporterEvent::MetadataFileSolcModeCombinationExecutionCompleted {
|
||||||
let case_idx = test.case_idx;
|
metadata_file_path,
|
||||||
let test_path = test.metadata_file_path.display();
|
mode,
|
||||||
let test_mode = test.mode.clone();
|
case_status,
|
||||||
|
} = event
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
match case_result {
|
let _ = writeln!(buf, "{} - {}", mode, metadata_file_path.display());
|
||||||
Ok(_inputs) => {
|
for (case_idx, case_status) in case_status.into_iter() {
|
||||||
number_of_successes += 1;
|
let _ = write!(buf, "\tCase Index {case_idx:>3}: ");
|
||||||
let _ = writeln!(
|
let _ = match case_status {
|
||||||
|
TestCaseStatus::Succeeded { steps_executed } => {
|
||||||
|
number_of_successes += 1;
|
||||||
|
writeln!(
|
||||||
|
buf,
|
||||||
|
"{}{}Case Succeeded{}{} - Steps Executed: {}",
|
||||||
|
GREEN, BOLD, BOLD_RESET, COLOR_RESET, steps_executed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
TestCaseStatus::Failed { reason } => {
|
||||||
|
number_of_failures += 1;
|
||||||
|
writeln!(
|
||||||
|
buf,
|
||||||
|
"{}{}Case Failed{}{} - Reason: {}",
|
||||||
|
RED, BOLD, BOLD_RESET, COLOR_RESET, reason
|
||||||
|
)
|
||||||
|
}
|
||||||
|
TestCaseStatus::Ignored { reason, .. } => writeln!(
|
||||||
buf,
|
buf,
|
||||||
"{GREEN}Case Succeeded:{COLOR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode})"
|
"{}{}Case Ignored{}{} - Reason: {}",
|
||||||
);
|
GREY, BOLD, BOLD_RESET, COLOR_RESET, reason
|
||||||
}
|
),
|
||||||
Err(err) => {
|
};
|
||||||
number_of_failures += 1;
|
|
||||||
let _ = writeln!(
|
|
||||||
buf,
|
|
||||||
"{RED}Case Failed:{COLOR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode})"
|
|
||||||
);
|
|
||||||
failures.push((test, err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = writeln!(buf,);
|
|
||||||
let elapsed = start.elapsed();
|
|
||||||
|
|
||||||
// Now, log the failures with more complete errors at the bottom, like `cargo test` does, so
|
|
||||||
// that we don't have to scroll through the entire output to find them.
|
|
||||||
if !failures.is_empty() {
|
|
||||||
let _ = writeln!(buf, "{BOLD}Failures:{BOLD_RESET}\n");
|
|
||||||
|
|
||||||
for failure in failures {
|
|
||||||
let (test, err) = failure;
|
|
||||||
let case_name = test.case.name.as_deref().unwrap_or("unnamed_case");
|
|
||||||
let case_idx = test.case_idx;
|
|
||||||
let test_path = test.metadata_file_path.display();
|
|
||||||
let test_mode = test.mode.clone();
|
|
||||||
|
|
||||||
let _ = writeln!(
|
|
||||||
buf,
|
|
||||||
"---- {RED}Case Failed:{COLOR_RESET} {test_path} -> {case_name}:{case_idx} (mode: {test_mode}) ----\n\n{err}\n"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
let _ = writeln!(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary at the end.
|
// Summary at the end.
|
||||||
let _ = writeln!(
|
let _ = writeln!(
|
||||||
buf,
|
buf,
|
||||||
"{} cases: {GREEN}{number_of_successes}{COLOR_RESET} cases succeeded, {RED}{number_of_failures}{COLOR_RESET} cases failed in {} seconds",
|
"{} cases: {}{}{} cases succeeded, {}{}{} cases failed in {} seconds",
|
||||||
number_of_successes + number_of_failures,
|
number_of_successes + number_of_failures,
|
||||||
elapsed.as_secs()
|
GREEN,
|
||||||
|
number_of_successes,
|
||||||
|
COLOR_RESET,
|
||||||
|
RED,
|
||||||
|
number_of_failures,
|
||||||
|
COLOR_RESET,
|
||||||
|
start.elapsed().as_secs()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ impl ReportAggregator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_test_succeeded_event(&mut self, event: TestSucceededEvent) {
|
fn handle_test_succeeded_event(&mut self, event: TestSucceededEvent) {
|
||||||
// Remove this from the set of cases we're tracking
|
// Remove this from the set of cases we're tracking since it has completed.
|
||||||
self.remaining_cases
|
self.remaining_cases
|
||||||
.entry(event.test_specifier.metadata_file_path.clone().into())
|
.entry(event.test_specifier.metadata_file_path.clone().into())
|
||||||
.or_default()
|
.or_default()
|
||||||
@@ -139,10 +139,11 @@ impl ReportAggregator {
|
|||||||
test_case_report.status = Some(TestCaseStatus::Succeeded {
|
test_case_report.status = Some(TestCaseStatus::Succeeded {
|
||||||
steps_executed: event.steps_executed,
|
steps_executed: event.steps_executed,
|
||||||
});
|
});
|
||||||
|
self.handle_post_test_case_status_update(&event.test_specifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_test_failed_event(&mut self, event: TestFailedEvent) {
|
fn handle_test_failed_event(&mut self, event: TestFailedEvent) {
|
||||||
// Remove this from the set of cases we're tracking
|
// Remove this from the set of cases we're tracking since it has completed.
|
||||||
self.remaining_cases
|
self.remaining_cases
|
||||||
.entry(event.test_specifier.metadata_file_path.clone().into())
|
.entry(event.test_specifier.metadata_file_path.clone().into())
|
||||||
.or_default()
|
.or_default()
|
||||||
@@ -155,10 +156,11 @@ impl ReportAggregator {
|
|||||||
test_case_report.status = Some(TestCaseStatus::Failed {
|
test_case_report.status = Some(TestCaseStatus::Failed {
|
||||||
reason: event.reason,
|
reason: event.reason,
|
||||||
});
|
});
|
||||||
|
self.handle_post_test_case_status_update(&event.test_specifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_test_ignored_event(&mut self, event: TestIgnoredEvent) {
|
fn handle_test_ignored_event(&mut self, event: TestIgnoredEvent) {
|
||||||
// Remove this from the set of cases we're tracking
|
// Remove this from the set of cases we're tracking since it has completed.
|
||||||
self.remaining_cases
|
self.remaining_cases
|
||||||
.entry(event.test_specifier.metadata_file_path.clone().into())
|
.entry(event.test_specifier.metadata_file_path.clone().into())
|
||||||
.or_default()
|
.or_default()
|
||||||
@@ -172,6 +174,45 @@ impl ReportAggregator {
|
|||||||
reason: event.reason,
|
reason: event.reason,
|
||||||
additional_fields: event.additional_fields,
|
additional_fields: event.additional_fields,
|
||||||
});
|
});
|
||||||
|
self.handle_post_test_case_status_update(&event.test_specifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_post_test_case_status_update(&mut self, specifier: &TestSpecifier) {
|
||||||
|
let remaining_cases = self
|
||||||
|
.remaining_cases
|
||||||
|
.entry(specifier.metadata_file_path.clone().into())
|
||||||
|
.or_default()
|
||||||
|
.entry(specifier.solc_mode.clone())
|
||||||
|
.or_default();
|
||||||
|
if !remaining_cases.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let case_status = self
|
||||||
|
.report
|
||||||
|
.test_case_information
|
||||||
|
.entry(specifier.metadata_file_path.clone().into())
|
||||||
|
.or_default()
|
||||||
|
.entry(specifier.solc_mode.clone())
|
||||||
|
.or_default()
|
||||||
|
.iter()
|
||||||
|
.map(|(case_idx, case_report)| {
|
||||||
|
(
|
||||||
|
*case_idx,
|
||||||
|
case_report.status.clone().expect("Can't be uninitialized"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<BTreeMap<_, _>>();
|
||||||
|
let event = ReporterEvent::MetadataFileSolcModeCombinationExecutionCompleted {
|
||||||
|
metadata_file_path: specifier.metadata_file_path.clone().into(),
|
||||||
|
mode: specifier.solc_mode.clone(),
|
||||||
|
case_status,
|
||||||
|
};
|
||||||
|
|
||||||
|
// According to the documentation on send, the sending fails if there are no more receiver
|
||||||
|
// handles. Therefore, this isn't an error that we want to bubble up or anything. If we fail
|
||||||
|
// to send then we ignore the error.
|
||||||
|
let _ = self.listener_tx.send(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_leader_node_assigned_event(&mut self, event: LeaderNodeAssignedEvent) {
|
fn handle_leader_node_assigned_event(&mut self, event: LeaderNodeAssignedEvent) {
|
||||||
|
|||||||
@@ -1,4 +1,22 @@
|
|||||||
//! A reporter event sent by the report aggregator to the various listeners.
|
//! A reporter event sent by the report aggregator to the various listeners.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use revive_dt_compiler::Mode;
|
||||||
|
use revive_dt_format::case::CaseIdx;
|
||||||
|
|
||||||
|
use crate::{MetadataFilePath, TestCaseStatus};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ReporterEvent {}
|
pub enum ReporterEvent {
|
||||||
|
/// An event sent by the reporter once an entire metadata file and solc mode combination has
|
||||||
|
/// finished execution.
|
||||||
|
MetadataFileSolcModeCombinationExecutionCompleted {
|
||||||
|
/// The path of the metadata file.
|
||||||
|
metadata_file_path: MetadataFilePath,
|
||||||
|
/// The Solc mode that this metadata file was executed in.
|
||||||
|
mode: Mode,
|
||||||
|
/// The status of each one of the cases.
|
||||||
|
case_status: BTreeMap<CaseIdx, TestCaseStatus>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user