#!/usr/bin/env python3
"""Самопроверка моста без сети: python3 test_translate.py"""
import json
import re
from pathlib import Path
import translate as t
def test_marks_roundtrip():
src = 'She thinks this is bad & says “no”'
marked = t.to_marks(src)
assert "" not in marked and "⟦i⟧" in marked
assert "&" not in marked, "сущности должны раскрываться для переводчика"
body, ok = t.from_marks(marked)
assert ok
assert "this is bad" in body
assert "&" in body, "спецсимволы должны экранироваться обратно"
def test_inline_code_survives():
"""Имена функций в прозе не должны терять разметку по дороге к модели."""
marked = t.to_marks("call fetch() twice")
assert marked == "call ⟦code⟧fetch()⟦/code⟧ twice"
body, ok = t.from_marks(marked)
assert ok and "fetch()" in body
def test_broken_marks_drop_tags():
body, ok = t.from_marks("текст ⟦i⟧без закрытия")
assert not ok and "⟦" not in body and "" not in body
def test_language_detection():
assert t.detect_language("the quick brown fox " * 40)[0] == "en"
assert t.detect_language("быстрая бурая лиса " * 40)[0] == "ru"
assert t.detect_language("short")[0] == "unknown"
def test_split_and_rebuild(tmp: Path):
src = tmp / "book.html"
src.write_text("\n".join([
"",
"CHAPTER 1
",
"
He said hello loudly.
", '
Second chapter text.
", "", ]), encoding="utf-8") blocks = t.parse_blocks(src) assert len(blocks) == 4, "картинка не должна попадать в перевод" chapters = t.split_chapters(blocks) assert [len(c) for c in chapters] == [2, 2] extracted = tmp / "extracted" t.write_input(chapters, extracted) ch0 = json.loads((extracted / "chapter_000.json").read_text(encoding="utf-8")) assert ch0["title"] == "CHAPTER 1" assert ch0["paragraphs"][1] == "He said ⟦i⟧hello⟦/i⟧ loudly." # подделываем ответ book_translator: тот же формат, тот же порядок trans = tmp / "translations" trans.mkdir() for n, ch in enumerate(chapters): paragraphs = [json.loads((extracted / ("chapter_%03d.json" % n)) .read_text(encoding="utf-8"))["paragraphs"][i] .replace("He said", "Он сказал").replace("hello", "привет") for i in range(len(ch))] (trans / ("chapter_%03d_translated.json" % n)).write_text( json.dumps({"number": n, "paragraphs": paragraphs}, ensure_ascii=False), encoding="utf-8") out = tmp / "book.ru.html" assert t.rebuild(src, chapters, tmp, out) result = out.read_text(encoding="utf-8") assert "Он сказал привет loudly.
" in result assert '
' in result, "картинка должна уцелеть"
assert result.count("def compute_total(items, discount_rate, shipping_cost):",
" subtotal = sum(item.price * item.quantity for item in items)",
" return subtotal * (1 - discount_rate) + shipping_cost # not translated",
"",
])
prose = ("Текст главы про обработку данных, списки значений и способы " "хранения промежуточных результатов между запусками.
") src = tmp / "code.html" src.write_text("
raw = re.sub("<[^>]+>", "", src.read_text(encoding="utf-8"))
assert t.detect_language(raw)[0] == "en", "образец должен быть латинским целиком"
assert t.verify_output(src, "ru"), "код не должен заваливать приёмку"
def test_workdir_belongs_to_one_book(tmp: Path):
"""Имена chapter_NNN.json у всех книг одинаковы: чужой workdir склеит
перевод одной книги с текстом другой."""
wd = tmp / "wd"
wd.mkdir()
a = [(0, "p", "", "First book text.")]
b = [(0, "p", "", "Совершенно другая книга.")]
t.claim_workdir(wd, Path("a.html"), a)
t.claim_workdir(wd, Path("a.html"), a) # повторный запуск той же книги — ок
try:
t.claim_workdir(wd, Path("b.html"), b)
except SystemExit as e:
assert "занят другой книгой" in str(e)
else:
raise AssertionError("чужая книга в занятом workdir должна отвергаться")
def test_stale_chapters_removed(tmp: Path):
"""Прошлый прогон был длиннее — его главы иначе уедут в перевод."""
extracted = tmp / "extracted"
extracted.mkdir()
(extracted / "chapter_009.json").write_text("{}", encoding="utf-8")
t.write_input([[(0, "h1", "", "Глава")]], extracted)
assert not (extracted / "chapter_009.json").exists()
assert (extracted / "chapter_000.json").exists()
def test_verify_output_catches_untranslated(tmp: Path):
"""Регресс: первый боевой прогон отрапортовал успех на непереведённом файле."""
bad = tmp / "bad.html"
bad.write_text("[UNTRANSLATED] The build is broken.
\n" * 30, encoding="utf-8")
assert not t.verify_output(bad, "ru"), "заглушки должны заваливать проверку"
english = tmp / "en.html"
english.write_text("The build is broken again today.
\n" * 40, encoding="utf-8")
assert not t.verify_output(english, "ru"), "английский результат должен заваливать проверку"
good = tmp / "ru.html"
good.write_text("Сборка снова сломалась сегодня утром.
\n" * 40, encoding="utf-8")
assert t.verify_output(good, "ru")
if __name__ == "__main__":
import tempfile
test_marks_roundtrip()
test_inline_code_survives()
test_broken_marks_drop_tags()
test_language_detection()
for case in (test_split_and_rebuild, test_code_listings_are_left_alone,
test_workdir_belongs_to_one_book, test_stale_chapters_removed,
test_verify_output_catches_untranslated):
with tempfile.TemporaryDirectory() as d:
case(Path(d))
print("OK")