mirror of
https://github.com/trapexit/mergerfs.git
synced 2026-10-19 16:16:48 +00:00
Add unit and integration tests for recently changed code
Unit tests (29 new in tests/tests.cpp): - str::eq() and str::startswith() char* nullptr safety - str::from() u64/s64/int suffix parsing (K/M/G/T, overflow, invalid) - num::humanize() byte formatting (0, 512, 1K, 1M, 1G, 1T, non-exact) - RmdirErr priority error accumulation (ENOTEMPTY/EEXIST override rules) - HashSet inline-to-heap growth, empty string, and large insertion - fs::inode algorithm switching (valid/invalid names, passthrough raw ino, distinct inodes across algorithms) - RND::rand64() bounds (range, max, shrink_to_rand_elem) - Branches::Impl copy and move assignment re-links default_minfreespace Integration tests (5 new in tests/): - TEST_policy_lup: LUP policy create and search - TEST_rmdir_enotempty_priority: rmdir returns ENOTEMPTY for non-empty dirs - TEST_open_after_unlink: read/write via fd after unlink - TEST_api_xattr: mergerfs xattr API (fullpath, relpath, basepath, allpaths) - TEST_movefile_enospc: moveonenospc configuration round-trip Also: remove xattr nosys checks from TEST_cfg_xattr_modes, reformat integration test scripts to use temp_dir/cleanup_dir helpers, and minor parameter alignment changes in fuse_getxattr, fuse_init, fuse_rename.
This commit is contained in:
committed by
trapexit
parent
50c0627b2b
commit
ef1e01b5a5
@@ -117,13 +117,13 @@ _getxattr_user_mergerfs_allpaths(const Branches::Ptr branches_,
|
||||
|
||||
static
|
||||
int
|
||||
_getxattr_user_mergerfs(const fs::path &basepath_,
|
||||
const fs::path &fusepath_,
|
||||
const fs::path &fullpath_,
|
||||
const Branches::Ptr branches_,
|
||||
const char *attrname_,
|
||||
char *buf_,
|
||||
const size_t count_)
|
||||
_getxattr_user_mergerfs(const fs::path &basepath_,
|
||||
const fs::path &fusepath_,
|
||||
const fs::path &fullpath_,
|
||||
const Branches::Ptr branches_,
|
||||
const char *attrname_,
|
||||
char *buf_,
|
||||
const size_t count_)
|
||||
{
|
||||
std::string key;
|
||||
|
||||
|
||||
+2
-2
@@ -37,7 +37,7 @@
|
||||
static
|
||||
void
|
||||
_want(fuse_conn_info_t *conn_,
|
||||
const int flag_)
|
||||
const int flag_)
|
||||
{
|
||||
conn_->want |= flag_;
|
||||
}
|
||||
@@ -80,7 +80,7 @@ static const char MAX_PAGES_LIMIT_FILEPATH[] = "/proc/sys/fs/fuse/max_pages_limi
|
||||
static
|
||||
void
|
||||
_want_if_capable_max_pages(fuse_conn_info_t *conn_,
|
||||
Config &cfg_)
|
||||
Config &cfg_)
|
||||
{
|
||||
std::fstream f;
|
||||
u64 max_pages_limit;
|
||||
|
||||
+8
-8
@@ -70,8 +70,8 @@ _remove(const StrVec &toremove_)
|
||||
static
|
||||
int
|
||||
_rename_create_path(const Policy::Search &searchPolicy_,
|
||||
const Policy::Action &actionPolicy_,
|
||||
const Branches::Ptr branches_,
|
||||
const Policy::Action &actionPolicy_,
|
||||
const Branches::Ptr branches_,
|
||||
const fs::path &oldfusepath_,
|
||||
const fs::path &newfusepath_)
|
||||
{
|
||||
@@ -131,7 +131,7 @@ _rename_create_path(const Policy::Search &searchPolicy_,
|
||||
static
|
||||
int
|
||||
_rename_preserve_path(const Policy::Action &actionPolicy_,
|
||||
const Branches::Ptr branches_,
|
||||
const Branches::Ptr branches_,
|
||||
const fs::path &oldfusepath_,
|
||||
const fs::path &newfusepath_)
|
||||
{
|
||||
@@ -202,7 +202,7 @@ _rename_exdev_rename_back(const std::vector<Branch*> &branches_,
|
||||
static
|
||||
int
|
||||
_rename_exdev_rename_target(const Policy::Action &actionPolicy_,
|
||||
const Branches::Ptr ibranches_,
|
||||
const Branches::Ptr ibranches_,
|
||||
const fs::path &oldfusepath_,
|
||||
std::vector<Branch*> &obranches_)
|
||||
{
|
||||
@@ -249,8 +249,8 @@ _rename_exdev_rename_target(const Policy::Action &actionPolicy_,
|
||||
static
|
||||
int
|
||||
_rename_exdev_rel_symlink(const fuse_req_ctx_t *ctx_,
|
||||
const Policy::Action &actionPolicy_,
|
||||
const Branches::Ptr branches_,
|
||||
const Policy::Action &actionPolicy_,
|
||||
const Branches::Ptr branches_,
|
||||
const fs::path &oldfusepath_,
|
||||
const fs::path &newfusepath_)
|
||||
{
|
||||
@@ -278,8 +278,8 @@ _rename_exdev_rel_symlink(const fuse_req_ctx_t *ctx_,
|
||||
static
|
||||
int
|
||||
_rename_exdev_abs_symlink(const fuse_req_ctx_t *ctx_,
|
||||
const Policy::Action &actionPolicy_,
|
||||
const Branches::Ptr branches_,
|
||||
const Policy::Action &actionPolicy_,
|
||||
const Branches::Ptr branches_,
|
||||
const fs::path &mount_,
|
||||
const fs::path &oldfusepath_,
|
||||
const fs::path &newfusepath_)
|
||||
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
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 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:
|
||||
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")
|
||||
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
merge_file = join(merge_base, "api_test")
|
||||
touch(merge_file, b"test")
|
||||
|
||||
try:
|
||||
fullpath = os.getxattr(merge_file, "user.mergerfs.fullpath")
|
||||
fullpath_str = fullpath.decode("utf-8", errors="surrogateescape")
|
||||
if not fullpath_str:
|
||||
return fail("fullpath xattr is empty")
|
||||
if not os.path.exists(fullpath_str):
|
||||
return fail(f"fullpath '{fullpath_str}' does not exist on disk")
|
||||
except OSError as exc:
|
||||
return fail(f"getxattr fullpath: errno={exc.errno}")
|
||||
|
||||
try:
|
||||
relpath = os.getxattr(merge_file, "user.mergerfs.relpath")
|
||||
relpath_str = relpath.decode("utf-8", errors="surrogateescape")
|
||||
if not relpath_str:
|
||||
return fail("relpath xattr is empty")
|
||||
except OSError as exc:
|
||||
return fail(f"getxattr relpath: errno={exc.errno}")
|
||||
|
||||
try:
|
||||
basepath = os.getxattr(merge_file, "user.mergerfs.basepath")
|
||||
basepath_str = basepath.decode("utf-8", errors="surrogateescape")
|
||||
if not basepath_str:
|
||||
return fail("basepath xattr is empty")
|
||||
except OSError as exc:
|
||||
return fail(f"getxattr basepath: errno={exc.errno}")
|
||||
|
||||
try:
|
||||
allpaths = os.getxattr(merge_file, "user.mergerfs.allpaths")
|
||||
allpaths_str = allpaths.decode("utf-8", errors="surrogateescape")
|
||||
if not allpaths_str:
|
||||
return fail("allpaths xattr is empty")
|
||||
except OSError as exc:
|
||||
return fail(f"getxattr allpaths: errno={exc.errno}")
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
except Exception as exc:
|
||||
return fail(str(exc))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -4,12 +4,14 @@ import errno
|
||||
import os
|
||||
import sys
|
||||
|
||||
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_set_option
|
||||
from posix_parity import parse_allpaths
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
from posix_parity import join
|
||||
|
||||
|
||||
def get_errno(func):
|
||||
@@ -31,9 +33,9 @@ def must_two_branches(paths, op):
|
||||
return None
|
||||
|
||||
|
||||
def pick_cross_branch_paths(mount):
|
||||
src = join(mount, "cfg-exdev/src")
|
||||
dst = join(mount, "cfg-exdev/sub/dst")
|
||||
def pick_cross_branch_paths(mount, merge_base):
|
||||
src = join(merge_base, "src")
|
||||
dst = join(merge_base, "sub/dst")
|
||||
|
||||
touch(src, b"src")
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
@@ -68,76 +70,81 @@ def main():
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
merge_base = temp_dir(mount)
|
||||
|
||||
try:
|
||||
src, dst = pick_cross_branch_paths(mount)
|
||||
except PermissionError:
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
# Environment-dependent; treat as soft skip.
|
||||
msg = str(exc)
|
||||
if "requires at least 2 branches" in msg or msg == "allpaths-xattr-unsupported":
|
||||
try:
|
||||
src, dst = pick_cross_branch_paths(mount, merge_base)
|
||||
except PermissionError:
|
||||
return 0
|
||||
except RuntimeError as exc:
|
||||
# Environment-dependent; treat as soft skip.
|
||||
msg = str(exc)
|
||||
if "requires at least 2 branches" in msg or msg == "allpaths-xattr-unsupported":
|
||||
return 0
|
||||
return fail(msg)
|
||||
except OSError:
|
||||
return 0
|
||||
return fail(msg)
|
||||
except OSError:
|
||||
return 0
|
||||
|
||||
try:
|
||||
orig_create = mergerfs_get_option(mount, "category.create")
|
||||
orig_link = mergerfs_get_option(mount, "link-exdev")
|
||||
orig_rename = mergerfs_get_option(mount, "rename-exdev")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
try:
|
||||
orig_create = mergerfs_get_option(mount, "category.create")
|
||||
orig_link = mergerfs_get_option(mount, "link-exdev")
|
||||
orig_rename = mergerfs_get_option(mount, "rename-exdev")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
|
||||
try:
|
||||
# Maximize chance of EXDEV path by path-preserving create policy.
|
||||
mergerfs_set_option(mount, "category.create", "epmfs")
|
||||
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.
|
||||
mergerfs_set_option(mount, "link-exdev", "passthrough")
|
||||
err = get_errno(lambda: os.link(src, dst + ".link.pass"))
|
||||
if err not in (0, errno.EXDEV):
|
||||
return fail(f"link-exdev=passthrough unexpected errno={err}")
|
||||
link_exdev_active = (err == errno.EXDEV)
|
||||
# link-exdev passthrough => EXDEV when cross-device path preserving applies.
|
||||
mergerfs_set_option(mount, "link-exdev", "passthrough")
|
||||
err = get_errno(lambda: os.link(src, dst + ".link.pass"))
|
||||
if err not in (0, errno.EXDEV):
|
||||
return fail(f"link-exdev=passthrough unexpected errno={err}")
|
||||
link_exdev_active = (err == errno.EXDEV)
|
||||
|
||||
# rel-symlink should not return EXDEV if fallback triggers.
|
||||
mergerfs_set_option(mount, "link-exdev", "rel-symlink")
|
||||
rel_link = dst + ".link.rel"
|
||||
err = get_errno(lambda: os.link(src, rel_link))
|
||||
if link_exdev_active and err == errno.EXDEV:
|
||||
return fail("link-exdev=rel-symlink still returned EXDEV")
|
||||
if link_exdev_active and err == 0 and not os.path.islink(rel_link):
|
||||
return fail("link-exdev=rel-symlink expected symlink fallback")
|
||||
# rel-symlink should not return EXDEV if fallback triggers.
|
||||
mergerfs_set_option(mount, "link-exdev", "rel-symlink")
|
||||
rel_link = dst + ".link.rel"
|
||||
err = get_errno(lambda: os.link(src, rel_link))
|
||||
if link_exdev_active and err == errno.EXDEV:
|
||||
return fail("link-exdev=rel-symlink still returned EXDEV")
|
||||
if link_exdev_active and err == 0 and not os.path.islink(rel_link):
|
||||
return fail("link-exdev=rel-symlink expected symlink fallback")
|
||||
|
||||
# rename-exdev passthrough => EXDEV in cross-device path preserving case.
|
||||
src_ren = src + ".ren"
|
||||
touch(src_ren, b"src")
|
||||
mergerfs_set_option(mount, "rename-exdev", "passthrough")
|
||||
err = get_errno(lambda: os.rename(src_ren, dst + ".ren.pass"))
|
||||
if err not in (0, errno.EXDEV):
|
||||
return fail(f"rename-exdev=passthrough unexpected errno={err}")
|
||||
rename_exdev_active = (err == errno.EXDEV)
|
||||
# rename-exdev passthrough => EXDEV in cross-device path preserving case.
|
||||
src_ren = src + ".ren"
|
||||
touch(src_ren, b"src")
|
||||
mergerfs_set_option(mount, "rename-exdev", "passthrough")
|
||||
err = get_errno(lambda: os.rename(src_ren, dst + ".ren.pass"))
|
||||
if err not in (0, errno.EXDEV):
|
||||
return fail(f"rename-exdev=passthrough unexpected errno={err}")
|
||||
rename_exdev_active = (err == errno.EXDEV)
|
||||
|
||||
# rel-symlink should avoid EXDEV by fallback when applicable.
|
||||
src_ren2 = src + ".ren2"
|
||||
touch(src_ren2, b"src")
|
||||
mergerfs_set_option(mount, "rename-exdev", "rel-symlink")
|
||||
rel_ren = dst + ".ren.rel"
|
||||
err = get_errno(lambda: os.rename(src_ren2, rel_ren))
|
||||
if rename_exdev_active and err == errno.EXDEV:
|
||||
return fail("rename-exdev=rel-symlink still returned EXDEV")
|
||||
if rename_exdev_active and err == 0 and not os.path.islink(rel_ren):
|
||||
return fail("rename-exdev=rel-symlink expected symlink fallback")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
finally:
|
||||
try:
|
||||
mergerfs_set_option(mount, "category.create", orig_create)
|
||||
mergerfs_set_option(mount, "link-exdev", orig_link)
|
||||
mergerfs_set_option(mount, "rename-exdev", orig_rename)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# rel-symlink should avoid EXDEV by fallback when applicable.
|
||||
src_ren2 = src + ".ren2"
|
||||
touch(src_ren2, b"src")
|
||||
mergerfs_set_option(mount, "rename-exdev", "rel-symlink")
|
||||
rel_ren = dst + ".ren.rel"
|
||||
err = get_errno(lambda: os.rename(src_ren2, rel_ren))
|
||||
if rename_exdev_active and err == errno.EXDEV:
|
||||
return fail("rename-exdev=rel-symlink still returned EXDEV")
|
||||
if rename_exdev_active and err == 0 and not os.path.islink(rel_ren):
|
||||
return fail("rename-exdev=rel-symlink expected symlink fallback")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
finally:
|
||||
try:
|
||||
mergerfs_set_option(mount, "category.create", orig_create)
|
||||
mergerfs_set_option(mount, "link-exdev", orig_link)
|
||||
mergerfs_set_option(mount, "rename-exdev", orig_rename)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+37
-43
@@ -5,11 +5,13 @@ 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_get_option
|
||||
from posix_parity import mergerfs_set_option
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
from posix_parity import join
|
||||
|
||||
|
||||
def get_errno(func):
|
||||
@@ -26,56 +28,48 @@ def main():
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
path = join(mount, "cfg-xattr-modes/file")
|
||||
xname = "user.mergerfs.cfg.test"
|
||||
xval = b"v"
|
||||
|
||||
touch(path, b"x")
|
||||
merge_base = temp_dir(mount)
|
||||
|
||||
try:
|
||||
orig = mergerfs_get_option(mount, "xattr")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
path = join(merge_base, "file")
|
||||
xname = "user.mergerfs.cfg.test"
|
||||
xval = b"v"
|
||||
|
||||
try:
|
||||
mergerfs_set_option(mount, "xattr", "passthrough")
|
||||
set_err = get_errno(lambda: os.setxattr(path, xname, xval))
|
||||
get_err = get_errno(lambda: os.getxattr(path, xname))
|
||||
if set_err != 0:
|
||||
return fail(f"xattr=passthrough setxattr expected success got errno={set_err}")
|
||||
# Current known bug: getxattr may return ENODATA. We still enforce mode switch behavior.
|
||||
if get_err not in (0, errno.ENODATA):
|
||||
return fail(f"xattr=passthrough getxattr unexpected errno={get_err}")
|
||||
touch(path, b"x")
|
||||
|
||||
mergerfs_set_option(mount, "xattr", "noattr")
|
||||
err = get_errno(lambda: os.getxattr(path, xname))
|
||||
if err != errno.ENODATA:
|
||||
return fail(f"xattr=noattr getxattr expected ENODATA got errno={err}")
|
||||
err = get_errno(lambda: os.setxattr(path, xname, xval))
|
||||
if err != errno.ENODATA:
|
||||
return fail(f"xattr=noattr setxattr expected ENODATA got errno={err}")
|
||||
try:
|
||||
orig = mergerfs_get_option(mount, "xattr")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
|
||||
mergerfs_set_option(mount, "xattr", "nosys")
|
||||
err = get_errno(lambda: os.getxattr(path, xname))
|
||||
if err != errno.ENOSYS:
|
||||
return fail(f"xattr=nosys getxattr expected ENOSYS got errno={err}")
|
||||
err = get_errno(lambda: os.listxattr(path))
|
||||
if err != errno.ENOSYS:
|
||||
return fail(f"xattr=nosys listxattr expected ENOSYS got errno={err}")
|
||||
try:
|
||||
mergerfs_set_option(mount, "xattr", "passthrough")
|
||||
set_err = get_errno(lambda: os.setxattr(path, xname, xval))
|
||||
get_err = get_errno(lambda: os.getxattr(path, xname))
|
||||
if set_err != 0:
|
||||
return fail(f"xattr=passthrough setxattr expected success got errno={set_err}")
|
||||
# Current known bug: getxattr may return ENODATA. We still enforce mode switch behavior.
|
||||
if get_err not in (0, errno.ENODATA):
|
||||
return fail(f"xattr=passthrough getxattr unexpected errno={get_err}")
|
||||
|
||||
mergerfs_set_option(mount, "xattr", "noattr")
|
||||
err = get_errno(lambda: os.getxattr(path, xname))
|
||||
if err != errno.ENODATA:
|
||||
return fail(f"xattr=noattr getxattr expected ENODATA got errno={err}")
|
||||
err = get_errno(lambda: os.setxattr(path, xname, xval))
|
||||
if err != errno.ENODATA:
|
||||
return fail(f"xattr=noattr setxattr expected ENODATA got errno={err}")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
finally:
|
||||
try:
|
||||
mergerfs_set_option(mount, "xattr", orig)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# In nosys mode runtime control should stop working.
|
||||
err = get_errno(lambda: mergerfs_get_option(mount, "xattr"))
|
||||
if err != errno.ENOSYS:
|
||||
return fail(f"xattr=nosys runtime control expected ENOSYS got errno={err}")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
finally:
|
||||
try:
|
||||
mergerfs_set_option(mount, "xattr", orig)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import errno
|
||||
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_set_option
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def get_errno(func):
|
||||
try:
|
||||
func()
|
||||
return 0
|
||||
except OSError as exc:
|
||||
return exc.errno
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_movefile_enospc <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
orig_moveonenospc = mergerfs_get_option(mount, "moveonenospc")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
|
||||
branches = mergerfs_branches(mount)
|
||||
if len(branches) < 2:
|
||||
return 0
|
||||
|
||||
merge_base = temp_dir(mount)
|
||||
|
||||
try:
|
||||
mergerfs_set_option(mount, "moveonenospc", "true")
|
||||
mergerfs_set_option(mount, "func.moveonenospc", "mfs")
|
||||
|
||||
merge_file = join(merge_base, "enospc_test")
|
||||
touch(merge_file, b"test data for enospc")
|
||||
|
||||
mergerfs_set_option(mount, "moveonenospc", orig_moveonenospc)
|
||||
|
||||
return 0
|
||||
except Exception as exc:
|
||||
try:
|
||||
mergerfs_set_option(mount, "moveonenospc", orig_moveonenospc)
|
||||
except Exception:
|
||||
pass
|
||||
return fail(str(exc))
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Executable
+74
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
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 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]
|
||||
merge_base = temp_dir(mount)
|
||||
|
||||
try:
|
||||
merge_file = join(merge_base, "testfile")
|
||||
touch(merge_file, b"hello world")
|
||||
|
||||
fd = os.open(merge_file, os.O_RDWR)
|
||||
if fd < 0:
|
||||
return fail("open file for R/W failed")
|
||||
|
||||
data = os.read(fd, 4096)
|
||||
if data != b"hello world":
|
||||
os.close(fd)
|
||||
return fail(f"read before unlink: expected 'hello world', got {data!r}")
|
||||
|
||||
os.unlink(merge_file)
|
||||
|
||||
fd2 = os.dup(fd)
|
||||
os.lseek(fd2, 0, os.SEEK_SET)
|
||||
data2 = os.read(fd2, 4096)
|
||||
if data2 != b"hello world":
|
||||
os.close(fd2)
|
||||
os.close(fd)
|
||||
return fail(f"read after unlink via dup: expected 'hello world', got {data2!r}")
|
||||
|
||||
os.lseek(fd, 0, os.SEEK_SET)
|
||||
written = os.write(fd, b"modified")
|
||||
if written != len(b"modified"):
|
||||
os.close(fd2)
|
||||
os.close(fd)
|
||||
return fail(f"write after unlink: wrote {written}, expected {len(b'modified')}")
|
||||
|
||||
os.lseek(fd, 0, os.SEEK_SET)
|
||||
data3 = os.read(fd, 4096)
|
||||
if not data3.startswith(b"modified"):
|
||||
os.close(fd2)
|
||||
os.close(fd)
|
||||
return fail(f"read back written data: got {data3!r} after writing 'modified'")
|
||||
|
||||
os.close(fd2)
|
||||
os.close(fd)
|
||||
|
||||
return 0
|
||||
except Exception as exc:
|
||||
return fail(str(exc))
|
||||
finally:
|
||||
try:
|
||||
os.unlink(join(merge_base, "testfile"))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
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_set_option
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_policy_lup <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
|
||||
try:
|
||||
orig_create = mergerfs_get_option(mount, "func.create")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
|
||||
try:
|
||||
orig_search = mergerfs_get_option(mount, "func.search")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
|
||||
try:
|
||||
orig_action = mergerfs_get_option(mount, "func.action")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
|
||||
merge_base = temp_dir(mount)
|
||||
|
||||
try:
|
||||
mergerfs_set_option(mount, "func.create", "lup")
|
||||
mergerfs_set_option(mount, "func.search", "lup")
|
||||
|
||||
path = join(merge_base, "lup_test_file")
|
||||
touch(path, b"lup create test")
|
||||
if not os.path.exists(path):
|
||||
return fail("LUP create: file not created")
|
||||
|
||||
fullpath = os.getxattr(path, "user.mergerfs.fullpath")
|
||||
if not fullpath:
|
||||
return fail("LUP create: no fullpath attribute")
|
||||
|
||||
path2 = join(merge_base, "lup_test_file2")
|
||||
touch(path2, b"lup search test")
|
||||
fullpath2 = os.getxattr(path2, "user.mergerfs.fullpath")
|
||||
if not fullpath2:
|
||||
return fail("LUP search: no fullpath attribute")
|
||||
|
||||
mergerfs_set_option(mount, "func.create", orig_create)
|
||||
mergerfs_set_option(mount, "func.search", orig_search)
|
||||
|
||||
return 0
|
||||
except Exception as exc:
|
||||
mergerfs_set_option(mount, "func.create", orig_create)
|
||||
mergerfs_set_option(mount, "func.search", orig_search)
|
||||
return fail(str(exc))
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+43
-34
@@ -5,10 +5,12 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -20,48 +22,55 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-access/file")
|
||||
native_file = join(native, "posix-access/file")
|
||||
merge_notdir = join(mount, "posix-access/notdir")
|
||||
native_notdir = join(native, "posix-access/notdir")
|
||||
merge_missing = join(mount, "posix-access/missing")
|
||||
native_missing = join(native, "posix-access/missing")
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
cleanup_paths([merge_file, merge_notdir])
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
merge_notdir = join(merge_base, "notdir")
|
||||
native_notdir = join(native_base, "notdir")
|
||||
merge_missing = join(merge_base, "missing")
|
||||
native_missing = join(native_base, "missing")
|
||||
|
||||
touch(merge_file, b"ok", 0o644)
|
||||
touch(native_file, b"ok", 0o644)
|
||||
touch(merge_notdir, b"x", 0o644)
|
||||
touch(native_notdir, b"x", 0o644)
|
||||
cleanup_paths([merge_file, merge_notdir])
|
||||
|
||||
err = compare_access("F_OK existing", merge_file, native_file, os.F_OK)
|
||||
if err:
|
||||
return fail(err)
|
||||
touch(merge_file, b"ok", 0o644)
|
||||
touch(native_file, b"ok", 0o644)
|
||||
touch(merge_notdir, b"x", 0o644)
|
||||
touch(native_notdir, b"x", 0o644)
|
||||
|
||||
err = compare_access(
|
||||
"F_OK missing", merge_missing, native_missing, os.F_OK, errno.ENOENT
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_access("F_OK existing", merge_file, native_file, os.F_OK)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_access(
|
||||
"X_OK non-directory prefix",
|
||||
join(merge_notdir, "child"),
|
||||
join(native_notdir, "child"),
|
||||
os.X_OK,
|
||||
errno.ENOTDIR,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_access(
|
||||
"F_OK missing", merge_missing, native_missing, os.F_OK, errno.ENOENT
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
os.chmod(merge_file, 0)
|
||||
os.chmod(native_file, 0)
|
||||
err = compare_access(
|
||||
"X_OK non-directory prefix",
|
||||
join(merge_notdir, "child"),
|
||||
join(native_notdir, "child"),
|
||||
os.X_OK,
|
||||
errno.ENOTDIR,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_access("R_OK unreadable", merge_file, native_file, os.R_OK)
|
||||
if err:
|
||||
return fail(err)
|
||||
os.chmod(merge_file, 0)
|
||||
os.chmod(native_file, 0)
|
||||
|
||||
return 0
|
||||
err = compare_access("R_OK unreadable", merge_file, native_file, os.R_OK)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+33
-24
@@ -7,9 +7,11 @@ import struct
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -30,38 +32,45 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-bmap/file")
|
||||
native_file = join(native, "posix-bmap/file")
|
||||
touch(merge_file, b"x" * 8192)
|
||||
touch(native_file, b"x" * 8192)
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDONLY)
|
||||
nfd = os.open(native_file, os.O_RDONLY)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
# FIBMAP often requires CAP_SYS_RAWIO; compare errno parity if denied.
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
touch(merge_file, b"x" * 8192)
|
||||
touch(native_file, b"x" * 8192)
|
||||
|
||||
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),
|
||||
lambda: fibmap(nfd, 0),
|
||||
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 block0",
|
||||
"ioctl FIBMAP EBADF",
|
||||
lambda: fibmap(mfd, 0),
|
||||
lambda: fibmap(nfd, 0),
|
||||
lambda a, b: (a >= 0) == (b >= 0),
|
||||
)
|
||||
if err:
|
||||
# If both denied by privilege checks, compare_calls already passes.
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
# EBADF parity on closed fds.
|
||||
err = compare_calls(
|
||||
"ioctl FIBMAP EBADF",
|
||||
lambda: fibmap(mfd, 0),
|
||||
lambda: fibmap(nfd, 0),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+59
-50
@@ -6,12 +6,14 @@ import stat
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
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 should_compare_inode
|
||||
from posix_parity import stat_cmp_basic
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -28,65 +30,72 @@ def main():
|
||||
stcmp = stat_cmp_basic_with_inode if should_compare_inode(mount) else stat_cmp_basic
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-chmod/file")
|
||||
native_file = join(native, "posix-chmod/file")
|
||||
merge_missing = join(mount, "posix-chmod/missing")
|
||||
native_missing = join(native, "posix-chmod/missing")
|
||||
merge_notdir = join(mount, "posix-chmod/notdir")
|
||||
native_notdir = join(native, "posix-chmod/notdir")
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
cleanup_paths([merge_file, merge_notdir])
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
merge_missing = join(merge_base, "missing")
|
||||
native_missing = join(native_base, "missing")
|
||||
merge_notdir = join(merge_base, "notdir")
|
||||
native_notdir = join(native_base, "notdir")
|
||||
|
||||
touch(merge_file, b"x", 0o644)
|
||||
touch(native_file, b"x", 0o644)
|
||||
touch(merge_notdir, b"x", 0o644)
|
||||
touch(native_notdir, b"x", 0o644)
|
||||
cleanup_paths([merge_file, merge_notdir])
|
||||
|
||||
err = compare_calls(
|
||||
"chmod success",
|
||||
lambda: os.chmod(merge_file, 0o600),
|
||||
lambda: os.chmod(native_file, 0o600),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
touch(merge_file, b"x", 0o644)
|
||||
touch(native_file, b"x", 0o644)
|
||||
touch(merge_notdir, b"x", 0o644)
|
||||
touch(native_notdir, b"x", 0o644)
|
||||
|
||||
err = compare_calls(
|
||||
"chmod stat parity",
|
||||
lambda: os.lstat(merge_file),
|
||||
lambda: os.lstat(native_file),
|
||||
stcmp,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"chmod ENOENT",
|
||||
lambda: os.chmod(merge_missing, 0o600),
|
||||
lambda: os.chmod(native_missing, 0o600),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"chmod ENOTDIR",
|
||||
lambda: os.chmod(join(merge_notdir, "child"), 0o600),
|
||||
lambda: os.chmod(join(native_notdir, "child"), 0o600),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
if os.geteuid() != 0:
|
||||
err = compare_calls(
|
||||
"chmod EPERM setuid",
|
||||
lambda: os.chmod(merge_file, stat.S_ISUID | 0o644),
|
||||
lambda: os.chmod(native_file, stat.S_ISUID | 0o644),
|
||||
"chmod success",
|
||||
lambda: os.chmod(merge_file, 0o600),
|
||||
lambda: os.chmod(native_file, 0o600),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
else:
|
||||
_ = errno.EPERM
|
||||
|
||||
return 0
|
||||
err = compare_calls(
|
||||
"chmod stat parity",
|
||||
lambda: os.lstat(merge_file),
|
||||
lambda: os.lstat(native_file),
|
||||
stcmp,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"chmod ENOENT",
|
||||
lambda: os.chmod(merge_missing, 0o600),
|
||||
lambda: os.chmod(native_missing, 0o600),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"chmod ENOTDIR",
|
||||
lambda: os.chmod(join(merge_notdir, "child"), 0o600),
|
||||
lambda: os.chmod(join(native_notdir, "child"), 0o600),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
if os.geteuid() != 0:
|
||||
err = compare_calls(
|
||||
"chmod EPERM setuid",
|
||||
lambda: os.chmod(merge_file, stat.S_ISUID | 0o644),
|
||||
lambda: os.chmod(native_file, stat.S_ISUID | 0o644),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
else:
|
||||
_ = errno.EPERM
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+48
-39
@@ -4,10 +4,12 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -21,54 +23,61 @@ def main():
|
||||
gid = os.getgid()
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-chown/file")
|
||||
native_file = join(native, "posix-chown/file")
|
||||
merge_missing = join(mount, "posix-chown/missing")
|
||||
native_missing = join(native, "posix-chown/missing")
|
||||
merge_notdir = join(mount, "posix-chown/notdir")
|
||||
native_notdir = join(native, "posix-chown/notdir")
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
cleanup_paths([merge_file, merge_notdir])
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
merge_missing = join(merge_base, "missing")
|
||||
native_missing = join(native_base, "missing")
|
||||
merge_notdir = join(merge_base, "notdir")
|
||||
native_notdir = join(native_base, "notdir")
|
||||
|
||||
touch(merge_file, b"x", 0o644)
|
||||
touch(native_file, b"x", 0o644)
|
||||
touch(merge_notdir, b"x", 0o644)
|
||||
touch(native_notdir, b"x", 0o644)
|
||||
cleanup_paths([merge_file, merge_notdir])
|
||||
|
||||
err = compare_calls(
|
||||
"chown self",
|
||||
lambda: os.chown(merge_file, uid, gid),
|
||||
lambda: os.chown(native_file, uid, gid),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
touch(merge_file, b"x", 0o644)
|
||||
touch(native_file, b"x", 0o644)
|
||||
touch(merge_notdir, b"x", 0o644)
|
||||
touch(native_notdir, b"x", 0o644)
|
||||
|
||||
err = compare_calls(
|
||||
"chown ENOENT",
|
||||
lambda: os.chown(merge_missing, uid, gid),
|
||||
lambda: os.chown(native_missing, uid, gid),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"chown ENOTDIR",
|
||||
lambda: os.chown(join(merge_notdir, "child"), uid, gid),
|
||||
lambda: os.chown(join(native_notdir, "child"), uid, gid),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
if os.geteuid() != 0:
|
||||
err = compare_calls(
|
||||
"chown EPERM foreign uid",
|
||||
lambda: os.chown(merge_file, 0, gid),
|
||||
lambda: os.chown(native_file, 0, gid),
|
||||
"chown self",
|
||||
lambda: os.chown(merge_file, uid, gid),
|
||||
lambda: os.chown(native_file, uid, gid),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
err = compare_calls(
|
||||
"chown ENOENT",
|
||||
lambda: os.chown(merge_missing, uid, gid),
|
||||
lambda: os.chown(native_missing, uid, gid),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"chown ENOTDIR",
|
||||
lambda: os.chown(join(merge_notdir, "child"), uid, gid),
|
||||
lambda: os.chown(join(native_notdir, "child"), uid, gid),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
if os.geteuid() != 0:
|
||||
err = compare_calls(
|
||||
"chown EPERM foreign uid",
|
||||
lambda: os.chown(merge_file, 0, gid),
|
||||
lambda: os.chown(native_file, 0, gid),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -4,9 +4,11 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -21,103 +23,110 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_src = join(mount, "posix-cfr/src")
|
||||
merge_dst = join(mount, "posix-cfr/dst")
|
||||
native_src = join(native, "posix-cfr/src")
|
||||
native_dst = join(native, "posix-cfr/dst")
|
||||
|
||||
payload = b"0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
touch(merge_src, payload)
|
||||
touch(native_src, payload)
|
||||
touch(merge_dst, b"")
|
||||
touch(native_dst, b"")
|
||||
|
||||
msfd = os.open(merge_src, os.O_RDONLY)
|
||||
mdfd = os.open(merge_dst, os.O_WRONLY)
|
||||
nsfd = os.open(native_src, os.O_RDONLY)
|
||||
ndfd = os.open(native_dst, os.O_WRONLY)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
err = compare_calls(
|
||||
"copy_file_range success",
|
||||
lambda: os.copy_file_range(msfd, mdfd, 16),
|
||||
lambda: os.copy_file_range(nsfd, ndfd, 16),
|
||||
lambda a, b: a == b,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(msfd)
|
||||
os.close(mdfd)
|
||||
os.close(nsfd)
|
||||
os.close(ndfd)
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
with open(merge_dst, "rb") as mf, open(native_dst, "rb") as nf:
|
||||
mdata = mf.read()
|
||||
ndata = nf.read()
|
||||
if mdata != ndata:
|
||||
return fail(f"copy_file_range dst mismatch mergerfs={mdata!r} native={ndata!r}")
|
||||
merge_src = join(merge_base, "src")
|
||||
merge_dst = join(merge_base, "dst")
|
||||
native_src = join(native_base, "src")
|
||||
native_dst = join(native_base, "dst")
|
||||
|
||||
mdfd = os.open(merge_dst, os.O_WRONLY)
|
||||
ndfd = os.open(native_dst, os.O_WRONLY)
|
||||
try:
|
||||
err = compare_calls(
|
||||
"copy_file_range EBADF src",
|
||||
lambda: os.copy_file_range(-1, mdfd, 1),
|
||||
lambda: os.copy_file_range(-1, ndfd, 1),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(mdfd)
|
||||
os.close(ndfd)
|
||||
payload = b"0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
touch(merge_src, payload)
|
||||
touch(native_src, payload)
|
||||
touch(merge_dst, b"")
|
||||
touch(native_dst, b"")
|
||||
|
||||
# Exercise very large copy lengths to validate the 64-bit copy result
|
||||
# path when available. Use sparse files to keep runtime and disk usage low.
|
||||
large_len = (5 << 30) + 4096
|
||||
merge_large_src = join(mount, "posix-cfr/src-large")
|
||||
merge_large_dst = join(mount, "posix-cfr/dst-large")
|
||||
native_large_src = join(native, "posix-cfr/src-large")
|
||||
native_large_dst = join(native, "posix-cfr/dst-large")
|
||||
touch(merge_large_src, b"")
|
||||
touch(merge_large_dst, b"")
|
||||
touch(native_large_src, b"")
|
||||
touch(native_large_dst, b"")
|
||||
|
||||
msfd = os.open(merge_large_src, os.O_RDWR)
|
||||
mdfd = os.open(merge_large_dst, os.O_RDWR)
|
||||
nsfd = os.open(native_large_src, os.O_RDWR)
|
||||
ndfd = os.open(native_large_dst, os.O_RDWR)
|
||||
try:
|
||||
# Make sparse files with one non-zero byte at the end so both sides
|
||||
# have comparable logical contents.
|
||||
os.ftruncate(msfd, large_len)
|
||||
os.ftruncate(nsfd, large_len)
|
||||
os.pwrite(msfd, b"z", large_len - 1)
|
||||
os.pwrite(nsfd, b"z", large_len - 1)
|
||||
|
||||
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)
|
||||
|
||||
mstat = os.fstat(mdfd)
|
||||
nstat = os.fstat(ndfd)
|
||||
if mstat.st_size != nstat.st_size:
|
||||
return fail(
|
||||
"copy_file_range large sparse dst size mismatch "
|
||||
f"mergerfs={mstat.st_size} native={nstat.st_size}"
|
||||
msfd = os.open(merge_src, os.O_RDONLY)
|
||||
mdfd = os.open(merge_dst, os.O_WRONLY)
|
||||
nsfd = os.open(native_src, os.O_RDONLY)
|
||||
ndfd = os.open(native_dst, os.O_WRONLY)
|
||||
try:
|
||||
err = compare_calls(
|
||||
"copy_file_range success",
|
||||
lambda: os.copy_file_range(msfd, mdfd, 16),
|
||||
lambda: os.copy_file_range(nsfd, ndfd, 16),
|
||||
lambda a, b: a == b,
|
||||
)
|
||||
finally:
|
||||
os.close(msfd)
|
||||
os.close(mdfd)
|
||||
os.close(nsfd)
|
||||
os.close(ndfd)
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(msfd)
|
||||
os.close(mdfd)
|
||||
os.close(nsfd)
|
||||
os.close(ndfd)
|
||||
|
||||
return 0
|
||||
with open(merge_dst, "rb") as mf, open(native_dst, "rb") as nf:
|
||||
mdata = mf.read()
|
||||
ndata = nf.read()
|
||||
if mdata != ndata:
|
||||
return fail(f"copy_file_range dst mismatch mergerfs={mdata!r} native={ndata!r}")
|
||||
|
||||
mdfd = os.open(merge_dst, os.O_WRONLY)
|
||||
ndfd = os.open(native_dst, os.O_WRONLY)
|
||||
try:
|
||||
err = compare_calls(
|
||||
"copy_file_range EBADF src",
|
||||
lambda: os.copy_file_range(-1, mdfd, 1),
|
||||
lambda: os.copy_file_range(-1, ndfd, 1),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
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 runtime and disk usage low.
|
||||
large_len = (5 << 30) + 4096
|
||||
merge_large_src = join(merge_base, "src-large")
|
||||
merge_large_dst = join(merge_base, "dst-large")
|
||||
native_large_src = join(native_base, "src-large")
|
||||
native_large_dst = join(native_base, "dst-large")
|
||||
touch(merge_large_src, b"")
|
||||
touch(merge_large_dst, b"")
|
||||
touch(native_large_src, b"")
|
||||
touch(native_large_dst, b"")
|
||||
|
||||
msfd = os.open(merge_large_src, os.O_RDWR)
|
||||
mdfd = os.open(merge_large_dst, os.O_RDWR)
|
||||
nsfd = os.open(native_large_src, os.O_RDWR)
|
||||
ndfd = os.open(native_large_dst, os.O_RDWR)
|
||||
try:
|
||||
# Make sparse files with one non-zero byte at the end so both sides
|
||||
# have comparable logical contents.
|
||||
os.ftruncate(msfd, large_len)
|
||||
os.ftruncate(nsfd, large_len)
|
||||
os.pwrite(msfd, b"z", large_len - 1)
|
||||
os.pwrite(nsfd, b"z", large_len - 1)
|
||||
|
||||
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)
|
||||
|
||||
mstat = os.fstat(mdfd)
|
||||
nstat = os.fstat(ndfd)
|
||||
if mstat.st_size != nstat.st_size:
|
||||
return fail(
|
||||
"copy_file_range large sparse dst size mismatch "
|
||||
f"mergerfs={mstat.st_size} native={nstat.st_size}"
|
||||
)
|
||||
finally:
|
||||
os.close(msfd)
|
||||
os.close(mdfd)
|
||||
os.close(nsfd)
|
||||
os.close(ndfd)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5,10 +5,12 @@ import stat
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
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 temp_dir
|
||||
|
||||
|
||||
def main():
|
||||
@@ -19,74 +21,78 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
os.makedirs(join(mount, "posix-create"), exist_ok=True)
|
||||
os.makedirs(join(native, "posix-create"), exist_ok=True)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
merge_file = join(mount, "posix-create/file")
|
||||
native_file = join(native, "posix-create/file")
|
||||
merge_exist = join(mount, "posix-create/exist")
|
||||
native_exist = join(native, "posix-create/exist")
|
||||
merge_notdir = join(mount, "posix-create/notdir")
|
||||
native_notdir = join(native, "posix-create/notdir")
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
merge_exist = join(merge_base, "exist")
|
||||
native_exist = join(native_base, "exist")
|
||||
merge_notdir = join(merge_base, "notdir")
|
||||
native_notdir = join(native_base, "notdir")
|
||||
|
||||
cleanup_paths([merge_file, merge_exist, merge_notdir])
|
||||
cleanup_paths([merge_file, merge_exist, merge_notdir])
|
||||
|
||||
err = compare_calls(
|
||||
"create success",
|
||||
lambda: os.open(merge_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o640),
|
||||
lambda: os.open(native_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o640),
|
||||
close_fds=True,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"create success",
|
||||
lambda: os.open(merge_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o640),
|
||||
lambda: os.open(native_file, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o640),
|
||||
close_fds=True,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
with open(merge_exist, "wb"):
|
||||
pass
|
||||
with open(native_exist, "wb"):
|
||||
pass
|
||||
with open(merge_exist, "wb"):
|
||||
pass
|
||||
with open(native_exist, "wb"):
|
||||
pass
|
||||
|
||||
err = compare_calls(
|
||||
"create EEXIST",
|
||||
lambda: os.open(merge_exist, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o640),
|
||||
lambda: os.open(native_exist, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o640),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"create EEXIST",
|
||||
lambda: os.open(merge_exist, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o640),
|
||||
lambda: os.open(native_exist, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o640),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
with open(merge_notdir, "wb"):
|
||||
pass
|
||||
with open(native_notdir, "wb"):
|
||||
pass
|
||||
with open(merge_notdir, "wb"):
|
||||
pass
|
||||
with open(native_notdir, "wb"):
|
||||
pass
|
||||
|
||||
err = compare_calls(
|
||||
"create ENOTDIR",
|
||||
lambda: os.open(join(merge_notdir, "child"), os.O_CREAT | os.O_WRONLY, 0o640),
|
||||
lambda: os.open(join(native_notdir, "child"), os.O_CREAT | os.O_WRONLY, 0o640),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"create ENOTDIR",
|
||||
lambda: os.open(join(merge_notdir, "child"), os.O_CREAT | os.O_WRONLY, 0o640),
|
||||
lambda: os.open(join(native_notdir, "child"), os.O_CREAT | os.O_WRONLY, 0o640),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
merge_node = join(mount, "posix-create/mknod")
|
||||
native_node = join(native, "posix-create/mknod")
|
||||
cleanup_paths([merge_node])
|
||||
merge_node = join(merge_base, "mknod")
|
||||
native_node = join(native_base, "mknod")
|
||||
cleanup_paths([merge_node])
|
||||
|
||||
err = compare_calls(
|
||||
"mknod regular",
|
||||
lambda: os.mknod(merge_node, stat.S_IFREG | 0o600, 0),
|
||||
lambda: os.mknod(native_node, stat.S_IFREG | 0o600, 0),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"mknod regular",
|
||||
lambda: os.mknod(merge_node, stat.S_IFREG | 0o600, 0),
|
||||
lambda: os.mknod(native_node, stat.S_IFREG | 0o600, 0),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"mknod EEXIST",
|
||||
lambda: os.mknod(merge_node, stat.S_IFREG | 0o600, 0),
|
||||
lambda: os.mknod(native_node, stat.S_IFREG | 0o600, 0),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"mknod EEXIST",
|
||||
lambda: os.mknod(merge_node, stat.S_IFREG | 0o600, 0),
|
||||
lambda: os.mknod(native_node, stat.S_IFREG | 0o600, 0),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -4,9 +4,11 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -18,57 +20,64 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-fsync/file")
|
||||
native_file = join(native, "posix-fsync/file")
|
||||
merge_dir = join(mount, "posix-fsync/dir")
|
||||
native_dir = join(native, "posix-fsync/dir")
|
||||
|
||||
touch(merge_file, b"abc")
|
||||
touch(native_file, b"abc")
|
||||
os.makedirs(merge_dir, exist_ok=True)
|
||||
os.makedirs(native_dir, exist_ok=True)
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDWR)
|
||||
nfd = os.open(native_file, os.O_RDWR)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
os.lseek(mfd, 0, os.SEEK_END)
|
||||
os.lseek(nfd, 0, os.SEEK_END)
|
||||
os.write(mfd, b"-merge")
|
||||
os.write(nfd, b"-native")
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
err = compare_calls("fsync file", lambda: os.fsync(mfd), lambda: os.fsync(nfd))
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
merge_dir = join(merge_base, "dir")
|
||||
native_dir = join(native_base, "dir")
|
||||
|
||||
touch(merge_file, b"abc")
|
||||
touch(native_file, b"abc")
|
||||
os.makedirs(merge_dir, exist_ok=True)
|
||||
os.makedirs(native_dir, exist_ok=True)
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDWR)
|
||||
nfd = os.open(native_file, os.O_RDWR)
|
||||
try:
|
||||
os.lseek(mfd, 0, os.SEEK_END)
|
||||
os.lseek(nfd, 0, os.SEEK_END)
|
||||
os.write(mfd, b"-merge")
|
||||
os.write(nfd, b"-native")
|
||||
|
||||
err = compare_calls("fsync file", lambda: os.fsync(mfd), lambda: os.fsync(nfd))
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
mdirfd = os.open(merge_dir, os.O_RDONLY | os.O_DIRECTORY)
|
||||
ndirfd = os.open(native_dir, os.O_RDONLY | os.O_DIRECTORY)
|
||||
try:
|
||||
err = compare_calls("fsync dir", lambda: os.fsync(mdirfd), lambda: os.fsync(ndirfd))
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(mdirfd)
|
||||
os.close(ndirfd)
|
||||
|
||||
bad_m = os.open(merge_file, os.O_RDONLY)
|
||||
bad_n = os.open(native_file, os.O_RDONLY)
|
||||
os.close(bad_m)
|
||||
os.close(bad_n)
|
||||
err = compare_calls("fsync EBADF", lambda: os.fsync(bad_m), lambda: os.fsync(bad_n))
|
||||
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()
|
||||
if not (mdata.startswith(b"abc") and ndata.startswith(b"abc")):
|
||||
return fail(f"flush content prefix mismatch mergerfs={mdata!r} native={ndata!r}")
|
||||
|
||||
return 0
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
mdirfd = os.open(merge_dir, os.O_RDONLY | os.O_DIRECTORY)
|
||||
ndirfd = os.open(native_dir, os.O_RDONLY | os.O_DIRECTORY)
|
||||
try:
|
||||
err = compare_calls("fsync dir", lambda: os.fsync(mdirfd), lambda: os.fsync(ndirfd))
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(mdirfd)
|
||||
os.close(ndirfd)
|
||||
|
||||
bad_m = os.open(merge_file, os.O_RDONLY)
|
||||
bad_n = os.open(native_file, os.O_RDONLY)
|
||||
os.close(bad_m)
|
||||
os.close(bad_n)
|
||||
err = compare_calls("fsync EBADF", lambda: os.fsync(bad_m), lambda: os.fsync(bad_n))
|
||||
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()
|
||||
if not (mdata.startswith(b"abc") and ndata.startswith(b"abc")):
|
||||
return fail(f"flush content prefix mismatch mergerfs={mdata!r} native={ndata!r}")
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5,12 +5,14 @@ import stat
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
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 should_compare_inode
|
||||
from posix_parity import stat_cmp_basic
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -35,59 +37,66 @@ def main():
|
||||
stcmp = st_cmp_with_inode if should_compare_inode(mount) else st_cmp
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-getattr/file")
|
||||
native_file = join(native, "posix-getattr/file")
|
||||
merge_dir = join(mount, "posix-getattr/dir")
|
||||
native_dir = join(native, "posix-getattr/dir")
|
||||
merge_missing = join(mount, "posix-getattr/missing")
|
||||
native_missing = join(native, "posix-getattr/missing")
|
||||
merge_notdir = join(mount, "posix-getattr/notdir")
|
||||
native_notdir = join(native, "posix-getattr/notdir")
|
||||
|
||||
cleanup_paths([merge_file, merge_dir, merge_notdir])
|
||||
|
||||
touch(merge_file, b"abc", 0o640)
|
||||
touch(native_file, b"abc", 0o640)
|
||||
os.makedirs(merge_dir, exist_ok=True)
|
||||
os.makedirs(native_dir, exist_ok=True)
|
||||
touch(merge_notdir, b"x", 0o644)
|
||||
touch(native_notdir, b"x", 0o644)
|
||||
|
||||
err = compare_calls(
|
||||
"lstat regular", lambda: os.lstat(merge_file), lambda: os.lstat(native_file), stcmp
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"lstat directory", lambda: os.lstat(merge_dir), lambda: os.lstat(native_dir), stcmp
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDONLY)
|
||||
nfd = os.open(native_file, os.O_RDONLY)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
err = compare_calls("fstat open fd", lambda: os.fstat(mfd), lambda: os.fstat(nfd), stcmp)
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
merge_dir = join(merge_base, "dir")
|
||||
native_dir = join(native_base, "dir")
|
||||
merge_missing = join(merge_base, "missing")
|
||||
native_missing = join(native_base, "missing")
|
||||
merge_notdir = join(merge_base, "notdir")
|
||||
native_notdir = join(native_base, "notdir")
|
||||
|
||||
cleanup_paths([merge_file, merge_dir, merge_notdir])
|
||||
|
||||
touch(merge_file, b"abc", 0o640)
|
||||
touch(native_file, b"abc", 0o640)
|
||||
os.makedirs(merge_dir, exist_ok=True)
|
||||
os.makedirs(native_dir, exist_ok=True)
|
||||
touch(merge_notdir, b"x", 0o644)
|
||||
touch(native_notdir, b"x", 0o644)
|
||||
|
||||
err = compare_calls(
|
||||
"lstat regular", lambda: os.lstat(merge_file), lambda: os.lstat(native_file), stcmp
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"lstat directory", lambda: os.lstat(merge_dir), lambda: os.lstat(native_dir), stcmp
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
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)
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
err = compare_calls("lstat ENOENT", lambda: os.lstat(merge_missing), lambda: os.lstat(native_missing))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"lstat ENOTDIR",
|
||||
lambda: os.lstat(join(merge_notdir, "child")),
|
||||
lambda: os.lstat(join(native_notdir, "child")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
err = compare_calls("lstat ENOENT", lambda: os.lstat(merge_missing), lambda: os.lstat(native_missing))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"lstat ENOTDIR",
|
||||
lambda: os.lstat(join(merge_notdir, "child")),
|
||||
lambda: os.lstat(join(native_notdir, "child")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+33
-24
@@ -7,9 +7,11 @@ import termios
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -27,33 +29,40 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-ioctl/file")
|
||||
native_file = join(native, "posix-ioctl/file")
|
||||
touch(merge_file, b"hello-world")
|
||||
touch(native_file, b"hello-world")
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDONLY)
|
||||
nfd = os.open(native_file, os.O_RDONLY)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
err = compare_calls("ioctl FIONREAD", lambda: fionread(mfd), lambda: fionread(nfd), lambda a, b: a == b)
|
||||
if err:
|
||||
return fail(err)
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
os.lseek(mfd, 5, os.SEEK_SET)
|
||||
os.lseek(nfd, 5, os.SEEK_SET)
|
||||
err = compare_calls(
|
||||
"ioctl FIONREAD after seek",
|
||||
lambda: fionread(mfd),
|
||||
lambda: fionread(nfd),
|
||||
lambda a, b: a == b,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
touch(merge_file, b"hello-world")
|
||||
touch(native_file, b"hello-world")
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDONLY)
|
||||
nfd = os.open(native_file, os.O_RDONLY)
|
||||
try:
|
||||
err = compare_calls("ioctl FIONREAD", lambda: fionread(mfd), lambda: fionread(nfd), lambda a, b: a == b)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
os.lseek(mfd, 5, os.SEEK_SET)
|
||||
os.lseek(nfd, 5, os.SEEK_SET)
|
||||
err = compare_calls(
|
||||
"ioctl FIONREAD after seek",
|
||||
lambda: fionread(mfd),
|
||||
lambda: fionread(nfd),
|
||||
lambda a, b: a == b,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -4,10 +4,12 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -19,48 +21,55 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_src = join(mount, "posix-link/src")
|
||||
native_src = join(native, "posix-link/src")
|
||||
merge_dst = join(mount, "posix-link/dst")
|
||||
native_dst = join(native, "posix-link/dst")
|
||||
merge_slnk = join(mount, "posix-link/symlink")
|
||||
native_slnk = join(native, "posix-link/symlink")
|
||||
merge_missing = join(mount, "posix-link/missing")
|
||||
native_missing = join(native, "posix-link/missing")
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
cleanup_paths([merge_src, merge_dst, merge_slnk])
|
||||
touch(merge_src, b"src", 0o644)
|
||||
touch(native_src, b"src", 0o644)
|
||||
merge_src = join(merge_base, "src")
|
||||
native_src = join(native_base, "src")
|
||||
merge_dst = join(merge_base, "dst")
|
||||
native_dst = join(native_base, "dst")
|
||||
merge_slnk = join(merge_base, "symlink")
|
||||
native_slnk = join(native_base, "symlink")
|
||||
merge_missing = join(merge_base, "missing")
|
||||
native_missing = join(native_base, "missing")
|
||||
|
||||
err = compare_calls("link success", lambda: os.link(merge_src, merge_dst), lambda: os.link(native_src, native_dst))
|
||||
if err:
|
||||
return fail(err)
|
||||
cleanup_paths([merge_src, merge_dst, merge_slnk])
|
||||
touch(merge_src, b"src", 0o644)
|
||||
touch(native_src, b"src", 0o644)
|
||||
|
||||
err = compare_calls(
|
||||
"link ENOENT source",
|
||||
lambda: os.link(merge_missing, join(mount, "posix-link/dst2")),
|
||||
lambda: os.link(native_missing, join(native, "posix-link/dst2")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls("link success", lambda: os.link(merge_src, merge_dst), lambda: os.link(native_src, native_dst))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"symlink success",
|
||||
lambda: os.symlink("src", merge_slnk),
|
||||
lambda: os.symlink("src", native_slnk),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"link ENOENT source",
|
||||
lambda: os.link(merge_missing, join(merge_base, "dst2")),
|
||||
lambda: os.link(native_missing, join(native_base, "dst2")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"symlink EEXIST",
|
||||
lambda: os.symlink("src", merge_slnk),
|
||||
lambda: os.symlink("src", native_slnk),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"symlink success",
|
||||
lambda: os.symlink("src", merge_slnk),
|
||||
lambda: os.symlink("src", native_slnk),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
err = compare_calls(
|
||||
"symlink EEXIST",
|
||||
lambda: os.symlink("src", merge_slnk),
|
||||
lambda: os.symlink("src", native_slnk),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+68
-59
@@ -6,9 +6,11 @@ import struct
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -25,74 +27,81 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-locking/file")
|
||||
native_file = join(native, "posix-locking/file")
|
||||
touch(merge_file, b"x")
|
||||
touch(native_file, b"x")
|
||||
|
||||
mfd1 = os.open(merge_file, os.O_RDWR)
|
||||
mfd2 = os.open(merge_file, os.O_RDWR)
|
||||
nfd1 = os.open(native_file, os.O_RDWR)
|
||||
nfd2 = os.open(native_file, os.O_RDWR)
|
||||
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
err = compare_calls(
|
||||
"flock EX nonblock",
|
||||
lambda: fcntl.flock(mfd1, fcntl.LOCK_EX | fcntl.LOCK_NB),
|
||||
lambda: fcntl.flock(nfd1, fcntl.LOCK_EX | fcntl.LOCK_NB),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
err = compare_calls(
|
||||
"flock SH nonblock second fd",
|
||||
lambda: fcntl.flock(mfd2, fcntl.LOCK_SH | fcntl.LOCK_NB),
|
||||
lambda: fcntl.flock(nfd2, fcntl.LOCK_SH | fcntl.LOCK_NB),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
touch(merge_file, b"x")
|
||||
touch(native_file, b"x")
|
||||
|
||||
err = compare_calls("flock unlock", lambda: fcntl.flock(mfd1, fcntl.LOCK_UN), lambda: fcntl.flock(nfd1, fcntl.LOCK_UN))
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls("flock unlock second fd", lambda: fcntl.flock(mfd2, fcntl.LOCK_UN), lambda: fcntl.flock(nfd2, fcntl.LOCK_UN))
|
||||
if err:
|
||||
return fail(err)
|
||||
mfd1 = os.open(merge_file, os.O_RDWR)
|
||||
mfd2 = os.open(merge_file, os.O_RDWR)
|
||||
nfd1 = os.open(native_file, os.O_RDWR)
|
||||
nfd2 = os.open(native_file, os.O_RDWR)
|
||||
|
||||
# POSIX record lock via fcntl
|
||||
wrlk = pack_flock(fcntl.F_WRLCK)
|
||||
unlck = pack_flock(fcntl.F_UNLCK)
|
||||
try:
|
||||
err = compare_calls(
|
||||
"flock EX nonblock",
|
||||
lambda: fcntl.flock(mfd1, fcntl.LOCK_EX | fcntl.LOCK_NB),
|
||||
lambda: fcntl.flock(nfd1, fcntl.LOCK_EX | fcntl.LOCK_NB),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls("fcntl F_SETLK write lock", lambda: fcntl.fcntl(mfd1, fcntl.F_SETLK, wrlk), lambda: fcntl.fcntl(nfd1, fcntl.F_SETLK, wrlk))
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"flock SH nonblock second fd",
|
||||
lambda: fcntl.flock(mfd2, fcntl.LOCK_SH | fcntl.LOCK_NB),
|
||||
lambda: fcntl.flock(nfd2, fcntl.LOCK_SH | fcntl.LOCK_NB),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls("fcntl F_SETLK write lock second fd", lambda: fcntl.fcntl(mfd2, fcntl.F_SETLK, wrlk), lambda: fcntl.fcntl(nfd2, fcntl.F_SETLK, wrlk))
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls("flock unlock", lambda: fcntl.flock(mfd1, fcntl.LOCK_UN), lambda: fcntl.flock(nfd1, fcntl.LOCK_UN))
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls("flock unlock second fd", lambda: fcntl.flock(mfd2, fcntl.LOCK_UN), lambda: fcntl.flock(nfd2, fcntl.LOCK_UN))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls("fcntl F_SETLK unlock", lambda: fcntl.fcntl(mfd1, fcntl.F_SETLK, unlck), lambda: fcntl.fcntl(nfd1, fcntl.F_SETLK, unlck))
|
||||
if err:
|
||||
return fail(err)
|
||||
# POSIX record lock via fcntl
|
||||
wrlk = pack_flock(fcntl.F_WRLCK)
|
||||
unlck = pack_flock(fcntl.F_UNLCK)
|
||||
|
||||
bad_m = os.open(merge_file, os.O_RDONLY)
|
||||
bad_n = os.open(native_file, os.O_RDONLY)
|
||||
os.close(bad_m)
|
||||
os.close(bad_n)
|
||||
err = compare_calls(
|
||||
"fcntl EBADF",
|
||||
lambda: fcntl.fcntl(bad_m, fcntl.F_SETLK, wrlk),
|
||||
lambda: fcntl.fcntl(bad_n, fcntl.F_SETLK, wrlk),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls("fcntl F_SETLK write lock", lambda: fcntl.fcntl(mfd1, fcntl.F_SETLK, wrlk), lambda: fcntl.fcntl(nfd1, fcntl.F_SETLK, wrlk))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls("fcntl F_SETLK write lock second fd", lambda: fcntl.fcntl(mfd2, fcntl.F_SETLK, wrlk), lambda: fcntl.fcntl(nfd2, fcntl.F_SETLK, wrlk))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls("fcntl F_SETLK unlock", lambda: fcntl.fcntl(mfd1, fcntl.F_SETLK, unlck), lambda: fcntl.fcntl(nfd1, fcntl.F_SETLK, unlck))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
bad_m = os.open(merge_file, os.O_RDONLY)
|
||||
bad_n = os.open(native_file, os.O_RDONLY)
|
||||
os.close(bad_m)
|
||||
os.close(bad_n)
|
||||
err = compare_calls(
|
||||
"fcntl EBADF",
|
||||
lambda: fcntl.fcntl(bad_m, fcntl.F_SETLK, wrlk),
|
||||
lambda: fcntl.fcntl(bad_n, fcntl.F_SETLK, wrlk),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(mfd1)
|
||||
os.close(mfd2)
|
||||
os.close(nfd1)
|
||||
os.close(nfd2)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
os.close(mfd1)
|
||||
os.close(mfd2)
|
||||
os.close(nfd1)
|
||||
os.close(nfd2)
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -4,10 +4,12 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -19,52 +21,57 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_dir = join(mount, "posix-mkdir/dir")
|
||||
native_dir = join(native, "posix-mkdir/dir")
|
||||
merge_notdir = join(mount, "posix-mkdir/notdir")
|
||||
native_notdir = join(native, "posix-mkdir/notdir")
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
cleanup_paths([merge_dir, merge_notdir])
|
||||
os.makedirs(join(mount, "posix-mkdir"), exist_ok=True)
|
||||
os.makedirs(join(native, "posix-mkdir"), exist_ok=True)
|
||||
merge_dir = join(merge_base, "dir")
|
||||
native_dir = join(native_base, "dir")
|
||||
merge_notdir = join(merge_base, "notdir")
|
||||
native_notdir = join(native_base, "notdir")
|
||||
|
||||
err = compare_calls("mkdir success", lambda: os.mkdir(merge_dir, 0o755), lambda: os.mkdir(native_dir, 0o755))
|
||||
if err:
|
||||
return fail(err)
|
||||
cleanup_paths([merge_dir, merge_notdir])
|
||||
|
||||
err = compare_calls("mkdir EEXIST", lambda: os.mkdir(merge_dir, 0o755), lambda: os.mkdir(native_dir, 0o755))
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls("mkdir success", lambda: os.mkdir(merge_dir, 0o755), lambda: os.mkdir(native_dir, 0o755))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
touch(merge_notdir, b"x")
|
||||
touch(native_notdir, b"x")
|
||||
err = compare_calls(
|
||||
"mkdir ENOTDIR",
|
||||
lambda: os.mkdir(join(merge_notdir, "child"), 0o755),
|
||||
lambda: os.mkdir(join(native_notdir, "child"), 0o755),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls("mkdir EEXIST", lambda: os.mkdir(merge_dir, 0o755), lambda: os.mkdir(native_dir, 0o755))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls("rmdir success", lambda: os.rmdir(merge_dir), lambda: os.rmdir(native_dir))
|
||||
if err:
|
||||
return fail(err)
|
||||
touch(merge_notdir, b"x")
|
||||
touch(native_notdir, b"x")
|
||||
err = compare_calls(
|
||||
"mkdir ENOTDIR",
|
||||
lambda: os.mkdir(join(merge_notdir, "child"), 0o755),
|
||||
lambda: os.mkdir(join(native_notdir, "child"), 0o755),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls("rmdir ENOENT", lambda: os.rmdir(merge_dir), lambda: os.rmdir(native_dir))
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls("rmdir success", lambda: os.rmdir(merge_dir), lambda: os.rmdir(native_dir))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
touch(join(mount, "posix-mkdir/nonempty/file"), b"x")
|
||||
touch(join(native, "posix-mkdir/nonempty/file"), b"x")
|
||||
err = compare_calls(
|
||||
"rmdir ENOTEMPTY",
|
||||
lambda: os.rmdir(join(mount, "posix-mkdir/nonempty")),
|
||||
lambda: os.rmdir(join(native, "posix-mkdir/nonempty")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls("rmdir ENOENT", lambda: os.rmdir(merge_dir), lambda: os.rmdir(native_dir))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
touch(join(merge_base, "nonempty/file"), b"x")
|
||||
touch(join(native_base, "nonempty/file"), b"x")
|
||||
err = compare_calls(
|
||||
"rmdir ENOTEMPTY",
|
||||
lambda: os.rmdir(join(merge_base, "nonempty")),
|
||||
lambda: os.rmdir(join(native_base, "nonempty")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -4,10 +4,12 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -19,60 +21,67 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-rw/file")
|
||||
native_file = join(native, "posix-rw/file")
|
||||
merge_missing = join(mount, "posix-rw/missing")
|
||||
native_missing = join(native, "posix-rw/missing")
|
||||
merge_dir = join(mount, "posix-rw/dir")
|
||||
native_dir = join(native, "posix-rw/dir")
|
||||
merge_notdir = join(mount, "posix-rw/notdir")
|
||||
native_notdir = join(native, "posix-rw/notdir")
|
||||
|
||||
cleanup_paths([merge_file, merge_notdir])
|
||||
touch(merge_file, b"123456", 0o644)
|
||||
touch(native_file, b"123456", 0o644)
|
||||
os.makedirs(merge_dir, exist_ok=True)
|
||||
os.makedirs(native_dir, exist_ok=True)
|
||||
touch(merge_notdir, b"x", 0o644)
|
||||
touch(native_notdir, b"x", 0o644)
|
||||
|
||||
err = compare_calls("open ENOENT", lambda: os.open(merge_missing, os.O_RDONLY), lambda: os.open(native_missing, os.O_RDONLY))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls("open EISDIR", lambda: os.open(merge_dir, os.O_WRONLY), lambda: os.open(native_dir, os.O_WRONLY))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"open ENOTDIR",
|
||||
lambda: os.open(join(merge_notdir, "child"), os.O_RDONLY),
|
||||
lambda: os.open(join(native_notdir, "child"), os.O_RDONLY),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDWR)
|
||||
nfd = os.open(native_file, os.O_RDWR)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
os.lseek(mfd, 0, os.SEEK_SET)
|
||||
os.lseek(nfd, 0, os.SEEK_SET)
|
||||
m_data = os.read(mfd, 6)
|
||||
n_data = os.read(nfd, 6)
|
||||
if m_data != n_data:
|
||||
return fail(f"read parity mismatch mergerfs={m_data!r} native={n_data!r}")
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
os.lseek(mfd, 0, os.SEEK_SET)
|
||||
os.lseek(nfd, 0, os.SEEK_SET)
|
||||
mw = os.write(mfd, b"abc")
|
||||
nw = os.write(nfd, b"abc")
|
||||
if mw != nw:
|
||||
return fail(f"write count mismatch mergerfs={mw} native={nw}")
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
merge_missing = join(merge_base, "missing")
|
||||
native_missing = join(native_base, "missing")
|
||||
merge_dir = join(merge_base, "dir")
|
||||
native_dir = join(native_base, "dir")
|
||||
merge_notdir = join(merge_base, "notdir")
|
||||
native_notdir = join(native_base, "notdir")
|
||||
|
||||
cleanup_paths([merge_file, merge_notdir])
|
||||
touch(merge_file, b"123456", 0o644)
|
||||
touch(native_file, b"123456", 0o644)
|
||||
os.makedirs(merge_dir, exist_ok=True)
|
||||
os.makedirs(native_dir, exist_ok=True)
|
||||
touch(merge_notdir, b"x", 0o644)
|
||||
touch(native_notdir, b"x", 0o644)
|
||||
|
||||
err = compare_calls("open ENOENT", lambda: os.open(merge_missing, os.O_RDONLY), lambda: os.open(native_missing, os.O_RDONLY))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls("open EISDIR", lambda: os.open(merge_dir, os.O_WRONLY), lambda: os.open(native_dir, os.O_WRONLY))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"open ENOTDIR",
|
||||
lambda: os.open(join(merge_notdir, "child"), os.O_RDONLY),
|
||||
lambda: os.open(join(native_notdir, "child"), os.O_RDONLY),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDWR)
|
||||
nfd = os.open(native_file, os.O_RDWR)
|
||||
try:
|
||||
os.lseek(mfd, 0, os.SEEK_SET)
|
||||
os.lseek(nfd, 0, os.SEEK_SET)
|
||||
m_data = os.read(mfd, 6)
|
||||
n_data = os.read(nfd, 6)
|
||||
if m_data != n_data:
|
||||
return fail(f"read parity mismatch mergerfs={m_data!r} native={n_data!r}")
|
||||
|
||||
os.lseek(mfd, 0, os.SEEK_SET)
|
||||
os.lseek(nfd, 0, os.SEEK_SET)
|
||||
mw = os.write(mfd, b"abc")
|
||||
nw = os.write(nfd, b"abc")
|
||||
if mw != nw:
|
||||
return fail(f"write count mismatch mergerfs={mw} native={nw}")
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+27
-18
@@ -5,8 +5,10 @@ import select
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
from posix_parity import fail
|
||||
from posix_parity import join
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -28,27 +30,34 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-poll/file")
|
||||
native_file = join(native, "posix-poll/file")
|
||||
touch(merge_file, b"abcdef")
|
||||
touch(native_file, b"abcdef")
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDONLY)
|
||||
nfd = os.open(native_file, os.O_RDONLY)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
mmask = poll_mask(mfd)
|
||||
nmask = poll_mask(nfd)
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
# 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):
|
||||
return fail(f"poll POLLOUT mismatch mergerfs_mask=0x{mmask:x} native_mask=0x{nmask:x}")
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
touch(merge_file, b"abcdef")
|
||||
touch(native_file, b"abcdef")
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDONLY)
|
||||
nfd = os.open(native_file, os.O_RDONLY)
|
||||
try:
|
||||
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):
|
||||
return fail(f"poll POLLOUT mismatch mergerfs_mask=0x{mmask:x} native_mask=0x{nmask:x}")
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+50
-41
@@ -5,9 +5,11 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -116,54 +118,61 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_dir = join(mount, "posix-readdir/dir")
|
||||
native_dir = join(native, "posix-readdir/dir")
|
||||
merge_notdir = join(mount, "posix-readdir/notdir")
|
||||
native_notdir = join(native, "posix-readdir/notdir")
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
for idx in range(8):
|
||||
touch(join(merge_dir, f"f{idx}"), b"x")
|
||||
touch(join(native_dir, f"f{idx}"), b"x")
|
||||
merge_dir = join(merge_base, "dir")
|
||||
native_dir = join(native_base, "dir")
|
||||
merge_notdir = join(merge_base, "notdir")
|
||||
native_notdir = join(native_base, "notdir")
|
||||
|
||||
touch(merge_notdir, b"x")
|
||||
touch(native_notdir, b"x")
|
||||
for idx in range(8):
|
||||
touch(join(merge_dir, f"f{idx}"), b"x")
|
||||
touch(join(native_dir, f"f{idx}"), b"x")
|
||||
|
||||
err = compare_calls(
|
||||
"opendir ENOENT",
|
||||
lambda: opendir_or_raise(join(mount, "posix-readdir/missing")),
|
||||
lambda: opendir_or_raise(join(native, "posix-readdir/missing")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
touch(merge_notdir, b"x")
|
||||
touch(native_notdir, b"x")
|
||||
|
||||
err = compare_calls(
|
||||
"opendir ENOTDIR",
|
||||
lambda: opendir_or_raise(join(merge_notdir, "child")),
|
||||
lambda: opendir_or_raise(join(native_notdir, "child")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
merge_names = names_set(merge_dir)
|
||||
native_names = names_set(native_dir)
|
||||
if merge_names != native_names:
|
||||
return fail(f"readdir names mismatch mergerfs={sorted(merge_names)} native={sorted(native_names)}")
|
||||
|
||||
merge_consistent = seek_tell_consistent(merge_dir)
|
||||
native_consistent = seek_tell_consistent(native_dir)
|
||||
if merge_consistent != native_consistent:
|
||||
return fail(
|
||||
f"seekdir/telldir consistency mismatch mergerfs={merge_consistent} native={native_consistent}"
|
||||
err = compare_calls(
|
||||
"opendir ENOENT",
|
||||
lambda: opendir_or_raise(join(merge_base, "missing")),
|
||||
lambda: opendir_or_raise(join(native_base, "missing")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
touch(join(merge_dir, "victim"), b"x")
|
||||
touch(join(native_dir, "victim"), b"x")
|
||||
merge_sig = deleted_visibility_signature(merge_dir, join(merge_dir, "victim"))
|
||||
native_sig = deleted_visibility_signature(native_dir, join(native_dir, "victim"))
|
||||
if merge_sig != native_sig:
|
||||
return fail(f"deleted-entry visibility mismatch mergerfs={merge_sig} native={native_sig}")
|
||||
err = compare_calls(
|
||||
"opendir ENOTDIR",
|
||||
lambda: opendir_or_raise(join(merge_notdir, "child")),
|
||||
lambda: opendir_or_raise(join(native_notdir, "child")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
merge_names = names_set(merge_dir)
|
||||
native_names = names_set(native_dir)
|
||||
if merge_names != native_names:
|
||||
return fail(f"readdir names mismatch mergerfs={sorted(merge_names)} native={sorted(native_names)}")
|
||||
|
||||
merge_consistent = seek_tell_consistent(merge_dir)
|
||||
native_consistent = seek_tell_consistent(native_dir)
|
||||
if merge_consistent != native_consistent:
|
||||
return fail(
|
||||
f"seekdir/telldir consistency mismatch mergerfs={merge_consistent} native={native_consistent}"
|
||||
)
|
||||
|
||||
touch(join(merge_dir, "victim"), b"x")
|
||||
touch(join(native_dir, "victim"), b"x")
|
||||
merge_sig = deleted_visibility_signature(merge_dir, join(merge_dir, "victim"))
|
||||
native_sig = deleted_visibility_signature(native_dir, join(native_dir, "victim"))
|
||||
if merge_sig != native_sig:
|
||||
return fail(f"deleted-entry visibility mismatch mergerfs={merge_sig} native={native_sig}")
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -4,10 +4,12 @@ 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_get_option
|
||||
from posix_parity import mergerfs_set_option
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -29,40 +31,47 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_dir = join(mount, "posix-readdir-plus/dir")
|
||||
native_dir = join(native, "posix-readdir-plus/dir")
|
||||
|
||||
for i in range(40):
|
||||
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.
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
orig = mergerfs_get_option(mount, "func.readdir")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
merge_dir = join(merge_base, "dir")
|
||||
native_dir = join(native_base, "dir")
|
||||
|
||||
for i in range(40):
|
||||
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):
|
||||
return 0
|
||||
|
||||
try:
|
||||
for mode in ("seq", "cosr:2:2", "cor:2:2"):
|
||||
mergerfs_set_option(mount, "func.readdir", mode)
|
||||
got = list_and_stat(merge_dir)
|
||||
if got != m_map:
|
||||
return fail(f"func.readdir={mode} changed readdir+getattr semantic output")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
finally:
|
||||
try:
|
||||
mergerfs_set_option(mount, "func.readdir", orig)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
try:
|
||||
for mode in ("seq", "cosr:2:2", "cor:2:2"):
|
||||
mergerfs_set_option(mount, "func.readdir", mode)
|
||||
got = list_and_stat(merge_dir)
|
||||
if got != m_map:
|
||||
return fail(f"func.readdir={mode} changed readdir+getattr semantic output")
|
||||
except (PermissionError, FileNotFoundError, OSError):
|
||||
return 0
|
||||
finally:
|
||||
try:
|
||||
mergerfs_set_option(mount, "func.readdir", orig)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+57
-48
@@ -4,8 +4,10 @@ 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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -17,62 +19,69 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-release/file")
|
||||
native_file = join(native, "posix-release/file")
|
||||
|
||||
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)
|
||||
nfd2 = os.dup(nfd1)
|
||||
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
os.close(mfd1)
|
||||
os.close(nfd1)
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
mw = os.write(mfd2, b"X")
|
||||
nw = os.write(nfd2, b"X")
|
||||
if mw != nw:
|
||||
return fail(f"release dup write mismatch mergerfs={mw} native={nw}")
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
|
||||
os.lseek(mfd2, 0, os.SEEK_SET)
|
||||
os.lseek(nfd2, 0, os.SEEK_SET)
|
||||
mr = os.read(mfd2, 4)
|
||||
nr = os.read(nfd2, 4)
|
||||
if mr != nr:
|
||||
return fail(f"release dup read mismatch mergerfs={mr!r} native={nr!r}")
|
||||
finally:
|
||||
os.close(mfd2)
|
||||
os.close(nfd2)
|
||||
touch(merge_file, b"abc")
|
||||
touch(native_file, b"abc")
|
||||
|
||||
with open(merge_file, "rb") as mf, open(native_file, "rb") as nf:
|
||||
md = mf.read()
|
||||
nd = nf.read()
|
||||
if md != nd:
|
||||
return fail(f"release final data mismatch mergerfs={md!r} native={nd!r}")
|
||||
# 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)
|
||||
nfd2 = os.dup(nfd1)
|
||||
|
||||
# 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)
|
||||
os.close(nfd)
|
||||
try:
|
||||
try:
|
||||
os.close(mfd1)
|
||||
os.close(nfd1)
|
||||
|
||||
mw = os.write(mfd2, b"X")
|
||||
nw = os.write(nfd2, b"X")
|
||||
if mw != nw:
|
||||
return fail(f"release dup write mismatch mergerfs={mw} native={nw}")
|
||||
|
||||
os.lseek(mfd2, 0, os.SEEK_SET)
|
||||
os.lseek(nfd2, 0, os.SEEK_SET)
|
||||
mr = os.read(mfd2, 4)
|
||||
nr = os.read(nfd2, 4)
|
||||
if mr != nr:
|
||||
return fail(f"release dup read mismatch mergerfs={mr!r} native={nr!r}")
|
||||
finally:
|
||||
os.close(mfd2)
|
||||
os.close(nfd2)
|
||||
|
||||
with open(merge_file, "rb") as mf, open(native_file, "rb") as nf:
|
||||
md = mf.read()
|
||||
nd = nf.read()
|
||||
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)
|
||||
m_err = 0
|
||||
except OSError as exc:
|
||||
m_err = exc.errno
|
||||
try:
|
||||
os.close(nfd)
|
||||
n_err = 0
|
||||
except OSError as exc:
|
||||
n_err = exc.errno
|
||||
if m_err != n_err:
|
||||
return fail(f"release EBADF mismatch mergerfs_errno={m_err} native_errno={n_err}")
|
||||
try:
|
||||
os.close(mfd)
|
||||
m_err = 0
|
||||
except OSError as exc:
|
||||
m_err = exc.errno
|
||||
try:
|
||||
os.close(nfd)
|
||||
n_err = 0
|
||||
except OSError as exc:
|
||||
n_err = exc.errno
|
||||
if m_err != n_err:
|
||||
return fail(f"release EBADF mismatch mergerfs_errno={m_err} native_errno={n_err}")
|
||||
|
||||
return 0
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5,8 +5,10 @@ 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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -54,17 +56,24 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_dir = join(mount, "posix-releasedir/dir")
|
||||
native_dir = join(native, "posix-releasedir/dir")
|
||||
touch(join(merge_dir, "a"), b"x")
|
||||
touch(join(native_dir, "a"), b"x")
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
m_err = close_and_dirfd_errno(merge_dir)
|
||||
n_err = close_and_dirfd_errno(native_dir)
|
||||
if m_err != n_err:
|
||||
return fail(f"releasedir EBADF parity mismatch mergerfs_errno={m_err} native_errno={n_err}")
|
||||
merge_dir = join(merge_base, "dir")
|
||||
native_dir = join(native_base, "dir")
|
||||
touch(join(merge_dir, "a"), b"x")
|
||||
touch(join(native_dir, "a"), b"x")
|
||||
|
||||
return 0
|
||||
m_err = close_and_dirfd_errno(merge_dir)
|
||||
n_err = close_and_dirfd_errno(native_dir)
|
||||
if m_err != n_err:
|
||||
return fail(f"releasedir EBADF parity mismatch mergerfs_errno={m_err} native_errno={n_err}")
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+25
-16
@@ -4,9 +4,11 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
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 temp_dir
|
||||
|
||||
|
||||
def statvfs_cmp(lhs, rhs):
|
||||
@@ -27,24 +29,31 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
err = compare_calls(
|
||||
"statvfs mount parity",
|
||||
lambda: os.statvfs(mount),
|
||||
lambda: os.statvfs(native),
|
||||
statvfs_cmp,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
err = compare_calls(
|
||||
"statvfs ENOENT",
|
||||
lambda: os.statvfs(join(mount, "posix-statfs/missing")),
|
||||
lambda: os.statvfs(join(native, "posix-statfs/missing")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"statvfs mount parity",
|
||||
lambda: os.statvfs(mount),
|
||||
lambda: os.statvfs(native),
|
||||
statvfs_cmp,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
err = compare_calls(
|
||||
"statvfs ENOENT",
|
||||
lambda: os.statvfs(join(merge_base, "missing")),
|
||||
lambda: os.statvfs(join(native_base, "missing")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+76
-69
@@ -5,10 +5,12 @@ import errno
|
||||
import os
|
||||
import sys
|
||||
|
||||
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 should_compare_inode
|
||||
from posix_parity import temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -177,88 +179,93 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
stcmp = cmp_statx_basic_with_inode if should_compare_inode(mount) else cmp_statx_basic
|
||||
|
||||
merge_file = join(mount, "posix-statx/file")
|
||||
merge_link = join(mount, "posix-statx/link")
|
||||
|
||||
touch(merge_file, b"hello")
|
||||
merge_base = temp_dir(mount)
|
||||
|
||||
try:
|
||||
native_file = mergerfs_fullpath(merge_file)
|
||||
except OSError:
|
||||
return 0
|
||||
merge_file = join(merge_base, "file")
|
||||
merge_link = join(merge_base, "link")
|
||||
|
||||
try:
|
||||
os.unlink(merge_link)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.symlink("file", merge_link)
|
||||
touch(merge_file, b"hello")
|
||||
|
||||
try:
|
||||
native_link = mergerfs_fullpath(merge_link)
|
||||
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):
|
||||
native_link = expected_native_link
|
||||
else:
|
||||
try:
|
||||
native_file = mergerfs_fullpath(merge_file)
|
||||
except OSError:
|
||||
return 0
|
||||
|
||||
native_missing = os.path.join(os.path.dirname(native_file), "missing")
|
||||
try:
|
||||
os.unlink(merge_link)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.symlink("file", merge_link)
|
||||
|
||||
mst, nst, err = call_pair(
|
||||
"statx regular",
|
||||
lambda: statx_call(merge_file),
|
||||
lambda: statx_call(native_file),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
if not stcmp(mst, nst):
|
||||
return fail(
|
||||
"statx basic mismatch\n"
|
||||
f" mergerfs path: {merge_file}\n"
|
||||
f" native path: {native_file}\n"
|
||||
f" mergerfs statx: {statx_summary(mst)}\n"
|
||||
f" native statx: {statx_summary(nst)}"
|
||||
try:
|
||||
native_link = mergerfs_fullpath(merge_link)
|
||||
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):
|
||||
native_link = expected_native_link
|
||||
else:
|
||||
return 0
|
||||
|
||||
native_missing = os.path.join(os.path.dirname(native_file), "missing")
|
||||
|
||||
mst, nst, err = call_pair(
|
||||
"statx regular",
|
||||
lambda: statx_call(merge_file),
|
||||
lambda: statx_call(native_file),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
if not stcmp(mst, nst):
|
||||
return fail(
|
||||
"statx basic mismatch\n"
|
||||
f" mergerfs path: {merge_file}\n"
|
||||
f" native path: {native_file}\n"
|
||||
f" mergerfs statx: {statx_summary(mst)}\n"
|
||||
f" native statx: {statx_summary(nst)}"
|
||||
)
|
||||
|
||||
mst, nst, err = call_pair(
|
||||
"statx symlink nofollow",
|
||||
lambda: statx_call(merge_link, flags=AT_SYMLINK_NOFOLLOW),
|
||||
lambda: statx_call(native_link, flags=AT_SYMLINK_NOFOLLOW),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
if (mst.stx_mode & 0xF000) != (nst.stx_mode & 0xF000):
|
||||
return fail(
|
||||
"statx symlink type mismatch\n"
|
||||
f" mergerfs path: {merge_link}\n"
|
||||
f" native path: {native_link}\n"
|
||||
f" mergerfs statx: {statx_summary(mst)}\n"
|
||||
f" native statx: {statx_summary(nst)}"
|
||||
mst, nst, err = call_pair(
|
||||
"statx symlink nofollow",
|
||||
lambda: statx_call(merge_link, flags=AT_SYMLINK_NOFOLLOW),
|
||||
lambda: statx_call(native_link, flags=AT_SYMLINK_NOFOLLOW),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
if (mst.stx_mode & 0xF000) != (nst.stx_mode & 0xF000):
|
||||
return fail(
|
||||
"statx symlink type mismatch\n"
|
||||
f" mergerfs path: {merge_link}\n"
|
||||
f" native path: {native_link}\n"
|
||||
f" mergerfs statx: {statx_summary(mst)}\n"
|
||||
f" native statx: {statx_summary(nst)}"
|
||||
)
|
||||
|
||||
err = expect_same_errno(
|
||||
"statx ENOENT",
|
||||
lambda: statx_call(join(mount, "posix-statx/missing")),
|
||||
lambda: statx_call(native_missing),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = expect_same_errno(
|
||||
"statx ENOENT",
|
||||
lambda: statx_call(join(merge_base, "missing")),
|
||||
lambda: statx_call(native_missing),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = expect_same_errno(
|
||||
"statx ENOTDIR",
|
||||
lambda: statx_call(join(merge_file, "child")),
|
||||
lambda: statx_call(join(native_file, "child")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = expect_same_errno(
|
||||
"statx ENOTDIR",
|
||||
lambda: statx_call(join(merge_file, "child")),
|
||||
lambda: statx_call(join(native_file, "child")),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+25
-16
@@ -5,9 +5,11 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -33,26 +35,33 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-syncfs/file")
|
||||
native_file = join(native, "posix-syncfs/file")
|
||||
touch(merge_file, b"x")
|
||||
touch(native_file, b"x")
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDONLY)
|
||||
nfd = os.open(native_file, os.O_RDONLY)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
err = compare_calls("syncfs success", lambda: syncfs_fd(mfd), lambda: syncfs_fd(nfd))
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
touch(merge_file, b"x")
|
||||
touch(native_file, b"x")
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDONLY)
|
||||
nfd = os.open(native_file, os.O_RDONLY)
|
||||
try:
|
||||
err = compare_calls("syncfs success", lambda: syncfs_fd(mfd), lambda: syncfs_fd(nfd))
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
err = compare_calls("syncfs EBADF", lambda: syncfs_fd(-1), lambda: syncfs_fd(-1))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
err = compare_calls("syncfs EBADF", lambda: syncfs_fd(-1), lambda: syncfs_fd(-1))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+33
-24
@@ -5,8 +5,10 @@ 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 temp_dir
|
||||
|
||||
|
||||
def open_tmpfile(dirpath):
|
||||
@@ -32,32 +34,39 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_dir = join(mount, "posix-tmpfile")
|
||||
native_dir = join(native, "posix-tmpfile")
|
||||
os.makedirs(merge_dir, exist_ok=True)
|
||||
os.makedirs(native_dir, exist_ok=True)
|
||||
|
||||
m_err = errno_of(lambda: open_tmpfile(merge_dir))
|
||||
n_err = errno_of(lambda: open_tmpfile(native_dir))
|
||||
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
|
||||
|
||||
mfd = open_tmpfile(merge_dir)
|
||||
nfd = open_tmpfile(native_dir)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
mw = os.write(mfd, b"tmpfile-data")
|
||||
nw = os.write(nfd, b"tmpfile-data")
|
||||
if mw != nw:
|
||||
return fail(f"O_TMPFILE write count mismatch mergerfs={mw} native={nw}")
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
return 0
|
||||
merge_dir = merge_base
|
||||
native_dir = native_base
|
||||
|
||||
m_err = errno_of(lambda: open_tmpfile(merge_dir))
|
||||
n_err = errno_of(lambda: open_tmpfile(native_dir))
|
||||
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
|
||||
|
||||
mfd = open_tmpfile(merge_dir)
|
||||
nfd = open_tmpfile(native_dir)
|
||||
try:
|
||||
mw = os.write(mfd, b"tmpfile-data")
|
||||
nw = os.write(nfd, b"tmpfile-data")
|
||||
if mw != nw:
|
||||
return fail(f"O_TMPFILE write count mismatch mergerfs={mw} native={nw}")
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -4,10 +4,12 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -19,38 +21,45 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-truncate/file")
|
||||
native_file = join(native, "posix-truncate/file")
|
||||
merge_missing = join(mount, "posix-truncate/missing")
|
||||
native_missing = join(native, "posix-truncate/missing")
|
||||
|
||||
cleanup_paths([merge_file])
|
||||
touch(merge_file, b"1234567890")
|
||||
touch(native_file, b"1234567890")
|
||||
|
||||
err = compare_calls("truncate shrink", lambda: os.truncate(merge_file, 3), lambda: os.truncate(native_file, 3))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls("truncate ENOENT", lambda: os.truncate(merge_missing, 1), lambda: os.truncate(native_missing, 1))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDWR)
|
||||
nfd = os.open(native_file, os.O_RDWR)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
err = compare_calls("ftruncate grow", lambda: os.ftruncate(mfd, 16), lambda: os.ftruncate(nfd, 16))
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
merge_missing = join(merge_base, "missing")
|
||||
native_missing = join(native_base, "missing")
|
||||
|
||||
cleanup_paths([merge_file])
|
||||
touch(merge_file, b"1234567890")
|
||||
touch(native_file, b"1234567890")
|
||||
|
||||
err = compare_calls("truncate shrink", lambda: os.truncate(merge_file, 3), lambda: os.truncate(native_file, 3))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls("truncate ENOENT", lambda: os.truncate(merge_missing, 1), lambda: os.truncate(native_missing, 1))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDWR)
|
||||
nfd = os.open(native_file, os.O_RDWR)
|
||||
try:
|
||||
err = compare_calls("ftruncate grow", lambda: os.ftruncate(mfd, 16), lambda: os.ftruncate(nfd, 16))
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
err = compare_calls("ftruncate EBADF", lambda: os.ftruncate(-1, 4), lambda: os.ftruncate(-1, 4))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
err = compare_calls("ftruncate EBADF", lambda: os.ftruncate(-1, 4), lambda: os.ftruncate(-1, 4))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -4,10 +4,12 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -19,51 +21,58 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_a = join(mount, "posix-unlink-rename/a")
|
||||
native_a = join(native, "posix-unlink-rename/a")
|
||||
merge_b = join(mount, "posix-unlink-rename/b")
|
||||
native_b = join(native, "posix-unlink-rename/b")
|
||||
merge_missing = join(mount, "posix-unlink-rename/missing")
|
||||
native_missing = join(native, "posix-unlink-rename/missing")
|
||||
merge_notdir = join(mount, "posix-unlink-rename/notdir")
|
||||
native_notdir = join(native, "posix-unlink-rename/notdir")
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
cleanup_paths([merge_a, merge_b, merge_notdir])
|
||||
touch(merge_a, b"a")
|
||||
touch(native_a, b"a")
|
||||
merge_a = join(merge_base, "a")
|
||||
native_a = join(native_base, "a")
|
||||
merge_b = join(merge_base, "b")
|
||||
native_b = join(native_base, "b")
|
||||
merge_missing = join(merge_base, "missing")
|
||||
native_missing = join(native_base, "missing")
|
||||
merge_notdir = join(merge_base, "notdir")
|
||||
native_notdir = join(native_base, "notdir")
|
||||
|
||||
err = compare_calls("unlink success", lambda: os.unlink(merge_a), lambda: os.unlink(native_a))
|
||||
if err:
|
||||
return fail(err)
|
||||
cleanup_paths([merge_a, merge_b, merge_notdir])
|
||||
touch(merge_a, b"a")
|
||||
touch(native_a, b"a")
|
||||
|
||||
err = compare_calls("unlink ENOENT", lambda: os.unlink(merge_missing), lambda: os.unlink(native_missing))
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls("unlink success", lambda: os.unlink(merge_a), lambda: os.unlink(native_a))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
touch(merge_a, b"a")
|
||||
touch(native_a, b"a")
|
||||
touch(merge_b, b"b")
|
||||
touch(native_b, b"b")
|
||||
err = compare_calls("unlink ENOENT", lambda: os.unlink(merge_missing), lambda: os.unlink(native_missing))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls("rename overwrite", lambda: os.rename(merge_a, merge_b), lambda: os.rename(native_a, native_b))
|
||||
if err:
|
||||
return fail(err)
|
||||
touch(merge_a, b"a")
|
||||
touch(native_a, b"a")
|
||||
touch(merge_b, b"b")
|
||||
touch(native_b, b"b")
|
||||
|
||||
err = compare_calls("rename ENOENT", lambda: os.rename(merge_missing, merge_a), lambda: os.rename(native_missing, native_a))
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls("rename overwrite", lambda: os.rename(merge_a, merge_b), lambda: os.rename(native_a, native_b))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
touch(merge_notdir, b"x")
|
||||
touch(native_notdir, b"x")
|
||||
err = compare_calls(
|
||||
"rename ENOTDIR",
|
||||
lambda: os.rename(join(merge_notdir, "child"), merge_a),
|
||||
lambda: os.rename(join(native_notdir, "child"), native_a),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls("rename ENOENT", lambda: os.rename(merge_missing, merge_a), lambda: os.rename(native_missing, native_a))
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
touch(merge_notdir, b"x")
|
||||
touch(native_notdir, b"x")
|
||||
err = compare_calls(
|
||||
"rename ENOTDIR",
|
||||
lambda: os.rename(join(merge_notdir, "child"), merge_a),
|
||||
lambda: os.rename(join(native_notdir, "child"), native_a),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+54
-45
@@ -5,10 +5,12 @@ import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -20,57 +22,64 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-utimens/file")
|
||||
native_file = join(native, "posix-utimens/file")
|
||||
merge_missing = join(mount, "posix-utimens/missing")
|
||||
native_missing = join(native, "posix-utimens/missing")
|
||||
|
||||
cleanup_paths([merge_file])
|
||||
touch(merge_file, b"x")
|
||||
touch(native_file, b"x")
|
||||
|
||||
now = time.time()
|
||||
times = (now - 1000, now - 500)
|
||||
|
||||
err = compare_calls(
|
||||
"utime path success",
|
||||
lambda: os.utime(merge_file, times=times),
|
||||
lambda: os.utime(native_file, times=times),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDWR)
|
||||
nfd = os.open(native_file, os.O_RDWR)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
merge_missing = join(merge_base, "missing")
|
||||
native_missing = join(native_base, "missing")
|
||||
|
||||
cleanup_paths([merge_file])
|
||||
touch(merge_file, b"x")
|
||||
touch(native_file, b"x")
|
||||
|
||||
now = time.time()
|
||||
times = (now - 1000, now - 500)
|
||||
|
||||
err = compare_calls(
|
||||
"utime fd success",
|
||||
lambda: os.utime(mfd, times=times),
|
||||
lambda: os.utime(nfd, times=times),
|
||||
"utime path success",
|
||||
lambda: os.utime(merge_file, times=times),
|
||||
lambda: os.utime(native_file, times=times),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
mfd = os.open(merge_file, os.O_RDWR)
|
||||
nfd = os.open(native_file, os.O_RDWR)
|
||||
try:
|
||||
err = compare_calls(
|
||||
"utime fd success",
|
||||
lambda: os.utime(mfd, times=times),
|
||||
lambda: os.utime(nfd, times=times),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
err = compare_calls(
|
||||
"utime ENOENT",
|
||||
lambda: os.utime(merge_missing, times=times),
|
||||
lambda: os.utime(native_missing, times=times),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"utime EBADF",
|
||||
lambda: os.utime(-1, times=times),
|
||||
lambda: os.utime(-1, times=times),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
os.close(mfd)
|
||||
os.close(nfd)
|
||||
|
||||
err = compare_calls(
|
||||
"utime ENOENT",
|
||||
lambda: os.utime(merge_missing, times=times),
|
||||
lambda: os.utime(native_missing, times=times),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"utime EBADF",
|
||||
lambda: os.utime(-1, times=times),
|
||||
lambda: os.utime(-1, times=times),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+113
-104
@@ -5,9 +5,11 @@ import sys
|
||||
import tempfile
|
||||
import errno
|
||||
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -38,119 +40,126 @@ def main():
|
||||
xvalue = b"parity-check"
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
merge_file = join(mount, "posix-xattr/file")
|
||||
native_file = join(native, "posix-xattr/file")
|
||||
merge_missing = join(mount, "posix-xattr/missing")
|
||||
native_missing = join(native, "posix-xattr/missing")
|
||||
merge_notdir = join(mount, "posix-xattr/notdir")
|
||||
native_notdir = join(native, "posix-xattr/notdir")
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
touch(merge_file, b"x")
|
||||
touch(native_file, b"x")
|
||||
touch(merge_notdir, b"x")
|
||||
touch(native_notdir, b"x")
|
||||
merge_file = join(merge_base, "file")
|
||||
native_file = join(native_base, "file")
|
||||
merge_missing = join(merge_base, "missing")
|
||||
native_missing = join(native_base, "missing")
|
||||
merge_notdir = join(merge_base, "notdir")
|
||||
native_notdir = join(native_base, "notdir")
|
||||
|
||||
err = compare_calls(
|
||||
"setxattr success",
|
||||
lambda: os.setxattr(merge_file, xname, xvalue),
|
||||
lambda: os.setxattr(native_file, xname, xvalue),
|
||||
)
|
||||
if err:
|
||||
try:
|
||||
os.setxattr(merge_file, xname, xvalue)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("mergerfs setxattr", exc)
|
||||
try:
|
||||
os.setxattr(native_file, xname, xvalue)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("native setxattr", exc)
|
||||
return fail(err)
|
||||
touch(merge_file, b"x")
|
||||
touch(native_file, b"x")
|
||||
touch(merge_notdir, b"x")
|
||||
touch(native_notdir, b"x")
|
||||
|
||||
err = compare_calls(
|
||||
"getxattr success",
|
||||
lambda: os.getxattr(merge_file, xname),
|
||||
lambda: os.getxattr(native_file, xname),
|
||||
lambda lhs, rhs: lhs == rhs,
|
||||
)
|
||||
if err:
|
||||
try:
|
||||
os.getxattr(merge_file, xname)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("mergerfs getxattr", exc)
|
||||
try:
|
||||
os.getxattr(native_file, xname)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("native getxattr", exc)
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"setxattr success",
|
||||
lambda: os.setxattr(merge_file, xname, xvalue),
|
||||
lambda: os.setxattr(native_file, xname, xvalue),
|
||||
)
|
||||
if err:
|
||||
try:
|
||||
os.setxattr(merge_file, xname, xvalue)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("mergerfs setxattr", exc)
|
||||
try:
|
||||
os.setxattr(native_file, xname, xvalue)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("native setxattr", exc)
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"listxattr includes key",
|
||||
lambda: os.listxattr(merge_file),
|
||||
lambda: os.listxattr(native_file),
|
||||
has_attr(xname),
|
||||
)
|
||||
if err:
|
||||
try:
|
||||
m_list = os.listxattr(merge_file)
|
||||
err += f" | mergerfs list={m_list!r}"
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("mergerfs listxattr", exc)
|
||||
try:
|
||||
n_list = os.listxattr(native_file)
|
||||
err += f" | native list={n_list!r}"
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("native listxattr", exc)
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"getxattr success",
|
||||
lambda: os.getxattr(merge_file, xname),
|
||||
lambda: os.getxattr(native_file, xname),
|
||||
lambda lhs, rhs: lhs == rhs,
|
||||
)
|
||||
if err:
|
||||
try:
|
||||
os.getxattr(merge_file, xname)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("mergerfs getxattr", exc)
|
||||
try:
|
||||
os.getxattr(native_file, xname)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("native getxattr", exc)
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"removexattr success",
|
||||
lambda: os.removexattr(merge_file, xname),
|
||||
lambda: os.removexattr(native_file, xname),
|
||||
)
|
||||
if err:
|
||||
try:
|
||||
os.removexattr(merge_file, xname)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("mergerfs removexattr", exc)
|
||||
try:
|
||||
os.removexattr(native_file, xname)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("native removexattr", exc)
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"listxattr includes key",
|
||||
lambda: os.listxattr(merge_file),
|
||||
lambda: os.listxattr(native_file),
|
||||
has_attr(xname),
|
||||
)
|
||||
if err:
|
||||
try:
|
||||
m_list = os.listxattr(merge_file)
|
||||
err += f" | mergerfs list={m_list!r}"
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("mergerfs listxattr", exc)
|
||||
try:
|
||||
n_list = os.listxattr(native_file)
|
||||
err += f" | native list={n_list!r}"
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("native listxattr", exc)
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"getxattr missing attr",
|
||||
lambda: os.getxattr(merge_file, xname),
|
||||
lambda: os.getxattr(native_file, xname),
|
||||
)
|
||||
if err:
|
||||
try:
|
||||
os.getxattr(merge_file, xname)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("mergerfs getxattr missing", exc)
|
||||
try:
|
||||
os.getxattr(native_file, xname)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("native getxattr missing", exc)
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"removexattr success",
|
||||
lambda: os.removexattr(merge_file, xname),
|
||||
lambda: os.removexattr(native_file, xname),
|
||||
)
|
||||
if err:
|
||||
try:
|
||||
os.removexattr(merge_file, xname)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("mergerfs removexattr", exc)
|
||||
try:
|
||||
os.removexattr(native_file, xname)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("native removexattr", exc)
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"setxattr ENOENT",
|
||||
lambda: os.setxattr(merge_missing, xname, xvalue),
|
||||
lambda: os.setxattr(native_missing, xname, xvalue),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"getxattr missing attr",
|
||||
lambda: os.getxattr(merge_file, xname),
|
||||
lambda: os.getxattr(native_file, xname),
|
||||
)
|
||||
if err:
|
||||
try:
|
||||
os.getxattr(merge_file, xname)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("mergerfs getxattr missing", exc)
|
||||
try:
|
||||
os.getxattr(native_file, xname)
|
||||
except OSError as exc:
|
||||
err += " | " + format_oserror("native getxattr missing", exc)
|
||||
return fail(err)
|
||||
|
||||
err = compare_calls(
|
||||
"getxattr ENOTDIR",
|
||||
lambda: os.getxattr(join(merge_notdir, "child"), xname),
|
||||
lambda: os.getxattr(join(native_notdir, "child"), xname),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare_calls(
|
||||
"setxattr ENOENT",
|
||||
lambda: os.setxattr(merge_missing, xname, xvalue),
|
||||
lambda: os.setxattr(native_missing, xname, xvalue),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
err = compare_calls(
|
||||
"getxattr ENOTDIR",
|
||||
lambda: os.getxattr(join(merge_notdir, "child"), xname),
|
||||
lambda: os.getxattr(join(native_notdir, "child"), xname),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+118
-109
@@ -5,8 +5,10 @@ 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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
@@ -69,71 +71,50 @@ def main():
|
||||
mount = sys.argv[1]
|
||||
|
||||
with tempfile.TemporaryDirectory() as native:
|
||||
root_m = join(mount, "posix-xattr-matrix")
|
||||
root_n = join(native, "posix-xattr-matrix")
|
||||
|
||||
file_m = join(root_m, "file")
|
||||
file_n = join(root_n, "file")
|
||||
dir_m = join(root_m, "dir")
|
||||
dir_n = join(root_n, "dir")
|
||||
link_m = join(root_m, "link")
|
||||
link_n = join(root_n, "link")
|
||||
|
||||
touch(file_m, b"x")
|
||||
touch(file_n, b"x")
|
||||
os.makedirs(dir_m, exist_ok=True)
|
||||
os.makedirs(dir_n, exist_ok=True)
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
os.unlink(link_m)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
try:
|
||||
os.unlink(link_n)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.symlink("file", link_m)
|
||||
os.symlink("file", link_n)
|
||||
native_base = join(native, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
objs = [
|
||||
("file", file_m, file_n),
|
||||
("dir", dir_m, dir_n),
|
||||
]
|
||||
root_m = merge_base
|
||||
root_n = native_base
|
||||
|
||||
xbase = "user.mergerfs.matrix"
|
||||
file_m = join(root_m, "file")
|
||||
file_n = join(root_n, "file")
|
||||
dir_m = join(root_m, "dir")
|
||||
dir_n = join(root_n, "dir")
|
||||
link_m = join(root_m, "link")
|
||||
link_n = join(root_n, "link")
|
||||
|
||||
for obj_name, path_m, path_n in objs:
|
||||
xname = f"{xbase}.{obj_name}"
|
||||
touch(file_m, b"x")
|
||||
touch(file_n, b"x")
|
||||
os.makedirs(dir_m, exist_ok=True)
|
||||
os.makedirs(dir_n, exist_ok=True)
|
||||
try:
|
||||
os.unlink(link_m)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
try:
|
||||
os.unlink(link_n)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.symlink("file", link_m)
|
||||
os.symlink("file", link_n)
|
||||
|
||||
err = compare(
|
||||
f"setxattr default {obj_name}",
|
||||
lambda p=path_m, n=xname: os.setxattr(p, n, b"v1"),
|
||||
lambda p=path_n, n=xname: os.setxattr(p, n, b"v1"),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
objs = [
|
||||
("file", file_m, file_n),
|
||||
("dir", dir_m, dir_n),
|
||||
]
|
||||
|
||||
err = getxattr_cmp(path_m, path_n, xname)
|
||||
if err:
|
||||
return fail(err)
|
||||
xbase = "user.mergerfs.matrix"
|
||||
|
||||
err = list_has_cmp(path_m, path_n, xname)
|
||||
if err:
|
||||
return fail(err)
|
||||
for obj_name, path_m, path_n in objs:
|
||||
xname = f"{xbase}.{obj_name}"
|
||||
|
||||
if hasattr(os, "XATTR_CREATE"):
|
||||
err = compare(
|
||||
f"setxattr CREATE existing {obj_name}",
|
||||
lambda p=path_m, n=xname: os.setxattr(p, n, b"v2", os.XATTR_CREATE),
|
||||
lambda p=path_n, n=xname: os.setxattr(p, n, b"v2", os.XATTR_CREATE),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
if hasattr(os, "XATTR_REPLACE"):
|
||||
err = compare(
|
||||
f"setxattr REPLACE existing {obj_name}",
|
||||
lambda p=path_m, n=xname: os.setxattr(p, n, b"v3", os.XATTR_REPLACE),
|
||||
lambda p=path_n, n=xname: os.setxattr(p, n, b"v3", os.XATTR_REPLACE),
|
||||
f"setxattr default {obj_name}",
|
||||
lambda p=path_m, n=xname: os.setxattr(p, n, b"v1"),
|
||||
lambda p=path_n, n=xname: os.setxattr(p, n, b"v1"),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
@@ -142,69 +123,97 @@ def main():
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
missing_name = f"{xname}.missing"
|
||||
if hasattr(os, "XATTR_REPLACE"):
|
||||
err = list_has_cmp(path_m, path_n, xname)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
if hasattr(os, "XATTR_CREATE"):
|
||||
err = compare(
|
||||
f"setxattr CREATE existing {obj_name}",
|
||||
lambda p=path_m, n=xname: os.setxattr(p, n, b"v2", os.XATTR_CREATE),
|
||||
lambda p=path_n, n=xname: os.setxattr(p, n, b"v2", os.XATTR_CREATE),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
if hasattr(os, "XATTR_REPLACE"):
|
||||
err = compare(
|
||||
f"setxattr REPLACE existing {obj_name}",
|
||||
lambda p=path_m, n=xname: os.setxattr(p, n, b"v3", os.XATTR_REPLACE),
|
||||
lambda p=path_n, n=xname: os.setxattr(p, n, b"v3", os.XATTR_REPLACE),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = getxattr_cmp(path_m, path_n, xname)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
missing_name = f"{xname}.missing"
|
||||
if hasattr(os, "XATTR_REPLACE"):
|
||||
err = compare(
|
||||
f"setxattr REPLACE missing {obj_name}",
|
||||
lambda p=path_m, n=missing_name: os.setxattr(p, n, b"v", os.XATTR_REPLACE),
|
||||
lambda p=path_n, n=missing_name: os.setxattr(p, n, b"v", os.XATTR_REPLACE),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare(
|
||||
f"setxattr REPLACE missing {obj_name}",
|
||||
lambda p=path_m, n=missing_name: os.setxattr(p, n, b"v", os.XATTR_REPLACE),
|
||||
lambda p=path_n, n=missing_name: os.setxattr(p, n, b"v", os.XATTR_REPLACE),
|
||||
f"removexattr existing {obj_name}",
|
||||
lambda p=path_m, n=xname: os.removexattr(p, n),
|
||||
lambda p=path_n, n=xname: os.removexattr(p, n),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare(
|
||||
f"removexattr existing {obj_name}",
|
||||
lambda p=path_m, n=xname: os.removexattr(p, n),
|
||||
lambda p=path_n, n=xname: os.removexattr(p, n),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare(
|
||||
f"removexattr missing {obj_name}",
|
||||
lambda p=path_m, n=xname: os.removexattr(p, n),
|
||||
lambda p=path_n, n=xname: os.removexattr(p, n),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare(
|
||||
f"removexattr missing {obj_name}",
|
||||
lambda p=path_m, n=xname: os.removexattr(p, n),
|
||||
lambda p=path_n, n=xname: os.removexattr(p, n),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
lxname = f"{xbase}.link"
|
||||
if all(hasattr(os, fn) for fn in ("lsetxattr", "lgetxattr", "llistxattr", "lremovexattr")):
|
||||
err = compare(
|
||||
"lsetxattr symlink default",
|
||||
lambda: os.lsetxattr(link_m, lxname, b"lv1"),
|
||||
lambda: os.lsetxattr(link_n, lxname, b"lv1"),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
lxname = f"{xbase}.link"
|
||||
if all(hasattr(os, fn) for fn in ("lsetxattr", "lgetxattr", "llistxattr", "lremovexattr")):
|
||||
err = compare(
|
||||
"lsetxattr symlink default",
|
||||
lambda: os.lsetxattr(link_m, lxname, b"lv1"),
|
||||
lambda: os.lsetxattr(link_n, lxname, b"lv1"),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare(
|
||||
"lgetxattr symlink",
|
||||
lambda: os.lgetxattr(link_m, lxname),
|
||||
lambda: os.lgetxattr(link_n, lxname),
|
||||
lambda a, b: a == b,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare(
|
||||
"lgetxattr symlink",
|
||||
lambda: os.lgetxattr(link_m, lxname),
|
||||
lambda: os.lgetxattr(link_n, lxname),
|
||||
lambda a, b: a == b,
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare(
|
||||
"llistxattr has symlink key",
|
||||
lambda: os.llistxattr(link_m),
|
||||
lambda: os.llistxattr(link_n),
|
||||
lambda a, b: ((lxname in a) == (lxname in b)),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare(
|
||||
"llistxattr has symlink key",
|
||||
lambda: os.llistxattr(link_m),
|
||||
lambda: os.llistxattr(link_n),
|
||||
lambda a, b: ((lxname in a) == (lxname in b)),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
err = compare(
|
||||
"lremovexattr symlink",
|
||||
lambda: os.lremovexattr(link_m, lxname),
|
||||
lambda: os.lremovexattr(link_n, lxname),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
err = compare(
|
||||
"lremovexattr symlink",
|
||||
lambda: os.lremovexattr(link_m, lxname),
|
||||
lambda: os.lremovexattr(link_n, lxname),
|
||||
)
|
||||
if err:
|
||||
return fail(err)
|
||||
|
||||
return 0
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -6,6 +6,10 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from posix_parity import cleanup_dir
|
||||
from posix_parity import join
|
||||
from posix_parity import temp_dir
|
||||
|
||||
|
||||
libc = ctypes.CDLL(None, use_errno=True)
|
||||
libc.readlink.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_size_t]
|
||||
@@ -50,62 +54,66 @@ def main():
|
||||
target = "target-abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
with tempfile.TemporaryDirectory() as native_dir:
|
||||
paths = {
|
||||
"merge_link": os.path.join(mount, "readlink-semantics-link"),
|
||||
"native_link": os.path.join(native_dir, "readlink-semantics-link"),
|
||||
"merge_regular": os.path.join(mount, "readlink-semantics-regular"),
|
||||
"native_regular": os.path.join(native_dir, "readlink-semantics-regular"),
|
||||
"merge_notdir": os.path.join(mount, "readlink-semantics-notdir"),
|
||||
"native_notdir": os.path.join(native_dir, "readlink-semantics-notdir"),
|
||||
"merge_loop": os.path.join(mount, "readlink-semantics-loop"),
|
||||
"native_loop": os.path.join(native_dir, "readlink-semantics-loop"),
|
||||
"merge_private_dir": os.path.join(mount, "readlink-semantics-private"),
|
||||
"native_private_dir": os.path.join(native_dir, "readlink-semantics-private"),
|
||||
}
|
||||
paths["merge_private_link"] = os.path.join(paths["merge_private_dir"], "link")
|
||||
paths["native_private_link"] = os.path.join(paths["native_private_dir"], "link")
|
||||
merge_base = temp_dir(mount)
|
||||
try:
|
||||
native_base = join(native_dir, os.path.basename(merge_base))
|
||||
os.makedirs(native_base, exist_ok=True)
|
||||
|
||||
paths = {
|
||||
"merge_link": join(merge_base, "readlink-semantics-link"),
|
||||
"native_link": join(native_base, "readlink-semantics-link"),
|
||||
"merge_regular": join(merge_base, "readlink-semantics-regular"),
|
||||
"native_regular": join(native_base, "readlink-semantics-regular"),
|
||||
"merge_notdir": join(merge_base, "readlink-semantics-notdir"),
|
||||
"native_notdir": join(native_base, "readlink-semantics-notdir"),
|
||||
"merge_loop": join(merge_base, "readlink-semantics-loop"),
|
||||
"native_loop": join(native_base, "readlink-semantics-loop"),
|
||||
"merge_private_dir": join(merge_base, "readlink-semantics-private"),
|
||||
"native_private_dir": join(native_base, "readlink-semantics-private"),
|
||||
}
|
||||
paths["merge_private_link"] = os.path.join(paths["merge_private_dir"], "link")
|
||||
paths["native_private_link"] = os.path.join(paths["native_private_dir"], "link")
|
||||
|
||||
for p in (
|
||||
paths["merge_private_link"],
|
||||
paths["merge_link"],
|
||||
paths["merge_regular"],
|
||||
paths["merge_notdir"],
|
||||
paths["merge_loop"],
|
||||
):
|
||||
try:
|
||||
os.unlink(p)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
for p in (
|
||||
paths["merge_private_link"],
|
||||
paths["merge_link"],
|
||||
paths["merge_regular"],
|
||||
paths["merge_notdir"],
|
||||
paths["merge_loop"],
|
||||
):
|
||||
try:
|
||||
os.unlink(p)
|
||||
os.rmdir(paths["merge_private_dir"])
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
try:
|
||||
os.rmdir(paths["merge_private_dir"])
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except OSError:
|
||||
pass
|
||||
os.symlink(target, paths["merge_link"])
|
||||
os.symlink(target, paths["native_link"])
|
||||
|
||||
os.symlink(target, paths["merge_link"])
|
||||
os.symlink(target, paths["native_link"])
|
||||
with open(paths["merge_regular"], "w", encoding="ascii"):
|
||||
pass
|
||||
with open(paths["native_regular"], "w", encoding="ascii"):
|
||||
pass
|
||||
|
||||
with open(paths["merge_regular"], "w", encoding="ascii"):
|
||||
pass
|
||||
with open(paths["native_regular"], "w", encoding="ascii"):
|
||||
pass
|
||||
with open(paths["merge_notdir"], "w", encoding="ascii"):
|
||||
pass
|
||||
with open(paths["native_notdir"], "w", encoding="ascii"):
|
||||
pass
|
||||
|
||||
with open(paths["merge_notdir"], "w", encoding="ascii"):
|
||||
pass
|
||||
with open(paths["native_notdir"], "w", encoding="ascii"):
|
||||
pass
|
||||
os.symlink("readlink-semantics-loop", paths["merge_loop"])
|
||||
os.symlink("readlink-semantics-loop", paths["native_loop"])
|
||||
|
||||
os.symlink("readlink-semantics-loop", paths["merge_loop"])
|
||||
os.symlink("readlink-semantics-loop", paths["native_loop"])
|
||||
os.makedirs(paths["merge_private_dir"], exist_ok=True)
|
||||
os.makedirs(paths["native_private_dir"], exist_ok=True)
|
||||
os.symlink(target, paths["merge_private_link"])
|
||||
os.symlink(target, paths["native_private_link"])
|
||||
|
||||
os.makedirs(paths["merge_private_dir"], exist_ok=True)
|
||||
os.makedirs(paths["native_private_dir"], exist_ok=True)
|
||||
os.symlink(target, paths["merge_private_link"])
|
||||
os.symlink(target, paths["native_private_link"])
|
||||
|
||||
try:
|
||||
cases = []
|
||||
|
||||
for bufsiz in (0, 1, 2, 5, 8, len(target), len(target) + 1):
|
||||
@@ -127,8 +135,8 @@ def main():
|
||||
),
|
||||
(
|
||||
"ENOENT missing path",
|
||||
os.path.join(mount, "readlink-semantics-missing"),
|
||||
os.path.join(native_dir, "readlink-semantics-missing"),
|
||||
join(merge_base, "readlink-semantics-missing"),
|
||||
join(native_base, "readlink-semantics-missing"),
|
||||
128,
|
||||
errno.ENOENT,
|
||||
),
|
||||
@@ -148,8 +156,8 @@ def main():
|
||||
),
|
||||
(
|
||||
"ENAMETOOLONG long pathname",
|
||||
os.path.join(mount, "a" * 8192),
|
||||
os.path.join(native_dir, "a" * 8192),
|
||||
join(merge_base, "a" * 8192),
|
||||
join(native_base, "a" * 8192),
|
||||
128,
|
||||
errno.ENAMETOOLONG,
|
||||
),
|
||||
@@ -179,6 +187,7 @@ def main():
|
||||
|
||||
return 0
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
try:
|
||||
os.chmod(paths["merge_private_dir"], 0o700)
|
||||
except (FileNotFoundError, PermissionError):
|
||||
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import errno
|
||||
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 temp_dir
|
||||
from posix_parity import touch
|
||||
|
||||
|
||||
def get_errno(func):
|
||||
try:
|
||||
func()
|
||||
return 0
|
||||
except OSError as exc:
|
||||
return exc.errno
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("usage: TEST_rmdir_enotempty_priority <mountpoint>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
mount = sys.argv[1]
|
||||
merge_base = temp_dir(mount)
|
||||
|
||||
try:
|
||||
branches = mergerfs_branches(mount)
|
||||
if len(branches) < 2:
|
||||
return 0
|
||||
|
||||
merge_dir = join(merge_base, "nonempty_dir")
|
||||
os.makedirs(merge_dir)
|
||||
|
||||
child_file = join(merge_dir, "child.txt")
|
||||
branch0 = branches[0]
|
||||
native_child = join(branch0, os.path.relpath(child_file, mount))
|
||||
touch(child_file, b"prevents rmdir")
|
||||
|
||||
err = get_errno(lambda: os.rmdir(merge_dir))
|
||||
if err != errno.ENOTEMPTY:
|
||||
return fail(f"rmdir non-empty dir: expected ENOTEMPTY({errno.ENOTEMPTY}), got {err}")
|
||||
|
||||
os.unlink(child_file)
|
||||
err = get_errno(lambda: os.rmdir(merge_dir))
|
||||
if err != 0:
|
||||
return fail(f"rmdir empty dir after removing child: expected success, got errno={err}")
|
||||
|
||||
return 0
|
||||
except Exception as exc:
|
||||
return fail(str(exc))
|
||||
finally:
|
||||
cleanup_dir(merge_base)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -3,7 +3,9 @@
|
||||
import ctypes
|
||||
import errno
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import tempfile
|
||||
|
||||
|
||||
libc = ctypes.CDLL(None, use_errno=True)
|
||||
@@ -11,6 +13,20 @@ libc.access.argtypes = [ctypes.c_char_p, ctypes.c_int]
|
||||
libc.access.restype = ctypes.c_int
|
||||
|
||||
|
||||
def temp_dir(root):
|
||||
"""Create a temporary directory within the given root directory."""
|
||||
return tempfile.mkdtemp(dir=root)
|
||||
|
||||
|
||||
def cleanup_dir(path):
|
||||
"""Recursively remove a directory and its contents, ignoring errors."""
|
||||
if path and os.path.exists(path):
|
||||
try:
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def invoke(callable_):
|
||||
try:
|
||||
return True, callable_(), 0
|
||||
|
||||
+503
@@ -3,8 +3,11 @@
|
||||
#include "config.hpp"
|
||||
#include "fs_copyfile.hpp"
|
||||
#include "fs_inode.hpp"
|
||||
#include "from_string.hpp"
|
||||
#include "hashset.hpp"
|
||||
#include "num.hpp"
|
||||
#include "rapidhash/rapidhash.h"
|
||||
#include "rnd.hpp"
|
||||
#include "str.hpp"
|
||||
#include "thread_pool.hpp"
|
||||
|
||||
@@ -17,9 +20,11 @@
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -3074,6 +3079,475 @@ test_hashset_put_and_size()
|
||||
TEST_CHECK(set.size() == (int)(names_len + 1));
|
||||
}
|
||||
|
||||
void
|
||||
test_str_eq_nullptr()
|
||||
{
|
||||
TEST_CHECK(str::eq(nullptr, nullptr) == true);
|
||||
TEST_CHECK(str::eq(nullptr, "") == false);
|
||||
TEST_CHECK(str::eq("", nullptr) == false);
|
||||
TEST_CHECK(str::eq(nullptr, "hello") == false);
|
||||
TEST_CHECK(str::eq("hello", nullptr) == false);
|
||||
TEST_CHECK(str::eq(nullptr, nullptr) == true);
|
||||
}
|
||||
|
||||
void
|
||||
test_str_startswith_char_nullptr()
|
||||
{
|
||||
TEST_CHECK(str::startswith((const char*)nullptr, "foo") == false);
|
||||
TEST_CHECK(str::startswith("foo", (const char*)nullptr) == false);
|
||||
TEST_CHECK(str::startswith((const char*)nullptr, (const char*)nullptr) == false);
|
||||
TEST_CHECK(str::startswith("foobar", "foo") == true);
|
||||
TEST_CHECK(str::startswith("foobar", "bar") == false);
|
||||
TEST_CHECK(str::startswith("", "") == true);
|
||||
}
|
||||
|
||||
void
|
||||
test_str_from_u64_suffixes()
|
||||
{
|
||||
u64 val;
|
||||
|
||||
TEST_CHECK(str::from("0", &val) == 0);
|
||||
TEST_CHECK(val == 0);
|
||||
|
||||
TEST_CHECK(str::from("1024", &val) == 0);
|
||||
TEST_CHECK(val == 1024);
|
||||
|
||||
TEST_CHECK(str::from("1K", &val) == 0);
|
||||
TEST_CHECK(val == 1024);
|
||||
TEST_CHECK(str::from("1k", &val) == 0);
|
||||
TEST_CHECK(val == 1024);
|
||||
|
||||
TEST_CHECK(str::from("1M", &val) == 0);
|
||||
TEST_CHECK(val == 1024 * 1024);
|
||||
TEST_CHECK(str::from("1m", &val) == 0);
|
||||
TEST_CHECK(val == 1024 * 1024);
|
||||
|
||||
TEST_CHECK(str::from("1G", &val) == 0);
|
||||
TEST_CHECK(val == 1024ULL * 1024ULL * 1024ULL);
|
||||
TEST_CHECK(str::from("1g", &val) == 0);
|
||||
TEST_CHECK(val == 1024ULL * 1024ULL * 1024ULL);
|
||||
|
||||
TEST_CHECK(str::from("1T", &val) == 0);
|
||||
TEST_CHECK(val == 1024ULL * 1024ULL * 1024ULL * 1024ULL);
|
||||
TEST_CHECK(str::from("1t", &val) == 0);
|
||||
TEST_CHECK(val == 1024ULL * 1024ULL * 1024ULL * 1024ULL);
|
||||
|
||||
TEST_CHECK(str::from("1024B", &val) == 0);
|
||||
TEST_CHECK(val == 1024);
|
||||
TEST_CHECK(str::from("1024b", &val) == 0);
|
||||
TEST_CHECK(val == 1024);
|
||||
|
||||
TEST_CHECK(str::from("2K", &val) == 0);
|
||||
TEST_CHECK(val == 2048);
|
||||
TEST_CHECK(str::from("4M", &val) == 0);
|
||||
TEST_CHECK(val == 4ULL * 1024 * 1024);
|
||||
|
||||
TEST_CHECK(str::from("1P", &val) == -EINVAL);
|
||||
TEST_CHECK(str::from("hello", &val) == -EINVAL);
|
||||
TEST_CHECK(str::from("", &val) == -EINVAL);
|
||||
|
||||
TEST_CHECK(str::from("16777216T", &val) == -EOVERFLOW);
|
||||
|
||||
TEST_CHECK(str::from("9999999999999999999999", &val) == -EINVAL);
|
||||
}
|
||||
|
||||
void
|
||||
test_str_from_s64()
|
||||
{
|
||||
s64 val;
|
||||
|
||||
TEST_CHECK(str::from("0", &val) == 0);
|
||||
TEST_CHECK(val == 0);
|
||||
|
||||
TEST_CHECK(str::from("-1", &val) == 0);
|
||||
TEST_CHECK(val == -1);
|
||||
|
||||
TEST_CHECK(str::from("1K", &val) == 0);
|
||||
TEST_CHECK(val == 1024);
|
||||
|
||||
TEST_CHECK(str::from("1M", &val) == 0);
|
||||
TEST_CHECK(val == 1024 * 1024);
|
||||
|
||||
TEST_CHECK(str::from("1G", &val) == 0);
|
||||
TEST_CHECK(val == 1024LL * 1024 * 1024);
|
||||
|
||||
TEST_CHECK(str::from("1T", &val) == 0);
|
||||
TEST_CHECK(val == 1024LL * 1024 * 1024 * 1024);
|
||||
|
||||
TEST_CHECK(str::from("1P", &val) == -EINVAL);
|
||||
TEST_CHECK(str::from("abc", &val) == -EINVAL);
|
||||
}
|
||||
|
||||
void
|
||||
test_str_from_int()
|
||||
{
|
||||
int val;
|
||||
|
||||
TEST_CHECK(str::from("0", &val) == 0);
|
||||
TEST_CHECK(val == 0);
|
||||
|
||||
TEST_CHECK(str::from("123", &val) == 0);
|
||||
TEST_CHECK(val == 123);
|
||||
|
||||
TEST_CHECK(str::from("-456", &val) == 0);
|
||||
TEST_CHECK(val == -456);
|
||||
|
||||
TEST_CHECK(str::from("1K", &val) == 0);
|
||||
TEST_CHECK(val == 1);
|
||||
TEST_CHECK(str::from("abc", &val) == -EINVAL);
|
||||
}
|
||||
|
||||
void
|
||||
test_num_humanize()
|
||||
{
|
||||
TEST_CHECK(num::humanize(0) == "0");
|
||||
TEST_CHECK(num::humanize(512) == "512");
|
||||
TEST_CHECK(num::humanize(1024) == "1K");
|
||||
TEST_CHECK(num::humanize(2048) == "2K");
|
||||
TEST_CHECK(num::humanize(1024 * 1024) == "1M");
|
||||
TEST_CHECK(num::humanize(2 * 1024 * 1024) == "2M");
|
||||
TEST_CHECK(num::humanize(1024ULL * 1024 * 1024) == "1G");
|
||||
TEST_CHECK(num::humanize(2ULL * 1024 * 1024 * 1024) == "2G");
|
||||
TEST_CHECK(num::humanize(1024ULL * 1024 * 1024 * 1024) == "1T");
|
||||
TEST_CHECK(num::humanize(2ULL * 1024 * 1024 * 1024 * 1024) == "2T");
|
||||
TEST_CHECK(num::humanize(1536) == "1536");
|
||||
TEST_CHECK(num::humanize(1025) == "1025");
|
||||
}
|
||||
|
||||
struct RmdirErr
|
||||
{
|
||||
private:
|
||||
std::optional<int> _err;
|
||||
|
||||
public:
|
||||
RmdirErr() {}
|
||||
|
||||
operator int()
|
||||
{
|
||||
return (_err.has_value() ? _err.value() : -ENOENT);
|
||||
}
|
||||
|
||||
RmdirErr&
|
||||
operator=(int v_)
|
||||
{
|
||||
if(!_err.has_value())
|
||||
_err = ((v_ >= 0) ? 0 : v_);
|
||||
else if((v_ == -EEXIST) || (v_ == -ENOTEMPTY))
|
||||
_err = v_;
|
||||
else if((*_err == -EEXIST) || (*_err == -ENOTEMPTY))
|
||||
;
|
||||
else if(v_ >= 0)
|
||||
_err = 0;
|
||||
else
|
||||
_err = v_;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(int v_)
|
||||
{
|
||||
if(_err.has_value())
|
||||
return (_err.value() == v_);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
test_rmdir_err_default_is_enoent()
|
||||
{
|
||||
RmdirErr err;
|
||||
TEST_CHECK((int)err == -ENOENT);
|
||||
}
|
||||
|
||||
void
|
||||
test_rmdir_err_first_success_sets_zero()
|
||||
{
|
||||
RmdirErr err;
|
||||
err = 0;
|
||||
TEST_CHECK((int)err == 0);
|
||||
}
|
||||
|
||||
void
|
||||
test_rmdir_err_first_error_sets_value()
|
||||
{
|
||||
RmdirErr err;
|
||||
err = -EACCES;
|
||||
TEST_CHECK((int)err == -EACCES);
|
||||
}
|
||||
|
||||
void
|
||||
test_rmdir_err_enotempty_overwrites_other()
|
||||
{
|
||||
RmdirErr err;
|
||||
err = -EACCES;
|
||||
err = -ENOTEMPTY;
|
||||
TEST_CHECK((int)err == -ENOTEMPTY);
|
||||
}
|
||||
|
||||
void
|
||||
test_rmdir_err_eexist_overwrites_other()
|
||||
{
|
||||
RmdirErr err;
|
||||
err = -EACCES;
|
||||
err = -EEXIST;
|
||||
TEST_CHECK((int)err == -EEXIST);
|
||||
}
|
||||
|
||||
void
|
||||
test_rmdir_err_enotempty_not_overwritten_by_other()
|
||||
{
|
||||
RmdirErr err;
|
||||
err = -ENOTEMPTY;
|
||||
err = -EACCES;
|
||||
TEST_CHECK((int)err == -ENOTEMPTY);
|
||||
}
|
||||
|
||||
void
|
||||
test_rmdir_err_eexist_not_overwritten_by_other()
|
||||
{
|
||||
RmdirErr err;
|
||||
err = -EEXIST;
|
||||
err = -EACCES;
|
||||
TEST_CHECK((int)err == -EEXIST);
|
||||
}
|
||||
|
||||
void
|
||||
test_rmdir_err_success_does_not_overwrite_priority()
|
||||
{
|
||||
RmdirErr err;
|
||||
err = -ENOTEMPTY;
|
||||
err = 0;
|
||||
TEST_CHECK((int)err == -ENOTEMPTY);
|
||||
|
||||
RmdirErr err2;
|
||||
err2 = -EEXIST;
|
||||
err2 = 0;
|
||||
TEST_CHECK((int)err2 == -EEXIST);
|
||||
}
|
||||
|
||||
void
|
||||
test_rmdir_err_success_overwrites_generic_error()
|
||||
{
|
||||
RmdirErr err;
|
||||
err = -EACCES;
|
||||
err = 0;
|
||||
TEST_CHECK((int)err == 0);
|
||||
}
|
||||
|
||||
void
|
||||
test_rmdir_err_enotempty_overwrites_enotempty()
|
||||
{
|
||||
RmdirErr err;
|
||||
err = -ENOTEMPTY;
|
||||
err = -EEXIST;
|
||||
TEST_CHECK((int)err == -EEXIST);
|
||||
}
|
||||
|
||||
void
|
||||
test_hashset_inline_to_heap_growth()
|
||||
{
|
||||
HashSet set;
|
||||
|
||||
for(int i = 0; i < 20; ++i)
|
||||
{
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "key_%d", i);
|
||||
TEST_CHECK(set.put(buf) == 1);
|
||||
}
|
||||
|
||||
TEST_CHECK(set.size() == 20);
|
||||
|
||||
for(int i = 0; i < 20; ++i)
|
||||
{
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "key_%d", i);
|
||||
TEST_CHECK(set.put(buf) == 0);
|
||||
}
|
||||
|
||||
TEST_CHECK(set.size() == 20);
|
||||
}
|
||||
|
||||
void
|
||||
test_hashset_empty_string()
|
||||
{
|
||||
HashSet set;
|
||||
|
||||
TEST_CHECK(set.put("") == 1);
|
||||
TEST_CHECK(set.put("") == 0);
|
||||
TEST_CHECK(set.size() == 1);
|
||||
}
|
||||
|
||||
void
|
||||
test_hashset_many_items()
|
||||
{
|
||||
HashSet set;
|
||||
|
||||
for(int i = 0; i < 100; ++i)
|
||||
{
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "longer_key_name_%d", i);
|
||||
TEST_CHECK(set.put(buf) == 1);
|
||||
}
|
||||
|
||||
TEST_CHECK(set.size() == 100);
|
||||
|
||||
for(int i = 0; i < 100; ++i)
|
||||
{
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "longer_key_name_%d", i);
|
||||
TEST_CHECK(set.put(buf) == 0);
|
||||
}
|
||||
|
||||
TEST_CHECK(set.size() == 100);
|
||||
}
|
||||
|
||||
void
|
||||
test_fs_inode_set_algo_valid()
|
||||
{
|
||||
const std::vector<std::string> algos = {
|
||||
"passthrough",
|
||||
"path-hash",
|
||||
"path-hash32",
|
||||
"devino-hash",
|
||||
"devino-hash32",
|
||||
"hybrid-hash",
|
||||
"hybrid-hash32",
|
||||
};
|
||||
|
||||
for(const auto &algo : algos)
|
||||
{
|
||||
TEST_CHECK(fs::inode::set_algo(algo) == 0);
|
||||
TEST_CHECK(fs::inode::get_algo() == algo);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_fs_inode_set_algo_invalid()
|
||||
{
|
||||
TEST_CHECK(fs::inode::set_algo("nope") == -EINVAL);
|
||||
TEST_CHECK(fs::inode::set_algo("") == -EINVAL);
|
||||
TEST_CHECK(fs::inode::set_algo("HYBRID-HASH") == -EINVAL);
|
||||
}
|
||||
|
||||
void
|
||||
test_fs_inode_passthrough_returns_raw_ino()
|
||||
{
|
||||
TEST_CHECK(fs::inode::set_algo("passthrough") == 0);
|
||||
|
||||
const fs::path branch("/mnt/disk1");
|
||||
const fs::path fusepath("some/file");
|
||||
|
||||
TEST_CHECK(fs::inode::calc(branch, fusepath, S_IFREG, 42) == 42);
|
||||
TEST_CHECK(fs::inode::calc(branch, fusepath, S_IFDIR, 999) == 999);
|
||||
TEST_CHECK(fs::inode::calc(branch, fusepath, S_IFREG, 1) == 1);
|
||||
}
|
||||
|
||||
void
|
||||
test_fs_inode_different_algos_produce_distinct_inodes()
|
||||
{
|
||||
const fs::path branch("/mnt/disk1");
|
||||
const fs::path fusepath("some/file");
|
||||
const std::vector<std::string> algos = {
|
||||
"path-hash",
|
||||
"devino-hash",
|
||||
"hybrid-hash",
|
||||
};
|
||||
|
||||
std::vector<u64> inodes;
|
||||
for(const auto &algo : algos)
|
||||
{
|
||||
TEST_CHECK(fs::inode::set_algo(algo) == 0);
|
||||
inodes.push_back(fs::inode::calc(branch, fusepath, S_IFREG, 12345));
|
||||
}
|
||||
|
||||
for(size_t i = 1; i < inodes.size(); ++i)
|
||||
TEST_CHECK(inodes[i] != inodes[0]);
|
||||
}
|
||||
|
||||
void
|
||||
test_rnd_rand64_basic()
|
||||
{
|
||||
u64 v1 = RND::rand64();
|
||||
u64 v2 = RND::rand64();
|
||||
TEST_CHECK(v1 != v2 || true);
|
||||
}
|
||||
|
||||
void
|
||||
test_rnd_rand64_max()
|
||||
{
|
||||
for(int i = 0; i < 1000; ++i)
|
||||
{
|
||||
u64 v = RND::rand64(100);
|
||||
TEST_CHECK(v < 100);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_rnd_rand64_range()
|
||||
{
|
||||
for(int i = 0; i < 1000; ++i)
|
||||
{
|
||||
u64 v = RND::rand64(10, 50);
|
||||
TEST_CHECK(v >= 10);
|
||||
TEST_CHECK(v < 50);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_rnd_shrink_to_rand_elem()
|
||||
{
|
||||
std::vector<int> v = {1};
|
||||
RND::shrink_to_rand_elem(v);
|
||||
TEST_CHECK(v.size() == 1);
|
||||
|
||||
std::vector<int> v2;
|
||||
RND::shrink_to_rand_elem(v2);
|
||||
TEST_CHECK(v2.empty());
|
||||
}
|
||||
|
||||
void
|
||||
test_branch_copy_assignment_relinks_default_minfreespace()
|
||||
{
|
||||
uint64_t default_a = 1234;
|
||||
uint64_t default_b = 5678;
|
||||
Branches::Impl impl_a(&default_a);
|
||||
Branches::Impl impl_b(&default_b);
|
||||
|
||||
TEST_CHECK(impl_a.from_string("/a") == 0);
|
||||
TEST_CHECK(impl_b.from_string("/b") == 0);
|
||||
|
||||
impl_a = impl_b;
|
||||
|
||||
TEST_CHECK(impl_a.size() == 1);
|
||||
for(auto &branch : impl_a)
|
||||
{
|
||||
if(std::holds_alternative<const u64*>(branch._minfreespace))
|
||||
TEST_CHECK(std::get<const u64*>(branch._minfreespace) == &default_a);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_branch_move_assignment_relinks_default_minfreespace()
|
||||
{
|
||||
uint64_t default_a = 1234;
|
||||
uint64_t default_b = 5678;
|
||||
Branches::Impl impl_a(&default_a);
|
||||
Branches::Impl impl_b(&default_b);
|
||||
|
||||
TEST_CHECK(impl_a.from_string("/a") == 0);
|
||||
TEST_CHECK(impl_b.from_string("/b") == 0);
|
||||
|
||||
impl_a = std::move(impl_b);
|
||||
|
||||
TEST_CHECK(impl_a.size() == 1);
|
||||
for(auto &branch : impl_a)
|
||||
{
|
||||
if(std::holds_alternative<const u64*>(branch._minfreespace))
|
||||
TEST_CHECK(std::get<const u64*>(branch._minfreespace) == &default_a);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_rapidhash_withSeed_preserves_default_output()
|
||||
{
|
||||
@@ -3218,6 +3692,35 @@ TEST_LIST =
|
||||
{"config_prune_cmd_xattr",test_config_prune_cmd_xattr},
|
||||
{"fs_copyfile_basic",test_fs_copyfile_basic},
|
||||
{"fs_copyfile_source_changes_cleanup_tmpfiles",test_fs_copyfile_source_changes_cleanup_tmpfiles},
|
||||
{"str_eq_nullptr",test_str_eq_nullptr},
|
||||
{"str_startswith_char_nullptr",test_str_startswith_char_nullptr},
|
||||
{"str_from_u64_suffixes",test_str_from_u64_suffixes},
|
||||
{"str_from_s64",test_str_from_s64},
|
||||
{"str_from_int",test_str_from_int},
|
||||
{"num_humanize",test_num_humanize},
|
||||
{"rmdir_err_default_is_enoent",test_rmdir_err_default_is_enoent},
|
||||
{"rmdir_err_first_success_sets_zero",test_rmdir_err_first_success_sets_zero},
|
||||
{"rmdir_err_first_error_sets_value",test_rmdir_err_first_error_sets_value},
|
||||
{"rmdir_err_enotempty_overwrites_other",test_rmdir_err_enotempty_overwrites_other},
|
||||
{"rmdir_err_eexist_overwrites_other",test_rmdir_err_eexist_overwrites_other},
|
||||
{"rmdir_err_enotempty_not_overwritten_by_other",test_rmdir_err_enotempty_not_overwritten_by_other},
|
||||
{"rmdir_err_eexist_not_overwritten_by_other",test_rmdir_err_eexist_not_overwritten_by_other},
|
||||
{"rmdir_err_success_does_not_overwrite_priority",test_rmdir_err_success_does_not_overwrite_priority},
|
||||
{"rmdir_err_success_overwrites_generic_error",test_rmdir_err_success_overwrites_generic_error},
|
||||
{"rmdir_err_enotempty_overwrites_enotempty",test_rmdir_err_enotempty_overwrites_enotempty},
|
||||
{"hashset_inline_to_heap_growth",test_hashset_inline_to_heap_growth},
|
||||
{"hashset_empty_string",test_hashset_empty_string},
|
||||
{"hashset_many_items",test_hashset_many_items},
|
||||
{"fs_inode_set_algo_valid",test_fs_inode_set_algo_valid},
|
||||
{"fs_inode_set_algo_invalid",test_fs_inode_set_algo_invalid},
|
||||
{"fs_inode_passthrough_returns_raw_ino",test_fs_inode_passthrough_returns_raw_ino},
|
||||
{"fs_inode_different_algos_produce_distinct_inodes",test_fs_inode_different_algos_produce_distinct_inodes},
|
||||
{"rnd_rand64_basic",test_rnd_rand64_basic},
|
||||
{"rnd_rand64_max",test_rnd_rand64_max},
|
||||
{"rnd_rand64_range",test_rnd_rand64_range},
|
||||
{"rnd_shrink_to_rand_elem",test_rnd_shrink_to_rand_elem},
|
||||
{"branch_copy_assignment_relinks_default_minfreespace",test_branch_copy_assignment_relinks_default_minfreespace},
|
||||
{"branch_move_assignment_relinks_default_minfreespace",test_branch_move_assignment_relinks_default_minfreespace},
|
||||
{"str",test_str_stuff},
|
||||
{"tp_construct_default",test_tp_construct_default},
|
||||
{"tp_construct_named",test_tp_construct_named},
|
||||
|
||||
Reference in New Issue
Block a user