diff --git a/Cargo.lock b/Cargo.lock index f6e4dc28..5d836902 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ dependencies = [ "once_cell", "regex", "secrecy", - "semver", + "semver 0.9.0", "serde", "signal-hook", "termcolor", @@ -2954,7 +2954,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", ] [[package]] @@ -3064,6 +3064,12 @@ dependencies = [ "serde", ] +[[package]] +name = "semver" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f3aac57ee7f3272d8395c6e4f502f434f0e289fcd62876f70daa008c20dcabe" + [[package]] name = "semver-parser" version = "0.7.0" @@ -4622,6 +4628,8 @@ dependencies = [ "once_cell", "pin-project 0.4.27", "rand 0.8.1", + "regex", + "semver 1.0.3", "sentry", "sentry-tracing", "serde", diff --git a/zebra-network/src/constants.rs b/zebra-network/src/constants.rs index 81a1796f..c693fc1a 100644 --- a/zebra-network/src/constants.rs +++ b/zebra-network/src/constants.rs @@ -133,7 +133,7 @@ lazy_static! { /// OS-specific error when the port attempting to be opened is already in use. pub static ref PORT_IN_USE_ERROR: Regex = if cfg!(unix) { #[allow(clippy::trivial_regex)] - Regex::new("already in use") + Regex::new(®ex::escape("already in use")) } else { Regex::new("(access a socket in a way forbidden by its access permissions)|(Only one usage of each socket address)") }.expect("regex is valid"); diff --git a/zebra-test/src/command.rs b/zebra-test/src/command.rs index eadd4095..fb0f038a 100644 --- a/zebra-test/src/command.rs +++ b/zebra-test/src/command.rs @@ -10,7 +10,7 @@ use tracing::instrument; use std::os::unix::process::ExitStatusExt; use std::{ convert::Infallible as NoDir, - fmt::Write as _, + fmt::{self, Write as _}, io::BufRead, io::{BufReader, Lines, Read}, path::Path, @@ -194,7 +194,7 @@ impl TestChild { }) } - /// Set a timeout for `expect_stdout` or `expect_stderr`. + /// Set a timeout for `expect_stdout_line_matches` or `expect_stderr_line_matches`. /// /// Does not apply to `wait_with_output`. pub fn with_timeout(mut self, timeout: Duration) -> Self { @@ -215,7 +215,7 @@ impl TestChild { /// Kills the child after the configured timeout has elapsed. /// See `expect_line_matching` for details. #[instrument(skip(self))] - pub fn expect_stdout(&mut self, regex: &str) -> Result<&mut Self> { + pub fn expect_stdout_line_matches(&mut self, regex: &str) -> Result<&mut Self> { if self.stdout.is_none() { self.stdout = self .child @@ -228,7 +228,7 @@ impl TestChild { let mut lines = self .stdout .take() - .expect("child must capture stdout to call expect_stdout"); + .expect("child must capture stdout to call expect_stdout_line_matches"); match self.expect_line_matching(&mut lines, regex, "stdout") { Ok(()) => { @@ -245,7 +245,7 @@ impl TestChild { /// Kills the child after the configured timeout has elapsed. /// See `expect_line_matching` for details. #[instrument(skip(self))] - pub fn expect_stderr(&mut self, regex: &str) -> Result<&mut Self> { + pub fn expect_stderr_line_matches(&mut self, regex: &str) -> Result<&mut Self> { if self.stderr.is_none() { self.stderr = self .child @@ -258,7 +258,7 @@ impl TestChild { let mut lines = self .stderr .take() - .expect("child must capture stderr to call expect_stderr"); + .expect("child must capture stderr to call expect_stderr_line_matches"); match self.expect_line_matching(&mut lines, regex, "stderr") { Ok(()) => { @@ -399,94 +399,181 @@ impl TestOutput { Ok(self) } - #[instrument(skip(self))] - pub fn stdout_contains(&self, regex: &str) -> Result<&Self> { - let re = regex::Regex::new(regex)?; - let stdout = String::from_utf8_lossy(&self.output.stdout); + /// Checks the output of a command, using a closure to determine if the + /// output is valid. + /// + /// If the closure returns `true`, the check returns `Ok(self)`. + /// If the closure returns `false`, the check returns an error containing + /// `output_name` and `err_msg`, with context from the command. + /// + /// `output` is typically `self.output.stdout` or `self.output.stderr`. + #[instrument(skip(self, output_predicate, output))] + pub fn output_check

( + &self, + output_predicate: P, + output: &[u8], + output_name: impl ToString + fmt::Debug, + err_msg: impl ToString + fmt::Debug, + ) -> Result<&Self> + where + P: FnOnce(&str) -> bool, + { + let output = String::from_utf8_lossy(output); - for line in stdout.lines() { - if re.is_match(line) { - return Ok(self); - } - } - - Err(eyre!( - "stdout of command did not contain any matches for the given regex" - )) - .context_from(self) - .with_section(|| format!("{:?}", regex).header("Match Regex:")) - } - - #[instrument(skip(self))] - pub fn stdout_equals(&self, s: &str) -> Result<&Self> { - let stdout = String::from_utf8_lossy(&self.output.stdout); - - if stdout == s { - return Ok(self); - } - - Err(eyre!("stdout of command is not equal the given string")) + if output_predicate(&output) { + Ok(self) + } else { + Err(eyre!( + "{} of command did not {}", + output_name.to_string(), + err_msg.to_string() + )) .context_from(self) - .with_section(|| format!("{:?}", s).header("Match String:")) + } } + /// Checks each line in the output of a command, using a closure to determine + /// if the line is valid. + /// + /// See [`output_check`] for details. + #[instrument(skip(self, line_predicate, output))] + pub fn any_output_line

( + &self, + mut line_predicate: P, + output: &[u8], + output_name: impl ToString + fmt::Debug, + err_msg: impl ToString + fmt::Debug, + ) -> Result<&Self> + where + P: FnMut(&str) -> bool, + { + let output_predicate = |stdout: &str| { + for line in stdout.lines() { + if line_predicate(line) { + return true; + } + } + false + }; + + self.output_check( + output_predicate, + output, + output_name, + format!("have any lines that {}", err_msg.to_string()), + ) + } + + /// Tests if any lines in the output of a command contain `s`. + /// + /// See [`any_output_line`] for details. + #[instrument(skip(self, output))] + pub fn any_output_line_contains( + &self, + s: &str, + output: &[u8], + output_name: impl ToString + fmt::Debug, + err_msg: impl ToString + fmt::Debug, + ) -> Result<&Self> { + self.any_output_line( + |line| line.contains(s), + output, + output_name, + format!("contain {}", err_msg.to_string()), + ) + .with_section(|| format!("{:?}", s).header("Match String:")) + } + + /// Tests if standard output contains `s`. + #[instrument(skip(self))] + pub fn stdout_contains(&self, s: &str) -> Result<&Self> { + self.output_check( + |stdout| stdout.contains(s), + &self.output.stdout, + "stdout", + "contain the given string", + ) + .with_section(|| format!("{:?}", s).header("Match String:")) + } + + /// Tests if standard output matches `regex`. #[instrument(skip(self))] pub fn stdout_matches(&self, regex: &str) -> Result<&Self> { let re = regex::Regex::new(regex)?; - let stdout = String::from_utf8_lossy(&self.output.stdout); - if re.is_match(&stdout) { - return Ok(self); - } - - Err(eyre!("stdout of command is not equal to the given regex")) - .context_from(self) - .with_section(|| format!("{:?}", regex).header("Match Regex:")) - } - - #[instrument(skip(self))] - pub fn stderr_contains(&self, regex: &str) -> Result<&Self> { - let re = regex::Regex::new(regex)?; - let stderr = String::from_utf8_lossy(&self.output.stderr); - - for line in stderr.lines() { - if re.is_match(line) { - return Ok(self); - } - } - - Err(eyre!( - "stderr of command did not contain any matches for the given regex" - )) - .context_from(self) + self.output_check( + |stdout| re.is_match(stdout), + &self.output.stdout, + "stdout", + "matched the given regex", + ) .with_section(|| format!("{:?}", regex).header("Match Regex:")) } + /// Tests if any lines in standard output contain `s`. #[instrument(skip(self))] - pub fn stderr_equals(&self, s: &str) -> Result<&Self> { - let stderr = String::from_utf8_lossy(&self.output.stderr); - - if stderr == s { - return Ok(self); - } - - Err(eyre!("stderr of command is not equal the given string")) - .context_from(self) - .with_section(|| format!("{:?}", s).header("Match String:")) + pub fn stdout_line_contains(&self, s: &str) -> Result<&Self> { + self.any_output_line_contains(s, &self.output.stdout, "stdout", "the given string") } + /// Tests if any lines in standard output match `regex`. + #[instrument(skip(self))] + pub fn stdout_line_matches(&self, regex: &str) -> Result<&Self> { + let re = regex::Regex::new(regex)?; + + self.any_output_line( + |line| re.is_match(line), + &self.output.stdout, + "stdout", + "matched the given regex", + ) + .with_section(|| format!("{:?}", regex).header("Line Match Regex:")) + } + + /// Tests if standard error contains `s`. + #[instrument(skip(self))] + pub fn stderr_contains(&self, s: &str) -> Result<&Self> { + self.output_check( + |stderr| stderr.contains(s), + &self.output.stderr, + "stderr", + "contain the given string", + ) + .with_section(|| format!("{:?}", s).header("Match String:")) + } + + /// Tests if standard error matches `regex`. #[instrument(skip(self))] pub fn stderr_matches(&self, regex: &str) -> Result<&Self> { let re = regex::Regex::new(regex)?; - let stderr = String::from_utf8_lossy(&self.output.stderr); - if re.is_match(&stderr) { - return Ok(self); - } + self.output_check( + |stderr| re.is_match(stderr), + &self.output.stderr, + "stderr", + "matched the given regex", + ) + .with_section(|| format!("{:?}", regex).header("Match Regex:")) + } - Err(eyre!("stderr of command is not equal to the given regex")) - .context_from(self) - .with_section(|| format!("{:?}", regex).header("Match Regex:")) + /// Tests if any lines in standard error contain `s`. + #[instrument(skip(self))] + pub fn stderr_line_contains(&self, s: &str) -> Result<&Self> { + self.any_output_line_contains(s, &self.output.stderr, "stderr", "the given string") + } + + /// Tests if any lines in standard error match `regex`. + #[instrument(skip(self))] + pub fn stderr_line_matches(&self, regex: &str) -> Result<&Self> { + let re = regex::Regex::new(regex)?; + + self.any_output_line( + |line| re.is_match(line), + &self.output.stderr, + "stderr", + "matched the given regex", + ) + .with_section(|| format!("{:?}", regex).header("Line Match Regex:")) } /// Returns Ok if the program was killed, Err(Report) if exit was by another diff --git a/zebra-test/tests/command.rs b/zebra-test/tests/command.rs index 06304d75..b8faf172 100644 --- a/zebra-test/tests/command.rs +++ b/zebra-test/tests/command.rs @@ -63,9 +63,11 @@ fn kill_on_timeout_output_continuous_lines() -> Result<()> { .spawn_child_with_command(TEST_CMD, &["-v", "/dev/zero"])? .with_timeout(Duration::from_secs(2)); - // We need to use expect_stdout, because wait_with_output ignores timeouts. + // We need to use expect_stdout_line_matches, because wait_with_output ignores timeouts. // We use a non-matching regex, to trigger the timeout. - assert!(child.expect_stdout("this regex should not match").is_err()); + assert!(child + .expect_stdout_line_matches("this regex should not match") + .is_err()); Ok(()) } @@ -88,9 +90,11 @@ fn finish_before_timeout_output_single_line() -> Result<()> { .spawn_child_with_command(TEST_CMD, &["zebra_test_output"])? .with_timeout(Duration::from_secs(2)); - // We need to use expect_stdout, because wait_with_output ignores timeouts. + // We need to use expect_stdout_line_matches, because wait_with_output ignores timeouts. // We use a non-matching regex, to trigger the timeout. - assert!(child.expect_stdout("this regex should not match").is_err()); + assert!(child + .expect_stdout_line_matches("this regex should not match") + .is_err()); Ok(()) } @@ -115,9 +119,11 @@ fn kill_on_timeout_continuous_output_no_newlines() -> Result<()> { .spawn_child_with_command(TEST_CMD, &["/dev/zero"])? .with_timeout(Duration::from_secs(2)); - // We need to use expect_stdout, because wait_with_output ignores timeouts. + // We need to use expect_stdout_line_matches, because wait_with_output ignores timeouts. // We use a non-matching regex, to trigger the timeout. - assert!(child.expect_stdout("this regex should not match").is_err()); + assert!(child + .expect_stdout_line_matches("this regex should not match") + .is_err()); Ok(()) } @@ -141,9 +147,11 @@ fn finish_before_timeout_short_output_no_newlines() -> Result<()> { .spawn_child_with_command(TEST_CMD, &["zebra_test_output"])? .with_timeout(Duration::from_secs(2)); - // We need to use expect_stdout, because wait_with_output ignores timeouts. + // We need to use expect_stdout_line_matches, because wait_with_output ignores timeouts. // We use a non-matching regex, to trigger the timeout. - assert!(child.expect_stdout("this regex should not match").is_err()); + assert!(child + .expect_stdout_line_matches("this regex should not match") + .is_err()); Ok(()) } @@ -167,9 +175,11 @@ fn kill_on_timeout_no_output() -> Result<()> { .spawn_child_with_command(TEST_CMD, &["120"])? .with_timeout(Duration::from_secs(2)); - // We need to use expect_stdout, because wait_with_output ignores timeouts. + // We need to use expect_stdout_line_matches, because wait_with_output ignores timeouts. // We use a non-matching regex, to trigger the timeout. - assert!(child.expect_stdout("this regex should not match").is_err()); + assert!(child + .expect_stdout_line_matches("this regex should not match") + .is_err()); Ok(()) } diff --git a/zebrad/Cargo.toml b/zebrad/Cargo.toml index ac804448..b726a327 100644 --- a/zebrad/Cargo.toml +++ b/zebrad/Cargo.toml @@ -53,6 +53,8 @@ vergen = { version = "5.1.8", default-features = false, features = ["cargo", "gi [dev-dependencies] abscissa_core = { version = "0.5", features = ["testing"] } once_cell = "1.7" +regex = "1.4.6" +semver = "1.0.3" tempdir = "0.3.7" zebra-test = { path = "../zebra-test" } diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index df6864d3..315e1b18 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -18,8 +18,10 @@ #![deny(clippy::await_holding_lock)] #![forbid(unsafe_code)] -use color_eyre::eyre::Result; -use eyre::WrapErr; +use color_eyre::{ + eyre::{Result, WrapErr}, + Help, +}; use tempdir::TempDir; use std::{collections::HashSet, convert::TryInto, env, path::Path, path::PathBuf, time::Duration}; @@ -45,11 +47,6 @@ use zebrad::config::ZebradConfig; /// metrics or tracing test failures in Windows CI. const LAUNCH_DELAY: Duration = Duration::from_secs(10); -/// A regular expression that matches `zebrad` versions. -/// -/// `zebrad` uses [semantic versioning](https://semver.org/). -const ZEBRAD_VERSION_REGEX: &str = r"zebrad [0-9].[0-9].[0-9]-[A-Za-z]*.[0-9]+"; - fn default_test_config() -> Result { let auto_port_ipv4_local = zebra_network::Config { listen_addr: "127.0.0.1:0".parse()?, @@ -218,7 +215,7 @@ fn generate_no_args() -> Result<()> { let output = output.assert_success()?; // First line - output.stdout_contains(r"# Default configuration for zebrad.")?; + output.stdout_line_contains("# Default configuration for zebrad")?; Ok(()) } @@ -301,6 +298,17 @@ 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(); @@ -311,11 +319,16 @@ fn help_no_args() -> Result<()> { let output = child.wait_with_output()?; let output = output.assert_success()?; - // First line haves the version - output.stdout_contains(ZEBRAD_VERSION_REGEX)?; + // The first line should have the version + output.any_output_line( + is_zebrad_version, + &output.output.stdout, + "stdout", + "a valid zebrad semantic version", + )?; // Make sure we are in help by looking usage string - output.stdout_contains(r"USAGE:")?; + output.stdout_line_contains("USAGE:")?; Ok(()) } @@ -356,7 +369,7 @@ fn start_no_args() -> Result<()> { let output = child.wait_with_output()?; let output = output.assert_failure()?; - output.stdout_contains(r"Starting zebrad$")?; + output.stdout_line_contains("Starting zebrad")?; // Make sure the command was killed output.assert_was_killed()?; @@ -563,7 +576,7 @@ fn app_no_args() -> Result<()> { let output = child.wait_with_output()?; let output = output.assert_success()?; - output.stdout_contains(r"USAGE:")?; + output.stdout_line_contains("USAGE:")?; Ok(()) } @@ -578,7 +591,13 @@ fn version_no_args() -> Result<()> { let output = child.wait_with_output()?; let output = output.assert_success()?; - output.stdout_contains(ZEBRAD_VERSION_REGEX)?; + // The output should only contain the version + output.output_check( + is_zebrad_version, + &output.output.stdout, + "stdout", + "a valid zebrad semantic version", + )?; Ok(()) } @@ -608,12 +627,12 @@ fn valid_generated_config_test() -> Result<()> { // Unlike the other tests, these tests can not be run in parallel, because // they use the generated config. So parallel execution can cause port and // cache conflicts. - valid_generated_config("start", r"Starting zebrad$")?; + valid_generated_config("start", "Starting zebrad")?; Ok(()) } -fn valid_generated_config(command: &str, expected_output: &str) -> Result<()> { +fn valid_generated_config(command: &str, expect_stdout_line_contains: &str) -> Result<()> { zebra_test::init(); let testdir = testdir()?; @@ -643,7 +662,7 @@ fn valid_generated_config(command: &str, expected_output: &str) -> Result<()> { let output = child.wait_with_output()?; let output = output.assert_failure()?; - output.stdout_contains(expected_output)?; + output.stdout_line_contains(expect_stdout_line_contains)?; // [Note on port conflict](#Note on port conflict) output.assert_was_killed().wrap_err("Possible port or cache conflict. Are there other acceptance test, zebrad, or zcashd processes running?")?; @@ -800,8 +819,8 @@ fn sync_until( let mut child = tempdir.spawn_child(&["start"])?.with_timeout(timeout); let network = format!("network: {},", network); - child.expect_stdout(&network)?; - child.expect_stdout(stop_regex)?; + child.expect_stdout_line_matches(&network)?; + child.expect_stdout_line_matches(stop_regex)?; child.kill()?; Ok(child.dir) @@ -833,8 +852,8 @@ fn create_cached_database_height(network: Network, height: Height) -> Result<()> .bypass_test_capture(true); let network = format!("network: {},", network); - child.expect_stdout(&network)?; - child.expect_stdout(STOP_AT_HEIGHT_REGEX)?; + child.expect_stdout_line_matches(&network)?; + child.expect_stdout_line_matches(STOP_AT_HEIGHT_REGEX)?; child.kill()?; Ok(()) @@ -980,20 +999,21 @@ async fn metrics_endpoint() -> Result<()> { assert!(res.status().is_success()); let body = hyper::body::to_bytes(res).await; let (body, mut child) = child.kill_on_error(body)?; - assert!( - std::str::from_utf8(&body) - .expect("metrics response is valid UTF-8") - .contains("metrics snapshot"), - "metrics exporter returns data in the expected format" - ); - child.kill()?; let output = child.wait_with_output()?; let output = output.assert_failure()?; + output.any_output_line_contains( + "metrics snapshot", + &body, + "metrics exporter response", + "the metrics response header", + )?; + std::str::from_utf8(&body).expect("unexpected invalid UTF-8 in metrics exporter response"); + // Make sure metrics was started - output.stdout_contains(format!(r"Opened metrics endpoint at {}", endpoint).as_str())?; + output.stdout_line_contains(format!("Opened metrics endpoint at {}", endpoint).as_str())?; // [Note on port conflict](#Note on port conflict) output @@ -1037,9 +1057,6 @@ async fn tracing_endpoint() -> Result<()> { assert!(res.status().is_success()); let body = hyper::body::to_bytes(res).await; let (body, child) = child.kill_on_error(body)?; - assert!(std::str::from_utf8(&body).unwrap().contains( - "This HTTP endpoint allows dynamic control of the filter applied to\ntracing events." - )); // Set a filter and make sure it was changed let request = Request::post(url_filter.clone()) @@ -1055,9 +1072,6 @@ async fn tracing_endpoint() -> Result<()> { assert!(tracing_res.status().is_success()); let tracing_body = hyper::body::to_bytes(tracing_res).await; let (tracing_body, mut child) = child.kill_on_error(tracing_body)?; - assert!(std::str::from_utf8(&tracing_body) - .unwrap() - .contains("zebrad=debug")); child.kill()?; @@ -1065,9 +1079,36 @@ async fn tracing_endpoint() -> Result<()> { let output = output.assert_failure()?; // Make sure tracing endpoint was started - output.stdout_contains(format!(r"Opened tracing endpoint at {}", endpoint).as_str())?; + output.stdout_line_contains(format!("Opened tracing endpoint at {}", endpoint).as_str())?; // TODO: Match some trace level messages from output + // Make sure the endpoint header is correct + // The header is split over two lines. But we don't want to require line + // breaks at a specific word, so we run two checks for different substrings. + output.any_output_line_contains( + "HTTP endpoint allows dynamic control of the filter", + &body, + "tracing filter endpoint response", + "the tracing response header", + )?; + output.any_output_line_contains( + "tracing events", + &body, + "tracing filter endpoint response", + "the tracing response header", + )?; + std::str::from_utf8(&body).expect("unexpected invalid UTF-8 in tracing filter response"); + + // Make sure endpoint requests change the filter + output.any_output_line_contains( + "zebrad=debug", + &tracing_body, + "tracing filter endpoint response", + "the modified tracing filter", + )?; + std::str::from_utf8(&tracing_body) + .expect("unexpected invalid UTF-8 in modified tracing filter response"); + // [Note on port conflict](#Note on port conflict) output .assert_was_killed() @@ -1091,7 +1132,10 @@ fn zebra_zcash_listener_conflict() -> Result<()> { let mut config = default_test_config()?; config.network.listen_addr = listen_addr.parse().unwrap(); let dir1 = TempDir::new("zebrad_tests")?.with_config(&mut config)?; - let regex1 = format!(r"Opened Zcash protocol endpoint at {}", listen_addr); + let regex1 = regex::escape(&format!( + "Opened Zcash protocol endpoint at {}", + listen_addr + )); // From another folder create a configuration with the same listener. // `network.listen_addr` will be the same in the 2 nodes. @@ -1119,7 +1163,7 @@ fn zebra_metrics_conflict() -> Result<()> { let mut config = default_test_config()?; config.metrics.endpoint_addr = Some(listen_addr.parse().unwrap()); let dir1 = TempDir::new("zebrad_tests")?.with_config(&mut config)?; - let regex1 = format!(r"Opened metrics endpoint at {}", listen_addr); + let regex1 = regex::escape(&format!(r"Opened metrics endpoint at {}", listen_addr)); // From another folder create a configuration with the same endpoint. // `metrics.endpoint_addr` will be the same in the 2 nodes. @@ -1147,7 +1191,7 @@ fn zebra_tracing_conflict() -> Result<()> { let mut config = default_test_config()?; config.tracing.endpoint_addr = Some(listen_addr.parse().unwrap()); let dir1 = TempDir::new("zebrad_tests")?.with_config(&mut config)?; - let regex1 = format!(r"Opened tracing endpoint at {}", listen_addr); + let regex1 = regex::escape(&format!(r"Opened tracing endpoint at {}", listen_addr)); // From another folder create a configuration with the same endpoint. // `tracing.endpoint_addr` will be the same in the 2 nodes. @@ -1173,7 +1217,7 @@ fn zebra_state_conflict() -> Result<()> { // Windows problems with this match will be worked on at #1654 // We are matching the whole opened path only for unix by now. - let regex = if cfg!(unix) { + let contains = if cfg!(unix) { let mut dir_conflict_full = PathBuf::new(); dir_conflict_full.push(dir_conflict.path()); dir_conflict_full.push("state"); @@ -1193,7 +1237,7 @@ fn zebra_state_conflict() -> Result<()> { check_config_conflict( dir_conflict.path(), - regex.as_str(), + regex::escape(&contains).as_str(), dir_conflict.path(), LOCK_FILE_ERROR.as_str(), )?; @@ -1259,9 +1303,8 @@ where // Look for the success regex output1 - .stdout_contains(first_stdout_regex) + .stdout_line_matches(first_stdout_regex) .context_from(&output2)?; - use color_eyre::Help; output1 .assert_was_killed() .warning("Possible port conflict. Are there other acceptance tests running?") @@ -1269,7 +1312,7 @@ where // In the second node we look for the conflict regex output2 - .stderr_contains(second_stderr_regex) + .stderr_line_matches(second_stderr_regex) .context_from(&output1)?; output2 .assert_was_not_killed()