conftest.py revision 14299
14484Sbinkertn@umich.edu"""pytest configuration
24484Sbinkertn@umich.edu
34484Sbinkertn@umich.eduExtends output capture as needed by pybind11: ignore constructors, optional unordered lines.
44484Sbinkertn@umich.eduAdds docstring and exceptions message sanitizers: ignore Python 2 vs 3 differences.
54484Sbinkertn@umich.edu"""
64484Sbinkertn@umich.edu
74484Sbinkertn@umich.eduimport pytest
84484Sbinkertn@umich.eduimport textwrap
94484Sbinkertn@umich.eduimport difflib
104484Sbinkertn@umich.eduimport re
114484Sbinkertn@umich.eduimport sys
124484Sbinkertn@umich.eduimport contextlib
134484Sbinkertn@umich.eduimport platform
144484Sbinkertn@umich.eduimport gc
154484Sbinkertn@umich.edu
164484Sbinkertn@umich.edu_unicode_marker = re.compile(r'u(\'[^\']*\')')
174484Sbinkertn@umich.edu_long_marker = re.compile(r'([0-9])L')
184484Sbinkertn@umich.edu_hexadecimal = re.compile(r'0x[0-9a-fA-F]+')
194484Sbinkertn@umich.edu
204484Sbinkertn@umich.edu# test_async.py requires support for async and await
214484Sbinkertn@umich.educollect_ignore = []
224484Sbinkertn@umich.eduif sys.version_info[:2] < (3, 5):
234484Sbinkertn@umich.edu    collect_ignore.append("test_async.py")
244484Sbinkertn@umich.edu
254484Sbinkertn@umich.edu
264484Sbinkertn@umich.edudef _strip_and_dedent(s):
274484Sbinkertn@umich.edu    """For triple-quote strings"""
284484Sbinkertn@umich.edu    return textwrap.dedent(s.lstrip('\n').rstrip())
294484Sbinkertn@umich.edu
304484Sbinkertn@umich.edu
314484Sbinkertn@umich.edudef _split_and_sort(s):
324484Sbinkertn@umich.edu    """For output which does not require specific line order"""
334484Sbinkertn@umich.edu    return sorted(_strip_and_dedent(s).splitlines())
344484Sbinkertn@umich.edu
354484Sbinkertn@umich.edu
364484Sbinkertn@umich.edudef _make_explanation(a, b):
374484Sbinkertn@umich.edu    """Explanation for a failed assert -- the a and b arguments are List[str]"""
384484Sbinkertn@umich.edu    return ["--- actual / +++ expected"] + [line.strip('\n') for line in difflib.ndiff(a, b)]
394484Sbinkertn@umich.edu
404484Sbinkertn@umich.edu
414484Sbinkertn@umich.educlass Output(object):
424484Sbinkertn@umich.edu    """Basic output post-processing and comparison"""
434484Sbinkertn@umich.edu    def __init__(self, string):
444484Sbinkertn@umich.edu        self.string = string
454484Sbinkertn@umich.edu        self.explanation = []
464484Sbinkertn@umich.edu
474484Sbinkertn@umich.edu    def __str__(self):
484484Sbinkertn@umich.edu        return self.string
494484Sbinkertn@umich.edu
504484Sbinkertn@umich.edu    def __eq__(self, other):
514484Sbinkertn@umich.edu        # Ignore constructor/destructor output which is prefixed with "###"
524484Sbinkertn@umich.edu        a = [line for line in self.string.strip().splitlines() if not line.startswith("###")]
534484Sbinkertn@umich.edu        b = _strip_and_dedent(other).splitlines()
544484Sbinkertn@umich.edu        if a == b:
554484Sbinkertn@umich.edu            return True
564484Sbinkertn@umich.edu        else:
574484Sbinkertn@umich.edu            self.explanation = _make_explanation(a, b)
584484Sbinkertn@umich.edu            return False
594484Sbinkertn@umich.edu
604484Sbinkertn@umich.edu
614484Sbinkertn@umich.educlass Unordered(Output):
624484Sbinkertn@umich.edu    """Custom comparison for output without strict line ordering"""
634484Sbinkertn@umich.edu    def __eq__(self, other):
644484Sbinkertn@umich.edu        a = _split_and_sort(self.string)
654484Sbinkertn@umich.edu        b = _split_and_sort(other)
664484Sbinkertn@umich.edu        if a == b:
674484Sbinkertn@umich.edu            return True
684484Sbinkertn@umich.edu        else:
694484Sbinkertn@umich.edu            self.explanation = _make_explanation(a, b)
704484Sbinkertn@umich.edu            return False
714484Sbinkertn@umich.edu
724484Sbinkertn@umich.edu
734484Sbinkertn@umich.educlass Capture(object):
744484Sbinkertn@umich.edu    def __init__(self, capfd):
754484Sbinkertn@umich.edu        self.capfd = capfd
764484Sbinkertn@umich.edu        self.out = ""
774484Sbinkertn@umich.edu        self.err = ""
784484Sbinkertn@umich.edu
794484Sbinkertn@umich.edu    def __enter__(self):
804484Sbinkertn@umich.edu        self.capfd.readouterr()
814484Sbinkertn@umich.edu        return self
824484Sbinkertn@umich.edu
834484Sbinkertn@umich.edu    def __exit__(self, *args):
844484Sbinkertn@umich.edu        self.out, self.err = self.capfd.readouterr()
854484Sbinkertn@umich.edu
864484Sbinkertn@umich.edu    def __eq__(self, other):
874484Sbinkertn@umich.edu        a = Output(self.out)
884484Sbinkertn@umich.edu        b = other
894484Sbinkertn@umich.edu        if a == b:
904484Sbinkertn@umich.edu            return True
914484Sbinkertn@umich.edu        else:
924484Sbinkertn@umich.edu            self.explanation = a.explanation
934484Sbinkertn@umich.edu            return False
944484Sbinkertn@umich.edu
954484Sbinkertn@umich.edu    def __str__(self):
964484Sbinkertn@umich.edu        return self.out
974484Sbinkertn@umich.edu
984484Sbinkertn@umich.edu    def __contains__(self, item):
994484Sbinkertn@umich.edu        return item in self.out
1004484Sbinkertn@umich.edu
1014484Sbinkertn@umich.edu    @property
1024484Sbinkertn@umich.edu    def unordered(self):
1034484Sbinkertn@umich.edu        return Unordered(self.out)
1044484Sbinkertn@umich.edu
1054484Sbinkertn@umich.edu    @property
1064484Sbinkertn@umich.edu    def stderr(self):
1074484Sbinkertn@umich.edu        return Output(self.err)
1084484Sbinkertn@umich.edu
1094484Sbinkertn@umich.edu
1104484Sbinkertn@umich.edu@pytest.fixture
1114484Sbinkertn@umich.edudef capture(capsys):
1124484Sbinkertn@umich.edu    """Extended `capsys` with context manager and custom equality operators"""
1134484Sbinkertn@umich.edu    return Capture(capsys)
1144484Sbinkertn@umich.edu
1154484Sbinkertn@umich.edu
1164484Sbinkertn@umich.educlass SanitizedString(object):
1174484Sbinkertn@umich.edu    def __init__(self, sanitizer):
1184484Sbinkertn@umich.edu        self.sanitizer = sanitizer
1194484Sbinkertn@umich.edu        self.string = ""
1204484Sbinkertn@umich.edu        self.explanation = []
1214484Sbinkertn@umich.edu
1224484Sbinkertn@umich.edu    def __call__(self, thing):
1234484Sbinkertn@umich.edu        self.string = self.sanitizer(thing)
1244484Sbinkertn@umich.edu        return self
1254484Sbinkertn@umich.edu
1264484Sbinkertn@umich.edu    def __eq__(self, other):
1274484Sbinkertn@umich.edu        a = self.string
1284484Sbinkertn@umich.edu        b = _strip_and_dedent(other)
1294484Sbinkertn@umich.edu        if a == b:
1304484Sbinkertn@umich.edu            return True
1314484Sbinkertn@umich.edu        else:
1324484Sbinkertn@umich.edu            self.explanation = _make_explanation(a.splitlines(), b.splitlines())
1334484Sbinkertn@umich.edu            return False
1344484Sbinkertn@umich.edu
1354484Sbinkertn@umich.edu
1364484Sbinkertn@umich.edudef _sanitize_general(s):
1374484Sbinkertn@umich.edu    s = s.strip()
1384484Sbinkertn@umich.edu    s = s.replace("pybind11_tests.", "m.")
1394484Sbinkertn@umich.edu    s = s.replace("unicode", "str")
1404484Sbinkertn@umich.edu    s = _long_marker.sub(r"\1", s)
1414484Sbinkertn@umich.edu    s = _unicode_marker.sub(r"\1", s)
1424484Sbinkertn@umich.edu    return s
1434484Sbinkertn@umich.edu
1444484Sbinkertn@umich.edu
1454484Sbinkertn@umich.edudef _sanitize_docstring(thing):
1464484Sbinkertn@umich.edu    s = thing.__doc__
1474484Sbinkertn@umich.edu    s = _sanitize_general(s)
1484484Sbinkertn@umich.edu    return s
1494484Sbinkertn@umich.edu
1504484Sbinkertn@umich.edu
1514484Sbinkertn@umich.edu@pytest.fixture
1524484Sbinkertn@umich.edudef doc():
1534484Sbinkertn@umich.edu    """Sanitize docstrings and add custom failure explanation"""
1544484Sbinkertn@umich.edu    return SanitizedString(_sanitize_docstring)
1554484Sbinkertn@umich.edu
1564484Sbinkertn@umich.edu
1574484Sbinkertn@umich.edudef _sanitize_message(thing):
1584484Sbinkertn@umich.edu    s = str(thing)
1594484Sbinkertn@umich.edu    s = _sanitize_general(s)
1604484Sbinkertn@umich.edu    s = _hexadecimal.sub("0", s)
1614484Sbinkertn@umich.edu    return s
1624484Sbinkertn@umich.edu
1634484Sbinkertn@umich.edu
1644484Sbinkertn@umich.edu@pytest.fixture
1654484Sbinkertn@umich.edudef msg():
1664484Sbinkertn@umich.edu    """Sanitize messages and add custom failure explanation"""
1674484Sbinkertn@umich.edu    return SanitizedString(_sanitize_message)
1684484Sbinkertn@umich.edu
1694484Sbinkertn@umich.edu
1704484Sbinkertn@umich.edu# noinspection PyUnusedLocal
1714484Sbinkertn@umich.edudef pytest_assertrepr_compare(op, left, right):
1724484Sbinkertn@umich.edu    """Hook to insert custom failure explanation"""
1734484Sbinkertn@umich.edu    if hasattr(left, 'explanation'):
1744484Sbinkertn@umich.edu        return left.explanation
1754484Sbinkertn@umich.edu
1764484Sbinkertn@umich.edu
1774484Sbinkertn@umich.edu@contextlib.contextmanager
1784484Sbinkertn@umich.edudef suppress(exception):
1794484Sbinkertn@umich.edu    """Suppress the desired exception"""
1804484Sbinkertn@umich.edu    try:
1814484Sbinkertn@umich.edu        yield
1824484Sbinkertn@umich.edu    except exception:
1834484Sbinkertn@umich.edu        pass
1844484Sbinkertn@umich.edu
1854484Sbinkertn@umich.edu
1864484Sbinkertn@umich.edudef gc_collect():
1874484Sbinkertn@umich.edu    ''' Run the garbage collector twice (needed when running
1884484Sbinkertn@umich.edu    reference counting tests with PyPy) '''
1894484Sbinkertn@umich.edu    gc.collect()
1904484Sbinkertn@umich.edu    gc.collect()
1914484Sbinkertn@umich.edu
1924484Sbinkertn@umich.edu
1934484Sbinkertn@umich.edudef pytest_configure():
1944484Sbinkertn@umich.edu    """Add import suppression and test requirements to `pytest` namespace"""
1954484Sbinkertn@umich.edu    try:
1964484Sbinkertn@umich.edu        import numpy as np
1974484Sbinkertn@umich.edu    except ImportError:
1984484Sbinkertn@umich.edu        np = None
1994484Sbinkertn@umich.edu    try:
2004484Sbinkertn@umich.edu        import scipy
2014484Sbinkertn@umich.edu    except ImportError:
2024484Sbinkertn@umich.edu        scipy = None
2034484Sbinkertn@umich.edu    try:
2044484Sbinkertn@umich.edu        from pybind11_tests.eigen import have_eigen
2054484Sbinkertn@umich.edu    except ImportError:
2064484Sbinkertn@umich.edu        have_eigen = False
2074484Sbinkertn@umich.edu    pypy = platform.python_implementation() == "PyPy"
2084484Sbinkertn@umich.edu
2094484Sbinkertn@umich.edu    skipif = pytest.mark.skipif
2104484Sbinkertn@umich.edu    pytest.suppress = suppress
2114484Sbinkertn@umich.edu    pytest.requires_numpy = skipif(not np, reason="numpy is not installed")
2124484Sbinkertn@umich.edu    pytest.requires_scipy = skipif(not np, reason="scipy is not installed")
2134484Sbinkertn@umich.edu    pytest.requires_eigen_and_numpy = skipif(not have_eigen or not np,
2144484Sbinkertn@umich.edu                                             reason="eigen and/or numpy are not installed")
2154484Sbinkertn@umich.edu    pytest.requires_eigen_and_scipy = skipif(
2164484Sbinkertn@umich.edu        not have_eigen or not scipy, reason="eigen and/or scipy are not installed")
2174484Sbinkertn@umich.edu    pytest.unsupported_on_pypy = skipif(pypy, reason="unsupported on PyPy")
2184484Sbinkertn@umich.edu    pytest.unsupported_on_py2 = skipif(sys.version_info.major < 3,
2194484Sbinkertn@umich.edu                                       reason="unsupported on Python 2.x")
2204484Sbinkertn@umich.edu    pytest.gc_collect = gc_collect
2214484Sbinkertn@umich.edu
2224484Sbinkertn@umich.edu
2234484Sbinkertn@umich.edudef _test_import_pybind11():
2244484Sbinkertn@umich.edu    """Early diagnostic for test module initialization errors
2254484Sbinkertn@umich.edu
2264484Sbinkertn@umich.edu    When there is an error during initialization, the first import will report the
2274484Sbinkertn@umich.edu    real error while all subsequent imports will report nonsense. This import test
228    is done early (in the pytest configuration file, before any tests) in order to
229    avoid the noise of having all tests fail with identical error messages.
230
231    Any possible exception is caught here and reported manually *without* the stack
232    trace. This further reduces noise since the trace would only show pytest internals
233    which are not useful for debugging pybind11 module issues.
234    """
235    # noinspection PyBroadException
236    try:
237        import pybind11_tests  # noqa: F401 imported but unused
238    except Exception as e:
239        print("Failed to import pybind11_tests from pytest:")
240        print("  {}: {}".format(type(e).__name__, e))
241        sys.exit(1)
242
243
244_test_import_pybind11()
245