conftest.py revision 12391:ceeca8b41e4b
14561Sgblack@eecs.umich.edu"""pytest configuration 24561Sgblack@eecs.umich.edu 34561Sgblack@eecs.umich.eduExtends output capture as needed by pybind11: ignore constructors, optional unordered lines. 44561Sgblack@eecs.umich.eduAdds docstring and exceptions message sanitizers: ignore Python 2 vs 3 differences. 54561Sgblack@eecs.umich.edu""" 64561Sgblack@eecs.umich.edu 74561Sgblack@eecs.umich.eduimport pytest 84561Sgblack@eecs.umich.eduimport textwrap 94561Sgblack@eecs.umich.eduimport difflib 104561Sgblack@eecs.umich.eduimport re 114561Sgblack@eecs.umich.eduimport sys 124561Sgblack@eecs.umich.eduimport contextlib 134561Sgblack@eecs.umich.eduimport platform 144561Sgblack@eecs.umich.eduimport gc 154561Sgblack@eecs.umich.edu 164561Sgblack@eecs.umich.edu_unicode_marker = re.compile(r'u(\'[^\']*\')') 174561Sgblack@eecs.umich.edu_long_marker = re.compile(r'([0-9])L') 184561Sgblack@eecs.umich.edu_hexadecimal = re.compile(r'0x[0-9a-fA-F]+') 194561Sgblack@eecs.umich.edu 204561Sgblack@eecs.umich.edu 214561Sgblack@eecs.umich.edudef _strip_and_dedent(s): 224561Sgblack@eecs.umich.edu """For triple-quote strings""" 234561Sgblack@eecs.umich.edu return textwrap.dedent(s.lstrip('\n').rstrip()) 244561Sgblack@eecs.umich.edu 254561Sgblack@eecs.umich.edu 264561Sgblack@eecs.umich.edudef _split_and_sort(s): 274561Sgblack@eecs.umich.edu """For output which does not require specific line order""" 284561Sgblack@eecs.umich.edu return sorted(_strip_and_dedent(s).splitlines()) 294561Sgblack@eecs.umich.edu 304561Sgblack@eecs.umich.edu 314561Sgblack@eecs.umich.edudef _make_explanation(a, b): 324561Sgblack@eecs.umich.edu """Explanation for a failed assert -- the a and b arguments are List[str]""" 334561Sgblack@eecs.umich.edu return ["--- actual / +++ expected"] + [line.strip('\n') for line in difflib.ndiff(a, b)] 344561Sgblack@eecs.umich.edu 354561Sgblack@eecs.umich.edu 364561Sgblack@eecs.umich.educlass Output(object): 374561Sgblack@eecs.umich.edu """Basic output post-processing and comparison""" 384561Sgblack@eecs.umich.edu def __init__(self, string): 394561Sgblack@eecs.umich.edu self.string = string 404561Sgblack@eecs.umich.edu self.explanation = [] 414561Sgblack@eecs.umich.edu 424561Sgblack@eecs.umich.edu def __str__(self): 434561Sgblack@eecs.umich.edu return self.string 444561Sgblack@eecs.umich.edu 454561Sgblack@eecs.umich.edu def __eq__(self, other): 464561Sgblack@eecs.umich.edu # Ignore constructor/destructor output which is prefixed with "###" 474561Sgblack@eecs.umich.edu a = [line for line in self.string.strip().splitlines() if not line.startswith("###")] 484561Sgblack@eecs.umich.edu b = _strip_and_dedent(other).splitlines() 494561Sgblack@eecs.umich.edu if a == b: 504561Sgblack@eecs.umich.edu return True 514561Sgblack@eecs.umich.edu else: 524561Sgblack@eecs.umich.edu self.explanation = _make_explanation(a, b) 534561Sgblack@eecs.umich.edu return False 544561Sgblack@eecs.umich.edu 554561Sgblack@eecs.umich.edu 564561Sgblack@eecs.umich.educlass Unordered(Output): 574561Sgblack@eecs.umich.edu """Custom comparison for output without strict line ordering""" 584561Sgblack@eecs.umich.edu def __eq__(self, other): 594561Sgblack@eecs.umich.edu a = _split_and_sort(self.string) 604561Sgblack@eecs.umich.edu b = _split_and_sort(other) 614561Sgblack@eecs.umich.edu if a == b: 624587Sgblack@eecs.umich.edu return True 634587Sgblack@eecs.umich.edu else: 644587Sgblack@eecs.umich.edu self.explanation = _make_explanation(a, b) 654587Sgblack@eecs.umich.edu return False 664587Sgblack@eecs.umich.edu 674587Sgblack@eecs.umich.edu 684587Sgblack@eecs.umich.educlass Capture(object): 694587Sgblack@eecs.umich.edu def __init__(self, capfd): 704561Sgblack@eecs.umich.edu self.capfd = capfd 714561Sgblack@eecs.umich.edu self.out = "" 724561Sgblack@eecs.umich.edu self.err = "" 734561Sgblack@eecs.umich.edu 744561Sgblack@eecs.umich.edu def __enter__(self): 754561Sgblack@eecs.umich.edu self.capfd.readouterr() 764561Sgblack@eecs.umich.edu return self 774561Sgblack@eecs.umich.edu 784561Sgblack@eecs.umich.edu def __exit__(self, *_): 794561Sgblack@eecs.umich.edu self.out, self.err = self.capfd.readouterr() 804587Sgblack@eecs.umich.edu 814587Sgblack@eecs.umich.edu def __eq__(self, other): 824587Sgblack@eecs.umich.edu a = Output(self.out) 834587Sgblack@eecs.umich.edu b = other 844587Sgblack@eecs.umich.edu if a == b: 854587Sgblack@eecs.umich.edu return True 864587Sgblack@eecs.umich.edu else: 874587Sgblack@eecs.umich.edu self.explanation = a.explanation 884587Sgblack@eecs.umich.edu return False 894587Sgblack@eecs.umich.edu 904587Sgblack@eecs.umich.edu def __str__(self): 914587Sgblack@eecs.umich.edu return self.out 924587Sgblack@eecs.umich.edu 934587Sgblack@eecs.umich.edu def __contains__(self, item): 944587Sgblack@eecs.umich.edu return item in self.out 954587Sgblack@eecs.umich.edu 964587Sgblack@eecs.umich.edu @property 974587Sgblack@eecs.umich.edu def unordered(self): 984587Sgblack@eecs.umich.edu return Unordered(self.out) 994587Sgblack@eecs.umich.edu 1004587Sgblack@eecs.umich.edu @property 1014587Sgblack@eecs.umich.edu def stderr(self): 1024587Sgblack@eecs.umich.edu return Output(self.err) 1034587Sgblack@eecs.umich.edu 1044587Sgblack@eecs.umich.edu 1054587Sgblack@eecs.umich.edu@pytest.fixture 1064587Sgblack@eecs.umich.edudef capture(capsys): 1074587Sgblack@eecs.umich.edu """Extended `capsys` with context manager and custom equality operators""" 1084587Sgblack@eecs.umich.edu return Capture(capsys) 1094587Sgblack@eecs.umich.edu 1104587Sgblack@eecs.umich.edu 1114587Sgblack@eecs.umich.educlass SanitizedString(object): 1124587Sgblack@eecs.umich.edu def __init__(self, sanitizer): 1134587Sgblack@eecs.umich.edu self.sanitizer = sanitizer 1144587Sgblack@eecs.umich.edu self.string = "" 1154587Sgblack@eecs.umich.edu self.explanation = [] 1164587Sgblack@eecs.umich.edu 1174587Sgblack@eecs.umich.edu def __call__(self, thing): 1184587Sgblack@eecs.umich.edu self.string = self.sanitizer(thing) 1194587Sgblack@eecs.umich.edu return self 1204587Sgblack@eecs.umich.edu 1214587Sgblack@eecs.umich.edu def __eq__(self, other): 1224587Sgblack@eecs.umich.edu a = self.string 1234587Sgblack@eecs.umich.edu b = _strip_and_dedent(other) 1244587Sgblack@eecs.umich.edu if a == b: 1254587Sgblack@eecs.umich.edu return True 1264587Sgblack@eecs.umich.edu else: 1274587Sgblack@eecs.umich.edu self.explanation = _make_explanation(a.splitlines(), b.splitlines()) 1284587Sgblack@eecs.umich.edu return False 1294587Sgblack@eecs.umich.edu 1304587Sgblack@eecs.umich.edu 1314587Sgblack@eecs.umich.edudef _sanitize_general(s): 1324587Sgblack@eecs.umich.edu s = s.strip() 1334587Sgblack@eecs.umich.edu s = s.replace("pybind11_tests.", "m.") 1344587Sgblack@eecs.umich.edu s = s.replace("unicode", "str") 1354587Sgblack@eecs.umich.edu s = _long_marker.sub(r"\1", s) 1364587Sgblack@eecs.umich.edu s = _unicode_marker.sub(r"\1", s) 1374587Sgblack@eecs.umich.edu return s 1384587Sgblack@eecs.umich.edu 1394587Sgblack@eecs.umich.edu 1404587Sgblack@eecs.umich.edudef _sanitize_docstring(thing): 1414587Sgblack@eecs.umich.edu s = thing.__doc__ 1424587Sgblack@eecs.umich.edu s = _sanitize_general(s) 1434587Sgblack@eecs.umich.edu return s 1444587Sgblack@eecs.umich.edu 1454587Sgblack@eecs.umich.edu 1464587Sgblack@eecs.umich.edu@pytest.fixture 1474587Sgblack@eecs.umich.edudef doc(): 1484587Sgblack@eecs.umich.edu """Sanitize docstrings and add custom failure explanation""" 1494587Sgblack@eecs.umich.edu return SanitizedString(_sanitize_docstring) 1504587Sgblack@eecs.umich.edu 1514587Sgblack@eecs.umich.edu 1524587Sgblack@eecs.umich.edudef _sanitize_message(thing): 1534587Sgblack@eecs.umich.edu s = str(thing) 1544587Sgblack@eecs.umich.edu s = _sanitize_general(s) 1554587Sgblack@eecs.umich.edu s = _hexadecimal.sub("0", s) 1564587Sgblack@eecs.umich.edu return s 1574587Sgblack@eecs.umich.edu 1584587Sgblack@eecs.umich.edu 1594587Sgblack@eecs.umich.edu@pytest.fixture 1604587Sgblack@eecs.umich.edudef msg(): 1614587Sgblack@eecs.umich.edu """Sanitize messages and add custom failure explanation""" 1624587Sgblack@eecs.umich.edu return SanitizedString(_sanitize_message) 1634587Sgblack@eecs.umich.edu 1644587Sgblack@eecs.umich.edu 1654587Sgblack@eecs.umich.edu# noinspection PyUnusedLocal 1664587Sgblack@eecs.umich.edudef pytest_assertrepr_compare(op, left, right): 1674587Sgblack@eecs.umich.edu """Hook to insert custom failure explanation""" 1684587Sgblack@eecs.umich.edu if hasattr(left, 'explanation'): 1694587Sgblack@eecs.umich.edu return left.explanation 1704587Sgblack@eecs.umich.edu 1714587Sgblack@eecs.umich.edu 1724587Sgblack@eecs.umich.edu@contextlib.contextmanager 1734587Sgblack@eecs.umich.edudef suppress(exception): 1744587Sgblack@eecs.umich.edu """Suppress the desired exception""" 1754587Sgblack@eecs.umich.edu try: 1764587Sgblack@eecs.umich.edu yield 1774587Sgblack@eecs.umich.edu except exception: 1784587Sgblack@eecs.umich.edu pass 1794587Sgblack@eecs.umich.edu 1804587Sgblack@eecs.umich.edu 1814587Sgblack@eecs.umich.edudef gc_collect(): 1824587Sgblack@eecs.umich.edu ''' Run the garbage collector twice (needed when running 1834587Sgblack@eecs.umich.edu reference counting tests with PyPy) ''' 1844587Sgblack@eecs.umich.edu gc.collect() 1854587Sgblack@eecs.umich.edu gc.collect() 1864587Sgblack@eecs.umich.edu 1874587Sgblack@eecs.umich.edu 1884587Sgblack@eecs.umich.edudef pytest_namespace(): 1894587Sgblack@eecs.umich.edu """Add import suppression and test requirements to `pytest` namespace""" 1904587Sgblack@eecs.umich.edu try: 1914587Sgblack@eecs.umich.edu import numpy as np 1924587Sgblack@eecs.umich.edu except ImportError: 1934587Sgblack@eecs.umich.edu np = None 1944587Sgblack@eecs.umich.edu try: 1954587Sgblack@eecs.umich.edu import scipy 1964587Sgblack@eecs.umich.edu except ImportError: 1974587Sgblack@eecs.umich.edu scipy = None 1984587Sgblack@eecs.umich.edu try: 1994587Sgblack@eecs.umich.edu from pybind11_tests.eigen import have_eigen 2004587Sgblack@eecs.umich.edu except ImportError: 2014587Sgblack@eecs.umich.edu have_eigen = False 2024587Sgblack@eecs.umich.edu pypy = platform.python_implementation() == "PyPy" 2034587Sgblack@eecs.umich.edu 2044587Sgblack@eecs.umich.edu skipif = pytest.mark.skipif 2054587Sgblack@eecs.umich.edu return { 2064587Sgblack@eecs.umich.edu 'suppress': suppress, 2074587Sgblack@eecs.umich.edu 'requires_numpy': skipif(not np, reason="numpy is not installed"), 2084587Sgblack@eecs.umich.edu 'requires_scipy': skipif(not np, reason="scipy is not installed"), 2094587Sgblack@eecs.umich.edu 'requires_eigen_and_numpy': skipif(not have_eigen or not np, 2104587Sgblack@eecs.umich.edu reason="eigen and/or numpy are not installed"), 2114587Sgblack@eecs.umich.edu 'requires_eigen_and_scipy': skipif(not have_eigen or not scipy, 2124587Sgblack@eecs.umich.edu reason="eigen and/or scipy are not installed"), 2134587Sgblack@eecs.umich.edu 'unsupported_on_pypy': skipif(pypy, reason="unsupported on PyPy"), 2144587Sgblack@eecs.umich.edu 'unsupported_on_py2': skipif(sys.version_info.major < 3, 2154587Sgblack@eecs.umich.edu reason="unsupported on Python 2.x"), 2164587Sgblack@eecs.umich.edu 'gc_collect': gc_collect 2174587Sgblack@eecs.umich.edu } 2184587Sgblack@eecs.umich.edu 2194587Sgblack@eecs.umich.edu 2204587Sgblack@eecs.umich.edudef _test_import_pybind11(): 2214587Sgblack@eecs.umich.edu """Early diagnostic for test module initialization errors 2224587Sgblack@eecs.umich.edu 2234587Sgblack@eecs.umich.edu When there is an error during initialization, the first import will report the 2244587Sgblack@eecs.umich.edu real error while all subsequent imports will report nonsense. This import test 2254587Sgblack@eecs.umich.edu is done early (in the pytest configuration file, before any tests) in order to 2264587Sgblack@eecs.umich.edu avoid the noise of having all tests fail with identical error messages. 2274587Sgblack@eecs.umich.edu 2284587Sgblack@eecs.umich.edu Any possible exception is caught here and reported manually *without* the stack 2294587Sgblack@eecs.umich.edu trace. This further reduces noise since the trace would only show pytest internals 2304587Sgblack@eecs.umich.edu which are not useful for debugging pybind11 module issues. 2314587Sgblack@eecs.umich.edu """ 2324587Sgblack@eecs.umich.edu # noinspection PyBroadException 2334587Sgblack@eecs.umich.edu try: 2344587Sgblack@eecs.umich.edu import pybind11_tests # noqa: F401 imported but unused 2354587Sgblack@eecs.umich.edu except Exception as e: 2364587Sgblack@eecs.umich.edu print("Failed to import pybind11_tests from pytest:") 2374587Sgblack@eecs.umich.edu print(" {}: {}".format(type(e).__name__, e)) 2384587Sgblack@eecs.umich.edu sys.exit(1) 2394587Sgblack@eecs.umich.edu 2404587Sgblack@eecs.umich.edu 2414587Sgblack@eecs.umich.edu_test_import_pybind11() 2424587Sgblack@eecs.umich.edu