Browse Source

Merge #511

511: Test cleanup r=klochowicz a=klochowicz

A small refactor, hopefully reducing entry barrier in creating new tests.

Co-authored-by: Mariusz Klochowicz <mariusz@klochowicz.com>
shutdown-taker-if-no-maker-present
bors[bot] 3 years ago
committed by GitHub
parent
commit
84773d02ab
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 182
      daemon/tests/happy_path.rs
  2. 69
      daemon/tests/harness/flow.rs
  3. 34
      daemon/tests/harness/mocks/mod.rs
  4. 55
      daemon/tests/harness/mod.rs

182
daemon/tests/happy_path.rs

@ -1,21 +1,9 @@
use crate::harness::bdk::dummy_partially_signed_transaction;
use crate::harness::mocks::oracle::dummy_announcement;
use crate::harness::mocks::wallet::build_party_params;
use crate::harness::start_both;
use anyhow::Context;
use daemon::maker_cfd;
use daemon::model::cfd::{Cfd, CfdState, Order, Origin};
use daemon::model::{Price, Usd};
use daemon::tokio_ext::FutureExt;
use harness::bdk::dummy_tx_id;
use crate::harness::flow::{is_next_none, next_cfd, next_order, next_some};
use crate::harness::{assert_is_same_order, dummy_new_order, init_tracing, start_both};
use daemon::model::cfd::CfdState;
use daemon::model::Usd;
use maia::secp256k1_zkp::schnorrsig;
use rust_decimal_macros::dec;
use std::time::Duration;
use tokio::sync::{watch, MutexGuard};
use tracing::subscriber::DefaultGuard;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
mod harness;
#[tokio::test]
@ -70,22 +58,6 @@ async fn taker_takes_order_and_maker_rejects() {
assert!(matches!(taker_cfd.state, CfdState::Rejected { .. }));
assert!(matches!(maker_cfd.state, CfdState::Rejected { .. }));
}
// Helper function setting up a "happy path" wallet mock
fn mock_wallet_sign_and_broadcast(wallet: &mut MutexGuard<'_, harness::mocks::wallet::MockWallet>) {
let mut seq = mockall::Sequence::new();
wallet
.expect_sign()
.times(1)
.returning(|_| Ok(dummy_partially_signed_transaction()))
.in_sequence(&mut seq);
wallet
.expect_broadcast()
.times(1)
.returning(|_| Ok(dummy_tx_id()))
.in_sequence(&mut seq);
}
#[tokio::test]
#[cfg_attr(not(feature = "expensive_tests"), ignore)]
async fn taker_takes_order_and_maker_accepts_and_contract_setup() {
@ -101,39 +73,13 @@ async fn taker_takes_order_and_maker_accepts_and_contract_setup() {
taker.take_order(received.clone(), Usd::new(dec!(5)));
let (_, _) = next_cfd(&mut taker.cfd_feed, &mut maker.cfd_feed).await;
maker
.mocks
.oracle()
.await
.expect_get_announcement()
.returning(|_| Some(dummy_announcement()));
taker
.mocks
.oracle()
.await
.expect_get_announcement()
.returning(|_| Some(dummy_announcement()));
maker.mocks.mock_oracle_annoucement().await;
taker.mocks.mock_oracle_annoucement().await;
maker.accept_take_request(received.clone());
#[allow(clippy::redundant_closure)] // clippy is in the wrong here
maker
.mocks
.wallet()
.await
.expect_build_party_params()
.times(1)
.returning(|msg| build_party_params(msg));
#[allow(clippy::redundant_closure)] // clippy is in the wrong here
taker
.mocks
.wallet()
.await
.expect_build_party_params()
.times(1)
.returning(|msg| build_party_params(msg));
maker.mocks.mock_party_params().await;
taker.mocks.mock_party_params().await;
let (taker_cfd, maker_cfd) = next_cfd(&mut taker.cfd_feed, &mut maker.cfd_feed).await;
// TODO: More elaborate Cfd assertions
@ -142,8 +88,8 @@ async fn taker_takes_order_and_maker_accepts_and_contract_setup() {
assert!(matches!(taker_cfd.state, CfdState::ContractSetup { .. }));
assert!(matches!(maker_cfd.state, CfdState::ContractSetup { .. }));
mock_wallet_sign_and_broadcast(&mut maker.mocks.wallet().await);
mock_wallet_sign_and_broadcast(&mut taker.mocks.wallet().await);
maker.mocks.mock_wallet_sign_and_broadcast().await;
taker.mocks.mock_wallet_sign_and_broadcast().await;
let (taker_cfd, maker_cfd) = next_cfd(&mut taker.cfd_feed, &mut maker.cfd_feed).await;
// TODO: More elaborate Cfd assertions
@ -152,111 +98,3 @@ async fn taker_takes_order_and_maker_accepts_and_contract_setup() {
assert!(matches!(taker_cfd.state, CfdState::PendingOpen { .. }));
assert!(matches!(maker_cfd.state, CfdState::PendingOpen { .. }));
}
/// The order cannot be directly compared in tests as the origin is different,
/// therefore wrap the assertion macro in a code that unifies the 'Origin'
fn assert_is_same_order(a: &Order, b: &Order) {
// Assume the same origin
let mut a = a.clone();
let mut b = b.clone();
a.origin = Origin::Ours;
b.origin = Origin::Ours;
assert_eq!(a, b);
}
fn dummy_new_order() -> maker_cfd::NewOrder {
maker_cfd::NewOrder {
price: Price::new(dec!(50_000)).expect("unexpected failure"),
min_quantity: Usd::new(dec!(5)),
max_quantity: Usd::new(dec!(100)),
}
}
/// Returns the first `Cfd` from both channels
///
/// Ensures that there is only one `Cfd` present in both channels.
async fn next_cfd(
rx_a: &mut watch::Receiver<Vec<Cfd>>,
rx_b: &mut watch::Receiver<Vec<Cfd>>,
) -> (Cfd, Cfd) {
let (a, b) = tokio::join!(next(rx_a), next(rx_b));
assert_eq!(a.len(), 1);
assert_eq!(b.len(), 1);
(a.first().unwrap().clone(), b.first().unwrap().clone())
}
async fn next_order(
rx_a: &mut watch::Receiver<Option<Order>>,
rx_b: &mut watch::Receiver<Option<Order>>,
) -> (Order, Order) {
let (a, b) = tokio::join!(next_some(rx_a), next_some(rx_b));
(a, b)
}
/// Returns the value if the next Option received on the stream is Some
///
/// Panics if None is received on the stream.
async fn next_some<T>(rx: &mut watch::Receiver<Option<T>>) -> T
where
T: Clone,
{
if let Some(value) = next(rx).await {
value
} else {
panic!("Received None when Some was expected")
}
}
/// Returns true if the next Option received on the stream is None
///
/// Returns false if Some is received.
async fn is_next_none<T>(rx: &mut watch::Receiver<Option<T>>) -> bool
where
T: Clone,
{
next(rx).await.is_none()
}
/// Returns watch channel value upon change
async fn next<T>(rx: &mut watch::Receiver<T>) -> T
where
T: Clone,
{
// TODO: Make timeout configurable, only contract setup can take up to 2 min on CI
rx.changed()
.timeout(Duration::from_secs(120))
.await
.context("Waiting for next element in channel is taking too long, aborting")
.unwrap()
.unwrap();
rx.borrow().clone()
}
fn init_tracing() -> DefaultGuard {
let filter = EnvFilter::from_default_env()
// apply warning level globally
.add_directive(format!("{}", LevelFilter::WARN).parse().unwrap())
// log traces from test itself
.add_directive(
format!("happy_path={}", LevelFilter::DEBUG)
.parse()
.unwrap(),
)
.add_directive(format!("taker={}", LevelFilter::DEBUG).parse().unwrap())
.add_directive(format!("maker={}", LevelFilter::DEBUG).parse().unwrap())
.add_directive(format!("daemon={}", LevelFilter::DEBUG).parse().unwrap())
.add_directive(format!("rocket={}", LevelFilter::WARN).parse().unwrap());
let guard = tracing_subscriber::fmt()
.with_env_filter(filter)
.with_test_writer()
.set_default();
tracing::info!("Running version: {}", env!("VERGEN_GIT_SEMVER_LIGHTWEIGHT"));
guard
}

69
daemon/tests/harness/flow.rs

@ -0,0 +1,69 @@
use std::time::Duration;
use anyhow::Context;
use daemon::model::cfd::{Cfd, Order};
use daemon::tokio_ext::FutureExt;
use tokio::sync::watch;
/// Returns the first `Cfd` from both channels
///
/// Ensures that there is only one `Cfd` present in both channels.
pub async fn next_cfd(
rx_a: &mut watch::Receiver<Vec<Cfd>>,
rx_b: &mut watch::Receiver<Vec<Cfd>>,
) -> (Cfd, Cfd) {
let (a, b) = tokio::join!(next(rx_a), next(rx_b));
assert_eq!(a.len(), 1);
assert_eq!(b.len(), 1);
(a.first().unwrap().clone(), b.first().unwrap().clone())
}
pub async fn next_order(
rx_a: &mut watch::Receiver<Option<Order>>,
rx_b: &mut watch::Receiver<Option<Order>>,
) -> (Order, Order) {
let (a, b) = tokio::join!(next_some(rx_a), next_some(rx_b));
(a, b)
}
/// Returns the value if the next Option received on the stream is Some
///
/// Panics if None is received on the stream.
pub async fn next_some<T>(rx: &mut watch::Receiver<Option<T>>) -> T
where
T: Clone,
{
if let Some(value) = next(rx).await {
value
} else {
panic!("Received None when Some was expected")
}
}
/// Returns true if the next Option received on the stream is None
///
/// Returns false if Some is received.
pub async fn is_next_none<T>(rx: &mut watch::Receiver<Option<T>>) -> bool
where
T: Clone,
{
next(rx).await.is_none()
}
/// Returns watch channel value upon change
pub async fn next<T>(rx: &mut watch::Receiver<T>) -> T
where
T: Clone,
{
// TODO: Make timeout configurable, only contract setup can take up to 2 min on CI
rx.changed()
.timeout(Duration::from_secs(120))
.await
.context("Waiting for next element in channel is taking too long, aborting")
.unwrap()
.unwrap();
rx.borrow().clone()
}

34
daemon/tests/harness/mocks/mod.rs

@ -6,6 +6,8 @@ use self::monitor::MonitorActor;
use self::oracle::OracleActor;
use self::wallet::WalletActor;
use super::bdk::{dummy_partially_signed_transaction, dummy_tx_id};
pub mod monitor;
pub mod oracle;
pub mod wallet;
@ -30,6 +32,38 @@ impl Mocks {
pub async fn oracle(&mut self) -> MutexGuard<'_, oracle::MockOracle> {
self.oracle.lock().await
}
// Helper function setting up a "happy path" wallet mock
pub async fn mock_wallet_sign_and_broadcast(&mut self) {
let mut seq = mockall::Sequence::new();
self.wallet()
.await
.expect_sign()
.times(1)
.returning(|_| Ok(dummy_partially_signed_transaction()))
.in_sequence(&mut seq);
self.wallet()
.await
.expect_broadcast()
.times(1)
.returning(|_| Ok(dummy_tx_id()))
.in_sequence(&mut seq);
}
pub async fn mock_oracle_annoucement(&mut self) {
self.oracle()
.await
.expect_get_announcement()
.return_const(Some(oracle::dummy_announcement()));
}
pub async fn mock_party_params(&mut self) {
#[allow(clippy::redundant_closure)] // clippy is in the wrong here
self.wallet()
.await
.expect_build_party_params()
.returning(|msg| wallet::build_party_params(msg));
}
}
impl Default for Mocks {

55
daemon/tests/harness/mod.rs

@ -3,19 +3,25 @@ use crate::harness::mocks::oracle::OracleActor;
use crate::harness::mocks::wallet::WalletActor;
use crate::schnorrsig;
use daemon::maker_cfd::CfdAction;
use daemon::model::cfd::{Cfd, Order};
use daemon::model::Usd;
use daemon::model::cfd::{Cfd, Order, Origin};
use daemon::model::{Price, Usd};
use daemon::seed::Seed;
use daemon::{connection, db, maker_cfd, maker_inc_connections, taker_cfd};
use rust_decimal_macros::dec;
use sqlx::SqlitePool;
use std::net::SocketAddr;
use std::str::FromStr;
use std::task::Poll;
use tokio::sync::watch;
use tracing::subscriber::DefaultGuard;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use xtra::spawn::TokioGlobalSpawnExt;
use xtra::Actor;
pub mod bdk;
pub mod flow;
pub mod maia;
pub mod mocks;
@ -207,3 +213,48 @@ async fn in_memory_db() -> SqlitePool {
pool
}
/// The order cannot be directly compared in tests as the origin is different,
/// therefore wrap the assertion macro in a code that unifies the 'Origin'
pub fn assert_is_same_order(a: &Order, b: &Order) {
// Assume the same origin
let mut a = a.clone();
let mut b = b.clone();
a.origin = Origin::Ours;
b.origin = Origin::Ours;
assert_eq!(a, b);
}
pub fn dummy_new_order() -> maker_cfd::NewOrder {
maker_cfd::NewOrder {
price: Price::new(dec!(50_000)).expect("unexpected failure"),
min_quantity: Usd::new(dec!(5)),
max_quantity: Usd::new(dec!(100)),
}
}
pub fn init_tracing() -> DefaultGuard {
let filter = EnvFilter::from_default_env()
// apply warning level globally
.add_directive(format!("{}", LevelFilter::WARN).parse().unwrap())
// log traces from test itself
.add_directive(
format!("happy_path={}", LevelFilter::DEBUG)
.parse()
.unwrap(),
)
.add_directive(format!("taker={}", LevelFilter::DEBUG).parse().unwrap())
.add_directive(format!("maker={}", LevelFilter::DEBUG).parse().unwrap())
.add_directive(format!("daemon={}", LevelFilter::DEBUG).parse().unwrap())
.add_directive(format!("rocket={}", LevelFilter::WARN).parse().unwrap());
let guard = tracing_subscriber::fmt()
.with_env_filter(filter)
.with_test_writer()
.set_default();
tracing::info!("Running version: {}", env!("VERGEN_GIT_SEMVER_LIGHTWEIGHT"));
guard
}

Loading…
Cancel
Save