T0. refactor(test): split zebrad acceptance tests into sub-modules (#3901)
* Improve launch delay docs * Initial split of zebrad acceptance tests into modules * Split shared lightwalletd test code into a module
This commit is contained in:
parent
84ee78dee4
commit
9a8ab9468d
|
@ -79,3 +79,16 @@ pub fn random_known_port() -> u16 {
|
|||
|
||||
rand::thread_rng().gen_range(53500..60999)
|
||||
}
|
||||
|
||||
/// Returns the "magic" port number that tells the operating system to
|
||||
/// choose a random unallocated port.
|
||||
///
|
||||
/// The OS chooses a different port each time it opens a connection or
|
||||
/// listener with this magic port number.
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// See the usage note for `random_known_port`.
|
||||
pub fn random_unallocated_port() -> u16 {
|
||||
0
|
||||
}
|
||||
|
|
|
@ -21,16 +21,12 @@
|
|||
//! or you have poor network connectivity,
|
||||
//! skip all the network tests by setting the `ZEBRA_SKIP_NETWORK_TESTS` environmental variable.
|
||||
|
||||
use std::{collections::HashSet, convert::TryInto, env, path::PathBuf, time::Duration};
|
||||
|
||||
use color_eyre::{
|
||||
eyre::{Result, WrapErr},
|
||||
Help,
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
|
||||
use std::{
|
||||
collections::HashSet, convert::TryInto, env, net::SocketAddr, path::Path, path::PathBuf,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use zebra_chain::{
|
||||
block::Height,
|
||||
|
@ -38,312 +34,25 @@ use zebra_chain::{
|
|||
};
|
||||
use zebra_network::constants::PORT_IN_USE_ERROR;
|
||||
use zebra_state::constants::LOCK_FILE_ERROR;
|
||||
use zebra_test::{
|
||||
command::{ContextFrom, TestDirExt},
|
||||
net::random_known_port,
|
||||
prelude::*,
|
||||
|
||||
use zebra_test::{command::ContextFrom, net::random_known_port, prelude::*};
|
||||
|
||||
mod common;
|
||||
|
||||
use common::{
|
||||
check::{is_zebrad_version, EphemeralCheck, EphemeralConfig},
|
||||
config::{default_test_config, persistent_test_config, testdir},
|
||||
launch::{ZebradTestDirExt, BETWEEN_NODES_DELAY, LAUNCH_DELAY, LIGHTWALLETD_DELAY},
|
||||
lightwalletd::{
|
||||
random_known_rpc_port_config, zebra_skip_lightwalletd_tests, LightWalletdTestDirExt,
|
||||
},
|
||||
sync::{
|
||||
create_cached_database_height, sync_until, MempoolBehavior, LARGE_CHECKPOINT_TEST_HEIGHT,
|
||||
LARGE_CHECKPOINT_TIMEOUT, MEDIUM_CHECKPOINT_TEST_HEIGHT, STOP_AT_HEIGHT_REGEX,
|
||||
STOP_ON_LOAD_TIMEOUT, SYNC_FINISHED_REGEX, TINY_CHECKPOINT_TEST_HEIGHT,
|
||||
TINY_CHECKPOINT_TIMEOUT,
|
||||
},
|
||||
};
|
||||
use zebrad::{
|
||||
components::{mempool, sync},
|
||||
config::{SyncSection, TracingSection, ZebradConfig},
|
||||
};
|
||||
|
||||
/// The amount of time we wait after launching `zebrad`.
|
||||
///
|
||||
/// Previously, this value was 3 seconds, which caused rare
|
||||
/// metrics or tracing test failures in Windows CI.
|
||||
const LAUNCH_DELAY: Duration = Duration::from_secs(15);
|
||||
|
||||
/// The amount of time we wait after launching `lightwalletd`,
|
||||
/// and between expected `lightwalletd` log messages.
|
||||
const LIGHTWALLETD_DELAY: Duration = Duration::from_secs(60);
|
||||
|
||||
/// The amount of time we wait between launching two
|
||||
/// conflicting nodes.
|
||||
const BETWEEN_NODES_DELAY: Duration = Duration::from_secs(2);
|
||||
|
||||
/// Returns a config with:
|
||||
/// - a Zcash listener on an unused port on IPv4 localhost, and
|
||||
/// - an ephemeral state,
|
||||
/// - the minimum syncer lookahead limit, and
|
||||
/// - shorter task intervals, to improve test coverage.
|
||||
fn default_test_config() -> Result<ZebradConfig> {
|
||||
const TEST_DURATION: Duration = Duration::from_secs(30);
|
||||
|
||||
let network = zebra_network::Config {
|
||||
// The OS automatically chooses an unused port.
|
||||
listen_addr: "127.0.0.1:0".parse()?,
|
||||
crawl_new_peer_interval: TEST_DURATION,
|
||||
..zebra_network::Config::default()
|
||||
};
|
||||
|
||||
let sync = SyncSection {
|
||||
// Avoid downloading unnecessary blocks.
|
||||
lookahead_limit: sync::MIN_LOOKAHEAD_LIMIT,
|
||||
..SyncSection::default()
|
||||
};
|
||||
|
||||
let mempool = mempool::Config {
|
||||
eviction_memory_time: TEST_DURATION,
|
||||
..mempool::Config::default()
|
||||
};
|
||||
|
||||
let consensus = zebra_consensus::Config {
|
||||
debug_skip_parameter_preload: true,
|
||||
..zebra_consensus::Config::default()
|
||||
};
|
||||
|
||||
let force_use_color = !matches!(
|
||||
env::var("ZEBRA_FORCE_USE_COLOR"),
|
||||
Err(env::VarError::NotPresent)
|
||||
);
|
||||
let tracing = TracingSection {
|
||||
force_use_color,
|
||||
..TracingSection::default()
|
||||
};
|
||||
|
||||
let config = ZebradConfig {
|
||||
network,
|
||||
state: zebra_state::Config::ephemeral(),
|
||||
sync,
|
||||
mempool,
|
||||
consensus,
|
||||
tracing,
|
||||
..ZebradConfig::default()
|
||||
};
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn persistent_test_config() -> Result<ZebradConfig> {
|
||||
let mut config = default_test_config()?;
|
||||
config.state.ephemeral = false;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn testdir() -> Result<TempDir> {
|
||||
tempfile::Builder::new()
|
||||
.prefix("zebrad_tests")
|
||||
.tempdir()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Extension trait for methods on `tempfile::TempDir` for using it as a test
|
||||
/// directory for `zebrad`.
|
||||
trait ZebradTestDirExt
|
||||
where
|
||||
Self: AsRef<Path> + Sized,
|
||||
{
|
||||
// Zebra methods
|
||||
|
||||
/// Spawn `zebrad` with `args` as a child process in this test directory,
|
||||
/// potentially taking ownership of the tempdir for the duration of the
|
||||
/// child process.
|
||||
///
|
||||
/// If there is a config in the test directory, pass it to `zebrad`.
|
||||
fn spawn_child(self, args: &[&str]) -> Result<TestChild<Self>>;
|
||||
|
||||
/// Create a config file and use it for all subsequently spawned `zebrad` processes.
|
||||
/// Returns an error if the config already exists.
|
||||
///
|
||||
/// If needed:
|
||||
/// - recursively create directories for the config and state
|
||||
/// - set `config.cache_dir` based on `self`
|
||||
fn with_config(self, config: &mut ZebradConfig) -> Result<Self>;
|
||||
|
||||
/// Create a config file with the exact contents of `config`, and use it for
|
||||
/// all subsequently spawned `zebrad` processes. Returns an error if the config
|
||||
/// already exists.
|
||||
///
|
||||
/// If needed:
|
||||
/// - recursively create directories for the config and state
|
||||
fn with_exact_config(self, config: &ZebradConfig) -> Result<Self>;
|
||||
|
||||
/// Overwrite any existing `zebrad` config file, and use the newly written config for
|
||||
/// all subsequently spawned processes.
|
||||
///
|
||||
/// If needed:
|
||||
/// - recursively create directories for the config and state
|
||||
/// - set `config.cache_dir` based on `self`
|
||||
fn replace_config(self, config: &mut ZebradConfig) -> Result<Self>;
|
||||
|
||||
/// `cache_dir` config update helper for `zebrad`.
|
||||
///
|
||||
/// If needed:
|
||||
/// - set the cache_dir in the config.
|
||||
fn cache_config_update_helper(self, config: &mut ZebradConfig) -> Result<Self>;
|
||||
|
||||
/// Config writing helper for `zebrad`.
|
||||
///
|
||||
/// If needed:
|
||||
/// - recursively create directories for the config and state,
|
||||
///
|
||||
/// Then write out the config.
|
||||
fn write_config_helper(self, config: &ZebradConfig) -> Result<Self>;
|
||||
|
||||
// lightwalletd methods
|
||||
|
||||
/// Spawn `lightwalletd` with `args` as a child process in this test directory,
|
||||
/// potentially taking ownership of the tempdir for the duration of the
|
||||
/// child process.
|
||||
///
|
||||
/// By default, launch a working test instance with logging, and avoid port conflicts.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If there is no lightwalletd config in the test directory.
|
||||
fn spawn_lightwalletd_child(self, args: &[&str]) -> Result<TestChild<Self>>;
|
||||
|
||||
/// Create a config file and use it for all subsequently spawned `lightwalletd` processes.
|
||||
/// Returns an error if the config already exists.
|
||||
///
|
||||
/// If needed:
|
||||
/// - recursively create directories for the config
|
||||
fn with_lightwalletd_config(self, zebra_rpc_listener: SocketAddr) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl<T> ZebradTestDirExt for T
|
||||
where
|
||||
Self: TestDirExt + AsRef<Path> + Sized,
|
||||
{
|
||||
fn spawn_child(self, args: &[&str]) -> Result<TestChild<Self>> {
|
||||
let dir = self.as_ref();
|
||||
let default_config_path = dir.join("zebrad.toml");
|
||||
|
||||
if default_config_path.exists() {
|
||||
let mut extra_args: Vec<_> = vec![
|
||||
"-c",
|
||||
default_config_path
|
||||
.as_path()
|
||||
.to_str()
|
||||
.expect("Path is valid Unicode"),
|
||||
];
|
||||
extra_args.extend_from_slice(args);
|
||||
self.spawn_child_with_command(env!("CARGO_BIN_EXE_zebrad"), &extra_args)
|
||||
} else {
|
||||
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;
|
||||
|
||||
// Remove any existing config before writing a new one
|
||||
let dir = self.as_ref();
|
||||
let config_file = dir.join("zebrad.toml");
|
||||
match fs::remove_file(config_file) {
|
||||
Ok(()) => {}
|
||||
// If the config file doesn't exist, that's 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> {
|
||||
if !config.state.ephemeral {
|
||||
let dir = self.as_ref();
|
||||
let cache_dir = dir.join("state");
|
||||
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)
|
||||
}
|
||||
|
||||
fn spawn_lightwalletd_child(self, extra_args: &[&str]) -> Result<TestChild<Self>> {
|
||||
let dir = self.as_ref().to_owned();
|
||||
let default_config_path = dir.join("lightwalletd-zcash.conf");
|
||||
|
||||
assert!(
|
||||
default_config_path.exists(),
|
||||
"lightwalletd requires a config"
|
||||
);
|
||||
|
||||
// By default, launch a working test instance with logging,
|
||||
// and avoid port conflicts.
|
||||
let mut args: Vec<_> = vec![
|
||||
// the fake zcashd conf we just wrote
|
||||
"--zcash-conf-path",
|
||||
default_config_path
|
||||
.as_path()
|
||||
.to_str()
|
||||
.expect("Path is valid Unicode"),
|
||||
// the lightwalletd cache directory
|
||||
//
|
||||
// TODO: create a sub-directory for lightwalletd
|
||||
"--data-dir",
|
||||
dir.to_str().expect("Path is valid Unicode"),
|
||||
// log to standard output
|
||||
//
|
||||
// TODO: if lightwalletd needs to run on Windows,
|
||||
// work out how to log to the terminal on all platforms
|
||||
"--log-file",
|
||||
"/dev/stdout",
|
||||
// let the OS choose a random available wallet client port
|
||||
"--grpc-bind-addr",
|
||||
"127.0.0.1:0",
|
||||
"--http-bind-addr",
|
||||
"127.0.0.1:0",
|
||||
// don't require a TLS certificate for the HTTP server
|
||||
"--no-tls-very-insecure",
|
||||
];
|
||||
args.extend_from_slice(extra_args);
|
||||
|
||||
self.spawn_child_with_command("lightwalletd", &args)
|
||||
}
|
||||
|
||||
fn with_lightwalletd_config(self, zebra_rpc_listener: SocketAddr) -> Result<Self> {
|
||||
use std::fs;
|
||||
|
||||
let lightwalletd_config = format!(
|
||||
"\
|
||||
rpcbind={}\n\
|
||||
rpcport={}\n\
|
||||
",
|
||||
zebra_rpc_listener.ip(),
|
||||
zebra_rpc_listener.port(),
|
||||
);
|
||||
|
||||
let dir = self.as_ref();
|
||||
fs::create_dir_all(dir)?;
|
||||
|
||||
let config_file = dir.join("lightwalletd-zcash.conf");
|
||||
fs::write(config_file, lightwalletd_config.as_bytes())?;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_no_args() -> Result<()> {
|
||||
|
@ -362,38 +71,6 @@ fn generate_no_args() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Panics if `$pred` is false, with an error report containing:
|
||||
/// * context from `$source`, and
|
||||
/// * an optional wrapper error, using `$fmt_arg`+ as a format string and
|
||||
/// arguments.
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_args() -> Result<()> {
|
||||
zebra_test::init();
|
||||
|
@ -440,17 +117,6 @@ fn generate_args() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Is `s` a valid `zebrad` version string?
|
||||
///
|
||||
/// Trims whitespace before parsing the version.
|
||||
///
|
||||
/// Returns false if the version is invalid, or if there is anything else on the
|
||||
/// line that contains the version. In particular, this check will fail if `s`
|
||||
/// includes any terminal formatting.
|
||||
fn is_zebrad_version(s: &str) -> bool {
|
||||
semver::Version::parse(s.replace("zebrad", "").trim()).is_ok()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_no_args() -> Result<()> {
|
||||
zebra_test::init();
|
||||
|
@ -576,24 +242,6 @@ fn persistent_mode() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// The cache_dir config used in the ephemeral mode tests
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum EphemeralConfig {
|
||||
/// the cache_dir config is left at its default value
|
||||
Default,
|
||||
/// the cache_dir config is set to a path in the tempdir
|
||||
MisconfiguredCacheDir,
|
||||
}
|
||||
|
||||
/// The check performed by the ephemeral mode tests
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum EphemeralCheck {
|
||||
/// an existing directory is not deleted
|
||||
ExistingDirectory,
|
||||
/// a missing directory is not created
|
||||
MissingDirectory,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ephemeral_existing_directory() -> Result<()> {
|
||||
ephemeral(EphemeralConfig::Default, EphemeralCheck::ExistingDirectory)
|
||||
|
@ -827,45 +475,6 @@ fn valid_generated_config(command: &str, expect_stdout_line_contains: &str) -> R
|
|||
Ok(())
|
||||
}
|
||||
|
||||
const TINY_CHECKPOINT_TEST_HEIGHT: Height = Height(0);
|
||||
const MEDIUM_CHECKPOINT_TEST_HEIGHT: Height =
|
||||
Height(zebra_consensus::MAX_CHECKPOINT_HEIGHT_GAP as u32);
|
||||
const LARGE_CHECKPOINT_TEST_HEIGHT: Height =
|
||||
Height((zebra_consensus::MAX_CHECKPOINT_HEIGHT_GAP * 2) as u32);
|
||||
|
||||
const STOP_AT_HEIGHT_REGEX: &str = "stopping at configured height";
|
||||
|
||||
/// The text that should be logged when the initial sync finishes at the estimated chain tip.
|
||||
///
|
||||
/// This message is only logged if:
|
||||
/// - we have reached the estimated chain tip,
|
||||
/// - we have synced all known checkpoints,
|
||||
/// - the syncer has stopped downloading lots of blocks, and
|
||||
/// - we are regularly downloading some blocks via the syncer or block gossip.
|
||||
const SYNC_FINISHED_REGEX: &str = "finished initial sync to chain tip, using gossiped blocks";
|
||||
|
||||
/// The maximum amount of time Zebra should take to reload after shutting down.
|
||||
///
|
||||
/// This should only take a second, but sometimes CI VMs or RocksDB can be slow.
|
||||
const STOP_ON_LOAD_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
/// The maximum amount of time Zebra should take to sync a few hundred blocks.
|
||||
///
|
||||
/// Usually the small checkpoint is much shorter than this.
|
||||
const TINY_CHECKPOINT_TIMEOUT: Duration = Duration::from_secs(120);
|
||||
|
||||
/// The maximum amount of time Zebra should take to sync a thousand blocks.
|
||||
const LARGE_CHECKPOINT_TIMEOUT: Duration = Duration::from_secs(180);
|
||||
|
||||
/// The test sync height where we switch to using the default lookahead limit.
|
||||
///
|
||||
/// Most tests only download a few blocks. So tests default to the minimum lookahead limit,
|
||||
/// to avoid downloading extra blocks, and slowing down the test.
|
||||
///
|
||||
/// But if we're going to be downloading lots of blocks, we use the default lookahead limit,
|
||||
/// so that the sync is faster. This can increase the RAM needed for tests.
|
||||
const MIN_HEIGHT_FOR_DEFAULT_LOOKAHEAD: Height = Height(3 * sync::DEFAULT_LOOKAHEAD_LIMIT as u32);
|
||||
|
||||
/// Test if `zebrad` can sync the first checkpoint on mainnet.
|
||||
///
|
||||
/// The first checkpoint contains a single genesis block.
|
||||
|
@ -1083,229 +692,6 @@ fn full_sync_test(network: Network, timeout_argument_name: &'static str) -> Resu
|
|||
}
|
||||
}
|
||||
|
||||
/// Sync on `network` until `zebrad` reaches `height`, or until it logs `stop_regex`.
|
||||
///
|
||||
/// If `stop_regex` is encountered before the process exits, kills the
|
||||
/// process, and mark the test as successful, even if `height` has not
|
||||
/// been reached. To disable the height limit, and just stop at `stop_regex`,
|
||||
/// use `Height::MAX` for the `height`.
|
||||
///
|
||||
/// # Test Settings
|
||||
///
|
||||
/// If `reuse_tempdir` is supplied, use it as the test's temporary directory.
|
||||
///
|
||||
/// If `height` is higher than the mandatory checkpoint,
|
||||
/// configures `zebrad` to preload the Zcash parameters.
|
||||
/// If it is lower, skips the parameter preload.
|
||||
///
|
||||
/// Configures `zebrad` to debug-enable the mempool based on `mempool_behavior`,
|
||||
/// then check the logs for the expected `mempool_behavior`.
|
||||
///
|
||||
/// If `checkpoint_sync` is true, configures `zebrad` to use as many checkpoints as possible.
|
||||
/// If it is false, only use the mandatory checkpoints.
|
||||
///
|
||||
/// If `check_legacy_chain` is true, make sure the logs contain the legacy chain check.
|
||||
///
|
||||
/// If your test environment does not have network access, skip
|
||||
/// this test by setting the `ZEBRA_SKIP_NETWORK_TESTS` env var.
|
||||
///
|
||||
/// # Test Status
|
||||
///
|
||||
/// On success, returns the associated `TempDir`. Returns an error if
|
||||
/// the child exits or `timeout` elapses before `stop_regex` is found.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn sync_until(
|
||||
height: Height,
|
||||
network: Network,
|
||||
stop_regex: &str,
|
||||
timeout: Duration,
|
||||
// Test Settings
|
||||
// TODO: turn these into an argument struct
|
||||
reuse_tempdir: impl Into<Option<TempDir>>,
|
||||
mempool_behavior: MempoolBehavior,
|
||||
checkpoint_sync: bool,
|
||||
check_legacy_chain: bool,
|
||||
) -> Result<TempDir> {
|
||||
zebra_test::init();
|
||||
|
||||
if zebra_test::net::zebra_skip_network_tests() {
|
||||
return testdir();
|
||||
}
|
||||
|
||||
let reuse_tempdir = reuse_tempdir.into();
|
||||
|
||||
// Use a persistent state, so we can handle large syncs
|
||||
let mut config = persistent_test_config()?;
|
||||
config.network.network = network;
|
||||
config.state.debug_stop_at_height = Some(height.0);
|
||||
config.mempool.debug_enable_at_height = mempool_behavior.enable_at_height();
|
||||
config.consensus.checkpoint_sync = checkpoint_sync;
|
||||
|
||||
// Download the parameters at launch, if we're going to need them later.
|
||||
if height > network.mandatory_checkpoint_height() {
|
||||
config.consensus.debug_skip_parameter_preload = false;
|
||||
}
|
||||
|
||||
// Use the default lookahead limit if we're syncing lots of blocks.
|
||||
// (Most tests use a smaller limit to minimise redundant block downloads.)
|
||||
if height > MIN_HEIGHT_FOR_DEFAULT_LOOKAHEAD {
|
||||
config.sync.lookahead_limit = sync::DEFAULT_LOOKAHEAD_LIMIT;
|
||||
}
|
||||
|
||||
let tempdir = if let Some(reuse_tempdir) = reuse_tempdir {
|
||||
reuse_tempdir.replace_config(&mut config)?
|
||||
} else {
|
||||
testdir()?.with_config(&mut config)?
|
||||
};
|
||||
|
||||
let mut child = tempdir.spawn_child(&["start"])?.with_timeout(timeout);
|
||||
|
||||
let network = format!("network: {},", network);
|
||||
|
||||
if mempool_behavior.require_activation() {
|
||||
// require that the mempool activated,
|
||||
// checking logs as they arrive
|
||||
|
||||
child.expect_stdout_line_matches(&network)?;
|
||||
|
||||
if check_legacy_chain {
|
||||
child.expect_stdout_line_matches("starting legacy chain check")?;
|
||||
child.expect_stdout_line_matches("no legacy chain found")?;
|
||||
}
|
||||
|
||||
// before the stop regex, expect mempool activation
|
||||
if mempool_behavior.require_forced_activation() {
|
||||
child.expect_stdout_line_matches("enabling mempool for debugging")?;
|
||||
}
|
||||
child.expect_stdout_line_matches("activating mempool")?;
|
||||
|
||||
// then wait for the stop log, which must happen after the mempool becomes active
|
||||
child.expect_stdout_line_matches(stop_regex)?;
|
||||
|
||||
// make sure the child process is dead
|
||||
// if it has already exited, ignore that error
|
||||
let _ = child.kill();
|
||||
|
||||
Ok(child.dir)
|
||||
} else {
|
||||
// Require that the mempool didn't activate,
|
||||
// checking the entire `zebrad` output after it exits.
|
||||
//
|
||||
// # Correctness
|
||||
//
|
||||
// Unlike the other mempool behaviours, `zebrad` must stop after logging the stop regex,
|
||||
// without being killed by [`sync_until`] test harness.
|
||||
//
|
||||
// Since it needs to collect all the output,
|
||||
// the test harness can't kill `zebrad` after it logs the `stop_regex`.
|
||||
assert!(
|
||||
height.0 < 2_000_000,
|
||||
"zebrad must exit by itself, so we can collect all the output",
|
||||
);
|
||||
let output = child.wait_with_output()?;
|
||||
|
||||
output.stdout_line_contains(&network)?;
|
||||
|
||||
if check_legacy_chain {
|
||||
output.stdout_line_contains("starting legacy chain check")?;
|
||||
output.stdout_line_contains("no legacy chain found")?;
|
||||
}
|
||||
|
||||
// check it did not activate or use the mempool
|
||||
assert!(output.stdout_line_contains("activating mempool").is_err());
|
||||
assert!(output
|
||||
.stdout_line_contains("sending mempool transaction broadcast")
|
||||
.is_err());
|
||||
|
||||
// check it logged the stop regex before exiting
|
||||
output.stdout_line_matches(stop_regex)?;
|
||||
|
||||
// check exited by itself, successfully
|
||||
output.assert_was_not_killed()?;
|
||||
let output = output.assert_success()?;
|
||||
|
||||
Ok(output.dir.expect("wait_with_output sets dir"))
|
||||
}
|
||||
}
|
||||
|
||||
fn cached_mandatory_checkpoint_test_config() -> Result<ZebradConfig> {
|
||||
let mut config = persistent_test_config()?;
|
||||
config.state.cache_dir = "/zebrad-cache".into();
|
||||
|
||||
// To get to the mandatory checkpoint, we need to sync lots of blocks.
|
||||
// (Most tests use a smaller limit to minimise redundant block downloads.)
|
||||
//
|
||||
// If we're syncing past the checkpoint with cached state, we don't need the extra lookahead.
|
||||
// But the extra downloaded blocks shouldn't slow down the test that much,
|
||||
// and testing with the defaults gives us better test coverage.
|
||||
config.sync.lookahead_limit = sync::DEFAULT_LOOKAHEAD_LIMIT;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Create or update a cached state for `network`, stopping at `height`.
|
||||
///
|
||||
/// # Test Settings
|
||||
///
|
||||
/// If `debug_skip_parameter_preload` is true, configures `zebrad` to preload the Zcash parameters.
|
||||
/// If it is false, skips the parameter preload.
|
||||
///
|
||||
/// If `checkpoint_sync` is true, configures `zebrad` to use as many checkpoints as possible.
|
||||
/// If it is false, only use the mandatory checkpoints.
|
||||
///
|
||||
/// If `check_legacy_chain` is true, make sure the logs contain the legacy chain check.
|
||||
///
|
||||
/// The test passes when `zebrad` logs the `stop_regex`.
|
||||
/// Typically this is `STOP_AT_HEIGHT_REGEX`,
|
||||
/// with an extra check for checkpoint or full validation.
|
||||
///
|
||||
/// This test ignores the `ZEBRA_SKIP_NETWORK_TESTS` env var.
|
||||
///
|
||||
/// # Test Status
|
||||
///
|
||||
/// Returns an error if the child exits or the fixed timeout elapses
|
||||
/// before `STOP_AT_HEIGHT_REGEX` is found.
|
||||
#[allow(clippy::print_stderr)]
|
||||
fn create_cached_database_height(
|
||||
network: Network,
|
||||
height: Height,
|
||||
debug_skip_parameter_preload: bool,
|
||||
checkpoint_sync: bool,
|
||||
stop_regex: &str,
|
||||
) -> Result<()> {
|
||||
eprintln!("creating cached database");
|
||||
|
||||
// 16 hours
|
||||
let timeout = Duration::from_secs(60 * 60 * 16);
|
||||
|
||||
// Use a persistent state, so we can handle large syncs
|
||||
let mut config = cached_mandatory_checkpoint_test_config()?;
|
||||
// TODO: add convenience methods?
|
||||
config.network.network = network;
|
||||
config.state.debug_stop_at_height = Some(height.0);
|
||||
config.consensus.debug_skip_parameter_preload = debug_skip_parameter_preload;
|
||||
config.consensus.checkpoint_sync = checkpoint_sync;
|
||||
|
||||
let dir = PathBuf::from("/zebrad-cache");
|
||||
let mut child = dir
|
||||
.with_exact_config(&config)?
|
||||
.spawn_child(&["start"])?
|
||||
.with_timeout(timeout)
|
||||
.bypass_test_capture(true);
|
||||
|
||||
let network = format!("network: {},", network);
|
||||
child.expect_stdout_line_matches(&network)?;
|
||||
|
||||
child.expect_stdout_line_matches("starting legacy chain check")?;
|
||||
child.expect_stdout_line_matches("no legacy chain found")?;
|
||||
|
||||
child.expect_stdout_line_matches(stop_regex)?;
|
||||
|
||||
child.kill()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_cached_database(network: Network) -> Result<()> {
|
||||
let height = network.mandatory_checkpoint_height();
|
||||
let checkpoint_stop_regex = format!("{}.*CommitFinalized request", STOP_AT_HEIGHT_REGEX);
|
||||
|
@ -1387,20 +773,6 @@ fn sync_past_mandatory_checkpoint_testnet() {
|
|||
sync_past_mandatory_checkpoint(network).unwrap();
|
||||
}
|
||||
|
||||
/// Returns the "magic" port number that tells the operating system to
|
||||
/// choose a random unallocated port.
|
||||
///
|
||||
/// The OS chooses a different port each time it opens a connection or
|
||||
/// listener with this magic port number.
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// See the usage note for `random_known_port`.
|
||||
#[allow(dead_code)]
|
||||
fn random_unallocated_port() -> u16 {
|
||||
0
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn metrics_endpoint() -> Result<()> {
|
||||
use hyper::Client;
|
||||
|
@ -1560,20 +932,18 @@ async fn rpc_endpoint() -> Result<()> {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
// [Note on port conflict](#Note on port conflict)
|
||||
let port = random_known_port();
|
||||
let endpoint = format!("127.0.0.1:{}", port);
|
||||
let url = format!("http://{}", endpoint);
|
||||
|
||||
// Write a configuration that has RPC listen_addr set
|
||||
let mut config = default_test_config()?;
|
||||
config.rpc.listen_addr = Some(endpoint.parse().unwrap());
|
||||
// [Note on port conflict](#Note on port conflict)
|
||||
let mut config = random_known_rpc_port_config()?;
|
||||
let url = format!("http://{}", config.rpc.listen_addr.unwrap());
|
||||
|
||||
let dir = testdir()?.with_config(&mut config)?;
|
||||
let mut child = dir.spawn_child(&["start"])?;
|
||||
|
||||
// Wait until port is open.
|
||||
child.expect_stdout_line_matches(format!("Opened RPC endpoint at {}", endpoint).as_str())?;
|
||||
child.expect_stdout_line_matches(
|
||||
format!("Opened RPC endpoint at {}", config.rpc.listen_addr.unwrap()).as_str(),
|
||||
)?;
|
||||
|
||||
// Create an http client
|
||||
let client = Client::new();
|
||||
|
@ -1627,48 +997,32 @@ async fn rpc_endpoint() -> Result<()> {
|
|||
#[test]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn lightwalletd_integration() -> Result<()> {
|
||||
use std::net::SocketAddr;
|
||||
|
||||
zebra_test::init();
|
||||
|
||||
// Skip the test unless we specifically asked for it
|
||||
//
|
||||
// TODO: check if the lightwalletd binary is in the PATH?
|
||||
// (this doesn't seem to be implemented in the standard library)
|
||||
if env::var("ZEBRA_TEST_LIGHTWALLETD").is_err() {
|
||||
tracing::info!(
|
||||
"skipped lightwalletd integration test, \
|
||||
set the 'ZEBRA_TEST_LIGHTWALLETD' environmental variable to run the test",
|
||||
);
|
||||
|
||||
// Skip the test unless the user specifically asked for it
|
||||
if zebra_skip_lightwalletd_tests() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Launch zebrad
|
||||
|
||||
// Write a configuration that has RPC listen_addr set
|
||||
// [Note on port conflict](#Note on port conflict)
|
||||
let listen_port = random_known_port();
|
||||
let listen_ip = "127.0.0.1".parse().expect("hard-coded IP is valid");
|
||||
let zebra_rpc_listener = SocketAddr::new(listen_ip, listen_port);
|
||||
|
||||
// Write a configuration that has the rpc listen_addr option set
|
||||
// TODO: split this config into another function?
|
||||
let mut config = default_test_config()?;
|
||||
config.rpc.listen_addr = Some(zebra_rpc_listener);
|
||||
let mut config = random_known_rpc_port_config()?;
|
||||
|
||||
let zdir = testdir()?.with_config(&mut config)?;
|
||||
let mut zebrad = zdir.spawn_child(&["start"])?.with_timeout(LAUNCH_DELAY);
|
||||
|
||||
// Wait until `zebrad` has opened the RPC endpoint
|
||||
zebrad.expect_stdout_line_matches(
|
||||
format!("Opened RPC endpoint at {}", zebra_rpc_listener).as_str(),
|
||||
format!("Opened RPC endpoint at {}", config.rpc.listen_addr.unwrap()).as_str(),
|
||||
)?;
|
||||
|
||||
// Launch lightwalletd
|
||||
|
||||
// Write a fake zcashd configuration that has the rpcbind and rpcport options set
|
||||
let ldir = testdir()?;
|
||||
let ldir = ldir.with_lightwalletd_config(zebra_rpc_listener)?;
|
||||
let ldir = ldir.with_lightwalletd_config(config.rpc.listen_addr.unwrap())?;
|
||||
|
||||
// Launch the lightwalletd process
|
||||
let result = ldir.spawn_lightwalletd_child(&[]);
|
||||
|
@ -1720,6 +1074,7 @@ fn lightwalletd_integration() -> Result<()> {
|
|||
//
|
||||
// zcash/lightwalletd exits by itself, but
|
||||
// adityapk00/lightwalletd keeps on going, so it gets killed by the test harness.
|
||||
|
||||
lightwalletd_output
|
||||
.assert_was_killed()
|
||||
.wrap_err("Possible port conflict. Are there other acceptance tests running?")?;
|
||||
|
@ -1828,15 +1183,15 @@ fn zebra_rpc_conflict() -> Result<()> {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
// Write a configuration that has RPC listen_addr set
|
||||
// [Note on port conflict](#Note on port conflict)
|
||||
let port = random_known_port();
|
||||
let listen_addr = format!("127.0.0.1:{}", port);
|
||||
let mut config = random_known_rpc_port_config()?;
|
||||
|
||||
// Write a configuration that has our created RPC listen_addr
|
||||
let mut config = default_test_config()?;
|
||||
config.rpc.listen_addr = Some(listen_addr.parse().unwrap());
|
||||
let dir1 = testdir()?.with_config(&mut config)?;
|
||||
let regex1 = regex::escape(&format!(r"Opened RPC endpoint at {}", listen_addr));
|
||||
let regex1 = regex::escape(&format!(
|
||||
r"Opened RPC endpoint at {}",
|
||||
config.rpc.listen_addr.unwrap(),
|
||||
));
|
||||
|
||||
// From another folder create a configuration with the same endpoint.
|
||||
// `rpc.listen_addr` will be the same in the 2 nodes.
|
||||
|
@ -1961,63 +1316,3 @@ where
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// What the expected behavior of the mempool is for a test that uses [`sync_until`].
|
||||
enum MempoolBehavior {
|
||||
/// The mempool should be forced to activate at a certain height, for debug purposes.
|
||||
///
|
||||
/// [`sync_until`] will kill `zebrad` after it logs mempool activation,
|
||||
/// then the `stop_regex`.
|
||||
ForceActivationAt(Height),
|
||||
|
||||
/// The mempool should be automatically activated.
|
||||
///
|
||||
/// [`sync_until`] will kill `zebrad` after it logs mempool activation,
|
||||
/// then the `stop_regex`.
|
||||
ShouldAutomaticallyActivate,
|
||||
|
||||
/// The mempool should not become active during the test.
|
||||
///
|
||||
/// # Correctness
|
||||
///
|
||||
/// Unlike the other mempool behaviours, `zebrad` must stop after logging the stop regex,
|
||||
/// without being killed by [`sync_until`] test harness.
|
||||
///
|
||||
/// Since it needs to collect all the output,
|
||||
/// the test harness can't kill `zebrad` after it logs the `stop_regex`.
|
||||
ShouldNotActivate,
|
||||
}
|
||||
|
||||
impl MempoolBehavior {
|
||||
/// Return the height value that the mempool should be enabled at, if available.
|
||||
pub fn enable_at_height(&self) -> Option<u32> {
|
||||
match self {
|
||||
MempoolBehavior::ForceActivationAt(height) => Some(height.0),
|
||||
MempoolBehavior::ShouldAutomaticallyActivate | MempoolBehavior::ShouldNotActivate => {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the mempool should activate,
|
||||
/// either by forced or automatic activation.
|
||||
pub fn require_activation(&self) -> bool {
|
||||
self.require_forced_activation() || self.require_automatic_activation()
|
||||
}
|
||||
|
||||
/// Returns `true` if the mempool should be forcefully activated at a specified height.
|
||||
pub fn require_forced_activation(&self) -> bool {
|
||||
matches!(self, MempoolBehavior::ForceActivationAt(_))
|
||||
}
|
||||
|
||||
/// Returns `true` if the mempool should automatically activate.
|
||||
pub fn require_automatic_activation(&self) -> bool {
|
||||
matches!(self, MempoolBehavior::ShouldAutomaticallyActivate)
|
||||
}
|
||||
|
||||
/// Returns `true` if the mempool should not activate.
|
||||
#[allow(dead_code)]
|
||||
pub fn require_no_activation(&self) -> bool {
|
||||
matches!(self, MempoolBehavior::ShouldNotActivate)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
//! Shared checks for the `zebrad` acceptance tests.
|
||||
//!
|
||||
//! # Warning
|
||||
//!
|
||||
//! Test functions in this file will not be run.
|
||||
//! This file is only for test library code.
|
||||
|
||||
/// The cache_dir config used in the ephemeral mode tests
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum EphemeralConfig {
|
||||
/// the cache_dir config is left at its default value
|
||||
Default,
|
||||
/// the cache_dir config is set to a path in the tempdir
|
||||
MisconfiguredCacheDir,
|
||||
}
|
||||
|
||||
/// The check performed by the ephemeral mode tests
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum EphemeralCheck {
|
||||
/// an existing directory is not deleted
|
||||
ExistingDirectory,
|
||||
/// a missing directory is not created
|
||||
MissingDirectory,
|
||||
}
|
||||
|
||||
/// Is `s` a valid `zebrad` version string?
|
||||
///
|
||||
/// Trims whitespace before parsing the version.
|
||||
///
|
||||
/// Returns false if the version is invalid, or if there is anything else on the
|
||||
/// line that contains the version. In particular, this check will fail if `s`
|
||||
/// includes any terminal formatting.
|
||||
pub fn is_zebrad_version(s: &str) -> bool {
|
||||
semver::Version::parse(s.replace("zebrad", "").trim()).is_ok()
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
//! `zebrad` config-specific shared code for the `zebrad` acceptance tests.
|
||||
//!
|
||||
//! # Warning
|
||||
//!
|
||||
//! Test functions in this file will not be run.
|
||||
//! This file is only for test library code.
|
||||
|
||||
use std::{env, time::Duration};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use zebrad::{
|
||||
components::{mempool, sync},
|
||||
config::{SyncSection, TracingSection, ZebradConfig},
|
||||
};
|
||||
|
||||
/// Returns a config with:
|
||||
/// - a Zcash listener on an unused port on IPv4 localhost, and
|
||||
/// - an ephemeral state,
|
||||
/// - the minimum syncer lookahead limit, and
|
||||
/// - shorter task intervals, to improve test coverage.
|
||||
pub fn default_test_config() -> Result<ZebradConfig> {
|
||||
const TEST_DURATION: Duration = Duration::from_secs(30);
|
||||
|
||||
let network = zebra_network::Config {
|
||||
// The OS automatically chooses an unused port.
|
||||
listen_addr: "127.0.0.1:0".parse()?,
|
||||
crawl_new_peer_interval: TEST_DURATION,
|
||||
..zebra_network::Config::default()
|
||||
};
|
||||
|
||||
let sync = SyncSection {
|
||||
// Avoid downloading unnecessary blocks.
|
||||
lookahead_limit: sync::MIN_LOOKAHEAD_LIMIT,
|
||||
..SyncSection::default()
|
||||
};
|
||||
|
||||
let mempool = mempool::Config {
|
||||
eviction_memory_time: TEST_DURATION,
|
||||
..mempool::Config::default()
|
||||
};
|
||||
|
||||
let consensus = zebra_consensus::Config {
|
||||
debug_skip_parameter_preload: true,
|
||||
..zebra_consensus::Config::default()
|
||||
};
|
||||
|
||||
let force_use_color = !matches!(
|
||||
env::var("ZEBRA_FORCE_USE_COLOR"),
|
||||
Err(env::VarError::NotPresent)
|
||||
);
|
||||
let tracing = TracingSection {
|
||||
force_use_color,
|
||||
..TracingSection::default()
|
||||
};
|
||||
|
||||
let config = ZebradConfig {
|
||||
network,
|
||||
state: zebra_state::Config::ephemeral(),
|
||||
sync,
|
||||
mempool,
|
||||
consensus,
|
||||
tracing,
|
||||
..ZebradConfig::default()
|
||||
};
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn persistent_test_config() -> Result<ZebradConfig> {
|
||||
let mut config = default_test_config()?;
|
||||
config.state.ephemeral = false;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn testdir() -> Result<TempDir> {
|
||||
tempfile::Builder::new()
|
||||
.prefix("zebrad_tests")
|
||||
.tempdir()
|
||||
.map_err(Into::into)
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
//! `zebrad` launch-specific shared code for the `zebrad` acceptance tests.
|
||||
//!
|
||||
//! # Warning
|
||||
//!
|
||||
//! Test functions in this file will not be run.
|
||||
//! This file is only for test library code.
|
||||
|
||||
use std::{env, path::Path, time::Duration};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
|
||||
use zebrad::config::ZebradConfig;
|
||||
|
||||
use zebra_test::{command::TestDirExt, prelude::*};
|
||||
|
||||
/// After we launch `zebrad`, wait this long for the command to start up,
|
||||
/// take the actions expected by the tests, and log the expected logs.
|
||||
///
|
||||
/// Previously, this value was 3 seconds, which caused rare
|
||||
/// metrics or tracing test failures in Windows CI.
|
||||
pub const LAUNCH_DELAY: Duration = Duration::from_secs(15);
|
||||
|
||||
/// After we launch `lightwalletd`, wait this long for the command to start up,
|
||||
/// take the actions expected by the tests, and log the expected logs.
|
||||
///
|
||||
/// `lightwalletd`'s actions also depend on the actions of the `zebrad` instance
|
||||
/// it is using for its RPCs.
|
||||
pub const LIGHTWALLETD_DELAY: Duration = Duration::from_secs(60);
|
||||
|
||||
/// The amount of time we wait between launching two
|
||||
/// conflicting nodes.
|
||||
pub const BETWEEN_NODES_DELAY: Duration = Duration::from_secs(2);
|
||||
|
||||
/// Extension trait for methods on `tempfile::TempDir` for using it as a test
|
||||
/// directory for `zebrad`.
|
||||
pub trait ZebradTestDirExt
|
||||
where
|
||||
Self: AsRef<Path> + Sized,
|
||||
{
|
||||
// Zebra methods
|
||||
|
||||
/// Spawn `zebrad` with `args` as a child process in this test directory,
|
||||
/// potentially taking ownership of the tempdir for the duration of the
|
||||
/// child process.
|
||||
///
|
||||
/// If there is a config in the test directory, pass it to `zebrad`.
|
||||
fn spawn_child(self, args: &[&str]) -> Result<TestChild<Self>>;
|
||||
|
||||
/// Create a config file and use it for all subsequently spawned `zebrad` processes.
|
||||
/// Returns an error if the config already exists.
|
||||
///
|
||||
/// If needed:
|
||||
/// - recursively create directories for the config and state
|
||||
/// - set `config.cache_dir` based on `self`
|
||||
fn with_config(self, config: &mut ZebradConfig) -> Result<Self>;
|
||||
|
||||
/// Create a config file with the exact contents of `config`, and use it for
|
||||
/// all subsequently spawned `zebrad` processes. Returns an error if the config
|
||||
/// already exists.
|
||||
///
|
||||
/// If needed:
|
||||
/// - recursively create directories for the config and state
|
||||
fn with_exact_config(self, config: &ZebradConfig) -> Result<Self>;
|
||||
|
||||
/// Overwrite any existing `zebrad` config file, and use the newly written config for
|
||||
/// all subsequently spawned processes.
|
||||
///
|
||||
/// If needed:
|
||||
/// - recursively create directories for the config and state
|
||||
/// - set `config.cache_dir` based on `self`
|
||||
fn replace_config(self, config: &mut ZebradConfig) -> Result<Self>;
|
||||
|
||||
/// `cache_dir` config update helper for `zebrad`.
|
||||
///
|
||||
/// If needed:
|
||||
/// - set the cache_dir in the config.
|
||||
fn cache_config_update_helper(self, config: &mut ZebradConfig) -> Result<Self>;
|
||||
|
||||
/// Config writing helper for `zebrad`.
|
||||
///
|
||||
/// If needed:
|
||||
/// - recursively create directories for the config and state,
|
||||
///
|
||||
/// Then write out the config.
|
||||
fn write_config_helper(self, config: &ZebradConfig) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl<T> ZebradTestDirExt for T
|
||||
where
|
||||
Self: TestDirExt + AsRef<Path> + Sized,
|
||||
{
|
||||
fn spawn_child(self, args: &[&str]) -> Result<TestChild<Self>> {
|
||||
let dir = self.as_ref();
|
||||
let default_config_path = dir.join("zebrad.toml");
|
||||
|
||||
if default_config_path.exists() {
|
||||
let mut extra_args: Vec<_> = vec![
|
||||
"-c",
|
||||
default_config_path
|
||||
.as_path()
|
||||
.to_str()
|
||||
.expect("Path is valid Unicode"),
|
||||
];
|
||||
extra_args.extend_from_slice(args);
|
||||
self.spawn_child_with_command(env!("CARGO_BIN_EXE_zebrad"), &extra_args)
|
||||
} else {
|
||||
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;
|
||||
|
||||
// Remove any existing config before writing a new one
|
||||
let dir = self.as_ref();
|
||||
let config_file = dir.join("zebrad.toml");
|
||||
match fs::remove_file(config_file) {
|
||||
Ok(()) => {}
|
||||
// If the config file doesn't exist, that's 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> {
|
||||
if !config.state.ephemeral {
|
||||
let dir = self.as_ref();
|
||||
let cache_dir = dir.join("state");
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Panics if `$pred` is false, with an error report containing:
|
||||
/// * context from `$source`, and
|
||||
/// * an optional wrapper error, using `$fmt_arg`+ as a format string and
|
||||
/// arguments.
|
||||
#[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);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
//! `lightwalletd`-specific shared code for the `zebrad` acceptance tests.
|
||||
//!
|
||||
//! # Warning
|
||||
//!
|
||||
//! Test functions in this file will not be run.
|
||||
//! This file is only for test library code.
|
||||
|
||||
use std::{env, net::SocketAddr, path::Path};
|
||||
|
||||
use zebra_test::{
|
||||
command::{TestChild, TestDirExt},
|
||||
net::random_known_port,
|
||||
prelude::*,
|
||||
};
|
||||
use zebrad::config::ZebradConfig;
|
||||
|
||||
use super::{config::default_test_config, launch::ZebradTestDirExt};
|
||||
|
||||
/// The name of the env var that enables Zebra lightwalletd integration tests.
|
||||
/// These tests need a `lightwalletd` binary in the test machine's path.
|
||||
///
|
||||
/// We use a constant so that the compiler detects typos.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This environmental variable is used to enable the lightwalletd tests.
|
||||
/// But the network tests are *disabled* by their environmental variables.
|
||||
const ZEBRA_TEST_LIGHTWALLETD: &str = "ZEBRA_TEST_LIGHTWALLETD";
|
||||
|
||||
/// Should we skip Zebra lightwalletd integration tests?
|
||||
#[allow(clippy::print_stderr)]
|
||||
pub fn zebra_skip_lightwalletd_tests() -> bool {
|
||||
// TODO: check if the lightwalletd binary is in the PATH?
|
||||
// (this doesn't seem to be implemented in the standard library)
|
||||
//
|
||||
// See is_command_available in zebra-test/tests/command.rs for one way to do this.
|
||||
|
||||
if env::var_os(ZEBRA_TEST_LIGHTWALLETD).is_none() {
|
||||
// This message is captured by the test runner, use
|
||||
// `cargo test -- --nocapture` to see it.
|
||||
eprintln!(
|
||||
"Skipped lightwalletd integration test, \
|
||||
set the 'ZEBRA_TEST_LIGHTWALLETD' environmental variable to run the test",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns a `zebrad` config with a random known RPC port.
|
||||
pub fn random_known_rpc_port_config() -> Result<ZebradConfig> {
|
||||
// [Note on port conflict](#Note on port conflict)
|
||||
let listen_port = random_known_port();
|
||||
let listen_ip = "127.0.0.1".parse().expect("hard-coded IP is valid");
|
||||
let zebra_rpc_listener = SocketAddr::new(listen_ip, listen_port);
|
||||
|
||||
// Write a configuration that has the rpc listen_addr option set
|
||||
// TODO: split this config into another function?
|
||||
let mut config = default_test_config()?;
|
||||
config.rpc.listen_addr = Some(zebra_rpc_listener);
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Extension trait for methods on `tempfile::TempDir` for using it as a test
|
||||
/// directory for `zebrad`.
|
||||
pub trait LightWalletdTestDirExt: ZebradTestDirExt
|
||||
where
|
||||
Self: AsRef<Path> + Sized,
|
||||
{
|
||||
/// Spawn `lightwalletd` with `args` as a child process in this test directory,
|
||||
/// potentially taking ownership of the tempdir for the duration of the
|
||||
/// child process.
|
||||
///
|
||||
/// By default, launch a working test instance with logging, and avoid port conflicts.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If there is no lightwalletd config in the test directory.
|
||||
fn spawn_lightwalletd_child(self, args: &[&str]) -> Result<TestChild<Self>>;
|
||||
|
||||
/// Create a config file and use it for all subsequently spawned `lightwalletd` processes.
|
||||
/// Returns an error if the config already exists.
|
||||
///
|
||||
/// If needed:
|
||||
/// - recursively create directories for the config
|
||||
fn with_lightwalletd_config(self, zebra_rpc_listener: SocketAddr) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl<T> LightWalletdTestDirExt for T
|
||||
where
|
||||
Self: TestDirExt + AsRef<Path> + Sized,
|
||||
{
|
||||
fn spawn_lightwalletd_child(self, extra_args: &[&str]) -> Result<TestChild<Self>> {
|
||||
let dir = self.as_ref().to_owned();
|
||||
let default_config_path = dir.join("lightwalletd-zcash.conf");
|
||||
|
||||
assert!(
|
||||
default_config_path.exists(),
|
||||
"lightwalletd requires a config"
|
||||
);
|
||||
|
||||
// By default, launch a working test instance with logging,
|
||||
// and avoid port conflicts.
|
||||
let mut args: Vec<_> = vec![
|
||||
// the fake zcashd conf we just wrote
|
||||
"--zcash-conf-path",
|
||||
default_config_path
|
||||
.as_path()
|
||||
.to_str()
|
||||
.expect("Path is valid Unicode"),
|
||||
// the lightwalletd cache directory
|
||||
//
|
||||
// TODO: create a sub-directory for lightwalletd
|
||||
"--data-dir",
|
||||
dir.to_str().expect("Path is valid Unicode"),
|
||||
// log to standard output
|
||||
//
|
||||
// TODO: if lightwalletd needs to run on Windows,
|
||||
// work out how to log to the terminal on all platforms
|
||||
"--log-file",
|
||||
"/dev/stdout",
|
||||
// let the OS choose a random available wallet client port
|
||||
"--grpc-bind-addr",
|
||||
"127.0.0.1:0",
|
||||
"--http-bind-addr",
|
||||
"127.0.0.1:0",
|
||||
// don't require a TLS certificate for the HTTP server
|
||||
"--no-tls-very-insecure",
|
||||
];
|
||||
args.extend_from_slice(extra_args);
|
||||
|
||||
self.spawn_child_with_command("lightwalletd", &args)
|
||||
}
|
||||
|
||||
fn with_lightwalletd_config(self, zebra_rpc_listener: SocketAddr) -> Result<Self> {
|
||||
use std::fs;
|
||||
|
||||
let lightwalletd_config = format!(
|
||||
"\
|
||||
rpcbind={}\n\
|
||||
rpcport={}\n\
|
||||
",
|
||||
zebra_rpc_listener.ip(),
|
||||
zebra_rpc_listener.port(),
|
||||
);
|
||||
|
||||
let dir = self.as_ref();
|
||||
fs::create_dir_all(dir)?;
|
||||
|
||||
let config_file = dir.join("lightwalletd-zcash.conf");
|
||||
fs::write(config_file, lightwalletd_config.as_bytes())?;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
//! Shared code for the `zebrad` acceptance tests.
|
||||
//!
|
||||
//! # Warning
|
||||
//!
|
||||
//! Test functions in this file and its submodules will not be run.
|
||||
//! This file is only for test library code.
|
||||
//!
|
||||
//! This module uses the legacy directory structure,
|
||||
//! to avoid compiling an empty "common" test binary:
|
||||
//! https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-tests
|
||||
|
||||
pub mod check;
|
||||
pub mod config;
|
||||
pub mod launch;
|
||||
pub mod lightwalletd;
|
||||
pub mod sync;
|
|
@ -0,0 +1,345 @@
|
|||
//! `zebrad` sync-specific shared code for the `zebrad` acceptance tests.
|
||||
//!
|
||||
//! # Warning
|
||||
//!
|
||||
//! Test functions in this file will not be run.
|
||||
//! This file is only for test library code.
|
||||
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use zebra_chain::{block::Height, parameters::Network};
|
||||
use zebrad::{components::sync, config::ZebradConfig};
|
||||
|
||||
use zebra_test::prelude::*;
|
||||
|
||||
use super::{
|
||||
config::{persistent_test_config, testdir},
|
||||
launch::ZebradTestDirExt,
|
||||
};
|
||||
|
||||
pub const TINY_CHECKPOINT_TEST_HEIGHT: Height = Height(0);
|
||||
pub const MEDIUM_CHECKPOINT_TEST_HEIGHT: Height =
|
||||
Height(zebra_consensus::MAX_CHECKPOINT_HEIGHT_GAP as u32);
|
||||
pub const LARGE_CHECKPOINT_TEST_HEIGHT: Height =
|
||||
Height((zebra_consensus::MAX_CHECKPOINT_HEIGHT_GAP * 2) as u32);
|
||||
|
||||
pub const STOP_AT_HEIGHT_REGEX: &str = "stopping at configured height";
|
||||
|
||||
/// The text that should be logged when the initial sync finishes at the estimated chain tip.
|
||||
///
|
||||
/// This message is only logged if:
|
||||
/// - we have reached the estimated chain tip,
|
||||
/// - we have synced all known checkpoints,
|
||||
/// - the syncer has stopped downloading lots of blocks, and
|
||||
/// - we are regularly downloading some blocks via the syncer or block gossip.
|
||||
pub const SYNC_FINISHED_REGEX: &str = "finished initial sync to chain tip, using gossiped blocks";
|
||||
|
||||
/// The maximum amount of time Zebra should take to reload after shutting down.
|
||||
///
|
||||
/// This should only take a second, but sometimes CI VMs or RocksDB can be slow.
|
||||
pub const STOP_ON_LOAD_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
/// The maximum amount of time Zebra should take to sync a few hundred blocks.
|
||||
///
|
||||
/// Usually the small checkpoint is much shorter than this.
|
||||
pub const TINY_CHECKPOINT_TIMEOUT: Duration = Duration::from_secs(120);
|
||||
|
||||
/// The maximum amount of time Zebra should take to sync a thousand blocks.
|
||||
pub const LARGE_CHECKPOINT_TIMEOUT: Duration = Duration::from_secs(180);
|
||||
|
||||
/// The test sync height where we switch to using the default lookahead limit.
|
||||
///
|
||||
/// Most tests only download a few blocks. So tests default to the minimum lookahead limit,
|
||||
/// to avoid downloading extra blocks, and slowing down the test.
|
||||
///
|
||||
/// But if we're going to be downloading lots of blocks, we use the default lookahead limit,
|
||||
/// so that the sync is faster. This can increase the RAM needed for tests.
|
||||
pub const MIN_HEIGHT_FOR_DEFAULT_LOOKAHEAD: Height =
|
||||
Height(3 * sync::DEFAULT_LOOKAHEAD_LIMIT as u32);
|
||||
|
||||
/// What the expected behavior of the mempool is for a test that uses [`sync_until`].
|
||||
pub enum MempoolBehavior {
|
||||
/// The mempool should be forced to activate at a certain height, for debug purposes.
|
||||
///
|
||||
/// [`sync_until`] will kill `zebrad` after it logs mempool activation,
|
||||
/// then the `stop_regex`.
|
||||
ForceActivationAt(Height),
|
||||
|
||||
/// The mempool should be automatically activated.
|
||||
///
|
||||
/// [`sync_until`] will kill `zebrad` after it logs mempool activation,
|
||||
/// then the `stop_regex`.
|
||||
ShouldAutomaticallyActivate,
|
||||
|
||||
/// The mempool should not become active during the test.
|
||||
///
|
||||
/// # Correctness
|
||||
///
|
||||
/// Unlike the other mempool behaviours, `zebrad` must stop after logging the stop regex,
|
||||
/// without being killed by [`sync_until`] test harness.
|
||||
///
|
||||
/// Since it needs to collect all the output,
|
||||
/// the test harness can't kill `zebrad` after it logs the `stop_regex`.
|
||||
ShouldNotActivate,
|
||||
}
|
||||
|
||||
impl MempoolBehavior {
|
||||
/// Return the height value that the mempool should be enabled at, if available.
|
||||
pub fn enable_at_height(&self) -> Option<u32> {
|
||||
match self {
|
||||
MempoolBehavior::ForceActivationAt(height) => Some(height.0),
|
||||
MempoolBehavior::ShouldAutomaticallyActivate | MempoolBehavior::ShouldNotActivate => {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the mempool should activate,
|
||||
/// either by forced or automatic activation.
|
||||
pub fn require_activation(&self) -> bool {
|
||||
self.require_forced_activation() || self.require_automatic_activation()
|
||||
}
|
||||
|
||||
/// Returns `true` if the mempool should be forcefully activated at a specified height.
|
||||
pub fn require_forced_activation(&self) -> bool {
|
||||
matches!(self, MempoolBehavior::ForceActivationAt(_))
|
||||
}
|
||||
|
||||
/// Returns `true` if the mempool should automatically activate.
|
||||
pub fn require_automatic_activation(&self) -> bool {
|
||||
matches!(self, MempoolBehavior::ShouldAutomaticallyActivate)
|
||||
}
|
||||
|
||||
/// Returns `true` if the mempool should not activate.
|
||||
#[allow(dead_code)]
|
||||
pub fn require_no_activation(&self) -> bool {
|
||||
matches!(self, MempoolBehavior::ShouldNotActivate)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync on `network` until `zebrad` reaches `height`, or until it logs `stop_regex`.
|
||||
///
|
||||
/// If `stop_regex` is encountered before the process exits, kills the
|
||||
/// process, and mark the test as successful, even if `height` has not
|
||||
/// been reached. To disable the height limit, and just stop at `stop_regex`,
|
||||
/// use `Height::MAX` for the `height`.
|
||||
///
|
||||
/// # Test Settings
|
||||
///
|
||||
/// If `reuse_tempdir` is supplied, use it as the test's temporary directory.
|
||||
///
|
||||
/// If `height` is higher than the mandatory checkpoint,
|
||||
/// configures `zebrad` to preload the Zcash parameters.
|
||||
/// If it is lower, skips the parameter preload.
|
||||
///
|
||||
/// Configures `zebrad` to debug-enable the mempool based on `mempool_behavior`,
|
||||
/// then check the logs for the expected `mempool_behavior`.
|
||||
///
|
||||
/// If `checkpoint_sync` is true, configures `zebrad` to use as many checkpoints as possible.
|
||||
/// If it is false, only use the mandatory checkpoints.
|
||||
///
|
||||
/// If `check_legacy_chain` is true, make sure the logs contain the legacy chain check.
|
||||
///
|
||||
/// If your test environment does not have network access, skip
|
||||
/// this test by setting the `ZEBRA_SKIP_NETWORK_TESTS` env var.
|
||||
///
|
||||
/// # Test Status
|
||||
///
|
||||
/// On success, returns the associated `TempDir`. Returns an error if
|
||||
/// the child exits or `timeout` elapses before `stop_regex` is found.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn sync_until(
|
||||
height: Height,
|
||||
network: Network,
|
||||
stop_regex: &str,
|
||||
timeout: Duration,
|
||||
// Test Settings
|
||||
// TODO: turn these into an argument struct
|
||||
reuse_tempdir: impl Into<Option<TempDir>>,
|
||||
mempool_behavior: MempoolBehavior,
|
||||
checkpoint_sync: bool,
|
||||
check_legacy_chain: bool,
|
||||
) -> Result<TempDir> {
|
||||
zebra_test::init();
|
||||
|
||||
if zebra_test::net::zebra_skip_network_tests() {
|
||||
return testdir();
|
||||
}
|
||||
|
||||
let reuse_tempdir = reuse_tempdir.into();
|
||||
|
||||
// Use a persistent state, so we can handle large syncs
|
||||
let mut config = persistent_test_config()?;
|
||||
config.network.network = network;
|
||||
config.state.debug_stop_at_height = Some(height.0);
|
||||
config.mempool.debug_enable_at_height = mempool_behavior.enable_at_height();
|
||||
config.consensus.checkpoint_sync = checkpoint_sync;
|
||||
|
||||
// Download the parameters at launch, if we're going to need them later.
|
||||
if height > network.mandatory_checkpoint_height() {
|
||||
config.consensus.debug_skip_parameter_preload = false;
|
||||
}
|
||||
|
||||
// Use the default lookahead limit if we're syncing lots of blocks.
|
||||
// (Most tests use a smaller limit to minimise redundant block downloads.)
|
||||
if height > MIN_HEIGHT_FOR_DEFAULT_LOOKAHEAD {
|
||||
config.sync.lookahead_limit = sync::DEFAULT_LOOKAHEAD_LIMIT;
|
||||
}
|
||||
|
||||
let tempdir = if let Some(reuse_tempdir) = reuse_tempdir {
|
||||
reuse_tempdir.replace_config(&mut config)?
|
||||
} else {
|
||||
testdir()?.with_config(&mut config)?
|
||||
};
|
||||
|
||||
let mut child = tempdir.spawn_child(&["start"])?.with_timeout(timeout);
|
||||
|
||||
let network = format!("network: {},", network);
|
||||
|
||||
if mempool_behavior.require_activation() {
|
||||
// require that the mempool activated,
|
||||
// checking logs as they arrive
|
||||
|
||||
child.expect_stdout_line_matches(&network)?;
|
||||
|
||||
if check_legacy_chain {
|
||||
child.expect_stdout_line_matches("starting legacy chain check")?;
|
||||
child.expect_stdout_line_matches("no legacy chain found")?;
|
||||
}
|
||||
|
||||
// before the stop regex, expect mempool activation
|
||||
if mempool_behavior.require_forced_activation() {
|
||||
child.expect_stdout_line_matches("enabling mempool for debugging")?;
|
||||
}
|
||||
child.expect_stdout_line_matches("activating mempool")?;
|
||||
|
||||
// then wait for the stop log, which must happen after the mempool becomes active
|
||||
child.expect_stdout_line_matches(stop_regex)?;
|
||||
|
||||
// make sure the child process is dead
|
||||
// if it has already exited, ignore that error
|
||||
let _ = child.kill();
|
||||
|
||||
Ok(child.dir)
|
||||
} else {
|
||||
// Require that the mempool didn't activate,
|
||||
// checking the entire `zebrad` output after it exits.
|
||||
//
|
||||
// # Correctness
|
||||
//
|
||||
// Unlike the other mempool behaviours, `zebrad` must stop after logging the stop regex,
|
||||
// without being killed by [`sync_until`] test harness.
|
||||
//
|
||||
// Since it needs to collect all the output,
|
||||
// the test harness can't kill `zebrad` after it logs the `stop_regex`.
|
||||
assert!(
|
||||
height.0 < 2_000_000,
|
||||
"zebrad must exit by itself, so we can collect all the output",
|
||||
);
|
||||
let output = child.wait_with_output()?;
|
||||
|
||||
output.stdout_line_contains(&network)?;
|
||||
|
||||
if check_legacy_chain {
|
||||
output.stdout_line_contains("starting legacy chain check")?;
|
||||
output.stdout_line_contains("no legacy chain found")?;
|
||||
}
|
||||
|
||||
// check it did not activate or use the mempool
|
||||
assert!(output.stdout_line_contains("activating mempool").is_err());
|
||||
assert!(output
|
||||
.stdout_line_contains("sending mempool transaction broadcast")
|
||||
.is_err());
|
||||
|
||||
// check it logged the stop regex before exiting
|
||||
output.stdout_line_matches(stop_regex)?;
|
||||
|
||||
// check exited by itself, successfully
|
||||
output.assert_was_not_killed()?;
|
||||
let output = output.assert_success()?;
|
||||
|
||||
Ok(output.dir.expect("wait_with_output sets dir"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a test config for caching Zebra's state up to the mandatory checkpoint.
|
||||
pub fn cached_mandatory_checkpoint_test_config() -> Result<ZebradConfig> {
|
||||
let mut config = persistent_test_config()?;
|
||||
config.state.cache_dir = "/zebrad-cache".into();
|
||||
|
||||
// To get to the mandatory checkpoint, we need to sync lots of blocks.
|
||||
// (Most tests use a smaller limit to minimise redundant block downloads.)
|
||||
//
|
||||
// If we're syncing past the checkpoint with cached state, we don't need the extra lookahead.
|
||||
// But the extra downloaded blocks shouldn't slow down the test that much,
|
||||
// and testing with the defaults gives us better test coverage.
|
||||
config.sync.lookahead_limit = sync::DEFAULT_LOOKAHEAD_LIMIT;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Create or update a cached state for `network`, stopping at `height`.
|
||||
///
|
||||
/// # Test Settings
|
||||
///
|
||||
/// If `debug_skip_parameter_preload` is true, configures `zebrad` to preload the Zcash parameters.
|
||||
/// If it is false, skips the parameter preload.
|
||||
///
|
||||
/// If `checkpoint_sync` is true, configures `zebrad` to use as many checkpoints as possible.
|
||||
/// If it is false, only use the mandatory checkpoints.
|
||||
///
|
||||
/// If `check_legacy_chain` is true, make sure the logs contain the legacy chain check.
|
||||
///
|
||||
/// The test passes when `zebrad` logs the `stop_regex`.
|
||||
/// Typically this is `STOP_AT_HEIGHT_REGEX`,
|
||||
/// with an extra check for checkpoint or full validation.
|
||||
///
|
||||
/// This test ignores the `ZEBRA_SKIP_NETWORK_TESTS` env var.
|
||||
///
|
||||
/// # Test Status
|
||||
///
|
||||
/// Returns an error if the child exits or the fixed timeout elapses
|
||||
/// before `STOP_AT_HEIGHT_REGEX` is found.
|
||||
#[allow(clippy::print_stderr)]
|
||||
pub fn create_cached_database_height(
|
||||
network: Network,
|
||||
height: Height,
|
||||
debug_skip_parameter_preload: bool,
|
||||
checkpoint_sync: bool,
|
||||
stop_regex: &str,
|
||||
) -> Result<()> {
|
||||
eprintln!("creating cached database");
|
||||
|
||||
// 16 hours
|
||||
let timeout = Duration::from_secs(60 * 60 * 16);
|
||||
|
||||
// Use a persistent state, so we can handle large syncs
|
||||
let mut config = cached_mandatory_checkpoint_test_config()?;
|
||||
// TODO: add convenience methods?
|
||||
config.network.network = network;
|
||||
config.state.debug_stop_at_height = Some(height.0);
|
||||
config.consensus.debug_skip_parameter_preload = debug_skip_parameter_preload;
|
||||
config.consensus.checkpoint_sync = checkpoint_sync;
|
||||
|
||||
let dir = PathBuf::from("/zebrad-cache");
|
||||
let mut child = dir
|
||||
.with_exact_config(&config)?
|
||||
.spawn_child(&["start"])?
|
||||
.with_timeout(timeout)
|
||||
.bypass_test_capture(true);
|
||||
|
||||
let network = format!("network: {},", network);
|
||||
child.expect_stdout_line_matches(&network)?;
|
||||
|
||||
child.expect_stdout_line_matches("starting legacy chain check")?;
|
||||
child.expect_stdout_line_matches("no legacy chain found")?;
|
||||
|
||||
child.expect_stdout_line_matches(stop_regex)?;
|
||||
|
||||
child.kill()?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue