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