conftest.py revision 12037
111986Sandreas.sandberg@arm.com"""pytest configuration
211986Sandreas.sandberg@arm.com
311986Sandreas.sandberg@arm.comExtends output capture as needed by pybind11: ignore constructors, optional unordered lines.
411986Sandreas.sandberg@arm.comAdds docstring and exceptions message sanitizers: ignore Python 2 vs 3 differences.
511986Sandreas.sandberg@arm.com"""
611986Sandreas.sandberg@arm.com
711986Sandreas.sandberg@arm.comimport pytest
811986Sandreas.sandberg@arm.comimport textwrap
911986Sandreas.sandberg@arm.comimport difflib
1011986Sandreas.sandberg@arm.comimport re
1111986Sandreas.sandberg@arm.comimport sys
1211986Sandreas.sandberg@arm.comimport contextlib
1312037Sandreas.sandberg@arm.comimport platform
1412037Sandreas.sandberg@arm.comimport gc
1511986Sandreas.sandberg@arm.com
1611986Sandreas.sandberg@arm.com_unicode_marker = re.compile(r'u(\'[^\']*\')')
1711986Sandreas.sandberg@arm.com_long_marker = re.compile(r'([0-9])L')
1811986Sandreas.sandberg@arm.com_hexadecimal = re.compile(r'0x[0-9a-fA-F]+')
1911986Sandreas.sandberg@arm.com
2011986Sandreas.sandberg@arm.com
2111986Sandreas.sandberg@arm.comdef _strip_and_dedent(s):
2211986Sandreas.sandberg@arm.com    """For triple-quote strings"""
2311986Sandreas.sandberg@arm.com    return textwrap.dedent(s.lstrip('\n').rstrip())
2411986Sandreas.sandberg@arm.com
2511986Sandreas.sandberg@arm.com
2611986Sandreas.sandberg@arm.comdef _split_and_sort(s):
2711986Sandreas.sandberg@arm.com    """For output which does not require specific line order"""
2811986Sandreas.sandberg@arm.com    return sorted(_strip_and_dedent(s).splitlines())
2911986Sandreas.sandberg@arm.com
3011986Sandreas.sandberg@arm.com
3111986Sandreas.sandberg@arm.comdef _make_explanation(a, b):
3211986Sandreas.sandberg@arm.com    """Explanation for a failed assert -- the a and b arguments are List[str]"""
3311986Sandreas.sandberg@arm.com    return ["--- actual / +++ expected"] + [line.strip('\n') for line in difflib.ndiff(a, b)]
3411986Sandreas.sandberg@arm.com
3511986Sandreas.sandberg@arm.com
3611986Sandreas.sandberg@arm.comclass Output(object):
3711986Sandreas.sandberg@arm.com    """Basic output post-processing and comparison"""
3811986Sandreas.sandberg@arm.com    def __init__(self, string):
3911986Sandreas.sandberg@arm.com        self.string = string
4011986Sandreas.sandberg@arm.com        self.explanation = []
4111986Sandreas.sandberg@arm.com
4211986Sandreas.sandberg@arm.com    def __str__(self):
4311986Sandreas.sandberg@arm.com        return self.string
4411986Sandreas.sandberg@arm.com
4511986Sandreas.sandberg@arm.com    def __eq__(self, other):
4611986Sandreas.sandberg@arm.com        # Ignore constructor/destructor output which is prefixed with "###"
4711986Sandreas.sandberg@arm.com        a = [line for line in self.string.strip().splitlines() if not line.startswith("###")]
4811986Sandreas.sandberg@arm.com        b = _strip_and_dedent(other).splitlines()
4911986Sandreas.sandberg@arm.com        if a == b:
5011986Sandreas.sandberg@arm.com            return True
5111986Sandreas.sandberg@arm.com        else:
5211986Sandreas.sandberg@arm.com            self.explanation = _make_explanation(a, b)
5311986Sandreas.sandberg@arm.com            return False
5411986Sandreas.sandberg@arm.com
5511986Sandreas.sandberg@arm.com
5611986Sandreas.sandberg@arm.comclass Unordered(Output):
5711986Sandreas.sandberg@arm.com    """Custom comparison for output without strict line ordering"""
5811986Sandreas.sandberg@arm.com    def __eq__(self, other):
5911986Sandreas.sandberg@arm.com        a = _split_and_sort(self.string)
6011986Sandreas.sandberg@arm.com        b = _split_and_sort(other)
6111986Sandreas.sandberg@arm.com        if a == b:
6211986Sandreas.sandberg@arm.com            return True
6311986Sandreas.sandberg@arm.com        else:
6411986Sandreas.sandberg@arm.com            self.explanation = _make_explanation(a, b)
6511986Sandreas.sandberg@arm.com            return False
6611986Sandreas.sandberg@arm.com
6711986Sandreas.sandberg@arm.com
6811986Sandreas.sandberg@arm.comclass Capture(object):
6911986Sandreas.sandberg@arm.com    def __init__(self, capfd):
7011986Sandreas.sandberg@arm.com        self.capfd = capfd
7111986Sandreas.sandberg@arm.com        self.out = ""
7211986Sandreas.sandberg@arm.com        self.err = ""
7311986Sandreas.sandberg@arm.com
7411986Sandreas.sandberg@arm.com    def __enter__(self):
7511986Sandreas.sandberg@arm.com        self.capfd.readouterr()
7611986Sandreas.sandberg@arm.com        return self
7711986Sandreas.sandberg@arm.com
7811986Sandreas.sandberg@arm.com    def __exit__(self, *_):
7911986Sandreas.sandberg@arm.com        self.out, self.err = self.capfd.readouterr()
8011986Sandreas.sandberg@arm.com
8111986Sandreas.sandberg@arm.com    def __eq__(self, other):
8211986Sandreas.sandberg@arm.com        a = Output(self.out)
8311986Sandreas.sandberg@arm.com        b = other
8411986Sandreas.sandberg@arm.com        if a == b:
8511986Sandreas.sandberg@arm.com            return True
8611986Sandreas.sandberg@arm.com        else:
8711986Sandreas.sandberg@arm.com            self.explanation = a.explanation
8811986Sandreas.sandberg@arm.com            return False
8911986Sandreas.sandberg@arm.com
9011986Sandreas.sandberg@arm.com    def __str__(self):
9111986Sandreas.sandberg@arm.com        return self.out
9211986Sandreas.sandberg@arm.com
9311986Sandreas.sandberg@arm.com    def __contains__(self, item):
9411986Sandreas.sandberg@arm.com        return item in self.out
9511986Sandreas.sandberg@arm.com
9611986Sandreas.sandberg@arm.com    @property
9711986Sandreas.sandberg@arm.com    def unordered(self):
9811986Sandreas.sandberg@arm.com        return Unordered(self.out)
9911986Sandreas.sandberg@arm.com
10011986Sandreas.sandberg@arm.com    @property
10111986Sandreas.sandberg@arm.com    def stderr(self):
10211986Sandreas.sandberg@arm.com        return Output(self.err)
10311986Sandreas.sandberg@arm.com
10411986Sandreas.sandberg@arm.com
10511986Sandreas.sandberg@arm.com@pytest.fixture
10612037Sandreas.sandberg@arm.comdef capture(capsys):
10712037Sandreas.sandberg@arm.com    """Extended `capsys` with context manager and custom equality operators"""
10812037Sandreas.sandberg@arm.com    return Capture(capsys)
10911986Sandreas.sandberg@arm.com
11011986Sandreas.sandberg@arm.com
11111986Sandreas.sandberg@arm.comclass SanitizedString(object):
11211986Sandreas.sandberg@arm.com    def __init__(self, sanitizer):
11311986Sandreas.sandberg@arm.com        self.sanitizer = sanitizer
11411986Sandreas.sandberg@arm.com        self.string = ""
11511986Sandreas.sandberg@arm.com        self.explanation = []
11611986Sandreas.sandberg@arm.com
11711986Sandreas.sandberg@arm.com    def __call__(self, thing):
11811986Sandreas.sandberg@arm.com        self.string = self.sanitizer(thing)
11911986Sandreas.sandberg@arm.com        return self
12011986Sandreas.sandberg@arm.com
12111986Sandreas.sandberg@arm.com    def __eq__(self, other):
12211986Sandreas.sandberg@arm.com        a = self.string
12311986Sandreas.sandberg@arm.com        b = _strip_and_dedent(other)
12411986Sandreas.sandberg@arm.com        if a == b:
12511986Sandreas.sandberg@arm.com            return True
12611986Sandreas.sandberg@arm.com        else:
12711986Sandreas.sandberg@arm.com            self.explanation = _make_explanation(a.splitlines(), b.splitlines())
12811986Sandreas.sandberg@arm.com            return False
12911986Sandreas.sandberg@arm.com
13011986Sandreas.sandberg@arm.com
13111986Sandreas.sandberg@arm.comdef _sanitize_general(s):
13211986Sandreas.sandberg@arm.com    s = s.strip()
13311986Sandreas.sandberg@arm.com    s = s.replace("pybind11_tests.", "m.")
13411986Sandreas.sandberg@arm.com    s = s.replace("unicode", "str")
13511986Sandreas.sandberg@arm.com    s = _long_marker.sub(r"\1", s)
13611986Sandreas.sandberg@arm.com    s = _unicode_marker.sub(r"\1", s)
13711986Sandreas.sandberg@arm.com    return s
13811986Sandreas.sandberg@arm.com
13911986Sandreas.sandberg@arm.com
14011986Sandreas.sandberg@arm.comdef _sanitize_docstring(thing):
14111986Sandreas.sandberg@arm.com    s = thing.__doc__
14211986Sandreas.sandberg@arm.com    s = _sanitize_general(s)
14311986Sandreas.sandberg@arm.com    return s
14411986Sandreas.sandberg@arm.com
14511986Sandreas.sandberg@arm.com
14611986Sandreas.sandberg@arm.com@pytest.fixture
14711986Sandreas.sandberg@arm.comdef doc():
14811986Sandreas.sandberg@arm.com    """Sanitize docstrings and add custom failure explanation"""
14911986Sandreas.sandberg@arm.com    return SanitizedString(_sanitize_docstring)
15011986Sandreas.sandberg@arm.com
15111986Sandreas.sandberg@arm.com
15211986Sandreas.sandberg@arm.comdef _sanitize_message(thing):
15311986Sandreas.sandberg@arm.com    s = str(thing)
15411986Sandreas.sandberg@arm.com    s = _sanitize_general(s)
15511986Sandreas.sandberg@arm.com    s = _hexadecimal.sub("0", s)
15611986Sandreas.sandberg@arm.com    return s
15711986Sandreas.sandberg@arm.com
15811986Sandreas.sandberg@arm.com
15911986Sandreas.sandberg@arm.com@pytest.fixture
16011986Sandreas.sandberg@arm.comdef msg():
16111986Sandreas.sandberg@arm.com    """Sanitize messages and add custom failure explanation"""
16211986Sandreas.sandberg@arm.com    return SanitizedString(_sanitize_message)
16311986Sandreas.sandberg@arm.com
16411986Sandreas.sandberg@arm.com
16511986Sandreas.sandberg@arm.com# noinspection PyUnusedLocal
16611986Sandreas.sandberg@arm.comdef pytest_assertrepr_compare(op, left, right):
16711986Sandreas.sandberg@arm.com    """Hook to insert custom failure explanation"""
16811986Sandreas.sandberg@arm.com    if hasattr(left, 'explanation'):
16911986Sandreas.sandberg@arm.com        return left.explanation
17011986Sandreas.sandberg@arm.com
17111986Sandreas.sandberg@arm.com
17211986Sandreas.sandberg@arm.com@contextlib.contextmanager
17311986Sandreas.sandberg@arm.comdef suppress(exception):
17411986Sandreas.sandberg@arm.com    """Suppress the desired exception"""
17511986Sandreas.sandberg@arm.com    try:
17611986Sandreas.sandberg@arm.com        yield
17711986Sandreas.sandberg@arm.com    except exception:
17811986Sandreas.sandberg@arm.com        pass
17911986Sandreas.sandberg@arm.com
18011986Sandreas.sandberg@arm.com
18112037Sandreas.sandberg@arm.comdef gc_collect():
18212037Sandreas.sandberg@arm.com    ''' Run the garbage collector twice (needed when running
18312037Sandreas.sandberg@arm.com    reference counting tests with PyPy) '''
18412037Sandreas.sandberg@arm.com    gc.collect()
18512037Sandreas.sandberg@arm.com    gc.collect()
18612037Sandreas.sandberg@arm.com
18712037Sandreas.sandberg@arm.com
18811986Sandreas.sandberg@arm.comdef pytest_namespace():
18911986Sandreas.sandberg@arm.com    """Add import suppression and test requirements to `pytest` namespace"""
19011986Sandreas.sandberg@arm.com    try:
19111986Sandreas.sandberg@arm.com        import numpy as np
19211986Sandreas.sandberg@arm.com    except ImportError:
19311986Sandreas.sandberg@arm.com        np = None
19411986Sandreas.sandberg@arm.com    try:
19511986Sandreas.sandberg@arm.com        import scipy
19611986Sandreas.sandberg@arm.com    except ImportError:
19711986Sandreas.sandberg@arm.com        scipy = None
19811986Sandreas.sandberg@arm.com    try:
19911986Sandreas.sandberg@arm.com        from pybind11_tests import have_eigen
20011986Sandreas.sandberg@arm.com    except ImportError:
20111986Sandreas.sandberg@arm.com        have_eigen = False
20212037Sandreas.sandberg@arm.com    pypy = platform.python_implementation() == "PyPy"
20311986Sandreas.sandberg@arm.com
20411986Sandreas.sandberg@arm.com    skipif = pytest.mark.skipif
20511986Sandreas.sandberg@arm.com    return {
20611986Sandreas.sandberg@arm.com        'suppress': suppress,
20711986Sandreas.sandberg@arm.com        'requires_numpy': skipif(not np, reason="numpy is not installed"),
20811986Sandreas.sandberg@arm.com        'requires_scipy': skipif(not np, reason="scipy is not installed"),
20911986Sandreas.sandberg@arm.com        'requires_eigen_and_numpy': skipif(not have_eigen or not np,
21011986Sandreas.sandberg@arm.com                                           reason="eigen and/or numpy are not installed"),
21111986Sandreas.sandberg@arm.com        'requires_eigen_and_scipy': skipif(not have_eigen or not scipy,
21211986Sandreas.sandberg@arm.com                                           reason="eigen and/or scipy are not installed"),
21312037Sandreas.sandberg@arm.com        'unsupported_on_pypy': skipif(pypy, reason="unsupported on PyPy"),
21412037Sandreas.sandberg@arm.com        'gc_collect': gc_collect
21511986Sandreas.sandberg@arm.com    }
21611986Sandreas.sandberg@arm.com
21711986Sandreas.sandberg@arm.com
21811986Sandreas.sandberg@arm.comdef _test_import_pybind11():
21911986Sandreas.sandberg@arm.com    """Early diagnostic for test module initialization errors
22011986Sandreas.sandberg@arm.com
22111986Sandreas.sandberg@arm.com    When there is an error during initialization, the first import will report the
22211986Sandreas.sandberg@arm.com    real error while all subsequent imports will report nonsense. This import test
22311986Sandreas.sandberg@arm.com    is done early (in the pytest configuration file, before any tests) in order to
22411986Sandreas.sandberg@arm.com    avoid the noise of having all tests fail with identical error messages.
22511986Sandreas.sandberg@arm.com
22611986Sandreas.sandberg@arm.com    Any possible exception is caught here and reported manually *without* the stack
22711986Sandreas.sandberg@arm.com    trace. This further reduces noise since the trace would only show pytest internals
22811986Sandreas.sandberg@arm.com    which are not useful for debugging pybind11 module issues.
22911986Sandreas.sandberg@arm.com    """
23011986Sandreas.sandberg@arm.com    # noinspection PyBroadException
23111986Sandreas.sandberg@arm.com    try:
23211986Sandreas.sandberg@arm.com        import pybind11_tests  # noqa: F401 imported but unused
23311986Sandreas.sandberg@arm.com    except Exception as e:
23411986Sandreas.sandberg@arm.com        print("Failed to import pybind11_tests from pytest:")
23511986Sandreas.sandberg@arm.com        print("  {}: {}".format(type(e).__name__, e))
23611986Sandreas.sandberg@arm.com        sys.exit(1)
23711986Sandreas.sandberg@arm.com
23811986Sandreas.sandberg@arm.com
23911986Sandreas.sandberg@arm.com_test_import_pybind11()
240