154 lines
5.8 KiB
Python
154 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
test_diff_sqlite_guards.py -- regression guards for the two blind spots this lane closed.
|
|
|
|
Wizard: null (lane: Codeforge -- hard coding and agentic reasoning).
|
|
|
|
Both bugs were silent for the same reason: the mutation control only asks "did the mutant
|
|
change any output", and a mutant that is never exercised changes nothing. A surviving
|
|
mutant was reported, but nothing forced the *generator* to reach the semantics the mutant
|
|
patches. These guards make that false.
|
|
|
|
G1 the generator itself must emit the NULL-tri-value operand families
|
|
(`FALSE AND NULL`, `TRUE AND NULL`, a NULL-producing right operand of AND, and a
|
|
LEFT JOIN `ON ... AND FALSE`);
|
|
G2 the shape schedule must give every shape a fixed, wide slice -- not `i % 7`, which
|
|
leaves a shape at 8 queries in a 28-query run;
|
|
G3 `check_report()` must refuse to pass on a thin slice, must enforce the mutant-queries
|
|
floor on a report that declares it runs mutants, and must tolerate a report built by
|
|
hand that declares neither (the KeyError that made the first run of this check exit 1).
|
|
|
|
Run: python3 test_diff_sqlite_guards.py (exit 0 = guards hold)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import random
|
|
import sys
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, HERE)
|
|
import diff_sqlite as D # noqa: E402
|
|
|
|
|
|
def g1_generator_emits_the_families():
|
|
"""Draw many predicates and assert each family appears, not merely that some did."""
|
|
rng = random.Random(7)
|
|
spec, rows = D.make_schema(rng)
|
|
gen = D.Gen(rng, spec, rows)
|
|
gen.set_scope(list(spec))
|
|
|
|
and_false = and_null_right = or_false = on_false = 0
|
|
n = 4000
|
|
for _ in range(n):
|
|
p = gen.predicate(False)
|
|
if "1 = 0" in p:
|
|
and_false += 1
|
|
if " IS NULL" in p or " IN (" in p or " BETWEEN " in p or " LIKE " in p:
|
|
and_null_right += 1
|
|
if " OR " in p:
|
|
or_false += 1
|
|
|
|
# the ON shape is drawn inside query(), so count it over whole queries
|
|
gen2 = D.Gen(rng, spec, rows)
|
|
for _ in range(n):
|
|
q = gen2.query("join")
|
|
if "ON" in q and "1 = 0" in q.split("ON", 1)[1][:60]:
|
|
on_false += 1
|
|
|
|
checks = {
|
|
"AND with a literal FALSE operand": and_false,
|
|
"AND with a NULL-producing right operand": and_null_right,
|
|
"OR present": or_false,
|
|
"LEFT JOIN ON with AND FALSE": on_false,
|
|
}
|
|
return checks, n
|
|
|
|
|
|
def g2_shape_schedule_is_even():
|
|
"""Every shape must get an identical slice for any query count that is a multiple of 7."""
|
|
rng = random.Random(11)
|
|
counts = dict.fromkeys(D.SHAPES, 0)
|
|
total = D._QUERIES_PER_SHAPE * len(D.SHAPES) * 5
|
|
for i in range(total):
|
|
shape = D.SHAPES[(i // D._QUERIES_PER_SHAPE) % len(D.SHAPES)]
|
|
counts[shape] += 1
|
|
rng.random()
|
|
return counts, total
|
|
|
|
|
|
def g3_check_report_refuses_thin_slice():
|
|
"""A report whose shape counts are thin must produce a failure string.
|
|
|
|
Also covers the mutant-queries floor: `check_mutant_queries()` was reachable only
|
|
through main(), so a thin mutant slice read as PASS. Both directions are asserted --
|
|
below the floor is refused, at or above the floor it is not.
|
|
"""
|
|
report = {
|
|
"queries": 28, "seed": 0, "shape_counts": dict.fromkeys(D.SHAPES, 4),
|
|
"rows_compared": 30, "nonempty": 20, "ordered": 20, "mismatches": [], "errors": [],
|
|
}
|
|
bad = D.check_report(report)
|
|
thin = [b for b in bad if "thin slice" in b]
|
|
|
|
floor = D.mutant_floor()
|
|
below = D.check_mutant_queries(floor - 1)
|
|
at_floor = D.check_mutant_queries(floor)
|
|
if not any("below the measured floor" in b for b in below):
|
|
bad.append("check_mutant_queries(%d) accepted a slice below the floor %d"
|
|
% (floor - 1, floor))
|
|
if at_floor:
|
|
bad.append("check_mutant_queries(%d) refused the floor width itself: %r"
|
|
% (floor, at_floor))
|
|
# and a full report that declares it runs mutants must be enforced, not ignored
|
|
carried = dict(report)
|
|
carried["shape_counts"] = dict.fromkeys(D.SHAPES, D.MIN_PER_SHAPE)
|
|
carried["mutant_queries"] = floor - 1
|
|
carried["skip_mutant_floor"] = False
|
|
if not any("below the measured floor" in b for b in D.check_report(carried)):
|
|
bad.append("check_report ignored the mutant_queries width it was handed")
|
|
# ...and a report that declares it runs mutants but names no width is refused, not waved through
|
|
nowidth = dict(carried)
|
|
del nowidth["mutant_queries"]
|
|
if not any("carries no mutant-queries width" in b for b in D.check_report(nowidth)):
|
|
bad.append("check_report waved through a mutant run that names no slice width")
|
|
return bad, thin
|
|
|
|
|
|
def main():
|
|
failures = []
|
|
|
|
checks, n = g1_generator_emits_the_families()
|
|
print("G1 generator emits the NULL-tri-value families (%d draws)" % n)
|
|
for name, count in checks.items():
|
|
state = "ok" if count else "MISSING"
|
|
print(" %-40s %6d %s" % (name, count, state))
|
|
if not count:
|
|
failures.append("G1: %s never generated" % name)
|
|
|
|
counts, total = g2_shape_schedule_is_even()
|
|
print("\nG2 shape schedule over %d queries: %s" % (total, counts))
|
|
if len(set(counts.values())) != 1:
|
|
failures.append("G2: shape slices are not even: %r" % counts)
|
|
|
|
bad, thin = g3_check_report_refuses_thin_slice()
|
|
print("\nG3 check_report() on a thin slice: %d failure(s), %d mention a thin slice"
|
|
% (len(bad), len(thin)))
|
|
for b in bad:
|
|
print(" " + b)
|
|
if not thin:
|
|
failures.append("G3: check_report accepted a thin slice")
|
|
|
|
print()
|
|
if failures:
|
|
for f in failures:
|
|
print("FAIL: " + f)
|
|
print("RESULT: FAIL")
|
|
return 1
|
|
print("RESULT: PASS")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main()) |