From d5c4811e665aa6b4f03b50c8c1c4f9955c93e505 Mon Sep 17 00:00:00 2001 From: zancas Date: Wed, 12 Nov 2025 21:51:01 -0800 Subject: [PATCH 1/7] start helperizing claude test code into something more sane --- Cargo.lock | 81 +++++++++++++++++++++++++++++++++++++-- zainod/Cargo.toml | 6 +++ zainod/tests/lifecycle.rs | 51 ++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 zainod/tests/lifecycle.rs diff --git a/Cargo.lock b/Cargo.lock index 0c696356f..cd204062d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -302,6 +302,21 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "assert_cmd" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcbb6924530aa9e0432442af08bbcafdad182db80d2e560da42a6d442535bf85" +dependencies = [ + "anstyle", + "bstr", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "async-compression" version = "0.4.30" @@ -598,15 +613,12 @@ dependencies = [ "itertools 0.12.1", "lazy_static", "lazycell", - "log", - "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", "shlex", "syn 2.0.106", - "which 4.4.2", ] [[package]] @@ -1646,6 +1658,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.10.7" @@ -2071,6 +2089,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits 0.2.19", +] + [[package]] name = "fluid-let" version = "1.0.0" @@ -3612,6 +3639,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549e471b99ccaf2f89101bec68f4d244457d5a95a9c3d0672e9564124397741d" +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "notify" version = "8.2.0" @@ -4299,6 +4332,36 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -6067,6 +6130,12 @@ dependencies = [ "windows-sys 0.61.0", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "thiserror" version = "1.0.69" @@ -8541,10 +8610,14 @@ dependencies = [ name = "zainod" version = "0.1.2" dependencies = [ + "assert_cmd", "clap", "figment", "http", + "portpicker", + "predicates", "serde", + "tempfile", "thiserror 1.0.69", "tokio", "toml 0.5.11", @@ -9086,7 +9159,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf6e76f310bb2d3cc233086a97c1710ba1de7ffbbf8198b8113407d0f427dfc" dependencies = [ - "bindgen 0.69.5", + "bindgen 0.72.1", "bitflags 2.9.4", "cc", "enum_primitive", diff --git a/zainod/Cargo.toml b/zainod/Cargo.toml index 1584cfd7a..bb40fc3a2 100644 --- a/zainod/Cargo.toml +++ b/zainod/Cargo.toml @@ -50,3 +50,9 @@ thiserror = { workspace = true } # Formats toml = { workspace = true } figment= { workspace = true, features = ["toml", "env", "test"] } + +[dev-dependencies] +assert_cmd = "2.0" +predicates = "3.0" +tempfile = { workspace = true } +portpicker = { workspace = true } diff --git a/zainod/tests/lifecycle.rs b/zainod/tests/lifecycle.rs new file mode 100644 index 000000000..d5b9fd210 --- /dev/null +++ b/zainod/tests/lifecycle.rs @@ -0,0 +1,51 @@ +//! claude inspired Integration test for the zainod binary +//! +//! These tests launch the actual zainod binary as a subprocess and test its +//! behavior as a daemon, including startup, TCP connections, and shutdown. +//! +//! Run with: cargo nextest run --package zainod --test lifecycle + +use std::{net::SocketAddr, process::Child}; +use tempfile::TempDir; + +/// A *private* test type to facilitate test process management +/// +/// It automatically kills the daemon when dropped to ensure cleanup. +struct ZainodTestContainer { + /// service addresses + addresses: ZainodServiceAddresses, + /// child process handle + process: Child, + /// Temporary directory for config and data + _temp_dir: TempDir, +} + +/// Ports that zainod may have +struct ZainodServiceAddresses { + /// grpc_server listener for incoming client connections to the zainod grpc server + grpc_address: SocketAddr, + /// lightwallet client supporting RPC-server JSON-RPC listen address + json_rpc_address: SocketAddr, +} + +impl ZainodServiceAddresses { + fn generate_addresses() -> Self { + let grpc_port = portpicker::pick_unused_port().expect("No ports for grpc"); + let json_rpc_port = portpicker::pick_unused_port().expect("No ports for json_rpc"); + ZainodServiceAddresses { + grpc_address: format!("127.0.0.1:{}", grpc_port) + .parse::() + .unwrap(), + json_rpc_address: format!("127.0.0.1:{}", json_rpc_port) + .parse::() + .unwrap(), + } + } +} +impl ZainodTestContainer { + async fn spawn() { + todo!() + } +} +#[test] +fn an_int_test() {} From 85f73183bf9936cbc627beaee69ceb0597816e2a Mon Sep 17 00:00:00 2001 From: zancas Date: Thu, 13 Nov 2025 19:35:44 -0800 Subject: [PATCH 2/7] make the test a tokio test, manage paths with helper --- zainod/tests/lifecycle.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/zainod/tests/lifecycle.rs b/zainod/tests/lifecycle.rs index d5b9fd210..82204a812 100644 --- a/zainod/tests/lifecycle.rs +++ b/zainod/tests/lifecycle.rs @@ -5,7 +5,7 @@ //! //! Run with: cargo nextest run --package zainod --test lifecycle -use std::{net::SocketAddr, process::Child}; +use std::{net::SocketAddr, path::PathBuf, process::Child}; use tempfile::TempDir; /// A *private* test type to facilitate test process management @@ -28,6 +28,21 @@ struct ZainodServiceAddresses { json_rpc_address: SocketAddr, } +struct TestPaths { + config: PathBuf, + db: PathBuf, + zebra_db: PathBuf, +} +impl TestPaths { + fn generate_paths() -> Self { + let temp_dir = tempfile::tempdir().expect("to create a tempdir"); + Self { + config: temp_dir.path().join("test_config.toml"), + db: temp_dir.path().join("zaino_db"), + zebra_db: temp_dir.path().join("zebra_db"), + } + } +} impl ZainodServiceAddresses { fn generate_addresses() -> Self { let grpc_port = portpicker::pick_unused_port().expect("No ports for grpc"); @@ -44,8 +59,11 @@ impl ZainodServiceAddresses { } impl ZainodTestContainer { async fn spawn() { - todo!() + let addresses = ZainodServiceAddresses::generate_addresses(); + let test_paths = TestPaths::generate_paths(); } } -#[test] -fn an_int_test() {} +#[tokio::test] +async fn an_int_test() { + ZainodTestContainer::spawn().await; +} From 52409067fd8cee85849a30063690a4336a880a26 Mon Sep 17 00:00:00 2001 From: zancas Date: Thu, 13 Nov 2025 20:49:06 -0800 Subject: [PATCH 3/7] manage Zebrad addresses with the same ServiceAddresses struct --- zainod/tests/lifecycle.rs | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/zainod/tests/lifecycle.rs b/zainod/tests/lifecycle.rs index 82204a812..924db3b09 100644 --- a/zainod/tests/lifecycle.rs +++ b/zainod/tests/lifecycle.rs @@ -13,15 +13,15 @@ use tempfile::TempDir; /// It automatically kills the daemon when dropped to ensure cleanup. struct ZainodTestContainer { /// service addresses - addresses: ZainodServiceAddresses, + addresses: ServiceAddresses, /// child process handle process: Child, /// Temporary directory for config and data _temp_dir: TempDir, } -/// Ports that zainod may have -struct ZainodServiceAddresses { +/// SocketAddrs that zainod may have +struct ServiceAddresses { /// grpc_server listener for incoming client connections to the zainod grpc server grpc_address: SocketAddr, /// lightwallet client supporting RPC-server JSON-RPC listen address @@ -43,11 +43,23 @@ impl TestPaths { } } } -impl ZainodServiceAddresses { - fn generate_addresses() -> Self { - let grpc_port = portpicker::pick_unused_port().expect("No ports for grpc"); - let json_rpc_port = portpicker::pick_unused_port().expect("No ports for json_rpc"); - ZainodServiceAddresses { +fn pick_non_reserved_port(reserved: &Vec) -> u16 { + loop { + let port = portpicker::pick_unused_port().expect("to pick one!"); + + if !reserved.contains(&port) { + return port; + } + } +} +impl ServiceAddresses { + fn generate_random_unused_addresses() -> Self { + // disallow zebra ports from being picked + let mut reserved = vec![18230, 18232]; + let grpc_port = pick_non_reserved_port(&reserved); + reserved.extend([grpc_port]); + let json_rpc_port = pick_non_reserved_port(&reserved); + ServiceAddresses { grpc_address: format!("127.0.0.1:{}", grpc_port) .parse::() .unwrap(), @@ -56,10 +68,16 @@ impl ZainodServiceAddresses { .unwrap(), } } + fn zebrad_default_addresses() -> Self { + ServiceAddresses { + grpc_address: "127.0.0.1:18232".parse::().unwrap(), + json_rpc_address: "127.0.0.1:18230".parse::().unwrap(), + } + } } impl ZainodTestContainer { async fn spawn() { - let addresses = ZainodServiceAddresses::generate_addresses(); + let zainod_addresses = ServiceAddresses::generate_random_unused_addresses(); let test_paths = TestPaths::generate_paths(); } } From 0b340bc00b313c051e58ebbcb51f293b47538163 Mon Sep 17 00:00:00 2001 From: zancas Date: Thu, 13 Nov 2025 21:29:44 -0800 Subject: [PATCH 4/7] begin defining config writing test function --- zainod/tests/lifecycle.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/zainod/tests/lifecycle.rs b/zainod/tests/lifecycle.rs index 924db3b09..df690e7fc 100644 --- a/zainod/tests/lifecycle.rs +++ b/zainod/tests/lifecycle.rs @@ -55,6 +55,7 @@ fn pick_non_reserved_port(reserved: &Vec) -> u16 { impl ServiceAddresses { fn generate_random_unused_addresses() -> Self { // disallow zebra ports from being picked + // I'm assuming zebra spawns first let mut reserved = vec![18230, 18232]; let grpc_port = pick_non_reserved_port(&reserved); reserved.extend([grpc_port]); @@ -75,8 +76,39 @@ impl ServiceAddresses { } } } +fn create_test_config( + zainod_grpc: SocketAddr, + zainod_rpc: Option, + validator_rpc_addr: SocketAddr, + validator_grpc_address: SocketAddr, + db_path: PathBuf, + zebra_db_path: PathBuf, +) -> String { + let json_server_selection = if let Some(addr) = zainod_rpc { + format!( + r#" +[json_server_settings] +json_rpc_listen_address = "{}" + "#, + addr + ) + } else { + String::new() + }; + format! { + r#" +backend = "fetch" +{json_server_selection} +[grpc_settings] +grpc_listen_address = "{zainod_grpc}" +path = "{db_path}" +zebra_db_path = "{zebra_db_path}" +network = "Regtest" +"#, db_path = db_path.display(), zebra_db_path = zebra_db_path.display()} +} impl ZainodTestContainer { async fn spawn() { + let zebrad_addresses = ServiceAddresses::zebrad_default_addresses(); let zainod_addresses = ServiceAddresses::generate_random_unused_addresses(); let test_paths = TestPaths::generate_paths(); } From e1259ea8dde2826975dddd757a80a0303e4c8ce0 Mon Sep 17 00:00:00 2001 From: zancas Date: Fri, 14 Nov 2025 17:00:43 -0800 Subject: [PATCH 5/7] minor format tweaks --- zainod/tests/lifecycle.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zainod/tests/lifecycle.rs b/zainod/tests/lifecycle.rs index df690e7fc..906957634 100644 --- a/zainod/tests/lifecycle.rs +++ b/zainod/tests/lifecycle.rs @@ -96,15 +96,18 @@ json_rpc_listen_address = "{}" String::new() }; format! { - r#" + r#" backend = "fetch" {json_server_selection} [grpc_settings] grpc_listen_address = "{zainod_grpc}" +[validator_settings] path = "{db_path}" zebra_db_path = "{zebra_db_path}" network = "Regtest" -"#, db_path = db_path.display(), zebra_db_path = zebra_db_path.display()} +"#, db_path = db_path.display(), + zebra_db_path = zebra_db_path.display() + } } impl ZainodTestContainer { async fn spawn() { From 4e0d95ee15c5a89c99836cf80606581f17ebdbfc Mon Sep 17 00:00:00 2001 From: zancas Date: Fri, 14 Nov 2025 17:06:33 -0800 Subject: [PATCH 6/7] rename addr --> address and finish config boilerplate --- zainod/tests/lifecycle.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/zainod/tests/lifecycle.rs b/zainod/tests/lifecycle.rs index 906957634..dcc357ae7 100644 --- a/zainod/tests/lifecycle.rs +++ b/zainod/tests/lifecycle.rs @@ -79,7 +79,7 @@ impl ServiceAddresses { fn create_test_config( zainod_grpc: SocketAddr, zainod_rpc: Option, - validator_rpc_addr: SocketAddr, + validator_rpc_address: SocketAddr, validator_grpc_address: SocketAddr, db_path: PathBuf, zebra_db_path: PathBuf, @@ -102,6 +102,16 @@ backend = "fetch" [grpc_settings] grpc_listen_address = "{zainod_grpc}" [validator_settings] +validator_jsonrpc_listen_address = "{validator_rpc_address}" +validator_grpc_listen_address = "{validator_grpc_address}" +validator_user = "testuser" +validator_password = "testpass" +[service] +# Use default service settings +[storage] +[storage.cache] +# Use default cache settings +[storage.database] path = "{db_path}" zebra_db_path = "{zebra_db_path}" network = "Regtest" From c2f3888a1a5836761887bf92724627d9ef7f1a8a Mon Sep 17 00:00:00 2001 From: zancas Date: Fri, 14 Nov 2025 17:18:49 -0800 Subject: [PATCH 7/7] pass params to create_test_config --- zainod/tests/lifecycle.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/zainod/tests/lifecycle.rs b/zainod/tests/lifecycle.rs index dcc357ae7..12b08d684 100644 --- a/zainod/tests/lifecycle.rs +++ b/zainod/tests/lifecycle.rs @@ -124,6 +124,15 @@ impl ZainodTestContainer { let zebrad_addresses = ServiceAddresses::zebrad_default_addresses(); let zainod_addresses = ServiceAddresses::generate_random_unused_addresses(); let test_paths = TestPaths::generate_paths(); + let config = create_test_config( + zainod_addresses.grpc_address, + Some(zainod_addresses.json_rpc_address), + zebrad_addresses.json_rpc_address, + zebrad_addresses.grpc_address, + test_paths.db, + test_paths.zebra_db, + ); + eprintln!("{}", config); } } #[tokio::test]