Skip to content

Commit 9ef4080

Browse files
Merge pull request #122 from BenWiederhake/dev-cksum
Implement and exhaustively test cksum's weird implicit flags in 12 lines
2 parents 2c1f1a3 + 0081703 commit 9ef4080

File tree

2 files changed

+204
-0
lines changed

2 files changed

+204
-0
lines changed

tests/coreutils.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ mod basename;
1313
#[path = "coreutils/cat.rs"]
1414
mod cat;
1515

16+
#[path = "coreutils/cksum.rs"]
17+
mod cksum;
18+
1619
#[path = "coreutils/dd.rs"]
1720
mod dd;
1821

tests/coreutils/cksum.rs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
use uutils_args::{Arguments, Options};
2+
3+
#[derive(Debug, Clone, Arguments)]
4+
enum Arg {
5+
#[arg("-b", "--binary")]
6+
Binary,
7+
8+
#[arg("-t", "--text")]
9+
Text,
10+
11+
#[arg("--tag")]
12+
Tag,
13+
14+
#[arg("--untagged")]
15+
Untagged,
16+
}
17+
18+
#[derive(Default, Debug, PartialEq)]
19+
enum Tristate {
20+
True,
21+
#[default]
22+
Unset,
23+
False,
24+
}
25+
26+
#[derive(Default, Debug)]
27+
struct Settings {
28+
binary: Tristate,
29+
tag: Tristate,
30+
}
31+
32+
impl Options<Arg> for Settings {
33+
fn apply(&mut self, arg: Arg) {
34+
match arg {
35+
Arg::Binary => self.binary = Tristate::True,
36+
Arg::Text => self.binary = Tristate::False,
37+
Arg::Tag => {
38+
// https://github.com/uutils/coreutils/issues/6364
39+
self.binary = Tristate::Unset;
40+
self.tag = Tristate::True;
41+
}
42+
Arg::Untagged => {
43+
// https://github.com/uutils/coreutils/issues/6364
44+
if self.tag == Tristate::True {
45+
self.binary = Tristate::Unset;
46+
}
47+
self.tag = Tristate::False;
48+
}
49+
}
50+
}
51+
}
52+
53+
#[derive(Debug, PartialEq)]
54+
enum ResultingFormat {
55+
UntaggedText,
56+
UntaggedBinary,
57+
Tagged,
58+
ErrorInstead,
59+
}
60+
61+
impl Settings {
62+
fn format(&self) -> ResultingFormat {
63+
// Interpret "Unset" as "tagged":
64+
if self.tag != Tristate::False {
65+
// -> Tagged.
66+
// Error only if the user explicitly requests the text format:
67+
if self.binary == Tristate::False {
68+
ResultingFormat::ErrorInstead
69+
} else {
70+
ResultingFormat::Tagged
71+
}
72+
} else {
73+
// -> Untagged.
74+
// Binary only if the user explicitly requests it:
75+
if self.binary == Tristate::True {
76+
ResultingFormat::UntaggedBinary
77+
} else {
78+
ResultingFormat::UntaggedText
79+
}
80+
}
81+
}
82+
}
83+
84+
// Convenience function for testing
85+
#[cfg(test)]
86+
fn assert_format(args: &[&str], expected: ResultingFormat) {
87+
let mut full_argv = vec!["bin_name"];
88+
full_argv.extend(args);
89+
let result = Settings::default().parse(full_argv).unwrap();
90+
assert_eq!(
91+
(result.0.format(), result.1.as_slice()),
92+
(expected, [].as_slice()),
93+
"{:?}",
94+
args
95+
);
96+
}
97+
98+
// These tests basically force the reader to make the same conclusions as
99+
// https://github.com/uutils/coreutils/issues/6364
100+
// Quotes from the issue are marked with a leading ">".
101+
102+
#[test]
103+
fn binary_text_toggle_in_tagged() {
104+
// > Observe that -b/-t seems to be doing precisely what we would hope for: toggle between binary/text mode:
105+
// -b/-t/--tagged switch between tagged/error behavior
106+
assert_format(&[], ResultingFormat::Tagged);
107+
assert_format(&["-t"], ResultingFormat::ErrorInstead);
108+
assert_format(&["-t", "-b"], ResultingFormat::Tagged);
109+
assert_format(&["-t", "--tag"], ResultingFormat::Tagged);
110+
}
111+
112+
#[test]
113+
fn binary_text_toggle_in_untagged() {
114+
// Once we're in untagged format, -b/-t switch between binary/text behavior
115+
assert_format(&["--untagged"], ResultingFormat::UntaggedText);
116+
assert_format(&["--untagged", "-t"], ResultingFormat::UntaggedText);
117+
assert_format(&["--untagged", "-b"], ResultingFormat::UntaggedBinary);
118+
assert_format(&["--untagged", "-t", "-b"], ResultingFormat::UntaggedBinary);
119+
assert_format(&["--untagged", "-b", "-t"], ResultingFormat::UntaggedText);
120+
}
121+
122+
// > Observe that --tag/--untagged seems to be the flags that have the weird behavior attached to
123+
// > them. In particular, the T state seems to be more that one actual state, probably
124+
// > differentiated along the "text-binary-axis".
125+
126+
#[test]
127+
fn nondeterministic_edges() {
128+
// Same behavior:
129+
assert_format(&[], ResultingFormat::Tagged);
130+
assert_format(&["-b"], ResultingFormat::Tagged);
131+
// But must have different internal state:
132+
assert_format(&["--untagged"], ResultingFormat::UntaggedText);
133+
assert_format(&["-b", "--untagged"], ResultingFormat::UntaggedBinary);
134+
}
135+
136+
#[test]
137+
fn selfloops() {
138+
// "T"
139+
assert_format(&[], ResultingFormat::Tagged);
140+
assert_format(&["-b"], ResultingFormat::Tagged);
141+
assert_format(&["--tag"], ResultingFormat::Tagged);
142+
assert_format(&["-b", "--tag"], ResultingFormat::Tagged);
143+
// "E"
144+
assert_format(&["-t"], ResultingFormat::ErrorInstead);
145+
assert_format(&["-t", "-t"], ResultingFormat::ErrorInstead);
146+
// "A"
147+
assert_format(&["-b", "--untagged"], ResultingFormat::UntaggedBinary);
148+
assert_format(&["-b", "--untagged", "-b"], ResultingFormat::UntaggedBinary);
149+
assert_format(
150+
&["-b", "--untagged", "--untagged"],
151+
ResultingFormat::UntaggedBinary,
152+
);
153+
// "S"
154+
assert_format(&["--untagged"], ResultingFormat::UntaggedText);
155+
assert_format(&["--untagged", "-t"], ResultingFormat::UntaggedText);
156+
assert_format(&["--untagged", "--untagged"], ResultingFormat::UntaggedText);
157+
}
158+
159+
#[test]
160+
fn other_diagonals() {
161+
// From "A" and "S" ...
162+
assert_format(&["-b", "--untagged"], ResultingFormat::UntaggedBinary);
163+
assert_format(&["--untagged"], ResultingFormat::UntaggedText);
164+
// ... to "T":
165+
assert_format(&["-b", "--untagged", "--tag"], ResultingFormat::Tagged);
166+
assert_format(&["--untagged", "--tag"], ResultingFormat::Tagged);
167+
// From "E" to "S":
168+
assert_format(&["-t"], ResultingFormat::ErrorInstead);
169+
assert_format(&["-t", "--untagged"], ResultingFormat::UntaggedText);
170+
}
171+
172+
#[test]
173+
fn suffix_b_u_not_deterministic() {
174+
// > Ending in bU does not determine the result:
175+
assert_format(&["-b", "--untagged"], ResultingFormat::UntaggedBinary);
176+
assert_format(
177+
&["--tag", "-b", "--untagged"],
178+
ResultingFormat::UntaggedText,
179+
);
180+
assert_format(
181+
&["--untagged", "-b", "--untagged"],
182+
ResultingFormat::UntaggedBinary,
183+
);
184+
assert_format(
185+
&["-b", "--untagged", "-b", "--untagged"],
186+
ResultingFormat::UntaggedBinary,
187+
);
188+
assert_format(
189+
&["--tag", "--untagged", "-b", "--untagged"],
190+
ResultingFormat::UntaggedBinary,
191+
);
192+
assert_format(
193+
&["--untagged", "--tag", "-b", "--untagged"],
194+
ResultingFormat::UntaggedText,
195+
);
196+
// > Therefore, U does not set the binary-ness to a constant, but rather depends on the tagged-ness.
197+
}
198+
199+
// I *think* that this battery of tests fully specifies the full behavior.
200+
// In any case, brute-forcing all of the 4^n combinations up to 5 arguments
201+
// shows no counter-examples, so this implementation is definitely a good match.

0 commit comments

Comments
 (0)