if (fuse_session_mount(se, fuse_opts.mountpoint))
die("fuse_mount err: %m");
+ /* This print statement is a trigger for tests. */
+ printf("Fuse mount initialized.\n");
+
fuse_daemonize(fuse_opts.foreground);
ret = fuse_session_loop(se);
--- /dev/null
+#!/usr/bin/python3
+#
+# pytest fixture definitions.
+
+import pytest
+import util
+
+@pytest.fixture
+def bfuse(tmpdir):
+ '''A test requesting a "bfuse" is given one via this fixture.'''
+
+ dev = util.format_1g(tmpdir)
+ mnt = util.mountpoint(tmpdir)
+ bf = util.BFuse(dev, mnt)
+
+ yield bf
+
+ if bf.returncode is None:
+ bf.unmount(timeout=5.0)
assert len(ret.stderr) == 0
def test_fsck(tmpdir):
- dev = util.device_1g(tmpdir)
- util.run_bch('format', dev, valgrind=False, check=True)
+ dev = util.format_1g(tmpdir)
ret = util.run_bch('fsck', dev, valgrind=True)
assert len(ret.stderr) == 0
def test_list(tmpdir):
- dev = util.device_1g(tmpdir)
- util.run_bch('format', dev, valgrind=False, check=True)
+ dev = util.format_1g(tmpdir)
ret = util.run_bch('list', dev, valgrind=True)
assert len(ret.stdout.splitlines()) == 2
def test_list_inodes(tmpdir):
- dev = util.device_1g(tmpdir)
- util.run_bch('format', dev, valgrind=False, check=True)
+ dev = util.format_1g(tmpdir)
ret = util.run_bch('list', '-b', 'inodes', dev, valgrind=True)
assert len(ret.stdout.splitlines()) == (2 + 2) # 2 inodes on clean format
def test_list_dirent(tmpdir):
- dev = util.device_1g(tmpdir)
- util.run_bch('format', dev, valgrind=False, check=True)
+ dev = util.format_1g(tmpdir)
ret = util.run_bch('list', '-b', 'dirents', dev, valgrind=True)
import pytest
import signal
import subprocess
+import time
import util
from pathlib import Path
def test_write_after_free():
with pytest.raises(util.ValgrindFailedError):
ret = util.run(helper, 'write_after_free', valgrind=True)
+
+def test_mountpoint(tmpdir):
+ path = util.mountpoint(tmpdir)
+ assert str(path)[-4:] == '/mnt'
+ assert path.is_dir()
+
+def test_timestamp():
+ t1 = time.clock_gettime(time.CLOCK_REALTIME)
+ with util.Timestamp() as ts:
+ t2 = time.clock_gettime(time.CLOCK_REALTIME)
+ t3 = time.clock_gettime(time.CLOCK_REALTIME)
+
+ assert not ts.contains(t1)
+ assert ts.contains(t2)
+ assert not ts.contains(t3)
--- /dev/null
+#!/usr/bin/python3
+#
+# Tests of the fuse mount functionality.
+
+import os
+import util
+
+def test_mount(bfuse):
+ bfuse.mount()
+ bfuse.unmount()
+ bfuse.verify()
+
+def test_lostfound(bfuse):
+ bfuse.mount()
+
+ lf = bfuse.mnt / "lost+found"
+ assert lf.is_dir()
+
+ st = lf.stat()
+ assert st.st_mode == 0o40700
+
+ bfuse.unmount()
+ bfuse.verify()
+
+def test_create(bfuse):
+ bfuse.mount()
+
+ path = bfuse.mnt / "file"
+
+ with util.Timestamp() as ts:
+ fd = os.open(path, os.O_CREAT, 0o700)
+
+ assert fd >= 0
+
+ os.close(fd)
+ assert path.is_file()
+
+ # Verify file.
+ st = path.stat()
+ assert st.st_mode == 0o100700
+ assert st.st_mtime == st.st_ctime
+ assert st.st_mtime == st.st_atime
+ assert ts.contains(st.st_mtime)
+
+ # Verify dir.
+ dst = bfuse.mnt.stat()
+ assert dst.st_mtime == dst.st_ctime
+ assert ts.contains(dst.st_mtime)
+
+ bfuse.unmount()
+ bfuse.verify()
+
+def test_mkdir(bfuse):
+ bfuse.mount()
+
+ path = bfuse.mnt / "dir"
+
+ with util.Timestamp() as ts:
+ os.mkdir(path, 0o700)
+
+ assert path.is_dir()
+
+ # Verify child.
+ st = path.stat()
+ assert st.st_mode == 0o40700
+ assert st.st_mtime == st.st_ctime
+ assert st.st_mtime == st.st_atime
+ assert ts.contains(st.st_mtime)
+
+ # Verify parent.
+ dst = bfuse.mnt.stat()
+ assert dst.st_mtime == dst.st_ctime
+ assert ts.contains(dst.st_mtime)
+
+ bfuse.unmount()
+ bfuse.verify()
+
+def test_unlink(bfuse):
+ bfuse.mount()
+
+ path = bfuse.mnt / "file"
+ path.touch(mode=0o600, exist_ok=False)
+
+ with util.Timestamp() as ts:
+ os.unlink(path)
+
+ assert not path.exists()
+
+ # Verify dir.
+ dst = bfuse.mnt.stat()
+ assert dst.st_mtime == dst.st_ctime
+ assert ts.contains(dst.st_mtime)
+
+ bfuse.unmount()
+ bfuse.verify()
+
+def test_rmdir(bfuse):
+ bfuse.mount()
+
+ path = bfuse.mnt / "dir"
+ path.mkdir(mode=0o700, exist_ok=False)
+
+ with util.Timestamp() as ts:
+ os.rmdir(path)
+
+ assert not path.exists()
+
+ # Verify dir.
+ dst = bfuse.mnt.stat()
+ assert dst.st_mtime == dst.st_ctime
+ assert ts.contains(dst.st_mtime)
+
+ bfuse.unmount()
+ bfuse.verify()
+
+def test_rename(bfuse):
+ bfuse.mount()
+
+ srcdir = bfuse.mnt
+
+ path = srcdir / "file"
+ path.touch(mode=0o600, exist_ok=False)
+
+ destdir = srcdir / "dir"
+ destdir.mkdir(mode=0o700, exist_ok=False)
+
+ destpath = destdir / "file"
+
+ path_pre_st = path.stat()
+
+ with util.Timestamp() as ts:
+ os.rename(path, destpath)
+
+ assert not path.exists()
+ assert destpath.is_file()
+
+ # Verify dirs.
+ src_st = srcdir.stat()
+ assert src_st.st_mtime == src_st.st_ctime
+ assert ts.contains(src_st.st_mtime)
+
+ dest_st = destdir.stat()
+ assert dest_st.st_mtime == dest_st.st_ctime
+ assert ts.contains(dest_st.st_mtime)
+
+ # Verify file.
+ path_post_st = destpath.stat()
+ assert path_post_st.st_mtime == path_pre_st.st_mtime
+ assert path_post_st.st_atime == path_pre_st.st_atime
+ assert ts.contains(path_post_st.st_ctime)
+
+ bfuse.unmount()
+ bfuse.verify()
+
+def test_link(bfuse):
+ bfuse.mount()
+
+ srcdir = bfuse.mnt
+
+ path = srcdir / "file"
+ path.touch(mode=0o600, exist_ok=False)
+
+ destdir = srcdir / "dir"
+ destdir.mkdir(mode=0o700, exist_ok=False)
+
+ destpath = destdir / "file"
+
+ path_pre_st = path.stat()
+ srcdir_pre_st = srcdir.stat()
+
+ with util.Timestamp() as ts:
+ os.link(path, destpath)
+
+ assert path.exists()
+ assert destpath.is_file()
+
+ # Verify source dir is unchanged.
+ srcdir_post_st = srcdir.stat()
+ assert srcdir_pre_st == srcdir_post_st
+
+ # Verify dest dir.
+ destdir_st = destdir.stat()
+ assert destdir_st.st_mtime == destdir_st.st_ctime
+ assert ts.contains(destdir_st.st_mtime)
+
+ # Verify file.
+ path_post_st = path.stat()
+ destpath_post_st = destpath.stat()
+ assert path_post_st == destpath_post_st
+
+ assert path_post_st.st_mtime == path_pre_st.st_mtime
+ assert path_post_st.st_atime == path_pre_st.st_atime
+ assert ts.contains(path_post_st.st_ctime)
+
+ bfuse.unmount()
+ bfuse.verify()
+
+def test_write(bfuse):
+ bfuse.mount()
+
+ path = bfuse.mnt / "file"
+ path.touch(mode=0o600, exist_ok=False)
+
+ pre_st = path.stat()
+
+ fd = os.open(path, os.O_WRONLY)
+ assert fd >= 0
+
+ with util.Timestamp() as ts:
+ written = os.write(fd, b'test')
+
+ os.close(fd)
+
+ assert written == 4
+
+ post_st = path.stat()
+ assert post_st.st_atime == pre_st.st_atime
+ assert post_st.st_mtime == post_st.st_ctime
+ assert ts.contains(post_st.st_mtime)
+
+ assert path.read_bytes() == b'test'
#!/usr/bin/python3
import os
+import pytest
import re
import subprocess
import tempfile
+import threading
+import time
+
from pathlib import Path
DIR = Path('..')
"""Default 1g sparse file for use with bcachefs."""
path = tmpdir / 'dev-1g'
return sparse_file(path, 1024**3)
+
+def format_1g(tmpdir):
+ """Format a default filesystem on a 1g device."""
+ dev = device_1g(tmpdir)
+ run_bch('format', dev, check=True)
+ return dev
+
+def mountpoint(tmpdir):
+ """Construct a mountpoint "mnt" for tests."""
+ path = Path(tmpdir) / 'mnt'
+ path.mkdir(mode = 0o700)
+ return path
+
+class Timestamp:
+ '''Context manager to assist in verifying timestamps.
+
+ Records the range of times which would be valid for an encoded operation to
+ use.
+
+ FIXME: The kernel code is currently using CLOCK_REALTIME_COARSE, but python
+ didn't expose this time API (yet). Probably the kernel shouldn't be using
+ _COARSE anyway, but this might lead to occasional errors.
+
+ To make sure this doesn't happen, we sleep a fraction of a second in an
+ attempt to guarantee containment.
+
+ N.B. this might be better tested by overriding the clock used in bcachefs.
+
+ '''
+ def __init__(self):
+ self.start = None
+ self.end = None
+
+ def __enter__(self):
+ self.start = time.clock_gettime(time.CLOCK_REALTIME)
+ time.sleep(0.1)
+ return self
+
+ def __exit__(self, type, value, traceback):
+ time.sleep(0.1)
+ self.end = time.clock_gettime(time.CLOCK_REALTIME)
+
+ def contains(self, test):
+ '''True iff the test time is within the range.'''
+ return self.start <= test <= self.end
+
+class FuseError(Exception):
+ def __init__(self, msg):
+ self.msg = msg
+
+class BFuse(threading.Thread):
+ '''bcachefs fuse runner.
+
+ This class runs bcachefs in fusemount mode, and waits until the mount has
+ reached a point suitable for testing the filesystem.
+
+ bcachefs is run under valgrind by default, and is checked for errors.
+ '''
+
+ def __init__(self, dev, mnt):
+ threading.Thread.__init__(self)
+ self.dev = dev
+ self.mnt = mnt
+ self.ready = threading.Event()
+ self.proc = None
+ self.returncode = None
+ self.stdout = None
+ self.stderr = None
+ self.vout = None
+
+ def run(self):
+ """Background thread which runs "bcachefs fusemount" under valgrind"""
+
+ vout = tempfile.NamedTemporaryFile()
+ cmd = [ 'valgrind',
+ '--leak-check=full',
+ '--log-file={}'.format(vout.name),
+ BCH_PATH,
+ 'fusemount', '-f', self.dev, self.mnt]
+
+ print("Running {}".format(cmd))
+
+ err = tempfile.TemporaryFile()
+ self.proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=err,
+ encoding='utf-8')
+
+ out1 = self.expect(self.proc.stdout, r'^Fuse mount initialized.$')
+ self.ready.set()
+
+ print("Waiting for process.")
+ (out2, _) = self.proc.communicate()
+ print("Process exited.")
+
+ self.stdout = out1 + out2
+ self.stderr = err.read()
+ self.returncode = self.proc.returncode
+ self.vout = vout
+
+ def expect(self, pipe, regex):
+ """Wait for the child process to mount."""
+
+ c = re.compile(regex)
+
+ out = ""
+ for line in pipe:
+ print('Expect line "{}"'.format(line.rstrip()))
+ out += line
+ if c.match(line):
+ print("Matched.")
+ return out
+
+ raise FuseError('stdout did not contain regex "{}"'.format(regex))
+
+ def mount(self):
+ print("Starting fuse thread.")
+ self.start()
+ self.ready.wait()
+ print("Fuse is mounted.")
+
+ def unmount(self, timeout=None):
+ print("Unmounting fuse.")
+ run("fusermount3", "-zu", self.mnt)
+ print("Waiting for thread to exit.")
+
+ self.join(timeout)
+ if self.isAlive():
+ self.proc.kill()
+ self.join()
+
+ check_valgrind(self.vout)
+
+ def verify(self):
+ assert self.returncode == 0
+ assert len(self.stdout) > 0
+ assert len(self.stderr) == 0