115 lines
4.2 KiB
Python
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
|