Files
computer-playbook/tests/unit/python/utils/cache/test_render.py
T
kevinveenbirkenbachandClaude Opus 5 750a8c3929 fix(cache): render over a file a container left behind
A gate run died two seconds in with PermissionError on
compose/package-cache/pip.conf. The worktree is bind-mounted into the
container, so a container that rendered these files owns them afterwards
while the directory stays the host's. Overwriting needs the file's
permission and unlinking the directory's, which is why the host could no
longer write a file in a folder it owns.

_write now catches the refusal and replaces the inode instead. Only after a
refusal, never by default: compose/swarm/cache.override.yml bind-mounts an
apt list into a container by inode, and swapping one that was writable
anyway would leave that mount reading a file nobody updates any more. The
new test pins that half as well, so the guard cannot be simplified away.

Shown failing first: the previous _write raises PermissionError on the same
input the test feeds the new one. Shown working in place: lint-ansible came
back in 3m40s through a run during which make dotenv-force re-rendered these
very files, where the same sequence had aborted the target after two seconds
before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-06 09:59:59 +02:00

68 lines
2.1 KiB
Python

"""Unit tests for writing the generated package-cache files.
The shape measured in a gate run: a container had rendered
compose/package-cache/pip.conf under its own uid, and the next host-side
render died on it with PermissionError while the directory stayed writable.
"""
from __future__ import annotations
import stat
import tempfile
import unittest
from pathlib import Path
from utils.cache.render import _write
class TestWrite(unittest.TestCase):
"""A file the renderer cannot overwrite is replaced, not given up on."""
def test_an_unwritable_file_is_replaced(self) -> None:
with tempfile.TemporaryDirectory() as directory:
target = Path(directory, "pip.conf")
target.write_text("stale", encoding="utf-8")
target.chmod(stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
_write(target, "fresh")
self.assertEqual(
target.read_text(
encoding="utf-8"
), # nocheck: cache-read tempdir fixture rewritten in this test
"fresh",
)
def test_a_writable_file_keeps_its_inode(self) -> None:
with tempfile.TemporaryDirectory() as directory:
target = Path(directory, "arch.list")
target.write_text("stale", encoding="utf-8")
before = target.stat().st_ino
_write(target, "fresh")
self.assertEqual(
target.read_text(
encoding="utf-8"
), # nocheck: cache-read tempdir fixture rewritten in this test
"fresh",
)
self.assertEqual(target.stat().st_ino, before)
def test_a_missing_parent_is_created(self) -> None:
with tempfile.TemporaryDirectory() as directory:
target = Path(directory, "apt", "debian.list")
_write(target, "fresh")
self.assertEqual(
target.read_text(
encoding="utf-8"
), # nocheck: cache-read tempdir fixture rewritten in this test
"fresh",
)
if __name__ == "__main__":
unittest.main()