OpenTranscode/tests/test_concurrent_workers.py

79 lines
3.2 KiB
Python

"""
Concurrent-worker temp-dir isolation test.
QA finding: OTC-013 (per-worker temp dir isolation).
Each ``EncoderWorker`` is assigned its own per-PID subdirectory under the
shared app temp dir (``_worker_temp_dir(os.getpid())`` in v3). This is
critical for concurrency: the final cleanup sweep (``_final_cleanup_sweep``)
deletes everything inside ``self._temp_dir`` and must NOT touch a sibling
worker's intermediates.
The 1 case:
- Two workers (with distinct PIDs) get distinct ``_temp_dir`` paths.
The test patches ``os.getpid`` to return distinct values for the two
``EncoderWorker()`` constructor calls (since both run in the same test
process and would otherwise share a PID), and redirects the shared app
temp dir to ``tmp_path`` so the real ``~/.cache/OpenTranscode/`` is not
touched.
"""
from __future__ import annotations
import pytest
def test_workers_get_distinct_temp_dirs(v3_module, mock_env, tmp_path, monkeypatch):
"""Two workers with different PIDs get different ``_temp_dir`` paths.
The directory naming convention is ``worker-{pid}`` under the shared
app temp dir. Distinct PIDs => distinct subdir names => no overlap,
so each worker's cleanup sweep is isolated from concurrent workers.
"""
# Redirect the shared app temp dir to tmp_path so the real
# ~/.cache/OpenTranscode/ is NOT touched by this test.
v3_module._APP_CACHE_DIR = tmp_path
# Two distinct fake PIDs for the two workers. (In production, workers
# run in separate OS processes via the distro's av1an binary, which
# itself spawns SvtAv1EncApp / vpxenc / x265 as subprocesses — each
# getting its own PID. Even within a single process, the per-PID
# subdir logic ensures concurrent workers don't collide on temp space.)
pids = iter([11111, 22222])
monkeypatch.setattr("os.getpid", lambda: next(pids))
# Build two real EncoderWorker instances via __init__. __init__ does
# NOT start the QThread (only .start() does), so this is safe in a
# headless test environment.
common_kwargs = dict(
in_dir=tmp_path / "in",
out_dir=tmp_path / "out",
video_codec=v3_module.VIDEO_CODECS[0],
audio_profile=v3_module.AUDIO_PROFILES[0],
container=v3_module.CONTAINER_PROFILES[0],
crf=30,
preset_label="Medium (6)",
delete_source=False,
env=mock_env,
extensions={".mkv"},
resolution=v3_module.RESOLUTION_PRESETS[0], # "Original" (no scaling)
)
worker1 = v3_module.EncoderWorker(**common_kwargs)
worker2 = v3_module.EncoderWorker(**common_kwargs)
# The critical assertion: distinct temp dirs.
assert worker1._temp_dir != worker2._temp_dir, (
f"Two concurrent workers got the same _temp_dir: {worker1._temp_dir}"
)
# Both should be subdirs of the shared app temp dir, with the per-PID
# naming convention.
assert worker1._temp_dir.parent == tmp_path
assert worker2._temp_dir.parent == tmp_path
assert worker1._temp_dir.name == "worker-11111"
assert worker2._temp_dir.name == "worker-22222"
# Both subdirs should actually exist on disk (the constructor creates
# them with mode=0o700 per SEI CERT FIO09-C).
assert worker1._temp_dir.exists()
assert worker2._temp_dir.exists()