summaryrefslogtreecommitdiff
path: root/subplotlib
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2020-12-30 13:27:26 +0000
committerDaniel Silverstone <dsilvers@digital-scurf.org>2020-12-30 13:27:26 +0000
commitf10bb254669cdf97fb9850865a9e216a30a69f2a (patch)
tree549038b2f74bfdc4bc5f139b69748abd612db7f5 /subplotlib
parent8704ec0f4a9052ef1344692349e36c8118ea5702 (diff)
downloadsubplot-f10bb254669cdf97fb9850865a9e216a30a69f2a.tar.gz
subplotlib: Add runcmd step library
Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
Diffstat (limited to 'subplotlib')
-rw-r--r--subplotlib/Cargo.toml4
-rw-r--r--subplotlib/runcmd.md166
-rw-r--r--subplotlib/src/steplibrary.rs1
-rw-r--r--subplotlib/src/steplibrary/runcmd.rs259
-rw-r--r--subplotlib/steplibrary/runcmd.yaml89
-rw-r--r--subplotlib/tests/runcmd.rs543
6 files changed, 1061 insertions, 1 deletions
diff --git a/subplotlib/Cargo.toml b/subplotlib/Cargo.toml
index e19705a..df1c6d7 100644
--- a/subplotlib/Cargo.toml
+++ b/subplotlib/Cargo.toml
@@ -15,4 +15,6 @@ tempfile = "3.1"
fs2 = "0.4"
chrono = "0.4"
filetime = "0.2"
-regex = "1.4" \ No newline at end of file
+regex = "1.4"
+shell-words = "1.0"
+unescape = "0.1" \ No newline at end of file
diff --git a/subplotlib/runcmd.md b/subplotlib/runcmd.md
new file mode 100644
index 0000000..ee1c1d4
--- /dev/null
+++ b/subplotlib/runcmd.md
@@ -0,0 +1,166 @@
+---
+title: Acceptance criteria for the runcmd step library for subplotlib.
+author: The Subplot project
+template: rust
+bindings:
+ - steplibrary/runcmd.yaml
+---
+
+# Introduction
+
+The [Subplot][] step library `runcmd` for Rust provides scenario steps
+and their implementations for running Unix commands and examining the
+results. The library consists of a bindings file `steplibrary/runcmd.yaml` and
+implementations inside the `subplotlib` crate itself.
+
+[subplot]: https://subplot.liw.fi/
+
+This document explains the acceptance criteria for the library and how
+they're verified. It uses the steps and functions from the
+`steplibrary/runcmd` library. The scenarios all have the same structure: run a
+command, then examine the exit code, standard output (stdout for
+short), or standard error output (stderr) of the command.
+
+The scenarios use the Unix commands `/bin/true` and `/bin/false` to
+generate exit codes, and `/bin/echo` to produce stdout. To generate
+stderr, they use the little helper script below.
+
+```{#err.sh .file .sh .numberLines}
+#!/bin/sh
+echo "$@" 1>&2
+```
+
+# Check exit code
+
+These scenarios verify the exit code. To make it easier to write
+scenarios in language that flows more naturally, there are a couple of
+variations.
+
+## Successful execution
+
+```scenario
+when I run /bin/true
+then exit code is 0
+and command is successful
+```
+
+## Failed execution
+
+```scenario
+when I try to run /bin/false
+then exit code is not 0
+and command fails
+```
+
+# Check output has what we want
+
+These scenarios verify that stdout or stderr do have something we want
+to have.
+
+## Check stdout is exactly as wanted
+
+Note that the string is surrounded by double quotes to make it clear
+to the reader what's inside. Also, C-style string escapes are
+understood.
+
+```scenario
+when I run /bin/echo hello, world
+then stdout is exactly "hello, world\n"
+```
+
+## Check stderr is exactly as wanted
+
+```scenario
+given helper script err.sh for runcmd
+when I run sh err.sh hello, world
+then stderr is exactly "hello, world\n"
+```
+
+## Check stdout using sub-string search
+
+Exact string comparisons are not always enough, so we can verify a
+sub-string is in output.
+
+```scenario
+when I run /bin/echo hello, world
+then stdout contains "world\n"
+and exit code is 0
+```
+
+## Check stderr using sub-string search
+
+```scenario
+given helper script err.sh for runcmd
+when I run sh err.sh hello, world
+then stderr contains "world\n"
+```
+
+## Check stdout using regular expressions
+
+Fixed strings are not always enough, so we can verify output matches a
+regular expression. Note that the regular expression is not delimited
+and does not get any C-style string escaped decoded.
+
+```scenario
+when I run /bin/echo hello, world
+then stdout matches regex world$
+```
+
+## Check stderr using regular expressions
+
+```scenario
+given helper script err.sh for runcmd
+when I run sh err.sh hello, world
+then stderr matches regex world$
+```
+
+# Check output doesn't have what we want to avoid
+
+These scenarios verify that the stdout or stderr do not
+have something we want to avoid.
+
+## Check stdout is not exactly something
+
+```scenario
+when I run /bin/echo hi
+then stdout isn't exactly "hello, world\n"
+```
+
+## Check stderr is not exactly something
+
+```scenario
+given helper script err.sh for runcmd
+when I run sh err.sh hi
+then stderr isn't exactly "hello, world\n"
+```
+
+## Check stdout doesn't contain sub-string
+
+```scenario
+when I run /bin/echo hi
+then stdout doesn't contain "world"
+```
+
+## Check stderr doesn't contain sub-string
+
+```scenario
+given helper script err.sh for runcmd
+when I run sh err.sh hi
+then stderr doesn't contain "world"
+```
+
+## Check stdout doesn't match regular expression
+
+```scenario
+when I run /bin/echo hi
+then stdout doesn't match regex world$
+
+```
+
+## Check stderr doesn't match regular expressions
+
+```scenario
+given helper script err.sh for runcmd
+when I run sh err.sh hi
+then stderr doesn't match regex world$
+```
diff --git a/subplotlib/src/steplibrary.rs b/subplotlib/src/steplibrary.rs
index 94bfa2b..e70ac1b 100644
--- a/subplotlib/src/steplibrary.rs
+++ b/subplotlib/src/steplibrary.rs
@@ -19,3 +19,4 @@
pub mod datadir;
pub mod files;
+pub mod runcmd;
diff --git a/subplotlib/src/steplibrary/runcmd.rs b/subplotlib/src/steplibrary/runcmd.rs
new file mode 100644
index 0000000..bceca95
--- /dev/null
+++ b/subplotlib/src/steplibrary/runcmd.rs
@@ -0,0 +1,259 @@
+//! Step library for running subprocesses as part of scenarios
+
+use regex::RegexBuilder;
+
+pub use super::datadir::Datadir;
+pub use crate::prelude::*;
+
+use std::collections::HashMap;
+use std::ffi::OsString;
+use std::io::Write;
+use std::path::PathBuf;
+use std::process::{Command, Stdio};
+
+#[derive(Default)]
+pub struct Runcmd {
+ env: HashMap<OsString, OsString>,
+ // push to "prepend", order reversed when added to env
+ paths: Vec<OsString>,
+ // The following are the result of any executed command
+ exitcode: Option<i32>,
+ stdout: Vec<u8>,
+ stderr: Vec<u8>,
+}
+
+impl ContextElement for Runcmd {
+ fn scenario_starts(&mut self) -> StepResult {
+ self.env.drain();
+ self.paths.drain(..);
+ self.prepend_to_path("/usr/bin");
+ self.prepend_to_path("/bin");
+ Ok(())
+ }
+}
+
+impl Runcmd {
+ pub fn prepend_to_path<S: Into<OsString>>(&mut self, element: S) {
+ self.paths.push(element.into());
+ }
+}
+
+#[step]
+pub fn helper_script(context: &Datadir, script: SubplotDataFile) {
+ context
+ .open_write(script.name())?
+ .write_all(script.data())?;
+}
+
+#[step]
+pub fn helper_srcdir_path(context: &mut Runcmd) {
+ context.prepend_to_path(env!("CARGO_MANIFEST_DIR"));
+}
+
+#[step]
+#[context(Datadir)]
+#[context(Runcmd)]
+pub fn run(context: &ScenarioContext, argv0: &str, args: &str) {
+ try_to_run::call(context, argv0, args)?;
+ exit_code_is::call(context, 0)?;
+}
+
+#[step]
+#[context(Datadir)]
+#[context(Runcmd)]
+pub fn try_to_run(context: &ScenarioContext, argv0: &str, args: &str) {
+ // This is the core of runcmd and is how we handle things
+ let argv0: PathBuf = if argv0.starts_with('.') {
+ context.with(
+ |datadir: &Datadir| datadir.canonicalise_filename(argv0),
+ false,
+ )?
+ } else {
+ argv0.into()
+ };
+ let datadir = context.with(
+ |datadir: &Datadir| Ok(datadir.base_path().to_path_buf()),
+ false,
+ )?;
+ let mut proc = Command::new(argv0);
+ proc.args(&shell_words::split(args)?);
+ proc.current_dir(&datadir);
+ proc.env_clear();
+ proc.env("SHELL", "/bin/sh");
+ proc.env("HOME", &datadir);
+ proc.env("TMPDIR", &datadir);
+ let path = context.with(
+ |runcmd: &Runcmd| Ok(std::env::join_paths(runcmd.paths.iter().rev())?),
+ false,
+ )?;
+ proc.env("PATH", path);
+ context.with(
+ |runcmd: &Runcmd| {
+ for (k, v) in runcmd.env.iter() {
+ proc.env(k, v);
+ }
+ Ok(())
+ },
+ false,
+ )?;
+ proc.stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped());
+ let mut output = proc.output()?;
+ context.with_mut(
+ |runcmd: &mut Runcmd| {
+ std::mem::swap(&mut runcmd.stdout, &mut output.stdout);
+ std::mem::swap(&mut runcmd.stderr, &mut output.stderr);
+ runcmd.exitcode = output.status.code();
+ Ok(())
+ },
+ false,
+ )?;
+}
+
+#[step]
+pub fn exit_code_is(context: &Runcmd, exit: i32) {
+ if context.exitcode != Some(exit) {
+ throw!(format!(
+ "expected exit code {}, but had {:?}",
+ exit, context.exitcode
+ ));
+ }
+}
+
+#[step]
+pub fn exit_code_is_not(context: &Runcmd, exit: i32) {
+ if context.exitcode.is_none() || context.exitcode == Some(exit) {
+ throw!(format!("Expected exit code to not equal {}", exit));
+ }
+}
+
+#[step]
+#[context(Runcmd)]
+pub fn exit_code_is_zero(context: &ScenarioContext) {
+ exit_code_is::call(context, 0)
+}
+
+#[step]
+#[context(Runcmd)]
+pub fn exit_code_is_nonzero(context: &ScenarioContext) {
+ exit_code_is_not::call(context, 0)
+}
+
+enum Stream {
+ Stdout,
+ Stderr,
+}
+enum MatchKind {
+ Exact,
+ Contains,
+ Regex,
+}
+
+#[throws(StepError)]
+fn check_matches(runcmd: &Runcmd, which: Stream, how: MatchKind, against: &str) -> bool {
+ let stream = match which {
+ Stream::Stdout => &runcmd.stdout,
+ Stream::Stderr => &runcmd.stderr,
+ };
+ let against = if matches!(how, MatchKind::Regex) {
+ against.to_string()
+ } else {
+ unescape::unescape(against).ok_or("unable to unescape input")?
+ };
+ match how {
+ MatchKind::Exact => stream == &against.as_bytes(),
+ MatchKind::Contains => stream
+ .windows(against.len())
+ .any(|window| window == against.as_bytes()),
+ MatchKind::Regex => {
+ let stream = String::from_utf8_lossy(stream);
+ let regex = RegexBuilder::new(&against).multi_line(true).build()?;
+ regex.is_match(&stream)
+ }
+ }
+}
+
+#[step]
+pub fn stdout_is(runcmd: &Runcmd, text: &str) {
+ if !check_matches(runcmd, Stream::Stdout, MatchKind::Exact, text)? {
+ throw!(format!("stdout is not {:?}", text));
+ }
+}
+
+#[step]
+pub fn stdout_isnt(runcmd: &Runcmd, text: &str) {
+ if check_matches(runcmd, Stream::Stdout, MatchKind::Exact, text)? {
+ throw!(format!("stdout is exactly {:?}", text));
+ }
+}
+
+#[step]
+pub fn stderr_is(runcmd: &Runcmd, text: &str) {
+ if !check_matches(runcmd, Stream::Stderr, MatchKind::Exact, text)? {
+ throw!(format!("stderr is not {:?}", text));
+ }
+}
+
+#[step]
+pub fn stderr_isnt(runcmd: &Runcmd, text: &str) {
+ if check_matches(runcmd, Stream::Stderr, MatchKind::Exact, text)? {
+ throw!(format!("stderr is exactly {:?}", text));
+ }
+}
+
+#[step]
+pub fn stdout_contains(runcmd: &Runcmd, text: &str) {
+ if !check_matches(runcmd, Stream::Stdout, MatchKind::Contains, text)? {
+ throw!(format!("stdout does not contain {:?}", text));
+ }
+}
+
+#[step]
+pub fn stdout_doesnt_contain(runcmd: &Runcmd, text: &str) {
+ if check_matches(runcmd, Stream::Stdout, MatchKind::Contains, text)? {
+ throw!(format!("stdout contains {:?}", text));
+ }
+}
+
+#[step]
+pub fn stderr_contains(runcmd: &Runcmd, text: &str) {
+ if !check_matches(runcmd, Stream::Stderr, MatchKind::Contains, text)? {
+ throw!(format!("stderr does not contain {:?}", text));
+ }
+}
+
+#[step]
+pub fn stderr_doesnt_contain(runcmd: &Runcmd, text: &str) {
+ if check_matches(runcmd, Stream::Stderr, MatchKind::Contains, text)? {
+ throw!(format!("stderr contains {:?}", text));
+ }
+}
+
+#[step]
+pub fn stdout_matches_regex(runcmd: &Runcmd, regex: &str) {
+ if !check_matches(runcmd, Stream::Stdout, MatchKind::Regex, regex)? {
+ throw!(format!("stdout does not match {:?}", regex));
+ }
+}
+
+#[step]
+pub fn stdout_doesnt_match_regex(runcmd: &Runcmd, regex: &str) {
+ if check_matches(runcmd, Stream::Stdout, MatchKind::Regex, regex)? {
+ throw!(format!("stdout matches {:?}", regex));
+ }
+}
+
+#[step]
+pub fn stderr_matches_regex(runcmd: &Runcmd, regex: &str) {
+ if !check_matches(runcmd, Stream::Stderr, MatchKind::Regex, regex)? {
+ throw!(format!("stderr does not match {:?}", regex));
+ }
+}
+
+#[step]
+pub fn stderr_doesnt_match_regex(runcmd: &Runcmd, regex: &str) {
+ if check_matches(runcmd, Stream::Stderr, MatchKind::Regex, regex)? {
+ throw!(format!("stderr matches {:?}", regex));
+ }
+}
diff --git a/subplotlib/steplibrary/runcmd.yaml b/subplotlib/steplibrary/runcmd.yaml
new file mode 100644
index 0000000..0a635c9
--- /dev/null
+++ b/subplotlib/steplibrary/runcmd.yaml
@@ -0,0 +1,89 @@
+# Bindings for the runcmd steplibrary
+
+- given: helper script {script} for runcmd
+ function: subplotlib::steplibrary::runcmd::helper_script
+ types:
+ script: file
+
+- given: srcdir is in the PATH
+ function: subplotlib::steplibrary::runcmd::helper_srcdir_path
+
+- when: I run (?P<argv0>\S+)(?P<args>.*)
+ regex: true
+ function: subplotlib::steplibrary::runcmd::run
+
+- when: I try to run (?P<argv0>\S+)(?P<args>.*)
+ regex: true
+ function: subplotlib::steplibrary::runcmd::try_to_run
+
+# Steps to examine exit code of latest command.
+
+- then: exit code is {exit}
+ function: subplotlib::steplibrary::runcmd::exit_code_is
+ types:
+ exit: int
+
+- then: exit code is not {exit}
+ function: subplotlib::steplibrary::runcmd::exit_code_is_not
+ types:
+ exit: int
+
+- then: command is successful
+ function: subplotlib::steplibrary::runcmd::exit_code_is_zero
+
+- then: command fails
+ function: subplotlib::steplibrary::runcmd::exit_code_is_nonzero
+
+# Steps to examine stdout/stderr for exact content.
+
+- then: stdout is exactly "(?P<text>.*)"
+ regex: true
+ function: subplotlib::steplibrary::runcmd::stdout_is
+
+- then: 'stdout isn''t exactly "(?P<text>.*)"'
+ regex: true
+ function: subplotlib::steplibrary::runcmd::stdout_isnt
+
+- then: stderr is exactly "(?P<text>.*)"
+ regex: true
+ function: subplotlib::steplibrary::runcmd::stderr_is
+
+- then: 'stderr isn''t exactly "(?P<text>.*)"'
+ regex: true
+ function: subplotlib::steplibrary::runcmd::stderr_isnt
+
+# Steps to examine stdout/stderr for sub-strings.
+
+- then: stdout contains "(?P<text>.*)"
+ regex: true
+ function: subplotlib::steplibrary::runcmd::stdout_contains
+
+- then: 'stdout doesn''t contain "(?P<text>.*)"'
+ regex: true
+ function: subplotlib::steplibrary::runcmd::stdout_doesnt_contain
+
+- then: stderr contains "(?P<text>.*)"
+ regex: true
+ function: subplotlib::steplibrary::runcmd::stderr_contains
+
+- then: 'stderr doesn''t contain "(?P<text>.*)"'
+ regex: true
+ function: subplotlib::steplibrary::runcmd::stderr_doesnt_contain
+
+# Steps to match stdout/stderr against regular expressions.
+
+- then: stdout matches regex (?P<regex>.*)
+ regex: true
+ function: subplotlib::steplibrary::runcmd::stdout_matches_regex
+
+- then: stdout doesn't match regex (?P<regex>.*)
+ regex: true
+ function: subplotlib::steplibrary::runcmd::stdout_doesnt_match_regex
+
+- then: stderr matches regex (?P<regex>.*)
+ regex: true
+ function: subplotlib::steplibrary::runcmd::stderr_matches_regex
+
+- then: stderr doesn't match regex (?P<regex>.*)
+ regex: true
+ function: subplotlib::steplibrary::runcmd::stderr_doesnt_match_regex
diff --git a/subplotlib/tests/runcmd.rs b/subplotlib/tests/runcmd.rs
new file mode 100644
index 0000000..a8514f3
--- /dev/null
+++ b/subplotlib/tests/runcmd.rs
@@ -0,0 +1,543 @@
+use subplotlib::prelude::*;
+
+// --------------------------------
+
+lazy_static! {
+ static ref SUBPLOT_EMBEDDED_FILES: Vec<SubplotDataFile> = vec![SubplotDataFile::new(
+ "ZXJyLnNo",
+ "IyEvYmluL3NoCmVjaG8gIiRAIiAxPiYyCg=="
+ ),];
+}
+
+// ---------------------------------
+
+// Successful execution
+#[test]
+fn successful_execution() {
+ let mut scenario = Scenario::new(&base64_decode("U3VjY2Vzc2Z1bCBleGVjdXRpb24="));
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "/bin/true"
+ &base64_decode("L2Jpbi90cnVl"),
+ )
+ .args(
+ // ""
+ &base64_decode(""),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::exit_code_is::Builder::default()
+ .exit(0)
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::exit_code_is_zero::Builder::default().build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Failed execution
+#[test]
+fn failed_execution() {
+ let mut scenario = Scenario::new(&base64_decode("RmFpbGVkIGV4ZWN1dGlvbg=="));
+
+ let step = subplotlib::steplibrary::runcmd::try_to_run::Builder::default()
+ .argv0(
+ // "/bin/false"
+ &base64_decode("L2Jpbi9mYWxzZQ=="),
+ )
+ .args(
+ // ""
+ &base64_decode(""),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::exit_code_is_not::Builder::default()
+ .exit(0)
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::exit_code_is_nonzero::Builder::default().build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Check stdout is exactly as wanted
+#[test]
+fn check_stdout_is_exactly_as_wanted() {
+ let mut scenario = Scenario::new(&base64_decode(
+ "Q2hlY2sgc3Rkb3V0IGlzIGV4YWN0bHkgYXMgd2FudGVk",
+ ));
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "/bin/echo"
+ &base64_decode("L2Jpbi9lY2hv"),
+ )
+ .args(
+ // " hello, world"
+ &base64_decode("IGhlbGxvLCB3b3JsZA=="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::stdout_is::Builder::default()
+ .text(
+ // "hello, world\n"
+ &base64_decode("aGVsbG8sIHdvcmxkXG4="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Check stderr is exactly as wanted
+#[test]
+fn check_stderr_is_exactly_as_wanted() {
+ let mut scenario = Scenario::new(&base64_decode(
+ "Q2hlY2sgc3RkZXJyIGlzIGV4YWN0bHkgYXMgd2FudGVk",
+ ));
+
+ let step = subplotlib::steplibrary::runcmd::helper_script::Builder::default()
+ .script({
+ use std::path::PathBuf;
+ // err.sh
+ let target_name: PathBuf = base64_decode("ZXJyLnNo").into();
+ SUBPLOT_EMBEDDED_FILES
+ .iter()
+ .find(|df| df.name() == target_name)
+ .expect("Unable to find file at runtime")
+ .clone()
+ })
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "sh"
+ &base64_decode("c2g="),
+ )
+ .args(
+ // " err.sh hello, world"
+ &base64_decode("IGVyci5zaCBoZWxsbywgd29ybGQ="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::stderr_is::Builder::default()
+ .text(
+ // "hello, world\n"
+ &base64_decode("aGVsbG8sIHdvcmxkXG4="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Check stdout using sub-string search
+#[test]
+fn check_stdout_using_sub_string_search() {
+ let mut scenario = Scenario::new(&base64_decode(
+ "Q2hlY2sgc3Rkb3V0IHVzaW5nIHN1Yi1zdHJpbmcgc2VhcmNo",
+ ));
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "/bin/echo"
+ &base64_decode("L2Jpbi9lY2hv"),
+ )
+ .args(
+ // " hello, world"
+ &base64_decode("IGhlbGxvLCB3b3JsZA=="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::stdout_contains::Builder::default()
+ .text(
+ // "world\n"
+ &base64_decode("d29ybGRcbg=="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::exit_code_is::Builder::default()
+ .exit(0)
+ .build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Check stderr using sub-string search
+#[test]
+fn check_stderr_using_sub_string_search() {
+ let mut scenario = Scenario::new(&base64_decode(
+ "Q2hlY2sgc3RkZXJyIHVzaW5nIHN1Yi1zdHJpbmcgc2VhcmNo",
+ ));
+
+ let step = subplotlib::steplibrary::runcmd::helper_script::Builder::default()
+ .script({
+ use std::path::PathBuf;
+ // err.sh
+ let target_name: PathBuf = base64_decode("ZXJyLnNo").into();
+ SUBPLOT_EMBEDDED_FILES
+ .iter()
+ .find(|df| df.name() == target_name)
+ .expect("Unable to find file at runtime")
+ .clone()
+ })
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "sh"
+ &base64_decode("c2g="),
+ )
+ .args(
+ // " err.sh hello, world"
+ &base64_decode("IGVyci5zaCBoZWxsbywgd29ybGQ="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::stderr_contains::Builder::default()
+ .text(
+ // "world\n"
+ &base64_decode("d29ybGRcbg=="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Check stdout using regular expressions
+#[test]
+fn check_stdout_using_regular_expressions() {
+ let mut scenario = Scenario::new(&base64_decode(
+ "Q2hlY2sgc3Rkb3V0IHVzaW5nIHJlZ3VsYXIgZXhwcmVzc2lvbnM=",
+ ));
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "/bin/echo"
+ &base64_decode("L2Jpbi9lY2hv"),
+ )
+ .args(
+ // " hello, world"
+ &base64_decode("IGhlbGxvLCB3b3JsZA=="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::stdout_matches_regex::Builder::default()
+ .regex(
+ // "world$"
+ &base64_decode("d29ybGQk"),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Check stderr using regular expressions
+#[test]
+fn check_stderr_using_regular_expressions() {
+ let mut scenario = Scenario::new(&base64_decode(
+ "Q2hlY2sgc3RkZXJyIHVzaW5nIHJlZ3VsYXIgZXhwcmVzc2lvbnM=",
+ ));
+
+ let step = subplotlib::steplibrary::runcmd::helper_script::Builder::default()
+ .script({
+ use std::path::PathBuf;
+ // err.sh
+ let target_name: PathBuf = base64_decode("ZXJyLnNo").into();
+ SUBPLOT_EMBEDDED_FILES
+ .iter()
+ .find(|df| df.name() == target_name)
+ .expect("Unable to find file at runtime")
+ .clone()
+ })
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "sh"
+ &base64_decode("c2g="),
+ )
+ .args(
+ // " err.sh hello, world"
+ &base64_decode("IGVyci5zaCBoZWxsbywgd29ybGQ="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::stderr_matches_regex::Builder::default()
+ .regex(
+ // "world$"
+ &base64_decode("d29ybGQk"),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Check stdout is not exactly something
+#[test]
+fn check_stdout_is_not_exactly_something() {
+ let mut scenario = Scenario::new(&base64_decode(
+ "Q2hlY2sgc3Rkb3V0IGlzIG5vdCBleGFjdGx5IHNvbWV0aGluZw==",
+ ));
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "/bin/echo"
+ &base64_decode("L2Jpbi9lY2hv"),
+ )
+ .args(
+ // " hi"
+ &base64_decode("IGhp"),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::stdout_isnt::Builder::default()
+ .text(
+ // "hello, world\n"
+ &base64_decode("aGVsbG8sIHdvcmxkXG4="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Check stderr is not exactly something
+#[test]
+fn check_stderr_is_not_exactly_something() {
+ let mut scenario = Scenario::new(&base64_decode(
+ "Q2hlY2sgc3RkZXJyIGlzIG5vdCBleGFjdGx5IHNvbWV0aGluZw==",
+ ));
+
+ let step = subplotlib::steplibrary::runcmd::helper_script::Builder::default()
+ .script({
+ use std::path::PathBuf;
+ // err.sh
+ let target_name: PathBuf = base64_decode("ZXJyLnNo").into();
+ SUBPLOT_EMBEDDED_FILES
+ .iter()
+ .find(|df| df.name() == target_name)
+ .expect("Unable to find file at runtime")
+ .clone()
+ })
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "sh"
+ &base64_decode("c2g="),
+ )
+ .args(
+ // " err.sh hi"
+ &base64_decode("IGVyci5zaCBoaQ=="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::stderr_isnt::Builder::default()
+ .text(
+ // "hello, world\n"
+ &base64_decode("aGVsbG8sIHdvcmxkXG4="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Check stdout doesn’t contain sub-string
+#[test]
+fn check_stdout_doesn_t_contain_sub_string() {
+ let mut scenario = Scenario::new(&base64_decode(
+ "Q2hlY2sgc3Rkb3V0IGRvZXNu4oCZdCBjb250YWluIHN1Yi1zdHJpbmc=",
+ ));
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "/bin/echo"
+ &base64_decode("L2Jpbi9lY2hv"),
+ )
+ .args(
+ // " hi"
+ &base64_decode("IGhp"),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::stdout_doesnt_contain::Builder::default()
+ .text(
+ // "world"
+ &base64_decode("d29ybGQ="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Check stderr doesn’t contain sub-string
+#[test]
+fn check_stderr_doesn_t_contain_sub_string() {
+ let mut scenario = Scenario::new(&base64_decode(
+ "Q2hlY2sgc3RkZXJyIGRvZXNu4oCZdCBjb250YWluIHN1Yi1zdHJpbmc=",
+ ));
+
+ let step = subplotlib::steplibrary::runcmd::helper_script::Builder::default()
+ .script({
+ use std::path::PathBuf;
+ // err.sh
+ let target_name: PathBuf = base64_decode("ZXJyLnNo").into();
+ SUBPLOT_EMBEDDED_FILES
+ .iter()
+ .find(|df| df.name() == target_name)
+ .expect("Unable to find file at runtime")
+ .clone()
+ })
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "sh"
+ &base64_decode("c2g="),
+ )
+ .args(
+ // " err.sh hi"
+ &base64_decode("IGVyci5zaCBoaQ=="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::stderr_doesnt_contain::Builder::default()
+ .text(
+ // "world"
+ &base64_decode("d29ybGQ="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Check stdout doesn’t match regular expression
+#[test]
+fn check_stdout_doesn_t_match_regular_expression() {
+ let mut scenario = Scenario::new(&base64_decode(
+ "Q2hlY2sgc3Rkb3V0IGRvZXNu4oCZdCBtYXRjaCByZWd1bGFyIGV4cHJlc3Npb24=",
+ ));
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "/bin/echo"
+ &base64_decode("L2Jpbi9lY2hv"),
+ )
+ .args(
+ // " hi"
+ &base64_decode("IGhp"),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::stdout_doesnt_match_regex::Builder::default()
+ .regex(
+ // "world$"
+ &base64_decode("d29ybGQk"),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}
+
+// ---------------------------------
+
+// Check stderr doesn’t match regular expressions
+#[test]
+fn check_stderr_doesn_t_match_regular_expressions() {
+ let mut scenario = Scenario::new(&base64_decode(
+ "Q2hlY2sgc3RkZXJyIGRvZXNu4oCZdCBtYXRjaCByZWd1bGFyIGV4cHJlc3Npb25z",
+ ));
+
+ let step = subplotlib::steplibrary::runcmd::helper_script::Builder::default()
+ .script({
+ use std::path::PathBuf;
+ // err.sh
+ let target_name: PathBuf = base64_decode("ZXJyLnNo").into();
+ SUBPLOT_EMBEDDED_FILES
+ .iter()
+ .find(|df| df.name() == target_name)
+ .expect("Unable to find file at runtime")
+ .clone()
+ })
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::run::Builder::default()
+ .argv0(
+ // "sh"
+ &base64_decode("c2g="),
+ )
+ .args(
+ // " err.sh hi"
+ &base64_decode("IGVyci5zaCBoaQ=="),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ let step = subplotlib::steplibrary::runcmd::stderr_doesnt_match_regex::Builder::default()
+ .regex(
+ // "world$"
+ &base64_decode("d29ybGQk"),
+ )
+ .build();
+ scenario.add_step(step, None);
+
+ scenario.run().unwrap();
+}