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_eip8246_delayed_clear_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(
171 unit.transaction.secret_key.unwrap_or_default(),
172 ),
173 });
174 }
175 };
176
177 let result = execute_single_test_runtime(
178 &mut evm,
179 RuntimeTestContext {
180 test,
181 expected_output: unit.out.as_ref(),
182 name: &name,
183 tx: &tx,
184 cache_state: &cache_state,
185 elapsed,
186 },
187 );
188
189 if let Err(e) = result {
190 return Err(TestError { name, path: path_str, kind: e });
191 }
192 }
193 }
194 }
195 Ok(())
196}
197
198fn run_test_worker(
201 state: TestRunnerState,
202 keep_going: bool,
203 mode: CompileMode,
204 backend: Option<&JitBackend>,
205) -> Result<(), TestError> {
206 loop {
207 if !keep_going && state.n_errors.load(Ordering::SeqCst) > 0 {
208 return Ok(());
209 }
210
211 let Some(test_path) = state.next_test() else {
212 return Ok(());
213 };
214
215 let t0 = Instant::now();
216 let result = match mode {
217 CompileMode::Interpreter => {
218 execute_test_suite(&test_path, &state.elapsed, false, false)
219 }
220 CompileMode::Jit | CompileMode::Aot => {
221 execute_test_suite_runtime(&test_path, &state.elapsed, backend.unwrap())
222 }
223 };
224 let elapsed = t0.elapsed();
225 if elapsed > Duration::from_secs(5) {
226 eprintln!("slow statetest file ({elapsed:?}): {}", test_path.display());
227 }
228
229 state.console_bar.inc(1);
230
231 if let Err(err) = result {
232 state.n_errors.fetch_add(1, Ordering::SeqCst);
233 if !keep_going {
234 return Err(err);
235 }
236 }
237 }
238}
239
240pub fn run(
242 test_files: Vec<PathBuf>,
243 single_thread: bool,
244 keep_going: bool,
245 mode: CompileMode,
246) -> Result<(), TestError> {
247 let _ = tracing_subscriber::fmt::try_init();
248
249 let n_files = test_files.len();
250 let state = TestRunnerState::new(test_files);
251
252 let backend = if matches!(mode, CompileMode::Aot | CompileMode::Jit) {
253 let cpus = thread::available_parallelism().map(|n| n.get()).unwrap_or(1);
254 let store = if mode == CompileMode::Aot {
255 let store = RuntimeArtifactStore::new().map_err(|e| TestError {
256 name: "backend".to_string(),
257 path: String::new(),
258 kind: TestErrorKind::CompilationError(format!("tempdir: {e}")),
259 })?;
260 Some(Arc::new(store) as Arc<dyn ArtifactStore>)
261 } else {
262 None
263 };
264 let config = RuntimeConfig {
265 enabled: true,
266 blocking: true,
267 aot: mode == CompileMode::Aot,
268 store,
269 tuning: RuntimeTuning {
270 jit_hot_threshold: 0,
271 jit_worker_count: cpus,
272 ..Default::default()
273 },
274 ..Default::default()
275 };
276 Some(JitBackend::new(config).map_err(|e| TestError {
277 name: "backend".to_string(),
278 path: String::new(),
279 kind: TestErrorKind::CompilationError(format!("backend start: {e}")),
280 })?)
281 } else {
282 None
283 };
284
285 let num_threads = if single_thread {
286 1
287 } else {
288 match thread::available_parallelism() {
289 Ok(n) => n.get().min(n_files),
290 Err(_) => 1,
291 }
292 };
293
294 let barrier = Arc::new(Barrier::new(num_threads));
295
296 let mut handles = Vec::with_capacity(num_threads);
297 for i in 0..num_threads {
298 let state = state.clone();
299 let backend = backend.clone();
300 let barrier = barrier.clone();
301
302 let thread = Builder::new()
303 .name(format!("runner-{i}"))
304 .spawn(move || {
305 let stop = state.stop.clone();
310 let result = panic::catch_unwind(AssertUnwindSafe(|| {
311 run_test_worker(state, keep_going, mode, backend.as_ref())
312 }));
313 if result.is_err() || (!keep_going && result.as_ref().is_ok_and(|r| r.is_err())) {
314 stop.store(true, Ordering::SeqCst);
315 }
316 barrier.wait();
320 match result {
321 Ok(r) => r,
322 Err(payload) => panic::resume_unwind(payload),
323 }
324 })
325 .unwrap();
326
327 handles.push(thread);
328 }
329
330 let mut thread_errors = Vec::new();
331 for (i, handle) in handles.into_iter().enumerate() {
332 match handle.join() {
333 Ok(Ok(())) => {}
334 Ok(Err(e)) => thread_errors.push(e),
335 Err(_) => thread_errors.push(TestError {
336 name: format!("thread {i} panicked"),
337 path: String::new(),
338 kind: TestErrorKind::Panic,
339 }),
340 }
341 }
342
343 state.console_bar.finish();
344
345 println!(
346 "Finished execution. Total CPU time: {:.6}s",
347 state.elapsed.lock().unwrap().as_secs_f64()
348 );
349
350 if let Some(backend) = &backend {
351 let stats = backend.stats();
352 println!(
353 "Runtime backend: {} hits, {} misses, {} resident",
354 stats.lookup_hits, stats.lookup_misses, stats.resident_entries,
355 );
356 }
357
358 drop(backend);
359
360 let n_errors = state.n_errors.load(Ordering::SeqCst);
361 let n_thread_errors = thread_errors.len();
362
363 if n_errors == 0 && n_thread_errors == 0 {
364 println!("All tests passed!");
365 Ok(())
366 } else {
367 println!("Encountered {n_errors} errors out of {n_files} total tests");
368
369 if n_thread_errors == 0 {
370 process::exit(1);
371 }
372
373 if n_thread_errors > 1 {
374 println!("{n_thread_errors} threads returned an error, out of {num_threads} total:");
375 for error in &thread_errors {
376 println!("{error}");
377 }
378 }
379 Err(thread_errors.swap_remove(0))
380 }
381}