diff --git a/.config/nextest.toml b/.config/nextest.toml index a28c1314f3a6..bfdad1d17591 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -29,12 +29,6 @@ slow-timeout = { period = "120s", terminate-after = 3 } filter = 'test(state_migration_generate_actors_metadata)' slow-timeout = { period = "120s", terminate-after = 3 } -# This test downloads RPC test snapshot files from the network, which can take a while. -# It is only run on CI, so we can afford to be more patient. -[[profile.default.overrides]] -filter = 'test(tool::subcommands::api_cmd::test_snapshot::tests::rpc_regression_tests)' -slow-timeout = { period = "120s", terminate-after = 3 } - [[profile.default.overrides]] # lint runs `cargo check` for source file discovery, which can take a while filter = 'binary(lint)' diff --git a/build.rs b/build.rs index 89083851344e..48230ed761a0 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,8 @@ // Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT +use std::io::Write; + fn main() { // whitelist the cfg for cargo clippy println!("cargo::rustc-check-cfg=cfg(f3sidecar)"); @@ -30,6 +32,8 @@ fn main() { // }) .build(); } + + rpc_regression_tests_gen(); } // See @@ -44,3 +48,38 @@ fn is_sidecar_ffi_enabled() -> bool { _ => true, } } + +fn rpc_regression_tests_gen() { + use std::{fs::File, io::BufWriter, path::PathBuf}; + + println!("cargo:rerun-if-changed=src/tool/subcommands/api_cmd/test_snapshots.txt"); + + let tests: Vec<&str> = include_str!("src/tool/subcommands/api_cmd/test_snapshots.txt") + .lines() + .map(str::trim) + .filter(|l| !l.is_empty() && !l.starts_with('#')) + .collect(); + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let out_path = out_dir.join("__rpc_regression_tests_gen.rs"); + let mut w = BufWriter::new(File::create(&out_path).unwrap()); + for test in tests { + // Derive a valid Rust identifier from the snapshot filename. + let ident = test + .strip_suffix(".rpcsnap.json.zst") + .unwrap_or(test) + .chars() + .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) + .collect::(); + + writeln!( + w, + r#" + #[tokio::test(flavor = "multi_thread")] + async fn rpc_test_{ident}() {{ + rpc_regression_test_run("{test}").await + }} + "#, + ) + .unwrap(); + } +} diff --git a/docs/docs/developers/guides/rpc_test_snapshot.md b/docs/docs/developers/guides/rpc_test_snapshot.md index c79c9e81ebd6..25ce0ac7f28c 100644 --- a/docs/docs/developers/guides/rpc_test_snapshot.md +++ b/docs/docs/developers/guides/rpc_test_snapshot.md @@ -35,11 +35,11 @@ A test snapshot generated in the previous step is in JSON format, for easier ins - Manual Method 1. Compress the test snapshots if not already done. - 2. Upload the `.json.zst` files to the DigitalOcean space `forest-snapshots/rpc_test` + 2. Upload the `{filename}.rpcsnap.json.zst` files to the DigitalOcean space `forest-snapshots/rpc_test` 3. Include the file names in `src/tool/subcommands/api_cmd/test_snapshots.txt` - 4. Run the tests: + 4. Run the test: ``` - cargo test --lib -- --test rpc_regression_tests --nocapture + cargo test --lib {filename} -- --nocapture ``` - Using the Script diff --git a/src/tool/subcommands/api_cmd/test_snapshot.rs b/src/tool/subcommands/api_cmd/test_snapshot.rs index 83a59ed437e8..6d01e3b1349a 100644 --- a/src/tool/subcommands/api_cmd/test_snapshot.rs +++ b/src/tool/subcommands/api_cmd/test_snapshot.rs @@ -184,68 +184,55 @@ mod tests { use crate::utils::net::{DownloadFileOption, download_file_with_cache}; use crate::utils::proofs_api::ensure_proof_params_downloaded; use ahash::HashSet; + use anyhow::Context as _; use directories::ProjectDirs; - use futures::{StreamExt, stream::FuturesUnordered}; - use itertools::Itertools as _; + use std::sync::LazyLock; use std::time::Instant; - use tokio::sync::Semaphore; + use tokio::sync::Mutex; use url::Url; - #[tokio::test(flavor = "multi_thread")] - async fn rpc_regression_tests() { + // To run a single test: cargo test --lib filecoin_multisig_statedecodeparams_1754230255631789 -- --nocapture + include!(concat!(env!("OUT_DIR"), "/__rpc_regression_tests_gen.rs")); + + async fn rpc_regression_test_run(name: &str) { // Skip for debug build on CI as the downloading is slow and flaky if crate::utils::is_ci() && crate::utils::is_debug_build() { return; } + // Set proof parameter data dir and make sure the proofs are available - crate::utils::proofs_api::maybe_set_proofs_parameter_cache_dir_env( - &Config::default().client.data_dir, - ); - ensure_proof_params_downloaded().await.unwrap(); - let urls = include_str!("test_snapshots.txt") - .trim() - .split("\n") - .filter_map(|n| { - Url::parse( - format!( - "https://forest-snapshots.fra1.cdn.digitaloceanspaces.com/rpc_test/{n}" - ) - .as_str(), - ) - .ok() - .map(|url| (n, url)) - }) - .collect_vec(); + { + static PROOF_PARAMS_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); + let _guard = PROOF_PARAMS_LOCK.lock().await; + crate::utils::proofs_api::maybe_set_proofs_parameter_cache_dir_env( + &Config::default().client.data_dir, + ); + ensure_proof_params_downloaded().await.unwrap(); + } + let url: Url = + format!("https://forest-snapshots.fra1.cdn.digitaloceanspaces.com/rpc_test/{name}") + .parse() + .with_context(|| format!("Failed to parse URL for test: {name}")) + .unwrap(); let project_dir = ProjectDirs::from("com", "ChainSafe", "Forest").unwrap(); let cache_dir = project_dir.cache_dir().join("test").join("rpc-snapshots"); - let semaphore = Arc::new(Semaphore::new(4)); - let mut tasks = FuturesUnordered::from_iter(urls.into_iter().map(|(filename, url)| { - let cache_dir = cache_dir.clone(); - let semaphore = semaphore.clone(); - async move { - let _permit = semaphore.acquire().await.unwrap(); - let result = - download_file_with_cache(&url, &cache_dir, DownloadFileOption::NonResumable) - .await - .unwrap(); - (filename, result.path) - } - })); + let path = download_file_with_cache(&url, &cache_dir, DownloadFileOption::NonResumable) + .await + .unwrap() + .path; // We need to set RNG seed so that tests are run with deterministic // output. The snapshots should be generated with a node running with the same seed, if // they are testing methods that are not deterministic, e.g., // `[`crate::rpc::methods::gas::estimate_gas_premium`]`. unsafe { std::env::set_var(crate::utils::rand::FIXED_RNG_SEED_ENV, "4213666") }; - while let Some((filename, file_path)) = tasks.next().await { - print!("Testing {filename} ..."); - let start = Instant::now(); - run_test_from_snapshot(&file_path).await.unwrap(); - println!( - " succeeded, took {}.", - humantime::format_duration(start.elapsed()) - ); - } + print!("Testing {name} ..."); + let start = Instant::now(); + run_test_from_snapshot(&path).await.unwrap(); + println!( + " succeeded, took {}.", + humantime::format_duration(start.elapsed()) + ); } #[test]