Skip to content

Commit

Permalink
Merge pull request #122 from BenWiederhake/dev-cksum
Browse files Browse the repository at this point in the history
Implement and exhaustively test cksum's weird implicit flags in 12 lines
  • Loading branch information
tertsdiepraam authored May 6, 2024
2 parents 2c1f1a3 + 0081703 commit 9ef4080
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 0 deletions.
3 changes: 3 additions & 0 deletions tests/coreutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ mod basename;
#[path = "coreutils/cat.rs"]
mod cat;

#[path = "coreutils/cksum.rs"]
mod cksum;

#[path = "coreutils/dd.rs"]
mod dd;

Expand Down
201 changes: 201 additions & 0 deletions tests/coreutils/cksum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
use uutils_args::{Arguments, Options};

#[derive(Debug, Clone, Arguments)]
enum Arg {
#[arg("-b", "--binary")]
Binary,

#[arg("-t", "--text")]
Text,

#[arg("--tag")]
Tag,

#[arg("--untagged")]
Untagged,
}

#[derive(Default, Debug, PartialEq)]
enum Tristate {
True,
#[default]
Unset,
False,
}

#[derive(Default, Debug)]
struct Settings {
binary: Tristate,
tag: Tristate,
}

impl Options<Arg> for Settings {
fn apply(&mut self, arg: Arg) {
match arg {
Arg::Binary => self.binary = Tristate::True,
Arg::Text => self.binary = Tristate::False,
Arg::Tag => {
// https://github.com/uutils/coreutils/issues/6364
self.binary = Tristate::Unset;
self.tag = Tristate::True;
}
Arg::Untagged => {
// https://github.com/uutils/coreutils/issues/6364
if self.tag == Tristate::True {
self.binary = Tristate::Unset;
}
self.tag = Tristate::False;
}
}
}
}

#[derive(Debug, PartialEq)]
enum ResultingFormat {
UntaggedText,
UntaggedBinary,
Tagged,
ErrorInstead,
}

impl Settings {
fn format(&self) -> ResultingFormat {
// Interpret "Unset" as "tagged":
if self.tag != Tristate::False {
// -> Tagged.
// Error only if the user explicitly requests the text format:
if self.binary == Tristate::False {
ResultingFormat::ErrorInstead
} else {
ResultingFormat::Tagged
}
} else {
// -> Untagged.
// Binary only if the user explicitly requests it:
if self.binary == Tristate::True {
ResultingFormat::UntaggedBinary
} else {
ResultingFormat::UntaggedText
}
}
}
}

// Convenience function for testing
#[cfg(test)]
fn assert_format(args: &[&str], expected: ResultingFormat) {
let mut full_argv = vec!["bin_name"];
full_argv.extend(args);
let result = Settings::default().parse(full_argv).unwrap();
assert_eq!(
(result.0.format(), result.1.as_slice()),
(expected, [].as_slice()),
"{:?}",
args
);
}

// These tests basically force the reader to make the same conclusions as
// https://github.com/uutils/coreutils/issues/6364
// Quotes from the issue are marked with a leading ">".

#[test]
fn binary_text_toggle_in_tagged() {
// > Observe that -b/-t seems to be doing precisely what we would hope for: toggle between binary/text mode:
// -b/-t/--tagged switch between tagged/error behavior
assert_format(&[], ResultingFormat::Tagged);
assert_format(&["-t"], ResultingFormat::ErrorInstead);
assert_format(&["-t", "-b"], ResultingFormat::Tagged);
assert_format(&["-t", "--tag"], ResultingFormat::Tagged);
}

#[test]
fn binary_text_toggle_in_untagged() {
// Once we're in untagged format, -b/-t switch between binary/text behavior
assert_format(&["--untagged"], ResultingFormat::UntaggedText);
assert_format(&["--untagged", "-t"], ResultingFormat::UntaggedText);
assert_format(&["--untagged", "-b"], ResultingFormat::UntaggedBinary);
assert_format(&["--untagged", "-t", "-b"], ResultingFormat::UntaggedBinary);
assert_format(&["--untagged", "-b", "-t"], ResultingFormat::UntaggedText);
}

// > Observe that --tag/--untagged seems to be the flags that have the weird behavior attached to
// > them. In particular, the T state seems to be more that one actual state, probably
// > differentiated along the "text-binary-axis".

#[test]
fn nondeterministic_edges() {
// Same behavior:
assert_format(&[], ResultingFormat::Tagged);
assert_format(&["-b"], ResultingFormat::Tagged);
// But must have different internal state:
assert_format(&["--untagged"], ResultingFormat::UntaggedText);
assert_format(&["-b", "--untagged"], ResultingFormat::UntaggedBinary);
}

#[test]
fn selfloops() {
// "T"
assert_format(&[], ResultingFormat::Tagged);
assert_format(&["-b"], ResultingFormat::Tagged);
assert_format(&["--tag"], ResultingFormat::Tagged);
assert_format(&["-b", "--tag"], ResultingFormat::Tagged);
// "E"
assert_format(&["-t"], ResultingFormat::ErrorInstead);
assert_format(&["-t", "-t"], ResultingFormat::ErrorInstead);
// "A"
assert_format(&["-b", "--untagged"], ResultingFormat::UntaggedBinary);
assert_format(&["-b", "--untagged", "-b"], ResultingFormat::UntaggedBinary);
assert_format(
&["-b", "--untagged", "--untagged"],
ResultingFormat::UntaggedBinary,
);
// "S"
assert_format(&["--untagged"], ResultingFormat::UntaggedText);
assert_format(&["--untagged", "-t"], ResultingFormat::UntaggedText);
assert_format(&["--untagged", "--untagged"], ResultingFormat::UntaggedText);
}

#[test]
fn other_diagonals() {
// From "A" and "S" ...
assert_format(&["-b", "--untagged"], ResultingFormat::UntaggedBinary);
assert_format(&["--untagged"], ResultingFormat::UntaggedText);
// ... to "T":
assert_format(&["-b", "--untagged", "--tag"], ResultingFormat::Tagged);
assert_format(&["--untagged", "--tag"], ResultingFormat::Tagged);
// From "E" to "S":
assert_format(&["-t"], ResultingFormat::ErrorInstead);
assert_format(&["-t", "--untagged"], ResultingFormat::UntaggedText);
}

#[test]
fn suffix_b_u_not_deterministic() {
// > Ending in bU does not determine the result:
assert_format(&["-b", "--untagged"], ResultingFormat::UntaggedBinary);
assert_format(
&["--tag", "-b", "--untagged"],
ResultingFormat::UntaggedText,
);
assert_format(
&["--untagged", "-b", "--untagged"],
ResultingFormat::UntaggedBinary,
);
assert_format(
&["-b", "--untagged", "-b", "--untagged"],
ResultingFormat::UntaggedBinary,
);
assert_format(
&["--tag", "--untagged", "-b", "--untagged"],
ResultingFormat::UntaggedBinary,
);
assert_format(
&["--untagged", "--tag", "-b", "--untagged"],
ResultingFormat::UntaggedText,
);
// > Therefore, U does not set the binary-ness to a constant, but rather depends on the tagged-ness.
}

// I *think* that this battery of tests fully specifies the full behavior.
// In any case, brute-forcing all of the 4^n combinations up to 5 arguments
// shows no counter-examples, so this implementation is definitely a good match.

0 comments on commit 9ef4080

Please sign in to comment.