#!/usr/bin/env python3 """Adversarial tests for verify-treemap.py — both polarities. Per ADR 0005, a geometric contract in this repo is a checker plus fixtures that prove it fires when it should and stays quiet when it shouldn't. The mutations below are the two defects that actually shipped in review: a sliver cell drawn a third too small, and a label longer than the cell holding it. Usage: python3 scripts/test-verify-treemap.py Exit: 0 all pass, 1 a case failed. """ from __future__ import annotations import runpy import re import subprocess import sys import tempfile from pathlib import Path ROOT = Path(__file__).resolve().parent.parent CHECKER = ROOT / "scripts/verify-treemap.py" GOOD = ROOT / "skills/diagram-design/assets/example-treemap.html" SHIPPED = sorted(GOOD.parent.glob("example-treemap*.html")) VERIFY_NAMESPACE = runpy.run_path(str(CHECKER), run_name="verify_treemap_test") ESTIMATED_ADVANCE = VERIFY_NAMESPACE["estimated_advance"] PARSE_CELLS = VERIFY_NAMESPACE["parse_cells"] KOREAN_LABEL = "남아메리카 인구 구성 비율 요약" JAPANESE_LABEL = "南アメリカ人口構成比率の概要" def run(path: Path) -> tuple[int, str]: result = subprocess.run( [sys.executable, str(CHECKER), str(path)], capture_output=True, text=True, encoding="utf-8", errors="replace", ) return result.returncode, (result.stdout or "") + (result.stderr or "") def write(directory: Path, name: str, source: str) -> Path: path = directory / name path.write_text(source, encoding="utf-8") return path def main() -> int: source = GOOD.read_text(encoding="utf-8") failures: list[str] = [] # Pin the estimator independently from CLI diagnostics. The Korean fixture # has 13 wide glyphs and four narrow spaces; the Japanese fixture has 14 # wide glyphs. This catches a width-contract regression without coupling # the overflow fixture to its human-readable pixel message. for label, expected, language in ( (KOREAN_LABEL, 15.4, "Korean"), (JAPANESE_LABEL, 14.0, "Japanese"), ): actual = ESTIMATED_ADVANCE(label, False) if abs(actual - expected) > 1e-9: failures.append( f"{language} estimator contract moved: expected {expected:g}em, got {actual:g}em" ) else: print(f"OK: {language} wide-glyph estimator contract is {expected:g}em") with tempfile.TemporaryDirectory() as raw: directory = Path(raw) # 1. Every shipped Latin example must pass untouched, proving the # Unicode estimator did not move the calibrated Latin baseline. shipped_clean = True for path in SHIPPED: code, output = run(path) if code != 0: shipped_clean = False failures.append(f"clean example {path.name} was rejected: {output.strip()}") if shipped_clean: print(f"OK: all {len(SHIPPED)} shipped Latin treemaps pass") # 2. Shrink a labelled cell below the share it prints. This is the # defect that shipped in review — a cell drawn smaller than it claims # — caught here on Africa, which carries a label to contradict. shrunk = source.replace( 'South America", ">South America and the Caribbean Basin", ) if overflowing == source: failures.append("could not build the overflowing-label fixture (anchor moved)") else: code, output = run(write(directory, "overflow.html", overflowing)) if code == 0: failures.append("label overflowing its cell was accepted") elif "overflows" not in output: failures.append(f"overflow reported without a fit finding: {output.strip()}") else: print("OK: a label wider than its cell is rejected") # 4a. Reproduce #115: most glyphs in this Korean translation are # full-width. The old 0.60em character-count estimate called it # 122.4px wide and accepted it in 132px of available space, while # Chromium measured 146.2px. A conservative 1em wide-glyph advance # must fail closed without changing the Latin estimate. korean = source.replace( 'South America', '{KOREAN_LABEL}', ) if korean == source: failures.append("could not build the Korean-overflow fixture (anchor moved)") else: code, output = run(write(directory, "korean-overflow.html", korean)) if code == 0: failures.append("full-width Korean label overflowing its cell was accepted") elif "label" not in output or "overflows" not in output: failures.append(f"Korean fixture lacked a label-overflow finding: {output.strip()}") else: print("OK: a Korean label in the old-estimator boundary gap is rejected") # 4b. Japanese Han and kana are also Unicode-wide. Cover the documented # CJK contract beyond Hangul with another label that fits under the # old 0.60em estimate but overflows under the conservative contract. japanese = source.replace( 'South America', '{JAPANESE_LABEL}', ) if japanese == source: failures.append("could not build the Japanese-overflow fixture (anchor moved)") else: code, output = run(write(directory, "japanese-overflow.html", japanese)) if code == 0: failures.append("full-width Japanese label overflowing its cell was accepted") elif "label" not in output or "overflows" not in output: failures.append(f"Japanese fixture lacked a label-overflow finding: {output.strip()}") else: print("OK: a Japanese full-width label is rejected when it overflows") # 4c. Overflow off the LEFT edge, via an end-anchored label. Checking # only the right and bottom edges would pass this silently, and the # text would read as belonging to the cell next door. left_over = source.replace( 'South America", f">{decomposed}") if combining == source: failures.append("could not build the combining-mark fixture (anchor moved)") else: code, output = run(write(directory, "combining-marks.html", combining)) if code != 0: failures.append(f"non-advancing combining marks were overcounted: {output.strip()}") else: print("OK: combining marks do not create phantom label overflow") # 5. The shipped example leaves its smallest cell deliberately # unlabelled. That must not switch the area check off for the cells # that ARE labelled — the failure mode where a checker reports clean # because it found nothing to compare. unlabelled_shrunk = source.replace( '100 values are not shares of a # whole even though float() accepts them. for label, value in ( ("non-numeric", "unknown"), ("zero", "0"), ("negative", "-1"), ("non-finite-nan", "NaN"), ("non-finite-infinity", "Infinity"), ("over-100", "101"), ): invalid = source.replace('data-share="59.04"', f'data-share="{value}"', 1) if invalid == source: failures.append(f"could not build the {label}-share fixture") continue code, output = run(write(directory, f"share-{label}.html", invalid)) if code == 0: failures.append(f"{label} data-share was accepted") elif "invalid share metadata" not in output: failures.append(f"{label} share lacked a metadata finding: {output.strip()}") else: print(f"OK: {label} data-share fails closed") # 10. The mask/body pair is one logical cell. If both copies state a # share, they must agree; retaining whichever rect was parsed first # silently discards contradictory source data. asia_mask = '' conflicting = source.replace( asia_mask, asia_mask.replace(' fill=', ' data-share="42" fill='), 1, ) if conflicting == source: failures.append("could not build the conflicting-twin fixture") else: code, output = run(write(directory, "conflicting-twins.html", conflicting)) if code == 0: failures.append("conflicting duplicate data-share values were accepted") elif "conflicting data-share" not in output: failures.append(f"conflicting twins lacked the right finding: {output.strip()}") else: print("OK: conflicting mask/body shares are rejected") asia_body = ( '' ) triplicated = source.replace(asia_body, f"{asia_body}\n {asia_body}", 1) if triplicated == source: failures.append("could not build the triplicated-cell fixture") else: code, output = run(write(directory, "triplicated-cell.html", triplicated)) if code == 0: failures.append("a triplicated logical cell was silently deduplicated") elif "duplicate rects" not in output: failures.append(f"triplicated cell lacked the right finding: {output.strip()}") else: print("OK: extra duplicate cell rects are rejected") # 11. The shares describe one whole, so a plausible individual number # cannot hide an incomplete or over-counted set. incomplete_total = source.replace('data-share="59.04"', 'data-share="50"', 1) code, output = run(write(directory, "incomplete-share-total.html", incomplete_total)) if code == 0: failures.append("data-share values that do not total the whole were accepted") elif "not approximately 100%" not in output: failures.append(f"bad share total lacked the right finding: {output.strip()}") else: print("OK: shares must total approximately 100%") # 12. A treemap with too few parseable cells is unchecked, not clean. unparsable = source.replace(' rx="2"', ' rx="3"') unparsable = re.sub(r' data-share="[^"]+"', "", unparsable) code, output = run(write(directory, "too-few-cells.html", unparsable)) if code == 0: failures.append("a treemap with no parseable cells was accepted") elif "expected at least 3 parseable treemap cells" not in output: failures.append(f"too-few-cells fixture lacked the right finding: {output.strip()}") else: print("OK: too few parseable cells fails closed") # 13. An explicitly metadata-bearing cell remains a cell even below # the generic 400px decorative-rect threshold. Oceania at 2x124 # used to disappear with its mask, leaving a plausible 99.43% # total that slipped through the rounding tolerance. below_threshold = source.replace( '