From 8b3842b1f9861956bc0fd6f91e5da0b476dec73c Mon Sep 17 00:00:00 2001 From: mcharsley Date: Fri, 17 Mar 2017 17:02:45 +0000 Subject: [PATCH] Added support for -exec[dir] (#17) * Deleted vertical whitespace In an attempt to get around a mistake I made when merging from the master fork * Added a default implementation for has_side_effects And removed all the "return false" implementations (as specified by most of the Matchers). * Added support for -exec and -execdir * Fixed path_to_testing_commandline * Minor tweaks from code review --- Cargo.toml | 6 +- src/find/matchers/exec.rs | 97 ++++++++++++++++++++++++++ src/find/matchers/logical_matchers.rs | 53 ++++++++++++--- src/find/matchers/mod.rs | 80 ++++++++++++++++++++-- src/find/matchers/name.rs | 8 --- src/find/matchers/prune.rs | 4 -- src/find/matchers/size.rs | 4 -- src/find/matchers/time.rs | 8 --- src/find/matchers/type_matcher.rs | 4 -- src/find/mod.rs | 4 +- src/testing/commandline/main.rs | 71 +++++++++++++++++++ tests/common/mod.rs | 11 +++ tests/common/test_helpers.rs | 86 +++++++++++++++++++++++ tests/exec_unit_tests.rs | 98 +++++++++++++++++++++++++++ tests/find_exec_tests.rs | 93 +++++++++++++++++++++++++ 15 files changed, 582 insertions(+), 45 deletions(-) create mode 100644 src/find/matchers/exec.rs create mode 100644 src/testing/commandline/main.rs create mode 100644 tests/common/mod.rs create mode 100644 tests/common/test_helpers.rs create mode 100644 tests/exec_unit_tests.rs create mode 100644 tests/find_exec_tests.rs diff --git a/Cargo.toml b/Cargo.toml index 450b5cf..5d6a82f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,8 @@ regex = "0.2" [[bin]] name = "find" -path = "src/find/main.rs" \ No newline at end of file +path = "src/find/main.rs" + +[[bin]] +name = "testing-commandline" +path = "src/testing/commandline/main.rs" \ No newline at end of file diff --git a/src/find/matchers/exec.rs b/src/find/matchers/exec.rs new file mode 100644 index 0000000..228dbab --- /dev/null +++ b/src/find/matchers/exec.rs @@ -0,0 +1,97 @@ +// Copyright 2017 Google Inc. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::error::Error; +use std::ffi::OsString; +use std::io::{Write, stderr}; +use std::path::Path; +use std::process::Command; +use walkdir::DirEntry; + +use find::matchers::{Matcher, MatcherIO}; + +enum Arg { + Filename, + LiteralArg(OsString), +} + +pub struct SingleExecMatcher { + executable: String, + args: Vec, + exec_in_parent_dir: bool, +} + +impl SingleExecMatcher { + pub fn new(executable: &str, + args: &[&str], + exec_in_parent_dir: bool) + -> Result> { + + let transformed_args = args.iter() + .map(|&a| match a { + "{}" => Arg::Filename, + _ => Arg::LiteralArg(OsString::from(a)), + }) + .collect(); + + Ok(SingleExecMatcher { + executable: executable.to_string(), + args: transformed_args, + exec_in_parent_dir: exec_in_parent_dir, + }) + } + + pub fn new_box(executable: &str, + args: &[&str], + exec_in_parent_dir: bool) + -> Result, Box> { + Ok(Box::new(SingleExecMatcher::new(executable, args, exec_in_parent_dir)?)) + } +} + +impl Matcher for SingleExecMatcher { + fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + let mut command = Command::new(&self.executable); + let path_to_file = if self.exec_in_parent_dir { + if let Some(f) = file_info.path().file_name() { + Path::new(".").join(f) + } else { + file_info.path().to_path_buf() + } + } else { + file_info.path().to_path_buf() + }; + + for arg in &self.args { + command.arg(match arg { + &Arg::LiteralArg(ref a) => a.as_os_str(), + &Arg::Filename => path_to_file.as_os_str(), + }); + } + if self.exec_in_parent_dir { + if let Some(parent) = file_info.path().parent() { + command.current_dir(parent); + } + } + match command.status() { + Ok(status) => return status.success(), + Err(e) => { + writeln!(&mut stderr(), "Failed to run {}: {}", self.executable, e).unwrap(); + return false; + } + } + } + + fn has_side_effects(&self) -> bool { + return true; + } +} + + +#[cfg(test)] +/// No tests here, because we need to call out to an external executable. See +/// tests/exec_unit_tests.rs instead. +mod tests {} diff --git a/src/find/matchers/logical_matchers.rs b/src/find/matchers/logical_matchers.rs index 4530154..903683f 100644 --- a/src/find/matchers/logical_matchers.rs +++ b/src/find/matchers/logical_matchers.rs @@ -11,6 +11,7 @@ //! to "-foo -o ( -bar -baz )", not "( -foo -o -bar ) -baz"). use std::error::Error; use std::iter::Iterator; +use std::path::PathBuf; use walkdir::DirEntry; use find::matchers::{Matcher, MatcherIO}; @@ -41,6 +42,18 @@ impl Matcher for AndMatcher { fn has_side_effects(&self) -> bool { self.submatchers.iter().any(|x| x.has_side_effects()) } + + fn finished_dir(&self, dir: &PathBuf) { + for m in &self.submatchers { + m.finished_dir(dir); + } + } + + fn finished(&self) { + for m in &self.submatchers { + m.finished(); + } + } } pub struct AndMatcherBuilder { @@ -97,6 +110,18 @@ impl Matcher for OrMatcher { fn has_side_effects(&self) -> bool { self.submatchers.iter().any(|x| x.has_side_effects()) } + + fn finished_dir(&self, dir: &PathBuf) { + for m in &self.submatchers { + m.finished_dir(dir); + } + } + + fn finished(&self) { + for m in &self.submatchers { + m.finished(); + } + } } pub struct OrMatcherBuilder { @@ -171,6 +196,18 @@ impl Matcher for ListMatcher { fn has_side_effects(&self) -> bool { self.submatchers.iter().any(|x| x.has_side_effects()) } + + fn finished_dir(&self, dir: &PathBuf) { + for m in &self.submatchers { + m.finished_dir(dir); + } + } + + fn finished(&self) { + for m in &self.submatchers { + m.finished(); + } + } } pub struct ListMatcherBuilder { @@ -249,10 +286,6 @@ impl Matcher for TrueMatcher { fn matches(&self, _dir_entry: &DirEntry, _: &mut MatcherIO) -> bool { true } - - fn has_side_effects(&self) -> bool { - false - } } /// A simple matcher that never matches. @@ -262,10 +295,6 @@ impl Matcher for FalseMatcher { fn matches(&self, _dir_entry: &DirEntry, _: &mut MatcherIO) -> bool { false } - - fn has_side_effects(&self) -> bool { - false - } } impl FalseMatcher { @@ -298,6 +327,14 @@ impl Matcher for NotMatcher { fn has_side_effects(&self) -> bool { self.submatcher.has_side_effects() } + + fn finished_dir(&self, dir: &PathBuf) { + self.submatcher.finished_dir(dir); + } + + fn finished(&self) { + self.submatcher.finished(); + } } #[cfg(test)] diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index 288e767..327c98e 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -4,6 +4,7 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +pub mod exec; mod logical_matchers; mod name; mod printer; @@ -14,6 +15,7 @@ mod type_matcher; use regex::Regex; use std::error::Error; +use std::path::PathBuf; use std::time::SystemTime; use walkdir::DirEntry; @@ -57,11 +59,23 @@ pub trait Matcher { /// Returns whether the given file matches the object's predicate. fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool; - /// Returns whether the matcher has any side-effects. Iff no such matcher - /// exists in the chain, then the filename will be printed to stdout. While - /// this is a compile-time fact for most matchers, it's run-time for matchers - /// that contain a collection of sub-Matchers. - fn has_side_effects(&self) -> bool; + /// Returns whether the matcher has any side-effects (e.g. executing a + /// command, deleting a file). Iff no such matcher exists in the chain, then + /// the filename will be printed to stdout. While this is a compile-time + /// fact for most matchers, it's run-time for matchers that contain a + /// collection of sub-Matchers. + fn has_side_effects(&self) -> bool { + // most matchers don't have side-effects, so supply a default implementation. + return false; + } + + /// Notification that find has finished processing a given directory. + fn finished_dir(&self, _finished_directory: &PathBuf) {} + + /// Notification that find has finished processing all directories - + /// allowing for any cleanup that isn't suitable for destructors (e.g. + /// blocking calls, I/O etc.) + fn finished(&self) {} } pub enum ComparableValue { @@ -239,7 +253,30 @@ fn build_matcher_tree(args: &[&str], i += 1; Some(size::SizeMatcher::new_box(size, &unit)?) } - + "-exec" | "-execdir" => { + let mut arg_index = i + 1; + while arg_index < args.len() && args[arg_index] != ";" { + if args[arg_index] == "+" { + // MultiExecMatcher isn't written yet + return Err(From::from(format!("{} [args...] + isn't supported yet. \ + Only {} [args...] ;", + args[i], + args[i]))); + } + arg_index += 1; + } + if arg_index < i + 2 || arg_index == args.len() { + // at the minimum we need the executable and the ';' + return Err(From::from(format!("missing argument to {}", args[i]))); + } + let expression = args[i]; + let executable = args[i + 1]; + let exec_args = &args[i + 2..arg_index]; + i = arg_index; + Some(exec::SingleExecMatcher::new_box(executable, + exec_args, + expression == "-execdir")?) + } "-prune" => Some(prune::PruneMatcher::new_box()), "-not" | "!" => { if !are_more_expressions(args, i) { @@ -728,4 +765,35 @@ mod tests { panic!("parsing a bad ctime value should fail"); } } + + #[test] + fn build_top_level_exec_not_enough_args() { + let mut config = Config::default(); + + if let Err(e) = build_top_level_matcher(&["-exec"], &mut config) { + assert!(e.description().contains("missing argument")); + } else { + panic!("parsing argument list with exec and no executable or semi-colon should fail"); + } + + if let Err(e) = build_top_level_matcher(&["-exec", ";"], &mut config) { + assert!(e.description().contains("missing argument")); + } else { + panic!("parsing argument list with exec and no executable should fail"); + } + + if let Err(e) = build_top_level_matcher(&["-exec", "foo"], &mut config) { + assert!(e.description().contains("missing argument")); + } else { + panic!("parsing argument list with exec and no executable should fail"); + } + } + + #[test] + fn build_top_level_exec_should_eat_args() { + let mut config = Config::default(); + build_top_level_matcher(&["-exec", "foo", "-o", "(", ";"], &mut config) + .expect("parsing argument list with exec that takes brackets and -os should work"); + } + } diff --git a/src/find/matchers/name.rs b/src/find/matchers/name.rs index a3840c5..146e552 100644 --- a/src/find/matchers/name.rs +++ b/src/find/matchers/name.rs @@ -32,10 +32,6 @@ impl Matcher for NameMatcher { fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { self.pattern.matches(file_info.file_name().to_string_lossy().as_ref()) } - - fn has_side_effects(&self) -> bool { - false - } } /// This matcher makes a case-insensitive comparison of the name against a @@ -61,10 +57,6 @@ impl super::Matcher for CaselessNameMatcher { self.pattern .matches(file_info.file_name().to_string_lossy().to_lowercase().as_ref()) } - - fn has_side_effects(&self) -> bool { - false - } } diff --git a/src/find/matchers/prune.rs b/src/find/matchers/prune.rs index f740779..0f5fa9b 100644 --- a/src/find/matchers/prune.rs +++ b/src/find/matchers/prune.rs @@ -26,10 +26,6 @@ impl Matcher for PruneMatcher { matcher_io.mark_current_dir_to_be_skipped(); true } - - fn has_side_effects(&self) -> bool { - false - } } #[cfg(test)] diff --git a/src/find/matchers/size.rs b/src/find/matchers/size.rs index f6532af..cb68e50 100644 --- a/src/find/matchers/size.rs +++ b/src/find/matchers/size.rs @@ -103,10 +103,6 @@ impl Matcher for SizeMatcher { } } } - - fn has_side_effects(&self) -> bool { - false - } } #[cfg(test)] diff --git a/src/find/matchers/time.rs b/src/find/matchers/time.rs index d0c8635..5b63b9c 100644 --- a/src/find/matchers/time.rs +++ b/src/find/matchers/time.rs @@ -57,10 +57,6 @@ impl Matcher for NewerMatcher { Ok(t) => t, } } - - fn has_side_effects(&self) -> bool { - false - } } #[derive(Clone, Copy, Debug)] @@ -102,10 +98,6 @@ impl Matcher for FileTimeMatcher { Ok(t) => t, } } - - fn has_side_effects(&self) -> bool { - false - } } diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 9f4fd4e..b5579d4 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -37,10 +37,6 @@ impl Matcher for TypeMatcher { fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { (self.file_type_fn)(&file_info.file_type()) } - - fn has_side_effects(&self) -> bool { - false - } } #[cfg(test)] diff --git a/src/find/mod.rs b/src/find/mod.rs index b8a421c..37e33c3 100644 --- a/src/find/mod.rs +++ b/src/find/mod.rs @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -mod matchers; +pub mod matchers; use std::cell::RefCell; use std::error::Error; @@ -171,6 +171,7 @@ Early alpha implementation. Currently the only expressions supported are -atime [+-]N -mtime [+-]N -newer path_to_file + -exec[dir] executable [args] [{{}}] [more args] ; -sorted a non-standard extension that sorts directory contents by name before processing them. Less efficient, but allows for deterministic output. @@ -499,5 +500,4 @@ mod tests { assert_eq!(deps.get_output_as_string(), ""); } - } diff --git a/src/testing/commandline/main.rs b/src/testing/commandline/main.rs new file mode 100644 index 0000000..bfb0b73 --- /dev/null +++ b/src/testing/commandline/main.rs @@ -0,0 +1,71 @@ +// Copyright 2017 Google Inc. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::env; +use std::fs::{self, File, OpenOptions}; +use std::io::Write; +use std::path::PathBuf; + +fn usage() -> ! { + println!("Simple command-line app just used for testing -exec flags!"); + std::process::exit(2); +} + +#[derive(Default)] +struct Config { + exit_with_failure: bool, + destination_dir: String, +} + +fn open_file(destination_dir: &str) -> File { + let mut file_number = + fs::read_dir(destination_dir).expect("failed to read destination").count(); + + loop { + file_number += 1; + let mut file_path: PathBuf = PathBuf::from(destination_dir); + file_path.push(format!("{}.txt", file_number)); + if let Ok(f) = OpenOptions::new() + .write(true) + .create_new(true) + .open(file_path) { + return f; + } + } +} + +fn main() { + let args = env::args().collect::>(); + if args.len() < 2 || args[1] == "-h" || args[1] == "--help" { + usage(); + } + let mut config = Config::default(); + config.destination_dir = args[1].clone(); + for arg in &args[2..] { + if arg.starts_with("--") { + match arg.as_ref() { + "--exit_with_failure" => { + config.exit_with_failure = true; + } + _ => { + usage(); + } + } + } + } + + { + let mut f = open_file(&config.destination_dir); + // first two args are going to be the path to this executable and + // the destination_dir we want to write to. Don't write either of those + // as they'll be non-deterministic. + f.write_fmt(format_args!("cwd={}\nargs={:?}\n", + env::current_dir().unwrap().to_string_lossy(), + &args[2..])) + .expect("failed to write to file"); + } + std::process::exit(if config.exit_with_failure { 2 } else { 0 }); +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..eb372d0 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,11 @@ +// Copyright 2017 Google Inc. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + + +// As this module is included by all the integration tests, any function used +// in one test but not another can cause a dead code warning. +#[allow(dead_code)] +pub mod test_helpers; diff --git a/tests/common/test_helpers.rs b/tests/common/test_helpers.rs new file mode 100644 index 0000000..11dc332 --- /dev/null +++ b/tests/common/test_helpers.rs @@ -0,0 +1,86 @@ +// Copyright 2017 Google Inc. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + + +use std::cell::RefCell; +use std::env; +use std::io::{Cursor, Read, Write}; +use std::time::SystemTime; +use std::vec::Vec; +use walkdir::{DirEntry, WalkDir}; + +use findutils::find::matchers::MatcherIO; +use findutils::find::Dependencies; + +/// A copy of find::tests::FakeDependencies. +/// TODO: find out how to share #[cfg(test)] functions/structs between unit +/// and integration tests. +pub struct FakeDependencies { + pub output: RefCell>>, + now: SystemTime, +} + +impl<'a> FakeDependencies { + pub fn new() -> FakeDependencies { + FakeDependencies { + output: RefCell::new(Cursor::new(Vec::::new())), + now: SystemTime::now(), + } + } + + pub fn new_matcher_io(&'a self) -> MatcherIO<'a> { + MatcherIO::new(self) + } + + pub fn get_output_as_string(&self) -> String { + let mut cursor = self.output.borrow_mut(); + cursor.set_position(0); + let mut contents = String::new(); + cursor.read_to_string(&mut contents).unwrap(); + contents + } +} + +impl<'a> Dependencies<'a> for FakeDependencies { + fn get_output(&'a self) -> &'a RefCell { + &self.output + } + + fn now(&'a self) -> SystemTime { + self.now + } +} + +pub fn path_to_testing_commandline() -> String { + + let mut path_to_use = env::current_exe() + // this will be something along the lines of /my/homedir/findutils/target/debug/deps/findutils-5532804878869ef1 + .expect("can't find path of this executable") + .parent() + .expect("can't find parent directory of this executable") + .to_path_buf(); + // and we want /my/homedir/findutils/target/debug/testing-commandline + if path_to_use.ends_with("deps") { + path_to_use.pop(); + } + path_to_use = path_to_use.join("testing-commandline"); + path_to_use.to_string_lossy() + .to_string() +} + + +/// A copy of find::tests::FakeDependencies. +/// TODO: find out how to share #[cfg(test)] functions/structs between unit +/// and integration tests. +pub fn get_dir_entry_for(directory: &str, filename: &str) -> DirEntry { + for wrapped_dir_entry in WalkDir::new(directory) { + let dir_entry = wrapped_dir_entry.unwrap(); + if dir_entry.file_name().to_string_lossy() == filename { + return dir_entry; + } + } + panic!("Couldn't find {} in {}", directory, filename); +} diff --git a/tests/exec_unit_tests.rs b/tests/exec_unit_tests.rs new file mode 100644 index 0000000..7f4e920 --- /dev/null +++ b/tests/exec_unit_tests.rs @@ -0,0 +1,98 @@ +// Copyright 2017 Google Inc. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + + +/// ! This file contains what would be normally be unit tests for find::matchers::exec. +/// ! But as the tests require running an external executable, they need to be run +/// ! as integration tests so we can ensure that our testing-commandline binary +/// ! has been built. +extern crate findutils; +extern crate tempdir; +extern crate walkdir; + + +use std::env; +use std::fs::File; +use std::io::Read; +use tempdir::TempDir; + + +use findutils::find::matchers::Matcher; +use findutils::find::matchers::exec::*; +use common::test_helpers::*; + +mod common; + +#[test] +fn matching_executes_code() { + + let temp_dir = TempDir::new("matching_executes_code").unwrap(); + let temp_dir_path = temp_dir.path().to_string_lossy(); + + let abbbc = get_dir_entry_for("test_data/simple", "abbbc"); + let matcher = SingleExecMatcher::new(&path_to_testing_commandline(), + &vec![temp_dir_path.as_ref(), "abc", "{}", "xyz"], + false) + .expect("Failed to create matcher"); + let deps = FakeDependencies::new(); + assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io())); + + let mut f = File::open(temp_dir.path().join("1.txt")).expect("Failed to open output file"); + let mut s = String::new(); + f.read_to_string(&mut s).expect("failed to read output file"); + assert_eq!(s, + format!("cwd={}\nargs=[\"abc\", \"test_data/simple/abbbc\", \"xyz\"]\n", + env::current_dir().unwrap().to_string_lossy())); +} + +#[test] +fn matching_executes_code_in_files_directory() { + + let temp_dir = TempDir::new("matching_executes_code_in_files_directory").unwrap(); + let temp_dir_path = temp_dir.path().to_string_lossy(); + + let abbbc = get_dir_entry_for("test_data/simple", "abbbc"); + let matcher = SingleExecMatcher::new(&path_to_testing_commandline(), + &vec![temp_dir_path.as_ref(), "abc", "{}", "xyz"], + true) + .expect("Failed to create matcher"); + let deps = FakeDependencies::new(); + assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io())); + + let mut f = File::open(temp_dir.path().join("1.txt")).expect("Failed to open output file"); + let mut s = String::new(); + f.read_to_string(&mut s).expect("failed to read output file"); + assert_eq!(s, + format!("cwd={}/test_data/simple\nargs=[\"abc\", \"./abbbc\", \"xyz\"]\n", + env::current_dir().unwrap().to_string_lossy())); +} + +#[test] +fn matching_fails_if_executable_fails() { + + let temp_dir = TempDir::new("matching_fails_if_executable_fails").unwrap(); + let temp_dir_path = temp_dir.path().to_string_lossy(); + + let abbbc = get_dir_entry_for("test_data/simple", "abbbc"); + let matcher = SingleExecMatcher::new(&path_to_testing_commandline(), + &vec![temp_dir_path.as_ref(), + "--exit_with_failure", + "abc", + "{}", + "xyz"], + true) + .expect("Failed to create matcher"); + let deps = FakeDependencies::new(); + assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io())); + + let mut f = File::open(temp_dir.path().join("1.txt")).expect("Failed to open output file"); + let mut s = String::new(); + f.read_to_string(&mut s).expect("failed to read output file"); + assert_eq!(s, + format!("cwd={}/test_data/simple\nargs=[\"--exit_with_failure\", \"abc\", \ + \"./abbbc\", \"xyz\"]\n", + env::current_dir().unwrap().to_string_lossy())); +} diff --git a/tests/find_exec_tests.rs b/tests/find_exec_tests.rs new file mode 100644 index 0000000..2443519 --- /dev/null +++ b/tests/find_exec_tests.rs @@ -0,0 +1,93 @@ +// Copyright 2017 Google Inc. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + + +/// ! This file contains what would be normally be unit tests for find::find_main +/// ! related to -exec[dir] and ok[dir] clauses. +/// ! But as the tests require running an external executable, they need to be run +/// ! as integration tests so we can ensure that our testing-commandline binary +/// ! has been built. +extern crate findutils; +extern crate tempdir; +extern crate walkdir; + + +use std::env; +use std::fs::File; +use std::io::Read; +use tempdir::TempDir; + +use findutils::find::find_main; +use common::test_helpers::*; + +mod common; +#[test] +fn find_exec() { + let temp_dir = TempDir::new("find_exec").unwrap(); + let temp_dir_path = temp_dir.path().to_string_lossy(); + let deps = FakeDependencies::new(); + + let rc = find_main(&["find", + "./test_data/simple/subdir", + "-type", + "f", + "-exec", + &path_to_testing_commandline(), + temp_dir_path.as_ref(), + "(", + "{}", + "-o", + ";"], + &deps); + + assert_eq!(rc, 0); + // exec has side effects, so we won't output anything unless -print is + // explicitly passed in. + assert_eq!(deps.get_output_as_string(), ""); + + // check the executable ran as expected + let mut f = File::open(temp_dir.path().join("1.txt")).expect("Failed to open output file"); + let mut s = String::new(); + f.read_to_string(&mut s).expect("failed to read output file"); + assert_eq!(s, + format!("cwd={}\nargs=[\"(\", \"./test_data/simple/subdir/ABBBC\", \"-o\"]\n", + env::current_dir().unwrap().to_string_lossy())); +} + +#[test] +fn find_execdir() { + let temp_dir = TempDir::new("find_execdir").unwrap(); + let temp_dir_path = temp_dir.path().to_string_lossy(); + let deps = FakeDependencies::new(); + // only look at files because the "size" of a directory is a system (and filesystem) + // dependent thing and we want these tests to be universal. + let rc = find_main(&["find", + "./test_data/simple/subdir", + "-type", + "f", + "-execdir", + &path_to_testing_commandline(), + temp_dir_path.as_ref(), + ")", + "{}", + ",", + ";"], + &deps); + + assert_eq!(rc, 0); + // exec has side effects, so we won't output anything unless -print is + // explicitly passed in. + assert_eq!(deps.get_output_as_string(), ""); + + // check the executable ran as expected + let mut f = File::open(temp_dir.path().join("1.txt")).expect("Failed to open output file"); + let mut s = String::new(); + f.read_to_string(&mut s).expect("failed to read output file"); + assert_eq!(s, + format!("cwd={}/test_data/simple/subdir\nargs=[\")\", \"./ABBBC\", \",\"]\n", + env::current_dir().unwrap().to_string_lossy())); + +}