Skip to content

Commit 6c394f9

Browse files
test: generic-delta fuzz (decoder-never-crashes + string round-trip)
1 parent 843d693 commit 6c394f9

2 files changed

Lines changed: 61 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- Unit suite mirroring `gcf-go`: self-proving round-trip (diff -> encode -> apply -> recomputed root), determinism / row-order invariance, no-type-collision canonicalization, every invariant/error path, full-payload wire round-trip, the complete server -> wire -> consumer end-to-end loop, and malformed-wire-fails-closed.
1919
- Conformance runner support for `generic-pack-root`, `generic-delta`, `generic-delta-verify`, `generic-delta-decode` (12 shared fixtures); verified to produce identical pack roots and delta wire to `gcf-go`.
2020
- Session helper suite (`test_generic_delta_session.py`) mirroring `gcf-go`: FixedN cadence pattern, size-guard triggering, schema-change forced full, FixedN(15)-over-30-turns count, and the load-bearing consumer-stays-in-sync check under both policies. Conformance runner support for `generic-delta-session` (3 shared fixtures: fixed-N, size-guard, schema-change).
21+
- Generic-delta fuzz (`test_generic_delta_fuzz.py`), mirroring `gcf-go`: the decoder never crashes on arbitrary/mutated input, and arbitrary UTF-8 string cells (including multi-byte and control characters) survive the full-wire round-trip with the pack root preserved.
2122

2223
## v2.2.2 (2026-07-10)
2324

tests/test_generic_delta_fuzz.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""Fuzz/property tests for generic-profile delta (mirrors gcf-go FuzzGeneric*).
2+
3+
Two properties:
4+
A. decode_generic_delta / decode_generic_full never crash on arbitrary or
5+
mutated input (they fail closed with a controlled error, or return).
6+
B. arbitrary string cell values survive the full-wire round-trip
7+
(quoting/escaping) with the pack root preserved.
8+
"""
9+
import random
10+
11+
from gcf.generic_delta import (
12+
GenericSet,
13+
decode_generic_delta,
14+
decode_generic_full,
15+
encode_generic_full,
16+
generic_pack_root,
17+
)
18+
19+
# structural + delimiter + unicode chars, the ones that stress quoting/escaping
20+
ALPHABET = list("abcXYZ0129 .,-~^@#=|\t\n\r\"\\/éñ中🦞")
21+
22+
23+
def _rand_str(rng, maxlen=20):
24+
return "".join(rng.choice(ALPHABET) for _ in range(rng.randint(0, maxlen)))
25+
26+
27+
def test_fuzz_string_cell_roundtrip():
28+
rng = random.Random(1234)
29+
for _ in range(20000):
30+
a, b = _rand_str(rng), _rand_str(rng)
31+
s = GenericSet(key="id", name="t", fields=["id", "a", "b"], rows=[
32+
{"id": 1, "a": a, "b": b},
33+
{"id": 2, "a": b, "b": a},
34+
])
35+
got, _ = decode_generic_full(encode_generic_full(s, ""))
36+
assert generic_pack_root(got) == generic_pack_root(s), repr((a, b))
37+
38+
39+
def test_fuzz_decode_never_crashes():
40+
rng = random.Random(99)
41+
seeds = [
42+
"GCF profile=generic delta=true base_root=a new_root=b key=id\n## added [1]{@id,x}\n1|2\n",
43+
"GCF profile=generic pack_root=r key=id\n## t [2]{@id,x}\n1|2\n3|4\n",
44+
"## removed [1]{@id}\n99\n",
45+
"",
46+
]
47+
for _ in range(20000):
48+
if rng.random() < 0.5:
49+
data = _rand_str(rng, 80)
50+
else:
51+
chars = list(rng.choice(seeds))
52+
for _ in range(rng.randint(0, 5)):
53+
if chars:
54+
chars[rng.randrange(len(chars))] = rng.choice(ALPHABET)
55+
data = "".join(chars)
56+
for fn in (decode_generic_delta, decode_generic_full):
57+
try:
58+
fn(data) # controlled error is fine; must terminate, must not crash
59+
except Exception:
60+
pass

0 commit comments

Comments
 (0)