1use crate::runner::{
4 TestError, TestErrorKind, TestRunnerState, check_evm_execution, execute_test_suite, skip_test,
5};
6use revm_context::{Cfg, Context, Journal, cfg::CfgEnv, tx::TxEnv};
7use revm_context_interface::journaled_state::JournalTr;
8use revm_database::{self as database};
9use revm_database_interface::{DatabaseCommit, EmptyDB};
10use revm_handler::{Handler, MainBuilder, MainContext, MainnetContext, MainnetEvm};
11use revm_primitives::{U256, hardfork::SpecId};
12use revm_statetest_types::{SpecName, TestSuite};
13use std::{
14 fs,
15 panic::{self, AssertUnwindSafe},
16 path::{Path, PathBuf},
17 process,
18 sync::{Arc, Barrier, Mutex, atomic::Ordering},
19 thread::{self, Builder},
20 time::{Duration, Instant},
21};
22
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
27pub enum CompileMode {
28 #[default]
30 Interpreter,
31 Jit,
33 Aot,
35}
36
37use revmc::{
40 revm_evm::JitEvm,
41 runtime::{ArtifactStore, JitBackend, RuntimeArtifactStore, RuntimeConfig, RuntimeTuning},
42};
43
44type RuntimeState = database::State<EmptyDB>;
45type RuntimeEvm = JitEvm<MainnetEvm<MainnetContext<RuntimeState>>>;
46
47fn execute_single_test_runtime(
49 evm: &mut RuntimeEvm,
50 ctx: RuntimeTestContext<'_>,
51) -> Result<(), TestErrorKind> {
52 let prestate = ctx.cache_state.clone();
53 let state =
54 database::State::builder().with_cached_prestate(prestate).with_bundle_update().build();
55 let mut journal = Journal::new(state);
56 journal.set_spec_id(*evm.ctx.cfg.spec());
57 journal.set_eip7708_config(
58 evm.ctx.cfg.is_eip7708_disabled(),
59 evm.ctx.cfg.is_eip7708_delayed_burn_disabled(),
60 );
61
62 let timer = Instant::now();
63 evm.ctx.tx = ctx.tx.clone();
64 evm.ctx.journaled_state = journal;
65
66 let mut handler = revm_handler::MainnetHandler::default();
67 let exec_result = handler.run(evm);
68 if exec_result.is_ok() {
69 let s = evm.ctx.journaled_state.finalize();
70 DatabaseCommit::commit(&mut evm.ctx.journaled_state.database, s);
71 }
72 *ctx.elapsed.lock().unwrap() += timer.elapsed();
73
74 let spec = *evm.ctx.cfg.spec();
75 check_evm_execution(
76 ctx.test,
77 ctx.expected_output,
78 ctx.name,
79 &exec_result,
80 &mut evm.ctx.journaled_state.database,
81 spec,
82 false,
83 )
84}
85
86struct RuntimeTestContext<'a> {
87 test: &'a revm_statetest_types::Test,
88 expected_output: Option<&'a revm_primitives::Bytes>,
89 name: &'a str,
90 tx: &'a TxEnv,
91 cache_state: &'a database::CacheState,
92 elapsed: &'a Arc<Mutex<Duration>>,
93}
94
95fn skip_runtime_test(path: &Path) -> bool {
96 if skip_test(path) {
97 return true;
98 }
99
100 path.file_name().is_some_and(|name| {
105 name == "test_stack_overflow.json" || name == "precompsEIP2929Cancun.json"
106 })
107}
108
109fn execute_test_suite_runtime(
113 path: &Path,
114 elapsed: &Arc<Mutex<Duration>>,
115 backend: &JitBackend,
116) -> Result<(), TestError> {
117 if skip_runtime_test(path) {
118 return Ok(());
119 }
120
121 let s = fs::read_to_string(path).unwrap();
122 let path_str = path.to_string_lossy().into_owned();
123 let suite: TestSuite = serde_json::from_str(&s).map_err(|e| TestError {
124 name: "Unknown".to_string(),
125 path: path_str.clone(),
126 kind: e.into(),
127 })?;
128
129 for (name, unit) in suite.0 {
130 let cache_state = unit.state();
131 let mut cfg = CfgEnv::default();
132 cfg.chain_id = unit.env.current_chain_id.unwrap_or(U256::ONE).try_into().unwrap_or(1);
133
134 for (spec_name, tests) in &unit.post {
135 if *spec_name == SpecName::Constantinople {
136 continue;
137 }
138
139 let spec_id = spec_name.to_spec_id();
140 cfg.set_spec_and_mainnet_gas_params(spec_id);
141
142 if cfg.spec().is_enabled_in(SpecId::OSAKA) {
143 cfg.set_max_blobs_per_tx(6);
144 } else if cfg.spec().is_enabled_in(SpecId::PRAGUE) {
145 cfg.set_max_blobs_per_tx(9);
146 } else {
147 cfg.set_max_blobs_per_tx(6);
148 }
149
150 let block = unit.block_env(&mut cfg);
151 let initial_state = database::State::builder()
152 .with_cached_prestate(cache_state.clone())
153 .with_bundle_update()
154 .build();
155 let evm_context = Context::mainnet()
156 .with_block(block.clone())
157 .with_cfg(cfg.clone())
158 .with_db(initial_state);
159 let inner = evm_context.build_mainnet();
160 let mut evm = JitEvm::new(inner, backend.clone());
161
162 for test in tests.iter() {
163 let tx = match test.tx_env(&unit) {
164 Ok(tx) => tx,
165 Err(_) if test.expect_exception.is_some() => continue,
166 Err(_) => {
167 return Err(TestError {
168 name,
169 path: path_str,
170 kind: TestErrorKind::UnknownPrivateKey(unit.transaction.secret_key),
171 });
172 }
173 };
174
175 let result = execute_single_test_runtime(
176 &mut evm,
177 RuntimeTestContext {
178 test,
179 expected_output: unit.out.as_ref(),
180 name: &name,
181 tx: &tx,
182 cache_state: &cache_state,
183 elapsed,
184 },
185 );
186
187 if let Err(e) = result {
188 return Err(TestError { name, path: path_str, kind: e });
189 }
190 }
191 }
192 }
193 Ok(())
194}
195
196fn run_test_worker(
199 state: TestRunnerState,
200 keep_going: bool,
201 mode: CompileMode,
202 backend: Option<&JitBackend>,
203) -> Result<(), TestError> {
204 loop {
205 if !keep_going && state.n_errors.load(Ordering::SeqCst) > 0 {
206 return Ok(());
207 }
208
209 let Some(test_path) = state.next_test() else {
210 return Ok(());
211 };
212
213 let t0 = Instant::now();
214 let result = match mode {
215 CompileMode::Interpreter => {
216 execute_test_suite(&test_path, &state.elapsed, false, false)
217 }
218 CompileMode::Jit | CompileMode::Aot => {
219 execute_test_suite_runtime(&test_path, &state.elapsed, backend.unwrap())
220 }
221 };
222 let elapsed = t0.elapsed();
223 if elapsed > Duration::from_secs(5) {
224 eprintln!("slow statetest file ({elapsed:?}): {}", test_path.display());
225 }
226
227 state.console_bar.inc(1);
228
229 if let Err(err) = result {
230 state.n_errors.fetch_add(1, Ordering::SeqCst);
231 if !keep_going {
232 return Err(err);
233 }
234 }
235 }
236}
237
238pub fn run(
240 test_files: Vec<PathBuf>,
241 single_thread: bool,
242 keep_going: bool,
243 mode: CompileMode,
244) -> Result<(), TestError> {
245 let _ = tracing_subscriber::fmt::try_init();
246
247 let n_files = test_files.len();
248 let state = TestRunnerState::new(test_files);
249
250 let backend = if matches!(mode, CompileMode::Aot | CompileMode::Jit) {
251 let cpus = thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
252 let store = if mode == CompileMode::Aot {
253 let store = RuntimeArtifactStore::new().map_err(|e| TestError {
254 name: "backend".to_string(),
255 path: String::new(),
256 kind: TestErrorKind::CompilationError(format!("tempdir: {e}")),
257 })?;
258 Some(Arc::new(store) as Arc<dyn ArtifactStore>)
259 } else {
260 None
261 };
262 let config = RuntimeConfig {
263 enabled: true,
264 blocking: true,
265 aot: mode == CompileMode::Aot,
266 store,
267 tuning: RuntimeTuning {
268 jit_hot_threshold: 0,
269 jit_worker_count: cpus,
270 ..Default::default()
271 },
272 ..Default::default()
273 };
274 Some(JitBackend::new(config).map_err(|e| TestError {
275 name: "backend".to_string(),
276 path: String::new(),
277 kind: TestErrorKind::CompilationError(format!("backend start: {e}")),
278 })?)
279 } else {
280 None
281 };
282
283 let num_threads = if single_thread {
284 1
285 } else {
286 match thread::available_parallelism() {
287 Ok(n) => n.get().min(n_files),
288 Err(_) => 1,
289 }
290 };
291
292 let barrier = Arc::new(Barrier::new(num_threads));
293
294 let mut handles = Vec::with_capacity(num_threads);
295 for i in 0..num_threads {
296 let state = state.clone();
297 let backend = backend.clone();
298 let barrier = barrier.clone();
299
300 let thread = Builder::new()
301 .name(format!("runner-{i}"))
302 .spawn(move || {
303 let stop = state.stop.clone();
308 let result = panic::catch_unwind(AssertUnwindSafe(|| {
309 run_test_worker(state, keep_going, mode, backend.as_ref())
310 }));
311 if result.is_err() || (!keep_going && result.as_ref().is_ok_and(|r| r.is_err())) {
312 stop.store(true, Ordering::SeqCst);
313 }
314 barrier.wait();
318 match result {
319 Ok(r) => r,
320 Err(payload) => panic::resume_unwind(payload),
321 }
322 })
323 .unwrap();
324
325 handles.push(thread);
326 }
327
328 let mut thread_errors = Vec::new();
329 for (i, handle) in handles.into_iter().enumerate() {
330 match handle.join() {
331 Ok(Ok(())) => {}
332 Ok(Err(e)) => thread_errors.push(e),
333 Err(_) => thread_errors.push(TestError {
334 name: format!("thread {i} panicked"),
335 path: String::new(),
336 kind: TestErrorKind::Panic,
337 }),
338 }
339 }
340
341 state.console_bar.finish();
342
343 println!(
344 "Finished execution. Total CPU time: {:.6}s",
345 state.elapsed.lock().unwrap().as_secs_f64()
346 );
347
348 if let Some(backend) = &backend {
349 let stats = backend.stats();
350 println!(
351 "Runtime backend: {} hits, {} misses, {} resident",
352 stats.lookup_hits, stats.lookup_misses, stats.resident_entries,
353 );
354 }
355
356 drop(backend);
357
358 let n_errors = state.n_errors.load(Ordering::SeqCst);
359 let n_thread_errors = thread_errors.len();
360
361 if n_errors == 0 && n_thread_errors == 0 {
362 println!("All tests passed!");
363 Ok(())
364 } else {
365 println!("Encountered {n_errors} errors out of {n_files} total tests");
366
367 if n_thread_errors == 0 {
368 process::exit(1);
369 }
370
371 if n_thread_errors > 1 {
372 println!("{n_thread_errors} threads returned an error, out of {num_threads} total:");
373 for error in &thread_errors {
374 println!("{error}");
375 }
376 }
377 Err(thread_errors.swap_remove(0))
378 }
379}