use std::{
process::Command,
sync::{Arc, Mutex},
time::Duration,
};
const CACHE: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/replay_offline.cache.json");
const TX: &str = "0x41d34e7e13dfe0f85da9d407e2b2c381955d8c7eed428b17dc82327b2616b000";
fn mega_evme() -> Command {
Command::new(env!("CARGO_BIN_EXE_mega-evme"))
}
#[test]
fn test_replay_dump_rejects_transaction_overrides() {
let out = std::env::temp_dir().join(format!("mega_evme_dump_ovr_{}.json", std::process::id()));
let _ = std::fs::remove_file(&out);
let output = mega_evme()
.args([
"replay",
"--rpc.replay-file",
CACHE,
"--dump-fixture",
out.to_str().unwrap(),
"--override.gas-limit",
"50000",
TX,
])
.output()
.expect("failed to run mega-evme");
assert!(!output.status.success(), "--dump-fixture + overrides should fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("cannot be combined with transaction"),
"expected override-incompatibility error, got stderr:\n{stderr}"
);
assert!(!out.exists(), "must not write a fixture when the run is rejected");
}
#[test]
fn test_replay_dump_fixture_writes_validatable_file() {
let out = std::env::temp_dir().join(format!("mega_evme_dump_{}.json", std::process::id()));
let _ = std::fs::remove_file(&out);
let output = mega_evme()
.args(["replay", "--rpc.replay-file", CACHE, "--dump-fixture", out.to_str().unwrap(), TX])
.output()
.expect("failed to run mega-evme");
assert!(
output.status.success(),
"dump failed.\nstderr: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(out.exists(), "dump did not write the fixture file");
let elapsed = Arc::new(Mutex::new(Duration::ZERO));
let result = state_test::runner::execute_test_suite(&out, &elapsed, false, false);
let _ = std::fs::remove_file(&out);
result.unwrap_or_else(|e| panic!("dumped fixture failed to validate: {e}"));
}
#[test]
fn test_replay_dump_is_byte_reproducible() {
let dump_to = |suffix: &str| {
let out = std::env::temp_dir()
.join(format!("mega_evme_repro_{}_{suffix}.json", std::process::id()));
let _ = std::fs::remove_file(&out);
let output = mega_evme()
.args([
"replay",
"--rpc.replay-file",
CACHE,
"--dump-fixture",
out.to_str().unwrap(),
TX,
])
.output()
.expect("failed to run mega-evme");
assert!(
output.status.success(),
"dump failed.\nstderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let bytes = std::fs::read(&out).expect("read dumped fixture");
let _ = std::fs::remove_file(&out);
bytes
};
assert_eq!(
dump_to("a"),
dump_to("b"),
"two dumps of the same transaction must be byte-identical"
);
}
#[test]
fn test_replay_dump_overwrites_atomically_without_tmp_residue() {
let out =
std::env::temp_dir().join(format!("mega_evme_dump_atomic_{}.json", std::process::id()));
let tmp = out.with_extension("json.tmp");
let _ = std::fs::remove_file(&tmp);
std::fs::write(&out, br#"{"pre-existing":"corpus fixture"}"#).expect("seed existing fixture");
let output = mega_evme()
.args(["replay", "--rpc.replay-file", CACHE, "--dump-fixture", out.to_str().unwrap(), TX])
.output()
.expect("failed to run mega-evme");
assert!(
output.status.success(),
"dump over an existing fixture failed.\nstderr: {}",
String::from_utf8_lossy(&output.stderr)
);
assert!(!tmp.exists(), "dump must not leave a .json.tmp file behind");
let content = std::fs::read_to_string(&out).expect("read dumped fixture");
let _ = std::fs::remove_file(&out);
let suite: serde_json::Value =
serde_json::from_str(&content).expect("overwritten fixture must be valid JSON");
let unit = suite
.get(format!("replay_{TX}"))
.expect("overwritten fixture must hold the replayed transaction's unit");
assert!(
unit.get("post").is_some(),
"overwritten fixture must contain the freshly computed post section"
);
}
#[test]
fn test_replay_dump_rejects_receipt_from_different_block() {
let mut envelope: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(CACHE).expect("read offline cache"))
.expect("parse offline cache");
let mut doctored = false;
for entry in envelope["cache"].as_array_mut().expect("cache entries").iter_mut() {
let value = entry["value"].as_str().expect("entry value is a string");
if !value.contains("cumulativeGasUsed") {
continue;
}
let mut response: serde_json::Value =
serde_json::from_str(value).expect("parse receipt response");
response["result"]["blockHash"] = serde_json::Value::String(
"0x1111111111111111111111111111111111111111111111111111111111111111".into(),
);
entry["value"] = serde_json::Value::String(response.to_string());
doctored = true;
}
assert!(doctored, "offline cache should contain the receipt entry");
let doctored_cache =
std::env::temp_dir().join(format!("mega_evme_reorg_cache_{}.json", std::process::id()));
std::fs::write(&doctored_cache, envelope.to_string()).expect("write doctored cache");
let out =
std::env::temp_dir().join(format!("mega_evme_dump_reorg_{}.json", std::process::id()));
let _ = std::fs::remove_file(&out);
let output = mega_evme()
.args([
"replay",
"--rpc.replay-file",
doctored_cache.to_str().unwrap(),
"--dump-fixture",
out.to_str().unwrap(),
TX,
])
.output()
.expect("failed to run mega-evme");
let _ = std::fs::remove_file(&doctored_cache);
assert!(!output.status.success(), "a receipt from a different block must abort the dump");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("different inclusion"),
"expected reorg/divergent-endpoint hint, got stderr:\n{stderr}"
);
assert!(!out.exists(), "must not write a fixture when the receipt anchor mismatches");
}
#[test]
fn test_replay_dump_rejects_spec_override() {
let out = std::env::temp_dir().join(format!("mega_evme_dump_spec_{}.json", std::process::id()));
let _ = std::fs::remove_file(&out);
let output = mega_evme()
.args([
"replay",
"--rpc.replay-file",
CACHE,
"--dump-fixture",
out.to_str().unwrap(),
"--override.spec",
"Rex4",
TX,
])
.output()
.expect("failed to run mega-evme");
assert!(!output.status.success(), "--dump-fixture + --override.spec should fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("cannot be combined with --override.spec"),
"expected spec-override-incompatibility error, got stderr:\n{stderr}"
);
assert!(!out.exists(), "must not write a fixture when the run is rejected");
}