numfmt: buffer output in non-abort invalid modes and re-enable GNU tests

Buffer formatted output so that --invalid=fail does not duplicate lines
on error, and --invalid=abort streams directly for partial output.

Fixes GNU numfmt.pl test: field-3. Re-enables the full numfmt.pl test
suite by removing the skip list added earlier in the stack.
This commit is contained in:
Sylvestre Ledru
2026-04-04 23:24:46 +02:00
parent d68ff519b5
commit 864e95a253
4 changed files with 36 additions and 29 deletions
+2 -2
View File
@@ -596,7 +596,7 @@ fn split_bytes<'a>(input: &'a [u8], delim: &'a [u8]) -> impl Iterator<Item = &'a
})
}
pub fn write_formatted_with_delimiter<W: std::io::Write>(
pub fn write_formatted_with_delimiter<W: std::io::Write + ?Sized>(
writer: &mut W,
input: &[u8],
options: &NumfmtOptions,
@@ -632,7 +632,7 @@ pub fn write_formatted_with_delimiter<W: std::io::Write>(
Ok(())
}
pub fn write_formatted_with_whitespace<W: std::io::Write>(
pub fn write_formatted_with_whitespace<W: std::io::Write + ?Sized>(
writer: &mut W,
s: &str,
options: &NumfmtOptions,
+25 -9
View File
@@ -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<W: std::io::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<W: std::io::Write>(
return Ok(true);
}
writer.write_all(&formatted_line)?;
if buffer_output {
writer.write_all(&formatted_line)?;
}
Ok(false)
}
+9
View File
@@ -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!()
-18
View File
@@ -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