diff --git a/tests/coreutils.rs b/tests/coreutils.rs index 6ebfeac..de16e8d 100644 --- a/tests/coreutils.rs +++ b/tests/coreutils.rs @@ -13,6 +13,9 @@ mod basename; #[path = "coreutils/cat.rs"] mod cat; +#[path = "coreutils/cksum.rs"] +mod cksum; + #[path = "coreutils/dd.rs"] mod dd; diff --git a/tests/coreutils/cksum.rs b/tests/coreutils/cksum.rs new file mode 100644 index 0000000..71682fd --- /dev/null +++ b/tests/coreutils/cksum.rs @@ -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 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.