diff --git a/Cargo.lock b/Cargo.lock index 4e391df..c972145 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2471,12 +2471,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" -[[package]] -name = "simple-semaphore" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373fc06169acd9b556ee8cbacdf2237dd10e83d725caba1333b967b15628d73b" - [[package]] name = "slab" version = "0.4.12" @@ -2574,7 +2568,6 @@ dependencies = [ "secp", "serde_json", "sha2", - "simple-semaphore", "tempfile", "tokio", ] @@ -3014,7 +3007,6 @@ dependencies = [ "rand 0.9.2", "rusqlite", "secp", - "simple-semaphore", "tempfile", "testenv", "thiserror 2.0.18", diff --git a/Cargo.toml b/Cargo.toml index dbac0ba..7025f25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ rand = "0.9.2" rand_chacha = "0.9.0" rusqlite = { version = "0.31.0", features = ["bundled-sqlcipher"] } secp = "0.6.0" -simple-semaphore = "1.0.0" tempfile = "3.27.0" thiserror = "2.0.18" tokio = "1.50.0" diff --git a/README.md b/README.md index 043083b..08cb647 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ exposes a single initialization function that all binaries and test environments should call once at startup. The API is intentionally minimal: -```rust +``` bmp_tracing::init("info"); ``` @@ -79,6 +79,16 @@ The tests spin up bitcoind, Electrs and other servers as needed, so you may need cargo test ``` +the test are run as single threaded by default, because in the multithreaded case they are flaky. +due to some timing issue not addressed yet. +If you want to speed up the tests use + +```bash +TEST_MULTITHREADED=true cargo test +``` + +If they run through, everything is good, if not start the test as single threaded again. + ## reading the Markdown files Some of the markdown files have LaTeX included, you can best view them using RustRover. diff --git a/bmp_tracing/src/lib.rs b/bmp_tracing/src/lib.rs index 4054a40..d38547a 100644 --- a/bmp_tracing/src/lib.rs +++ b/bmp_tracing/src/lib.rs @@ -2,13 +2,15 @@ use std::error::Error as _; use std::fs::File; use std::io; use std::path::PathBuf; +use std::sync::{Mutex, PoisonError}; +pub use tracing; +pub use tracing_subscriber; use tracing_subscriber::filter::EnvFilter; use tracing_subscriber::layer::SubscriberExt as _; use tracing_subscriber::registry::LookupSpan; use tracing_subscriber::util::SubscriberInitExt as _; use tracing_subscriber::{Layer, fmt}; -pub use {tracing, tracing_subscriber}; #[derive(Debug, Clone)] #[expect(clippy::exhaustive_enums)] @@ -49,8 +51,12 @@ pub fn init(default_level: &str) { init_with_config(default_level, LogConfig::Stdout); } +static TRACE_INIT: Mutex<()> = Mutex::new(()); + /// Initialize tracing with custom output configuration. pub fn init_with_config(default_level: &str, config: LogConfig) { + // ignoring the error from lock with unit type is safe + let _lock = TRACE_INIT.lock().unwrap_or_else(PoisonError::into_inner); if tracing::dispatcher::has_been_set() { return; } diff --git a/test-runner.sh b/test-runner.sh new file mode 100644 index 0000000..cb641fc --- /dev/null +++ b/test-runner.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# this is for continuous test under multithreaded conditions. +export RUST_BACKTRACE=full +export TEST_MULTITHREADED=true + +for i in {32..1}; do + echo "Running tests with $i threads..." + cargo test -- --test-threads=$i + if [ $? -ne 0 ]; then + echo "Tests failed with $i threads. Exiting." + exit 1 + fi +done + +echo "All tests passed for threads 1 to 32." diff --git a/testenv/Cargo.toml b/testenv/Cargo.toml index 2f86843..c3e58b0 100644 --- a/testenv/Cargo.toml +++ b/testenv/Cargo.toml @@ -13,7 +13,6 @@ bmp_tracing = { workspace = true } hex = { workspace = true } rand = { workspace = true } secp = { workspace = true } -simple-semaphore = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true, features = ["net"] } diff --git a/testenv/src/lib.rs b/testenv/src/lib.rs index 2848848..c8012b8 100644 --- a/testenv/src/lib.rs +++ b/testenv/src/lib.rs @@ -1,7 +1,7 @@ //! Bitcoin regtest environment using electrsd with automatic executable downloads use std::net::SocketAddrV4; -use std::sync::{Arc, LazyLock}; +use std::sync::{Mutex, MutexGuard, PoisonError}; use std::time::Duration; use anyhow::{Context as _, Result}; @@ -21,25 +21,23 @@ use hmac::{Hmac, Mac as _}; use rand::{Rng as _, RngCore as _}; use secp::Scalar; use sha2::Sha256; -use simple_semaphore::{Permit, Semaphore}; -use tempfile::{TempDir, tempdir}; +use tempfile::TempDir; use tokio::net::TcpListener; /// Bitcoin regtest environment manager -pub struct TestEnv { +pub struct TestEnv<'a> { bitcoind: Node, electrsd: ElectrsD, timeout: Duration, delay: Duration, bdk_electrum_client: BdkElectrumClient, ctx: Secp256k1, - _permit: Permit, - _tmp_dir: TempDir, explorer_process: Option, container_name: Option, explorer_port: Option, bitcoin_rpc_pwd: String, mempool: Vec, + _guard: Option>, } /// Configuration parameters. @@ -51,6 +49,7 @@ pub struct Config<'a> { pub electrsd: electrsd::Conf<'a>, pub timeout: Duration, pub delay: Duration, + pub runmultithreaded: bool, } impl Default for Config<'_> { @@ -80,12 +79,12 @@ impl Default for Config<'_> { }, timeout: Duration::from_secs(5), delay: Duration::from_millis(200), + runmultithreaded: "true" == std::env::var("TEST_MULTITHREADED").unwrap_or_else(|_| "false".to_string()).trim().to_lowercase() } } } const NETWORK: Network = Network::Regtest; -static SEMAPHORE: LazyLock> = LazyLock::new(|| Semaphore::new(1)); // Type alias for Hmac-Sha256 type HmacSha256 = Hmac; @@ -155,8 +154,9 @@ pub fn validate_rpcauth(rpcauth_line: &str, username: &str, password: &str) -> b // but you can use `subtle` crate if you want constant-time equality. hmac_hex_actual.eq_ignore_ascii_case(hmac_hex_expected) } +static TESTENV_LOCK: Mutex<()> = Mutex::new(()); -impl TestEnv { +impl<'a> TestEnv<'a> { /// Create a new test environment with automatic executable downloads pub fn new() -> Result { Self::new_with_conf(Config::default()) @@ -196,10 +196,12 @@ impl TestEnv { /// create environment with automatic downloads pub fn new_with_conf(config: Config) -> Result { - let permit = SEMAPHORE.acquire(); // have testenvs single threaded because of bitcoind and electrs references. - let tmp_dir = tempdir().expect("failed to create temporary directory"); - std::env::set_current_dir(tmp_dir.path()).expect("failed to set current directory"); - + let _guard = if config.runmultithreaded { + None + } else { + // can recover because unit type won't corrupt + Some(TESTENV_LOCK.lock().unwrap_or_else(PoisonError::into_inner)) + }; // Try to start bitcoind (from environment or downloads) tracing::info!("Starting bitcoind..."); // rpcauth for each bitcoind and save the pwd @@ -248,7 +250,6 @@ impl TestEnv { )?; let bdk_electrum_client = BdkElectrumClient::new(client); - // permit will be dropped when TestEnv is dropped let test_env = Self { bitcoind, electrsd, @@ -256,13 +257,12 @@ impl TestEnv { delay: config.delay, bdk_electrum_client, ctx: Secp256k1::new(), - _permit: permit, - _tmp_dir: tmp_dir, explorer_process: None, container_name: None, explorer_port: None, bitcoin_rpc_pwd, mempool: Vec::new(), + _guard, }; tracing::info!("Bitcoin regtest environment ready!"); Ok(test_env) @@ -538,7 +538,7 @@ impl TestEnv { } } -impl Drop for TestEnv { +impl<'a> Drop for TestEnv<'a> { fn drop(&mut self) { if let Some(name) = self.container_name.take() { tracing::info!("Stopping explorer container {name}..."); diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index 22d9f39..1fe54e5 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -23,5 +23,4 @@ testenv = { path = "../testenv" } [dev-dependencies] bdk_wallet = { workspace = true, features = ["test-utils"] } bmp_tracing = { workspace = true } -simple-semaphore = { workspace = true } tempfile = { workspace = true }