Rewrite acceptance test matching
- Add a custom semver match for `zebrad` versions - Prefer "line contains string" matches, so tests ignore minor changes - Escape regex meta-characters when a literal string match is intended - Rename test functions so they are more precise - Rewrite match internals to remove duplicate code and enable custom matches - Document match functions
This commit is contained in:
parent
fc0edb5c44
commit
56ef08e385
|
|
@ -19,7 +19,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"regex",
|
"regex",
|
||||||
"secrecy",
|
"secrecy",
|
||||||
"semver",
|
"semver 0.9.0",
|
||||||
"serde",
|
"serde",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
|
|
@ -2954,7 +2954,7 @@ version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"semver",
|
"semver 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3064,6 +3064,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f3aac57ee7f3272d8395c6e4f502f434f0e289fcd62876f70daa008c20dcabe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver-parser"
|
name = "semver-parser"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
|
@ -4622,6 +4628,8 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pin-project 0.4.27",
|
"pin-project 0.4.27",
|
||||||
"rand 0.8.1",
|
"rand 0.8.1",
|
||||||
|
"regex",
|
||||||
|
"semver 1.0.3",
|
||||||
"sentry",
|
"sentry",
|
||||||
"sentry-tracing",
|
"sentry-tracing",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ lazy_static! {
|
||||||
/// OS-specific error when the port attempting to be opened is already in use.
|
/// 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) {
|
pub static ref PORT_IN_USE_ERROR: Regex = if cfg!(unix) {
|
||||||
#[allow(clippy::trivial_regex)]
|
#[allow(clippy::trivial_regex)]
|
||||||
Regex::new("already in use")
|
Regex::new(®ex::escape("already in use"))
|
||||||
} else {
|
} else {
|
||||||
Regex::new("(access a socket in a way forbidden by its access permissions)|(Only one usage of each socket address)")
|
Regex::new("(access a socket in a way forbidden by its access permissions)|(Only one usage of each socket address)")
|
||||||
}.expect("regex is valid");
|
}.expect("regex is valid");
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use tracing::instrument;
|
||||||
use std::os::unix::process::ExitStatusExt;
|
use std::os::unix::process::ExitStatusExt;
|
||||||
use std::{
|
use std::{
|
||||||
convert::Infallible as NoDir,
|
convert::Infallible as NoDir,
|
||||||
fmt::Write as _,
|
fmt::{self, Write as _},
|
||||||
io::BufRead,
|
io::BufRead,
|
||||||
io::{BufReader, Lines, Read},
|
io::{BufReader, Lines, Read},
|
||||||
path::Path,
|
path::Path,
|
||||||
|
|
@ -194,7 +194,7 @@ impl<T> TestChild<T> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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`.
|
/// Does not apply to `wait_with_output`.
|
||||||
pub fn with_timeout(mut self, timeout: Duration) -> Self {
|
pub fn with_timeout(mut self, timeout: Duration) -> Self {
|
||||||
|
|
@ -215,7 +215,7 @@ impl<T> TestChild<T> {
|
||||||
/// Kills the child after the configured timeout has elapsed.
|
/// Kills the child after the configured timeout has elapsed.
|
||||||
/// See `expect_line_matching` for details.
|
/// See `expect_line_matching` for details.
|
||||||
#[instrument(skip(self))]
|
#[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() {
|
if self.stdout.is_none() {
|
||||||
self.stdout = self
|
self.stdout = self
|
||||||
.child
|
.child
|
||||||
|
|
@ -228,7 +228,7 @@ impl<T> TestChild<T> {
|
||||||
let mut lines = self
|
let mut lines = self
|
||||||
.stdout
|
.stdout
|
||||||
.take()
|
.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") {
|
match self.expect_line_matching(&mut lines, regex, "stdout") {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
|
|
@ -245,7 +245,7 @@ impl<T> TestChild<T> {
|
||||||
/// Kills the child after the configured timeout has elapsed.
|
/// Kills the child after the configured timeout has elapsed.
|
||||||
/// See `expect_line_matching` for details.
|
/// See `expect_line_matching` for details.
|
||||||
#[instrument(skip(self))]
|
#[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() {
|
if self.stderr.is_none() {
|
||||||
self.stderr = self
|
self.stderr = self
|
||||||
.child
|
.child
|
||||||
|
|
@ -258,7 +258,7 @@ impl<T> TestChild<T> {
|
||||||
let mut lines = self
|
let mut lines = self
|
||||||
.stderr
|
.stderr
|
||||||
.take()
|
.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") {
|
match self.expect_line_matching(&mut lines, regex, "stderr") {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
|
|
@ -399,94 +399,181 @@ impl<T> TestOutput<T> {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
/// Checks the output of a command, using a closure to determine if the
|
||||||
pub fn stdout_contains(&self, regex: &str) -> Result<&Self> {
|
/// output is valid.
|
||||||
let re = regex::Regex::new(regex)?;
|
///
|
||||||
let stdout = String::from_utf8_lossy(&self.output.stdout);
|
/// 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<P>(
|
||||||
|
&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 output_predicate(&output) {
|
||||||
if re.is_match(line) {
|
Ok(self)
|
||||||
return Ok(self);
|
} else {
|
||||||
}
|
Err(eyre!(
|
||||||
}
|
"{} of command did not {}",
|
||||||
|
output_name.to_string(),
|
||||||
Err(eyre!(
|
err_msg.to_string()
|
||||||
"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"))
|
|
||||||
.context_from(self)
|
.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<P>(
|
||||||
|
&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))]
|
#[instrument(skip(self))]
|
||||||
pub fn stdout_matches(&self, regex: &str) -> Result<&Self> {
|
pub fn stdout_matches(&self, regex: &str) -> Result<&Self> {
|
||||||
let re = regex::Regex::new(regex)?;
|
let re = regex::Regex::new(regex)?;
|
||||||
let stdout = String::from_utf8_lossy(&self.output.stdout);
|
|
||||||
|
|
||||||
if re.is_match(&stdout) {
|
self.output_check(
|
||||||
return Ok(self);
|
|stdout| re.is_match(stdout),
|
||||||
}
|
&self.output.stdout,
|
||||||
|
"stdout",
|
||||||
Err(eyre!("stdout of command is not equal to the given regex"))
|
"matched 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)
|
|
||||||
.with_section(|| format!("{:?}", regex).header("Match Regex:"))
|
.with_section(|| format!("{:?}", regex).header("Match Regex:"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests if any lines in standard output contain `s`.
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
pub fn stderr_equals(&self, s: &str) -> Result<&Self> {
|
pub fn stdout_line_contains(&self, s: &str) -> Result<&Self> {
|
||||||
let stderr = String::from_utf8_lossy(&self.output.stderr);
|
self.any_output_line_contains(s, &self.output.stdout, "stdout", "the given string")
|
||||||
|
|
||||||
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:"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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))]
|
#[instrument(skip(self))]
|
||||||
pub fn stderr_matches(&self, regex: &str) -> Result<&Self> {
|
pub fn stderr_matches(&self, regex: &str) -> Result<&Self> {
|
||||||
let re = regex::Regex::new(regex)?;
|
let re = regex::Regex::new(regex)?;
|
||||||
let stderr = String::from_utf8_lossy(&self.output.stderr);
|
|
||||||
|
|
||||||
if re.is_match(&stderr) {
|
self.output_check(
|
||||||
return Ok(self);
|
|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"))
|
/// Tests if any lines in standard error contain `s`.
|
||||||
.context_from(self)
|
#[instrument(skip(self))]
|
||||||
.with_section(|| format!("{:?}", regex).header("Match Regex:"))
|
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
|
/// Returns Ok if the program was killed, Err(Report) if exit was by another
|
||||||
|
|
|
||||||
|
|
@ -63,9 +63,11 @@ fn kill_on_timeout_output_continuous_lines() -> Result<()> {
|
||||||
.spawn_child_with_command(TEST_CMD, &["-v", "/dev/zero"])?
|
.spawn_child_with_command(TEST_CMD, &["-v", "/dev/zero"])?
|
||||||
.with_timeout(Duration::from_secs(2));
|
.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.
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -88,9 +90,11 @@ fn finish_before_timeout_output_single_line() -> Result<()> {
|
||||||
.spawn_child_with_command(TEST_CMD, &["zebra_test_output"])?
|
.spawn_child_with_command(TEST_CMD, &["zebra_test_output"])?
|
||||||
.with_timeout(Duration::from_secs(2));
|
.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.
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -115,9 +119,11 @@ fn kill_on_timeout_continuous_output_no_newlines() -> Result<()> {
|
||||||
.spawn_child_with_command(TEST_CMD, &["/dev/zero"])?
|
.spawn_child_with_command(TEST_CMD, &["/dev/zero"])?
|
||||||
.with_timeout(Duration::from_secs(2));
|
.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.
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -141,9 +147,11 @@ fn finish_before_timeout_short_output_no_newlines() -> Result<()> {
|
||||||
.spawn_child_with_command(TEST_CMD, &["zebra_test_output"])?
|
.spawn_child_with_command(TEST_CMD, &["zebra_test_output"])?
|
||||||
.with_timeout(Duration::from_secs(2));
|
.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.
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -167,9 +175,11 @@ fn kill_on_timeout_no_output() -> Result<()> {
|
||||||
.spawn_child_with_command(TEST_CMD, &["120"])?
|
.spawn_child_with_command(TEST_CMD, &["120"])?
|
||||||
.with_timeout(Duration::from_secs(2));
|
.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.
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ vergen = { version = "5.1.8", default-features = false, features = ["cargo", "gi
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
abscissa_core = { version = "0.5", features = ["testing"] }
|
abscissa_core = { version = "0.5", features = ["testing"] }
|
||||||
once_cell = "1.7"
|
once_cell = "1.7"
|
||||||
|
regex = "1.4.6"
|
||||||
|
semver = "1.0.3"
|
||||||
tempdir = "0.3.7"
|
tempdir = "0.3.7"
|
||||||
zebra-test = { path = "../zebra-test" }
|
zebra-test = { path = "../zebra-test" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,10 @@
|
||||||
#![deny(clippy::await_holding_lock)]
|
#![deny(clippy::await_holding_lock)]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::{
|
||||||
use eyre::WrapErr;
|
eyre::{Result, WrapErr},
|
||||||
|
Help,
|
||||||
|
};
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
|
|
||||||
use std::{collections::HashSet, convert::TryInto, env, path::Path, path::PathBuf, time::Duration};
|
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.
|
/// metrics or tracing test failures in Windows CI.
|
||||||
const LAUNCH_DELAY: Duration = Duration::from_secs(10);
|
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<ZebradConfig> {
|
fn default_test_config() -> Result<ZebradConfig> {
|
||||||
let auto_port_ipv4_local = zebra_network::Config {
|
let auto_port_ipv4_local = zebra_network::Config {
|
||||||
listen_addr: "127.0.0.1:0".parse()?,
|
listen_addr: "127.0.0.1:0".parse()?,
|
||||||
|
|
@ -218,7 +215,7 @@ fn generate_no_args() -> Result<()> {
|
||||||
let output = output.assert_success()?;
|
let output = output.assert_success()?;
|
||||||
|
|
||||||
// First line
|
// First line
|
||||||
output.stdout_contains(r"# Default configuration for zebrad.")?;
|
output.stdout_line_contains("# Default configuration for zebrad")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -301,6 +298,17 @@ fn generate_args() -> Result<()> {
|
||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn help_no_args() -> Result<()> {
|
fn help_no_args() -> Result<()> {
|
||||||
zebra_test::init();
|
zebra_test::init();
|
||||||
|
|
@ -311,11 +319,16 @@ fn help_no_args() -> Result<()> {
|
||||||
let output = child.wait_with_output()?;
|
let output = child.wait_with_output()?;
|
||||||
let output = output.assert_success()?;
|
let output = output.assert_success()?;
|
||||||
|
|
||||||
// First line haves the version
|
// The first line should have the version
|
||||||
output.stdout_contains(ZEBRAD_VERSION_REGEX)?;
|
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
|
// Make sure we are in help by looking usage string
|
||||||
output.stdout_contains(r"USAGE:")?;
|
output.stdout_line_contains("USAGE:")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -356,7 +369,7 @@ fn start_no_args() -> Result<()> {
|
||||||
let output = child.wait_with_output()?;
|
let output = child.wait_with_output()?;
|
||||||
let output = output.assert_failure()?;
|
let output = output.assert_failure()?;
|
||||||
|
|
||||||
output.stdout_contains(r"Starting zebrad$")?;
|
output.stdout_line_contains("Starting zebrad")?;
|
||||||
|
|
||||||
// Make sure the command was killed
|
// Make sure the command was killed
|
||||||
output.assert_was_killed()?;
|
output.assert_was_killed()?;
|
||||||
|
|
@ -563,7 +576,7 @@ fn app_no_args() -> Result<()> {
|
||||||
let output = child.wait_with_output()?;
|
let output = child.wait_with_output()?;
|
||||||
let output = output.assert_success()?;
|
let output = output.assert_success()?;
|
||||||
|
|
||||||
output.stdout_contains(r"USAGE:")?;
|
output.stdout_line_contains("USAGE:")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -578,7 +591,13 @@ fn version_no_args() -> Result<()> {
|
||||||
let output = child.wait_with_output()?;
|
let output = child.wait_with_output()?;
|
||||||
let output = output.assert_success()?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -608,12 +627,12 @@ fn valid_generated_config_test() -> Result<()> {
|
||||||
// Unlike the other tests, these tests can not be run in parallel, because
|
// 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
|
// they use the generated config. So parallel execution can cause port and
|
||||||
// cache conflicts.
|
// cache conflicts.
|
||||||
valid_generated_config("start", r"Starting zebrad$")?;
|
valid_generated_config("start", "Starting zebrad")?;
|
||||||
|
|
||||||
Ok(())
|
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();
|
zebra_test::init();
|
||||||
|
|
||||||
let testdir = testdir()?;
|
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 = child.wait_with_output()?;
|
||||||
let output = output.assert_failure()?;
|
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)
|
// [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?")?;
|
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 mut child = tempdir.spawn_child(&["start"])?.with_timeout(timeout);
|
||||||
|
|
||||||
let network = format!("network: {},", network);
|
let network = format!("network: {},", network);
|
||||||
child.expect_stdout(&network)?;
|
child.expect_stdout_line_matches(&network)?;
|
||||||
child.expect_stdout(stop_regex)?;
|
child.expect_stdout_line_matches(stop_regex)?;
|
||||||
child.kill()?;
|
child.kill()?;
|
||||||
|
|
||||||
Ok(child.dir)
|
Ok(child.dir)
|
||||||
|
|
@ -833,8 +852,8 @@ fn create_cached_database_height(network: Network, height: Height) -> Result<()>
|
||||||
.bypass_test_capture(true);
|
.bypass_test_capture(true);
|
||||||
|
|
||||||
let network = format!("network: {},", network);
|
let network = format!("network: {},", network);
|
||||||
child.expect_stdout(&network)?;
|
child.expect_stdout_line_matches(&network)?;
|
||||||
child.expect_stdout(STOP_AT_HEIGHT_REGEX)?;
|
child.expect_stdout_line_matches(STOP_AT_HEIGHT_REGEX)?;
|
||||||
child.kill()?;
|
child.kill()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -980,20 +999,21 @@ async fn metrics_endpoint() -> Result<()> {
|
||||||
assert!(res.status().is_success());
|
assert!(res.status().is_success());
|
||||||
let body = hyper::body::to_bytes(res).await;
|
let body = hyper::body::to_bytes(res).await;
|
||||||
let (body, mut child) = child.kill_on_error(body)?;
|
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()?;
|
child.kill()?;
|
||||||
|
|
||||||
let output = child.wait_with_output()?;
|
let output = child.wait_with_output()?;
|
||||||
let output = output.assert_failure()?;
|
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
|
// 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)
|
// [Note on port conflict](#Note on port conflict)
|
||||||
output
|
output
|
||||||
|
|
@ -1037,9 +1057,6 @@ async fn tracing_endpoint() -> Result<()> {
|
||||||
assert!(res.status().is_success());
|
assert!(res.status().is_success());
|
||||||
let body = hyper::body::to_bytes(res).await;
|
let body = hyper::body::to_bytes(res).await;
|
||||||
let (body, child) = child.kill_on_error(body)?;
|
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
|
// Set a filter and make sure it was changed
|
||||||
let request = Request::post(url_filter.clone())
|
let request = Request::post(url_filter.clone())
|
||||||
|
|
@ -1055,9 +1072,6 @@ async fn tracing_endpoint() -> Result<()> {
|
||||||
assert!(tracing_res.status().is_success());
|
assert!(tracing_res.status().is_success());
|
||||||
let tracing_body = hyper::body::to_bytes(tracing_res).await;
|
let tracing_body = hyper::body::to_bytes(tracing_res).await;
|
||||||
let (tracing_body, mut child) = child.kill_on_error(tracing_body)?;
|
let (tracing_body, mut child) = child.kill_on_error(tracing_body)?;
|
||||||
assert!(std::str::from_utf8(&tracing_body)
|
|
||||||
.unwrap()
|
|
||||||
.contains("zebrad=debug"));
|
|
||||||
|
|
||||||
child.kill()?;
|
child.kill()?;
|
||||||
|
|
||||||
|
|
@ -1065,9 +1079,36 @@ async fn tracing_endpoint() -> Result<()> {
|
||||||
let output = output.assert_failure()?;
|
let output = output.assert_failure()?;
|
||||||
|
|
||||||
// Make sure tracing endpoint was started
|
// 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
|
// 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)
|
// [Note on port conflict](#Note on port conflict)
|
||||||
output
|
output
|
||||||
.assert_was_killed()
|
.assert_was_killed()
|
||||||
|
|
@ -1091,7 +1132,10 @@ fn zebra_zcash_listener_conflict() -> Result<()> {
|
||||||
let mut config = default_test_config()?;
|
let mut config = default_test_config()?;
|
||||||
config.network.listen_addr = listen_addr.parse().unwrap();
|
config.network.listen_addr = listen_addr.parse().unwrap();
|
||||||
let dir1 = TempDir::new("zebrad_tests")?.with_config(&mut config)?;
|
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.
|
// From another folder create a configuration with the same listener.
|
||||||
// `network.listen_addr` will be the same in the 2 nodes.
|
// `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()?;
|
let mut config = default_test_config()?;
|
||||||
config.metrics.endpoint_addr = Some(listen_addr.parse().unwrap());
|
config.metrics.endpoint_addr = Some(listen_addr.parse().unwrap());
|
||||||
let dir1 = TempDir::new("zebrad_tests")?.with_config(&mut config)?;
|
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.
|
// From another folder create a configuration with the same endpoint.
|
||||||
// `metrics.endpoint_addr` will be the same in the 2 nodes.
|
// `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()?;
|
let mut config = default_test_config()?;
|
||||||
config.tracing.endpoint_addr = Some(listen_addr.parse().unwrap());
|
config.tracing.endpoint_addr = Some(listen_addr.parse().unwrap());
|
||||||
let dir1 = TempDir::new("zebrad_tests")?.with_config(&mut config)?;
|
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.
|
// From another folder create a configuration with the same endpoint.
|
||||||
// `tracing.endpoint_addr` will be the same in the 2 nodes.
|
// `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
|
// Windows problems with this match will be worked on at #1654
|
||||||
// We are matching the whole opened path only for unix by now.
|
// 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();
|
let mut dir_conflict_full = PathBuf::new();
|
||||||
dir_conflict_full.push(dir_conflict.path());
|
dir_conflict_full.push(dir_conflict.path());
|
||||||
dir_conflict_full.push("state");
|
dir_conflict_full.push("state");
|
||||||
|
|
@ -1193,7 +1237,7 @@ fn zebra_state_conflict() -> Result<()> {
|
||||||
|
|
||||||
check_config_conflict(
|
check_config_conflict(
|
||||||
dir_conflict.path(),
|
dir_conflict.path(),
|
||||||
regex.as_str(),
|
regex::escape(&contains).as_str(),
|
||||||
dir_conflict.path(),
|
dir_conflict.path(),
|
||||||
LOCK_FILE_ERROR.as_str(),
|
LOCK_FILE_ERROR.as_str(),
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -1259,9 +1303,8 @@ where
|
||||||
|
|
||||||
// Look for the success regex
|
// Look for the success regex
|
||||||
output1
|
output1
|
||||||
.stdout_contains(first_stdout_regex)
|
.stdout_line_matches(first_stdout_regex)
|
||||||
.context_from(&output2)?;
|
.context_from(&output2)?;
|
||||||
use color_eyre::Help;
|
|
||||||
output1
|
output1
|
||||||
.assert_was_killed()
|
.assert_was_killed()
|
||||||
.warning("Possible port conflict. Are there other acceptance tests running?")
|
.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
|
// In the second node we look for the conflict regex
|
||||||
output2
|
output2
|
||||||
.stderr_contains(second_stderr_regex)
|
.stderr_line_matches(second_stderr_regex)
|
||||||
.context_from(&output1)?;
|
.context_from(&output1)?;
|
||||||
output2
|
output2
|
||||||
.assert_was_not_killed()
|
.assert_was_not_killed()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue