From ba8d44b8ce22ceaec493b320dc208a8dd8448b0e Mon Sep 17 00:00:00 2001 From: Shashank Date: Fri, 10 Jul 2026 04:17:26 +0530 Subject: [PATCH 1/3] strict raw json check --- src/tool/subcommands/api_cmd/test_snapshot.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/tool/subcommands/api_cmd/test_snapshot.rs b/src/tool/subcommands/api_cmd/test_snapshot.rs index 48973697b66d..ef9999ef214e 100644 --- a/src/tool/subcommands/api_cmd/test_snapshot.rs +++ b/src/tool/subcommands/api_cmd/test_snapshot.rs @@ -137,12 +137,9 @@ pub async fn run_test_from_snapshot(path: &Path) -> anyhow::Result<()> { .context("failed to parse params")?; let result = <$ty>::handle(ctx.clone(), params, &ext) .await - .map(|r| r.into_lotus_json()) - .map_err(|e| e.deref().to_string()); - let expected = match expected_response.clone() { - Ok(v) => serde_json::from_value(v).map_err(|e| e.to_string()), - Err(e) => Err(e), - }; + .map_err(|e| e.deref().to_string()) + .and_then(|r| r.into_lotus_json_value().map_err(|e| e.to_string())); + let expected = expected_response.clone(); pretty_assertions::assert_eq!(result, expected); run = true; } From fa3adc58a586e1c915c457b9da241cf726ca48cd Mon Sep 17 00:00:00 2001 From: Shashank Date: Fri, 10 Jul 2026 11:32:30 +0530 Subject: [PATCH 2/3] skip non-deterministic and known divergence fields --- src/tool/subcommands/api_cmd/test_snapshot.rs | 40 ++++++++++++++++++- .../subcommands/api_cmd/test_snapshots.txt | 2 +- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/tool/subcommands/api_cmd/test_snapshot.rs b/src/tool/subcommands/api_cmd/test_snapshot.rs index ef9999ef214e..9400c43796db 100644 --- a/src/tool/subcommands/api_cmd/test_snapshot.rs +++ b/src/tool/subcommands/api_cmd/test_snapshot.rs @@ -70,6 +70,36 @@ fn backfill_eth_mappings(db: &MemoryDB, index: Option) -> anyhow::Result< Ok(()) } +/// JSON fields removed from both actual and expected responses before the +/// strict raw-JSON snapshot comparison. See each group below for why. +const SKIPPED_COMPARISON_FIELDS: &[&str] = &[ + // Skip time taken and duration as they are non-deterministic. + "tt", + "Duration", + // Skip `accessList` as it is known-divergent from Lotus. + // See . + "accessList", +]; + +/// Recursively remove [`SKIPPED_COMPARISON_FIELDS`] from a JSON value so the +/// strict raw-JSON snapshot comparison skips them. +fn strip_skipped_fields(value: &mut serde_json::Value) { + match value { + serde_json::Value::Object(map) => { + for field in SKIPPED_COMPARISON_FIELDS { + map.remove(*field); + } + for val in map.values_mut() { + strip_skipped_fields(val); + } + } + serde_json::Value::Array(items) => { + items.iter_mut().for_each(strip_skipped_fields); + } + _ => {} + } +} + pub async fn run_test_from_snapshot(path: &Path) -> anyhow::Result<()> { let mut run = false; let snapshot_bytes = std::fs::read(path)?; @@ -135,11 +165,17 @@ pub async fn run_test_from_snapshot(path: &Path) -> anyhow::Result<()> { { let params = <$ty>::parse_params(params_raw.clone(), ParamStructure::Either) .context("failed to parse params")?; - let result = <$ty>::handle(ctx.clone(), params, &ext) + let mut result = <$ty>::handle(ctx.clone(), params, &ext) .await .map_err(|e| e.deref().to_string()) .and_then(|r| r.into_lotus_json_value().map_err(|e| e.to_string())); - let expected = expected_response.clone(); + let mut expected = expected_response.clone(); + if let Ok(v) = result.as_mut() { + strip_skipped_fields(v); + } + if let Ok(v) = expected.as_mut() { + strip_skipped_fields(v); + } pretty_assertions::assert_eq!(result, expected); run = true; } diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index 4d71573e7d28..3693e485c296 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -12,7 +12,7 @@ filecoin_chaingetfinalizedtipset_1776082510830037.rpcsnap.json.zst filecoin_chaingetgenesis_1736937286915866.rpcsnap.json.zst filecoin_chaingetmessage_1758734340836824.rpcsnap.json.zst filecoin_chaingetmessagesintipset_1758734696116136.rpcsnap.json.zst -filecoin_chaingetparentmessages_1736937305551928.rpcsnap.json.zst +filecoin_chaingetparentmessages_1783660760016875.rpcsnap.json.zst filecoin_chaingetparentreceipts_1736937305550289.rpcsnap.json.zst filecoin_chaingetpath_1736937942817384.rpcsnap.json.zst filecoin_chaingettipset_1736937942817675.rpcsnap.json.zst From 4ef762792404f15a471b4a89df389bdfeec320d4 Mon Sep 17 00:00:00 2001 From: Shashank Date: Mon, 13 Jul 2026 14:32:30 +0530 Subject: [PATCH 3/3] filter fields by methods --- src/tool/subcommands/api_cmd/test_snapshot.rs | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/src/tool/subcommands/api_cmd/test_snapshot.rs b/src/tool/subcommands/api_cmd/test_snapshot.rs index 9400c43796db..5c88445fc325 100644 --- a/src/tool/subcommands/api_cmd/test_snapshot.rs +++ b/src/tool/subcommands/api_cmd/test_snapshot.rs @@ -70,31 +70,44 @@ fn backfill_eth_mappings(db: &MemoryDB, index: Option) -> anyhow::Result< Ok(()) } -/// JSON fields removed from both actual and expected responses before the -/// strict raw-JSON snapshot comparison. See each group below for why. -const SKIPPED_COMPARISON_FIELDS: &[&str] = &[ - // Skip time taken and duration as they are non-deterministic. - "tt", - "Duration", - // Skip `accessList` as it is known-divergent from Lotus. - // See . - "accessList", -]; +/// JSON fields filtered from both actual and expected responses +/// before strict raw-JSON snapshot comparison, scoped to the methods whose +/// responses actually contain them. +fn fields_to_filter(method: &str) -> &'static [&'static str] { + match method { + // Skip time taken and duration as they are non-deterministic. + "Filecoin.StateCall" | "Filecoin.StateReplay" | "Filecoin.StateCompute" => { + &["Duration", "tt"] + } + // Skip `accessList` as it is known-divergent from Lotus. + // See . + "Filecoin.EthGetBlockByHash" + | "Filecoin.EthGetBlockByNumber" + | "Filecoin.EthGetTransactionByHash" + | "Filecoin.EthGetTransactionByHashLimited" + | "Filecoin.EthGetTransactionByBlockHashAndIndex" + | "Filecoin.EthGetTransactionByBlockNumberAndIndex" => &["accessList"], + _ => &[], + } +} -/// Recursively remove [`SKIPPED_COMPARISON_FIELDS`] from a JSON value so the -/// strict raw-JSON snapshot comparison skips them. -fn strip_skipped_fields(value: &mut serde_json::Value) { +/// Recursively filters `fields` from a JSON value so the strict raw-JSON +/// snapshot comparison ignores them. +fn filter_out_fields(value: &mut serde_json::Value, fields: &[&str]) { + if fields.is_empty() { + return; + } match value { serde_json::Value::Object(map) => { - for field in SKIPPED_COMPARISON_FIELDS { + for field in fields { map.remove(*field); } for val in map.values_mut() { - strip_skipped_fields(val); + filter_out_fields(val, fields); } } serde_json::Value::Array(items) => { - items.iter_mut().for_each(strip_skipped_fields); + items.iter_mut().for_each(|v| filter_out_fields(v, fields)); } _ => {} } @@ -170,11 +183,12 @@ pub async fn run_test_from_snapshot(path: &Path) -> anyhow::Result<()> { .map_err(|e| e.deref().to_string()) .and_then(|r| r.into_lotus_json_value().map_err(|e| e.to_string())); let mut expected = expected_response.clone(); + let fields = fields_to_filter(<$ty>::NAME); if let Ok(v) = result.as_mut() { - strip_skipped_fields(v); + filter_out_fields(v, fields); } if let Ok(v) = expected.as_mut() { - strip_skipped_fields(v); + filter_out_fields(v, fields); } pretty_assertions::assert_eq!(result, expected); run = true;