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:
Janito Vaqueiro Ferreira Filho 2021-06-30 21:30:31 -03:00 committed by GitHub
parent 3dbb4098e5
commit f33923f12f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 115 additions and 46 deletions

1
Cargo.lock generated
View File

@ -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",

View File

@ -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

View File

@ -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"

View File

@ -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