Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c81ddd3
test: add comprehensive test coverage for tsort cycle detection and g…
mattsu2020 Nov 15, 2025
3db9f15
refactor(tests): consolidate multi-line string constants to single-li…
mattsu2020 Nov 15, 2025
a3db118
feat(tsort): add hidden warn flag and reverse successor iteration order
mattsu2020 Nov 15, 2025
c75da6f
fix(test): prefix uniq error messages with program name
mattsu2020 Nov 15, 2025
70c7ed4
fix(test): update chroot error assertion to include command prefix
mattsu2020 Nov 15, 2025
a3667f2
fix(test/chroot): update error message assertion to match standardize…
mattsu2020 Nov 15, 2025
507024e
fix: update uniq error messages in tests, removing 'uniq: ' prefix
mattsu2020 Nov 15, 2025
f7b5f67
fix(comm): update test assertion to match actual error message withou…
mattsu2020 Nov 15, 2025
8601226
refactor(clap_localization): replace print_prefixed_error with direct…
mattsu2020 Nov 15, 2025
326509d
refactor(clap_localization): remove prefixed error printing and use d…
mattsu2020 Nov 15, 2025
399464a
test(tests/tsort): ignore test for single input file until error mess…
mattsu2020 Nov 15, 2025
57ab06a
Merge branch 'uutils:main' into tsort_compatibility
mattsu2020 Nov 16, 2025
37cc210
Merge branch 'main' into tsort_compatibility
mattsu2020 Dec 1, 2025
062f993
feat(tsort): reject multiple input arguments with custom error
mattsu2020 Dec 1, 2025
4275d60
refactor: format TSORT_EXTRA_OPERAND_ERROR constant for readability
mattsu2020 Dec 1, 2025
6b420d8
chore: remove tests_tsort.patch from gnu-patches series
mattsu2020 Dec 1, 2025
e288476
fix(tsort): simplify error message construction by removing .into() w…
mattsu2020 Dec 2, 2025
832c734
feat: internationalize error messages in tsort command
mattsu2020 Dec 3, 2025
7d21d9c
fix(tsort): ensure expect message is &str by calling .as_str()
mattsu2020 Dec 3, 2025
7cb4474
Merge branch 'main' into tsort_compatibility
mattsu2020 Dec 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/uu/tsort/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ tsort-usage = tsort [OPTIONS] FILE
tsort-error-is-dir = read error: Is a directory
tsort-error-odd = input contains an odd number of tokens
tsort-error-loop = input contains a loop:
tsort-error-extra-operand = extra operand { $operand }
Try '{ $util } --help' for more information.
tsort-error-at-least-one-input = at least one input
3 changes: 3 additions & 0 deletions src/uu/tsort/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ tsort-usage = tsort [OPTIONS] FILE
tsort-error-is-dir = erreur de lecture : c'est un répertoire
tsort-error-odd = l'entrée contient un nombre impair de jetons
tsort-error-loop = l'entrée contient une boucle :
tsort-error-extra-operand = opérande supplémentaire { $operand }
Essayez '{ $util } --help' pour plus d'informations.
tsort-error-at-least-one-input = au moins une entrée
46 changes: 37 additions & 9 deletions src/uu/tsort/src/tsort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//spell-checker:ignore TAOCP indegree
use clap::{Arg, Command};
use clap::{Arg, ArgAction, Command};
use std::collections::hash_map::Entry;
use std::collections::{HashMap, VecDeque};
use std::ffi::OsString;
use std::path::Path;
use thiserror::Error;
use uucore::display::Quotable;
use uucore::error::{UError, UResult};
use uucore::error::{UError, UResult, USimpleError};
use uucore::{format_usage, show};

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

let input = matches
.get_one::<OsString>(options::FILE)
.expect("Value is required by clap");
let mut inputs: Vec<OsString> = matches
.get_many::<OsString>(options::FILE)
.map(|vals| vals.cloned().collect())
.unwrap_or_default();

if inputs.is_empty() {
inputs.push(OsString::from("-"));
}

if inputs.len() > 1 {
return Err(USimpleError::new(
1,
translate!(
"tsort-error-extra-operand",
"operand" => inputs[1].quote(),
"util" => uucore::util_name()
),
));
}

let input = inputs
.into_iter()
.next()
.expect(translate!("tsort-error-at-least-one-input").as_str());

let data = if input == "-" {
let stdin = std::io::stdin();
std::io::read_to_string(stdin)?
} else {
let path = Path::new(input);
let path = Path::new(&input);
if path.is_dir() {
return Err(TsortError::IsDir(input.to_string_lossy().to_string()).into());
}
Expand Down Expand Up @@ -96,12 +117,19 @@ pub fn uu_app() -> Command {
.override_usage(format_usage(&translate!("tsort-usage")))
.about(translate!("tsort-about"))
.infer_long_args(true)
.arg(
Arg::new("warn")
.short('w')
.action(ArgAction::SetTrue)
.hide(true),
)
.arg(
Arg::new(options::FILE)
.default_value("-")
.hide(true)
.value_parser(clap::value_parser!(OsString))
.value_hint(clap::ValueHint::FilePath),
.value_hint(clap::ValueHint::FilePath)
.num_args(0..)
.action(ArgAction::Append),
)
}

Expand Down Expand Up @@ -190,7 +218,7 @@ impl<'input> Graph<'input> {
let v = self.find_next_node(&mut independent_nodes_queue);
println!("{v}");
if let Some(node_to_process) = self.nodes.remove(v) {
for successor_name in node_to_process.successor_names {
for successor_name in node_to_process.successor_names.into_iter().rev() {
let successor_node = self.nodes.get_mut(successor_name).unwrap();
successor_node.predecessor_count -= 1;
if successor_node.predecessor_count == 0 {
Expand Down
1 change: 0 additions & 1 deletion src/uucore/src/lib/mods/clap_localization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ impl<'a> ErrorFormatter<'a> {
"value" => self.color_mgr.colorize(&value, Color::Yellow),
"option" => self.color_mgr.colorize(&option, Color::Green)
);

// Include validation error if present
match err.source() {
Some(source) if matches!(err.kind(), ErrorKind::ValueValidation) => {
Expand Down
96 changes: 94 additions & 2 deletions tests/by-util/test_tsort.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ fn test_multiple_arguments() {
.arg("call_graph.txt")
.arg("invalid_file")
.fails()
.stderr_contains("unexpected argument 'invalid_file' found");
.stderr_contains("extra operand 'invalid_file'");
}

#[test]
Expand Down Expand Up @@ -119,7 +119,7 @@ fn test_two_cycles() {
new_ucmd!()
.pipe_in("a b b c c b b d d b")
.fails_with_code(1)
.stdout_is("a\nb\nc\nd\n")
.stdout_is("a\nb\nd\nc\n")
.stderr_is("tsort: -: input contains a loop:\ntsort: b\ntsort: c\ntsort: -: input contains a loop:\ntsort: b\ntsort: d\n");
}

Expand Down Expand Up @@ -153,3 +153,95 @@ fn test_loop_for_iterative_dfs_correctness() {
.fails_with_code(1)
.stderr_contains("tsort: -: input contains a loop:\ntsort: B\ntsort: C");
}

const TSORT_LOOP_STDERR: &str = "tsort: f: input contains a loop:\ntsort: s\ntsort: t\n";
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";
const TSORT_ODD_ERROR: &str = "tsort: -: input contains an odd number of tokens\n";
const TSORT_EXTRA_OPERAND_ERROR: &str =
"tsort: extra operand 'g'\nTry 'tsort --help' for more information.\n";

#[test]
fn test_cycle_loop_from_file() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("f", "t b\nt s\ns t\n");

ucmd.arg("f")
.fails_with_code(1)
.stdout_is("s\nt\nb\n")
.stderr_is(TSORT_LOOP_STDERR);
}

#[test]
fn test_cycle_loop_with_extra_node_from_file() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("f", "t x\nt s\ns t\n");

ucmd.arg("f")
.fails_with_code(1)
.stdout_is("s\nt\nx\n")
.stderr_is(TSORT_LOOP_STDERR);
}

#[test]
fn test_cycle_loop_multiple_loops_from_file() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("f", "a a\na b\na c\nc a\nb a\n");

ucmd.arg("f")
.fails_with_code(1)
.stdout_is("a\nc\nb\n")
.stderr_is(TSORT_LOOP_STDERR_AC);
}

#[test]
fn test_posix_graph_examples() {
new_ucmd!()
.pipe_in("a b c c d e\ng g\nf g e f\nh h\n")
.succeeds()
.stdout_only("a\nc\nd\nh\nb\ne\nf\ng\n");

new_ucmd!()
.pipe_in("b a\nd c\nz h x h r h\n")
.succeeds()
.stdout_only("b\nd\nr\nx\nz\na\nc\nh\n");
}

#[test]
fn test_linear_tree_graphs() {
new_ucmd!()
.pipe_in("a b b c c d d e e f f g\n")
.succeeds()
.stdout_only("a\nb\nc\nd\ne\nf\ng\n");

new_ucmd!()
.pipe_in("a b b c c d d e e f f g\nc x x y y z\n")
.succeeds()
.stdout_only("a\nb\nc\nx\nd\ny\ne\nz\nf\ng\n");

new_ucmd!()
.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")
.succeeds()
.stdout_only("a\nb\nc\nx\nd\ny\ne\nz\nf\nr\ng\ns\nt\n");
}

#[test]
fn test_odd_number_of_tokens() {
new_ucmd!()
.pipe_in("a\n")
.fails_with_code(1)
.stdout_is("")
.stderr_is(TSORT_ODD_ERROR);
}

#[test]
fn test_only_one_input_file() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("f", "");
at.write("g", "");

ucmd.arg("f")
.arg("g")
.fails_with_code(1)
.stdout_is("")
.stderr_is(TSORT_EXTRA_OPERAND_ERROR);
}
20 changes: 10 additions & 10 deletions tests/fixtures/tsort/call_graph.expected
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
main
parse_options
tail_file
tail_forever
tail
tail_file
parse_options
recheck
tail
write_header
tail_lines
tail_bytes
pretty_name
start_lines
file_lines
pipe_lines
xlseek
start_bytes
tail_bytes
tail_lines
pipe_bytes
start_bytes
xlseek
pipe_lines
file_lines
start_lines
dump_remainder
1 change: 0 additions & 1 deletion util/gnu-patches/series
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ tests_env_env-S.pl.patch
tests_invalid_opt.patch
tests_ls_no_cap.patch
tests_sort_merge.pl.patch
tests_tsort.patch
tests_du_move_dir_while_traversing.patch
test_mkdir_restorecon.patch
error_msg_uniq.diff
Expand Down
17 changes: 0 additions & 17 deletions util/gnu-patches/tests_tsort.patch

This file was deleted.

Loading