Skip to content

Commit 374a7d6

Browse files
authored
fix exit code when stdin is /dev/null (#1011)
1 parent 3c4b167 commit 374a7d6

4 files changed

Lines changed: 55 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Categories Used:
2323

2424
### Bug Fixes
2525

26+
- Decompress: exit with a non-zero code when a file conflict can't be resolved on non-interactive stdin, instead of silently skipping and reporting success (https://github.com/ouch-org/ouch/pull/1011)
27+
2628
### Tweaks
2729

2830

src/utils/question.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,12 @@ impl<'a, T: Default> ChoicePrompt<'a, T> {
205205
let message = self.prompt;
206206

207207
if is_stdin_dev_null()? {
208-
eprintln!("{message}");
209-
eprintln!("Stdin is null, can't read user input (bypass with --yes, but be careful)");
210-
return Ok(T::default());
208+
// stdin is /dev/null so the conflict prompt cannot be answered; fail instead of silently skipping
209+
let error = FinalError::with_title("Cannot read input to resolve file conflict")
210+
.detail(format!("Tried to ask: \"{message}\""))
211+
.detail("Stdin is connected to /dev/null, so the prompt cannot be answered.")
212+
.hint("If using Ouch in scripting, consider using `--yes` and `--no`.");
213+
return Err(error.into());
211214
}
212215

213216
let _locks = lock_and_flush_output_stdio()?;

tests/integration.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,3 +1569,37 @@ fn decompress_concatenated_lz4_frames() {
15691569
encoder.finish().unwrap()
15701570
});
15711571
}
1572+
1573+
/// A conflict that cannot be resolved because stdin is /dev/null must fail, not exit 0.
1574+
#[test]
1575+
fn decompress_conflict_with_dev_null_stdin_exits_nonzero() {
1576+
use std::process::Stdio;
1577+
1578+
let (_tempdir, dir) = testdir().unwrap();
1579+
fs::create_dir(dir.join("d")).unwrap();
1580+
fs::write(dir.join("d").join("f.txt"), b"hi").unwrap();
1581+
1582+
crate::utils::cargo_bin()
1583+
.current_dir(dir)
1584+
.args(["compress", "d", "a.zip", "--yes"])
1585+
.assert()
1586+
.success();
1587+
// first extraction creates ./a, so the next run hits a conflict
1588+
crate::utils::cargo_bin()
1589+
.current_dir(dir)
1590+
.args(["decompress", "a.zip", "--yes"])
1591+
.assert()
1592+
.success();
1593+
1594+
// stdin is /dev/null so the conflict cannot be answered; the run must fail instead of exiting 0
1595+
let status = crate::utils::cargo_bin_command()
1596+
.current_dir(dir)
1597+
.args(["decompress", "a.zip"])
1598+
.stdin(Stdio::null())
1599+
.status()
1600+
.unwrap();
1601+
assert!(
1602+
!status.success(),
1603+
"decompress with an unresolvable conflict on /dev/null stdin must exit non-zero"
1604+
);
1605+
}

tests/utils.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,21 @@ macro_rules! ouch {
2323
}
2424

2525
pub fn cargo_bin() -> Command {
26+
Command::from_std(cargo_bin_command())
27+
}
28+
29+
/// Like [`cargo_bin`] but returns a `std::process::Command`, so callers can set stdin/stdout.
30+
pub fn cargo_bin_command() -> std::process::Command {
31+
let bin = assert_cmd::cargo::cargo_bin("ouch");
2632
env::vars()
27-
.find_map(|(k, v)| {
28-
(k.starts_with("CARGO_TARGET_") && k.ends_with("_RUNNER")).then(|| {
29-
let mut runner = v.split_whitespace();
30-
let mut cmd = Command::new(runner.next().unwrap());
31-
cmd.args(runner).arg(assert_cmd::cargo::cargo_bin("ouch"));
32-
cmd
33-
})
33+
.find(|(k, _)| k.starts_with("CARGO_TARGET_") && k.ends_with("_RUNNER"))
34+
.map(|(_, runner)| {
35+
let mut parts = runner.split_whitespace();
36+
let mut cmd = std::process::Command::new(parts.next().unwrap());
37+
cmd.args(parts).arg(&bin);
38+
cmd
3439
})
35-
.unwrap_or_else(|| Command::cargo_bin("ouch").expect("Failed to find ouch executable"))
40+
.unwrap_or_else(|| std::process::Command::new(&bin))
3641
}
3742

3843
pub fn testdir() -> io::Result<(tempfile::TempDir, &'static Path)> {

0 commit comments

Comments
 (0)