From 95372ae81f1f1ead72c2c9d1ca898dbda9be0cdb Mon Sep 17 00:00:00 2001 From: soenkehahn Date: Fri, 22 Mar 2019 18:48:17 -0400 Subject: [PATCH 1/8] move assert_eq_yaml to test-utils --- Cargo.lock | 2 ++ test-utils/Cargo.toml | 2 ++ test-utils/src/lib.rs | 11 +++++++++++ tests/holes.rs | 4 ++-- tests/recording.rs | 3 +-- tests/utils.rs | 10 ---------- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63dd289..06edc48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -320,8 +320,10 @@ dependencies = [ name = "test-utils" version = "0.1.0" dependencies = [ + "pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "trim-margin 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index 7fea99f..3692896 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -7,3 +7,5 @@ edition = "2018" [dependencies] tempdir = "*" trim-margin = "*" +yaml-rust = "*" +pretty_assertions = "*" diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 7ca2620..cb97728 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -1,11 +1,13 @@ #![deny(clippy::all)] +use pretty_assertions::assert_eq; use std::collections::VecDeque; use std::fs; use std::path::PathBuf; use std::process::Command; use tempdir::TempDir; use trim_margin::MarginTrimmable; +use yaml_rust::YamlLoader; type R = Result>; @@ -75,3 +77,12 @@ macro_rules! assert_error { assert_eq!(format!("{}", $result.unwrap_err()), $expected); }; } + +pub fn assert_eq_yaml(result: &str, expected: &str) -> R<()> { + let result = + YamlLoader::load_from_str(result).map_err(|error| format!("{}\n({})", error, result))?; + let expected = YamlLoader::load_from_str(expected) + .map_err(|error| format!("{}\n({})", error, expected))?; + assert_eq!(result, expected); + Ok(()) +} diff --git a/tests/holes.rs b/tests/holes.rs index 0ddcc50..78150d9 100644 --- a/tests/holes.rs +++ b/tests/holes.rs @@ -13,8 +13,8 @@ use scriptkeeper::context::Context; use scriptkeeper::utils::path_to_string; use scriptkeeper::{cli, run_main, R}; use std::fs; -use test_utils::trim_margin; -use utils::{assert_eq_yaml, prepare_script}; +use test_utils::{assert_eq_yaml, trim_margin}; +use utils::prepare_script; fn test_holes(script_code: &str, existing: &str, expected: &str) -> R<()> { let (script, test_file) = prepare_script(script_code, existing)?; diff --git a/tests/recording.rs b/tests/recording.rs index f841f68..3dce3f6 100644 --- a/tests/recording.rs +++ b/tests/recording.rs @@ -10,8 +10,7 @@ mod utils; use scriptkeeper::context::Context; use scriptkeeper::{cli, run_main, R}; -use test_utils::{trim_margin, TempFile}; -use utils::assert_eq_yaml; +use test_utils::{assert_eq_yaml, trim_margin, TempFile}; mod yaml_formatting { use super::*; diff --git a/tests/utils.rs b/tests/utils.rs index e6602ae..2c2c509 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -12,7 +12,6 @@ use scriptkeeper::{context::Context, run_scriptkeeper, ExitCode, R}; use std::fs; use std::path::PathBuf; use test_utils::{trim_margin, TempFile}; -use yaml_rust::YamlLoader; fn compare_results(result: (ExitCode, String), expected: Result<(), &str>) { let expected_output = match expected { @@ -57,12 +56,3 @@ pub fn test_run_with_context( pub fn test_run(script_code: &str, tests: &str, expected: Result<(), &str>) -> R<()> { test_run_with_context(&Context::new_mock(), script_code, tests, expected) } - -pub fn assert_eq_yaml(result: &str, expected: &str) -> R<()> { - let result = - YamlLoader::load_from_str(result).map_err(|error| format!("{}\n({})", error, result))?; - let expected = YamlLoader::load_from_str(expected) - .map_err(|error| format!("{}\n({})", error, expected))?; - assert_eq!(result, expected); - Ok(()) -} From 3959160495a139acf4fda423200fe688684b45ea Mon Sep 17 00:00:00 2001 From: soenkehahn Date: Fri, 22 Mar 2019 18:48:17 -0400 Subject: [PATCH 2/8] serialize Steps including stdout field --- src/test_spec/mod.rs | 108 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 13 deletions(-) diff --git a/src/test_spec/mod.rs b/src/test_spec/mod.rs index 9f9e13f..fdf29e8 100644 --- a/src/test_spec/mod.rs +++ b/src/test_spec/mod.rs @@ -77,15 +77,23 @@ impl Step { fn serialize(&self) -> Yaml { let command = Yaml::String(self.command_matcher.format()); - if self.exitcode == 0 { + if self.exitcode == 0 && self.stdout.is_empty() { command } else { let mut step = LinkedHashMap::new(); step.insert(Yaml::from_str("command"), command); - step.insert( - Yaml::from_str("exitcode"), - Yaml::Integer(i64::from(self.exitcode)), - ); + if !self.stdout.is_empty() { + step.insert( + Yaml::from_str("stdout"), + Yaml::String(String::from_utf8_lossy(&self.stdout).into_owned()), + ); + } + if self.exitcode != 0 { + step.insert( + Yaml::from_str("exitcode"), + Yaml::Integer(i64::from(self.exitcode)), + ); + } Yaml::Hash(step) } } @@ -1160,6 +1168,7 @@ mod load { mod serialize { use super::*; use pretty_assertions::assert_eq; + use test_utils::trim_margin; fn roundtrip(tests: Tests) -> R<()> { let yaml = tests.serialize()?; @@ -1208,14 +1217,87 @@ mod serialize { roundtrip(Tests::new(vec![test])) } - #[test] - fn includes_the_step_exitcodes() -> R<()> { - let test = Test::new(vec![Step { - command_matcher: CommandMatcher::ExactMatch(Command::new("cp")?), - stdout: vec![], - exitcode: 42, - }]); - roundtrip(Tests::new(vec![test])) + mod steps { + use super::*; + + #[test] + fn includes_the_step_exitcodes() -> R<()> { + let test = Test::new(vec![Step { + command_matcher: CommandMatcher::ExactMatch(Command::new("cp")?), + stdout: vec![], + exitcode: 42, + }]); + roundtrip(Tests::new(vec![test])) + } + + #[test] + fn includes_stdout_of_steps() -> R<()> { + let protocol = Test::new(vec![Step { + command_matcher: CommandMatcher::ExactMatch(Command::new("cp")?), + stdout: b"foo".to_vec(), + exitcode: 0, + }]); + roundtrip(Tests::new(vec![protocol])) + } + + mod when_serialized_as_object { + use super::*; + use pretty_assertions::assert_eq; + use std::io::Cursor; + use test_utils::assert_eq_yaml; + + #[test] + fn does_not_include_exitcode_when_zero() -> R<()> { + let mut buffer = Cursor::new(vec![]); + write_yaml( + &mut buffer, + &Tests::new(vec![Test::new(vec![Step { + command_matcher: CommandMatcher::ExactMatch(Command::new("cp")?), + stdout: b"foo".to_vec(), + exitcode: 0, + }])]) + .serialize()?, + )?; + assert_eq_yaml( + &String::from_utf8(buffer.into_inner())?, + &trim_margin( + " + |tests: + | - steps: + | - command: cp + | stdout: foo + ", + )?, + )?; + Ok(()) + } + + #[test] + fn does_not_include_stdout_when_empty() -> R<()> { + let mut buffer = Cursor::new(vec![]); + write_yaml( + &mut buffer, + &Tests::new(vec![Test::new(vec![Step { + command_matcher: CommandMatcher::ExactMatch(Command::new("cp")?), + stdout: vec![], + exitcode: 1, + }])]) + .serialize()?, + )?; + assert_eq_yaml( + &String::from_utf8(buffer.into_inner())?, + &trim_margin( + " + |tests: + | - steps: + | - command: cp + | exitcode: 1 + ", + )?, + )?; + Ok(()) + } + } } #[test] From a1c37ee81064f7224b5a66f080bea9211f928477 Mon Sep 17 00:00:00 2001 From: soenkehahn Date: Fri, 22 Mar 2019 18:48:17 -0400 Subject: [PATCH 3/8] refactor ExecutableMock --- src/lib.rs | 8 +-- src/test_checker/executable_mock.rs | 92 +++++++++++++++++++---------- src/test_checker/mod.rs | 19 +++--- 3 files changed, 72 insertions(+), 47 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e7adb5b..3da9e17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ pub mod utils; use crate::context::Context; use crate::recorder::{hole_recorder::run_against_tests, Recorder}; use crate::test_checker::executable_mock; +use crate::test_checker::executable_mock::ExecutableMock; use crate::test_spec::yaml::write_yaml; use crate::test_spec::Tests; use crate::tracer::stdio_redirecting::CaptureStderr; @@ -80,7 +81,7 @@ pub fn run_main(context: &Context, args: &cli::Args) -> R { Ok(match args { cli::Args::ExecutableMock { executable_mock_path, - } => executable_mock::run(context, &executable_mock_path)?, + } => ExecutableMock::run(context, &executable_mock_path)?, cli::Args::Scriptkeeper { script_path, record, @@ -97,20 +98,19 @@ pub fn run_main(context: &Context, args: &cli::Args) -> R { #[cfg(test)] mod run_main { use super::*; - use executable_mock::create_mock_executable; + use crate::test_checker::executable_mock; use test_utils::TempFile; #[test] fn when_passed_executable_mock_flag_behaves_like_executable_mock() -> R<()> { let context = Context::new_mock(); - let executable_contents = create_mock_executable( + let executable_mock = ExecutableMock::new( &context, executable_mock::Config { stdout: b"foo".to_vec(), exitcode: 0, }, )?; - let executable_mock = TempFile::write_temp_script(&executable_contents)?; run_main( &context, &cli::Args::ExecutableMock { diff --git a/src/test_checker/executable_mock.rs b/src/test_checker/executable_mock.rs index caef9dd..ba6bc03 100644 --- a/src/test_checker/executable_mock.rs +++ b/src/test_checker/executable_mock.rs @@ -1,10 +1,14 @@ use crate::context::Context; +use crate::tracer::tracee_memory; +use crate::utils::short_temp_files::ShortTempFile; use crate::{ExitCode, R}; use bincode::{deserialize, serialize}; +use libc::user_regs_struct; +use nix::unistd::Pid; use std::fs; use std::io::Write; use std::os::unix::ffi::OsStrExt; -use std::path::Path; +use std::path::{Path, PathBuf}; #[derive(Debug, Serialize, Deserialize)] pub struct Config { @@ -12,33 +16,59 @@ pub struct Config { pub exitcode: i32, } -pub fn create_mock_executable(context: &Context, config: Config) -> R> { - let mut result = b"#!".to_vec(); - result.append( - &mut context - .scriptkeeper_executable() - .as_os_str() - .as_bytes() - .to_vec(), - ); - result.append(&mut b" --executable-mock\n".to_vec()); - result.append(&mut serialize(&config)?); - Ok(result) +#[derive(Debug)] +pub struct ExecutableMock { + temp_file: ShortTempFile, } -pub fn run(context: &Context, executable_mock_path: &Path) -> R { - let config: Config = deserialize(&skip_hashbang_line(fs::read(executable_mock_path)?))?; - context.stdout().write_all(&config.stdout)?; - Ok(ExitCode(config.exitcode)) -} +impl ExecutableMock { + pub fn new(context: &Context, mock_config: Config) -> R { + let mut contents = b"#!".to_vec(); + contents.append( + &mut context + .scriptkeeper_executable() + .as_os_str() + .as_bytes() + .to_vec(), + ); + contents.append(&mut b" --executable-mock\n".to_vec()); + contents.append(&mut serialize(&mock_config)?); + let temp_file = ShortTempFile::new(&contents)?; + Ok(ExecutableMock { temp_file }) + } -fn skip_hashbang_line(input: Vec) -> Vec { - input - .clone() - .into_iter() - .skip_while(|char: &u8| *char != b'\n') - .skip(1) - .collect() + pub fn path(&self) -> PathBuf { + self.temp_file.path() + } + + pub fn poke_for_execve_syscall( + pid: Pid, + registers: &user_regs_struct, + executable_mock_path: PathBuf, + ) -> R<()> { + tracee_memory::poke_single_word_string( + pid, + registers.rdi, + &executable_mock_path.as_os_str().as_bytes(), + ) + } + + pub fn run(context: &Context, executable_mock_path: &Path) -> R { + let config: Config = deserialize(&ExecutableMock::skip_hashbang_line(fs::read( + executable_mock_path, + )?))?; + context.stdout().write_all(&config.stdout)?; + Ok(ExitCode(config.exitcode)) + } + + fn skip_hashbang_line(input: Vec) -> Vec { + input + .clone() + .into_iter() + .skip_while(|char: &u8| *char != b'\n') + .skip(1) + .collect() + } } #[cfg(test)] @@ -48,28 +78,28 @@ mod test { use test_utils::TempFile; #[test] - fn renders_an_executable_that_outputs_the_given_stdout() -> R<()> { - let mock_executable = TempFile::write_temp_script(&create_mock_executable( + fn creates_an_executable_that_outputs_the_given_stdout() -> R<()> { + let mock_executable = ExecutableMock::new( &Context::new_mock(), Config { stdout: b"foo".to_vec(), exitcode: 0, }, - )?)?; + )?; let output = Command::new(mock_executable.path()).output()?; assert_eq!(output.stdout, b"foo"); Ok(()) } #[test] - fn renders_an_executable_that_exits_with_the_given_exitcode() -> R<()> { - let mock_executable = TempFile::write_temp_script(&create_mock_executable( + fn creates_an_executable_that_exits_with_the_given_exitcode() -> R<()> { + let mock_executable = ExecutableMock::new( &Context::new_mock(), Config { stdout: b"foo".to_vec(), exitcode: 42, }, - )?)?; + )?; let output = Command::new(mock_executable.path()).output()?; assert_eq!(output.status.code(), Some(42)); Ok(()) diff --git a/src/test_checker/mod.rs b/src/test_checker/mod.rs index 08ed203..83ae294 100644 --- a/src/test_checker/mod.rs +++ b/src/test_checker/mod.rs @@ -9,6 +9,7 @@ use crate::tracer::{tracee_memory, SyscallMock}; use crate::utils::short_temp_files::ShortTempFile; use crate::R; use checker_result::CheckerResult; +use executable_mock::ExecutableMock; use libc::{c_ulonglong, user_regs_struct}; use nix::sys::ptrace; use nix::unistd::Pid; @@ -22,7 +23,7 @@ pub struct TestChecker { pub test: Test, pub unmocked_commands: Vec, pub result: CheckerResult, - temporary_executables: Vec, + temporary_executables: Vec, } impl TestChecker { @@ -62,11 +63,9 @@ impl TestChecker { TestChecker::allow_failing_scripts_to_continue() } }; - let mock_executable_contents = - executable_mock::create_mock_executable(&self.context, mock_config)?; - let temp_executable = ShortTempFile::new(&mock_executable_contents)?; - let path = temp_executable.path(); - self.temporary_executables.push(temp_executable); + let executable_mock = ExecutableMock::new(&self.context, mock_config)?; + let path = executable_mock.path(); + self.temporary_executables.push(executable_mock); Ok(path) } @@ -102,15 +101,11 @@ impl SyscallMock for TestChecker { .iter() .any(|unmocked_command| test_spec::compare_executables(unmocked_command, &executable)); if !is_unmocked_command { - let mock_executable_path = self.handle_step(test_spec::Command { + let executable_mock_path = self.handle_step(test_spec::Command { executable, arguments, })?; - tracee_memory::poke_single_word_string( - pid, - registers.rdi, - &mock_executable_path.as_os_str().as_bytes(), - )?; + ExecutableMock::poke_for_execve_syscall(pid, registers, executable_mock_path)?; } Ok(()) } From 55e054e6d3f99fc80f278e70973a530202cd4e37 Mon Sep 17 00:00:00 2001 From: soenkehahn Date: Fri, 22 Mar 2019 18:48:17 -0400 Subject: [PATCH 4/8] move executable_mock module to the top-level --- src/{test_checker => }/executable_mock.rs | 0 src/lib.rs | 6 +++--- src/test_checker/mod.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/{test_checker => }/executable_mock.rs (100%) diff --git a/src/test_checker/executable_mock.rs b/src/executable_mock.rs similarity index 100% rename from src/test_checker/executable_mock.rs rename to src/executable_mock.rs diff --git a/src/lib.rs b/src/lib.rs index 3da9e17..3322b74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ extern crate memoffset; pub mod cli; pub mod context; +mod executable_mock; mod recorder; mod test_checker; mod test_spec; @@ -21,9 +22,8 @@ mod tracer; pub mod utils; use crate::context::Context; +use crate::executable_mock::ExecutableMock; use crate::recorder::{hole_recorder::run_against_tests, Recorder}; -use crate::test_checker::executable_mock; -use crate::test_checker::executable_mock::ExecutableMock; use crate::test_spec::yaml::write_yaml; use crate::test_spec::Tests; use crate::tracer::stdio_redirecting::CaptureStderr; @@ -98,7 +98,7 @@ pub fn run_main(context: &Context, args: &cli::Args) -> R { #[cfg(test)] mod run_main { use super::*; - use crate::test_checker::executable_mock; + use crate::executable_mock; use test_utils::TempFile; #[test] diff --git a/src/test_checker/mod.rs b/src/test_checker/mod.rs index 83ae294..8cddb86 100644 --- a/src/test_checker/mod.rs +++ b/src/test_checker/mod.rs @@ -1,7 +1,8 @@ pub mod checker_result; -pub mod executable_mock; use crate::context::Context; +use crate::executable_mock; +use crate::executable_mock::ExecutableMock; use crate::test_spec; use crate::test_spec::Test; use crate::tracer::stdio_redirecting::Redirector; @@ -9,7 +10,6 @@ use crate::tracer::{tracee_memory, SyscallMock}; use crate::utils::short_temp_files::ShortTempFile; use crate::R; use checker_result::CheckerResult; -use executable_mock::ExecutableMock; use libc::{c_ulonglong, user_regs_struct}; use nix::sys::ptrace; use nix::unistd::Pid; From 2d0375bf9c0a91d03bbd324275a8b160c2677758 Mon Sep 17 00:00:00 2001 From: soenkehahn Date: Fri, 22 Mar 2019 18:48:17 -0400 Subject: [PATCH 5/8] wip: failing test --- tests/recording.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/recording.rs b/tests/recording.rs index 3dce3f6..a795506 100644 --- a/tests/recording.rs +++ b/tests/recording.rs @@ -8,7 +8,9 @@ #[path = "./utils.rs"] mod utils; +use quale::which; use scriptkeeper::context::Context; +use scriptkeeper::utils::path_to_string; use scriptkeeper::{cli, run_main, R}; use test_utils::{assert_eq_yaml, trim_margin, TempFile}; @@ -150,3 +152,24 @@ fn records_command_exitcodes() -> R<()> { "#, ) } + +#[test] +#[ignore] +fn records_stdout_of_commands() -> R<()> { + let echo = which("echo").ok_or("echo not found in $PATH")?; + test_recording( + &format!( + " + |#!/usr/bin/env bash + |{} foo > /dev/null + ", + path_to_string(&echo)? + ), + " + |protocols: + | - protocol: + | - command: echo foo + | stdout: foo + ", + ) +} From 18e654a92e8874b622c44f018972fdc993343556 Mon Sep 17 00:00:00 2001 From: soenkehahn Date: Thu, 4 Apr 2019 14:38:13 -0400 Subject: [PATCH 6/8] implement executable_mock wrapper that executes given commands --- Justfile | 5 ++ src/executable_mock.rs | 108 +++++++++++++++++++++++++++++----------- src/lib.rs | 2 +- src/test_checker/mod.rs | 5 +- 4 files changed, 86 insertions(+), 34 deletions(-) diff --git a/Justfile b/Justfile index 135f054..020b106 100755 --- a/Justfile +++ b/Justfile @@ -23,6 +23,11 @@ dev: clear ; printf "\e[3J" cargo test --all --color=always --features 'dev test' -- --test-threads=1 --quiet +unit_test: + clear ; printf "\e[3J" + cargo build --color=always --features=dev + cargo test --lib --all --color=always --features 'dev test' -- --test-threads=1 --quiet + run_bigger: cargo run -- tests/examples/bigger/script diff --git a/src/executable_mock.rs b/src/executable_mock.rs index ba6bc03..6bd2ec5 100644 --- a/src/executable_mock.rs +++ b/src/executable_mock.rs @@ -9,11 +9,12 @@ use std::fs; use std::io::Write; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; +use std::process::Command; #[derive(Debug, Serialize, Deserialize)] -pub struct Config { - pub stdout: Vec, - pub exitcode: i32, +pub enum Config { + Config { stdout: Vec, exitcode: i32 }, + Wrapper { executable: PathBuf }, } #[derive(Debug)] @@ -37,6 +38,15 @@ impl ExecutableMock { Ok(ExecutableMock { temp_file }) } + fn wrapper(context: &Context, executable: &Path) -> R { + ExecutableMock::new( + context, + Config::Wrapper { + executable: executable.to_owned(), + }, + ) + } + pub fn path(&self) -> PathBuf { self.temp_file.path() } @@ -57,8 +67,16 @@ impl ExecutableMock { let config: Config = deserialize(&ExecutableMock::skip_hashbang_line(fs::read( executable_mock_path, )?))?; - context.stdout().write_all(&config.stdout)?; - Ok(ExitCode(config.exitcode)) + match config { + Config::Config { stdout, exitcode } => { + context.stdout().write_all(&stdout)?; + Ok(ExitCode(exitcode)) + } + Config::Wrapper { executable } => { + Command::new(&executable).output()?; + Ok(ExitCode(0)) + } + } } fn skip_hashbang_line(input: Vec) -> Vec { @@ -75,33 +93,63 @@ impl ExecutableMock { mod test { use super::*; use std::process::Command; - use test_utils::TempFile; + use test_utils::{trim_margin, TempFile}; - #[test] - fn creates_an_executable_that_outputs_the_given_stdout() -> R<()> { - let mock_executable = ExecutableMock::new( - &Context::new_mock(), - Config { - stdout: b"foo".to_vec(), - exitcode: 0, - }, - )?; - let output = Command::new(mock_executable.path()).output()?; - assert_eq!(output.stdout, b"foo"); - Ok(()) + mod new { + use super::*; + + #[test] + fn creates_an_executable_that_outputs_the_given_stdout() -> R<()> { + let executable_mock = ExecutableMock::new( + &Context::new_mock(), + Config::Config { + stdout: b"foo".to_vec(), + exitcode: 0, + }, + )?; + let output = Command::new(&executable_mock.path()).output(); + assert_eq!(output?.stdout, b"foo"); + Ok(()) + } + + #[test] + fn creates_an_executable_that_exits_with_the_given_exitcode() -> R<()> { + let executable_mock = ExecutableMock::new( + &Context::new_mock(), + Config::Config { + stdout: b"foo".to_vec(), + exitcode: 42, + }, + )?; + let output = Command::new(executable_mock.path()).output()?; + assert_eq!(output.status.code(), Some(42)); + Ok(()) + } } - #[test] - fn creates_an_executable_that_exits_with_the_given_exitcode() -> R<()> { - let mock_executable = ExecutableMock::new( - &Context::new_mock(), - Config { - stdout: b"foo".to_vec(), - exitcode: 42, - }, - )?; - let output = Command::new(mock_executable.path()).output()?; - assert_eq!(output.status.code(), Some(42)); - Ok(()) + mod wrapper { + use super::*; + use crate::utils::path_to_string; + use tempdir::TempDir; + + #[test] + fn executes_the_given_command() -> R<()> { + let temp_dir = TempDir::new("test")?; + let path = temp_dir.path().join("foo.txt"); + let script = TempFile::write_temp_script( + trim_margin(&format!( + " + |#!/usr/bin/env bash + |echo foo > {} + ", + path_to_string(&path)? + ))? + .as_bytes(), + )?; + let executable_mock = ExecutableMock::wrapper(&Context::new_mock(), &script.path())?; + Command::new(executable_mock.path()).status()?; + assert_eq!(String::from_utf8(fs::read(&path)?)?, "foo\n"); + Ok(()) + } } } diff --git a/src/lib.rs b/src/lib.rs index 3322b74..229c689 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,7 +106,7 @@ mod run_main { let context = Context::new_mock(); let executable_mock = ExecutableMock::new( &context, - executable_mock::Config { + executable_mock::Config::Config { stdout: b"foo".to_vec(), exitcode: 0, }, diff --git a/src/test_checker/mod.rs b/src/test_checker/mod.rs index 8cddb86..c077270 100644 --- a/src/test_checker/mod.rs +++ b/src/test_checker/mod.rs @@ -7,7 +7,6 @@ use crate::test_spec; use crate::test_spec::Test; use crate::tracer::stdio_redirecting::Redirector; use crate::tracer::{tracee_memory, SyscallMock}; -use crate::utils::short_temp_files::ShortTempFile; use crate::R; use checker_result::CheckerResult; use libc::{c_ulonglong, user_regs_struct}; @@ -38,7 +37,7 @@ impl TestChecker { } fn allow_failing_scripts_to_continue() -> executable_mock::Config { - executable_mock::Config { + executable_mock::Config::Config { stdout: vec![], exitcode: 0, } @@ -53,7 +52,7 @@ impl TestChecker { &received.format(), ); } - executable_mock::Config { + executable_mock::Config::Config { stdout: next_test_step.stdout, exitcode: next_test_step.exitcode, } From 8a2151b0f3c6362c8bd59b68027d83a32f62f008 Mon Sep 17 00:00:00 2001 From: soenkehahn Date: Thu, 4 Apr 2019 15:13:12 -0400 Subject: [PATCH 7/8] add test for relaying stdout --- src/executable_mock.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/executable_mock.rs b/src/executable_mock.rs index 6bd2ec5..4277b84 100644 --- a/src/executable_mock.rs +++ b/src/executable_mock.rs @@ -73,7 +73,7 @@ impl ExecutableMock { Ok(ExitCode(exitcode)) } Config::Wrapper { executable } => { - Command::new(&executable).output()?; + Command::new(&executable).status()?; Ok(ExitCode(0)) } } @@ -135,12 +135,12 @@ mod test { #[test] fn executes_the_given_command() -> R<()> { let temp_dir = TempDir::new("test")?; - let path = temp_dir.path().join("foo.txt"); + let path = temp_dir.path().join("foo"); let script = TempFile::write_temp_script( trim_margin(&format!( " |#!/usr/bin/env bash - |echo foo > {} + |touch {} ", path_to_string(&path)? ))? @@ -148,7 +148,24 @@ mod test { )?; let executable_mock = ExecutableMock::wrapper(&Context::new_mock(), &script.path())?; Command::new(executable_mock.path()).status()?; - assert_eq!(String::from_utf8(fs::read(&path)?)?, "foo\n"); + assert!(path.exists()); + Ok(()) + } + + #[test] + fn relays_stdout() -> R<()> { + let script = TempFile::write_temp_script( + trim_margin( + " + |#!/usr/bin/env bash + |echo foo + ", + )? + .as_bytes(), + )?; + let executable_mock = ExecutableMock::wrapper(&Context::new_mock(), &script.path())?; + let output = Command::new(executable_mock.path()).output()?; + assert_eq!(String::from_utf8(output.stdout)?, "foo\n"); Ok(()) } } From b6c680feca89d8326e0c1cad178ae7bd9db0fbf3 Mon Sep 17 00:00:00 2001 From: soenkehahn Date: Thu, 4 Apr 2019 15:13:32 -0400 Subject: [PATCH 8/8] wip --- Justfile | 2 +- src/executable_mock.rs | 21 ++++++++++++++++++++- src/lib.rs | 2 +- src/recorder/hole_recorder.rs | 4 +++- src/recorder/mod.rs | 22 ++++++++++++++++++---- src/test_checker/mod.rs | 2 +- 6 files changed, 44 insertions(+), 9 deletions(-) diff --git a/Justfile b/Justfile index 020b106..252162d 100755 --- a/Justfile +++ b/Justfile @@ -21,7 +21,7 @@ scripts: dev: clear ; printf "\e[3J" - cargo test --all --color=always --features 'dev test' -- --test-threads=1 --quiet + cargo test --all --color=always --features 'dev test' -- --test-threads=1 unit_test: clear ; printf "\e[3J" diff --git a/src/executable_mock.rs b/src/executable_mock.rs index 4277b84..680731e 100644 --- a/src/executable_mock.rs +++ b/src/executable_mock.rs @@ -38,7 +38,7 @@ impl ExecutableMock { Ok(ExecutableMock { temp_file }) } - fn wrapper(context: &Context, executable: &Path) -> R { + pub fn wrapper(context: &Context, executable: &Path) -> R { ExecutableMock::new( context, Config::Wrapper { @@ -168,5 +168,24 @@ mod test { assert_eq!(String::from_utf8(output.stdout)?, "foo\n"); Ok(()) } + + #[test] + fn relays_the_process_environment() -> R<()> { + let script = TempFile::write_temp_script( + trim_margin( + " + |#!/usr/bin/env bash + |echo $FOO + ", + )? + .as_bytes(), + )?; + let executable_mock = ExecutableMock::wrapper(&Context::new_mock(), &script.path())?; + let output = Command::new(executable_mock.path()) + .env("FOO", "bar") + .output()?; + assert_eq!(String::from_utf8(output.stdout)?, "bar\n"); + Ok(()) + } } } diff --git a/src/lib.rs b/src/lib.rs index 229c689..ae14158 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -141,7 +141,7 @@ fn print_recorded_test(context: &Context, program: &Path) -> R { vec![], HashMap::new(), CaptureStderr::NoCapture, - Recorder::empty(), + Recorder::empty(context), )?; write_yaml(&mut *context.stdout(), &Tests::new(vec![test]).serialize()?)?; Ok(ExitCode(0)) diff --git a/src/recorder/hole_recorder.rs b/src/recorder/hole_recorder.rs index 5a21cbe..f58a2fb 100644 --- a/src/recorder/hole_recorder.rs +++ b/src/recorder/hole_recorder.rs @@ -56,6 +56,7 @@ impl SyscallMock for HoleRecorder { CheckerResult::Pass => { *self = HoleRecorder::Recorder { recorder: Recorder::new( + &checker.context, original_test.clone(), &checker.unmocked_commands, ), @@ -86,7 +87,8 @@ impl SyscallMock for HoleRecorder { } => match checker.result { CheckerResult::Pass => { original_test.ends_with_hole = false; - let recorder = Recorder::new(original_test, &checker.unmocked_commands); + let recorder = + Recorder::new(&checker.context, original_test, &checker.unmocked_commands); RecorderResult::Recorded(recorder.handle_end(exitcode, redirector)?) } failure @ CheckerResult::Failure(_) => { diff --git a/src/recorder/mod.rs b/src/recorder/mod.rs index 602c397..53b3614 100644 --- a/src/recorder/mod.rs +++ b/src/recorder/mod.rs @@ -1,6 +1,8 @@ pub mod hole_recorder; mod result; +use crate::context::Context; +use crate::executable_mock::ExecutableMock; use crate::test_spec::command::Command; use crate::test_spec::command_matcher::CommandMatcher; use crate::test_spec::{compare_executables, Step, Test}; @@ -13,25 +15,32 @@ use std::ffi::OsString; use std::path::PathBuf; pub struct Recorder { + context: Context, test: Test, command: Option, unmocked_commands: Vec, + temporary_executables: Vec, } impl Recorder { - pub fn empty() -> Recorder { + pub fn empty(context: &Context) -> Recorder { + // fixme: use new Recorder { + context: context.clone(), test: Test::new(vec![]), command: None, unmocked_commands: vec![], + temporary_executables: vec![], } } - pub fn new(test: Test, unmocked_commands: &[PathBuf]) -> Recorder { + pub fn new(context: &Context, test: Test, unmocked_commands: &[PathBuf]) -> Recorder { Recorder { + context: context.clone(), test, command: None, unmocked_commands: unmocked_commands.to_vec(), + temporary_executables: vec![], } } } @@ -41,8 +50,8 @@ impl SyscallMock for Recorder { fn handle_execve_enter( &mut self, - _pid: Pid, - _registers: &user_regs_struct, + pid: Pid, + registers: &user_regs_struct, executable: PathBuf, arguments: Vec, ) -> R<()> { @@ -51,6 +60,11 @@ impl SyscallMock for Recorder { .iter() .any(|unmocked_command| compare_executables(unmocked_command, &executable)); if !is_unmocked_command { + let executable_mock_path = ExecutableMock::wrapper(&self.context, &executable)?; + let path = executable_mock_path.path(); + self.temporary_executables.push(executable_mock_path); + ExecutableMock::poke_for_execve_syscall(pid, registers, path)?; + self.command = Some(Command { executable, arguments, diff --git a/src/test_checker/mod.rs b/src/test_checker/mod.rs index c077270..f5722d0 100644 --- a/src/test_checker/mod.rs +++ b/src/test_checker/mod.rs @@ -18,7 +18,7 @@ use std::path::PathBuf; #[derive(Debug)] pub struct TestChecker { - context: Context, + pub context: Context, pub test: Test, pub unmocked_commands: Vec, pub result: CheckerResult,