Blame external/pybind11/tests/conftest.py

Packit 534379
"""pytest configuration
Packit 534379
Packit 534379
Extends output capture as needed by pybind11: ignore constructors, optional unordered lines.
Packit 534379
Adds docstring and exceptions message sanitizers: ignore Python 2 vs 3 differences.
Packit 534379
"""
Packit 534379
Packit 534379
import pytest
Packit 534379
import textwrap
Packit 534379
import difflib
Packit 534379
import re
Packit 534379
import sys
Packit 534379
import contextlib
Packit 534379
import platform
Packit 534379
import gc
Packit 534379
Packit 534379
_unicode_marker = re.compile(r'u(\'[^\']*\')')
Packit 534379
_long_marker = re.compile(r'([0-9])L')
Packit 534379
_hexadecimal = re.compile(r'0x[0-9a-fA-F]+')
Packit 534379
Packit 534379
# test_async.py requires support for async and await
Packit 534379
collect_ignore = []
Packit 534379
if sys.version_info[:2] < (3, 5):
Packit 534379
    collect_ignore.append("test_async.py")
Packit 534379
Packit 534379
Packit 534379
def _strip_and_dedent(s):
Packit 534379
    """For triple-quote strings"""
Packit 534379
    return textwrap.dedent(s.lstrip('\n').rstrip())
Packit 534379
Packit 534379
Packit 534379
def _split_and_sort(s):
Packit 534379
    """For output which does not require specific line order"""
Packit 534379
    return sorted(_strip_and_dedent(s).splitlines())
Packit 534379
Packit 534379
Packit 534379
def _make_explanation(a, b):
Packit 534379
    """Explanation for a failed assert -- the a and b arguments are List[str]"""
Packit 534379
    return ["--- actual / +++ expected"] + [line.strip('\n') for line in difflib.ndiff(a, b)]
Packit 534379
Packit 534379
Packit 534379
class Output(object):
Packit 534379
    """Basic output post-processing and comparison"""
Packit 534379
    def __init__(self, string):
Packit 534379
        self.string = string
Packit 534379
        self.explanation = []
Packit 534379
Packit 534379
    def __str__(self):
Packit 534379
        return self.string
Packit 534379
Packit 534379
    def __eq__(self, other):
Packit 534379
        # Ignore constructor/destructor output which is prefixed with "###"
Packit 534379
        a = [line for line in self.string.strip().splitlines() if not line.startswith("###")]
Packit 534379
        b = _strip_and_dedent(other).splitlines()
Packit 534379
        if a == b:
Packit 534379
            return True
Packit 534379
        else:
Packit 534379
            self.explanation = _make_explanation(a, b)
Packit 534379
            return False
Packit 534379
Packit 534379
Packit 534379
class Unordered(Output):
Packit 534379
    """Custom comparison for output without strict line ordering"""
Packit 534379
    def __eq__(self, other):
Packit 534379
        a = _split_and_sort(self.string)
Packit 534379
        b = _split_and_sort(other)
Packit 534379
        if a == b:
Packit 534379
            return True
Packit 534379
        else:
Packit 534379
            self.explanation = _make_explanation(a, b)
Packit 534379
            return False
Packit 534379
Packit 534379
Packit 534379
class Capture(object):
Packit 534379
    def __init__(self, capfd):
Packit 534379
        self.capfd = capfd
Packit 534379
        self.out = ""
Packit 534379
        self.err = ""
Packit 534379
Packit 534379
    def __enter__(self):
Packit 534379
        self.capfd.readouterr()
Packit 534379
        return self
Packit 534379
Packit 534379
    def __exit__(self, *args):
Packit 534379
        self.out, self.err = self.capfd.readouterr()
Packit 534379
Packit 534379
    def __eq__(self, other):
Packit 534379
        a = Output(self.out)
Packit 534379
        b = other
Packit 534379
        if a == b:
Packit 534379
            return True
Packit 534379
        else:
Packit 534379
            self.explanation = a.explanation
Packit 534379
            return False
Packit 534379
Packit 534379
    def __str__(self):
Packit 534379
        return self.out
Packit 534379
Packit 534379
    def __contains__(self, item):
Packit 534379
        return item in self.out
Packit 534379
Packit 534379
    @property
Packit 534379
    def unordered(self):
Packit 534379
        return Unordered(self.out)
Packit 534379
Packit 534379
    @property
Packit 534379
    def stderr(self):
Packit 534379
        return Output(self.err)
Packit 534379
Packit 534379
Packit 534379
@pytest.fixture
Packit 534379
def capture(capsys):
Packit 534379
    """Extended `capsys` with context manager and custom equality operators"""
Packit 534379
    return Capture(capsys)
Packit 534379
Packit 534379
Packit 534379
class SanitizedString(object):
Packit 534379
    def __init__(self, sanitizer):
Packit 534379
        self.sanitizer = sanitizer
Packit 534379
        self.string = ""
Packit 534379
        self.explanation = []
Packit 534379
Packit 534379
    def __call__(self, thing):
Packit 534379
        self.string = self.sanitizer(thing)
Packit 534379
        return self
Packit 534379
Packit 534379
    def __eq__(self, other):
Packit 534379
        a = self.string
Packit 534379
        b = _strip_and_dedent(other)
Packit 534379
        if a == b:
Packit 534379
            return True
Packit 534379
        else:
Packit 534379
            self.explanation = _make_explanation(a.splitlines(), b.splitlines())
Packit 534379
            return False
Packit 534379
Packit 534379
Packit 534379
def _sanitize_general(s):
Packit 534379
    s = s.strip()
Packit 534379
    s = s.replace("pybind11_tests.", "m.")
Packit 534379
    s = s.replace("unicode", "str")
Packit 534379
    s = _long_marker.sub(r"\1", s)
Packit 534379
    s = _unicode_marker.sub(r"\1", s)
Packit 534379
    return s
Packit 534379
Packit 534379
Packit 534379
def _sanitize_docstring(thing):
Packit 534379
    s = thing.__doc__
Packit 534379
    s = _sanitize_general(s)
Packit 534379
    return s
Packit 534379
Packit 534379
Packit 534379
@pytest.fixture
Packit 534379
def doc():
Packit 534379
    """Sanitize docstrings and add custom failure explanation"""
Packit 534379
    return SanitizedString(_sanitize_docstring)
Packit 534379
Packit 534379
Packit 534379
def _sanitize_message(thing):
Packit 534379
    s = str(thing)
Packit 534379
    s = _sanitize_general(s)
Packit 534379
    s = _hexadecimal.sub("0", s)
Packit 534379
    return s
Packit 534379
Packit 534379
Packit 534379
@pytest.fixture
Packit 534379
def msg():
Packit 534379
    """Sanitize messages and add custom failure explanation"""
Packit 534379
    return SanitizedString(_sanitize_message)
Packit 534379
Packit 534379
Packit 534379
# noinspection PyUnusedLocal
Packit 534379
def pytest_assertrepr_compare(op, left, right):
Packit 534379
    """Hook to insert custom failure explanation"""
Packit 534379
    if hasattr(left, 'explanation'):
Packit 534379
        return left.explanation
Packit 534379
Packit 534379
Packit 534379
@contextlib.contextmanager
Packit 534379
def suppress(exception):
Packit 534379
    """Suppress the desired exception"""
Packit 534379
    try:
Packit 534379
        yield
Packit 534379
    except exception:
Packit 534379
        pass
Packit 534379
Packit 534379
Packit 534379
def gc_collect():
Packit 534379
    ''' Run the garbage collector twice (needed when running
Packit 534379
    reference counting tests with PyPy) '''
Packit 534379
    gc.collect()
Packit 534379
    gc.collect()
Packit 534379
Packit 534379
Packit 534379
def pytest_configure():
Packit 534379
    """Add import suppression and test requirements to `pytest` namespace"""
Packit 534379
    try:
Packit 534379
        import numpy as np
Packit 534379
    except ImportError:
Packit 534379
        np = None
Packit 534379
    try:
Packit 534379
        import scipy
Packit 534379
    except ImportError:
Packit 534379
        scipy = None
Packit 534379
    try:
Packit 534379
        from pybind11_tests.eigen import have_eigen
Packit 534379
    except ImportError:
Packit 534379
        have_eigen = False
Packit 534379
    pypy = platform.python_implementation() == "PyPy"
Packit 534379
Packit 534379
    skipif = pytest.mark.skipif
Packit 534379
    pytest.suppress = suppress
Packit 534379
    pytest.requires_numpy = skipif(not np, reason="numpy is not installed")
Packit 534379
    pytest.requires_scipy = skipif(not np, reason="scipy is not installed")
Packit 534379
    pytest.requires_eigen_and_numpy = skipif(not have_eigen or not np,
Packit 534379
                                             reason="eigen and/or numpy are not installed")
Packit 534379
    pytest.requires_eigen_and_scipy = skipif(
Packit 534379
        not have_eigen or not scipy, reason="eigen and/or scipy are not installed")
Packit 534379
    pytest.unsupported_on_pypy = skipif(pypy, reason="unsupported on PyPy")
Packit 534379
    pytest.unsupported_on_py2 = skipif(sys.version_info.major < 3,
Packit 534379
                                       reason="unsupported on Python 2.x")
Packit 534379
    pytest.gc_collect = gc_collect
Packit 534379
Packit 534379
Packit 534379
def _test_import_pybind11():
Packit 534379
    """Early diagnostic for test module initialization errors
Packit 534379
Packit 534379
    When there is an error during initialization, the first import will report the
Packit 534379
    real error while all subsequent imports will report nonsense. This import test
Packit 534379
    is done early (in the pytest configuration file, before any tests) in order to
Packit 534379
    avoid the noise of having all tests fail with identical error messages.
Packit 534379
Packit 534379
    Any possible exception is caught here and reported manually *without* the stack
Packit 534379
    trace. This further reduces noise since the trace would only show pytest internals
Packit 534379
    which are not useful for debugging pybind11 module issues.
Packit 534379
    """
Packit 534379
    # noinspection PyBroadException
Packit 534379
    try:
Packit 534379
        import pybind11_tests  # noqa: F401 imported but unused
Packit 534379
    except Exception as e:
Packit 534379
        print("Failed to import pybind11_tests from pytest:")
Packit 534379
        print("  {}: {}".format(type(e).__name__, e))
Packit 534379
        sys.exit(1)
Packit 534379
Packit 534379
Packit 534379
_test_import_pybind11()