Skip to content

Commit 8b31fd7

Browse files
percy-raskovacodex
andcommitted
fix(persistence): bound exporter encoding
Co-Authored-By: Codex <noreply@openai.com>
1 parent ceeb10b commit 8b31fd7

2 files changed

Lines changed: 129 additions & 20 deletions

File tree

tests/unit/persistence/test_rust_legacy_contract_fixtures.py

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
from __future__ import annotations
44

55
import importlib.util
6+
import os
7+
import stat
68
import subprocess
79
import sys
8-
from collections.abc import Iterator
910
from pathlib import Path
1011
from types import SimpleNamespace
1112

@@ -37,15 +38,31 @@ def test_overfull_migration_directory_stops_at_its_sentinel_entry(
3738
) -> None:
3839
"""Directory discovery must reject at its bounded sentinel entry."""
3940

40-
def entries() -> Iterator[Path]:
41-
for index in range(exporter.MAX_MIGRATION_DIRECTORY_ENTRIES + 1):
42-
yield Path(f"{index:04d}_migration.sql")
43-
raise AssertionError("migration discovery consumed beyond its sentinel entry")
41+
class ScandirContext:
42+
"""Bounded fake directory stream."""
4443

45-
def iterdir(_: Path) -> Iterator[Path]:
46-
return entries()
44+
def __init__(self) -> None:
45+
self.index = 0
4746

48-
monkeypatch.setattr(Path, "iterdir", iterdir)
47+
def __enter__(self) -> ScandirContext:
48+
return self
49+
50+
def __exit__(self, *_: object) -> None:
51+
return None
52+
53+
def __iter__(self) -> ScandirContext:
54+
return self
55+
56+
def __next__(self) -> object:
57+
if self.index == exporter.MAX_MIGRATION_DIRECTORY_ENTRIES + 1:
58+
raise AssertionError("directory scan consumed beyond its sentinel entry")
59+
self.index += 1
60+
return object()
61+
62+
def scandir(_: Path) -> ScandirContext:
63+
return ScandirContext()
64+
65+
monkeypatch.setattr(os, "scandir", scandir)
4966
with pytest.raises(RuntimeError, match=r"migration directory: entries exceed"):
5067
exporter._numbered_migrations()
5168

@@ -92,6 +109,87 @@ def read_bytes(*_: object, **__: object) -> bytes:
92109
assert not read_bytes_called
93110

94111

112+
def test_frame_rejects_over_budget_text_before_encoding() -> None:
113+
"""A character-length overflow must not invoke a costly custom encoder."""
114+
115+
class EncodingTrap(str):
116+
"""String that fails if the exporter encodes it."""
117+
118+
def encode(self, *_: object, **__: object) -> bytes:
119+
raise AssertionError("over-budget text must not be encoded")
120+
121+
chunk = EncodingTrap("x" * exporter.MAX_BYTES)
122+
with pytest.raises(ValueError, match=r"frame: framed bytes exceed"):
123+
exporter._frame([chunk], label="frame")
124+
125+
126+
def test_frame_checks_encoded_multibyte_length_before_nul_scan() -> None:
127+
"""UTF-8 expansion must still respect the remaining byte budget."""
128+
chunk = "é" * (exporter.MAX_BYTES // 2)
129+
with pytest.raises(ValueError, match=r"frame: framed bytes exceed"):
130+
exporter._frame([chunk], label="frame")
131+
132+
133+
def test_frame_preserves_empty_chunk_precedence_after_a_full_chunk() -> None:
134+
"""An empty chunk remains invalid before any aggregate-size error."""
135+
full_chunk = "x" * (exporter.MAX_BYTES - 1)
136+
with pytest.raises(ValueError, match=r"frame: empty chunk 1"):
137+
exporter._frame([full_chunk, ""], label="frame")
138+
139+
140+
@pytest.mark.parametrize(
141+
("mode", "size"),
142+
[
143+
(stat.S_IFREG, exporter.MAX_BYTES + 1),
144+
(stat.S_IFDIR, len(b"expected")),
145+
],
146+
)
147+
def test_fixture_check_uses_one_descriptor_and_stops_before_read(
148+
monkeypatch: pytest.MonkeyPatch,
149+
mode: int,
150+
size: int,
151+
) -> None:
152+
"""Oversize and nonregular fixtures must stop after descriptor metadata."""
153+
open_calls = 0
154+
155+
class Fixture:
156+
"""Descriptor-backed fixture trap."""
157+
158+
def __enter__(self) -> Fixture:
159+
return self
160+
161+
def __exit__(self, *_: object) -> None:
162+
return None
163+
164+
def fileno(self) -> int:
165+
return 41
166+
167+
def read(self, _: int = -1) -> bytes:
168+
raise AssertionError("metadata-rejected fixture must not be read")
169+
170+
class FixturePath:
171+
"""Path-shaped trap for the exporter only."""
172+
173+
def open(self, *_: object, **__: object) -> Fixture:
174+
nonlocal open_calls
175+
open_calls += 1
176+
return Fixture()
177+
178+
def stat(self, *_: object, **__: object) -> SimpleNamespace:
179+
raise AssertionError("fixture checks must not use Path.stat()")
180+
181+
def __str__(self) -> str:
182+
return "fixture.bin"
183+
184+
def fstat(descriptor: int) -> SimpleNamespace:
185+
assert descriptor == 41
186+
return SimpleNamespace(st_mode=mode, st_size=size)
187+
188+
monkeypatch.setattr(os, "fstat", fstat)
189+
assert not exporter._check(FixturePath(), b"expected")
190+
assert open_calls == 1
191+
192+
95193
def test_numbered_migrations_preserve_unique_missing_and_duplicate_results(
96194
monkeypatch: pytest.MonkeyPatch,
97195
tmp_path: Path,

tools/export_legacy_postgres_contract.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from __future__ import annotations
55

66
import argparse
7+
import os
8+
import stat
79
import sys
810
from collections.abc import Sequence
911
from itertools import islice
@@ -24,7 +26,8 @@ def _numbered_migrations() -> list[str]:
2426
"""Read exactly one bounded source file for each required migration version."""
2527
migration_dir = SRC / "babylon/persistence/migrations"
2628
try:
27-
entries = list(islice(migration_dir.iterdir(), MAX_MIGRATION_DIRECTORY_ENTRIES + 1))
29+
with os.scandir(migration_dir) as directory:
30+
entries = list(islice(directory, MAX_MIGRATION_DIRECTORY_ENTRIES + 1))
2831
except FileNotFoundError:
2932
entries = []
3033
if len(entries) > MAX_MIGRATION_DIRECTORY_ENTRIES:
@@ -48,13 +51,13 @@ def _numbered_migrations() -> list[str]:
4851
return chunks
4952

5053

51-
def _migration_matches(entries: Sequence[Path], version: int) -> list[Path]:
54+
def _migration_matches(entries: Sequence[os.DirEntry[str]], version: int) -> list[Path]:
5255
"""Return no more than the two source paths needed to detect duplicates."""
5356
prefix = f"{version:04d}_"
5457
matches: list[Path] = []
5558
for entry in islice(entries, MAX_MIGRATION_DIRECTORY_ENTRIES):
5659
if entry.name.startswith(prefix) and entry.name.endswith(".sql") and entry.is_file():
57-
matches.append(entry)
60+
matches.append(Path(entry.path))
5861
if len(matches) == 2:
5962
break
6063
return matches
@@ -78,12 +81,14 @@ def _frame(chunks: Sequence[str], *, label: str) -> bytes:
7881
raise ValueError(f"{label}: {len(chunks)} chunks exceeds {MAX_CHUNKS}")
7982
framed = bytearray()
8083
for index, chunk in enumerate(islice(chunks, MAX_CHUNKS)):
84+
if not chunk:
85+
raise ValueError(f"{label}: empty chunk {index}")
86+
remaining = MAX_BYTES - len(framed) - 1
87+
if len(chunk) > remaining:
88+
raise ValueError(f"{label}: framed bytes exceed {MAX_BYTES}")
8189
encoded = chunk.encode("utf-8")
82-
framed_length = len(framed) + len(encoded) + 1
83-
if framed_length > MAX_BYTES:
90+
if len(encoded) > remaining:
8491
raise ValueError(f"{label}: framed bytes exceed {MAX_BYTES}")
85-
if not encoded:
86-
raise ValueError(f"{label}: empty chunk {index}")
8792
if b"\0" in encoded:
8893
raise ValueError(f"{label}: embedded NUL in chunk {index}")
8994
framed.extend(encoded)
@@ -111,15 +116,21 @@ def _expected() -> tuple[bytes, bytes]:
111116

112117
def _check(path: Path, expected: bytes) -> bool:
113118
try:
114-
actual_size = path.stat().st_size
119+
with path.open("rb") as fixture:
120+
metadata = os.fstat(fixture.fileno())
121+
if not stat.S_ISREG(metadata.st_mode):
122+
print(f"invalid fixture: {path} is not a regular file", file=sys.stderr)
123+
return False
124+
if metadata.st_size != len(expected):
125+
print(f"stale fixture: {path}", file=sys.stderr)
126+
return False
127+
actual = fixture.read(len(expected) + 1)
115128
except FileNotFoundError:
116129
print(f"missing fixture: {path}", file=sys.stderr)
117130
return False
118-
if actual_size != len(expected):
119-
print(f"stale fixture: {path}", file=sys.stderr)
131+
except OSError as error:
132+
print(f"fixture access failed: {path}: {error}", file=sys.stderr)
120133
return False
121-
with path.open("rb") as fixture:
122-
actual = fixture.read(len(expected) + 1)
123134
if actual != expected:
124135
print(f"stale fixture: {path}", file=sys.stderr)
125136
return False

0 commit comments

Comments
 (0)