Add Hegel property tests for chomp and the upgrade prompt
Assisted-by: Claude Opus 5.5 via Claude Code
diff --git a/AGENTS.md b/AGENTS.md
index e956e4a..36cddae 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -41,6 +41,7 @@ frork-cli/
utils.rs # Shell helpers, path expansion, Lua bindings
tests/
cli.rs # Integration tests via assert_cmd
+ property.rs # Hegel property tests (chomp, upgrade prompt)
frork-lib/
Cargo.toml # Shared library crate (currently empty)
src/lib.rs
diff --git a/Cargo.lock b/Cargo.lock
index 219b98d..5294b59 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -155,30 +155,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
-name = "ciborium"
-version = "0.2.2"
+name = "chacha20"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
dependencies = [
- "ciborium-io",
- "ciborium-ll",
- "serde",
-]
-
-[[package]]
-name = "ciborium-io"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
-
-[[package]]
-name = "ciborium-ll"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
-dependencies = [
- "ciborium-io",
- "half",
+ "cfg-if",
+ "cpufeatures",
+ "rand_core",
]
[[package]]
@@ -236,6 +220,15 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+[[package]]
+name = "cpufeatures"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "crc32fast"
version = "1.5.0"
@@ -246,10 +239,23 @@ dependencies = [
]
[[package]]
-name = "crunchy"
-version = "0.2.4"
+name = "dashu-base"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+checksum = "993b95dc1b248e3f5747dcb017a41d6e75853a2e5ee4504f7d537c5b8dffdae4"
+
+[[package]]
+name = "dashu-int"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49c05a0d5cb0b39fcc87c46432fdac24b90dce239857c7f6b798be4ffc3c42c6"
+dependencies = [
+ "cfg-if",
+ "dashu-base",
+ "num-modular",
+ "rustversion",
+ "static_assertions",
+]
[[package]]
name = "difflib"
@@ -287,7 +293,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.61.2",
+ "windows-sys 0.60.2",
]
[[package]]
@@ -360,10 +366,22 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
- "r-efi",
+ "r-efi 5.3.0",
"wasip2",
]
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 6.0.0",
+ "rand_core",
+]
+
[[package]]
name = "gimli"
version = "0.32.3"
@@ -371,15 +389,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
-name = "half"
-version = "2.7.1"
+name = "hashbrown"
+version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
-dependencies = [
- "cfg-if",
- "crunchy",
- "zerocopy",
-]
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "heck"
@@ -389,23 +402,43 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hegeltest"
-version = "0.2.6"
+version = "0.32.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fa0b7c2d078af5a0d54c9fdbc4a5013f5aa408b88f014298c73e6d88369b133"
+checksum = "9210d5784dde569ca02bc14fa941617a695b065c974e5c56ae03c4fd7048c79f"
dependencies = [
- "ciborium",
"crc32fast",
+ "dashu-int",
+ "hegeltest-c",
"hegeltest-macros",
- "paste",
- "serde",
+ "miniz_oxide",
+ "parking_lot",
+ "rand",
+ "rustc-hash",
"tempfile",
]
+[[package]]
+name = "hegeltest-c"
+version = "0.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01ec51d829e2104efa326683fc0d7bf21baa46bee4915d1b200cbbcf2ec1ac2d"
+dependencies = [
+ "dashu-int",
+ "hashbrown",
+ "libm",
+ "miniz_oxide",
+ "once_cell",
+ "rand",
+ "rustc-hash",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "hegeltest-macros"
-version = "0.2.6"
+version = "0.32.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cf8e08fa4ce2a5d0ee0bd87318d49e7375eeba97fb3fa579bb4dea797ec4397"
+checksum = "7a79d57751ed94624b1fd65cf6c7f090cdc4ed48ebfffd74636b342c5885c07a"
dependencies = [
"proc-macro2",
"quote",
@@ -436,6 +469,12 @@ version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
@@ -585,9 +624,15 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
- "windows-sys 0.61.2",
+ "windows-sys 0.60.2",
]
+[[package]]
+name = "num-modular"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd8e500409e6cd603b03e477c26a6caecdc27ac58979a53e881c75eafc079f44"
+
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -656,12 +701,6 @@ dependencies = [
"windows-link",
]
-[[package]]
-name = "paste"
-version = "1.0.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
-
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@@ -728,6 +767,29 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rand"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.3",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
[[package]]
name = "redox_syscall"
version = "0.5.18"
@@ -788,7 +850,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.61.2",
+ "windows-sys 0.60.2",
]
[[package]]
@@ -864,6 +926,12 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
[[package]]
name = "strsim"
version = "0.11.1"
@@ -909,10 +977,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [
"fastrand",
- "getrandom",
+ "getrandom 0.3.4",
"once_cell",
"rustix",
- "windows-sys 0.61.2",
+ "windows-sys 0.60.2",
]
[[package]]
@@ -922,7 +990,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
dependencies = [
"rustix",
- "windows-sys 0.61.2",
+ "windows-sys 0.60.2",
]
[[package]]
@@ -1202,23 +1270,3 @@ name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
-
-[[package]]
-name = "zerocopy"
-version = "0.8.47"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
-dependencies = [
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.8.47"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
diff --git a/frork-cli/tests/property.rs b/frork-cli/tests/property.rs
new file mode 100644
index 0000000..5e4f4e8
--- /dev/null
+++ b/frork-cli/tests/property.rs
@@ -0,0 +1,67 @@
+// Property tests exercise invariants that hold across all inputs; unit tests
+// in the modules pin down known-good fixtures.
+#![cfg_attr(
+ test,
+ allow(
+ clippy::unwrap_used,
+ clippy::expect_used,
+ clippy::panic,
+ clippy::indexing_slicing,
+ clippy::arithmetic_side_effects
+ )
+)]
+
+use frork_cli::report;
+use frork_cli::utils::Utils;
+use hegel::TestCase;
+use hegel::generators::text;
+
+#[hegel::test]
+fn chomp_strips_all_trailing_newlines(tc: TestCase) {
+ let s = tc.draw(text());
+ let chomped = Utils::chomp(&s);
+ assert!(
+ !chomped.ends_with('\n') && !chomped.ends_with('\r'),
+ "chomp({s:?}) = {chomped:?}, which still ends in a newline"
+ );
+}
+
+#[hegel::test]
+fn chomp_is_idempotent(tc: TestCase) {
+ let s = tc.draw(text());
+ assert_eq!(
+ Utils::chomp(&Utils::chomp(&s)),
+ Utils::chomp(&s),
+ "chomping {s:?} twice differs from chomping once"
+ );
+}
+
+#[hegel::test]
+fn chomp_only_removes_from_the_end(tc: TestCase) {
+ let s = tc.draw(text());
+ assert!(
+ s.starts_with(&Utils::chomp(&s)),
+ "chomp({s:?}) is not a prefix of its input"
+ );
+}
+
+#[hegel::test]
+fn chomp_never_grows_its_input(tc: TestCase) {
+ let s = tc.draw(text());
+ assert!(
+ Utils::chomp(&s).len() <= s.len(),
+ "chomp({s:?}) grew its input"
+ );
+}
+
+#[hegel::test]
+fn wants_upgrade_accepts_only_y_or_yes(tc: TestCase) {
+ let s = tc.draw(text());
+ if report::wants_upgrade(&s) {
+ let normalized = s.trim().to_lowercase();
+ assert!(
+ normalized == "y" || normalized == "yes",
+ "wants_upgrade({s:?}) accepted something other than y/yes"
+ );
+ }
+}