diff options
author | Nikolaus Rath <Nikolaus@rath.org> | 2016-03-11 16:12:49 -0800 |
---|---|---|
committer | Nikolaus Rath <Nikolaus@rath.org> | 2016-03-11 16:12:49 -0800 |
commit | b5fb9f530d5abb606a064896fcec04b5f2da3737 (patch) | |
tree | ebfb71d9d1d48bc65b38216f65295355f6d31551 /tests/conftest.py | |
parent | 25fe43b60c06017fd51d7f8c338d4a865bd1be5f (diff) |
Import s3ql_2.17.1+hg2+dfsg.orig.tar.xz
Diffstat (limited to 'tests/conftest.py')
-rw-r--r-- | tests/conftest.py | 168 |
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. |