diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index 12f6e9ac5..d2b5aed5c 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -596,7 +596,7 @@ fn split_bytes<'a>(input: &'a [u8], delim: &'a [u8]) -> impl Iterator( +pub fn write_formatted_with_delimiter( writer: &mut W, input: &[u8], options: &NumfmtOptions, @@ -632,7 +632,7 @@ pub fn write_formatted_with_delimiter( Ok(()) } -pub fn write_formatted_with_whitespace( +pub fn write_formatted_with_whitespace( writer: &mut W, s: &str, options: &NumfmtOptions, diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index c0726ac67..63908c302 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -20,6 +20,7 @@ use std::str::FromStr; use units::{IEC_BASES, SI_BASES}; use uucore::display::Quotable; use uucore::error::UResult; +use uucore::util_name; use uucore::i18n::decimal::locale_grouping_separator; use uucore::parser::shortcut_value_parser::ShortcutValueParser; @@ -46,15 +47,28 @@ fn format_and_write( Some(i) => &input_line[..i], None => input_line, }; + + // In non-abort modes we buffer the formatted output so that on error we + // can emit the original line instead. + let buffer_output = !matches!(options.invalid, InvalidModes::Abort); let mut formatted_line = Vec::new(); - let handled_line = if options.delimiter.is_some() { - write_formatted_with_delimiter(&mut formatted_line, line, options, eol) - } else { - // Whitespace mode requires valid UTF-8 - match std::str::from_utf8(line) { - Ok(s) => write_formatted_with_whitespace(&mut formatted_line, s, options, eol), - Err(_) => { - Err(translate!("numfmt-error-invalid-number", "input" => escape_line(line).quote())) + let handled_line = { + let output: &mut dyn std::io::Write = if buffer_output { + &mut formatted_line + } else { + writer + }; + + if options.delimiter.is_some() { + write_formatted_with_delimiter(output, line, options, eol) + } else { + // Whitespace mode requires valid UTF-8 + match std::str::from_utf8(line) { + Ok(s) => write_formatted_with_whitespace(output, s, options, eol), + Err(_) => Err(translate!( + "numfmt-error-invalid-number", + "input" => escape_line(line).quote() + )), } } }; @@ -80,7 +94,9 @@ fn format_and_write( return Ok(true); } - writer.write_all(&formatted_line)?; + if buffer_output { + writer.write_all(&formatted_line)?; + } Ok(false) } diff --git a/tests/by-util/test_numfmt.rs b/tests/by-util/test_numfmt.rs index f4af8f4f1..4ffc258d7 100644 --- a/tests/by-util/test_numfmt.rs +++ b/tests/by-util/test_numfmt.rs @@ -1253,6 +1253,15 @@ fn test_invalid_fail_with_fields_does_not_duplicate_output() { .stderr_is("numfmt: invalid number: 'Foo'\n"); } +#[test] +fn test_abort_with_fields_preserves_partial_output() { + new_ucmd!() + .args(&["--field=3", "--from=auto", "Hello 40M World 90G"]) + .fails_with_code(2) + .stdout_is("Hello 40M ") + .stderr_is("numfmt: invalid number: 'World'\n"); +} + #[test] fn test_rejects_malformed_number_forms() { new_ucmd!() diff --git a/util/build-gnu.sh b/util/build-gnu.sh index e58c2dc34..6c5cb6145 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -192,24 +192,6 @@ sed -i "s|--coreutils-prog=||g" tests/misc/coreutils.sh # Use the system coreutils where the test fails due to error in a util that is not the one being tested sed -i "s|grep '^#define HAVE_CAP 1' \$CONFIG_HEADER > /dev/null|true|" tests/ls/capability.sh -# Keep GNU numfmt coverage focused on the behavior we currently support. -sed -i '/# uutils-numfmt-adjustments start/,/# uutils-numfmt-adjustments end/d' tests/numfmt/numfmt.pl -sed -i '/push @Tests, @Locale_Tests if $locale ne '\''C'\'';/a\ -# uutils-numfmt-adjustments start\ -my %skip_uutils_numfmt_tests = map { $_ => 1 } qw(\ - unit-sep-22-fail grp-1 grp-2 delim-7 field-1\ - field-range-err-1 field-range-err-2 field-range-err-3 field-range-err-4\ - field-range-err-6 field-range-err-7 field-range-err-8 field-range-err-9\ - field-range-err-10 field-range-err-11 field-range-err-12 field-range-err-13\ - strtod-6 strtod-6.1 leading-4 debug-2\ - devdebug-1 devdebug-2 devdebug-3 devdebug-4 devdebug-5 devdebug-6\ - devdebug-7 devdebug-9 devdebug-10 devdebug-11\ - help-1 fmt-err-9 fmt-err-11 fmt-15\ - ign-err-5 ign-err-m2.2 ign-err-m3.1\ -);\ -@Tests = grep { !$skip_uutils_numfmt_tests{$_->[0]} } @Tests;\ -# uutils-numfmt-adjustments end' tests/numfmt/numfmt.pl - # our messages are better sed -i "s|cannot stat 'symlink': Permission denied|not writing through dangling symlink 'symlink'|" tests/cp/fail-perm.sh sed -i "s|cp: target directory 'symlink': Permission denied|cp: 'symlink' is not a directory|" tests/cp/fail-perm.sh