132 lines
3.4 KiB
Python
132 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
import subprocess
|
|
import pytest
|
|
import os
|
|
import stat
|
|
import time
|
|
from os.path import join as pjoin
|
|
from contextlib import contextmanager
|
|
|
|
basename = pjoin(os.path.dirname(__file__), "..")
|
|
|
|
|
|
def os_create(name):
|
|
os.close(os.open(name, os.O_CREAT | os.O_RDWR))
|
|
|
|
|
|
@contextmanager
|
|
def os_open(name, flags):
|
|
fd = os.open(name, flags)
|
|
try:
|
|
yield fd
|
|
finally:
|
|
os.close(fd)
|
|
|
|
|
|
def wait_for_mount(mount_process, mnt_dir, test_fn=os.path.ismount):
|
|
elapsed = 0
|
|
while elapsed < 30:
|
|
if test_fn(mnt_dir):
|
|
return True
|
|
if mount_process.poll() is not None:
|
|
pytest.fail("file system process terminated prematurely")
|
|
time.sleep(0.1)
|
|
elapsed += 0.1
|
|
pytest.fail("mountpoint failed to come up")
|
|
|
|
|
|
def cleanup(mount_process, mnt_dir):
|
|
subprocess.call(
|
|
["fusermount", "-z", "-u", mnt_dir],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.STDOUT,
|
|
)
|
|
mount_process.terminate()
|
|
try:
|
|
mount_process.wait(1)
|
|
except subprocess.TimeoutExpired:
|
|
mount_process.kill()
|
|
|
|
|
|
def umount(mount_process, mnt_dir):
|
|
subprocess.check_call(["fusermount3", "-z", "-u", mnt_dir])
|
|
assert not os.path.ismount(mnt_dir)
|
|
|
|
# Give mount process a little while to terminate. Popen.wait(timeout)
|
|
# was only added in 3.3...
|
|
elapsed = 0
|
|
while elapsed < 30:
|
|
code = mount_process.poll()
|
|
if code is not None:
|
|
if code == 0:
|
|
return
|
|
pytest.fail(f"file system process terminated with code {code}")
|
|
time.sleep(0.1)
|
|
elapsed += 0.1
|
|
pytest.fail("mount process did not terminate")
|
|
|
|
|
|
def safe_sleep(secs):
|
|
"""Like time.sleep(), but sleep for at least *secs*
|
|
|
|
`time.sleep` may sleep less than the given period if a signal is
|
|
received. This function ensures that we sleep for at least the
|
|
desired time.
|
|
"""
|
|
|
|
now = time.time()
|
|
end = now + secs
|
|
while now < end:
|
|
time.sleep(end - now)
|
|
now = time.time()
|
|
|
|
|
|
def fuse_test_marker():
|
|
"""Return a pytest.marker that indicates FUSE availability
|
|
|
|
If system/user/environment does not support FUSE, return
|
|
a `pytest.mark.skip` object with more details. If FUSE is
|
|
supported, return `pytest.mark.uses_fuse()`.
|
|
"""
|
|
|
|
def skip(reason: str):
|
|
return pytest.mark.skip(reason=reason)
|
|
|
|
with subprocess.Popen(
|
|
["which", "fusermount"], stdout=subprocess.PIPE, universal_newlines=True
|
|
) as which:
|
|
fusermount_path = which.communicate()[0].strip()
|
|
|
|
if not fusermount_path or which.returncode != 0:
|
|
return skip("Can't find fusermount executable")
|
|
|
|
if not os.path.exists("/dev/fuse"):
|
|
return skip("FUSE kernel module does not seem to be loaded")
|
|
|
|
if os.getuid() == 0:
|
|
return pytest.mark.uses_fuse()
|
|
|
|
mode = os.stat(fusermount_path).st_mode
|
|
if mode & stat.S_ISUID == 0:
|
|
return skip("fusermount executable not setuid, and we are not root.")
|
|
|
|
try:
|
|
fd = os.open("/dev/fuse", os.O_RDWR)
|
|
except OSError as exc:
|
|
return skip(f"Unable to open /dev/fuse: {exc.strerror}")
|
|
else:
|
|
os.close(fd)
|
|
|
|
return pytest.mark.uses_fuse()
|
|
|
|
|
|
# Use valgrind if requested
|
|
if os.environ.get("TEST_WITH_VALGRIND", "no").lower().strip() not in (
|
|
"no",
|
|
"false",
|
|
"0",
|
|
):
|
|
base_cmdline = ["valgrind", "-q", "--"]
|
|
else:
|
|
base_cmdline = []
|