ef_tests/
result.rs

1//! Test results and errors
2
3use crate::Case;
4use reth_db::DatabaseError;
5use reth_ethereum_primitives::Block;
6use reth_primitives_traits::RecoveredBlock;
7use reth_provider::ProviderError;
8use reth_stateless::ExecutionWitness;
9use std::path::{Path, PathBuf};
10use thiserror::Error;
11
12/// Test errors
13///
14/// # Note
15///
16/// `Error::Skipped` should not be treated as a test failure.
17#[derive(Debug, Error)]
18#[non_exhaustive]
19pub enum Error {
20    /// The test was skipped
21    #[error("test was skipped")]
22    Skipped,
23    /// Block processing failed
24    /// Note: This includes but is not limited to execution.
25    /// For example, the header number could be incorrect.
26    #[error("block {block_number} failed to process: {err}")]
27    BlockProcessingFailed {
28        /// The block number for the block that failed
29        block_number: u64,
30        /// Contains the inputs necessary for the block stateless validation guest program used in
31        /// zkVMs to prove the block is invalid.
32        partial_program_inputs: Vec<(RecoveredBlock<Block>, ExecutionWitness)>,
33        /// The specific error
34        #[source]
35        err: Box<dyn std::error::Error + Send + Sync>,
36    },
37    /// An IO error occurred
38    #[error("an error occurred interacting with the file system at {path}: {error}")]
39    Io {
40        /// The path to the file or directory
41        path: PathBuf,
42        /// The specific error
43        #[source]
44        error: std::io::Error,
45    },
46    /// A deserialization error occurred
47    #[error("an error occurred deserializing the test at {path}: {error}")]
48    CouldNotDeserialize {
49        /// The path to the file we wanted to deserialize
50        path: PathBuf,
51        /// The specific error
52        #[source]
53        error: serde_json::Error,
54    },
55    /// A database error occurred.
56    #[error(transparent)]
57    Database(#[from] DatabaseError),
58    /// A test assertion failed.
59    #[error("test failed: {0}")]
60    Assertion(String),
61    /// An error internally in reth occurred.
62    #[error("test failed: {0}")]
63    Provider(#[from] ProviderError),
64    /// An error occurred while decoding RLP.
65    #[error("an error occurred deserializing RLP: {0}")]
66    RlpDecodeError(#[from] alloy_rlp::Error),
67    /// A consensus error occurred.
68    #[error("an error occurred during consensus checks: {0}")]
69    ConsensusError(#[from] reth_consensus::ConsensusError),
70}
71
72impl Error {
73    /// Create a new [`Error::BlockProcessingFailed`] error.
74    pub fn block_failed(
75        block_number: u64,
76        partial_program_inputs: Vec<(RecoveredBlock<Block>, ExecutionWitness)>,
77        err: impl std::error::Error + Send + Sync + 'static,
78    ) -> Self {
79        Self::BlockProcessingFailed { block_number, partial_program_inputs, err: Box::new(err) }
80    }
81}
82
83/// The result of running a test.
84#[derive(Debug)]
85pub struct CaseResult {
86    /// A description of the test.
87    pub desc: String,
88    /// The full path to the test.
89    pub path: PathBuf,
90    /// The result of the test.
91    pub result: Result<(), Error>,
92}
93
94impl CaseResult {
95    /// Create a new test result.
96    pub fn new(path: &Path, case: &impl Case, result: Result<(), Error>) -> Self {
97        Self { desc: case.description(), path: path.into(), result }
98    }
99}
100
101/// Assert that all the given tests passed and print the results to stdout.
102pub(crate) fn assert_tests_pass(suite_name: &str, path: &Path, results: &[CaseResult]) {
103    let (passed, failed, skipped) = categorize_results(results);
104
105    print_results(suite_name, path, &passed, &failed, &skipped);
106
107    assert!(failed.is_empty(), "Some tests failed (see above)");
108}
109
110/// Categorize test results into `(passed, failed, skipped)`.
111pub(crate) fn categorize_results(
112    results: &[CaseResult],
113) -> (Vec<&CaseResult>, Vec<&CaseResult>, Vec<&CaseResult>) {
114    let mut passed = Vec::new();
115    let mut failed = Vec::new();
116    let mut skipped = Vec::new();
117
118    for case in results {
119        match case.result.as_ref().err() {
120            Some(Error::Skipped) => skipped.push(case),
121            Some(_) => failed.push(case),
122            None => passed.push(case),
123        }
124    }
125
126    (passed, failed, skipped)
127}
128
129/// Display the given test results to stdout.
130pub(crate) fn print_results(
131    suite_name: &str,
132    path: &Path,
133    passed: &[&CaseResult],
134    failed: &[&CaseResult],
135    skipped: &[&CaseResult],
136) {
137    println!("Suite: {suite_name} (at {})", path.display());
138    println!(
139        "Ran {} tests ({} passed, {} failed, {} skipped)",
140        passed.len() + failed.len() + skipped.len(),
141        passed.len(),
142        failed.len(),
143        skipped.len()
144    );
145
146    for case in skipped {
147        println!("[S] Case {} skipped", case.path.display());
148    }
149
150    for case in failed {
151        let error = case.result.as_ref().unwrap_err();
152        println!("[!] Case {} failed (description: {}): {}", case.path.display(), case.desc, error);
153    }
154}