mkemoji wrote a row for every prefix of every alias, so that a query matched as it was typed; the trie is a prefix index already, and with a real emoji list those rows would be four times the aliases themselves. Now emoji.dict has one row per alias, trienode() names a key's node, and dictlookup walks the entries at and below it, the key's own first, up to Maxkouho. Trie children are appended rather than pushed, so the walk keeps the file's order and a bare digit still picks the superscript or subscript it always did. The hand-written symbol rows move to symbol.src; emoji.src is left to the emoji. mkemoji reads both by default, or the files it is given.
93 lines
2.9 KiB
Python
93 lines
2.9 KiB
Python
#!/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)
|
||
keys = list(data)
|
||
self.assertEqual([k for k in keys if k[:1] == "^"][:9],
|
||
[f"^{d}" for d in range(1, 10)])
|
||
self.assertEqual([k for k in keys if k[:1] == "_"][:9],
|
||
[f"_{d}" for d in range(1, 10)])
|
||
self.assertEqual([k for k in keys if k[:1] == "<"][2], "<3")
|
||
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()
|