mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
5269e233a6
-ok is like -exec ... ; but prompts the user on stderr before each
invocation and only runs the command if the response is affirmative
(starts with 'y' or 'Y'). -okdir is the corresponding -execdir variant.
Only the ';' terminator is accepted: POSIX does not define -ok ... + and
GNU find rejects it.
Response source: GNU find reads from /dev/tty so that the user's answer
always comes from the real terminal even when stdin is occupied (e.g.
`find -files0-from - -ok rm {} \;` reads paths from stdin, so responses
cannot also come from there). /dev/tty is the POSIX name for a
process's controlling terminal and exists on all Unix-like systems. We
open /dev/tty only when stdin is itself a terminal
(std::io::stdin().is_terminal() == true). When stdin is a pipe or file,
we read from stdin directly — matching BSD find and making scripted use
(and integration tests via pipe_in()) work naturally without any special
environment variable. On Windows, or when /dev/tty cannot be opened, we
likewise fall back to stdin.
Implementation: rather than a separate OkMatcher that duplicated
SingleExecMatcher's fields, constructor, and exec logic, the interactive
prompt is folded into SingleExecMatcher behind an `interactive: bool`
field. `SingleExecMatcher::new_interactive()` constructs the -ok/-okdir
variant; `matches()` adds a guarded block that builds a GNU-find-style
prompt ("< executable arg... >? ") and calls `matcher_io.confirm()`
before executing. If the user declines, the expression is false and the
command is not run. This keeps the arg-parsing, path resolution,
current_dir logic, and error handling in one place so bug fixes apply to
both -exec and -ok.
Dependencies::confirm() trait method: abstracts prompt+read so matchers
remain testable without a real terminal; FakeDependencies uses a
VecDeque of preset responses.
Tests:
- Unit tests (exec_unit_tests.rs): confirmed executes command, declined
skips command and returns false, confirmed but command fails returns
false, -okdir runs in parent dir
- Parser unit tests (matchers/mod.rs): missing-arg errors, missing
semicolon, correct parse, confirm/decline via FakeDependencies
- Integration tests (test_find.rs): pipe_in() makes stdin a pipe so
is_terminal() returns false and responses are read from the pipe;
"y"/"Y"/"yes"/" y" run command, "n" and empty response skip command,
-okdir runs in parent dir, prompt format verified, missing-semicolon
error
Closes https://github.com/uutils/findutils/issues/8.
289 lines
8.2 KiB
Rust
289 lines
8.2 KiB
Rust
// 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.
|
|
use std::env;
|
|
use std::fs::File;
|
|
use std::io::Read;
|
|
use tempfile::Builder;
|
|
|
|
use common::test_helpers::{fix_up_slashes, path_to_testing_commandline, FakeDependencies};
|
|
use findutils::find::find_main;
|
|
|
|
mod common;
|
|
|
|
#[test]
|
|
fn find_ok_confirmed() {
|
|
let temp_dir = Builder::new()
|
|
.prefix("find_ok_confirmed")
|
|
.tempdir()
|
|
.unwrap();
|
|
let temp_dir_path = temp_dir.path().to_string_lossy();
|
|
let deps = FakeDependencies::new();
|
|
deps.push_confirm_response(true);
|
|
|
|
let rc = find_main(
|
|
&[
|
|
"find",
|
|
&fix_up_slashes("./test_data/simple"),
|
|
"-maxdepth",
|
|
"1",
|
|
"-name",
|
|
"abbbc",
|
|
"-ok",
|
|
&path_to_testing_commandline(),
|
|
temp_dir_path.as_ref(),
|
|
"abc",
|
|
"{}",
|
|
"xyz",
|
|
";",
|
|
],
|
|
&deps,
|
|
);
|
|
|
|
assert_eq!(rc, 0);
|
|
assert_eq!(deps.get_output_as_string(), "");
|
|
|
|
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,
|
|
fix_up_slashes(&format!(
|
|
"cwd={}\nargs=\nabc\n./test_data/simple/abbbc\nxyz\n",
|
|
env::current_dir().unwrap().to_string_lossy()
|
|
))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn find_ok_declined() {
|
|
let temp_dir = Builder::new().prefix("find_ok_declined").tempdir().unwrap();
|
|
let temp_dir_path = temp_dir.path().to_string_lossy();
|
|
let deps = FakeDependencies::new();
|
|
deps.push_confirm_response(false);
|
|
|
|
let rc = find_main(
|
|
&[
|
|
"find",
|
|
&fix_up_slashes("./test_data/simple"),
|
|
"-maxdepth",
|
|
"1",
|
|
"-name",
|
|
"abbbc",
|
|
"-ok",
|
|
&path_to_testing_commandline(),
|
|
temp_dir_path.as_ref(),
|
|
"abc",
|
|
"{}",
|
|
"xyz",
|
|
";",
|
|
],
|
|
&deps,
|
|
);
|
|
|
|
assert_eq!(rc, 0);
|
|
// -ok is false when declined, so no default print either.
|
|
assert_eq!(deps.get_output_as_string(), "");
|
|
// Command did not run.
|
|
assert!(!temp_dir.path().join("1.txt").exists());
|
|
}
|
|
#[test]
|
|
fn find_exec() {
|
|
let temp_dir = tempfile::Builder::new()
|
|
.prefix("find_exec")
|
|
.tempdir()
|
|
.unwrap();
|
|
let temp_dir_path = temp_dir.path().to_string_lossy();
|
|
let deps = FakeDependencies::new();
|
|
|
|
let rc = find_main(
|
|
&[
|
|
"find",
|
|
&fix_up_slashes("./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,
|
|
fix_up_slashes(&format!(
|
|
"cwd={}\nargs=\n(\n./test_data/simple/subdir/ABBBC\n-o\n",
|
|
env::current_dir().unwrap().to_string_lossy()
|
|
))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn find_execdir() {
|
|
let temp_dir = Builder::new().prefix("example").tempdir().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",
|
|
&fix_up_slashes("./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,
|
|
fix_up_slashes(&format!(
|
|
"cwd={}/test_data/simple/subdir\nargs=\n)\n./ABBBC\n,\n",
|
|
env::current_dir().unwrap().to_string_lossy()
|
|
))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn find_exec_multi() {
|
|
let temp_dir = tempfile::Builder::new()
|
|
.prefix("find_exec_multi")
|
|
.tempdir()
|
|
.unwrap();
|
|
let temp_dir_path = temp_dir.path().to_string_lossy();
|
|
let deps = FakeDependencies::new();
|
|
|
|
let rc = find_main(
|
|
&[
|
|
"find",
|
|
&fix_up_slashes("./test_data/simple"),
|
|
"-type",
|
|
"f",
|
|
"-exec",
|
|
&path_to_testing_commandline(),
|
|
temp_dir_path.as_ref(),
|
|
"--sort",
|
|
"(",
|
|
"-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,
|
|
fix_up_slashes(&format!(
|
|
"cwd={}\nargs=\n(\n--sort\n-o\n./test_data/simple/abbbc\n./test_data/simple/subdir/ABBBC\n",
|
|
env::current_dir().unwrap().to_string_lossy()
|
|
))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn find_execdir_multi() {
|
|
let temp_dir = Builder::new()
|
|
.prefix("find_execdir_multi")
|
|
.tempdir()
|
|
.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",
|
|
&fix_up_slashes("./test_data/simple"),
|
|
"-maxdepth",
|
|
"1",
|
|
"-execdir",
|
|
&path_to_testing_commandline(),
|
|
temp_dir_path.as_ref(),
|
|
"--sort",
|
|
")",
|
|
"{}",
|
|
"+",
|
|
],
|
|
&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,
|
|
fix_up_slashes(&format!(
|
|
"cwd={}/test_data\nargs=\n)\n--sort\n./simple\n",
|
|
env::current_dir().unwrap().to_string_lossy()
|
|
))
|
|
);
|
|
let mut f = File::open(temp_dir.path().join("2.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,
|
|
fix_up_slashes(&format!(
|
|
"cwd={}/test_data/simple\nargs=\n)\n--sort\n./abbbc\n./subdir\n",
|
|
env::current_dir().unwrap().to_string_lossy()
|
|
))
|
|
);
|
|
}
|