mirror of
https://github.com/koverstreet/bcachefs-tools.git
synced 2025-02-23 00:00:02 +03:00
Initial version of bcachefs tests.
So far, these tests just test basic format, fsck, and list functions under valgrind, as well as a few self-validation tests. Signed-off-by: Justin Husted <sigstop@gmail.com>
This commit is contained in:
parent
d79d57ef89
commit
61bc316a4d
11
Makefile
11
Makefile
@ -2,6 +2,7 @@
|
|||||||
PREFIX?=/usr/local
|
PREFIX?=/usr/local
|
||||||
PKG_CONFIG?=pkg-config
|
PKG_CONFIG?=pkg-config
|
||||||
INSTALL=install
|
INSTALL=install
|
||||||
|
PYTEST=pytest-3
|
||||||
CFLAGS+=-std=gnu89 -O2 -g -MMD -Wall \
|
CFLAGS+=-std=gnu89 -O2 -g -MMD -Wall \
|
||||||
-Wno-pointer-sign \
|
-Wno-pointer-sign \
|
||||||
-fno-strict-aliasing \
|
-fno-strict-aliasing \
|
||||||
@ -65,12 +66,18 @@ endif
|
|||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: bcachefs
|
all: bcachefs
|
||||||
|
|
||||||
|
.PHONY: check
|
||||||
|
check: tests/test_helper bcachefs
|
||||||
|
cd tests; $(PYTEST)
|
||||||
|
|
||||||
SRCS=$(shell find . -type f -iname '*.c')
|
SRCS=$(shell find . -type f -iname '*.c')
|
||||||
DEPS=$(SRCS:.c=.d)
|
DEPS=$(SRCS:.c=.d)
|
||||||
-include $(DEPS)
|
-include $(DEPS)
|
||||||
|
|
||||||
OBJS=$(SRCS:.c=.o)
|
OBJS=$(SRCS:.c=.o)
|
||||||
bcachefs: $(OBJS)
|
bcachefs: $(filter-out ./tests/%.o, $(OBJS))
|
||||||
|
|
||||||
|
tests/test_helper: $(filter ./tests/%.o, $(OBJS))
|
||||||
|
|
||||||
# If the version string differs from the last build, update the last version
|
# If the version string differs from the last build, update the last version
|
||||||
ifneq ($(VERSION),$(shell cat .version 2>/dev/null))
|
ifneq ($(VERSION),$(shell cat .version 2>/dev/null))
|
||||||
@ -97,7 +104,7 @@ install: bcachefs
|
|||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
$(RM) bcachefs .version $(OBJS) $(DEPS)
|
$(RM) bcachefs tests/test_helper .version $(OBJS) $(DEPS)
|
||||||
|
|
||||||
.PHONY: deb
|
.PHONY: deb
|
||||||
deb: all
|
deb: all
|
||||||
|
68
tests/test_basic.py
Normal file
68
tests/test_basic.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
#
|
||||||
|
# Basic bcachefs functionality tests.
|
||||||
|
|
||||||
|
import re
|
||||||
|
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.device_1g(tmpdir)
|
||||||
|
util.run_bch('format', dev, valgrind=False, check=True)
|
||||||
|
|
||||||
|
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.device_1g(tmpdir)
|
||||||
|
util.run_bch('format', dev, valgrind=False, check=True)
|
||||||
|
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
|
||||||
|
ret = util.run_bch('list', '-b', 'inodes', dev, valgrind=True)
|
||||||
|
|
||||||
|
assert ret.returncode == 0
|
||||||
|
assert len(ret.stderr) == 0
|
||||||
|
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)
|
||||||
|
|
||||||
|
ret = util.run_bch('list', '-b', 'dirents', dev, valgrind=True)
|
||||||
|
|
||||||
|
assert ret.returncode == 0
|
||||||
|
assert len(ret.stderr) == 0
|
||||||
|
assert len(ret.stdout.splitlines()) == (2 + 1) # 1 dirent
|
||||||
|
|
||||||
|
# Example:
|
||||||
|
# u64s 8 type dirent 4096:2449855786607753081
|
||||||
|
# snap 0 len 0 ver 0: lost+found -> 4097
|
||||||
|
last = ret.stdout.splitlines()[-1]
|
||||||
|
assert re.match(r'^.*type dirent.*: lost\+found ->.*$', last)
|
53
tests/test_fixture.py
Normal file
53
tests/test_fixture.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
#
|
||||||
|
# Tests of the functions in util.py
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import util
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
#helper = Path('.') / 'test_helper'
|
||||||
|
helper = './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
|
||||||
|
|
||||||
|
def test_check():
|
||||||
|
with pytest.raises(subprocess.CalledProcessError):
|
||||||
|
ret = util.run(helper, 'abort', check=True)
|
||||||
|
|
||||||
|
def test_leak():
|
||||||
|
with pytest.raises(util.ValgrindFailedError):
|
||||||
|
ret = util.run(helper, 'leak', valgrind=True)
|
||||||
|
|
||||||
|
def test_undefined():
|
||||||
|
with pytest.raises(util.ValgrindFailedError):
|
||||||
|
ret = util.run(helper, 'undefined', valgrind=True)
|
||||||
|
|
||||||
|
def test_undefined_branch():
|
||||||
|
with pytest.raises(util.ValgrindFailedError):
|
||||||
|
ret = util.run(helper, 'undefined_branch', valgrind=True)
|
||||||
|
|
||||||
|
def test_read_after_free():
|
||||||
|
with pytest.raises(util.ValgrindFailedError):
|
||||||
|
ret = util.run(helper, 'read_after_free', valgrind=True)
|
||||||
|
|
||||||
|
def test_write_after_free():
|
||||||
|
with pytest.raises(util.ValgrindFailedError):
|
||||||
|
ret = util.run(helper, 'write_after_free', valgrind=True)
|
103
tests/test_helper.c
Normal file
103
tests/test_helper.c
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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 <test>\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;
|
||||||
|
}
|
8
tests/test_helper_trick.c
Normal file
8
tests/test_helper_trick.c
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* Prevent compiler from optimizing away a variable by referencing it from
|
||||||
|
* another compilation unit.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
trick_compiler(int *x)
|
||||||
|
{
|
||||||
|
}
|
71
tests/util.py
Normal file
71
tests/util.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
DIR = Path('..')
|
||||||
|
BCH_PATH = DIR / 'bcachefs'
|
||||||
|
|
||||||
|
VPAT = re.compile(r'ERROR SUMMARY: (\d+) errors from (\d+) contexts')
|
||||||
|
|
||||||
|
class ValgrindFailedError(Exception):
|
||||||
|
def __init__(self, log):
|
||||||
|
self.log = log
|
||||||
|
|
||||||
|
def check_valgrind(logfile):
|
||||||
|
log = logfile.read().decode('utf-8')
|
||||||
|
m = VPAT.search(log)
|
||||||
|
assert m is not None, 'Internal error: valgrind log did not match.'
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
if valgrind:
|
||||||
|
vout = tempfile.NamedTemporaryFile()
|
||||||
|
vcmd = ['valgrind',
|
||||||
|
'--leak-check=full',
|
||||||
|
'--log-file={}'.format(vout.name)]
|
||||||
|
cmds = vcmd + cmds
|
||||||
|
|
||||||
|
print("Running '{}'".format(cmds))
|
||||||
|
res = subprocess.run(cmds, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||||
|
encoding='utf-8', check=check)
|
||||||
|
|
||||||
|
if valgrind:
|
||||||
|
check_valgrind(vout)
|
||||||
|
|
||||||
|
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)
|
||||||
|
f = 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)
|
Loading…
Reference in New Issue
Block a user