Create a shared Tokio runtime for tests (#2397)
* Add a `zebra_test::RUNTIME` shared runtime Create a lazily instantiated Tokio runtime that can be shared by tests. * Split tests that require a shared runtime Split two tests that were previously in one because of the need to share a single Tokio runtime. With the `zebra_test::RUNTIME`, they can now share the runtime without having to be a single test.
This commit is contained in:
parent
3dbb4098e5
commit
f33923f12f
|
@ -4655,6 +4655,7 @@ dependencies = [
|
||||||
"futures 0.3.15",
|
"futures 0.3.15",
|
||||||
"hex",
|
"hex",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"once_cell",
|
||||||
"owo-colors 2.0.0",
|
"owo-colors 2.0.0",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"proptest",
|
"proptest",
|
||||||
|
|
|
@ -573,22 +573,14 @@ async fn v5_transaction_with_transparent_transfer_is_rejected_by_the_script() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests transactions with Sprout transfers.
|
/// Test if signed V4 transaction with a dummy [`sprout::JoinSplit`] is accepted.
|
||||||
///
|
|
||||||
/// This is actually two tests:
|
|
||||||
/// - Test if signed V4 transaction with a dummy [`sprout::JoinSplit`] is accepted.
|
|
||||||
/// - Test if an unsigned V4 transaction with a dummy [`sprout::JoinSplit`] is rejected.
|
/// - Test if an unsigned V4 transaction with a dummy [`sprout::JoinSplit`] is rejected.
|
||||||
///
|
///
|
||||||
/// The first test verifies if the transaction verifier correctly accepts a signed transaction. The
|
/// This test verifies if the transaction verifier correctly accepts a signed transaction.
|
||||||
/// second test verifies if the transaction verifier correctly rejects the transaction because of
|
#[test]
|
||||||
/// the invalid signature.
|
fn v4_with_signed_sprout_transfer_is_accepted() {
|
||||||
///
|
zebra_test::init();
|
||||||
/// These tests are grouped together because of a limitation to test shared [`tower_batch::Batch`]
|
zebra_test::RUNTIME.block_on(async {
|
||||||
/// services. Such services spawn a Tokio task in the runtime, and `#[tokio::test]` can create a
|
|
||||||
/// separate runtime for each test. This means that the worker task is created for one test and
|
|
||||||
/// destroyed before the other gets a chance to use it. (We'll fix this in #2390.)
|
|
||||||
#[tokio::test]
|
|
||||||
async fn v4_with_sprout_transfers() {
|
|
||||||
let network = Network::Mainnet;
|
let network = Network::Mainnet;
|
||||||
let network_upgrade = NetworkUpgrade::Canopy;
|
let network_upgrade = NetworkUpgrade::Canopy;
|
||||||
|
|
||||||
|
@ -605,7 +597,6 @@ async fn v4_with_sprout_transfers() {
|
||||||
let script_verifier = script::Verifier::new(state_service);
|
let script_verifier = script::Verifier::new(state_service);
|
||||||
let verifier = Verifier::new(network, script_verifier);
|
let verifier = Verifier::new(network, script_verifier);
|
||||||
|
|
||||||
for should_sign in [true, false] {
|
|
||||||
// Create a fake Sprout join split
|
// Create a fake Sprout join split
|
||||||
let (joinsplit_data, signing_key) = mock_sprout_join_split_data();
|
let (joinsplit_data, signing_key) = mock_sprout_join_split_data();
|
||||||
|
|
||||||
|
@ -618,7 +609,6 @@ async fn v4_with_sprout_transfers() {
|
||||||
sapling_shielded_data: None,
|
sapling_shielded_data: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_result = if should_sign {
|
|
||||||
// Sign the transaction
|
// Sign the transaction
|
||||||
let sighash = transaction.sighash(network_upgrade, HashType::ALL, None);
|
let sighash = transaction.sighash(network_upgrade, HashType::ALL, None);
|
||||||
|
|
||||||
|
@ -630,13 +620,56 @@ async fn v4_with_sprout_transfers() {
|
||||||
_ => unreachable!("Mock transaction was created incorrectly"),
|
_ => unreachable!("Mock transaction was created incorrectly"),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(transaction.hash())
|
let expected_hash = transaction.hash();
|
||||||
} else {
|
|
||||||
// TODO: Fix error downcast
|
// Test the transaction verifier
|
||||||
// Err(TransactionError::Ed25519(ed25519::Error::InvalidSignature))
|
let result = verifier
|
||||||
Err(TransactionError::InternalDowncastError(
|
.clone()
|
||||||
"downcast to redjubjub::Error failed, original error: InvalidSignature".to_string(),
|
.oneshot(Request::Block {
|
||||||
))
|
transaction: Arc::new(transaction),
|
||||||
|
known_utxos: Arc::new(HashMap::new()),
|
||||||
|
height: transaction_block_height,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(result, Ok(expected_hash));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test if an unsigned V4 transaction with a dummy [`sprout::JoinSplit`] is rejected.
|
||||||
|
///
|
||||||
|
/// This test verifies if the transaction verifier correctly rejects the transaction because of the
|
||||||
|
/// invalid signature.
|
||||||
|
#[test]
|
||||||
|
fn v4_with_unsigned_sprout_transfer_is_rejected() {
|
||||||
|
zebra_test::init();
|
||||||
|
zebra_test::RUNTIME.block_on(async {
|
||||||
|
let network = Network::Mainnet;
|
||||||
|
let network_upgrade = NetworkUpgrade::Canopy;
|
||||||
|
|
||||||
|
let canopy_activation_height = network_upgrade
|
||||||
|
.activation_height(network)
|
||||||
|
.expect("Canopy activation height is not set");
|
||||||
|
|
||||||
|
let transaction_block_height =
|
||||||
|
(canopy_activation_height + 10).expect("Canopy activation height is too large");
|
||||||
|
|
||||||
|
// Initialize the verifier
|
||||||
|
let state_service =
|
||||||
|
service_fn(|_| async { unreachable!("State service should not be called") });
|
||||||
|
let script_verifier = script::Verifier::new(state_service);
|
||||||
|
let verifier = Verifier::new(network, script_verifier);
|
||||||
|
|
||||||
|
// Create a fake Sprout join split
|
||||||
|
let (joinsplit_data, _) = mock_sprout_join_split_data();
|
||||||
|
|
||||||
|
let transaction = Transaction::V4 {
|
||||||
|
inputs: vec![],
|
||||||
|
outputs: vec![],
|
||||||
|
lock_time: LockTime::Height(block::Height(0)),
|
||||||
|
expiry_height: (transaction_block_height + 1).expect("expiry height is too large"),
|
||||||
|
joinsplit_data: Some(joinsplit_data),
|
||||||
|
sapling_shielded_data: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Test the transaction verifier
|
// Test the transaction verifier
|
||||||
|
@ -649,8 +682,18 @@ async fn v4_with_sprout_transfers() {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(result, expected_result);
|
assert_eq!(
|
||||||
}
|
result,
|
||||||
|
Err(
|
||||||
|
// TODO: Fix error downcast
|
||||||
|
// Err(TransactionError::Ed25519(ed25519::Error::InvalidSignature))
|
||||||
|
TransactionError::InternalDowncastError(
|
||||||
|
"downcast to redjubjub::Error failed, original error: InvalidSignature"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
|
|
|
@ -10,11 +10,13 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
once_cell = "1.8"
|
||||||
proptest = "0.10.1"
|
proptest = "0.10.1"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
regex = "1.4.6"
|
regex = "1.4.6"
|
||||||
|
|
||||||
tower = { version = "0.4", features = ["util"] }
|
tower = { version = "0.4", features = ["util"] }
|
||||||
|
tokio = { version = "0.3", features = ["rt-multi-thread"] }
|
||||||
futures = "0.3.15"
|
futures = "0.3.15"
|
||||||
|
|
||||||
color-eyre = "0.5.11"
|
color-eyre = "0.5.11"
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#![recursion_limit = "256"]
|
#![recursion_limit = "256"]
|
||||||
|
|
||||||
use color_eyre::section::PanicMessage;
|
use color_eyre::section::PanicMessage;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use tracing_error::ErrorLayer;
|
use tracing_error::ErrorLayer;
|
||||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||||
|
@ -24,6 +25,28 @@ pub mod prelude;
|
||||||
pub mod transcript;
|
pub mod transcript;
|
||||||
pub mod vectors;
|
pub mod vectors;
|
||||||
|
|
||||||
|
/// A multi-threaded Tokio runtime that can be shared between tests.
|
||||||
|
///
|
||||||
|
/// This shared runtime should be used in tests that use shared background tasks. An example is
|
||||||
|
/// with shared global `Lazy<BatchVerifier>` types, because they spawn a background task when they
|
||||||
|
/// are first initialized. This background task is stopped when the runtime is shut down, so having
|
||||||
|
/// a runtime per test means that only the first test actually manages to successfully use the
|
||||||
|
/// background task. Using the shared runtime allows the background task to keep running for the
|
||||||
|
/// other tests that also use it.
|
||||||
|
///
|
||||||
|
/// A shared runtime should not be used in tests that need to pause and resume the Tokio timer.
|
||||||
|
/// This is because multiple tests might be sharing the runtime at the same time, so there could be
|
||||||
|
/// conflicts with pausing and resuming the timer at incorrect points. Even if only one test runs
|
||||||
|
/// at a time, there's a risk of a test finishing while the timer is paused (due to a test failure,
|
||||||
|
/// for example) and that means that the next test will already start with an incorrect timer
|
||||||
|
/// state.
|
||||||
|
pub static RUNTIME: Lazy<tokio::runtime::Runtime> = Lazy::new(|| {
|
||||||
|
tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("Failed to create Tokio runtime")
|
||||||
|
});
|
||||||
|
|
||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
/// Initialize globals for tests such as the tracing subscriber and panic / error
|
/// Initialize globals for tests such as the tracing subscriber and panic / error
|
||||||
|
|
Loading…
Reference in New Issue