From a894e8291324e11b8b456208ed09b5d021efd235 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 8 Mar 2026 22:28:12 +0100 Subject: [PATCH 1/9] gnu test: get the version for our tests --- util/gnu-patches/series | 1 + util/gnu-patches/tests_help_help-version.patch | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 util/gnu-patches/tests_help_help-version.patch diff --git a/util/gnu-patches/series b/util/gnu-patches/series index f1a24d0f67e..995e8749f00 100644 --- a/util/gnu-patches/series +++ b/util/gnu-patches/series @@ -10,3 +10,4 @@ tests_sort_merge.pl.patch tests_du_move_dir_while_traversing.patch test_mkdir_restorecon.patch error_msg_uniq.diff +tests_help_help-version.patch diff --git a/util/gnu-patches/tests_help_help-version.patch b/util/gnu-patches/tests_help_help-version.patch new file mode 100644 index 00000000000..235ccb5d663 --- /dev/null +++ b/util/gnu-patches/tests_help_help-version.patch @@ -0,0 +1,17 @@ +Index: gnu/tests/help/help-version.sh +=================================================================== +--- gnu.orig/tests/help/help-version.sh ++++ gnu/tests/help/help-version.sh +@@ -56,9 +56,9 @@ expected_failure_status_fgrep=2 + test "$built_programs" \ + || fail_ "built_programs not specified!?!" + +-test "$VERSION" \ +- || fail_ "set envvar VERSION; it is required for a PATH sanity-check" +- ++# Extract VERSION dynamically from first program's output for uutils ++for i in $built_programs; do ++ VERSION=$(env $i --version | sed -n '1s/.* //p;q'); break; done + # Extract version from --version output of the first program + for i in $built_programs; do + v=$(env $i --version | sed -n '1s/.* //p;q') From 056ac21dafa6cccc6d30b99d4fd316c5a127afd8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 9 Mar 2026 22:29:43 +0100 Subject: [PATCH 2/9] uucore: return utility-specific exit code when --help/--version write fails --- src/uucore/src/lib/mods/clap_localization.rs | 39 +++++++++++++++++++- src/uucore/src/lib/mods/error.rs | 26 ++++++++++--- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/uucore/src/lib/mods/clap_localization.rs b/src/uucore/src/lib/mods/clap_localization.rs index 0e66702defe..d99f3c3aefa 100644 --- a/src/uucore/src/lib/mods/clap_localization.rs +++ b/src/uucore/src/lib/mods/clap_localization.rs @@ -11,7 +11,7 @@ //! instead of parsing error strings, providing a more robust solution. //! -use crate::error::{UResult, USimpleError}; +use crate::error::{UClapError, UResult, USimpleError}; use crate::locale::translate; use clap::error::{ContextKind, ErrorKind}; @@ -442,7 +442,10 @@ where { cmd.try_get_matches_from(itr).map_err(|e| { if e.exit_code() == 0 { - e.into() // Preserve help/version + // For help/version display, use exit_code as the write failure code so that + // if stdout is full (e.g., /dev/full), the program exits with the utility's + // expected error code rather than the default 1. + e.with_exit_code(exit_code).into() } else { let formatter = ErrorFormatter::new(crate::util_name()); let code = formatter.print_error(&e, exit_code); @@ -451,6 +454,38 @@ where }) } +/// Like [`handle_clap_result_with_exit_code`], but allows specifying separate exit codes for +/// argument parse errors and for write failures when printing help/version output. +/// +/// This is useful for utilities that use different exit codes for I/O errors vs. argument +/// parse errors (e.g., `tty` exits 3 on write errors but 2 on argument parse errors). +pub fn handle_clap_result_with_exit_codes( + cmd: Command, + itr: I, + parse_error_code: i32, + write_failure_code: i32, +) -> UResult +where + I: IntoIterator, + T: Into + Clone, +{ + cmd.try_get_matches_from(itr).map_err(|e| { + if e.exit_code() == 0 { + // For DisplayHelp/DisplayVersion, ClapErrorWrapper::code() ignores the `code` field + // and returns either 0 (success) or `write_failure_code` (on stdout write failure). + // We pass `parse_error_code` to `with_exit_code` only to satisfy the constructor; + // the actual success/failure distinction is driven by `write_failure_code`. + e.with_exit_code(parse_error_code) + .with_write_failure_code(write_failure_code) + .into() + } else { + let formatter = ErrorFormatter::new(crate::util_name()); + let code = formatter.print_error(&e, parse_error_code); + USimpleError::new(code, "") + } + }) +} + /// Handles a clap error directly with a custom exit code. /// /// This function processes a clap error and exits the program with the specified diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index d2239d12875..9f69e3f9422 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -701,10 +701,22 @@ impl From for Box { #[derive(Debug)] pub struct ClapErrorWrapper { code: i32, + write_failure_code: i32, error: clap::Error, print_failed: Cell, } +impl ClapErrorWrapper { + /// Override the exit code to use when writing help/version output fails (e.g., /dev/full). + /// + /// By default this matches `code`, but some utilities use different exit codes for I/O errors + /// vs. argument parse errors (e.g., `tty` exits 3 on write errors, 2 on parse errors). + pub fn with_write_failure_code(mut self, code: i32) -> Self { + self.write_failure_code = code; + self + } +} + /// Extension trait for `clap::Error` to adjust the exit code. pub trait UClapError { /// Set the exit code for the program if `uumain` returns `Ok(())`. @@ -715,6 +727,7 @@ impl From for Box { fn from(e: clap::Error) -> Self { Box::new(ClapErrorWrapper { code: 1, + write_failure_code: 1, error: e, print_failed: Cell::new(false), }) @@ -725,6 +738,7 @@ impl UClapError for clap::Error { fn with_exit_code(self, code: i32) -> ClapErrorWrapper { ClapErrorWrapper { code, + write_failure_code: code, error: self, print_failed: Cell::new(false), } @@ -742,11 +756,16 @@ impl UClapError> impl UError for ClapErrorWrapper { fn code(&self) -> i32 { // If the error is a DisplayHelp or DisplayVersion variant, - // check if printing failed. If it did, return 1, otherwise 0. + // check if printing failed. If it did, return the utility-specific write failure code, + // otherwise 0 (success). if let clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion = self.error.kind() { - i32::from(self.print_failed.get()) + if self.print_failed.get() { + self.write_failure_code + } else { + 0 + } } else { self.code } @@ -767,9 +786,6 @@ impl Display for ClapErrorWrapper { // Try to display this error to stderr, but ignore if that fails too // since we're already in an error state. let _ = writeln!(std::io::stderr(), "{}: {print_fail}", crate::util_name()); - // Mirror GNU behavior: when failing to print help or version, exit with error code. - // This avoids silent failures when stdout is full or closed. - set_exit_code(1); } // Always return Ok(()) to satisfy Display's contract and prevent panic Ok(()) From e1b8a378b4a06f2be4abc5b0c904f29c0367c3f7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 9 Mar 2026 22:29:46 +0100 Subject: [PATCH 3/9] coreutils: avoid panic on write failure for --help/--version (exit 1) --- src/bin/coreutils.rs | 73 +++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 59634849a6b..0f63155089e 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -16,26 +16,32 @@ const VERSION: &str = env!("CARGO_PKG_VERSION"); include!(concat!(env!("OUT_DIR"), "/uutils_map.rs")); -fn usage(utils: &UtilityMap, name: &str) { - println!("{name} {VERSION} (multi-call binary)\n"); - println!("Usage: {name} [function [arguments...]]"); - println!(" {name} --list"); - println!(); +fn usage(utils: &UtilityMap, name: &str) -> bool { + let mut out = io::stdout(); + let ok = writeln!(out, "{name} {VERSION} (multi-call binary)\n").is_ok() + && writeln!(out, "Usage: {name} [function [arguments...]]").is_ok() + && writeln!(out, " {name} --list").is_ok() + && writeln!(out).is_ok(); #[cfg(feature = "feat_common_core")] - { - println!("Functions:"); - println!(" '' [arguments...]"); - println!(); - } - println!("Options:"); - println!(" --list lists all defined functions, one per row\n"); - println!("Currently defined functions:\n"); + let ok = ok + && writeln!(out, "Functions:").is_ok() + && writeln!(out, " '' [arguments...]").is_ok() + && writeln!(out).is_ok(); let display_list = utils.keys().copied().join(", "); - let width = cmp::min(textwrap::termwidth(), 100) - 4 * 2; // (opinion/heuristic) max 100 chars wide with 4 character side indentions - println!( - "{}", - textwrap::indent(&textwrap::fill(&display_list, width), " ") - ); + let width = cmp::min(textwrap::termwidth(), 100) - 4 * 2; + ok && writeln!(out, "Options:").is_ok() + && writeln!( + out, + " --list lists all defined functions, one per row\n" + ) + .is_ok() + && writeln!(out, "Currently defined functions:\n").is_ok() + && writeln!( + out, + "{}", + textwrap::indent(&textwrap::fill(&display_list, width), " ") + ) + .is_ok() } #[allow(clippy::cognitive_complexity)] @@ -47,7 +53,9 @@ fn main() { let binary = validation::binary_path(&mut args); let binary_as_util = validation::name(&binary).unwrap_or_else(|| { - usage(&utils, ""); + if !usage(&utils, "") { + process::exit(1); + } process::exit(0); }); @@ -78,17 +86,28 @@ fn main() { "--list" => { // If --help is also present, show usage instead of list if args.any(|arg| arg == "--help" || arg == "-h") { - usage(&utils, binary_as_util); + if !usage(&utils, binary_as_util) { + process::exit(1); + } process::exit(0); } let utils: Vec<_> = utils.keys().collect(); for util in utils { - println!("{util}"); + if writeln!(io::stdout(), "{util}").is_err() { + process::exit(1); + } } process::exit(0); } "--version" | "-V" => { - println!("{binary_as_util} {VERSION} (multi-call binary)"); + if writeln!( + io::stdout(), + "{binary_as_util} {VERSION} (multi-call binary)" + ) + .is_err() + { + process::exit(1); + } process::exit(0); } // Not a special command: fallthrough to calling a util @@ -120,13 +139,15 @@ fn main() { .into_iter() .chain(args), ); - io::stdout().flush().expect("could not flush stdout"); + let _ = io::stdout().flush(); process::exit(code); } None => validation::not_found(&util_os), } } - usage(&utils, binary_as_util); + if !usage(&utils, binary_as_util) { + process::exit(1); + } process::exit(0); } else if util.starts_with('-') { // Argument looks like an option but wasn't recognized @@ -138,7 +159,9 @@ fn main() { } } else { // no arguments provided - usage(&utils, binary_as_util); + if !usage(&utils, binary_as_util) { + process::exit(1); + } process::exit(0); } } From 6cfc628d7b03113b70890b94391d0f5027fc656c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 9 Mar 2026 22:29:49 +0100 Subject: [PATCH 4/9] tty: exit 3 on write failure, exit 2 on parse error --- src/uu/tty/src/tty.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index cb5ae8e1721..2570b55d43b 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -19,7 +19,10 @@ mod options { #[uucore::main(no_signals)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uucore::clap_localization::handle_clap_result_with_exit_code(uu_app(), args, 2)?; + // tty exits 2 on argument parse errors but 3 on write errors (consistent with its + // normal write-error behavior on line output failures). + let matches = + uucore::clap_localization::handle_clap_result_with_exit_codes(uu_app(), args, 2, 3)?; // Disable SIGPIPE so we can handle broken pipe errors gracefully // and exit with code 3 instead of being killed by the signal. From 24f33db37987580b71788a84d862bae625ee626a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 9 Mar 2026 22:29:53 +0100 Subject: [PATCH 5/9] expr: exit 3 on write failure for --help/--version --- src/uu/expr/src/expr.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index f2bec49e8d7..e10dcaae84b 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -109,14 +109,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect::, _>>()?; if args.len() == 1 && args[0] == b"--help" { - uu_app().print_help()?; + if uu_app().print_help().is_err() { + std::process::exit(3); + } } else if args.len() == 1 && args[0] == b"--version" { - writeln!( + if writeln!( stdout(), "{} {}", uucore::util_name(), uucore::crate_version!() - )?; + ) + .is_err() + { + std::process::exit(3); + } } else { // The first argument may be "--" and should be be ignored. let args = if !args.is_empty() && args[0] == b"--" { From 945266ff4833fd631aa065384a6db89dd50c2bdc Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 9 Mar 2026 22:29:57 +0100 Subject: [PATCH 6/9] env, test: use correct exit codes on --help/--version write failure (125, 2) --- src/uu/env/src/env.rs | 6 ++++-- src/uu/test/src/test.rs | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 162b0b6d9e2..5cd719efa5c 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -40,7 +40,7 @@ use std::io::stderr; use std::os::unix::ffi::OsStrExt; use uucore::display::{Quotable, print_all_env_vars}; -use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError}; +use uucore::error::{ExitCode, UClapError, UError, UResult, USimpleError, UUsageError}; use uucore::line_ending::LineEnding; #[cfg(unix)] use uucore::signals::{signal_by_name_or_value, signal_name_by_value, signal_number_upper_bound}; @@ -654,7 +654,9 @@ impl EnvAppData { Err(e) => { match e.kind() { clap::error::ErrorKind::DisplayHelp - | clap::error::ErrorKind::DisplayVersion => return Err(e.into()), + | clap::error::ErrorKind::DisplayVersion => { + return Err(e.with_exit_code(125).into()); + } _ => { // Use ErrorFormatter directly to handle error with shebang message callback let formatter = diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 92b1dcdde02..30484418797 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -50,9 +50,10 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { if binary_name.ends_with('[') { // If invoked as [ we should recognize --help and --version (but not -h or -v) if args.len() == 1 && (args[0] == "--help" || args[0] == "--version") { - uucore::clap_localization::handle_clap_result( + uucore::clap_localization::handle_clap_result_with_exit_code( uu_app(), std::iter::once(program).chain(args.into_iter()), + 2, )?; return Ok(()); } From 786ac84e7959f13c4d60b065c73702760f3b3435 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 9 Mar 2026 22:30:00 +0100 Subject: [PATCH 7/9] csplit: allow empty regex pattern // --- src/uu/csplit/src/patterns.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/csplit/src/patterns.rs b/src/uu/csplit/src/patterns.rs index e13d3c1f226..64ac4242b56 100644 --- a/src/uu/csplit/src/patterns.rs +++ b/src/uu/csplit/src/patterns.rs @@ -104,7 +104,7 @@ pub fn get_patterns(args: &[&str]) -> Result, CsplitError> { fn extract_patterns(args: &[&str]) -> Result, CsplitError> { let mut patterns = Vec::with_capacity(args.len()); let to_match_reg = - Regex::new(r"^(/(?P.+)/|%(?P.+)%)(?P[\+-]?[0-9]+)?$").unwrap(); + Regex::new(r"^(/(?P.*)/|%(?P.*)%)(?P[\+-]?[0-9]+)?$").unwrap(); let execute_ntimes_reg = Regex::new(r"^\{(?P[0-9]+)|\*\}$").unwrap(); let mut iter = args.iter().copied().peekable(); From bf49ce937a2d926bec47c3f4fa732f23e6f5cd41 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 9 Mar 2026 22:30:03 +0100 Subject: [PATCH 8/9] build-gnu: always refresh hardlinks after cargo build, silence libstdbuf same-inode warning --- util/build-gnu.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 4a731f8aa29..f3c02e5f944 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -95,11 +95,11 @@ else # Use MULTICALL=y for faster build make MULTICALL=y SKIP_UTILS=more for binary in $("${UU_BUILD_DIR}"/coreutils --list) - do [ -e "${UU_BUILD_DIR}/${binary}" ] || ln -vf "${UU_BUILD_DIR}/coreutils" "${UU_BUILD_DIR}/${binary}" + do [ "${UU_BUILD_DIR}/coreutils" -ef "${UU_BUILD_DIR}/${binary}" ] || ln -vf "${UU_BUILD_DIR}/coreutils" "${UU_BUILD_DIR}/${binary}" done - ln -vf "${UU_BUILD_DIR}"/deps/libstdbuf.* -t "${UU_BUILD_DIR}" + ln -vf "${UU_BUILD_DIR}"/deps/libstdbuf.* -t "${UU_BUILD_DIR}" 2>/dev/null || true fi -[ -e "${UU_BUILD_DIR}/ginstall" ] || ln -vf "${UU_BUILD_DIR}/install" "${UU_BUILD_DIR}/ginstall" # The GNU tests use ginstall +[ "${UU_BUILD_DIR}/install" -ef "${UU_BUILD_DIR}/ginstall" ] || ln -vf "${UU_BUILD_DIR}/install" "${UU_BUILD_DIR}/ginstall" # The GNU tests use ginstall ## cd "${path_GNU}" && echo "[ pwd:'${PWD}' ]" From 91791ea14ccd1cf44054d2a354b680608218a5c0 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 9 Mar 2026 22:38:01 +0100 Subject: [PATCH 9/9] tests: verify --help/--version exit codes on /dev/full, csplit empty pattern --- tests/by-util/test_csplit.rs | 8 ++++++++ tests/by-util/test_env.rs | 14 ++++++++++++++ tests/by-util/test_expr.rs | 11 +++++++++++ tests/by-util/test_ls.rs | 11 +++++++++++ tests/by-util/test_sort.rs | 11 +++++++++++ tests/by-util/test_test.rs | 13 +++++++++++++ tests/by-util/test_tty.rs | 10 ++++++++++ 7 files changed, 78 insertions(+) diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index 76c217a29bb..9d59e2a332e 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1583,3 +1583,11 @@ fn test_write_error_dev_full_keep_files() { assert!(at.file_exists("xx00")); assert_eq!(at.read("xx00"), "1\n"); } + +#[test] +fn test_empty_regex_pattern() { + // '//' uses the empty regex (matches beginning of each line) + let (at, mut ucmd) = at_and_ucmd!(); + at.write("input", "hello\nworld\n"); + ucmd.args(&["input", "//"]).succeeds(); +} diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index cc67357b5bb..b3bfe27c696 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -2009,3 +2009,17 @@ fn test_ignore_signal_pipe_broken_pipe_regression() { "With --ignore-signal=PIPE, process should exit gracefully (0 or 1), got: {ignore_signal_exit_code}" ); } + +#[test] +#[cfg(target_os = "linux")] +fn test_help_version_dev_full_exit_code() { + use std::fs::OpenOptions; + use uutests::new_ucmd; + for arg in ["--help", "--version"] { + let dev_full = OpenOptions::new().write(true).open("/dev/full").unwrap(); + new_ucmd!() + .arg(arg) + .set_stdout(dev_full) + .fails_with_code(125); + } +} diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index ec1cf91598f..63785f47fc3 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -1975,3 +1975,14 @@ fn test_emoji_operations() { .succeeds() .stdout_only("1\n"); } + +#[test] +#[cfg(target_os = "linux")] +fn test_help_version_dev_full_exit_code() { + use std::fs::OpenOptions; + use uutests::new_ucmd; + for arg in ["--help", "--version"] { + let dev_full = OpenOptions::new().write(true).open("/dev/full").unwrap(); + new_ucmd!().arg(arg).set_stdout(dev_full).fails_with_code(3); + } +} diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 11365392ae1..28558753e05 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -7119,3 +7119,14 @@ fn test_ls_non_utf8_hidden() { scene.ucmd().succeeds().stdout_does_not_contain(".hidden"); } + +#[test] +#[cfg(target_os = "linux")] +fn test_help_version_dev_full_exit_code() { + use std::fs::OpenOptions; + use uutests::new_ucmd; + for arg in ["--help", "--version"] { + let dev_full = OpenOptions::new().write(true).open("/dev/full").unwrap(); + new_ucmd!().arg(arg).set_stdout(dev_full).fails_with_code(2); + } +} diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index d7eeab03993..e01d7db2f17 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -2766,3 +2766,14 @@ e f 5436 down data path1 path2 path3 path4 path5\n"; } /* spell-checker: enable */ + +#[test] +#[cfg(target_os = "linux")] +fn test_help_version_dev_full_exit_code() { + use std::fs::OpenOptions; + use uutests::new_ucmd; + for arg in ["--help", "--version"] { + let dev_full = OpenOptions::new().write(true).open("/dev/full").unwrap(); + new_ucmd!().arg(arg).set_stdout(dev_full).fails_with_code(2); + } +} diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index c767efa69b6..e6628e3be91 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -1073,3 +1073,16 @@ fn test_unary_op_as_literal_in_three_arg_form() { new_ucmd!().args(&["-f", "=", "a"]).fails_with_code(1); new_ucmd!().args(&["-f", "=", "a", "-o", "b"]).succeeds(); } + +#[test] +#[cfg(target_os = "linux")] +fn test_lbracket_help_dev_full_exit_code() { + use std::fs::OpenOptions; + use uutests::util::TestScenario; + let dev_full = OpenOptions::new().write(true).open("/dev/full").unwrap(); + TestScenario::new("[") + .ucmd() + .arg("--help") + .set_stdout(dev_full) + .fails_with_code(2); +} diff --git a/tests/by-util/test_tty.rs b/tests/by-util/test_tty.rs index b5aa23aa78e..3765332d347 100644 --- a/tests/by-util/test_tty.rs +++ b/tests/by-util/test_tty.rs @@ -95,3 +95,13 @@ fn test_version_pipe_no_stderr() { child.close_stdout(); child.wait().unwrap().no_stderr(); } + +#[test] +#[cfg(target_os = "linux")] +fn test_help_version_dev_full_exit_code() { + use std::fs::OpenOptions; + for arg in ["--help", "--version"] { + let dev_full = OpenOptions::new().write(true).open("/dev/full").unwrap(); + new_ucmd!().arg(arg).set_stdout(dev_full).fails_with_code(3); + } +}