diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2020-12-30 13:27:26 +0000 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2020-12-30 13:27:26 +0000 |
commit | f10bb254669cdf97fb9850865a9e216a30a69f2a (patch) | |
tree | 549038b2f74bfdc4bc5f139b69748abd612db7f5 /subplotlib | |
parent | 8704ec0f4a9052ef1344692349e36c8118ea5702 (diff) | |
download | subplot-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.toml | 4 | ||||
-rw-r--r-- | subplotlib/runcmd.md | 166 | ||||
-rw-r--r-- | subplotlib/src/steplibrary.rs | 1 | ||||
-rw-r--r-- | subplotlib/src/steplibrary/runcmd.rs | 259 | ||||
-rw-r--r-- | subplotlib/steplibrary/runcmd.yaml | 89 | ||||
-rw-r--r-- | subplotlib/tests/runcmd.rs | 543 |
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(); +} |