|
12 | 12 | Layering is the usual reason for a copy but not the only one, so this |
13 | 13 | module's scope is the PROMISE rather than that one pair of packages: |
14 | 14 | the comma-set pin below reads _pipeline._state instead of config, and |
15 | | -the last test reaches outside the package altogether, to a TOML file |
16 | | -that could not import a Python constant if it wanted to. |
| 15 | +the last three tests reach outside the package altogether -- two to a |
| 16 | +TOML file that could not import a Python constant if it wanted to, |
| 17 | +one to a generated corpus whose generator can, and must stay run. |
17 | 18 | """ |
| 19 | +import importlib.util |
| 20 | +import json |
18 | 21 | import re |
19 | 22 | import tomllib |
20 | 23 | from pathlib import Path |
@@ -232,3 +235,64 @@ def test_differential_cjk_rule_matches_the_script_ranges() -> None: |
232 | 235 | assert declared == expected, ( |
233 | 236 | f"{toml_path.name}'s CJK name_regex declares {sorted(declared)}; " |
234 | 237 | f"_SCRIPT_RANGES' BMP spans are {sorted(expected)}") |
| 238 | + |
| 239 | + |
| 240 | +def test_differential_compound_rule_matches_the_script_ranges() -> None: |
| 241 | + """The fix(cjk-delimited-nickname) rule (#295) carries the SECOND |
| 242 | + hand copy of the script spans in the toml: its require-a-classified- |
| 243 | + codepoint lookahead exists so the delimiter set alone cannot claim a |
| 244 | + Latin name's first/last regression ('John 「Jack」 Kennedy' -- the |
| 245 | + corner brackets sit outside every classified span). Pin that copy to |
| 246 | + the table exactly as the canonical CJK rule's is, and pin the |
| 247 | + delimiter set itself, so widening either (to ASCII quotes, say) is |
| 248 | + an explicit decision here rather than a silent absorption change. |
| 249 | +
|
| 250 | + Selection note for future rule authors: the canonical-CJK-rule pin |
| 251 | + above selects by the literal '#271'/'#272' substrings and asserts |
| 252 | + uniqueness -- this rule's slug avoids them on purpose, and any new |
| 253 | + rule's must too. |
| 254 | + """ |
| 255 | + toml_path = (Path(__file__).parents[2] / "tools" / "differential" |
| 256 | + / "expected_changes.toml") |
| 257 | + rules = tomllib.loads(toml_path.read_text())["change"] |
| 258 | + matched = [r for r in rules if "cjk-delimited-nickname" in r["issue"]] |
| 259 | + assert len(matched) == 1 |
| 260 | + regex = matched[0]["name_regex"] |
| 261 | + assert "[「」『』・・]" in regex, ( |
| 262 | + "the compound rule's delimiter set changed; decide deliberately") |
| 263 | + declared = { |
| 264 | + (int(lo, 16), int(hi, 16)) |
| 265 | + for lo, hi in re.findall(r"\\u([0-9A-Fa-f]{4})-\\u([0-9A-Fa-f]{4})", |
| 266 | + regex)} |
| 267 | + expected = {span |
| 268 | + for spans in _policy._SCRIPT_RANGES.values() |
| 269 | + for span in spans |
| 270 | + if span[1] <= 0xFFFF} | {(0xFF65, 0xFF65)} |
| 271 | + assert declared == expected, ( |
| 272 | + f"compound rule's codepoint lookahead declares {sorted(declared)}; " |
| 273 | + f"_SCRIPT_RANGES' BMP spans are {sorted(expected)}") |
| 274 | + |
| 275 | + |
| 276 | +def test_cjk_corpus_matches_the_case_table() -> None: |
| 277 | + """corpus_cjk.jsonl is GENERATED, not curated (#295): every |
| 278 | + distinct case-table text bearing a codepoint the script table |
| 279 | + classifies, sorted -- see build_cjk_corpus.py for why the other |
| 280 | + two corpora cannot carry these names. The checked-in file must |
| 281 | + equal what the generator would write, so a CJK case row added |
| 282 | + without regenerating fails HERE instead of silently narrowing |
| 283 | + the differential gate back toward the blind spot #295 closed. |
| 284 | + Same promise as the toml pin above, aimed at a generated artifact |
| 285 | + instead of a hand copy. |
| 286 | + """ |
| 287 | + tools = Path(__file__).parents[2] / "tools" / "differential" |
| 288 | + spec = importlib.util.spec_from_file_location( |
| 289 | + "build_cjk_corpus", tools / "build_cjk_corpus.py") |
| 290 | + assert spec is not None and spec.loader is not None |
| 291 | + module = importlib.util.module_from_spec(spec) |
| 292 | + spec.loader.exec_module(module) |
| 293 | + checked_in = [json.loads(line) for line in |
| 294 | + (tools / "corpus_cjk.jsonl") |
| 295 | + .read_text(encoding="utf-8").splitlines()] |
| 296 | + assert checked_in == module.selected_names(), ( |
| 297 | + "corpus_cjk.jsonl is stale: regenerate with " |
| 298 | + "`uv run python tools/differential/build_cjk_corpus.py`") |
0 commit comments