mirror of
https://github.com/uutils/uutils-args.git
synced 2026-06-10 16:13:08 -07:00
Merge pull request #122 from BenWiederhake/dev-cksum
Implement and exhaustively test cksum's weird implicit flags in 12 lines
This commit is contained in:
@@ -13,6 +13,9 @@ mod basename;
|
||||
#[path = "coreutils/cat.rs"]
|
||||
mod cat;
|
||||
|
||||
#[path = "coreutils/cksum.rs"]
|
||||
mod cksum;
|
||||
|
||||
#[path = "coreutils/dd.rs"]
|
||||
mod dd;
|
||||
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user