357 lines
14 KiB
Rust
357 lines
14 KiB
Rust
//! Provides TestType enum with shared code for acceptance tests
|
|
|
|
use std::{
|
|
env,
|
|
path::{Path, PathBuf},
|
|
time::Duration,
|
|
};
|
|
|
|
use indexmap::IndexSet;
|
|
|
|
use zebra_chain::parameters::Network;
|
|
use zebra_network::CacheDir;
|
|
use zebra_test::{command::NO_MATCHES_REGEX_ITER, prelude::*};
|
|
use zebrad::config::ZebradConfig;
|
|
|
|
use super::{
|
|
cached_state::ZEBRA_CACHED_STATE_DIR,
|
|
config::{default_test_config, random_known_rpc_port_config},
|
|
failure_messages::{
|
|
LIGHTWALLETD_EMPTY_ZEBRA_STATE_IGNORE_MESSAGES, LIGHTWALLETD_FAILURE_MESSAGES,
|
|
PROCESS_FAILURE_MESSAGES, ZEBRA_FAILURE_MESSAGES,
|
|
},
|
|
launch::{LIGHTWALLETD_DELAY, LIGHTWALLETD_FULL_SYNC_TIP_DELAY, LIGHTWALLETD_UPDATE_TIP_DELAY},
|
|
lightwalletd::LIGHTWALLETD_DATA_DIR,
|
|
sync::FINISH_PARTIAL_SYNC_TIMEOUT,
|
|
};
|
|
|
|
use TestType::*;
|
|
|
|
/// The type of integration test that we're running.
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
pub enum TestType {
|
|
/// Launch with an empty Zebra and lightwalletd state.
|
|
LaunchWithEmptyState {
|
|
/// Configures whether the test uses lightwalletd.
|
|
launches_lightwalletd: bool,
|
|
},
|
|
|
|
/// Do a full sync from an empty lightwalletd state.
|
|
///
|
|
/// This test requires a cached Zebra state.
|
|
//
|
|
// Only used with `--features=lightwalletd-grpc-tests`.
|
|
#[allow(dead_code)]
|
|
FullSyncFromGenesis {
|
|
/// Should the test allow a cached lightwalletd state?
|
|
///
|
|
/// If `false`, the test fails if the lightwalletd state is populated.
|
|
allow_lightwalletd_cached_state: bool,
|
|
},
|
|
|
|
/// Launch with a Zebra and lightwalletd state that might or might not be empty.
|
|
UseAnyState,
|
|
|
|
/// Sync to tip from a lightwalletd cached state.
|
|
///
|
|
/// This test requires a cached Zebra and lightwalletd state.
|
|
UpdateCachedState,
|
|
|
|
/// Launch `zebrad` and sync it to the tip, but don't launch `lightwalletd`.
|
|
///
|
|
/// If this test fails, the failure is in `zebrad` without RPCs or `lightwalletd`.
|
|
/// If it succeeds, but the RPC tests fail, the problem is caused by RPCs or `lightwalletd`.
|
|
///
|
|
/// This test requires a cached Zebra state.
|
|
UpdateZebraCachedStateNoRpc,
|
|
|
|
/// Launch `zebrad` and sync it to the tip, but don't launch `lightwalletd`.
|
|
///
|
|
/// This test requires a cached Zebra state.
|
|
#[allow(dead_code)]
|
|
UpdateZebraCachedStateWithRpc,
|
|
}
|
|
|
|
impl TestType {
|
|
/// Does this test need a Zebra cached state?
|
|
pub fn needs_zebra_cached_state(&self) -> bool {
|
|
// Handle the Zebra state directory based on the test type:
|
|
// - LaunchWithEmptyState: ignore the state directory
|
|
// - FullSyncFromGenesis, UpdateCachedState, UpdateZebraCachedStateNoRpc:
|
|
// skip the test if it is not available
|
|
match self {
|
|
LaunchWithEmptyState { .. } | UseAnyState => false,
|
|
FullSyncFromGenesis { .. }
|
|
| UpdateCachedState
|
|
| UpdateZebraCachedStateNoRpc
|
|
| UpdateZebraCachedStateWithRpc => true,
|
|
}
|
|
}
|
|
|
|
/// Does this test need a Zebra rpc server?
|
|
pub fn needs_zebra_rpc_server(&self) -> bool {
|
|
match self {
|
|
UpdateZebraCachedStateWithRpc | LaunchWithEmptyState { .. } => true,
|
|
UseAnyState
|
|
| UpdateZebraCachedStateNoRpc
|
|
| FullSyncFromGenesis { .. }
|
|
| UpdateCachedState => self.launches_lightwalletd(),
|
|
}
|
|
}
|
|
|
|
/// Does this test launch `lightwalletd`?
|
|
pub fn launches_lightwalletd(&self) -> bool {
|
|
match self {
|
|
UseAnyState | UpdateZebraCachedStateNoRpc | UpdateZebraCachedStateWithRpc => false,
|
|
FullSyncFromGenesis { .. } | UpdateCachedState => true,
|
|
LaunchWithEmptyState {
|
|
launches_lightwalletd,
|
|
} => *launches_lightwalletd,
|
|
}
|
|
}
|
|
|
|
/// Does this test need a `lightwalletd` cached state?
|
|
pub fn needs_lightwalletd_cached_state(&self) -> bool {
|
|
// Handle the lightwalletd state directory based on the test type:
|
|
// - LaunchWithEmptyState, UpdateZebraCachedStateNoRpc: ignore the state directory
|
|
// - FullSyncFromGenesis: use it if available, timeout if it is already populated
|
|
// - UpdateCachedState: skip the test if it is not available
|
|
match self {
|
|
LaunchWithEmptyState { .. }
|
|
| UseAnyState
|
|
| FullSyncFromGenesis { .. }
|
|
| UpdateZebraCachedStateNoRpc
|
|
| UpdateZebraCachedStateWithRpc => false,
|
|
UpdateCachedState => true,
|
|
}
|
|
}
|
|
|
|
/// Does this test allow a `lightwalletd` cached state, even if it is not required?
|
|
pub fn allow_lightwalletd_cached_state(&self) -> bool {
|
|
match self {
|
|
LaunchWithEmptyState { .. } => false,
|
|
FullSyncFromGenesis {
|
|
allow_lightwalletd_cached_state,
|
|
} => *allow_lightwalletd_cached_state,
|
|
UseAnyState
|
|
| UpdateCachedState
|
|
| UpdateZebraCachedStateNoRpc
|
|
| UpdateZebraCachedStateWithRpc => true,
|
|
}
|
|
}
|
|
|
|
/// Can this test create a new `LIGHTWALLETD_DATA_DIR` cached state?
|
|
pub fn can_create_lightwalletd_cached_state(&self) -> bool {
|
|
match self {
|
|
LaunchWithEmptyState { .. } | UseAnyState => false,
|
|
FullSyncFromGenesis { .. } | UpdateCachedState => true,
|
|
UpdateZebraCachedStateNoRpc | UpdateZebraCachedStateWithRpc => false,
|
|
}
|
|
}
|
|
|
|
/// Returns the Zebra state path for this test, if set.
|
|
#[allow(clippy::print_stderr)]
|
|
pub fn zebrad_state_path<S: AsRef<str>>(&self, test_name: S) -> Option<PathBuf> {
|
|
match env::var_os(ZEBRA_CACHED_STATE_DIR) {
|
|
Some(path) => Some(path.into()),
|
|
None => {
|
|
let test_name = test_name.as_ref();
|
|
eprintln!(
|
|
"skipped {test_name:?} {self:?} lightwalletd test, \
|
|
set the {ZEBRA_CACHED_STATE_DIR:?} environment variable to run the test",
|
|
);
|
|
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns a Zebra config for this test.
|
|
///
|
|
/// `replace_cache_dir` replaces any cached or ephemeral state.
|
|
///
|
|
/// Returns `None` if the test should be skipped,
|
|
/// and `Some(Err(_))` if the config could not be created.
|
|
pub fn zebrad_config<Str: AsRef<str>>(
|
|
&self,
|
|
test_name: Str,
|
|
use_internet_connection: bool,
|
|
replace_cache_dir: Option<&Path>,
|
|
network: Network,
|
|
) -> Option<Result<ZebradConfig>> {
|
|
let config = if self.needs_zebra_rpc_server() {
|
|
// This is what we recommend our users configure.
|
|
random_known_rpc_port_config(true, network)
|
|
} else {
|
|
default_test_config(network)
|
|
};
|
|
|
|
let mut config = match config {
|
|
Ok(config) => config,
|
|
Err(error) => return Some(Err(error)),
|
|
};
|
|
|
|
// We want to run multi-threaded RPCs, if we're using them
|
|
if self.launches_lightwalletd() {
|
|
// Automatically runs one thread per available CPU core
|
|
config.rpc.parallel_cpu_threads = 0;
|
|
}
|
|
|
|
if !use_internet_connection {
|
|
config.network.initial_mainnet_peers = IndexSet::new();
|
|
config.network.initial_testnet_peers = IndexSet::new();
|
|
// Avoid re-using cached peers from disk when we're supposed to be a disconnected instance
|
|
config.network.cache_dir = CacheDir::disabled();
|
|
|
|
// Activate the mempool immediately by default
|
|
config.mempool.debug_enable_at_height = Some(0);
|
|
}
|
|
|
|
// If we have a cached state, or we don't want to be ephemeral, update the config to use it
|
|
if replace_cache_dir.is_some() || self.needs_zebra_cached_state() {
|
|
let zebra_state_path = replace_cache_dir
|
|
.map(|path| path.to_owned())
|
|
.or_else(|| self.zebrad_state_path(test_name))?;
|
|
|
|
config.state.ephemeral = false;
|
|
config.state.cache_dir = zebra_state_path;
|
|
|
|
// And reset the concurrency to the default value
|
|
config.sync.checkpoint_verify_concurrency_limit =
|
|
zebrad::components::sync::DEFAULT_CHECKPOINT_CONCURRENCY_LIMIT;
|
|
}
|
|
|
|
Some(Ok(config))
|
|
}
|
|
|
|
/// Returns the `lightwalletd` state path for this test, if set, and if allowed for this test.
|
|
pub fn lightwalletd_state_path<S: AsRef<str>>(&self, test_name: S) -> Option<PathBuf> {
|
|
let test_name = test_name.as_ref();
|
|
|
|
// Can this test type use a lwd cached state, or create/update one?
|
|
let use_or_create_lwd_cache =
|
|
self.allow_lightwalletd_cached_state() || self.can_create_lightwalletd_cached_state();
|
|
|
|
if !self.launches_lightwalletd() || !use_or_create_lwd_cache {
|
|
tracing::info!(
|
|
"running {test_name:?} {self:?} lightwalletd test, \
|
|
ignoring any cached state in the {LIGHTWALLETD_DATA_DIR:?} environment variable",
|
|
);
|
|
|
|
return None;
|
|
}
|
|
|
|
match env::var_os(LIGHTWALLETD_DATA_DIR) {
|
|
Some(path) => Some(path.into()),
|
|
None => {
|
|
if self.needs_lightwalletd_cached_state() {
|
|
tracing::info!(
|
|
"skipped {test_name:?} {self:?} lightwalletd test, \
|
|
set the {LIGHTWALLETD_DATA_DIR:?} environment variable to run the test",
|
|
);
|
|
} else if self.allow_lightwalletd_cached_state() {
|
|
tracing::info!(
|
|
"running {test_name:?} {self:?} lightwalletd test without cached state, \
|
|
set the {LIGHTWALLETD_DATA_DIR:?} environment variable to run with cached state",
|
|
);
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the `zebrad` timeout for this test type.
|
|
pub fn zebrad_timeout(&self) -> Duration {
|
|
match self {
|
|
LaunchWithEmptyState { .. } | UseAnyState => LIGHTWALLETD_DELAY,
|
|
FullSyncFromGenesis { .. } => LIGHTWALLETD_FULL_SYNC_TIP_DELAY,
|
|
UpdateCachedState | UpdateZebraCachedStateNoRpc => LIGHTWALLETD_UPDATE_TIP_DELAY,
|
|
UpdateZebraCachedStateWithRpc => FINISH_PARTIAL_SYNC_TIMEOUT,
|
|
}
|
|
}
|
|
|
|
/// Returns the `lightwalletd` timeout for this test type.
|
|
#[track_caller]
|
|
pub fn lightwalletd_timeout(&self) -> Duration {
|
|
if !self.launches_lightwalletd() {
|
|
panic!("lightwalletd must not be launched in the {self:?} test");
|
|
}
|
|
|
|
// We use the same timeouts for zebrad and lightwalletd,
|
|
// because the tests check zebrad and lightwalletd concurrently.
|
|
match self {
|
|
LaunchWithEmptyState { .. } | UseAnyState => LIGHTWALLETD_DELAY,
|
|
FullSyncFromGenesis { .. } => LIGHTWALLETD_FULL_SYNC_TIP_DELAY,
|
|
UpdateCachedState | UpdateZebraCachedStateNoRpc | UpdateZebraCachedStateWithRpc => {
|
|
LIGHTWALLETD_UPDATE_TIP_DELAY
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns Zebra log regexes that indicate the tests have failed,
|
|
/// and regexes of any failures that should be ignored.
|
|
pub fn zebrad_failure_messages(&self) -> (Vec<String>, Vec<String>) {
|
|
let mut zebrad_failure_messages: Vec<String> = ZEBRA_FAILURE_MESSAGES
|
|
.iter()
|
|
.chain(PROCESS_FAILURE_MESSAGES)
|
|
.map(ToString::to_string)
|
|
.collect();
|
|
|
|
if self.needs_zebra_cached_state() {
|
|
// Fail if we need a cached Zebra state, but it's empty
|
|
zebrad_failure_messages.push("loaded Zebra state cache .*tip.*=.*None".to_string());
|
|
}
|
|
if matches!(*self, LaunchWithEmptyState { .. }) {
|
|
// Fail if we need an empty Zebra state, but it has blocks
|
|
zebrad_failure_messages
|
|
.push(r"loaded Zebra state cache .*tip.*=.*Height\([1-9][0-9]*\)".to_string());
|
|
}
|
|
|
|
let zebrad_ignore_messages = Vec::new();
|
|
|
|
(zebrad_failure_messages, zebrad_ignore_messages)
|
|
}
|
|
|
|
/// Returns `lightwalletd` log regexes that indicate the tests have failed,
|
|
/// and regexes of any failures that should be ignored.
|
|
#[track_caller]
|
|
pub fn lightwalletd_failure_messages(&self) -> (Vec<String>, Vec<String>) {
|
|
if !self.launches_lightwalletd() {
|
|
panic!("lightwalletd must not be launched in the {self:?} test");
|
|
}
|
|
|
|
let mut lightwalletd_failure_messages: Vec<String> = LIGHTWALLETD_FAILURE_MESSAGES
|
|
.iter()
|
|
.chain(PROCESS_FAILURE_MESSAGES)
|
|
.map(ToString::to_string)
|
|
.collect();
|
|
|
|
// Zebra state failures
|
|
if self.needs_zebra_cached_state() {
|
|
// Fail if we need a cached Zebra state, but it's empty
|
|
lightwalletd_failure_messages.push("No Chain tip available yet".to_string());
|
|
}
|
|
|
|
// lightwalletd state failures
|
|
if self.needs_lightwalletd_cached_state() {
|
|
// Fail if we need a cached lightwalletd state, but it isn't near the tip
|
|
lightwalletd_failure_messages.push("Found [0-9]{1,6} blocks in cache".to_string());
|
|
}
|
|
if !self.allow_lightwalletd_cached_state() {
|
|
// Fail if we need an empty lightwalletd state, but it has blocks
|
|
lightwalletd_failure_messages.push("Found [1-9][0-9]* blocks in cache".to_string());
|
|
}
|
|
|
|
let lightwalletd_ignore_messages = if matches!(*self, LaunchWithEmptyState { .. }) {
|
|
LIGHTWALLETD_EMPTY_ZEBRA_STATE_IGNORE_MESSAGES.iter()
|
|
} else {
|
|
NO_MATCHES_REGEX_ITER.iter()
|
|
}
|
|
.map(ToString::to_string)
|
|
.collect();
|
|
|
|
(lightwalletd_failure_messages, lightwalletd_ignore_messages)
|
|
}
|
|
}
|