depolymerization

wire gateway for Bitcoin/Ethereum
Log | Files | Refs | Submodules | README | LICENSE

commit af006fa67623989f3604711e33804b8273b047ec
parent a8ef065e84b5628db961eb87fc54822cb242db19
Author: Antoine A <>
Date:   Wed, 17 Nov 2021 16:22:42 +0100

Run instrumentation tests in parralel

Diffstat:
Msrc/bin/test.rs | 258+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
1 file changed, 186 insertions(+), 72 deletions(-)

diff --git a/src/bin/test.rs b/src/bin/test.rs @@ -1,5 +1,10 @@ use core::panic; -use std::{collections::HashSet, panic::AssertUnwindSafe}; +use std::{ + collections::HashSet, + panic::AssertUnwindSafe, + sync::{Arc, Condvar, Mutex}, + thread::JoinHandle, +}; use bitcoincore_rpc::{ bitcoin::{Address, Amount, Txid}, @@ -13,11 +18,7 @@ use depolymerization::{ }; use owo_colors::OwoColorize; -/// Instrumentation test tests -/// -/// Rust tests are run in parallel and are supposed to run in isolation. -/// As we test against an bitcoin JSON-RPC server and ordering of transaction is important -/// we want our tests to run sequentially and share commons rpc connections. +/// Instrumentation tes pub fn main() { let test_amount = Amount::from_sat(420); @@ -105,76 +106,61 @@ pub fn main() { ); } - run_test("OpReturn metadata", || { - // Send metadata - let msg = "J'aime le chocolat".as_bytes(); - let id = client_rpc - .send_op_return(&wire_addr, test_amount, msg) - .unwrap(); - // Check in mempool - assert!( - tx_exist(&client_rpc, &id, 0, Category::Send).unwrap(), - "Not in mempool" - ); - // Check mined - next_block(network, &client_rpc, &client_addr); - assert!( - tx_exist(&wire_rpc, &id, 1, Category::Receive).unwrap(), - "Not mined" - ); - // Check extract - let (_, extracted) = wire_rpc.get_tx_op_return(&id).unwrap(); - assert_eq!(msg, extracted, "Corrupted metadata"); - }); - - run_test("SegWit metadata", || { - // Send metadata - let key = rand_key(); - let id = client_rpc - .send_segwit_key(&wire_addr, test_amount, &key) - .unwrap(); - // Check in mempool - assert!( - tx_exist(&client_rpc, &id, 0, Category::Send).unwrap(), - "Not in mempool" - ); - // Check mined - next_block(network, &client_rpc, &client_addr); - assert!( - tx_exist(&wire_rpc, &id, 1, Category::Receive).unwrap(), - "Not mined" - ); - // Check extract - let (_, extracted) = wire_rpc.get_tx_segwit_key(&id).unwrap(); - assert_eq!(key, extracted, "Corrupted metadata"); - }); -} + let mut runner = TestRunner::new(network, client_rpc, wire_rpc, client_addr, wire_addr); + runner.run_test( + "OpReturn metadata", + move |miner, client_rpc, wire_rpc, client_addr, wire_addr| { + // Send metadata + let msg = "J'aime le chocolat".as_bytes(); + let id = client_rpc + .send_op_return(&wire_addr, test_amount, msg) + .unwrap(); + // Check in mempool + assert!( + tx_exist(&client_rpc, &id, 0, Category::Send).unwrap(), + "Not in mempool" + ); + // Check mined + miner.next_block(network, &client_rpc, &client_addr); + assert!( + tx_exist(&wire_rpc, &id, 1, Category::Receive).unwrap(), + "Not mined" + ); + // Check extract + let (_, extracted) = wire_rpc.get_tx_op_return(&id).unwrap(); + assert_eq!(msg, extracted, "Corrupted metadata"); + }, + ); -fn next_block(network: Network, rpc: &Client, address: &Address) { - match network { - Network::RegTest => { - // Manually mine a block - rpc.generate_to_address(1, address).unwrap(); - } - _ => { - // Wait for the next block - rpc.wait_for_new_block(0).ok(); - } - } -} + runner.run_test( + "SegWit metadata", + move |miner, client_rpc, wire_rpc, client_addr, wire_addr| { + // Send metadata + let key = rand_key(); + let id = client_rpc + .send_segwit_key(&wire_addr, test_amount, &key) + .unwrap(); + // Check in mempool + assert!( + tx_exist(&client_rpc, &id, 0, Category::Send).unwrap(), + "Not in mempool" + ); + // Check mined + miner.next_block(network, &client_rpc, &client_addr); + assert!( + tx_exist(&wire_rpc, &id, 1, Category::Receive).unwrap(), + "Not mined" + ); + // Check extract + let (_, extracted) = wire_rpc.get_tx_segwit_key(&id).unwrap(); + assert_eq!(key, extracted, "Corrupted metadata"); + }, + ); -fn run_test(name: &str, test: impl FnOnce() -> ()) -> bool { - println!("{}", format_args!("{} start", name).cyan()); - let result = std::panic::catch_unwind(AssertUnwindSafe(test)); - if result.is_ok() { - println!("{}", format_args!("{} OK", name).green()) - } else { - dbg!(&result); - println!("{}", format_args!("{} ERR", name).red()) - } - return result.is_ok(); + runner.conclude(); } +/// Check a specific transaction exist in a wallet historic fn tx_exist( rpc: &Client, id: &Txid, @@ -189,3 +175,131 @@ fn tx_exist( }); Ok(found) } + +/// Listen to block allowing multiple test to wait concurrently for new blocks +struct Miner { + height: Mutex<usize>, + cond: Condvar, +} + +impl Miner { + fn new(network: Network) -> Arc<Self> { + let miner = Arc::new(Self { + height: Mutex::new(0), + cond: Condvar::new(), + }); + let clone = miner.clone(); + std::thread::spawn(move || { + let rpc = common_rpc(network).unwrap(); + loop { + rpc.wait_for_new_block(0).ok(); + { + let mut locked = clone.height.lock().unwrap(); + *locked += 1; + } + clone.cond.notify_all(); + } + }); + return miner; + } + + /// Wait for a new block to be mined + fn next_block(&self, network: Network, rpc: &Client, address: &Address) { + match network { + Network::RegTest => { + // Manually mine a block + rpc.generate_to_address(1, address).unwrap(); + } + _ => { + // Wait for the next block + let mut height = self.height.lock().unwrap(); + let prev = *height; + loop { + height = self.cond.wait(height).unwrap(); + if *height > prev { + return; + } + } + } + } + } +} + +/// Rune test in parallel and track success and errors +struct TestRunner { + joins: Vec<JoinHandle<bool>>, + miner: Arc<Miner>, + client_rpc: Arc<Client>, + wire_rpc: Arc<Client>, + client_addr: Arc<Address>, + wire_addr: Arc<Address>, +} + +impl TestRunner { + fn new( + network: Network, + client_rpc: Client, + wire_rpc: Client, + client_addr: Address, + wire_addr: Address, + ) -> Self { + Self { + joins: Vec::new(), + miner: Miner::new(network), + client_rpc: Arc::new(client_rpc), + wire_rpc: Arc::new(wire_rpc), + client_addr: Arc::new(client_addr), + wire_addr: Arc::new(wire_addr), + } + } + + fn run_test( + &mut self, + name: &'static str, + test: impl FnOnce(Arc<Miner>, Arc<Client>, Arc<Client>, Arc<Address>, Arc<Address>) -> () + + Send + + 'static, + ) { + let miner = self.miner.clone(); + let client_rpc = self.client_rpc.clone(); + let wire_rpc = self.wire_rpc.clone(); + let client_addr = self.client_addr.clone(); + let wire_addr = self.wire_addr.clone(); + self.joins.push(std::thread::spawn(move || { + println!("{}", format_args!("{} start", name).cyan()); + + let result = std::panic::catch_unwind(AssertUnwindSafe(move || { + test(miner, client_rpc, wire_rpc, client_addr, wire_addr) + })); + if result.is_ok() { + println!("{}", format_args!("{} OK", name).green()) + } else { + dbg!(&result); + println!("{}", format_args!("{} ERR", name).red()) + } + return result.is_ok(); + })); + } + + /// Wait for tests completion and print results + fn conclude(self) { + let mut nb_ok = 0; + let mut nb_err = 0; + + for join in self.joins { + let result = join.join().unwrap(); + if result { + nb_ok += 1; + } else { + nb_err += 1; + } + } + + println!( + "Result for {} tests: {} ok and {} err", + nb_ok + nb_err, + nb_ok, + nb_err + ); + } +}