diff --git a/Makefile b/Makefile index 01a8177a9..7a1faa15c 100644 --- a/Makefile +++ b/Makefile @@ -152,9 +152,6 @@ all: bcachefs $(optional_build) debug: CFLAGS+=-Werror -DCONFIG_BCACHEFS_DEBUG=y -DCONFIG_VALGRIND=y debug: bcachefs -.PHONY: tests -tests: tests/test_helper - .PHONY: TAGS tags TAGS: ctags -e -R . @@ -178,14 +175,10 @@ RUST_SRCS:=$(shell find src bch_bindgen/src -type f -iname '*.rs') bcachefs: $(BCACHEFS_DEPS) $(RUST_SRCS) $(Q)$(CARGO_BUILD) -libbcachefs.a: $(filter-out ./tests/%.o, $(OBJS)) +libbcachefs.a: $(OBJS) @echo " [AR] $@" $(Q)$(AR) -rc $@ $+ -tests/test_helper: $(filter ./tests/%.o, $(OBJS)) - @echo " [LD] $@" - $(Q)$(CC) $(LDFLAGS) $+ $(LOADLIBES) $(LDLIBS) -o $@ - # If the version string differs from the last build, update the last version ifneq ($(VERSION),$(shell cat .version 2>/dev/null)) .PHONY: .version @@ -225,7 +218,7 @@ install_systemd: $(systemd_services) $(systemd_libexecfiles) .PHONY: clean clean: @echo "Cleaning all" - $(Q)$(RM) libbcachefs.a c_src/libbcachefs.a tests/test_helper .version *.tar.xz $(OBJS) $(DEPS) $(DOCGENERATED) + $(Q)$(RM) libbcachefs.a c_src/libbcachefs.a .version *.tar.xz $(OBJS) $(DEPS) $(DOCGENERATED) $(Q)$(CARGO_CLEAN) $(Q)$(RM) -f $(built_scripts) diff --git a/debian/control b/debian/control index b8dfcba9a..9e5ff022a 100644 --- a/debian/control +++ b/debian/control @@ -9,7 +9,6 @@ Build-Depends: debhelper-compat (= 13), python3:native, pkgconf, python3-docutils, - python3-pytest, libaio-dev, libfuse3-dev, libblkid-dev, diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index ffc2bad28..000000000 --- a/tests/conftest.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/python3 -# -# pytest fixture definitions. - -import pytest -from tests 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 - - bf.unmount(timeout=5.0) diff --git a/tests/test_basic.py b/tests/test_basic.py deleted file mode 100644 index ae8b4fdb0..000000000 --- a/tests/test_basic.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/python3 -# -# Basic bcachefs functionality tests. - -import re -from tests import util - -def test_help(): - ret = util.run_bch(valgrind=True) - - assert ret.returncode == 1 - assert "missing command" in ret.stdout - assert len(ret.stderr) == 0 - -def test_format(tmpdir): - dev = util.device_1g(tmpdir) - ret = util.run_bch('format', dev, valgrind=True) - - assert ret.returncode == 0 - assert len(ret.stdout) > 0 - assert len(ret.stderr) == 0 - -def test_fsck(tmpdir): - dev = util.format_1g(tmpdir) - - ret = util.run_bch('fsck', dev, valgrind=True) - - assert ret.returncode == 0 - assert len(ret.stdout) > 0 - assert len(ret.stderr) == 0 - -def test_list(tmpdir): - dev = util.format_1g(tmpdir) - - ret = util.run_bch('list', dev, valgrind=True) - - assert ret.returncode == 0 - assert len(ret.stderr) == 0 - assert "recovering from clean shutdown" in ret.stdout - -def test_list_inodes(tmpdir): - dev = util.format_1g(tmpdir) - - ret = util.run_bch('list', '-b', 'inodes', dev, valgrind=True) - - assert ret.returncode == 0 - assert len(ret.stderr) == 0 - assert len(ret.stdout.splitlines()) == (67) - -def test_list_dirent(tmpdir): - dev = util.format_1g(tmpdir) - - ret = util.run_bch('list', '-b', 'dirents', dev, valgrind=True) - - assert ret.returncode == 0 - assert len(ret.stderr) == 0 - assert len(ret.stdout.splitlines()) == (6) # See example: - - # Example: - # mounting version 1.6: btree_subvolume_children opts=ro,errors=continue,degraded,nochanges,norecovery,read_only - # recovering from clean shutdown, journal seq 9 - # alloc_read... done - # stripes_read... done - # snapshots_read... done - # u64s 8 type dirent 4096:453699834857023875:U32_MAX len 0 ver 0: lost+found -> 4097 type dir - last = ret.stdout.splitlines()[0] - assert re.match(r'^.*type dirent.*: lost\+found ->.*$', last) diff --git a/tests/test_fixture.py b/tests/test_fixture.py deleted file mode 100644 index d96ce88d2..000000000 --- a/tests/test_fixture.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/python3 -# -# Tests of the functions in util.py - -import signal -import subprocess -import time -import os -import pytest - -from tests import util - -helper = os.path.abspath(os.path.join(util.BASE_PATH, 'test_helper')) - -def test_sparse_file(tmpdir): - dev = util.sparse_file(tmpdir / '1k', 1024) - assert dev.stat().st_size == 1024 - -def test_device_1g(tmpdir): - dev = util.device_1g(tmpdir) - assert dev.stat().st_size == 1024**3 - -def test_abort(): - ret = util.run(helper, 'abort') - assert ret.returncode == -signal.SIGABRT - -def test_segfault(): - ret = util.run(helper, 'segfault') - assert ret.returncode == -signal.SIGSEGV - -@pytest.mark.skipif(not util.ENABLE_VALGRIND, reason="no valgrind") -def test_check(): - with pytest.raises(subprocess.CalledProcessError): - util.run(helper, 'abort', check=True) - -@pytest.mark.skipif(not util.ENABLE_VALGRIND, reason="no valgrind") -def test_leak(): - with pytest.raises(util.ValgrindFailedError): - util.run(helper, 'leak', valgrind=True) - -@pytest.mark.skipif(not util.ENABLE_VALGRIND, reason="no valgrind") -def test_undefined(): - with pytest.raises(util.ValgrindFailedError): - util.run(helper, 'undefined', valgrind=True) - -@pytest.mark.skipif(not util.ENABLE_VALGRIND, reason="no valgrind") -def test_undefined_branch(): - with pytest.raises(util.ValgrindFailedError): - util.run(helper, 'undefined_branch', valgrind=True) - -@pytest.mark.skipif(not util.ENABLE_VALGRIND, reason="no valgrind") -def test_read_after_free(): - with pytest.raises(util.ValgrindFailedError): - util.run(helper, 'read_after_free', valgrind=True) - -@pytest.mark.skipif(not util.ENABLE_VALGRIND, reason="no valgrind") -def test_write_after_free(): - with pytest.raises(util.ValgrindFailedError): - 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) diff --git a/tests/test_fuse.py b/tests/test_fuse.py deleted file mode 100644 index 48288e6a9..000000000 --- a/tests/test_fuse.py +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/python3 -# -# Tests of the fuse mount functionality. - -import pytest -import os -from tests import util - -pytestmark = pytest.mark.skipif( - not util.have_fuse(), reason="bcachefs not built with fuse support.") - -def test_mount(bfuse): - bfuse.mount() - bfuse.unmount() - bfuse.verify() - -@pytest.mark.skipif(util.ENABLE_VALGRIND, reason="test broken") -def test_remount(bfuse): - bfuse.mount() - bfuse.unmount() - 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' - - bfuse.unmount() - bfuse.verify() diff --git a/tests/test_helper.c b/tests/test_helper.c deleted file mode 100644 index 2faf4da64..000000000 --- a/tests/test_helper.c +++ /dev/null @@ -1,103 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -void trick_compiler(int *x); - -static void test_abort(void) -{ - abort(); -} - -static void test_segfault(void) -{ - raise(SIGSEGV); -} - -static void test_leak(void) -{ - int *p = malloc(sizeof *p); - trick_compiler(p); -} - -static void test_undefined(void) -{ - int *p = malloc(1); - printf("%d\n", *p); -} - -static void test_undefined_branch(void) -{ - int x; - trick_compiler(&x); - - if (x) - printf("1\n"); - else - printf("0\n"); -} - -static void test_read_after_free(void) -{ - int *p = malloc(sizeof *p); - free(p); - - printf("%d\n", *p); -} - -static void test_write_after_free(void) -{ - int *p = malloc(sizeof *p); - free(p); - - printf("%d\n", *p); -} - -typedef void (*test_fun)(void); - -struct test { - const char *name; - test_fun fun; -}; - -#define TEST(f) { .name = #f, .fun = test_##f, } -static struct test tests[] = { - TEST(abort), - TEST(segfault), - TEST(leak), - TEST(undefined), - TEST(undefined_branch), - TEST(read_after_free), - TEST(write_after_free), -}; -#define ntests (sizeof tests / sizeof *tests) - -int main(int argc, char *argv[]) -{ - int i; - - if (argc != 2) { - fprintf(stderr, "Usage: test_helper \n"); - exit(1); - } - - bool found = false; - for (i = 0; i < ntests; ++i) - if (!strcmp(argv[1], tests[i].name)) { - found = true; - printf("Running test: %s\n", tests[i].name); - tests[i].fun(); - break; - } - - if (!found) { - fprintf(stderr, "Unable to find test: %s\n", argv[1]); - exit(1); - } - - return 0; -} diff --git a/tests/test_helper_trick.c b/tests/test_helper_trick.c deleted file mode 100644 index 8bc11fd94..000000000 --- a/tests/test_helper_trick.c +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Prevent compiler from optimizing away a variable by referencing it from - * another compilation unit. - */ -void -trick_compiler(int *x) -{ -} diff --git a/tests/util.py b/tests/util.py deleted file mode 100644 index 68fe9e950..000000000 --- a/tests/util.py +++ /dev/null @@ -1,257 +0,0 @@ -#!/usr/bin/python3 - -import errno -import os -import re -import subprocess -import tempfile -import threading -import time - -from pathlib import Path - -BASE_PATH= os.path.dirname(__file__) -BCH_PATH = os.path.abspath(os.path.join(BASE_PATH, '../target/release', 'bcachefs')) -VALGRIND_PATH= os.path.abspath(os.path.join(BASE_PATH, - 'valgrind-suppressions.txt')) - -VPAT = re.compile(r'ERROR SUMMARY: (\d+) errors from (\d+) contexts') - -ENABLE_VALGRIND = os.getenv('BCACHEFS_TEST_USE_VALGRIND', 'no') == 'yes' - -class ValgrindFailedError(Exception): - def __init__(self, log): - self.log = log - -def check_valgrind(log): - m = VPAT.search(log) - if m is None: - print('Internal error: valgrind log did not match.') - print('-- valgrind log:') - print(log) - print('-- end log --') - assert False - - errors = int(m.group(1)) - if errors > 0: - raise ValgrindFailedError(log) - -def run(cmd, *args, valgrind=False, check=False): - """Run an external program via subprocess, optionally with valgrind. - - This subprocess wrapper will capture the stdout and stderr. If valgrind is - requested, it will be checked for errors and raise a - ValgrindFailedError if there's a problem. - """ - cmds = [cmd] + list(args) - valgrind = valgrind and ENABLE_VALGRIND - - print("Running '{}'".format(cmds)) - if valgrind: - vout = tempfile.NamedTemporaryFile() - vcmd = ['valgrind', - '--leak-check=full', - '--gen-suppressions=all', - '--suppressions={}'.format(VALGRIND_PATH), - '--log-file={}'.format(vout.name)] - cmds = vcmd + cmds - - res = subprocess.run(cmds, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, encoding='utf-8', check=check) - check_valgrind(vout.read().decode('utf-8')) - else: - res = subprocess.run(cmds, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, encoding='utf-8', check=check) - - return res - -def run_bch(*args, **kwargs): - """Wrapper to run the bcachefs binary specifically.""" - cmds = [BCH_PATH] + list(args) - return run(*cmds, **kwargs) - -def sparse_file(lpath, size): - """Construct a sparse file of the specified size. - - This is typically used to create device files for bcachefs. - """ - path = Path(lpath) - path.touch(mode = 0o600, exist_ok = False) - os.truncate(path, size) - - return path - -def device_1g(tmpdir): - """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: - '''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): - self.thread = None - 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""" - - vlog = None - cmd = [] - - if ENABLE_VALGRIND: - vlog = tempfile.NamedTemporaryFile() - cmd += [ 'valgrind', - '--leak-check=full', - '--gen-suppressions=all', - '--suppressions=valgrind-suppressions.txt', - '--log-file={}'.format(vlog.name) ] - - cmd += [ 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.returncode = self.proc.returncode - if self.returncode == 0: - errors = [ 'btree iterators leaked!', - 'emergency read only!' ] - for e in errors: - if e in out2: - print('Debug error found in output: "{}"'.format(e)) - self.returncode = errno.ENOMSG - - self.stdout = out1 + out2 - self.stderr = err.read() - if vlog: - self.vout = vlog.read().decode('utf-8') - - 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.") - - assert not self.thread - self.thread = threading.Thread(target=self.run) - self.thread.start() - - self.ready.wait() - print("Fuse is mounted.") - - def unmount(self, timeout=None): - print("Unmounting fuse.") - run("fusermount3", "-zu", self.mnt) - - if self.thread: - print("Waiting for thread to exit.") - self.thread.join(timeout) - if self.thread.is_alive(): - if self.proc: - self.proc.kill() - self.thread.join() - else: - print("Thread was already done.") - - self.thread = None - self.ready.clear() - - if self.vout: - check_valgrind(self.vout) - - def verify(self): - # avoid throwing exception in assertion - assert self.stdout is not None - assert self.stderr is not None - assert self.returncode == 0 - assert len(self.stdout) > 0 - assert len(self.stderr) == 0 - -def have_fuse(): - res = run(BCH_PATH, 'fusemount', valgrind=False) - return "Please supply a mountpoint." in res.stdout diff --git a/tests/valgrind-suppressions.txt b/tests/valgrind-suppressions.txt deleted file mode 100644 index 612a08eef..000000000 --- a/tests/valgrind-suppressions.txt +++ /dev/null @@ -1,38 +0,0 @@ -{ - call_rcu_memb - Memcheck:Leak - match-leak-kinds: possible,definite - ... - fun:get_default_call_rcu_data_memb - fun:call_rcu_memb -} -{ - call_rcu_data_init - Memcheck:Leak - match-leak-kinds: possible - fun:calloc - fun:_dl_allocate_tls - ... - fun:call_rcu_data_init -} -{ - urcu_memb_call_rcu - Memcheck:Leak - match-leak-kinds: possible - ... - fun:pthread_create* - obj:/*/liburcu.so.* - ... - fun:urcu_memb_call_rcu -} -{ - pthread_create - Memcheck:Leak - match-leak-kinds: possible - fun:calloc - ... - fun:allocate_stack - fun:pthread_create* - fun:kthread_create - fun:bch2_rebalance_start -}