Files
strans/tests/mkemoji_test.py
Hojun-Cho 5abd885e11 data(symbol): a prefix owns its slots only through its own row
map/symbol.src:12 says "Bare 1-9 choose from a prefix search; keep digit
aliases in matching slots", map/README:55 repeats it, and both
tests/engine_test.c:1001 and tests/mkemoji_test.py were written to it.
None of it happened.  dictprefix ends in below(), which returns a node's
own words first and then its children in rune order (dict.c:29-39), and
under `^` the punctuation sorts ahead of the digits:

	^ gave  ⁽ ⁾ ⁺ ⁻ ⁰ ¹ ² ³ ⁴ ⁵ ...
	_ gave  ₍ ₎ ₊ ₋ ₀ ₁ ₂ ₃ ₄ ₅ ...
	< gave  ← ♥ 🫰 🫶 ≤ ≠

so the file's own aliases picked the wrong character every time:

	          before   after
	^ then 1  ⁽        ¹
	^ then 2  ⁾        ²
	_ then 1  ₍        ₁
	< then 3  🫰        ♥

mkemoji has no bare `^` row to emit, because no source row claims `^` as
an alias -- the prefix exists in the trie only as the parent of `^1`..`^9`
and `^(`..`^n`, and a parent has no words of its own.  Giving the nine
superscripts `^` as a second alias, the nine subscripts `_`, and ←≤♥≠ `<`,
makes build() group them in source order and emit three rows:

	<	← ≤ ♥ ≠
	^	¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹
	_	₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉

which is what engine_test.c:1001's fixture has said all along, and the
first thing below() now returns.  Nothing is lost: `^0`, `^(`, `^i` and
their kind still answer their own key, `0` is not a selection key so it
still extends the query, and addkouho drops the duplicate when a child
repeats what the parent already offered.  Three rows on 18953.

tests/mkemoji_test.py was asking the wrong question.  It checked that the
first nine `^` keys in the file are `^1`..`^9` -- true before and after,
and decided nothing, because the file's order is not the trie's.  It now
checks the row the engine actually reads, and fails without this change
with a bare KeyError on `^`.

What this does not cover: `+1` and `-1`.  They are not slot aliases, they
are words -- plus one, minus one -- and  and  are the right first
answers to `+` and `-`.  👍 is slot 3 of `+` and reachable there.
2026-08-18 09:26:46 +09:00

92 lines
2.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
MKEMOJI = ROOT / "map" / "mkemoji"
TIMEOUT = 10
def generate(*sources):
args = [sys.executable, "-B", str(MKEMOJI)] + [str(s) for s in sources]
return subprocess.run(
args, capture_output=True, text=True, check=False, timeout=TIMEOUT
)
def table(output):
return dict(line.split("\t", 1) for line in output.splitlines())
class MkemojiTest(unittest.TestCase):
def source(self, text):
tmp = tempfile.TemporaryDirectory()
path = Path(tmp.name) / "emoji.src"
path.write_text(text, encoding="utf-8")
self.addCleanup(tmp.cleanup)
return path
def test_production_digit_slots(self):
result = generate()
self.assertEqual(result.returncode, 0, result.stderr)
data = table(result.stdout)
# The engine walks a prefix's children in rune order, so only the
# prefix's own row decides which candidate a digit picks.
self.assertEqual(data["^"].split(), list("¹²³⁴⁵⁶⁷⁸⁹"))
self.assertEqual(data["_"].split(), list("₁₂₃₄₅₆₇₈₉"))
self.assertEqual(data["<"].split()[2], "")
self.assertEqual(data["^1"], "¹")
self.assertEqual(data["_2"], "")
self.assertEqual(data["<3"].split()[0], "")
self.assertIn("😀", data["smile"].split())
self.assertIn("😀", data["웃음"].split())
def test_fold_normalize_and_source_order(self):
source = self.source(
"β\tALPHABET\talpha\n"
"α\talpha\n"
"\t\n"
"#\thash\n"
)
first = generate(source)
second = generate(source, source)
self.assertEqual(first.returncode, 0, first.stderr)
self.assertEqual(first.stdout, second.stdout)
data = table(first.stdout)
self.assertEqual(list(data), ["alphabet", "alpha", "é", "hash"])
self.assertEqual(data["alpha"].split(), ["β", "α"])
self.assertEqual(data["é"], "é")
self.assertEqual(data["hash"], "#")
def test_rejects_malformed_and_oversized_rows(self):
bad = [
"x\n",
"bad result\ta\n",
"x\t\n",
"x\t a\n",
"\0\ta\n",
"x\ta\0b\n",
"x\t;hidden\n",
"x" * 65 + "\ta\n",
"x\t" + "a" * 65 + "\n",
]
for text in bad:
with self.subTest(text=repr(text)):
result = generate(self.source(text))
self.assertEqual(result.returncode, 1)
def test_accepts_runtime_boundary(self):
source = self.source("x" * 64 + "\t" + "a" * 64 + "\n")
result = generate(source)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("a" * 64 + "\t" + "x" * 64, result.stdout)
if __name__ == "__main__":
unittest.main()