fix: correct throttle legacy resolution; add EXDEV temp-dir coverage
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"""End-to-end coverage for the `--temp-dir` EXDEV (cross-filesystem) fallback.
|
||||
|
||||
`file_to_disk_secure_impl` installs a completed temp file with `renameat(2)`;
|
||||
when the scratch dir lives on a different filesystem the rename fails with
|
||||
`EXDEV` and the engine retries with no scratch dir, writing the file directly in
|
||||
the destination directory (a non-atomic copy), matching rsync.
|
||||
|
||||
The daemon receiver confines `--temp-dir` to the authorized receive root, so a
|
||||
genuine cross-fs scratch there would require an in-root mount point. Bind/tmpfs
|
||||
mounting is not permitted in the CI container (no `CAP_SYS_ADMIN`, and
|
||||
unprivileged user namespaces are disabled), so this test reaches the exact same
|
||||
code path through the local `--read-batch` apply instead: it has no
|
||||
authorized-root confinement, so a relative `--temp-dir` that is a symlink to a
|
||||
tmpfs (`/dev/shm`) is accepted and the final install then crosses filesystems.
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import CLIENT_CMD, get_dest_received_dir
|
||||
|
||||
TMPFS = "/dev/shm"
|
||||
|
||||
|
||||
def _run(args):
|
||||
return subprocess.run(CLIENT_CMD + args, capture_output=True, text=True, timeout=180)
|
||||
|
||||
|
||||
def _read(path):
|
||||
with open(path, "rb") as fh:
|
||||
return fh.read()
|
||||
|
||||
|
||||
def test_read_batch_temp_dir_cross_filesystem_fallback(tmp_path):
|
||||
if not os.path.isdir(TMPFS):
|
||||
pytest.skip("no /dev/shm tmpfs available to force a cross-filesystem install")
|
||||
|
||||
source = tmp_path / "src"
|
||||
dest = tmp_path / "dst"
|
||||
source.mkdir()
|
||||
dest.mkdir()
|
||||
files = {
|
||||
"payload.bin": bytes(range(256)) * 64,
|
||||
"sub/nested.txt": b"nested exdev fallback\n" * 8,
|
||||
}
|
||||
for rel, data in files.items():
|
||||
full = source / rel
|
||||
full.parent.mkdir(parents=True, exist_ok=True)
|
||||
full.write_bytes(data)
|
||||
|
||||
batch = tmp_path / "tree.batch"
|
||||
r = _run(["--only-write-batch", str(batch), str(source)])
|
||||
assert r.returncode == 0, (r.stdout, r.stderr)
|
||||
|
||||
# A cross-filesystem scratch dir, reached through a relative --temp-dir
|
||||
# symlink (the local batch apply performs no authorized-root confinement).
|
||||
scratch = os.path.join(TMPFS, "fastsync_exdev_%d" % os.getpid())
|
||||
shutil.rmtree(scratch, ignore_errors=True)
|
||||
os.makedirs(scratch)
|
||||
os.symlink(scratch, dest / "scratch")
|
||||
try:
|
||||
assert os.stat(scratch).st_dev != os.stat(dest).st_dev, (
|
||||
"scratch and destination share a filesystem; EXDEV cannot be exercised"
|
||||
)
|
||||
r = _run(["--read-batch", str(batch), str(dest), "--temp-dir=scratch"])
|
||||
assert r.returncode == 0, (r.stdout, r.stderr)
|
||||
# The engine must report the non-atomic cross-fs fallback rather than
|
||||
# silently claiming an atomic install.
|
||||
assert "different filesystem" in (r.stdout + r.stderr), (r.stdout, r.stderr)
|
||||
# The tree is still byte-exact and the scratch dir is left clean.
|
||||
received = get_dest_received_dir(str(dest), str(source))
|
||||
for rel, data in files.items():
|
||||
assert _read(os.path.join(received, rel)) == data, f"content mismatch for {rel}"
|
||||
assert os.listdir(scratch) == [], "cross-fs temp file was not cleaned up"
|
||||
finally:
|
||||
shutil.rmtree(scratch, ignore_errors=True)
|
||||
Reference in New Issue
Block a user