Skip to content

Commit 0f8eb45

Browse files
authored
tsort: gnu misc tsort.pl (#9289)
* test: add comprehensive test coverage for tsort cycle detection and graph topologies - Introduce new test cases for cycle loops in file inputs, including extra nodes and multiple loops - Add tests for POSIX graph examples and linear tree graphs to validate topological sorting - Include tests for error handling on odd token counts and multiple file inputs - Ensures robustness and correctness of tsort implementation across various edge cases and standard scenarios * refactor(tests): consolidate multi-line string constants to single-line literals in tsort test file - Reformatted TSORT_LOOP_STDERR_AC and TSORT_UNEXPECTED_ARG_ERROR constants for improved code readability and consistency, with no change in string content or functionality. The multi-line format was merged into single lines to align with potential linting rules or style preferences for string literals in tests. This refactoring enhances maintainability without affecting test logic. * feat(tsort): add hidden warn flag and reverse successor iteration order - Added `ArgAction` import and a new hidden `-w`/`--warn` argument to the tsort command for future warning features - Modified iteration of successor names to use `.into_iter().rev()` in the topological sorting algorithm to process nodes in reverse order, ensuring more stable and predictable output sequence - Refactored Clap error localization in `clap_localization.rs` to use `print_prefixed_error()` method instead of direct `eprintln!` calls, improving consistency in error message formatting across the application * fix(test): prefix uniq error messages with program name Update expected error outputs in uniq tests to match the new format where error messages are prefixed with "uniq: ". This ensures test cases align with the updated error message formatting in the utility, providing clearer error identification by including the program name at the start of each error message. Changes affect multiple test assertions for invalid options and incompatible argument combinations. * fix(test): update chroot error assertion to include command prefix The expected error message now starts with "chroot: " to match the updated output format in the chroot utility. This ensures the test accurately reflects the command's behavior. * fix(test/chroot): update error message assertion to match standardized format Remove "chroot: " prefix from expected error output, aligning the test with the updated stderr format that omits utility name redundancy in error messages. * fix: update uniq error messages in tests, removing 'uniq: ' prefix Remove the 'uniq: ' prefix from expected error messages in test cases to match the updated output format of the uniq utility, ensuring tests pass with the current implementation. This change affects multiple GNU compatibility tests for invalid options and argument conflicts. * fix(comm): update test assertion to match actual error message without 'comm: ' prefix The stderr assertion in test_comm_arg_error was expecting an error message prefixed with "comm: ", but the actual command output does not include this prefix. This update fixes the test to align with the real behavior, ensuring the test passes correctly. * refactor(clap_localization): replace print_prefixed_error with direct stderr output in ErrorFormatter Replace the call to self.print_prefixed_error with direct eprintln for printing unexpected argument errors, and add an additional blank line for better formatting and readability in error messages. This change aims to simplify the output process and ensure consistent error presentation in the clap localization module. * refactor(clap_localization): remove prefixed error printing and use direct eprintln for cleaner output Modified error handling in clap_localization.rs to eliminate the utility name prefix by replacing self.print_prefixed_error calls with direct eprintln! invocations. This simplifies the codebase and changes error message formatting to display clap errors without the preceding util name. Removed the unused print_prefixed_error method. * test(tests/tsort): ignore test for single input file until error message is corrected - Added #[ignore] attribute to test_only_one_input_file to skip it during execution. - Reason: Test likely fails due to an incorrect error message; this prevents false negatives while the message is being fixed in the tsort utility. * feat(tsort): reject multiple input arguments with custom error - Change FILE arg to accept zero or more inputs (appended), defaulting to "-" if none - Add validation to error on more than one input with "extra operand" message - Update test to expect new error format, matching GNU tsort behavior - Unignore test_only_one_input_file after error message correction * refactor: format TSORT_EXTRA_OPERAND_ERROR constant for readability Split the TSORT_EXTRA_OPERAND_ERROR constant string into multiple lines to improve code formatting and adhere to line length guidelines. * chore: remove tests_tsort.patch from gnu-patches series Removed the tests_tsort.patch entry as it is no longer applied, possibly due to upstream integration or obsolescence, to keep the patch series current and relevant. * fix(tsort): simplify error message construction by removing .into() wrapper Remove unnecessary `.into()` call when creating the extra operand error in uumain, resulting in cleaner, more concise error handling code. This change does not alter the program's functionality but improves code readability and reduces nesting. * feat: internationalize error messages in tsort command Add localized strings for 'extra operand' and 'at least one input' errors in en-US and fr-FR locales. Update code to use translate! macro for consistent error reporting across languages, improving user experience for international users. * fix(tsort): ensure expect message is &str by calling .as_str() The translate! macro returns a String, but expect() requires a &str. Added .as_str() to convert the translated string for correct type usage and fix compilation error.
1 parent 370ea74 commit 0f8eb45

File tree

8 files changed

+147
-40
lines changed

8 files changed

+147
-40
lines changed

src/uu/tsort/locales/en-US.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ tsort-usage = tsort [OPTIONS] FILE
66
tsort-error-is-dir = read error: Is a directory
77
tsort-error-odd = input contains an odd number of tokens
88
tsort-error-loop = input contains a loop:
9+
tsort-error-extra-operand = extra operand { $operand }
10+
Try '{ $util } --help' for more information.
11+
tsort-error-at-least-one-input = at least one input

src/uu/tsort/locales/fr-FR.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ tsort-usage = tsort [OPTIONS] FILE
66
tsort-error-is-dir = erreur de lecture : c'est un répertoire
77
tsort-error-odd = l'entrée contient un nombre impair de jetons
88
tsort-error-loop = l'entrée contient une boucle :
9+
tsort-error-extra-operand = opérande supplémentaire { $operand }
10+
Essayez '{ $util } --help' pour plus d'informations.
11+
tsort-error-at-least-one-input = au moins une entrée

src/uu/tsort/src/tsort.rs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55
//spell-checker:ignore TAOCP indegree
6-
use clap::{Arg, Command};
6+
use clap::{Arg, ArgAction, Command};
77
use std::collections::hash_map::Entry;
88
use std::collections::{HashMap, VecDeque};
99
use std::ffi::OsString;
1010
use std::path::Path;
1111
use thiserror::Error;
1212
use uucore::display::Quotable;
13-
use uucore::error::{UError, UResult};
13+
use uucore::error::{UError, UResult, USimpleError};
1414
use uucore::{format_usage, show};
1515

1616
use uucore::translate;
@@ -49,15 +49,36 @@ impl UError for LoopNode<'_> {}
4949
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
5050
let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
5151

52-
let input = matches
53-
.get_one::<OsString>(options::FILE)
54-
.expect("Value is required by clap");
52+
let mut inputs: Vec<OsString> = matches
53+
.get_many::<OsString>(options::FILE)
54+
.map(|vals| vals.cloned().collect())
55+
.unwrap_or_default();
56+
57+
if inputs.is_empty() {
58+
inputs.push(OsString::from("-"));
59+
}
60+
61+
if inputs.len() > 1 {
62+
return Err(USimpleError::new(
63+
1,
64+
translate!(
65+
"tsort-error-extra-operand",
66+
"operand" => inputs[1].quote(),
67+
"util" => uucore::util_name()
68+
),
69+
));
70+
}
71+
72+
let input = inputs
73+
.into_iter()
74+
.next()
75+
.expect(translate!("tsort-error-at-least-one-input").as_str());
5576

5677
let data = if input == "-" {
5778
let stdin = std::io::stdin();
5879
std::io::read_to_string(stdin)?
5980
} else {
60-
let path = Path::new(input);
81+
let path = Path::new(&input);
6182
if path.is_dir() {
6283
return Err(TsortError::IsDir(input.to_string_lossy().to_string()).into());
6384
}
@@ -96,12 +117,19 @@ pub fn uu_app() -> Command {
96117
.override_usage(format_usage(&translate!("tsort-usage")))
97118
.about(translate!("tsort-about"))
98119
.infer_long_args(true)
120+
.arg(
121+
Arg::new("warn")
122+
.short('w')
123+
.action(ArgAction::SetTrue)
124+
.hide(true),
125+
)
99126
.arg(
100127
Arg::new(options::FILE)
101-
.default_value("-")
102128
.hide(true)
103129
.value_parser(clap::value_parser!(OsString))
104-
.value_hint(clap::ValueHint::FilePath),
130+
.value_hint(clap::ValueHint::FilePath)
131+
.num_args(0..)
132+
.action(ArgAction::Append),
105133
)
106134
}
107135

@@ -190,7 +218,7 @@ impl<'input> Graph<'input> {
190218
let v = self.find_next_node(&mut independent_nodes_queue);
191219
println!("{v}");
192220
if let Some(node_to_process) = self.nodes.remove(v) {
193-
for successor_name in node_to_process.successor_names {
221+
for successor_name in node_to_process.successor_names.into_iter().rev() {
194222
let successor_node = self.nodes.get_mut(successor_name).unwrap();
195223
successor_node.predecessor_count -= 1;
196224
if successor_node.predecessor_count == 0 {

src/uucore/src/lib/mods/clap_localization.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,6 @@ impl<'a> ErrorFormatter<'a> {
205205
"value" => self.color_mgr.colorize(&value, Color::Yellow),
206206
"option" => self.color_mgr.colorize(&option, Color::Green)
207207
);
208-
209208
// Include validation error if present
210209
match err.source() {
211210
Some(source) if matches!(err.kind(), ErrorKind::ValueValidation) => {

tests/by-util/test_tsort.rs

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ fn test_multiple_arguments() {
7777
.arg("call_graph.txt")
7878
.arg("invalid_file")
7979
.fails()
80-
.stderr_contains("unexpected argument 'invalid_file' found");
80+
.stderr_contains("extra operand 'invalid_file'");
8181
}
8282

8383
#[test]
@@ -119,7 +119,7 @@ fn test_two_cycles() {
119119
new_ucmd!()
120120
.pipe_in("a b b c c b b d d b")
121121
.fails_with_code(1)
122-
.stdout_is("a\nb\nc\nd\n")
122+
.stdout_is("a\nb\nd\nc\n")
123123
.stderr_is("tsort: -: input contains a loop:\ntsort: b\ntsort: c\ntsort: -: input contains a loop:\ntsort: b\ntsort: d\n");
124124
}
125125

@@ -153,3 +153,95 @@ fn test_loop_for_iterative_dfs_correctness() {
153153
.fails_with_code(1)
154154
.stderr_contains("tsort: -: input contains a loop:\ntsort: B\ntsort: C");
155155
}
156+
157+
const TSORT_LOOP_STDERR: &str = "tsort: f: input contains a loop:\ntsort: s\ntsort: t\n";
158+
const TSORT_LOOP_STDERR_AC: &str = "tsort: f: input contains a loop:\ntsort: a\ntsort: b\ntsort: f: input contains a loop:\ntsort: a\ntsort: c\n";
159+
const TSORT_ODD_ERROR: &str = "tsort: -: input contains an odd number of tokens\n";
160+
const TSORT_EXTRA_OPERAND_ERROR: &str =
161+
"tsort: extra operand 'g'\nTry 'tsort --help' for more information.\n";
162+
163+
#[test]
164+
fn test_cycle_loop_from_file() {
165+
let (at, mut ucmd) = at_and_ucmd!();
166+
at.write("f", "t b\nt s\ns t\n");
167+
168+
ucmd.arg("f")
169+
.fails_with_code(1)
170+
.stdout_is("s\nt\nb\n")
171+
.stderr_is(TSORT_LOOP_STDERR);
172+
}
173+
174+
#[test]
175+
fn test_cycle_loop_with_extra_node_from_file() {
176+
let (at, mut ucmd) = at_and_ucmd!();
177+
at.write("f", "t x\nt s\ns t\n");
178+
179+
ucmd.arg("f")
180+
.fails_with_code(1)
181+
.stdout_is("s\nt\nx\n")
182+
.stderr_is(TSORT_LOOP_STDERR);
183+
}
184+
185+
#[test]
186+
fn test_cycle_loop_multiple_loops_from_file() {
187+
let (at, mut ucmd) = at_and_ucmd!();
188+
at.write("f", "a a\na b\na c\nc a\nb a\n");
189+
190+
ucmd.arg("f")
191+
.fails_with_code(1)
192+
.stdout_is("a\nc\nb\n")
193+
.stderr_is(TSORT_LOOP_STDERR_AC);
194+
}
195+
196+
#[test]
197+
fn test_posix_graph_examples() {
198+
new_ucmd!()
199+
.pipe_in("a b c c d e\ng g\nf g e f\nh h\n")
200+
.succeeds()
201+
.stdout_only("a\nc\nd\nh\nb\ne\nf\ng\n");
202+
203+
new_ucmd!()
204+
.pipe_in("b a\nd c\nz h x h r h\n")
205+
.succeeds()
206+
.stdout_only("b\nd\nr\nx\nz\na\nc\nh\n");
207+
}
208+
209+
#[test]
210+
fn test_linear_tree_graphs() {
211+
new_ucmd!()
212+
.pipe_in("a b b c c d d e e f f g\n")
213+
.succeeds()
214+
.stdout_only("a\nb\nc\nd\ne\nf\ng\n");
215+
216+
new_ucmd!()
217+
.pipe_in("a b b c c d d e e f f g\nc x x y y z\n")
218+
.succeeds()
219+
.stdout_only("a\nb\nc\nx\nd\ny\ne\nz\nf\ng\n");
220+
221+
new_ucmd!()
222+
.pipe_in("a b b c c d d e e f f g\nc x x y y z\nf r r s s t\n")
223+
.succeeds()
224+
.stdout_only("a\nb\nc\nx\nd\ny\ne\nz\nf\nr\ng\ns\nt\n");
225+
}
226+
227+
#[test]
228+
fn test_odd_number_of_tokens() {
229+
new_ucmd!()
230+
.pipe_in("a\n")
231+
.fails_with_code(1)
232+
.stdout_is("")
233+
.stderr_is(TSORT_ODD_ERROR);
234+
}
235+
236+
#[test]
237+
fn test_only_one_input_file() {
238+
let (at, mut ucmd) = at_and_ucmd!();
239+
at.write("f", "");
240+
at.write("g", "");
241+
242+
ucmd.arg("f")
243+
.arg("g")
244+
.fails_with_code(1)
245+
.stdout_is("")
246+
.stderr_is(TSORT_EXTRA_OPERAND_ERROR);
247+
}
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
main
2-
parse_options
3-
tail_file
42
tail_forever
5-
tail
3+
tail_file
4+
parse_options
65
recheck
6+
tail
77
write_header
8-
tail_lines
9-
tail_bytes
108
pretty_name
11-
start_lines
12-
file_lines
13-
pipe_lines
14-
xlseek
15-
start_bytes
9+
tail_bytes
10+
tail_lines
1611
pipe_bytes
12+
start_bytes
13+
xlseek
14+
pipe_lines
15+
file_lines
16+
start_lines
1717
dump_remainder

util/gnu-patches/series

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ tests_env_env-S.pl.patch
77
tests_invalid_opt.patch
88
tests_ls_no_cap.patch
99
tests_sort_merge.pl.patch
10-
tests_tsort.patch
1110
tests_du_move_dir_while_traversing.patch
1211
test_mkdir_restorecon.patch
1312
error_msg_uniq.diff

util/gnu-patches/tests_tsort.patch

Lines changed: 0 additions & 17 deletions
This file was deleted.

0 commit comments

Comments
 (0)