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
2014299Sbbruce@ucdavis.edu# test_async.py requires support for async and await
2114299Sbbruce@ucdavis.educollect_ignore = []
2214299Sbbruce@ucdavis.eduif sys.version_info[:2] < (3, 5):
2314299Sbbruce@ucdavis.edu    collect_ignore.append("test_async.py")
2414299Sbbruce@ucdavis.edu
2511986Sandreas.sandberg@arm.com
2611986Sandreas.sandberg@arm.comdef _strip_and_dedent(s):
2711986Sandreas.sandberg@arm.com    """For triple-quote strings"""
2811986Sandreas.sandberg@arm.com    return textwrap.dedent(s.lstrip('\n').rstrip())
2911986Sandreas.sandberg@arm.com
3011986Sandreas.sandberg@arm.com
3111986Sandreas.sandberg@arm.comdef _split_and_sort(s):
3211986Sandreas.sandberg@arm.com    """For output which does not require specific line order"""
3311986Sandreas.sandberg@arm.com    return sorted(_strip_and_dedent(s).splitlines())
3411986Sandreas.sandberg@arm.com
3511986Sandreas.sandberg@arm.com
3611986Sandreas.sandberg@arm.comdef _make_explanation(a, b):
3711986Sandreas.sandberg@arm.com    """Explanation for a failed assert -- the a and b arguments are List[str]"""
3811986Sandreas.sandberg@arm.com    return ["--- actual / +++ expected"] + [line.strip('\n') for line in difflib.ndiff(a, b)]
3911986Sandreas.sandberg@arm.com
4011986Sandreas.sandberg@arm.com
4111986Sandreas.sandberg@arm.comclass Output(object):
4211986Sandreas.sandberg@arm.com    """Basic output post-processing and comparison"""
4311986Sandreas.sandberg@arm.com    def __init__(self, string):
4411986Sandreas.sandberg@arm.com        self.string = string
4511986Sandreas.sandberg@arm.com        self.explanation = []
4611986Sandreas.sandberg@arm.com
4711986Sandreas.sandberg@arm.com    def __str__(self):
4811986Sandreas.sandberg@arm.com        return self.string
4911986Sandreas.sandberg@arm.com
5011986Sandreas.sandberg@arm.com    def __eq__(self, other):
5111986Sandreas.sandberg@arm.com        # Ignore constructor/destructor output which is prefixed with "###"
5211986Sandreas.sandberg@arm.com        a = [line for line in self.string.strip().splitlines() if not line.startswith("###")]
5311986Sandreas.sandberg@arm.com        b = _strip_and_dedent(other).splitlines()
5411986Sandreas.sandberg@arm.com        if a == b:
5511986Sandreas.sandberg@arm.com            return True
5611986Sandreas.sandberg@arm.com        else:
5711986Sandreas.sandberg@arm.com            self.explanation = _make_explanation(a, b)
5811986Sandreas.sandberg@arm.com            return False
5911986Sandreas.sandberg@arm.com
6011986Sandreas.sandberg@arm.com
6111986Sandreas.sandberg@arm.comclass Unordered(Output):
6211986Sandreas.sandberg@arm.com    """Custom comparison for output without strict line ordering"""
6311986Sandreas.sandberg@arm.com    def __eq__(self, other):
6411986Sandreas.sandberg@arm.com        a = _split_and_sort(self.string)
6511986Sandreas.sandberg@arm.com        b = _split_and_sort(other)
6611986Sandreas.sandberg@arm.com        if a == b:
6711986Sandreas.sandberg@arm.com            return True
6811986Sandreas.sandberg@arm.com        else:
6911986Sandreas.sandberg@arm.com            self.explanation = _make_explanation(a, b)
7011986Sandreas.sandberg@arm.com            return False
7111986Sandreas.sandberg@arm.com
7211986Sandreas.sandberg@arm.com
7311986Sandreas.sandberg@arm.comclass Capture(object):
7411986Sandreas.sandberg@arm.com    def __init__(self, capfd):
7511986Sandreas.sandberg@arm.com        self.capfd = capfd
7611986Sandreas.sandberg@arm.com        self.out = ""
7711986Sandreas.sandberg@arm.com        self.err = ""
7811986Sandreas.sandberg@arm.com
7911986Sandreas.sandberg@arm.com    def __enter__(self):
8011986Sandreas.sandberg@arm.com        self.capfd.readouterr()
8111986Sandreas.sandberg@arm.com        return self
8211986Sandreas.sandberg@arm.com
8314299Sbbruce@ucdavis.edu    def __exit__(self, *args):
8411986Sandreas.sandberg@arm.com        self.out, self.err = self.capfd.readouterr()
8511986Sandreas.sandberg@arm.com
8611986Sandreas.sandberg@arm.com    def __eq__(self, other):
8711986Sandreas.sandberg@arm.com        a = Output(self.out)
8811986Sandreas.sandberg@arm.com        b = other
8911986Sandreas.sandberg@arm.com        if a == b:
9011986Sandreas.sandberg@arm.com            return True
9111986Sandreas.sandberg@arm.com        else:
9211986Sandreas.sandberg@arm.com            self.explanation = a.explanation
9311986Sandreas.sandberg@arm.com            return False
9411986Sandreas.sandberg@arm.com
9511986Sandreas.sandberg@arm.com    def __str__(self):
9611986Sandreas.sandberg@arm.com        return self.out
9711986Sandreas.sandberg@arm.com
9811986Sandreas.sandberg@arm.com    def __contains__(self, item):
9911986Sandreas.sandberg@arm.com        return item in self.out
10011986Sandreas.sandberg@arm.com
10111986Sandreas.sandberg@arm.com    @property
10211986Sandreas.sandberg@arm.com    def unordered(self):
10311986Sandreas.sandberg@arm.com        return Unordered(self.out)
10411986Sandreas.sandberg@arm.com
10511986Sandreas.sandberg@arm.com    @property
10611986Sandreas.sandberg@arm.com    def stderr(self):
10711986Sandreas.sandberg@arm.com        return Output(self.err)
10811986Sandreas.sandberg@arm.com
10911986Sandreas.sandberg@arm.com
11011986Sandreas.sandberg@arm.com@pytest.fixture
11112037Sandreas.sandberg@arm.comdef capture(capsys):
11212037Sandreas.sandberg@arm.com    """Extended `capsys` with context manager and custom equality operators"""
11312037Sandreas.sandberg@arm.com    return Capture(capsys)
11411986Sandreas.sandberg@arm.com
11511986Sandreas.sandberg@arm.com
11611986Sandreas.sandberg@arm.comclass SanitizedString(object):
11711986Sandreas.sandberg@arm.com    def __init__(self, sanitizer):
11811986Sandreas.sandberg@arm.com        self.sanitizer = sanitizer
11911986Sandreas.sandberg@arm.com        self.string = ""
12011986Sandreas.sandberg@arm.com        self.explanation = []
12111986Sandreas.sandberg@arm.com
12211986Sandreas.sandberg@arm.com    def __call__(self, thing):
12311986Sandreas.sandberg@arm.com        self.string = self.sanitizer(thing)
12411986Sandreas.sandberg@arm.com        return self
12511986Sandreas.sandberg@arm.com
12611986Sandreas.sandberg@arm.com    def __eq__(self, other):
12711986Sandreas.sandberg@arm.com        a = self.string
12811986Sandreas.sandberg@arm.com        b = _strip_and_dedent(other)
12911986Sandreas.sandberg@arm.com        if a == b:
13011986Sandreas.sandberg@arm.com            return True
13111986Sandreas.sandberg@arm.com        else:
13211986Sandreas.sandberg@arm.com            self.explanation = _make_explanation(a.splitlines(), b.splitlines())
13311986Sandreas.sandberg@arm.com            return False
13411986Sandreas.sandberg@arm.com
13511986Sandreas.sandberg@arm.com
13611986Sandreas.sandberg@arm.comdef _sanitize_general(s):
13711986Sandreas.sandberg@arm.com    s = s.strip()
13811986Sandreas.sandberg@arm.com    s = s.replace("pybind11_tests.", "m.")
13911986Sandreas.sandberg@arm.com    s = s.replace("unicode", "str")
14011986Sandreas.sandberg@arm.com    s = _long_marker.sub(r"\1", s)
14111986Sandreas.sandberg@arm.com    s = _unicode_marker.sub(r"\1", s)
14211986Sandreas.sandberg@arm.com    return s
14311986Sandreas.sandberg@arm.com
14411986Sandreas.sandberg@arm.com
14511986Sandreas.sandberg@arm.comdef _sanitize_docstring(thing):
14611986Sandreas.sandberg@arm.com    s = thing.__doc__
14711986Sandreas.sandberg@arm.com    s = _sanitize_general(s)
14811986Sandreas.sandberg@arm.com    return s
14911986Sandreas.sandberg@arm.com
15011986Sandreas.sandberg@arm.com
15111986Sandreas.sandberg@arm.com@pytest.fixture
15211986Sandreas.sandberg@arm.comdef doc():
15311986Sandreas.sandberg@arm.com    """Sanitize docstrings and add custom failure explanation"""
15411986Sandreas.sandberg@arm.com    return SanitizedString(_sanitize_docstring)
15511986Sandreas.sandberg@arm.com
15611986Sandreas.sandberg@arm.com
15711986Sandreas.sandberg@arm.comdef _sanitize_message(thing):
15811986Sandreas.sandberg@arm.com    s = str(thing)
15911986Sandreas.sandberg@arm.com    s = _sanitize_general(s)
16011986Sandreas.sandberg@arm.com    s = _hexadecimal.sub("0", s)
16111986Sandreas.sandberg@arm.com    return s
16211986Sandreas.sandberg@arm.com
16311986Sandreas.sandberg@arm.com
16411986Sandreas.sandberg@arm.com@pytest.fixture
16511986Sandreas.sandberg@arm.comdef msg():
16611986Sandreas.sandberg@arm.com    """Sanitize messages and add custom failure explanation"""
16711986Sandreas.sandberg@arm.com    return SanitizedString(_sanitize_message)
16811986Sandreas.sandberg@arm.com
16911986Sandreas.sandberg@arm.com
17011986Sandreas.sandberg@arm.com# noinspection PyUnusedLocal
17111986Sandreas.sandberg@arm.comdef pytest_assertrepr_compare(op, left, right):
17211986Sandreas.sandberg@arm.com    """Hook to insert custom failure explanation"""
17311986Sandreas.sandberg@arm.com    if hasattr(left, 'explanation'):
17411986Sandreas.sandberg@arm.com        return left.explanation
17511986Sandreas.sandberg@arm.com
17611986Sandreas.sandberg@arm.com
17711986Sandreas.sandberg@arm.com@contextlib.contextmanager
17811986Sandreas.sandberg@arm.comdef suppress(exception):
17911986Sandreas.sandberg@arm.com    """Suppress the desired exception"""
18011986Sandreas.sandberg@arm.com    try:
18111986Sandreas.sandberg@arm.com        yield
18211986Sandreas.sandberg@arm.com    except exception:
18311986Sandreas.sandberg@arm.com        pass
18411986Sandreas.sandberg@arm.com
18511986Sandreas.sandberg@arm.com
18612037Sandreas.sandberg@arm.comdef gc_collect():
18712037Sandreas.sandberg@arm.com    ''' Run the garbage collector twice (needed when running
18812037Sandreas.sandberg@arm.com    reference counting tests with PyPy) '''
18912037Sandreas.sandberg@arm.com    gc.collect()
19012037Sandreas.sandberg@arm.com    gc.collect()
19112037Sandreas.sandberg@arm.com
19212037Sandreas.sandberg@arm.com
19314299Sbbruce@ucdavis.edudef pytest_configure():
19411986Sandreas.sandberg@arm.com    """Add import suppression and test requirements to `pytest` namespace"""
19511986Sandreas.sandberg@arm.com    try:
19611986Sandreas.sandberg@arm.com        import numpy as np
19711986Sandreas.sandberg@arm.com    except ImportError:
19811986Sandreas.sandberg@arm.com        np = None
19911986Sandreas.sandberg@arm.com    try:
20011986Sandreas.sandberg@arm.com        import scipy
20111986Sandreas.sandberg@arm.com    except ImportError:
20211986Sandreas.sandberg@arm.com        scipy = None
20311986Sandreas.sandberg@arm.com    try:
20412391Sjason@lowepower.com        from pybind11_tests.eigen import have_eigen
20511986Sandreas.sandberg@arm.com    except ImportError:
20611986Sandreas.sandberg@arm.com        have_eigen = False
20712037Sandreas.sandberg@arm.com    pypy = platform.python_implementation() == "PyPy"
20811986Sandreas.sandberg@arm.com
20911986Sandreas.sandberg@arm.com    skipif = pytest.mark.skipif
21014299Sbbruce@ucdavis.edu    pytest.suppress = suppress
21114299Sbbruce@ucdavis.edu    pytest.requires_numpy = skipif(not np, reason="numpy is not installed")
21214299Sbbruce@ucdavis.edu    pytest.requires_scipy = skipif(not np, reason="scipy is not installed")
21314299Sbbruce@ucdavis.edu    pytest.requires_eigen_and_numpy = skipif(not have_eigen or not np,
21414299Sbbruce@ucdavis.edu                                             reason="eigen and/or numpy are not installed")
21514299Sbbruce@ucdavis.edu    pytest.requires_eigen_and_scipy = skipif(
21614299Sbbruce@ucdavis.edu        not have_eigen or not scipy, reason="eigen and/or scipy are not installed")
21714299Sbbruce@ucdavis.edu    pytest.unsupported_on_pypy = skipif(pypy, reason="unsupported on PyPy")
21814299Sbbruce@ucdavis.edu    pytest.unsupported_on_py2 = skipif(sys.version_info.major < 3,
21914299Sbbruce@ucdavis.edu                                       reason="unsupported on Python 2.x")
22014299Sbbruce@ucdavis.edu    pytest.gc_collect = gc_collect
22111986Sandreas.sandberg@arm.com
22211986Sandreas.sandberg@arm.com
22311986Sandreas.sandberg@arm.comdef _test_import_pybind11():
22411986Sandreas.sandberg@arm.com    """Early diagnostic for test module initialization errors
22511986Sandreas.sandberg@arm.com
22611986Sandreas.sandberg@arm.com    When there is an error during initialization, the first import will report the
22711986Sandreas.sandberg@arm.com    real error while all subsequent imports will report nonsense. This import test
22811986Sandreas.sandberg@arm.com    is done early (in the pytest configuration file, before any tests) in order to
22911986Sandreas.sandberg@arm.com    avoid the noise of having all tests fail with identical error messages.
23011986Sandreas.sandberg@arm.com
23111986Sandreas.sandberg@arm.com    Any possible exception is caught here and reported manually *without* the stack
23211986Sandreas.sandberg@arm.com    trace. This further reduces noise since the trace would only show pytest internals
23311986Sandreas.sandberg@arm.com    which are not useful for debugging pybind11 module issues.
23411986Sandreas.sandberg@arm.com    """
23511986Sandreas.sandberg@arm.com    # noinspection PyBroadException
23611986Sandreas.sandberg@arm.com    try:
23711986Sandreas.sandberg@arm.com        import pybind11_tests  # noqa: F401 imported but unused
23811986Sandreas.sandberg@arm.com    except Exception as e:
23911986Sandreas.sandberg@arm.com        print("Failed to import pybind11_tests from pytest:")
24011986Sandreas.sandberg@arm.com        print("  {}: {}".format(type(e).__name__, e))
24111986Sandreas.sandberg@arm.com        sys.exit(1)
24211986Sandreas.sandberg@arm.com
24311986Sandreas.sandberg@arm.com
24411986Sandreas.sandberg@arm.com_test_import_pybind11()
245