use std::{
env,
fmt::Debug,
net::SocketAddr,
path::{Path, PathBuf},
time::Duration,
};
use tempfile::TempDir;
use zebra_chain::parameters::Network::{self, *};
use zebra_network::CacheDir;
use zebra_test::{
args,
command::{Arguments, TestDirExt},
prelude::*,
};
use zebrad::config::ZebradConfig;
use crate::common::{
config::testdir, lightwalletd::zebra_skip_lightwalletd_tests,
sync::FINISH_PARTIAL_SYNC_TIMEOUT, test_type::TestType,
};
pub const LAUNCH_DELAY: Duration = Duration::from_secs(20);
pub const EXTENDED_LAUNCH_DELAY: Duration = Duration::from_secs(45);
pub const LIGHTWALLETD_DELAY: Duration = Duration::from_secs(60);
pub const BETWEEN_NODES_DELAY: Duration = Duration::from_secs(20);
pub const LIGHTWALLETD_UPDATE_TIP_DELAY: Duration = FINISH_PARTIAL_SYNC_TIMEOUT;
pub const LIGHTWALLETD_FULL_SYNC_TIP_DELAY: Duration = FINISH_PARTIAL_SYNC_TIMEOUT;
pub trait ZebradTestDirExt
where
Self: AsRef<Path> + Sized,
{
fn spawn_child(self, args: Arguments) -> Result<TestChild<Self>>;
fn with_config(self, config: &mut ZebradConfig) -> Result<Self>;
fn with_exact_config(self, config: &ZebradConfig) -> Result<Self>;
fn replace_config(self, config: &mut ZebradConfig) -> Result<Self>;
fn cache_config_update_helper(self, config: &mut ZebradConfig) -> Result<Self>;
fn write_config_helper(self, config: &ZebradConfig) -> Result<Self>;
}
impl<T> ZebradTestDirExt for T
where
Self: TestDirExt + AsRef<Path> + Sized,
{
#[allow(clippy::unwrap_in_result)]
fn spawn_child(self, extra_args: Arguments) -> Result<TestChild<Self>> {
let dir = self.as_ref();
let default_config_path = dir.join("zebrad.toml");
let mut args = Arguments::new();
if default_config_path.exists() {
args.set_parameter(
"-c",
default_config_path
.as_path()
.to_str()
.expect("Path is valid Unicode"),
);
}
args.merge_with(extra_args);
self.spawn_child_with_command(env!("CARGO_BIN_EXE_zebrad"), args)
}
fn with_config(self, config: &mut ZebradConfig) -> Result<Self> {
self.cache_config_update_helper(config)?
.write_config_helper(config)
}
fn with_exact_config(self, config: &ZebradConfig) -> Result<Self> {
self.write_config_helper(config)
}
fn replace_config(self, config: &mut ZebradConfig) -> Result<Self> {
use std::fs;
use std::io::ErrorKind;
let dir = self.as_ref();
let config_file = dir.join("zebrad.toml");
match fs::remove_file(config_file) {
Ok(()) => {}
Err(e) if e.kind() == ErrorKind::NotFound => {}
Err(e) => Err(e)?,
}
self.cache_config_update_helper(config)?
.write_config_helper(config)
}
fn cache_config_update_helper(self, config: &mut ZebradConfig) -> Result<Self> {
let dir = self.as_ref();
let cache_dir = PathBuf::from(dir);
if config.network.cache_dir.is_enabled() {
config.network.cache_dir = CacheDir::custom_path(&cache_dir);
}
if !config.state.ephemeral {
config.state.cache_dir = cache_dir;
}
Ok(self)
}
fn write_config_helper(self, config: &ZebradConfig) -> Result<Self> {
use std::fs;
use std::io::Write;
let dir = self.as_ref();
if !config.state.ephemeral {
let cache_dir = dir.join("state");
fs::create_dir_all(cache_dir)?;
} else {
fs::create_dir_all(dir)?;
}
let config_file = dir.join("zebrad.toml");
fs::File::create(config_file)?.write_all(toml::to_string(&config)?.as_bytes())?;
Ok(self)
}
}
#[tracing::instrument]
pub fn spawn_zebrad_for_rpc<S: AsRef<str> + Debug>(
network: Network,
test_name: S,
test_type: TestType,
use_internet_connection: bool,
) -> Result<Option<(TestChild<TempDir>, Option<SocketAddr>)>> {
spawn_zebrad_for_rpc_with_opts(network, test_name, test_type, use_internet_connection, true)
}
#[tracing::instrument]
pub fn spawn_zebrad_for_rpc_with_opts<S: AsRef<str> + Debug>(
network: Network,
test_name: S,
test_type: TestType,
use_internet_connection: bool,
use_non_finalized_backup: bool,
) -> Result<Option<(TestChild<TempDir>, Option<SocketAddr>)>> {
let test_name = test_name.as_ref();
if !can_spawn_zebrad_for_test_type(test_name, test_type, use_internet_connection) {
return Ok(None);
}
let mut config = test_type
.zebrad_config(test_name, use_internet_connection, None, &network)
.expect("already checked config")?;
config.state.should_backup_non_finalized_state = use_non_finalized_backup;
let (zebrad_failure_messages, zebrad_ignore_messages) = test_type.zebrad_failure_messages();
let zebrad = testdir()?
.with_exact_config(&config)?
.spawn_child(args!["start"])?
.bypass_test_capture(true)
.with_timeout(test_type.zebrad_timeout())
.with_failure_regex_iter(zebrad_failure_messages, zebrad_ignore_messages);
Ok(Some((zebrad, config.rpc.listen_addr)))
}
#[tracing::instrument]
pub fn spawn_zebrad_without_rpc<Str, Dir>(
network: Network,
test_name: Str,
use_cached_state: bool,
ephemeral: bool,
reuse_state_path: Dir,
use_internet_connection: bool,
) -> Result<Option<TestChild<TempDir>>>
where
Str: AsRef<str> + Debug,
Dir: Into<Option<TempDir>> + Debug,
{
use TestType::*;
let test_name = test_name.as_ref();
let reuse_state_path = reuse_state_path.into();
let testdir = reuse_state_path
.unwrap_or_else(|| testdir().expect("failed to create test temporary directory"));
let (test_type, replace_cache_dir) = if use_cached_state {
(UpdateZebraCachedStateNoRpc, None)
} else if ephemeral {
(
LaunchWithEmptyState {
launches_lightwalletd: false,
},
None,
)
} else {
(UseAnyState, Some(testdir.path()))
};
if !can_spawn_zebrad_for_test_type(test_name, test_type, use_internet_connection) {
return Ok(None);
}
let config = test_type
.zebrad_config(
test_name,
use_internet_connection,
replace_cache_dir,
&network,
)
.expect("already checked config")?;
let (zebrad_failure_messages, zebrad_ignore_messages) = test_type.zebrad_failure_messages();
let zebrad = testdir
.with_exact_config(&config)?
.spawn_child(args!["start"])?
.bypass_test_capture(true)
.with_timeout(test_type.zebrad_timeout())
.with_failure_regex_iter(zebrad_failure_messages, zebrad_ignore_messages);
Ok(Some(zebrad))
}
#[tracing::instrument]
pub fn can_spawn_zebrad_for_test_type<S: AsRef<str> + Debug>(
test_name: S,
test_type: TestType,
use_internet_connection: bool,
) -> bool {
if use_internet_connection && zebra_test::net::zebra_skip_network_tests() {
return false;
}
if test_type.launches_lightwalletd() && zebra_skip_lightwalletd_tests() {
return false;
}
test_type
.zebrad_config(test_name, true, None, &Mainnet)
.is_some()
}
#[macro_export]
macro_rules! assert_with_context {
($pred:expr, $source:expr) => {
if !$pred {
use color_eyre::Section as _;
use color_eyre::SectionExt as _;
use zebra_test::command::ContextFrom as _;
let report = color_eyre::eyre::eyre!("failed assertion")
.section(stringify!($pred).header("Predicate:"))
.context_from($source);
panic!("Error: {:?}", report);
}
};
($pred:expr, $source:expr, $($fmt_arg:tt)+) => {
if !$pred {
use color_eyre::Section as _;
use color_eyre::SectionExt as _;
use zebra_test::command::ContextFrom as _;
let report = color_eyre::eyre::eyre!("failed assertion")
.section(stringify!($pred).header("Predicate:"))
.context_from($source)
.wrap_err(format!($($fmt_arg)+));
panic!("Error: {:?}", report);
}
};
}