From eb7ff83f83a4905223caaaeb0036c46b0ca4a057 Mon Sep 17 00:00:00 2001 From: ksgk1 Date: Sun, 26 Apr 2026 13:55:08 +0200 Subject: [PATCH 1/9] sort: Fix inconsistent sort orderg under i18n-collator with equal sorting keys. --- src/uu/sort/src/sort.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 8b321b55d53..a160c1ad0f2 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -2656,7 +2656,16 @@ fn compare_by<'a>( let a_key = a_line_data.collation_key(a.index); let b_key = b_line_data.collation_key(b.index); let cmp = a_key.cmp(b_key); - return if global_settings.reverse { + // If collation keys are equal, fall back to lexicographic comparison + // This can be the case for inputs like 01 and 0_1, which have equal keys + if cmp == Ordering::Equal { + return if global_settings.reverse { + a.line.cmp(b.line).reverse() + } else { + a.line.cmp(b.line) + }; + } + if global_settings.reverse { cmp.reverse() } else { cmp From 091e81b66d28f46cf9e84b5bb38fbd4b14fbed97 Mon Sep 17 00:00:00 2001 From: ksgk1 Date: Mon, 27 Apr 2026 22:27:44 +0200 Subject: [PATCH 2/9] Test cases for fix #11980 --- tests/by-util/test_sort.rs | 29 +++++++++++++++++++++++++++++ tests/fixtures/sort/sort1.txt | 3 +++ tests/fixtures/sort/sort2.txt | 3 +++ 3 files changed, 35 insertions(+) create mode 100644 tests/fixtures/sort/sort1.txt create mode 100644 tests/fixtures/sort/sort2.txt diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index b0e7602fd7f..8b79065ac7c 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -2960,4 +2960,33 @@ e f 5436 down data path1 path2 path3 path4 path5\n"; .stdout_is(input); } +#[test] +fn test_inconsitent_sorting_with_i18n_collate() { + // Regression test for issue #11980 + // Lexicographic fallback sorting for equal sorting keys for 01 and 0_1 + let expected_output = "01\n01\n0_1\n0_1\n02\n02\n"; + new_ucmd!() + .env("LC_ALL", "en_US.UTF-8") + .arg("sort1.txt") + .arg("sort2.txt") + .succeeds() + .stdout_is(expected_output); + + let expected_output = "0_1\n0_1\n01\n01\n02\n02\n"; + new_ucmd!() + .env("LC_ALL", "en_US.UTF-8") + .arg("--sort=general-numeric") + .arg("sort1.txt") + .arg("sort2.txt") + .succeeds() + .stdout_is(expected_output); + + let expected_output = "01\n01\n02\n02\n0_1\n0_1\n"; + new_ucmd!() + .arg("sort1.txt") + .arg("sort2.txt") + .succeeds() + .stdout_is(expected_output); +} + /* spell-checker: enable */ diff --git a/tests/fixtures/sort/sort1.txt b/tests/fixtures/sort/sort1.txt new file mode 100644 index 00000000000..20a849540bb --- /dev/null +++ b/tests/fixtures/sort/sort1.txt @@ -0,0 +1,3 @@ +01 +0_1 +02 \ No newline at end of file diff --git a/tests/fixtures/sort/sort2.txt b/tests/fixtures/sort/sort2.txt new file mode 100644 index 00000000000..bafe91c4f18 --- /dev/null +++ b/tests/fixtures/sort/sort2.txt @@ -0,0 +1,3 @@ +0_1 +01 +02 \ No newline at end of file From 5f137e502c87e43dfef9fa607b716c9a3151e0eb Mon Sep 17 00:00:00 2001 From: ksgk1 Date: Mon, 27 Apr 2026 22:27:56 +0200 Subject: [PATCH 3/9] Simplyfing fix for #11980 --- src/uu/sort/src/sort.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index a160c1ad0f2..72f3496e7f1 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -2655,17 +2655,13 @@ fn compare_by<'a>( if global_settings.precomputed.fast_locale_collation { let a_key = a_line_data.collation_key(a.index); let b_key = b_line_data.collation_key(b.index); - let cmp = a_key.cmp(b_key); + let mut cmp = a_key.cmp(b_key); // If collation keys are equal, fall back to lexicographic comparison // This can be the case for inputs like 01 and 0_1, which have equal keys if cmp == Ordering::Equal { - return if global_settings.reverse { - a.line.cmp(b.line).reverse() - } else { - a.line.cmp(b.line) - }; - } - if global_settings.reverse { + cmp = a.line.cmp(b.line); + }; + return if global_settings.reverse { cmp.reverse() } else { cmp From d9039af8832d229129aa5351a03a042b34b2d6b0 Mon Sep 17 00:00:00 2001 From: ksgk1 Date: Tue, 28 Apr 2026 07:39:58 +0200 Subject: [PATCH 4/9] Fix clippy lint and rename test files. --- src/uu/sort/src/sort.rs | 4 ++-- tests/by-util/test_sort.rs | 12 ++++++------ .../sort/fix_i18n_collate_inconsistency_1.txt | 3 +++ .../sort/fix_i18n_collate_inconsistency_2.txt | 3 +++ 4 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 tests/fixtures/sort/fix_i18n_collate_inconsistency_1.txt create mode 100644 tests/fixtures/sort/fix_i18n_collate_inconsistency_2.txt diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 72f3496e7f1..e64ea99feff 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -2657,10 +2657,10 @@ fn compare_by<'a>( let b_key = b_line_data.collation_key(b.index); let mut cmp = a_key.cmp(b_key); // If collation keys are equal, fall back to lexicographic comparison - // This can be the case for inputs like 01 and 0_1, which have equal keys + // This can be the case for inputs like `01` and `0_1`, which have equal keys if cmp == Ordering::Equal { cmp = a.line.cmp(b.line); - }; + } return if global_settings.reverse { cmp.reverse() } else { diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 8b79065ac7c..6636cb88db8 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -2967,8 +2967,8 @@ fn test_inconsitent_sorting_with_i18n_collate() { let expected_output = "01\n01\n0_1\n0_1\n02\n02\n"; new_ucmd!() .env("LC_ALL", "en_US.UTF-8") - .arg("sort1.txt") - .arg("sort2.txt") + .arg("fix_i18n_collate_inconsistency_1.txt") + .arg("fix_i18n_collate_inconsistency_2.txt") .succeeds() .stdout_is(expected_output); @@ -2976,15 +2976,15 @@ fn test_inconsitent_sorting_with_i18n_collate() { new_ucmd!() .env("LC_ALL", "en_US.UTF-8") .arg("--sort=general-numeric") - .arg("sort1.txt") - .arg("sort2.txt") + .arg("fix_i18n_collate_inconsistency_1.txt") + .arg("fix_i18n_collate_inconsistency_2.txt") .succeeds() .stdout_is(expected_output); let expected_output = "01\n01\n02\n02\n0_1\n0_1\n"; new_ucmd!() - .arg("sort1.txt") - .arg("sort2.txt") + .arg("fix_i18n_collate_inconsistency_1.txt") + .arg("fix_i18n_collate_inconsistency_2.txt") .succeeds() .stdout_is(expected_output); } diff --git a/tests/fixtures/sort/fix_i18n_collate_inconsistency_1.txt b/tests/fixtures/sort/fix_i18n_collate_inconsistency_1.txt new file mode 100644 index 00000000000..20a849540bb --- /dev/null +++ b/tests/fixtures/sort/fix_i18n_collate_inconsistency_1.txt @@ -0,0 +1,3 @@ +01 +0_1 +02 \ No newline at end of file diff --git a/tests/fixtures/sort/fix_i18n_collate_inconsistency_2.txt b/tests/fixtures/sort/fix_i18n_collate_inconsistency_2.txt new file mode 100644 index 00000000000..bafe91c4f18 --- /dev/null +++ b/tests/fixtures/sort/fix_i18n_collate_inconsistency_2.txt @@ -0,0 +1,3 @@ +0_1 +01 +02 \ No newline at end of file From d064331b9d4b00b7d9f1f83ecade6b94e2056a4f Mon Sep 17 00:00:00 2001 From: ksgk1 Date: Tue, 28 Apr 2026 08:01:58 +0200 Subject: [PATCH 5/9] Remove old test files --- tests/fixtures/sort/sort1.txt | 3 --- tests/fixtures/sort/sort2.txt | 3 --- 2 files changed, 6 deletions(-) delete mode 100644 tests/fixtures/sort/sort1.txt delete mode 100644 tests/fixtures/sort/sort2.txt diff --git a/tests/fixtures/sort/sort1.txt b/tests/fixtures/sort/sort1.txt deleted file mode 100644 index 20a849540bb..00000000000 --- a/tests/fixtures/sort/sort1.txt +++ /dev/null @@ -1,3 +0,0 @@ -01 -0_1 -02 \ No newline at end of file diff --git a/tests/fixtures/sort/sort2.txt b/tests/fixtures/sort/sort2.txt deleted file mode 100644 index bafe91c4f18..00000000000 --- a/tests/fixtures/sort/sort2.txt +++ /dev/null @@ -1,3 +0,0 @@ -0_1 -01 -02 \ No newline at end of file From 7fcd0e3526658a196b9c0e2d9575cb8efcd5f0f7 Mon Sep 17 00:00:00 2001 From: ksgk1 <29155932+ksgk1@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:38:48 +0200 Subject: [PATCH 6/9] Update tests/by-util/test_sort.rs Co-authored-by: Daniel Hofstetter --- tests/by-util/test_sort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 6636cb88db8..d7a07c8422b 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -2961,7 +2961,7 @@ e f 5436 down data path1 path2 path3 path4 path5\n"; } #[test] -fn test_inconsitent_sorting_with_i18n_collate() { +fn test_consistent_sorting_with_i18n_collate() { // Regression test for issue #11980 // Lexicographic fallback sorting for equal sorting keys for 01 and 0_1 let expected_output = "01\n01\n0_1\n0_1\n02\n02\n"; From 573eaa0fdf6fceb07ef5d49f721748d62e3fb6f9 Mon Sep 17 00:00:00 2001 From: ksgk1 <29155932+ksgk1@users.noreply.github.com> Date: Tue, 28 Apr 2026 17:40:36 +0200 Subject: [PATCH 7/9] Update tests/by-util/test_sort.rs Co-authored-by: Daniel Hofstetter --- tests/by-util/test_sort.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index d7a07c8422b..e708378cf21 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -2983,6 +2983,7 @@ fn test_consistent_sorting_with_i18n_collate() { let expected_output = "01\n01\n02\n02\n0_1\n0_1\n"; new_ucmd!() + .env("LC_ALL", "C") .arg("fix_i18n_collate_inconsistency_1.txt") .arg("fix_i18n_collate_inconsistency_2.txt") .succeeds() From 6779e37f24a575fe733a9151bebc6eed0046b430 Mon Sep 17 00:00:00 2001 From: ksgk1 Date: Wed, 29 Apr 2026 17:18:21 +0200 Subject: [PATCH 8/9] Removing redundant test and swapping default order for sort to match sort's ordering. --- src/uu/sort/src/sort.rs | 2 +- tests/by-util/test_sort.rs | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 6cee0e7f308..10b95343ac8 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -2659,7 +2659,7 @@ fn compare_by<'a>( // If collation keys are equal, fall back to lexicographic comparison // This can be the case for inputs like `01` and `0_1`, which have equal keys if cmp == Ordering::Equal { - cmp = a.line.cmp(b.line); + cmp = b.line.cmp(a.line); } return if global_settings.reverse { cmp.reverse() diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index e708378cf21..1e3bc8842b2 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -2964,18 +2964,9 @@ e f 5436 down data path1 path2 path3 path4 path5\n"; fn test_consistent_sorting_with_i18n_collate() { // Regression test for issue #11980 // Lexicographic fallback sorting for equal sorting keys for 01 and 0_1 - let expected_output = "01\n01\n0_1\n0_1\n02\n02\n"; - new_ucmd!() - .env("LC_ALL", "en_US.UTF-8") - .arg("fix_i18n_collate_inconsistency_1.txt") - .arg("fix_i18n_collate_inconsistency_2.txt") - .succeeds() - .stdout_is(expected_output); - let expected_output = "0_1\n0_1\n01\n01\n02\n02\n"; new_ucmd!() .env("LC_ALL", "en_US.UTF-8") - .arg("--sort=general-numeric") .arg("fix_i18n_collate_inconsistency_1.txt") .arg("fix_i18n_collate_inconsistency_2.txt") .succeeds() From f7b05d8e4fa8958470089bd0eccc6b2b19109cb9 Mon Sep 17 00:00:00 2001 From: ksgk1 Date: Wed, 29 Apr 2026 23:43:40 +0200 Subject: [PATCH 9/9] Comment for clarification. --- src/uu/sort/src/sort.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 10b95343ac8..861c05f6ab1 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -2659,6 +2659,7 @@ fn compare_by<'a>( // If collation keys are equal, fall back to lexicographic comparison // This can be the case for inputs like `01` and `0_1`, which have equal keys if cmp == Ordering::Equal { + // Reversing the order to match sort's sorting behaviour cmp = b.line.cmp(a.line); } return if global_settings.reverse {