summaryrefslogtreecommitdiff
path: root/tests/conftest.py
diff options
context:
space:
mode:
authorNikolaus Rath <Nikolaus@rath.org>2016-03-11 16:12:49 -0800
committerNikolaus Rath <Nikolaus@rath.org>2016-03-11 16:12:49 -0800
commitb5fb9f530d5abb606a064896fcec04b5f2da3737 (patch)
treeebfb71d9d1d48bc65b38216f65295355f6d31551 /tests/conftest.py
parent25fe43b60c06017fd51d7f8c338d4a865bd1be5f (diff)
Import s3ql_2.17.1+hg2+dfsg.orig.tar.xz
Diffstat (limited to 'tests/conftest.py')
-rw-r--r--tests/conftest.py168
1 files changed, 103 insertions, 65 deletions
diff --git a/tests/conftest.py b/tests/conftest.py
index fd1f4a6..c344574 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -25,28 +25,7 @@ import faulthandler
import signal
import gc
import time
-
-# Converted to autouse fixture below if capture is activated
-def check_test_output(request, capfd):
- request.capfd = capfd
- def raise_on_exception_in_out():
- # Ensure that capturing has been set up (this may not be the case if one
- # of the test fixtures raises an exception)
- try:
- (stdout, stderr) = capfd.readouterr()
- except AttributeError:
- return
-
- # Write back what we've read (so that it will still be printed.
- sys.stdout.write(stdout)
- sys.stderr.write(stderr)
-
- if ('exception' in stderr.lower()
- or 'exception' in stdout.lower()):
- raise AssertionError('Suspicious output to stderr')
-
- request.addfinalizer(raise_on_exception_in_out)
-
+import re
# If a test fails, wait a moment before retrieving the captured
# stdout/stderr. When using a server process (like in t4_fuse.py), this makes
@@ -73,6 +52,95 @@ def s3ql_cmd_argv(request):
request.cls.s3ql_cmd_argv = lambda self, cmd: [ sys.executable,
os.path.join(basedir, 'bin', cmd) ]
+@pytest.fixture()
+def pass_capfd(request, capfd):
+ '''Provide capfd object to UnitTest instances'''
+ request.instance.capfd = capfd
+
+# Fail tests if the result in log messages of severity WARNING or more.
+# Previously (as of Mercurial commit 192dd923daa8, 2016-03-10) we instead
+# installed a custom Logger class that would immediately raise an
+# exception. However, this solution seemed ugly because normally the logging
+# methods suppress any exceptions. Therefore, exception handlers (which are
+# especially likely to log warnings) may rely on that and an unexpected
+# exception may result in improper clean-up. Furthermore, the custom logger
+# class required a second hack to allow logging the "unexpected warning message"
+# exception itself from sys.excepthook. Checking the logs after execution has
+# the drawback that we abort later, and that the failure seems to come from the
+# teardown method, but seems like an overall better solution.
+def check_test_log(caplog):
+ for record in caplog.records():
+ if (record.levelno >= logging.WARNING and
+ not getattr(record, 'caplog_ignore', False)):
+ raise AssertionError('Logger received warning messages')
+
+def check_test_output(capfd):
+ (stdout, stderr) = capfd.readouterr()
+
+ # Write back what we've read (so that it will still be printed.
+ sys.stdout.write(stdout)
+ sys.stderr.write(stderr)
+
+ # Strip out false positives
+ for (pattern, flags, count) in capfd.false_positives:
+ cp = re.compile(pattern, flags)
+ (stdout, cnt) = cp.subn('', stdout, count=count)
+ if count == 0 or count - cnt > 0:
+ stderr = cp.sub('', stderr, count=count - cnt)
+
+ for pattern in ('exception', 'error', 'warning', 'fatal',
+ 'fault', 'crash(?:ed)?', 'abort(?:ed)'):
+ cp = re.compile(r'\b{}\b'.format(pattern), re.IGNORECASE | re.MULTILINE)
+ hit = cp.search(stderr)
+ if hit:
+ raise AssertionError('Suspicious output to stderr (matched "%s")' % hit.group(0))
+ hit = cp.search(stdout)
+ if hit:
+ raise AssertionError('Suspicious output to stdout (matched "%s")' % hit.group(0))
+
+
+def register_output(self, pattern, count=1, flags=re.MULTILINE):
+ '''Register *pattern* as false positive for output checking
+
+ This prevents the test from failing because the output otherwise
+ appears suspicious.
+ '''
+
+ self.false_positives.append((pattern, flags, count))
+
+# This is a terrible hack that allows us to access the fixtures from the
+# pytest_runtest_call hook. Among a lot of other hidden assumptions, it probably
+# relies on tests running sequential (i.e., don't dare to use e.g. the xdist
+# plugin)
+current_cap_fixtures = None
+@pytest.yield_fixture(autouse=True)
+def save_cap_fixtures(request, capfd, caplog):
+ global current_cap_fixtures
+ capfd.false_positives = []
+ type(capfd).register_output = register_output
+
+ # Ignore DeprecationWarnings when running unit tests. They are
+ # unfortunately quite often a result of indirect imports via third party
+ # modules, so we can't actually fix them.
+ capfd.register_output(r'^(WARNING: )?(Pending)?DeprecationWarning: [^\n]+$', count=0)
+
+ if request.config.getoption('capture') == 'no':
+ capfd = None
+ current_cap_fixtures = (capfd, caplog)
+ bak = current_cap_fixtures
+ yield
+ # Try to catch problems with this hack (e.g. when running
+ # tests simultaneously)
+ assert bak is current_cap_fixtures
+ current_cap_fixtures = None
+
+@pytest.hookimpl(trylast=True)
+def pytest_runtest_call(item):
+ (capfd, caplog) = current_cap_fixtures
+ check_test_log(caplog)
+ if capfd is not None:
+ check_test_output(capfd)
+
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting")
group._addoption("--logdebug", action="append", metavar='<module>',
@@ -84,16 +152,7 @@ def pytest_addoption(parser):
group._addoption("--installed", action="store_true", default=False,
help="Test the installed package.")
-
def pytest_configure(config):
-
- # Enable stdout and stderr analysis, unless output capture is disabled
- if config.getoption('capture') != 'no':
- global check_test_output
- check_test_output = pytest.fixture(autouse=True)(check_test_output)
-
- logdebug = config.getoption('logdebug')
-
# If we are running from the S3QL source directory, make sure that we
# load modules from here
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
@@ -114,42 +173,21 @@ def pytest_configure(config):
faulthandler.enable(faultlog_fh)
faulthandler.register(signal.SIGUSR1, file=faultlog_fh)
- # Enable logging
- import s3ql.logging
+ # Configure logging. We don't set a default handler but rely on
+ # the catchlog pytest plugin.
+ logdebug = config.getoption('logdebug')
root_logger = logging.getLogger()
- if root_logger.handlers:
- root_logger.warning("Logging already initialized.")
- else:
- handler = logging.handlers.RotatingFileHandler(
- os.path.join(basedir, 'tests', 'test.log'),
- maxBytes=10 * 1024 ** 2, backupCount=0)
- if logdebug is None:
- formatter = logging.Formatter(
- '%(asctime)s.%(msecs)03d [%(process)s] %(threadName)s: '
- '[%(name)s] %(message)s', datefmt="%Y-%m-%d %H:%M:%S")
- else:
- formatter = logging.Formatter(
- '%(asctime)s.%(msecs)03d [%(process)s] %(threadName)s: '
- '[%(name)s.%(funcName)s] %(message)s', datefmt="%Y-%m-%d %H:%M:%S")
-
- handler.setFormatter(formatter)
- root_logger.addHandler(handler)
-
- if logdebug is not None:
- if 'all' in logdebug:
- root_logger.setLevel(logging.DEBUG)
- else:
- for module in logdebug:
- logging.getLogger(module).setLevel(logging.DEBUG)
- logging.disable(logging.NOTSET)
+ if logdebug is not None:
+ logging.disable(logging.NOTSET)
+ if 'all' in logdebug:
+ root_logger.setLevel(logging.DEBUG)
else:
- root_logger.setLevel(logging.WARNING)
-
- logging.captureWarnings(capture=True)
-
- # Make errors and warnings fatal
- s3ql.logging.EXCEPTION_SEVERITY = logging.WARNING
-
+ for module in logdebug:
+ logging.getLogger(module).setLevel(logging.DEBUG)
+ else:
+ root_logger.setLevel(logging.INFO)
+ logging.disable(logging.DEBUG)
+ logging.captureWarnings(capture=True)
# Run gc.collect() at the end of every test, so that we get ResourceWarnings
# as early as possible.