#!/usr/bin/python3
+import errno
import os
-import pytest
import re
import subprocess
import tempfile
from pathlib import Path
-DIR = Path('..')
-BCH_PATH = DIR / 'bcachefs'
+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(logfile):
- log = logfile.read().decode('utf-8')
+def check_valgrind(log):
m = VPAT.search(log)
- assert m is not None, 'Internal error: valgrind log did not match.'
+ 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:
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
- print("Running '{}'".format(cmds))
- res = subprocess.run(cmds, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- encoding='utf-8', check=check)
-
- if valgrind:
- check_valgrind(vout)
+ 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
This is typically used to create device files for bcachefs.
"""
path = Path(lpath)
- f = path.touch(mode = 0o600, exist_ok = False)
+ path.touch(mode = 0o600, exist_ok = False)
os.truncate(path, size)
return path
def __init__(self, msg):
self.msg = msg
-class BFuse(threading.Thread):
+class BFuse:
'''bcachefs fuse runner.
This class runs bcachefs in fusemount mode, and waits until the mount has
'''
def __init__(self, dev, mnt):
- threading.Thread.__init__(self)
+ self.thread = None
self.dev = dev
self.mnt = mnt
self.ready = threading.Event()
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]
+ 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))
(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()
- self.returncode = self.proc.returncode
- self.vout = vout
+ if vlog:
+ self.vout = vlog.read().decode('utf-8')
def expect(self, pipe, regex):
"""Wait for the child process to mount."""
def mount(self):
print("Starting fuse thread.")
- self.start()
+
+ 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)
- print("Waiting for thread to exit.")
- self.join(timeout)
- if self.isAlive():
- self.proc.kill()
- self.join()
+ 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()
- check_valgrind(self.vout)
+ 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