Skip to content

Commit fde648c

Browse files
committed
docs: make the stopped-run translate tests deterministic
The test added in the previous commit assumed the rejected page is seen before its sibling lands; with four pages and two in flight the loop may legitimately start a third page first, which then waited alone at the test's barrier. Split it into two order-independent tests: a sibling that lands beside a rejected page is written (exactly two pages in the run), and no further page starts once the rejection is seen (both in flight rejected). The worker now hands a ConfigError back as a value like a PageError, so that page's usage is counted and the loop needs no except.
1 parent b5557cb commit fde648c

2 files changed

Lines changed: 46 additions & 27 deletions

File tree

scripts/docs/translations.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -974,20 +974,20 @@ def command_translate(repo: Repo, args: argparse.Namespace, translator: Translat
974974
if translator is None and any(job.open for _, job in work):
975975
translator = anthropic_translator()
976976

977-
def produce(inputs: Inputs, job: Job) -> tuple[str | PageError, Usage]:
978-
"""One page's body (or why it failed) and the tokens it cost; runs on a pool thread."""
977+
def produce(inputs: Inputs, job: Job) -> tuple[str | PageError | ConfigError, Usage]:
978+
"""The page body, or why it failed or must stop the run, with the tokens it cost; runs on a pool thread."""
979979
spent = Usage()
980980
try:
981981
body = translate_page(repo, inputs, job, translator, model, spent) if translator else reassemble(repo, job)
982-
except PageError as exc:
982+
except (PageError, ConfigError) as exc:
983983
return exc, spent
984984
return body, spent
985985

986986
active = sorted({inputs.language.code for inputs, _ in work}, key=[lang.code for lang in languages].index)
987987
print(f"translating {len(work)} pages ({', '.join(active)}), {args.jobs} at a time, with {model}", flush=True)
988988
usage, failed = Usage(), False
989989
queue = deque(work)
990-
running: dict[Future[tuple[str | PageError, Usage]], tuple[Language, Job]] = {}
990+
running: dict[Future[tuple[str | PageError | ConfigError, Usage]], tuple[Language, Job]] = {}
991991
stopped: ConfigError | None = None # rejected credentials: start nothing more, keep what still lands
992992
pool = ThreadPoolExecutor(max_workers=args.jobs)
993993
try:
@@ -1000,12 +1000,11 @@ def produce(inputs: Inputs, job: Job) -> tuple[str | PageError, Usage]:
10001000
running[pool.submit(produce, inputs, job)] = (inputs.language, job)
10011001
for future in wait(running, return_when=FIRST_COMPLETED).done:
10021002
language, job = running.pop(future)
1003-
try:
1004-
result, spent = future.result()
1005-
except ConfigError as exc:
1006-
stopped = stopped or exc
1007-
continue
1003+
result, spent = future.result()
10081004
usage.add(spent)
1005+
if isinstance(result, ConfigError):
1006+
stopped = stopped or result
1007+
continue
10091008
page = job.state.page
10101009
if isinstance(result, PageError):
10111010
failed = True

tests/docs/test_translations.py

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -960,37 +960,57 @@ def complete(self, *, model: str, system: str, messages: Sequence[t.Message], ma
960960
assert run(capsys, root, "status") == (0, "ja (日本語): 0 missing, 0 outdated, 4 current, 0 removable\n", "")
961961

962962

963-
def test_rejected_credentials_with_pages_in_flight_start_no_further_page_but_a_finished_one_is_kept(
963+
class RejectedTogether:
964+
"""Lets two requests in together, then rejects the credentials of those whose page title is in `rejected`."""
965+
966+
def __init__(self, rejected: Sequence[str]) -> None:
967+
self.rejected, self.calls = rejected, 0
968+
self.both, self.lock = threading.Barrier(2, timeout=5), threading.Lock()
969+
970+
def complete(self, *, model: str, system: str, messages: Sequence[t.Message], max_tokens: int) -> t.Completion:
971+
with self.lock:
972+
self.calls += 1
973+
self.both.wait()
974+
if any(title in messages[0].content for title in self.rejected):
975+
raise t.ConfigError("the API rejected the credentials: invalid bearer token")
976+
return t.Completion(TOOLS_JA, t.Usage(10, 4, 0, 9))
977+
978+
979+
def test_rejected_credentials_keep_a_page_that_lands_from_the_same_flight(
964980
tmp_path: Path, capsys: pytest.CaptureFixture[str]
965981
) -> None:
966-
"""Tool-defined: with `--jobs 2`, when the API rejects the credentials for one page while another is in
967-
flight (here both are waiting before either is answered), the other page still lands and is written,
968-
the two remaining pages are never requested, usage is reported, and the run stops with exit 2."""
982+
"""Tool-defined: with two pages in flight together, the credentials being rejected for one does not throw
983+
away the other: whichever lands first, the good page is written and usage reported before exit 2."""
969984
root = make_repo(tmp_path)
985+
fake = RejectedTogether(["# Home"])
970986

971-
class RejectsHome:
972-
def __init__(self) -> None:
973-
self.both, self.lock, self.calls = threading.Barrier(2, timeout=5), threading.Lock(), 0
987+
code, out, err = translate(capsys, root, "--lang", "ja", "--pages", "index.md", "tools.md", jobs=2, translator=fake)
988+
989+
assert (code, fake.calls, err) == (2, 2, "translations: the API rejected the credentials: invalid bearer token\n")
990+
assert out == snapshot("""\
991+
translating 2 pages (ja), 2 at a time, with test-model
992+
ja: translated tools.md (3 of 3 sections)
993+
usage: 10 input / 4 output / 0 cache-write / 9 cache-read tokens
994+
""")
995+
assert sorted(path.name for path in (root / "i18n" / "ja" / "pages").glob("*.md")) == ["tools.md"]
974996

975-
def complete(self, *, model: str, system: str, messages: Sequence[t.Message], max_tokens: int) -> t.Completion:
976-
with self.lock:
977-
self.calls += 1
978-
self.both.wait()
979-
if "# Home" in messages[0].content:
980-
raise t.ConfigError("the API rejected the credentials: invalid bearer token")
981-
return t.Completion(TOOLS_JA, t.Usage(10, 4, 0, 9))
982997

983-
fake = RejectsHome()
998+
def test_rejected_credentials_with_pages_in_flight_start_no_further_page(
999+
tmp_path: Path, capsys: pytest.CaptureFixture[str]
1000+
) -> None:
1001+
"""Tool-defined: with `--jobs 2` over four pages, once the credentials are rejected (here for both pages in
1002+
flight) the two remaining pages are never requested and nothing is written."""
1003+
root = make_repo(tmp_path)
1004+
fake = RejectedTogether(["# Home", "# Tools"])
9841005

9851006
code, out, err = translate(capsys, root, "--lang", "ja", jobs=2, translator=fake)
9861007

9871008
assert (code, fake.calls, err) == (2, 2, "translations: the API rejected the credentials: invalid bearer token\n")
9881009
assert out == snapshot("""\
9891010
translating 4 pages (ja), 2 at a time, with test-model
990-
ja: translated tools.md (3 of 3 sections)
991-
usage: 10 input / 4 output / 0 cache-write / 9 cache-read tokens
1011+
usage: 0 input / 0 output / 0 cache-write / 0 cache-read tokens
9921012
""")
993-
assert sorted(path.name for path in (root / "i18n" / "ja" / "pages").glob("*.md")) == ["tools.md"]
1013+
assert not (root / "i18n" / "ja" / "pages").exists()
9941014

9951015

9961016
def test_translate_without_lang_works_through_every_language_with_that_languages_own_prompt(

0 commit comments

Comments
 (0)