From: Jeffrey Czyz Date: Sun, 21 Mar 2021 03:54:21 +0000 (-0400) Subject: Mock-like expectations for TestChainSource X-Git-Tag: v0.0.14~42^2~1 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=80e81fbea3a53fe88f056fc1761872e3eac5d13e;p=rust-lightning Mock-like expectations for TestChainSource Add a method to TestChainSource to test chain::Filter expectations. This is limited to register_output, allowing tests to assert that the method was called with a specific output and dictate what the return value is. Multiple expectations are checked in the order in which they were added. Failure occurs if a call doesn't match the next expectation or if there are unsatisfied expectations. If not expectations are added, then no calls are checked. --- diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 2eabfd5cb..2d3f930a0 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -39,7 +39,7 @@ use std::time::Duration; use std::sync::{Mutex, Arc}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::{cmp, mem}; -use std::collections::{HashMap, HashSet}; +use std::collections::{HashMap, HashSet, VecDeque}; use chain::keysinterface::InMemorySigner; pub struct TestVecWriter(pub Vec); @@ -518,6 +518,7 @@ pub struct TestChainSource { pub utxo_ret: Mutex>, pub watched_txn: Mutex>, pub watched_outputs: Mutex>, + expectations: Mutex>>, } impl TestChainSource { @@ -528,8 +529,17 @@ impl TestChainSource { utxo_ret: Mutex::new(Ok(TxOut { value: u64::max_value(), script_pubkey })), watched_txn: Mutex::new(HashSet::new()), watched_outputs: Mutex::new(HashSet::new()), + expectations: Mutex::new(None), } } + + /// Sets an expectation that [`chain::Filter::register_output`] is called. + pub fn expect(&self, expectation: OnRegisterOutput) -> &Self { + self.expectations.lock().unwrap() + .get_or_insert_with(|| VecDeque::new()) + .push_back(expectation); + self + } } impl chain::Access for TestChainSource { @@ -548,7 +558,71 @@ impl chain::Filter for TestChainSource { } fn register_output(&self, output: WatchedOutput) -> Option<(usize, Transaction)> { + let dependent_tx = match &mut *self.expectations.lock().unwrap() { + None => None, + Some(expectations) => match expectations.pop_front() { + None => { + panic!("Unexpected register_output: {:?}", + (output.outpoint, output.script_pubkey)); + }, + Some(expectation) => { + assert_eq!(output.outpoint, expectation.outpoint()); + assert_eq!(&output.script_pubkey, expectation.script_pubkey()); + expectation.returns + }, + }, + }; + self.watched_outputs.lock().unwrap().insert((output.outpoint, output.script_pubkey)); - None + dependent_tx + } +} + +impl Drop for TestChainSource { + fn drop(&mut self) { + if std::thread::panicking() { + return; + } + + if let Some(expectations) = &*self.expectations.lock().unwrap() { + if !expectations.is_empty() { + panic!("Unsatisfied expectations: {:?}", expectations); + } + } + } +} + +/// An expectation that [`chain::Filter::register_output`] was called with a transaction output and +/// returns an optional dependent transaction that spends the output in the same block. +pub struct OnRegisterOutput { + /// The transaction output to register. + pub with: TxOutReference, + + /// A dependent transaction spending the output along with its position in the block. + pub returns: Option<(usize, Transaction)>, +} + +/// A transaction output as identified by an index into a transaction's output list. +pub struct TxOutReference(pub Transaction, pub usize); + +impl OnRegisterOutput { + fn outpoint(&self) -> OutPoint { + let txid = self.with.0.txid(); + let index = self.with.1 as u16; + OutPoint { txid, index } + } + + fn script_pubkey(&self) -> &Script { + let index = self.with.1; + &self.with.0.output[index].script_pubkey + } +} + +impl std::fmt::Debug for OnRegisterOutput { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("OnRegisterOutput") + .field("outpoint", &self.outpoint()) + .field("script_pubkey", self.script_pubkey()) + .finish() } }