Files

152 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""Adversarial property test for the refactored chunker.
Targeted at the one invariant the docstring makes a strong claim about:
``reconstruct(chunk_text(t, m, o), o) == t.strip()`` for EVERY input,
including:
* repetitive sentences whose carried-overlap tail can coincidentally
equal a prefix of the next chunk's content (the genuine-vs-context
discriminator in ``reconstruct``);
* long repetitive-token sentences that hard-split, immediately followed
by a sentence-grouped chunk whose content begins with the same run
the overlap tail carries (genuine-overlap branch mis-taken as context);
* arbitrary inter-sentence whitespace (double space, tab, newline,
mixed runs) that the original bug collapsed.
If this exits 0, the lossless claim is backed by >= N randomised trials
across all three adversarial shapes. If it exits 1, it prints the first
counterexample with a full structural dump so the bug is reproducible.
"""
from __future__ import annotations
import random
import sys
sys.path.insert(0, "/root/.hermes/wizard-outputs/forge")
import chunker # noqa: E402
S = chunker._OVERLAP_SENTINEL
def shape_long_rep_then_sentence(seed: int) -> str:
"""Hard-split long repetitive sentence immediately followed by a
sentence chunk whose content begins with the *same* token run as the
long sentence's tail -- forces the genuine-overlap discriminator to be
exercised on a context-overlap site."""
rng = random.Random(seed)
tok = rng.choice(["word", "x", "abc", "node"])
# long sentence of repeated tokens (no real EOS until the end)
long_sent = " ".join([tok] * rng.randint(40, 220)) + "."
# next sentence repeats the tail token so the overlap tail (last oc chars
# of the long sentence) coincides with the next chunk's prefix.
next_sent = (tok + " ") * rng.randint(2, 8) + "tail."
# vary inter-sentence whitespace
sep = rng.choice([" ", " ", "\t", "\n", "\n\n", " \n ", " \t "])
return long_sent + sep + next_sent + (sep + "More " + tok + " words.\n\n") * 2
def shape_repetitive_sentences(seed: int) -> str:
"""Repeated sentence groups (the docstring's primary adversarial class)."""
rng = random.Random(seed)
groups = []
for _ in range(rng.randint(10, 60)):
g = rng.choice(
[
"Sentence one. ",
"Sentence two. ",
"Sentence three. ",
"Alpha beta. ",
"Gamma delta. ",
]
)
# occasionally insert weird interior whitespace
if rng.random() < 0.3:
g = g.replace(" ", rng.choice([" ", "\t", " \n "]))
groups.append(g)
return "".join(groups)
def shape_whitespace_variety(seed: int) -> str:
"""Lots of short sentences joined by weird whitespace runs."""
rng = random.Random(seed)
sents = ["Hello world", "How are you", "I am fine", "Test sentence"]
seps = [" ", " ", " ", "\t", "\n", "\n\n", " \n ", " \t "]
parts = []
for _ in range(rng.randint(5, 40)):
parts.append(rng.choice(sents))
parts.append(rng.choice(seps))
# trailing whitespace to exercise strip boundary
parts.append(rng.choice(seps))
return "".join(parts)
def shape_fuzz(seed: int) -> str:
rng = random.Random(seed)
words = ["alpha", "bravo", "charlie", "delta", "echo", "foxtrot",
"golf", "hotel", "india", "juliet"]
seps = [" ", " ", " ", "\t", "\n", "\n\n"]
parts = []
n = rng.randint(1, 8)
for _ in range(n):
nw = rng.randint(2, 12)
parts.append(" ".join(rng.choice(words) for _ in range(nw)))
parts.append(rng.choice([".", ".", ".", "?", "!", "..."]))
parts.append(rng.choice(seps))
return "".join(parts).strip() + rng.choice(["", "", " ", " \n "])
def check(text: str, mt: int, ot: int) -> bool:
if not text.strip():
return True
expect = text.strip()
cs = chunker.chunk_text(text, max_tokens=mt, overlap_tokens=ot)
rec = chunker.reconstruct(cs, ot)
return rec == expect
def main() -> int:
shapes = [shape_long_rep_then_sentence, shape_repetitive_sentences,
shape_whitespace_variety, shape_fuzz]
mts = [8, 10, 12, 16, 20, 24, 32, 40, 48, 64, 72, 100, 128]
ots = [0, 1, 2, 3, 4, 6, 8, 12, 16]
trials_per_shape = 600
total = 0
fails = 0
for si, shape in enumerate(shapes):
for i in range(trials_per_shape):
seed = si * 100000 + i
text = shape(seed)
mt = random.Random(seed).choice(mts)
ot = random.Random(seed + 1).choice(ots)
if ot >= mt:
ot = max(0, mt - 1)
total += 1
if not check(text, mt, ot):
fails += 1
print(f"FAIL shape={shape.__name__} seed={seed} mt={mt} ot={ot}")
print(f" text(:120)={text[:120]!r}... len={len(text)}")
cs = chunker.chunk_text(text, max_tokens=mt, overlap_tokens=ot)
rec = chunker.reconstruct(cs, ot)
exp = text.strip()
for j in range(min(len(rec), len(exp))):
if rec[j] != exp[j]:
print(f" FIRST DIFF at {j}")
print(f" exp: {exp[max(0,j-25):j+25]!r}")
print(f" rec: {rec[max(0,j-25):j+25]!r}")
break
else:
print(f" len exp={len(exp)} rec={len(rec)} "
f"(common prefix OK, lengths differ)")
if fails >= 3:
print("Stopping after 3 failures.")
return 1
print(f"trials={total} fails={fails}")
print("PASS: lossless round-trip across all adversarial shapes."
if fails == 0 else "FAIL: invariant violated.")
return 0 if fails == 0 else 1
if __name__ == "__main__":
sys.exit(main())