From b0ae52b509c82b85bc9757a6ba7206b31cdf8d19 Mon Sep 17 00:00:00 2001 From: soenkehahn Date: Mon, 25 Mar 2019 18:42:23 -0400 Subject: [PATCH 1/3] add mockedExecutables field to Protocols parsing --- src/protocol/mod.rs | 48 ++++++++++++++++++++++++++++++--- src/recorder/hole_recorder.rs | 1 + src/recorder/protocol_result.rs | 1 + 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index d447d3e..51c740b 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -369,6 +369,7 @@ pub struct Protocols { pub protocols: Vec, pub unmocked_commands: Vec, pub interpreter: Option, + pub mocked_executables: Vec, } impl Protocols { @@ -377,6 +378,7 @@ impl Protocols { protocols, unmocked_commands: vec![], interpreter: None, + mocked_executables: vec![], } } @@ -405,6 +407,16 @@ impl Protocols { Ok(()) } + fn add_mocked_executables(&mut self, object: &Hash) -> R<()> { + if let Ok(mocked_executables) = object.expect_field("mockedExecutables") { + for mocked_executable in mocked_executables.expect_array()? { + self.mocked_executables + .push(PathBuf::from(mocked_executable.expect_str()?)); + } + } + Ok(()) + } + fn parse(yaml: Yaml) -> R { Ok(match &yaml { Yaml::Array(array) => Protocols::from_array(&array)?, @@ -413,10 +425,19 @@ impl Protocols { object.expect_field("protocol"), ) { (Ok(protocols), _) => { - check_keys(&["protocols", "interpreter", "unmockedCommands"], object)?; + check_keys( + &[ + "protocols", + "interpreter", + "unmockedCommands", + "mockedExecutables", + ], + object, + )?; let mut protocols = Protocols::from_array(protocols.expect_array()?)?; protocols.add_unmocked_commands(object)?; protocols.add_interpreter(object)?; + protocols.add_mocked_executables(object)?; protocols } (Err(_), Ok(_)) => Protocols::new(vec![Protocol::from_object(&object)?]), @@ -559,7 +580,8 @@ mod load { format!( "error in {}.protocols.yaml: \ unexpected field 'foo', \ - possible values: 'protocols', 'interpreter', 'unmockedCommands'", + possible values: 'protocols', 'interpreter', 'unmockedCommands', \ + 'mockedExecutables'", path_to_string(&tempfile.path())? ) ); @@ -999,7 +1021,27 @@ mod load { )? .mocked_files .map(|path| path.to_string_lossy().to_string()), - vec![("/foo")] + vec!["/foo"] + ); + Ok(()) + } + + #[test] + fn allows_to_specify_mocked_executables() -> R<()> { + let tempfile = TempFile::new()?; + assert_eq!( + test_parse( + &tempfile, + r" + |protocols: + | - protocol: [] + |mockedExecutables: + | - foo + " + )? + .mocked_executables + .map(|path| path.to_string_lossy().to_string()), + vec!["foo"] ); Ok(()) } diff --git a/src/recorder/hole_recorder.rs b/src/recorder/hole_recorder.rs index 3321be7..f534433 100644 --- a/src/recorder/hole_recorder.rs +++ b/src/recorder/hole_recorder.rs @@ -112,6 +112,7 @@ pub fn run_against_protocols( protocols, unmocked_commands, interpreter, + .. }: Protocols, ) -> R { let results = ProtocolResult::collect_results( diff --git a/src/recorder/protocol_result.rs b/src/recorder/protocol_result.rs index 4dba55a..8eb3156 100644 --- a/src/recorder/protocol_result.rs +++ b/src/recorder/protocol_result.rs @@ -100,6 +100,7 @@ impl ProtocolResult { protocols: results.iter().map(|result| result.get_protocol()).collect(), unmocked_commands, interpreter: None, + mocked_executables: vec![], } .serialize()?, )?; From ff71eacc9aafc6615a40c4cd5f321d2c50249fcb Mon Sep 17 00:00:00 2001 From: soenkehahn Date: Mon, 25 Mar 2019 18:09:20 -0400 Subject: [PATCH 2/3] wip: implement with absolute paths --- src/protocol_checker/mod.rs | 19 +++++++++++++++++++ src/recorder/hole_recorder.rs | 11 +++++++++-- src/recorder/protocol_result.rs | 17 +++++++++++++++-- tests/files.rs | 22 ++++++++++++++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/protocol_checker/mod.rs b/src/protocol_checker/mod.rs index ec4ba33..ad1a576 100644 --- a/src/protocol_checker/mod.rs +++ b/src/protocol_checker/mod.rs @@ -21,6 +21,7 @@ pub struct ProtocolChecker { context: Context, pub protocol: Protocol, pub unmocked_commands: Vec, + pub mocked_executables: Vec, pub result: CheckerResult, temporary_executables: Vec, } @@ -30,11 +31,13 @@ impl ProtocolChecker { context: &Context, protocol: Protocol, unmocked_commands: &[PathBuf], + mocked_executables: &[PathBuf], ) -> ProtocolChecker { ProtocolChecker { context: context.clone(), protocol, unmocked_commands: unmocked_commands.to_vec(), + mocked_executables: mocked_executables.to_vec(), result: CheckerResult::Pass, temporary_executables: vec![], } @@ -149,6 +152,22 @@ impl SyscallMock for ProtocolChecker { let mut registers = *registers; registers.rax = 0; ptrace::setregs(pid, registers)?; + } else if self + .mocked_executables + .iter() + .any(|mocked_executable| PathBuf::from("/bin").join(mocked_executable) == filename) + { + let statbuf_ptr = registers.rsi; + let mock_mode = 0; + #[allow(clippy::forget_copy)] + tracee_memory::poke_four_bytes( + pid, + statbuf_ptr + (offset_of!(libc::stat, st_mode) as u64), + mock_mode as u32, + )?; + let mut registers = *registers; + registers.rax = 0; + ptrace::setregs(pid, registers)?; } Ok(()) } diff --git a/src/recorder/hole_recorder.rs b/src/recorder/hole_recorder.rs index f534433..7255f5d 100644 --- a/src/recorder/hole_recorder.rs +++ b/src/recorder/hole_recorder.rs @@ -26,10 +26,16 @@ impl HoleRecorder { pub fn new( context: &Context, unmocked_commands: &[PathBuf], + mocked_executables: &[PathBuf], protocol: Protocol, ) -> HoleRecorder { HoleRecorder::Checker { - checker: ProtocolChecker::new(context, protocol.clone(), unmocked_commands), + checker: ProtocolChecker::new( + context, + protocol.clone(), + unmocked_commands, + mocked_executables, + ), original_protocol: protocol, } } @@ -112,7 +118,7 @@ pub fn run_against_protocols( protocols, unmocked_commands, interpreter, - .. + mocked_executables, }: Protocols, ) -> R { let results = ProtocolResult::collect_results( @@ -121,6 +127,7 @@ pub fn run_against_protocols( program, protocols, &unmocked_commands, + &mocked_executables, )?; ProtocolResult::handle_results(context, protocols_file, unmocked_commands, &results) } diff --git a/src/recorder/protocol_result.rs b/src/recorder/protocol_result.rs index 8eb3156..7fa3750 100644 --- a/src/recorder/protocol_result.rs +++ b/src/recorder/protocol_result.rs @@ -45,6 +45,7 @@ impl ProtocolResult { program: &Path, protocols: Vec, unmocked_commands: &[PathBuf], + mocked_executables: &[PathBuf], ) -> R> { let mut results = vec![]; for protocol in protocols.into_iter() { @@ -53,6 +54,7 @@ impl ProtocolResult { &interpreter, program, unmocked_commands, + mocked_executables, protocol, )?); } @@ -119,6 +121,7 @@ fn run_against_protocol( interpreter: &Option, program: &Path, unmocked_commands: &[PathBuf], + mocked_executables: &[PathBuf], protocol: Protocol, ) -> R { macro_rules! run_against_mock { @@ -139,11 +142,21 @@ fn run_against_protocol( }; } if protocol.ends_with_hole { - run_against_mock!(HoleRecorder::new(context, unmocked_commands, protocol)) + run_against_mock!(HoleRecorder::new( + context, + unmocked_commands, + mocked_executables, + protocol + )) } else { Ok(ProtocolResult::Checked( protocol.clone(), - run_against_mock!(ProtocolChecker::new(context, protocol, unmocked_commands))?, + run_against_mock!(ProtocolChecker::new( + context, + protocol, + unmocked_commands, + mocked_executables + ))?, )) } } diff --git a/tests/files.rs b/tests/files.rs index e1e1c8a..b9042c2 100644 --- a/tests/files.rs +++ b/tests/files.rs @@ -70,3 +70,25 @@ fn does_not_mock_existence_of_unspecified_files() -> R<()> { )?; Ok(()) } + +#[test] +fn allows_to_mock_executable_existence() -> R<()> { + test_run( + r" + |#!/usr/bin/env bash + |/bin/does_not_exist + ", + r" + |protocols: + | - protocol: + | - /bin/does_not_exist + |mockedExecutables: + | - does_not_exist + ", + Ok(()), + )?; + Ok(()) +} + +#[test] +fn works_with_absolute_executables() {} From 0c00f698755ef51b7f03ccf819594f6b8fe2ba77 Mon Sep 17 00:00:00 2001 From: soenkehahn Date: Tue, 26 Mar 2019 17:13:51 -0400 Subject: [PATCH 3/3] add a mocked_executables parameter to EVERYTHING!!! --- src/lib.rs | 2 +- src/protocol/command.rs | 17 +++++---- src/protocol/command_matcher.rs | 32 ++++++++--------- src/protocol/executable_path.rs | 42 +++++++++++++++------- src/protocol/mod.rs | 14 ++++---- src/protocol_checker/mod.rs | 27 +++++++++----- src/recorder/hole_recorder.rs | 15 ++++++-- src/recorder/mod.rs | 16 ++++++--- src/recorder/protocol_result.rs | 5 ++- tests/files.rs | 63 ++++++++++++++++++++++----------- 10 files changed, 153 insertions(+), 80 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9d3f103..f4d9fc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -145,7 +145,7 @@ fn print_recorded_protocol(context: &Context, program: &Path) -> R { )?; write_yaml( &mut *context.stdout(), - &Protocols::new(vec![protocol]).serialize()?, + &Protocols::new(vec![protocol]).serialize(&[])?, )?; Ok(ExitCode(0)) } diff --git a/src/protocol/command.rs b/src/protocol/command.rs index aa93446..634b1b5 100644 --- a/src/protocol/command.rs +++ b/src/protocol/command.rs @@ -20,9 +20,12 @@ impl Command { } } - pub fn compare(&self, other: &Command) -> bool { - executable_path::compare_executables(&self.executable, &other.executable) - && self.arguments == other.arguments + pub fn compare(&self, mocked_executables: &[PathBuf], other: &Command) -> bool { + executable_path::compare_executables( + mocked_executables, + &self.executable, + &other.executable, + ) && self.arguments == other.arguments } fn escape(word: String) -> String { @@ -46,8 +49,8 @@ impl Command { .join(" ") } - pub fn format(&self) -> String { - let executable = executable_path::canonicalize(&self.executable) + pub fn format(&self, mocked_executables: &[PathBuf]) -> String { + let executable = executable_path::canonicalize(mocked_executables, &self.executable) .to_string_lossy() .into_owned(); if self.arguments.is_empty() { @@ -251,7 +254,7 @@ mod command { ($name:ident, $string:expr) => { #[test] fn $name() -> R<()> { - assert_eq!(Command::new($string)?.format(), $string); + assert_eq!(Command::new($string)?.format(&[]), $string); Ok(()) } }; @@ -261,7 +264,7 @@ mod command { ($name:ident, $input:expr, $normalized:expr) => { #[test] fn $name() -> R<()> { - assert_eq!(Command::new($input)?.format(), $normalized); + assert_eq!(Command::new($input)?.format(&[]), $normalized); Ok(()) } }; diff --git a/src/protocol/command_matcher.rs b/src/protocol/command_matcher.rs index ee54ca7..e469685 100644 --- a/src/protocol/command_matcher.rs +++ b/src/protocol/command_matcher.rs @@ -1,6 +1,7 @@ use super::command::Command; use crate::R; use regex::Regex; +use std::path::PathBuf; use std::str; #[derive(Debug, Clone, Eq, PartialEq)] @@ -10,16 +11,16 @@ pub enum CommandMatcher { } impl CommandMatcher { - pub fn matches(&self, other: &Command) -> bool { + pub fn matches(&self, mocked_executables: &[PathBuf], other: &Command) -> bool { match self { - CommandMatcher::ExactMatch(command) => command.compare(other), - CommandMatcher::RegexMatch(regex) => regex.is_match(&other.format()), + CommandMatcher::ExactMatch(command) => command.compare(mocked_executables, other), + CommandMatcher::RegexMatch(regex) => regex.is_match(&other.format(mocked_executables)), } } - pub fn format(&self) -> String { + pub fn format(&self, mocked_executables: &[PathBuf]) -> String { match self { - CommandMatcher::ExactMatch(command) => command.format(), + CommandMatcher::ExactMatch(command) => command.format(mocked_executables), CommandMatcher::RegexMatch(AnchoredRegex { original_string, .. }) => original_string.clone(), @@ -63,44 +64,43 @@ mod command_matcher { #[test] fn matches_command_executable() -> R<()> { - assert!( - CommandMatcher::ExactMatch(Command::new("true")?).matches(&Command::new("true")?) - ); + assert!(CommandMatcher::ExactMatch(Command::new("true")?) + .matches(&[], &Command::new("true")?)); Ok(()) } #[test] fn matches_command_with_arguments() -> R<()> { assert!(CommandMatcher::ExactMatch(Command::new("echo 1")?) - .matches(&Command::new("echo 1")?)); + .matches(&[], &Command::new("echo 1")?)); Ok(()) } #[test] fn matches_command_even_if_it_doesnt_exist() -> R<()> { - assert!(CommandMatcher::ExactMatch(Command::new("foo")?).matches(&Command::new("foo")?)); + assert!(CommandMatcher::ExactMatch(Command::new("foo")?) + .matches(&[], &Command::new("foo")?)); Ok(()) } #[test] fn matches_command_with_full_path() -> R<()> { assert!(CommandMatcher::ExactMatch(Command::new("/bin/true")?) - .matches(&Command::new("/bin/true")?)); + .matches(&[], &Command::new("/bin/true")?)); Ok(()) } #[test] fn doesnt_match_a_different_command() -> R<()> { - assert!( - !CommandMatcher::ExactMatch(Command::new("foo")?).matches(&Command::new("bar")?) - ); + assert!(!CommandMatcher::ExactMatch(Command::new("foo")?) + .matches(&[], &Command::new("bar")?)); Ok(()) } #[test] fn doesnt_match_with_the_same_executable_but_different_arguments() -> R<()> { assert!(!CommandMatcher::ExactMatch(Command::new("foo 1")?) - .matches(&Command::new("foo 2")?)); + .matches(&[], &Command::new("foo 2")?)); Ok(()) } } @@ -110,7 +110,7 @@ mod command_matcher { fn test_regex_matches_command(regex: &str, command: &str) -> R { let result = CommandMatcher::RegexMatch(AnchoredRegex::new(regex)?) - .matches(&Command::new(command)?); + .matches(&[], &Command::new(command)?); Ok(result) } diff --git a/src/protocol/executable_path.rs b/src/protocol/executable_path.rs index b344c20..cdfc455 100644 --- a/src/protocol/executable_path.rs +++ b/src/protocol/executable_path.rs @@ -1,8 +1,8 @@ use quale::which; use std::path::{Path, PathBuf}; -pub fn compare_executables(a: &Path, b: &Path) -> bool { - canonicalize(a) == canonicalize(b) +pub fn compare_executables(mocked_executables: &[PathBuf], a: &Path, b: &Path) -> bool { + canonicalize(mocked_executables, a) == canonicalize(mocked_executables, b) } #[cfg(test)] @@ -13,7 +13,7 @@ mod compare_executables { #[test] fn returns_true_if_executables_are_identical() -> R<()> { let executable = Path::new("./bin/myexec"); - assert!(compare_executables(executable, executable)); + assert!(compare_executables(&[], executable, executable)); Ok(()) } @@ -21,7 +21,7 @@ mod compare_executables { fn returns_false_if_executables_are_distinct() -> R<()> { let a = Path::new("./bin/myexec"); let b = Path::new("./bin/myotherexec"); - assert!(!compare_executables(a, b)); + assert!(!compare_executables(&[], a, b)); Ok(()) } @@ -30,17 +30,25 @@ mod compare_executables { let path = which("cp").unwrap(); let cp_long = path; let cp_short = Path::new("cp"); - assert!(compare_executables(&cp_long, cp_short)); + assert!(compare_executables(&[], &cp_long, cp_short)); Ok(()) } } -pub fn canonicalize(executable: &Path) -> PathBuf { +fn foo_which(mocked_executables: &[PathBuf], name: &Path) -> Option { + if mocked_executables.contains(&PathBuf::from(name)) { + Some(PathBuf::from("/bin").join(name)) + } else { + which(name) + } +} + +pub fn canonicalize(mocked_executables: &[PathBuf], executable: &Path) -> PathBuf { let file_name = match executable.file_name() { None => return executable.into(), Some(f) => f, }; - match which(file_name) { + match foo_which(mocked_executables, &PathBuf::from(file_name)) { Some(resolved) => { if resolved == executable { PathBuf::from(file_name) @@ -62,7 +70,7 @@ mod canonicalize { fn shortens_absolute_executable_paths_if_found_in_path() -> R<()> { let executable = "cp"; let resolved = which(executable).unwrap(); - let file_name = canonicalize(&resolved); + let file_name = canonicalize(&[], &resolved); assert_eq!(file_name, PathBuf::from("cp")); Ok(()) } @@ -70,7 +78,7 @@ mod canonicalize { #[test] fn does_not_shorten_executable_that_is_not_in_path() -> R<()> { let executable = Path::new("/foo/doesnotexist"); - let file_name = canonicalize(executable); + let file_name = canonicalize(&[], executable); assert_eq!(file_name, PathBuf::from("/foo/doesnotexist")); Ok(()) } @@ -78,7 +86,7 @@ mod canonicalize { #[test] fn does_not_shorten_executable_that_is_not_in_path_but_has_same_name_as_one_that_is() -> R<()> { let executable = Path::new("/not/in/path/ls"); - let file_name = canonicalize(executable); + let file_name = canonicalize(&[], executable); assert_eq!(file_name, PathBuf::from("/not/in/path/ls")); Ok(()) } @@ -86,7 +94,7 @@ mod canonicalize { #[test] fn does_not_shorten_relative_path() -> R<()> { let executable = Path::new("./foo"); - let file_name = canonicalize(executable); + let file_name = canonicalize(&[], executable); assert_eq!(file_name, PathBuf::from("./foo")); Ok(()) } @@ -94,8 +102,18 @@ mod canonicalize { #[test] fn does_not_modify_short_forms_if_found_in_path() -> R<()> { let executable = Path::new("ls"); - let file_name = canonicalize(executable); + let file_name = canonicalize(&[], executable); assert_eq!(file_name, PathBuf::from("ls")); Ok(()) } + + #[test] + fn shortens_mocked_executables() -> R<()> { + let file_name = canonicalize( + &[PathBuf::from("does_not_exist")], + &PathBuf::from("/bin/does_not_exist"), + ); + assert_eq!(file_name, PathBuf::from("does_not_exist")); + Ok(()) + } } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 51c740b..b48ca8f 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -75,8 +75,8 @@ impl Step { } } - fn serialize(&self) -> Yaml { - let command = Yaml::String(self.command_matcher.format()); + fn serialize(&self, mocked_executables: &[PathBuf]) -> Yaml { + let command = Yaml::String(self.command_matcher.format(mocked_executables)); if self.exitcode == 0 { command } else { @@ -337,7 +337,7 @@ impl Protocol { } } - fn serialize(&self) -> Yaml { + fn serialize(&self, mocked_executables: &[PathBuf]) -> Yaml { let mut protocol = LinkedHashMap::new(); if !self.arguments.is_empty() { let arguments = self.arguments.iter().map(OsString::from).collect(); @@ -350,7 +350,7 @@ impl Protocol { { let mut steps = vec![]; for step in &self.steps { - steps.push(step.serialize()); + steps.push(step.serialize(mocked_executables)); } protocol.insert(Yaml::from_str("protocol"), Yaml::Array(steps)); } @@ -499,13 +499,13 @@ impl Protocols { Ok(()) } - pub fn serialize(&self) -> R { + pub fn serialize(&self, mocked_executables: &[PathBuf]) -> R { let mut object = LinkedHashMap::new(); self.serialize_unmocked_commands(&mut object)?; { let mut protocols = vec![]; for protocol in self.protocols.iter() { - protocols.push(protocol.serialize()); + protocols.push(protocol.serialize(mocked_executables)); } object.insert(Yaml::from_str("protocols"), Yaml::Array(protocols)); } @@ -1213,7 +1213,7 @@ mod serialize { use pretty_assertions::assert_eq; fn roundtrip(protocols: Protocols) -> R<()> { - let yaml = protocols.serialize()?; + let yaml = protocols.serialize(&[])?; let result = Protocols::parse(yaml)?; assert_eq!(result, protocols); Ok(()) diff --git a/src/protocol_checker/mod.rs b/src/protocol_checker/mod.rs index ad1a576..171f7e3 100644 --- a/src/protocol_checker/mod.rs +++ b/src/protocol_checker/mod.rs @@ -53,10 +53,15 @@ impl ProtocolChecker { fn handle_step(&mut self, received: protocol::Command) -> R { let mock_config = match self.protocol.steps.pop_front() { Some(next_protocol_step) => { - if !next_protocol_step.command_matcher.matches(&received) { + if !next_protocol_step + .command_matcher + .matches(&self.mocked_executables, &received) + { self.register_step_error( - &next_protocol_step.command_matcher.format(), - &received.format(), + &next_protocol_step + .command_matcher + .format(&self.mocked_executables), + &received.format(&self.mocked_executables), ); } executable_mock::Config { @@ -65,7 +70,10 @@ impl ProtocolChecker { } } None => { - self.register_step_error("", &received.format()); + self.register_step_error( + "", + &received.format(&self.mocked_executables), + ); ProtocolChecker::allow_failing_scripts_to_continue() } }; @@ -104,10 +112,9 @@ impl SyscallMock for ProtocolChecker { executable: PathBuf, arguments: Vec, ) -> R<()> { - let is_unmocked_command = self - .unmocked_commands - .iter() - .any(|unmocked_command| protocol::compare_executables(unmocked_command, &executable)); + let is_unmocked_command = self.unmocked_commands.iter().any(|unmocked_command| { + protocol::compare_executables(&self.mocked_executables, unmocked_command, &executable) + }); if !is_unmocked_command { let mock_executable_path = self.handle_step(protocol::Command { executable, @@ -175,7 +182,9 @@ impl SyscallMock for ProtocolChecker { fn handle_end(mut self, exitcode: i32, redirector: &Redirector) -> R { if let Some(expected_step) = self.protocol.steps.pop_front() { self.register_step_error( - &expected_step.command_matcher.format(), + &expected_step + .command_matcher + .format(&self.mocked_executables), "