Files
KF-OnlineJudge/backend/tests/test_delivery_bytecode.py

115 lines
4.2 KiB
Python

"""Ordinary owned source/cache checks; no service, isolation or recovery claims."""
import importlib
import os
import subprocess
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
bytecode = importlib.import_module("scripts.release.bytecode")
manifest = importlib.import_module("scripts.release.manifest")
policy = importlib.import_module("scripts.release.policy")
@pytest.fixture
def compiled(tmp_path):
source = tmp_path / "site/toy.py"
source.parent.mkdir()
source.write_text("VALUE = (17, 'ordinary trusted fixture')\n")
source.chmod(0o444)
assert bytecode.compile_payload(tmp_path, "sd-" + "b" * 12) == 1
cache = source.parent / "__pycache__/toy.cpython-313.pyc"
return tmp_path, source, cache
def test_complete_cache_inventory_and_actual_import_without_source_compilation(compiled):
payload, source, cache = compiled
records = manifest.inventory(payload)
assert {r["path"] for r in records} == {
"site/toy.py",
"site/__pycache__/toy.cpython-313.pyc",
}
assert all(r["mode"] == "0444" for r in records)
# Only this tiny trusted fixture executes. An attempted source compilation
# fails, so success demonstrates CPython actually consumes the checked cache.
code = """import builtins,sys
sys.path.insert(0,sys.argv[1])
def refuse(*args,**kwargs): raise AssertionError('unexpected source compilation')
builtins.compile=refuse
import toy
assert toy.VALUE==(17,'ordinary trusted fixture')
"""
before = {p: bytecode._stamp(p.lstat()) for p in (source, cache)}
result = subprocess.run(
[sys.executable, "-I", "-B", "-c", code, str(source.parent)],
capture_output=True,
timeout=15,
)
assert result.returncode == 0, result.stderr
assert {p: bytecode._stamp(p.lstat()) for p in before} == before
def test_source_change_cannot_reuse_cache_even_with_restored_mtime(compiled):
payload, source, _ = compiled
before = source.stat()
source.chmod(0o600)
source.write_text("VALUE = (18, 'ordinary trusted fixture')\n")
source.chmod(0o444)
os.utime(source, ns=(before.st_atime_ns, before.st_mtime_ns))
with pytest.raises(policy.DeliveryError, match="bytecode source hash differs"):
manifest.inventory(payload)
@pytest.mark.parametrize("offset,value", [(0, 0), (4, 0), (4, 1), (4, 2)])
def test_wrong_magic_timestamp_and_unchecked_hash_caches_are_refused(compiled, offset, value):
payload, _, cache = compiled
data = bytearray(cache.read_bytes())
data[offset] = value
cache.chmod(0o600)
cache.write_bytes(data)
cache.chmod(0o444)
with pytest.raises(policy.DeliveryError, match="only pinned checked-hash"):
manifest.inventory(payload)
def test_builder_refuses_inherited_cache_and_does_not_write_to_external_prefix(
compiled, monkeypatch
):
payload, _, cache = compiled
before = cache.read_bytes()
monkeypatch.setattr(sys, "pycache_prefix", str(payload / "outside"))
with pytest.raises(policy.DeliveryError, match="inherited bytecode cache"):
bytecode.compile_payload(payload, "sd-" + "b" * 12)
assert cache.read_bytes() == before and not (payload / "outside").exists()
@pytest.mark.parametrize(
"name",
[
"site/toy.pyc",
"site/__pycache__/toy.cpython-314.pyc",
"site/__pycache__/toy.cpython-313.opt-1.pyc",
"site/__pycache__/toy.py",
"site/__pycache__/nested/__pycache__/toy.cpython-313.pyc",
"site/../__pycache__/toy.cpython-313.pyc",
"images/python313/__pycache__/toy.cpython-313.pyc",
"frontend/__pycache__/toy.cpython-313.pyc",
],
)
def test_cache_allowlist_keeps_wrong_versions_domains_and_paths_closed(name):
with pytest.raises(policy.DeliveryError):
policy.relative_name(name)
@pytest.mark.parametrize("prefix", policy.BYTECODE_ROOTS)
def test_only_source_adjacent_pinned_cache_paths_are_allowed(prefix):
name = prefix + "package/__pycache__/toy.cpython-313.pyc"
assert policy.relative_name(name) == name
assert policy.bytecode_source(name) == prefix + "package/toy.py"
directory = prefix + "package/__pycache__/.__directory__"
assert policy.relative_name(directory) == directory