mirror of
https://github.com/trapexit/mergerfs.git
synced 2026-10-19 16:16:48 +00:00
make all integration tests self-contained with ephemeral mounts
- Add mergerfs_mount() context manager in posix_parity.py that creates temporary branch dirs in tests/.test_tmp/, mounts an ephemeral mergerfs instance, and cleans up after use. - Add find_mergerfs(), find_fusermount(), mount_mergerfs(), unmount_mergerfs() helpers. - Update run-tests to invoke each TEST_* script without a mountpoint arg. - Update README.md to document the new `python3 tests/run-tests` invocation. - Fix 44 tests to use mergerfs_mount() instead of expecting a pre-mounted path. - Add tempfile import to 10 tests that were missing it. - Fix TEST_mount_lifecycle to use mergerfs_mount() context manager. - Fix TEST_posix_tmpfile missing join import. - Fix TEST_cfg_xattr_modes error message framing (no "(known bug)"). - Fix TEST_posix_copy_file_range: loop-copy full size, compare st_size and SHA256 hash; add NativeTmp for space management; route native temp to tests/.test_tmp/ to avoid ENOSPC on /tmp. - Fix TEST_posix_getattr_fgetattr: st_cmp skips st_size for dirs; add compare_ino param for safe inode comparison when inodecalc=passthrough. - Fix TEST_unlink_rename, TEST_use_* tests: namespace cleanup, consistent variable naming, None check for cmpfunc. - Fix TEST_readlink_semantics: remove type annotations to support older Python; wrap in mergerfs_mount() try/except. - Add flush=True to run-tests print so test names show immediately.
This commit is contained in:
committed by
trapexit
parent
6d6d73c7b3
commit
84e4cdeb7e
+7
-1
@@ -6,10 +6,13 @@ and compare behavior against equivalent native syscalls on a local filesystem.
|
||||
The suite is "soft POSIX": it focuses on practical parity for return values,
|
||||
`errno`, and key side effects, including common error paths.
|
||||
|
||||
Each test mounts its own ephemeral mergerfs instance with random temporary
|
||||
branches and cleans everything up automatically.
|
||||
|
||||
Run with:
|
||||
|
||||
```bash
|
||||
python3 tests/run-tests /mnt/mergerfs/
|
||||
python3 tests/run-tests
|
||||
```
|
||||
|
||||
## Coverage Matrix
|
||||
@@ -69,3 +72,6 @@ Across tests, parity checks include combinations of:
|
||||
- Tests that cannot exercise a behavior in the current environment may exit 77
|
||||
and are reported as `SKIP` by `tests/run-tests`.
|
||||
- The xattr test uses a `user.*` key and assumes user xattrs are enabled.
|
||||
- Tests mount their own ephemeral mergerfs instances using the `mergerfs_mount`
|
||||
context manager in `posix_parity.py`. Random directories are created and
|
||||
cleaned up automatically.
|
||||
|
||||
+8
-12
@@ -2,31 +2,25 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_branches
|
||||
from posix_parity import mergerfs_get_option
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import mergerfs_set_option
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
from posix_parity import mergerfs_get_option
|
||||
from posix_parity import mergerfs_set_option
|
||||
from posix_parity import mergerfs_branches
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_api_xattr <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, branches):
|
||||
ctrl = join(mount, ".mergerfs")
|
||||
if not os.path.exists(ctrl):
|
||||
return fail(".mergerfs control file not found")
|
||||
|
||||
branches = mergerfs_branches(mount)
|
||||
if not branches:
|
||||
return fail("no branches found")
|
||||
|
||||
@@ -72,7 +66,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
except Exception as exc:
|
||||
return fail(str(exc))
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_branches
|
||||
from posix_parity import mergerfs_get_option
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import mergerfs_set_option
|
||||
from posix_parity import touch
|
||||
|
||||
@@ -56,13 +57,9 @@ def setup_paths(branches, mount, rel_base, name):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_cfg_link_rename_exdev <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
try:
|
||||
with mergerfs_mount(num_branches=2) as (mount, branches):
|
||||
rel_base = f".mergerfs_exdev_test_{os.getpid()}_{uuid.uuid4().hex}"
|
||||
branches = mergerfs_branches(mount)
|
||||
if len(branches) < 2:
|
||||
print("EXDEV fallback test requires at least 2 branches", end="")
|
||||
return SKIP
|
||||
@@ -81,10 +78,8 @@ def main():
|
||||
return SKIP
|
||||
|
||||
try:
|
||||
# Maximize chance of EXDEV path by path-preserving create policy.
|
||||
mergerfs_set_option(mount, "category.create", "epmfs")
|
||||
|
||||
# link-exdev passthrough => EXDEV when cross-device path preserving applies.
|
||||
src, dst, src_base = setup_paths(branches, mount, rel_base, "link_pass")
|
||||
mergerfs_set_option(mount, "link-exdev", "passthrough")
|
||||
err = get_errno(lambda: os.link(src, dst + ".link.pass"))
|
||||
@@ -108,7 +103,6 @@ def main():
|
||||
if mode != "abs-base-symlink" and resolved != os.path.realpath(src):
|
||||
return fail(f"link-exdev={mode}: target {resolved!r} != {os.path.realpath(src)!r}")
|
||||
|
||||
# rename-exdev passthrough => EXDEV in cross-device path preserving case.
|
||||
src_ren, dst_ren, _ = setup_paths(branches, mount, rel_base, "rename_pass")
|
||||
mergerfs_set_option(mount, "rename-exdev", "passthrough")
|
||||
err = get_errno(lambda: os.rename(src_ren, dst_ren + ".ren.pass"))
|
||||
@@ -146,6 +140,9 @@ def main():
|
||||
finally:
|
||||
for branch in branches:
|
||||
cleanup_dir(join(branch, rel_base))
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5,16 +5,13 @@ import sys
|
||||
|
||||
from posix_parity import fail
|
||||
from posix_parity import mergerfs_get_option
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import mergerfs_set_option
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_cfg_statfs_ignore <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
try:
|
||||
orig_statfs = mergerfs_get_option(mount, "statfs")
|
||||
orig_ignore = mergerfs_get_option(mount, "statfs-ignore")
|
||||
@@ -33,7 +30,6 @@ def main():
|
||||
mergerfs_set_option(mount, "statfs-ignore", "ro")
|
||||
s_ro = os.statvfs(mount)
|
||||
|
||||
# Ignoring additional branch classes should never increase available blocks.
|
||||
if s_nc.f_bavail > s_none.f_bavail:
|
||||
return fail(
|
||||
f"statfs-ignore=nc increased bavail unexpectedly nc={s_nc.f_bavail} none={s_none.f_bavail}"
|
||||
@@ -59,6 +55,9 @@ def main():
|
||||
pass
|
||||
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,6 +9,7 @@ from posix_parity import cleanup_dir
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_get_option
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import mergerfs_set_option
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
@@ -23,16 +24,13 @@ def get_errno(func):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_cfg_xattr_modes <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
merge_base = temp_dir(mount)
|
||||
|
||||
try:
|
||||
path = join(merge_base, "file")
|
||||
xname = "user.mergerfs.cfg.test"
|
||||
xname = "user.test"
|
||||
xval = b"v"
|
||||
|
||||
touch(path, b"x")
|
||||
@@ -49,7 +47,7 @@ def main():
|
||||
if set_err != 0:
|
||||
return fail(f"xattr=passthrough setxattr expected success got errno={set_err}")
|
||||
if get_err == errno.ENODATA:
|
||||
return fail(f"xattr=passthrough getxattr returned ENODATA (known bug): this masks a parity violation")
|
||||
return fail(f"xattr=passthrough getxattr returned ENODATA, expected success")
|
||||
if get_err != 0:
|
||||
return fail(f"xattr=passthrough getxattr unexpected errno={get_err}")
|
||||
|
||||
@@ -60,10 +58,6 @@ def main():
|
||||
err = get_errno(lambda: os.setxattr(path, xname, xval))
|
||||
if err != errno.ENODATA:
|
||||
return fail(f"xattr=noattr setxattr expected ENODATA got errno={err}")
|
||||
|
||||
# xattr=nosys is intentionally not tested here. It disables the
|
||||
# xattr API, including the runtime interface this test uses to
|
||||
# restore options safely.
|
||||
except (PermissionError, FileNotFoundError):
|
||||
return 0
|
||||
finally:
|
||||
@@ -75,6 +69,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_io_passthrough_create_open <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
(fd, filepath) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
try:
|
||||
from posix_parity import mergerfs_mount
|
||||
with mergerfs_mount() as (mount, _):
|
||||
(fd, filepath) = tempfile.mkstemp(dir=mount)
|
||||
|
||||
try:
|
||||
test_data = b"passthrough io test data for create\n" * 100
|
||||
@@ -96,6 +95,9 @@ def main():
|
||||
os.unlink(filepath)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5,6 +5,8 @@ import sys
|
||||
import tempfile
|
||||
import threading
|
||||
|
||||
from posix_parity import mergerfs_mount
|
||||
|
||||
NUM_THREADS = 50
|
||||
TEST_DATA = b"race condition test data\n"
|
||||
|
||||
@@ -46,11 +48,9 @@ def open_and_operate(filepath, barrier, results, index):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_io_passthrough_open_race <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
(fd, filepath) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
(fd, filepath) = tempfile.mkstemp(dir=mount)
|
||||
|
||||
total_size = NUM_THREADS * len(TEST_DATA)
|
||||
os.ftruncate(fd, total_size)
|
||||
@@ -126,6 +126,9 @@ def main():
|
||||
|
||||
os.unlink(filepath)
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+11
-47
@@ -1,69 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from posix_parity import fail
|
||||
|
||||
|
||||
def has_tool(name):
|
||||
return shutil.which(name) is not None
|
||||
from posix_parity import find_fusermount
|
||||
from posix_parity import find_mergerfs
|
||||
from posix_parity import mergerfs_mount
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_mount_lifecycle <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# This is a soft integration harness check for init/destroy wiring.
|
||||
# Skip when environment is not prepared for nested mount tests.
|
||||
if os.geteuid() != 0:
|
||||
return 0
|
||||
if not has_tool("mergerfs"):
|
||||
if find_mergerfs() is None:
|
||||
return 0
|
||||
if not has_tool("fusermount") and not has_tool("fusermount3"):
|
||||
if find_fusermount() is None:
|
||||
return 0
|
||||
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
b1 = os.path.join(td, "b1")
|
||||
b2 = os.path.join(td, "b2")
|
||||
mp = os.path.join(td, "mp")
|
||||
os.makedirs(b1, exist_ok=True)
|
||||
os.makedirs(b2, exist_ok=True)
|
||||
os.makedirs(mp, exist_ok=True)
|
||||
|
||||
cmd = [
|
||||
"mergerfs",
|
||||
f"{b1}:{b2}",
|
||||
mp,
|
||||
"-o",
|
||||
"defaults,allow_other,use_ino,category.create=mfs",
|
||||
]
|
||||
|
||||
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
if p.returncode != 0:
|
||||
return fail(f"mount lifecycle: mergerfs mount failed: {p.stderr.decode(errors='replace')}")
|
||||
|
||||
try:
|
||||
with open(os.path.join(mp, "lifecycle-file"), "wb") as fp:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with open(os.path.join(mount, "lifecycle-file"), "wb") as fp:
|
||||
fp.write(b"ok")
|
||||
if not os.path.exists(os.path.join(mp, "lifecycle-file")):
|
||||
if not os.path.exists(os.path.join(mount, "lifecycle-file")):
|
||||
return fail("mount lifecycle: expected created file to exist")
|
||||
finally:
|
||||
um = shutil.which("fusermount3") or shutil.which("fusermount")
|
||||
for attempt in range(3):
|
||||
u = subprocess.run([um, "-u", mp], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
if u.returncode == 0:
|
||||
break
|
||||
time.sleep(1)
|
||||
if u.returncode != 0:
|
||||
subprocess.run([um, "-z", mp], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,6 +9,7 @@ from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_branches
|
||||
from posix_parity import mergerfs_get_option
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import mergerfs_set_option
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
@@ -18,12 +19,8 @@ SKIP = 77
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_movefile_enospc <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount(num_branches=2) as (mount, branches):
|
||||
try:
|
||||
orig_moveonenospc = mergerfs_get_option(mount, "moveonenospc")
|
||||
except (PermissionError, FileNotFoundError):
|
||||
@@ -35,13 +32,10 @@ def main():
|
||||
return SKIP
|
||||
raise
|
||||
|
||||
branches = mergerfs_branches(mount)
|
||||
if len(branches) < 2:
|
||||
print("moveonenospc test requires at least 2 branches", end="")
|
||||
return SKIP
|
||||
|
||||
# The documented feature only runs after write returns ENOSPC/EDQUOT. This
|
||||
# generic mountpoint test cannot safely force a branch into that state.
|
||||
merge_base = temp_dir(mount)
|
||||
|
||||
try:
|
||||
@@ -82,6 +76,9 @@ def main():
|
||||
return fail(str(exc))
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import mergerfs_mount
|
||||
|
||||
|
||||
def main():
|
||||
(fd, filepath) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
(fd, filepath) = tempfile.mkstemp(dir=mount)
|
||||
|
||||
os.unlink(filepath)
|
||||
|
||||
found = False
|
||||
for entry in os.scandir(sys.argv[1]):
|
||||
for entry in os.scandir(mount):
|
||||
if entry.name.startswith(".fuse_hidden"):
|
||||
print("found .fuse_hidden file {}".format(entry.name), end='')
|
||||
found = True
|
||||
@@ -21,6 +25,9 @@ def main():
|
||||
if found:
|
||||
return 1
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+9
-2
@@ -1,11 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import tempfile
|
||||
import ctypes
|
||||
import mmap
|
||||
import os
|
||||
import resource
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import mergerfs_mount
|
||||
|
||||
|
||||
libc = ctypes.CDLL(None, use_errno=True)
|
||||
@@ -28,7 +30,9 @@ def aligned_read(fd, size):
|
||||
|
||||
|
||||
def main():
|
||||
(fd, filepath) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
(fd, filepath) = tempfile.mkstemp(dir=mount)
|
||||
|
||||
os.close(fd)
|
||||
|
||||
@@ -60,6 +64,9 @@ def main():
|
||||
os.close(fd)
|
||||
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,21 +2,18 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_open_after_unlink <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
merge_base = temp_dir(mount)
|
||||
|
||||
try:
|
||||
@@ -62,6 +59,9 @@ def main():
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -8,6 +8,7 @@ from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_branches
|
||||
from posix_parity import mergerfs_get_option
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import mergerfs_set_option
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
@@ -50,12 +51,8 @@ def least_used_branches(branches, rel_dir):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_policy_lup <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount(num_branches=2) as (mount, branches):
|
||||
try:
|
||||
orig_create = mergerfs_get_option(mount, "func.create")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
@@ -68,7 +65,6 @@ def main():
|
||||
print("LUP search policy option unavailable", end="")
|
||||
return SKIP
|
||||
|
||||
branches = mergerfs_branches(mount)
|
||||
if len(branches) < 2:
|
||||
print("LUP policy test requires at least 2 branches", end="")
|
||||
return SKIP
|
||||
@@ -119,6 +115,9 @@ def main():
|
||||
cleanup_dir(merge_base)
|
||||
for branch in branches:
|
||||
cleanup_dir(join(branch, rel_base))
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -10,17 +10,14 @@ from posix_parity import cleanup_paths
|
||||
from posix_parity import compare_access
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_access <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -74,6 +71,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import ctypes
|
||||
import errno
|
||||
import fcntl
|
||||
import os
|
||||
@@ -11,6 +12,7 @@ from posix_parity import cleanup_dir
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
@@ -25,12 +27,8 @@ def fibmap(fd, block=0):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_bmap <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -45,7 +43,6 @@ def main():
|
||||
mfd = os.open(merge_file, os.O_RDONLY)
|
||||
nfd = os.open(native_file, os.O_RDONLY)
|
||||
try:
|
||||
# FIBMAP often requires CAP_SYS_RAWIO; compare errno parity if denied.
|
||||
err = compare_calls(
|
||||
"ioctl FIBMAP block0",
|
||||
lambda: fibmap(mfd, 0),
|
||||
@@ -53,13 +50,11 @@ def main():
|
||||
lambda a, b: (a >= 0) == (b >= 0),
|
||||
)
|
||||
if err:
|
||||
# If both denied by privilege checks, compare_calls already passes.
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
# EBADF parity on closed fds.
|
||||
err = compare_calls(
|
||||
"ioctl FIBMAP EBADF",
|
||||
lambda: fibmap(mfd, 0),
|
||||
@@ -71,6 +66,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -11,6 +11,7 @@ from posix_parity import cleanup_paths
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import should_compare_inode
|
||||
from posix_parity import stat_cmp_basic
|
||||
from posix_parity import temp_dir
|
||||
@@ -22,11 +23,8 @@ def stat_cmp_basic_with_inode(lhs, rhs):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_chmod <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
stcmp = stat_cmp_basic_with_inode if should_compare_inode(mount) else stat_cmp_basic
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
@@ -96,6 +94,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,16 +9,14 @@ from posix_parity import cleanup_paths
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_chown <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
uid = os.getuid()
|
||||
gid = os.getgid()
|
||||
|
||||
@@ -78,6 +76,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import errno
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
@@ -9,21 +11,34 @@ from posix_parity import cleanup_dir
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_copy_file_range <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
def _native_tmp():
|
||||
tests_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
parent = os.path.join(tests_dir, ".test_tmp")
|
||||
os.makedirs(parent, exist_ok=True)
|
||||
return tempfile.mkdtemp(prefix="native_", dir=parent)
|
||||
|
||||
|
||||
class NativeTmp:
|
||||
def __enter__(self):
|
||||
self.path = _native_tmp()
|
||||
return self.path
|
||||
|
||||
def __exit__(self, *args):
|
||||
shutil.rmtree(self.path, ignore_errors=True)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
if not hasattr(os, "copy_file_range"):
|
||||
return 0
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
with NativeTmp() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
@@ -79,10 +94,6 @@ def main():
|
||||
os.close(mdfd)
|
||||
os.close(ndfd)
|
||||
|
||||
# Exercise very large copy lengths to validate the 64-bit copy result
|
||||
# path when available. Use sparse files to keep disk usage low.
|
||||
# NOTE: 5GB+ length may be slow on some filesystems; targets
|
||||
# ENOSPC/EFBIG early if space is insufficient.
|
||||
large_len = (5 << 30) + 4096
|
||||
merge_large_src = join(merge_base, "src-large")
|
||||
merge_large_dst = join(merge_base, "dst-large")
|
||||
@@ -108,14 +119,23 @@ def main():
|
||||
return 0
|
||||
raise
|
||||
|
||||
err = compare_calls(
|
||||
"copy_file_range large sparse",
|
||||
lambda: os.copy_file_range(msfd, mdfd, large_len),
|
||||
lambda: os.copy_file_range(nsfd, ndfd, large_len),
|
||||
lambda a, b: a == b,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
total_m = 0
|
||||
while True:
|
||||
n = os.copy_file_range(msfd, mdfd, large_len)
|
||||
if n == 0:
|
||||
break
|
||||
total_m += n
|
||||
if total_m >= large_len:
|
||||
break
|
||||
|
||||
total_n = 0
|
||||
while True:
|
||||
n = os.copy_file_range(nsfd, ndfd, large_len)
|
||||
if n == 0:
|
||||
break
|
||||
total_n += n
|
||||
if total_n >= large_len:
|
||||
break
|
||||
|
||||
mstat = os.fstat(mdfd)
|
||||
nstat = os.fstat(ndfd)
|
||||
@@ -124,6 +144,27 @@ def main():
|
||||
"copy_file_range large sparse dst size mismatch "
|
||||
f"mergerfs={mstat.st_size} native={nstat.st_size}"
|
||||
)
|
||||
|
||||
def sha256_partial(fd, size):
|
||||
h = hashlib.sha256()
|
||||
pos = os.lseek(fd, 0, os.SEEK_SET)
|
||||
remaining = size
|
||||
while remaining > 0:
|
||||
chunk = os.read(fd, min(remaining, 65536))
|
||||
if not chunk:
|
||||
break
|
||||
h.update(chunk)
|
||||
remaining -= len(chunk)
|
||||
os.lseek(fd, pos, os.SEEK_SET)
|
||||
return h.digest()
|
||||
|
||||
if os.path.getsize(merge_large_dst) != os.path.getsize(native_large_dst):
|
||||
return fail("copy_file_range large: final sizes differ")
|
||||
|
||||
mhash = sha256_partial(mdfd, mstat.st_size)
|
||||
nhash = sha256_partial(ndfd, nstat.st_size)
|
||||
if mhash != nhash:
|
||||
return fail("copy_file_range large: content hash mismatch")
|
||||
finally:
|
||||
os.close(msfd)
|
||||
os.close(mdfd)
|
||||
@@ -133,6 +174,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -10,16 +10,13 @@ from posix_parity import cleanup_paths
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_create_mknod <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -94,6 +91,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -8,17 +8,14 @@ from posix_parity import cleanup_dir
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_fsync_flush <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -68,7 +65,6 @@ def main():
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
# Close-to-flush parity check: both paths should preserve appended payload.
|
||||
with open(merge_file, "rb") as mf, open(native_file, "rb") as nf:
|
||||
mdata = mf.read()
|
||||
ndata = nf.read()
|
||||
@@ -78,6 +74,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -10,31 +10,27 @@ from posix_parity import cleanup_paths
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import should_compare_inode
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def st_cmp(lhs, rhs):
|
||||
return (
|
||||
def st_cmp(lhs, rhs, compare_ino=False):
|
||||
base = (
|
||||
stat.S_IFMT(lhs.st_mode) == stat.S_IFMT(rhs.st_mode)
|
||||
and stat.S_IMODE(lhs.st_mode) == stat.S_IMODE(rhs.st_mode)
|
||||
and lhs.st_size == rhs.st_size
|
||||
and lhs.st_nlink == rhs.st_nlink
|
||||
)
|
||||
|
||||
|
||||
def st_cmp_with_inode(lhs, rhs):
|
||||
return st_cmp(lhs, rhs) and lhs.st_ino == rhs.st_ino
|
||||
if stat.S_ISDIR(lhs.st_mode) or stat.S_ISDIR(rhs.st_mode):
|
||||
return base and (not compare_ino or lhs.st_ino == rhs.st_ino)
|
||||
return base and lhs.st_size == rhs.st_size and (not compare_ino or lhs.st_ino == rhs.st_ino)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_getattr_fgetattr <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
stcmp = st_cmp_with_inode if should_compare_inode(mount) else st_cmp
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
compare_ino = should_compare_inode(mount)
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
@@ -61,13 +57,13 @@ def main():
|
||||
touch(native_notdir, b"x", 0o644)
|
||||
|
||||
err = compare_calls(
|
||||
"lstat regular", lambda: os.lstat(merge_file), lambda: os.lstat(native_file), stcmp
|
||||
"lstat regular", lambda: os.lstat(merge_file), lambda: os.lstat(native_file), lambda l, r: st_cmp(l, r, compare_ino)
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"lstat directory", lambda: os.lstat(merge_dir), lambda: os.lstat(native_dir), stcmp
|
||||
"lstat directory", lambda: os.lstat(merge_dir), lambda: os.lstat(native_dir), lambda l, r: st_cmp(l, r, compare_ino)
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
@@ -75,7 +71,7 @@ def main():
|
||||
mfd = os.open(merge_file, os.O_RDONLY)
|
||||
nfd = os.open(native_file, os.O_RDONLY)
|
||||
try:
|
||||
err = compare_calls("fstat open fd", lambda: os.fstat(mfd), lambda: os.fstat(nfd), stcmp)
|
||||
err = compare_calls("fstat open fd", lambda: os.fstat(mfd), lambda: os.fstat(nfd), lambda l, r: st_cmp(l, r, compare_ino))
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
@@ -97,6 +93,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -11,6 +11,7 @@ from posix_parity import cleanup_dir
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
@@ -22,12 +23,8 @@ def fionread(fd):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_ioctl <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -63,6 +60,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,17 +9,14 @@ from posix_parity import cleanup_paths
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_link_symlink <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -70,6 +67,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -12,12 +12,12 @@ from posix_parity import cleanup_dir
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def pack_flock(l_type, l_whence=0, l_start=0, l_len=0, l_pid=0):
|
||||
# struct flock (common glibc x86_64 layout)
|
||||
return struct.pack("hhqqi", l_type, l_whence, l_start, l_len, l_pid)
|
||||
|
||||
|
||||
@@ -51,12 +51,8 @@ def main():
|
||||
print(try_process_lock(sys.argv[2]))
|
||||
return 0
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_locking <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -97,7 +93,6 @@ def main():
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
# POSIX record lock via fcntl
|
||||
wrlk = pack_flock(fcntl.F_WRLCK)
|
||||
unlck = pack_flock(fcntl.F_UNLCK)
|
||||
|
||||
@@ -136,6 +131,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,17 +9,14 @@ from posix_parity import cleanup_paths
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_mkdir_rmdir <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -72,6 +69,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,17 +9,14 @@ from posix_parity import cleanup_paths
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_open_read_write <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -82,6 +79,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -8,6 +8,7 @@ import tempfile
|
||||
from posix_parity import cleanup_dir
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
@@ -23,12 +24,8 @@ def poll_mask(fd, timeout_ms=25):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_poll <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -46,7 +43,6 @@ def main():
|
||||
mmask = poll_mask(mfd)
|
||||
nmask = poll_mask(nfd)
|
||||
|
||||
# For regular files POLLIN and POLLOUT are typically ready.
|
||||
if bool(mmask & select.POLLIN) != bool(nmask & select.POLLIN):
|
||||
return fail(f"poll POLLIN mismatch mergerfs_mask=0x{mmask:x} native_mask=0x{nmask:x}")
|
||||
if bool(mmask & select.POLLOUT) != bool(nmask & select.POLLOUT):
|
||||
@@ -58,6 +54,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,6 +9,7 @@ from posix_parity import cleanup_dir
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
@@ -111,12 +112,8 @@ def deleted_visibility_signature(dirpath, victim_path):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_readdir <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -173,6 +170,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -8,6 +8,7 @@ from posix_parity import cleanup_dir
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_get_option
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import mergerfs_set_option
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
@@ -24,12 +25,8 @@ def list_and_stat(dirpath):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_readdir_plus <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -43,13 +40,11 @@ def main():
|
||||
touch(join(merge_dir, f"f{i:03d}"), bytes(str(i), "ascii"))
|
||||
touch(join(native_dir, f"f{i:03d}"), bytes(str(i), "ascii"))
|
||||
|
||||
# Exercise mapping from readdir entries to immediate getattr calls.
|
||||
m_map = list_and_stat(merge_dir)
|
||||
n_map = list_and_stat(native_dir)
|
||||
if m_map != n_map:
|
||||
return fail("readdir+getattr map mismatch between mergerfs and native")
|
||||
|
||||
# Optionally toggle readdir policy and ensure semantic output stays stable.
|
||||
try:
|
||||
orig = mergerfs_get_option(mount, "func.readdir")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
@@ -72,6 +67,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -7,17 +7,14 @@ import tempfile
|
||||
from posix_parity import cleanup_dir
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_release <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -30,7 +27,6 @@ def main():
|
||||
touch(merge_file, b"abc")
|
||||
touch(native_file, b"abc")
|
||||
|
||||
# Open + dup so one close does not release the underlying open-file state.
|
||||
mfd1 = os.open(merge_file, os.O_RDWR)
|
||||
nfd1 = os.open(native_file, os.O_RDWR)
|
||||
mfd2 = os.dup(mfd1)
|
||||
@@ -61,7 +57,6 @@ def main():
|
||||
if md != nd:
|
||||
return fail(f"release final data mismatch mergerfs={md!r} native={nd!r}")
|
||||
|
||||
# Closing same fd twice should map to EBADF on both.
|
||||
mfd = os.open(merge_file, os.O_RDONLY)
|
||||
nfd = os.open(native_file, os.O_RDONLY)
|
||||
os.close(mfd)
|
||||
@@ -82,6 +77,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -8,6 +8,7 @@ import tempfile
|
||||
from posix_parity import cleanup_dir
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
@@ -49,12 +50,8 @@ def close_and_dirfd_errno(path):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_releasedir <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -74,6 +71,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -8,6 +8,7 @@ from posix_parity import cleanup_dir
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
|
||||
|
||||
@@ -22,12 +23,8 @@ def statvfs_cmp(lhs, rhs):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_statfs <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -54,6 +51,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,6 +9,7 @@ from posix_parity import cleanup_dir
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_fullpath
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import should_compare_inode
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
@@ -172,11 +173,8 @@ def expect_same_errno(name, mcall, ncall):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_statx <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
stcmp = cmp_statx_basic_with_inode if should_compare_inode(mount) else cmp_statx_basic
|
||||
|
||||
merge_base = temp_dir(mount)
|
||||
@@ -203,9 +201,6 @@ def main():
|
||||
except OSError:
|
||||
return 0
|
||||
|
||||
# user.mergerfs.fullpath for a symlink may resolve to the target file path
|
||||
# depending on policy behavior. For AT_SYMLINK_NOFOLLOW comparisons we need
|
||||
# the underlying symlink path itself.
|
||||
expected_native_link = os.path.join(os.path.dirname(native_file), "link")
|
||||
if not os.path.islink(native_link):
|
||||
if os.path.islink(expected_native_link):
|
||||
@@ -266,6 +261,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,6 +9,7 @@ from posix_parity import cleanup_dir
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
@@ -28,12 +29,8 @@ def syncfs_fd(fd):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_syncfs <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -62,6 +59,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -7,6 +7,7 @@ import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
from posix_parity import fail
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import join
|
||||
from posix_parity import temp_dir
|
||||
|
||||
@@ -25,15 +26,11 @@ def errno_of(call):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_tmpfile <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if not hasattr(os, "O_TMPFILE"):
|
||||
return 0
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -45,14 +42,11 @@ def main():
|
||||
|
||||
m_err = errno_of(lambda: open_tmpfile(merge_dir))
|
||||
n_err = errno_of(lambda: open_tmpfile(native_dir))
|
||||
# mergerfs does not currently support O_TMPFILE, so EOPNOTSUPP is
|
||||
# the expected result even when the native filesystem supports it.
|
||||
if m_err == errno.EOPNOTSUPP:
|
||||
return 0
|
||||
if m_err != n_err:
|
||||
return fail(f"O_TMPFILE support mismatch mergerfs_errno={m_err} native_errno={n_err}")
|
||||
|
||||
# If unsupported on both, treat as skip-pass.
|
||||
if m_err in (errno.EOPNOTSUPP, errno.EISDIR, errno.EINVAL, errno.ENOSYS):
|
||||
return 0
|
||||
|
||||
@@ -74,6 +68,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,17 +9,14 @@ from posix_parity import cleanup_paths
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_truncate_ftruncate <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -60,6 +57,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,17 +9,14 @@ from posix_parity import cleanup_paths
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_unlink_rename <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -73,6 +70,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -10,17 +10,14 @@ from posix_parity import cleanup_paths
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_utimens <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -80,6 +77,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,6 +9,7 @@ from posix_parity import cleanup_dir
|
||||
from posix_parity import compare_calls
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
@@ -31,14 +32,8 @@ def has_attr(name):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_xattr <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
xname = "user.posix_parity"
|
||||
xvalue = b"parity-check"
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -57,6 +52,9 @@ def main():
|
||||
touch(merge_notdir, b"x")
|
||||
touch(native_notdir, b"x")
|
||||
|
||||
xname = "user.posix_parity"
|
||||
xvalue = b"parity-check"
|
||||
|
||||
err = compare_calls(
|
||||
"setxattr success",
|
||||
lambda: os.setxattr(merge_file, xname, xvalue),
|
||||
@@ -160,6 +158,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -8,6 +8,7 @@ import tempfile
|
||||
from posix_parity import cleanup_dir
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
@@ -88,12 +89,8 @@ def lremovexattr(path, name):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_posix_xattr_matrix <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
@@ -237,6 +234,9 @@ def main():
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -8,6 +8,7 @@ import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
|
||||
|
||||
@@ -16,7 +17,7 @@ libc.readlink.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_size_t]
|
||||
libc.readlink.restype = ctypes.c_ssize_t
|
||||
|
||||
|
||||
def readlink_raw(path: str, bufsiz: int):
|
||||
def readlink_raw(path, bufsiz):
|
||||
buf = ctypes.create_string_buffer(bufsiz)
|
||||
ctypes.set_errno(0)
|
||||
rv = libc.readlink(path.encode(), buf, bufsiz)
|
||||
@@ -25,11 +26,11 @@ def readlink_raw(path: str, bufsiz: int):
|
||||
return rv, err, bytes(buf)
|
||||
|
||||
|
||||
def compare_case(name: str,
|
||||
merge_path: str,
|
||||
native_path: str,
|
||||
bufsiz: int,
|
||||
expect_errno: int = None):
|
||||
def compare_case(name,
|
||||
merge_path,
|
||||
native_path,
|
||||
bufsiz,
|
||||
expect_errno=None):
|
||||
m_rv, m_errno, m_buf = readlink_raw(merge_path, bufsiz)
|
||||
n_rv, n_errno, n_buf = readlink_raw(native_path, bufsiz)
|
||||
|
||||
@@ -46,11 +47,8 @@ def compare_case(name: str,
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_readlink_semantics <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
target = "target-abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
with tempfile.TemporaryDirectory() as native_dir:
|
||||
@@ -179,7 +177,6 @@ def main():
|
||||
print(err, end="")
|
||||
return 1
|
||||
|
||||
# Explicitly guard the prior regression.
|
||||
rv, err, _ = readlink_raw(paths["merge_link"], 1)
|
||||
if rv != 1 or err != 0:
|
||||
print(f"bufsiz=1 expected rv=1 errno=0, got rv={rv} errno={err}", end="")
|
||||
@@ -198,6 +195,9 @@ def main():
|
||||
pass
|
||||
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -10,6 +10,7 @@ from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import mergerfs_branches
|
||||
from posix_parity import mergerfs_get_option
|
||||
from posix_parity import mergerfs_mount
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
@@ -23,15 +24,11 @@ def get_errno(func):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_rmdir_enotempty_priority <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
try:
|
||||
with mergerfs_mount(num_branches=2) as (mount, branches):
|
||||
merge_base = temp_dir(mount)
|
||||
|
||||
try:
|
||||
branches = mergerfs_branches(mount)
|
||||
if len(branches) < 2:
|
||||
return 0
|
||||
|
||||
@@ -57,6 +54,9 @@ def main():
|
||||
return fail(str(exc))
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import mergerfs_mount
|
||||
|
||||
|
||||
def main():
|
||||
(srcfd, src) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
(tgtfd, tgt) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
(srcfd, src) = tempfile.mkstemp(dir=mount)
|
||||
(tgtfd, tgt) = tempfile.mkstemp(dir=mount)
|
||||
|
||||
st_src_before = os.fstat(srcfd)
|
||||
st_tgt_before = os.fstat(tgtfd)
|
||||
@@ -35,6 +39,9 @@ def main():
|
||||
os.close(srcfd)
|
||||
os.close(tgtfd)
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import mergerfs_mount
|
||||
|
||||
|
||||
def main():
|
||||
(fd, filepath) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
(fd, filepath) = tempfile.mkstemp(dir=mount)
|
||||
|
||||
os.posix_fallocate(fd, 0, 1024)
|
||||
|
||||
@@ -29,6 +33,9 @@ def main():
|
||||
|
||||
os.close(fd)
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import mergerfs_mount
|
||||
|
||||
|
||||
def main():
|
||||
(fd, filepath) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
(fd, filepath) = tempfile.mkstemp(dir=mount)
|
||||
|
||||
os.fchmod(fd, 0o700)
|
||||
|
||||
@@ -30,6 +34,9 @@ def main():
|
||||
|
||||
os.close(fd)
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,36 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import mergerfs_mount
|
||||
|
||||
|
||||
def main():
|
||||
(fd, filepath) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
(fd, filepath) = tempfile.mkstemp(dir=mount)
|
||||
|
||||
os.fchown(fd, os.getuid(), os.getgid())
|
||||
uid = os.getuid()
|
||||
gid = os.getgid()
|
||||
|
||||
os.fchown(fd, uid, gid)
|
||||
|
||||
st1 = os.fstat(fd)
|
||||
if st1.st_uid != os.getuid() or st1.st_gid != os.getgid():
|
||||
if st1.st_uid != uid or st1.st_gid != gid:
|
||||
os.close(fd)
|
||||
os.unlink(filepath)
|
||||
print("fchown before unlink: expected uid={} gid={}, got uid={} gid={}".format(
|
||||
os.getuid(), os.getgid(), st1.st_uid, st1.st_gid), end='')
|
||||
uid, gid, st1.st_uid, st1.st_gid), end='')
|
||||
return 1
|
||||
|
||||
os.unlink(filepath)
|
||||
|
||||
os.fchown(fd, os.getuid(), os.getgid())
|
||||
os.fchown(fd, uid, gid)
|
||||
|
||||
st2 = os.fstat(fd)
|
||||
if st2.st_uid != os.getuid() or st2.st_gid != os.getgid():
|
||||
if st2.st_uid != uid or st2.st_gid != gid:
|
||||
os.close(fd)
|
||||
print("fchown after unlink: expected uid={} gid={}, got uid={} gid={}".format(
|
||||
os.getuid(), os.getgid(), st2.st_uid, st2.st_gid), end='')
|
||||
uid, gid, st2.st_uid, st2.st_gid), end='')
|
||||
return 1
|
||||
|
||||
os.close(fd)
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import mergerfs_mount
|
||||
|
||||
|
||||
def main():
|
||||
(fd, filepath) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
(fd, filepath) = tempfile.mkstemp(dir=mount)
|
||||
|
||||
x_before = os.lstat(filepath)
|
||||
x = os.fstat(fd)
|
||||
@@ -39,6 +43,9 @@ def main():
|
||||
|
||||
os.close(fd)
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,34 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import mergerfs_mount
|
||||
|
||||
|
||||
def main():
|
||||
(fd, filepath) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
(fd, filepath) = tempfile.mkstemp(dir=mount)
|
||||
|
||||
os.ftruncate(fd, 1024)
|
||||
os.ftruncate(fd, 10)
|
||||
|
||||
st1 = os.fstat(fd)
|
||||
if st1.st_size != 1024:
|
||||
if st1.st_size != 10:
|
||||
os.close(fd)
|
||||
os.unlink(filepath)
|
||||
print("ftruncate before unlink: expected size 1024, got {}".format(st1.st_size), end='')
|
||||
print("ftruncate before unlink: expected size 10, got {}".format(st1.st_size), end='')
|
||||
return 1
|
||||
|
||||
os.unlink(filepath)
|
||||
|
||||
os.ftruncate(fd, 2048)
|
||||
os.ftruncate(fd, 20)
|
||||
|
||||
st2 = os.fstat(fd)
|
||||
if st2.st_size != 2048:
|
||||
if st2.st_size != 20:
|
||||
os.close(fd)
|
||||
print("ftruncate after unlink: expected size 2048, got {}".format(st2.st_size), end='')
|
||||
print("ftruncate after unlink: expected size 20, got {}".format(st2.st_size), end='')
|
||||
return 1
|
||||
|
||||
os.close(fd)
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,40 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import mergerfs_mount
|
||||
|
||||
|
||||
def main():
|
||||
atime = 1234
|
||||
mtime = 5678
|
||||
(fd, filepath) = tempfile.mkstemp(dir=sys.argv[1])
|
||||
try:
|
||||
with mergerfs_mount() as (mount, _):
|
||||
(fd, filepath) = tempfile.mkstemp(dir=mount)
|
||||
|
||||
os.utime(fd, (atime, mtime))
|
||||
times = (1234567890, 9876543210)
|
||||
os.utime(fd, times=times)
|
||||
|
||||
st1 = os.fstat(fd)
|
||||
if st1.st_atime != atime or st1.st_mtime != mtime:
|
||||
if st1.st_atime != times[0] or st1.st_mtime != times[1]:
|
||||
os.close(fd)
|
||||
os.unlink(filepath)
|
||||
print("futimens before unlink: expected atime={} mtime={}, got atime={} mtime={}".format(
|
||||
atime, mtime, st1.st_atime, st1.st_mtime), end='')
|
||||
times[0], times[1], st1.st_atime, st1.st_mtime), end='')
|
||||
return 1
|
||||
|
||||
os.unlink(filepath)
|
||||
|
||||
atime2 = 9999
|
||||
mtime2 = 8888
|
||||
os.utime(fd, (atime2, mtime2))
|
||||
times2 = (1111111111, 2222222222)
|
||||
os.utime(fd, times=times2)
|
||||
|
||||
st2 = os.fstat(fd)
|
||||
if st2.st_atime != atime2 or st2.st_mtime != mtime2:
|
||||
if st2.st_atime != times2[0] or st2.st_mtime != times2[1]:
|
||||
os.close(fd)
|
||||
print("futimens after unlink: expected atime={} mtime={}, got atime={} mtime={}".format(
|
||||
atime2, mtime2, st2.st_atime, st2.st_mtime), end='')
|
||||
times2[0], times2[1], st2.st_atime, st2.st_mtime), end='')
|
||||
return 1
|
||||
|
||||
os.close(fd)
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
print(str(exc), end="")
|
||||
return 77
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5,7 +5,11 @@ import errno
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
libc = ctypes.CDLL(None, use_errno=True)
|
||||
@@ -13,6 +17,75 @@ libc.access.argtypes = [ctypes.c_char_p, ctypes.c_int]
|
||||
libc.access.restype = ctypes.c_int
|
||||
|
||||
|
||||
def find_mergerfs():
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
build_path = os.path.join(script_dir, "..", "build", "mergerfs")
|
||||
if os.path.isfile(build_path):
|
||||
return os.path.realpath(build_path)
|
||||
return shutil.which("mergerfs")
|
||||
|
||||
|
||||
def find_fusermount():
|
||||
return shutil.which("fusermount3") or shutil.which("fusermount")
|
||||
|
||||
|
||||
def has_tool(name):
|
||||
return shutil.which(name) is not None
|
||||
|
||||
|
||||
def mount_mergerfs(branches, mountpoint, options=None):
|
||||
mergerfs_bin = find_mergerfs()
|
||||
if mergerfs_bin is None:
|
||||
raise RuntimeError("mergerfs binary not found")
|
||||
if options is None:
|
||||
options = "defaults,use_ino,category.create=mfs"
|
||||
if os.geteuid() == 0:
|
||||
options += ",allow_other"
|
||||
branch_arg = ":".join(branches)
|
||||
cmd = [mergerfs_bin, branch_arg, mountpoint, "-o", options]
|
||||
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
||||
def unmount_mergerfs(mountpoint):
|
||||
um = find_fusermount()
|
||||
if um is None:
|
||||
raise RuntimeError("fusermount3/fusermount not found")
|
||||
for attempt in range(3):
|
||||
u = subprocess.run([um, "-u", mountpoint], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
if u.returncode == 0:
|
||||
return
|
||||
time.sleep(1)
|
||||
subprocess.run([um, "-z", mountpoint], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def mergerfs_mount(num_branches=2, options=None):
|
||||
mergerfs_bin = find_mergerfs()
|
||||
if mergerfs_bin is None:
|
||||
raise RuntimeError("mergerfs binary not found")
|
||||
if find_fusermount() is None:
|
||||
raise RuntimeError("fusermount3/fusermount not found")
|
||||
|
||||
tests_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
tmp_parent = os.path.join(tests_dir, ".test_tmp")
|
||||
os.makedirs(tmp_parent, exist_ok=True)
|
||||
tmpdir = tempfile.mkdtemp(prefix="mergerfs_test_", dir=tmp_parent)
|
||||
try:
|
||||
branches = [os.path.join(tmpdir, f"branch{i}") for i in range(num_branches)]
|
||||
for b in branches:
|
||||
os.makedirs(b, exist_ok=True)
|
||||
mountpoint = os.path.join(tmpdir, "mp")
|
||||
os.makedirs(mountpoint, exist_ok=True)
|
||||
mount_mergerfs(branches, mountpoint, options)
|
||||
yield mountpoint, branches
|
||||
finally:
|
||||
try:
|
||||
unmount_mergerfs(mountpoint)
|
||||
except Exception:
|
||||
pass
|
||||
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||
|
||||
|
||||
def temp_dir(root):
|
||||
"""Create a temporary directory within the given root directory."""
|
||||
return tempfile.mkdtemp(dir=root)
|
||||
|
||||
+2
-6
@@ -8,10 +8,6 @@ import subprocess
|
||||
SKIP = 77
|
||||
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
print('usage: run-tests <mountpoint>\n', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
test_path = os.path.realpath(sys.argv[0])
|
||||
test_path = os.path.dirname(test_path)
|
||||
|
||||
@@ -22,9 +18,9 @@ for entry in os.scandir(test_path):
|
||||
continue
|
||||
|
||||
try:
|
||||
print(entry.name + ': ', end='')
|
||||
print(entry.name + ': ', end='', flush=True)
|
||||
fullpath = os.path.join(test_path, entry.name)
|
||||
args = [fullpath, sys.argv[1]]
|
||||
args = [fullpath]
|
||||
rv = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
output, _ = rv.communicate(timeout=120)
|
||||
if rv.returncode == SKIP:
|
||||
|
||||
Reference in New Issue
Block a user