Skip to content

Commit 53d2772

Browse files
authored
Merge pull request #7527 from drinkcat/seq-tests
test_seq: Add a few more tests for corner cases
2 parents eed5c81 + 596ea0a commit 53d2772

3 files changed

Lines changed: 148 additions & 33 deletions

File tree

tests/by-util/test_seq.rs

Lines changed: 132 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
// file that was distributed with this source code.
55
// spell-checker:ignore lmnop xlmnop
66
use crate::common::util::TestScenario;
7-
use std::process::Stdio;
87

98
#[test]
109
fn test_invalid_arg() {
@@ -566,51 +565,52 @@ fn test_width_floats() {
566565
.stdout_only("09.0\n10.0\n");
567566
}
568567

569-
// TODO This is duplicated from `test_yes.rs`; refactor them.
570-
/// Run `seq`, capture some of the output, close the pipe, and verify it.
571-
fn run(args: &[&str], expected: &[u8]) {
572-
let mut cmd = new_ucmd!();
573-
let mut child = cmd.args(args).set_stdout(Stdio::piped()).run_no_wait();
574-
let buf = child.stdout_exact_bytes(expected.len());
575-
child.close_stdout();
576-
child.wait().unwrap().success();
577-
assert_eq!(buf.as_slice(), expected);
578-
}
579-
580568
#[test]
581569
fn test_neg_inf() {
582-
run(&["--", "-inf", "0"], b"-inf\n-inf\n-inf\n");
570+
new_ucmd!()
571+
.args(&["--", "-inf", "0"])
572+
.run_stdout_starts_with(b"-inf\n-inf\n-inf\n")
573+
.success();
583574
}
584575

585576
#[test]
586577
fn test_neg_infinity() {
587-
run(&["--", "-infinity", "0"], b"-inf\n-inf\n-inf\n");
578+
new_ucmd!()
579+
.args(&["--", "-infinity", "0"])
580+
.run_stdout_starts_with(b"-inf\n-inf\n-inf\n")
581+
.success();
588582
}
589583

590584
#[test]
591585
fn test_inf() {
592-
run(&["inf"], b"1\n2\n3\n");
586+
new_ucmd!()
587+
.args(&["inf"])
588+
.run_stdout_starts_with(b"1\n2\n3\n")
589+
.success();
593590
}
594591

595592
#[test]
596593
fn test_infinity() {
597-
run(&["infinity"], b"1\n2\n3\n");
594+
new_ucmd!()
595+
.args(&["infinity"])
596+
.run_stdout_starts_with(b"1\n2\n3\n")
597+
.success();
598598
}
599599

600600
#[test]
601601
fn test_inf_width() {
602-
run(
603-
&["-w", "1.000", "inf", "inf"],
604-
b"1.000\n inf\n inf\n inf\n",
605-
);
602+
new_ucmd!()
603+
.args(&["-w", "1.000", "inf", "inf"])
604+
.run_stdout_starts_with(b"1.000\n inf\n inf\n inf\n")
605+
.success();
606606
}
607607

608608
#[test]
609609
fn test_neg_inf_width() {
610-
run(
611-
&["-w", "1.000", "-inf", "-inf"],
612-
b"1.000\n -inf\n -inf\n -inf\n",
613-
);
610+
new_ucmd!()
611+
.args(&["-w", "1.000", "-inf", "-inf"])
612+
.run_stdout_starts_with(b"1.000\n -inf\n -inf\n -inf\n")
613+
.success();
614614
}
615615

616616
#[test]
@@ -936,3 +936,111 @@ fn test_parse_valid_hexadecimal_float_format_issues() {
936936
.succeeds()
937937
.stdout_only("9.92804e-09\n1\n");
938938
}
939+
940+
// GNU `seq` manual states that, when the parameters "all use a fixed point
941+
// decimal representation", the format should be `%.pf`, where the precision
942+
// is inferred from parameters. Else, `%g` is used.
943+
//
944+
// This is understandable, as translating hexadecimal precision to decimal precision
945+
// is not straightforward or intuitive to the user. There are some exceptions though,
946+
// if a mix of hexadecimal _integers_ and decimal floats are provided.
947+
//
948+
// The way precision is inferred is said to be able to "represent the output numbers
949+
// exactly". In practice, this means that trailing zeros in first/increment number are
950+
// considered, but not in the last number. This makes sense if we take that last number
951+
// as a "bound", and not really part of input/output.
952+
#[test]
953+
fn test_precision_corner_cases() {
954+
// Mixed input with integer hex still uses precision in decimal float
955+
new_ucmd!()
956+
.args(&["0x1", "0.90", "3"])
957+
.succeeds()
958+
.stdout_is("1.00\n1.90\n2.80\n");
959+
960+
// Mixed input with hex float reverts to %g
961+
new_ucmd!()
962+
.args(&["0x1.00", "0.90", "3"])
963+
.succeeds()
964+
.stdout_is("1\n1.9\n2.8\n");
965+
966+
// Even if it's the last number.
967+
new_ucmd!()
968+
.args(&["1", "1.20", "0x3.000000"])
969+
.succeeds()
970+
.stdout_is("1\n2.2\n");
971+
972+
// Otherwise, precision in last number is ignored.
973+
new_ucmd!()
974+
.args(&["1", "1.20", "3.000000"])
975+
.succeeds()
976+
.stdout_is("1.00\n2.20\n");
977+
978+
// Infinity is ignored
979+
new_ucmd!()
980+
.args(&["1", "1.2", "inf"])
981+
.run_stdout_starts_with(b"1.0\n2.2\n3.4\n")
982+
.success();
983+
}
984+
985+
// GNU `seq` manual only makes guarantees about `-w` working if the
986+
// provided numbers "all use a fixed point decimal representation",
987+
// and guides the user to use `-f` for other cases.
988+
#[test]
989+
fn test_equalize_widths_corner_cases() {
990+
// Mixed input with hexadecimal does behave as expected
991+
new_ucmd!()
992+
.args(&["-w", "0x1", "5.2", "9"])
993+
.succeeds()
994+
.stdout_is("1.0\n6.2\n");
995+
996+
// Confusingly, the number of integral digits in the last number is
997+
// used to pad the output numbers, while it is ignored for precision
998+
// computation.
999+
//
1000+
// This problem has been reported on list here:
1001+
// "bug#77179: seq incorrectly(?) pads output when last parameter magnitude"
1002+
// https://lists.gnu.org/archive/html/bug-coreutils/2025-03/msg00028.html
1003+
//
1004+
// TODO: This case could be handled correctly, consider fixing this in
1005+
// `uutils` implementation. Output should probably be "1.0\n6.2\n".
1006+
new_ucmd!()
1007+
.args(&["-w", "0x1", "5.2", "10.0000"])
1008+
.succeeds()
1009+
.stdout_is("01.0\n06.2\n");
1010+
1011+
// But if we fixed the case above, we need to make sure we still pad
1012+
// if the last number in the output requires an extra digit.
1013+
new_ucmd!()
1014+
.args(&["-w", "0x1", "5.2", "15.0000"])
1015+
.succeeds()
1016+
.stdout_is("01.0\n06.2\n11.4\n");
1017+
1018+
// GNU `seq` bails out if any hex float is in the output.
1019+
// Unlike the precision corner cases above, it is harder to justify
1020+
// this behavior for hexadecimal float inputs, as it is always be
1021+
// possible to output numbers with a fixed width.
1022+
//
1023+
// This problem has been reported on list here:
1024+
// "bug#76070: Subject: seq, hexadecimal args and equal width"
1025+
// https://lists.gnu.org/archive/html/bug-coreutils/2025-02/msg00007.html
1026+
//
1027+
// TODO: These cases could be handled correctly, consider fixing this in
1028+
// `uutils` implementation.
1029+
// If we ignore hexadecimal precision, the output should be "1.0\n6.2\n".
1030+
new_ucmd!()
1031+
.args(&["-w", "0x1.0000", "5.2", "10"])
1032+
.succeeds()
1033+
.stdout_is("1\n6.2\n");
1034+
// The equivalent `seq -w 1.0625 1.00002 3` correctly pads the first number: "1.06250\n2.06252\n"
1035+
new_ucmd!()
1036+
.args(&["-w", "0x1.1", "1.00002", "3"])
1037+
.succeeds()
1038+
.stdout_is("1.0625\n2.06252\n");
1039+
1040+
// We can't really pad with infinite number of zeros, so `-w` is ignored.
1041+
// (there is another test with infinity as an increment above)
1042+
new_ucmd!()
1043+
.args(&["-w", "1", "1.2", "inf"])
1044+
.run_stdout_starts_with(b"1.0\n2.2\n3.4\n")
1045+
.success();
1046+
}

tests/by-util/test_yes.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55
use std::ffi::OsStr;
6-
use std::process::{ExitStatus, Stdio};
6+
use std::process::ExitStatus;
77

88
#[cfg(unix)]
99
use std::os::unix::process::ExitStatusExt;
@@ -22,15 +22,10 @@ fn check_termination(result: ExitStatus) {
2222

2323
const NO_ARGS: &[&str] = &[];
2424

25-
/// Run `yes`, capture some of the output, close the pipe, and verify it.
25+
/// Run `yes`, capture some of the output, then check exit status.
2626
fn run(args: &[impl AsRef<OsStr>], expected: &[u8]) {
27-
let mut cmd = new_ucmd!();
28-
let mut child = cmd.args(args).set_stdout(Stdio::piped()).run_no_wait();
29-
let buf = child.stdout_exact_bytes(expected.len());
30-
child.close_stdout();
31-
32-
check_termination(child.wait().unwrap().exit_status());
33-
assert_eq!(buf.as_slice(), expected);
27+
let result = new_ucmd!().args(args).run_stdout_starts_with(expected);
28+
check_termination(result.exit_status());
3429
}
3530

3631
#[test]

tests/common/util.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,6 +1847,18 @@ impl UCommand {
18471847
let tmpdir_path = self.tmpd.as_ref().unwrap().path();
18481848
format!("{}/{file_rel_path}", tmpdir_path.to_str().unwrap())
18491849
}
1850+
1851+
/// Runs the command, checks that the stdout starts with "expected",
1852+
/// then terminates the command.
1853+
#[track_caller]
1854+
pub fn run_stdout_starts_with(&mut self, expected: &[u8]) -> CmdResult {
1855+
let mut child = self.set_stdout(Stdio::piped()).run_no_wait();
1856+
let buf = child.stdout_exact_bytes(expected.len());
1857+
child.close_stdout();
1858+
1859+
assert_eq!(buf.as_slice(), expected);
1860+
child.wait().unwrap()
1861+
}
18501862
}
18511863

18521864
impl std::fmt::Display for UCommand {

0 commit comments

Comments
 (0)