conftest.py revision 12391
113559Snikos.nikoleris@arm.com"""pytest configuration 212109SRekai.GonzalezAlberquilla@arm.com 312109SRekai.GonzalezAlberquilla@arm.comExtends output capture as needed by pybind11: ignore constructors, optional unordered lines. 412109SRekai.GonzalezAlberquilla@arm.comAdds docstring and exceptions message sanitizers: ignore Python 2 vs 3 differences. 512109SRekai.GonzalezAlberquilla@arm.com""" 612109SRekai.GonzalezAlberquilla@arm.com 712109SRekai.GonzalezAlberquilla@arm.comimport pytest 812109SRekai.GonzalezAlberquilla@arm.comimport textwrap 912109SRekai.GonzalezAlberquilla@arm.comimport difflib 1012109SRekai.GonzalezAlberquilla@arm.comimport re 1112109SRekai.GonzalezAlberquilla@arm.comimport sys 1212109SRekai.GonzalezAlberquilla@arm.comimport contextlib 134486Sbinkertn@umich.eduimport platform 144486Sbinkertn@umich.eduimport gc 154486Sbinkertn@umich.edu 164486Sbinkertn@umich.edu_unicode_marker = re.compile(r'u(\'[^\']*\')') 174486Sbinkertn@umich.edu_long_marker = re.compile(r'([0-9])L') 184486Sbinkertn@umich.edu_hexadecimal = re.compile(r'0x[0-9a-fA-F]+') 194486Sbinkertn@umich.edu 204486Sbinkertn@umich.edu 214486Sbinkertn@umich.edudef _strip_and_dedent(s): 224486Sbinkertn@umich.edu """For triple-quote strings""" 234486Sbinkertn@umich.edu return textwrap.dedent(s.lstrip('\n').rstrip()) 244486Sbinkertn@umich.edu 254486Sbinkertn@umich.edu 264486Sbinkertn@umich.edudef _split_and_sort(s): 274486Sbinkertn@umich.edu """For output which does not require specific line order""" 284486Sbinkertn@umich.edu return sorted(_strip_and_dedent(s).splitlines()) 294486Sbinkertn@umich.edu 304486Sbinkertn@umich.edu 314486Sbinkertn@umich.edudef _make_explanation(a, b): 324486Sbinkertn@umich.edu """Explanation for a failed assert -- the a and b arguments are List[str]""" 334486Sbinkertn@umich.edu return ["--- actual / +++ expected"] + [line.strip('\n') for line in difflib.ndiff(a, b)] 344486Sbinkertn@umich.edu 354486Sbinkertn@umich.edu 364486Sbinkertn@umich.educlass Output(object): 374486Sbinkertn@umich.edu """Basic output post-processing and comparison""" 384486Sbinkertn@umich.edu def __init__(self, string): 394486Sbinkertn@umich.edu self.string = string 404486Sbinkertn@umich.edu self.explanation = [] 4112563Sgabeblack@google.com 4212563Sgabeblack@google.com def __str__(self): 436654Snate@binkert.org return self.string 443102SN/A 453102SN/A def __eq__(self, other): 4613665Sandreas.sandberg@arm.com # Ignore constructor/destructor output which is prefixed with "###" 4713665Sandreas.sandberg@arm.com a = [line for line in self.string.strip().splitlines() if not line.startswith("###")] 4813665Sandreas.sandberg@arm.com b = _strip_and_dedent(other).splitlines() 4913665Sandreas.sandberg@arm.com if a == b: 5013665Sandreas.sandberg@arm.com return True 514486Sbinkertn@umich.edu else: 5213559Snikos.nikoleris@arm.com self.explanation = _make_explanation(a, b) 5313559Snikos.nikoleris@arm.com return False 5413559Snikos.nikoleris@arm.com 5513560Snikos.nikoleris@arm.com 5613560Snikos.nikoleris@arm.comclass Unordered(Output): 5713560Snikos.nikoleris@arm.com """Custom comparison for output without strict line ordering""" 5813563Snikos.nikoleris@arm.com def __eq__(self, other): 5913563Snikos.nikoleris@arm.com a = _split_and_sort(self.string) 6013563Snikos.nikoleris@arm.com b = _split_and_sort(other) 612817SN/A if a == b: 622817SN/A return True 639341SAndreas.Sandberg@arm.com else: 649341SAndreas.Sandberg@arm.com self.explanation = _make_explanation(a, b) 659518SAndreas.Sandberg@ARM.com return False 669518SAndreas.Sandberg@ARM.com 679518SAndreas.Sandberg@ARM.com 689518SAndreas.Sandberg@ARM.comclass Capture(object): 699518SAndreas.Sandberg@ARM.com def __init__(self, capfd): 709518SAndreas.Sandberg@ARM.com self.capfd = capfd 719518SAndreas.Sandberg@ARM.com self.out = "" 729518SAndreas.Sandberg@ARM.com self.err = "" 739518SAndreas.Sandberg@ARM.com 749518SAndreas.Sandberg@ARM.com def __enter__(self): 759518SAndreas.Sandberg@ARM.com self.capfd.readouterr() 769518SAndreas.Sandberg@ARM.com return self 772932SN/A 781681SN/A def __exit__(self, *_): 7911780Sarthur.perais@inria.fr self.out, self.err = self.capfd.readouterr() 8013710Sgabor.dozsa@arm.com 8113710Sgabor.dozsa@arm.com def __eq__(self, other): 8213710Sgabor.dozsa@arm.com a = Output(self.out) 831681SN/A b = other 849184Sandreas.hansson@arm.com if a == b: 859184Sandreas.hansson@arm.com return True 869184Sandreas.hansson@arm.com else: 879184Sandreas.hansson@arm.com self.explanation = a.explanation 889184Sandreas.hansson@arm.com return False 892932SN/A 909982Satgutier@umich.edu def __str__(self): 9110331Smitch.hayenga@arm.com return self.out 9210331Smitch.hayenga@arm.com 932932SN/A def __contains__(self, item): 949184Sandreas.hansson@arm.com return item in self.out 959184Sandreas.hansson@arm.com 969184Sandreas.hansson@arm.com @property 979184Sandreas.hansson@arm.com def unordered(self): 989184Sandreas.hansson@arm.com return Unordered(self.out) 992932SN/A 1001681SN/A @property 1019184Sandreas.hansson@arm.com def stderr(self): 1029184Sandreas.hansson@arm.com return Output(self.err) 1039184Sandreas.hansson@arm.com 1049184Sandreas.hansson@arm.com 1052932SN/A@pytest.fixture 1061681SN/Adef capture(capsys): 1079184Sandreas.hansson@arm.com """Extended `capsys` with context manager and custom equality operators""" 1082932SN/A return Capture(capsys) 1099184Sandreas.hansson@arm.com 1102932SN/A 1119184Sandreas.hansson@arm.comclass SanitizedString(object): 1122932SN/A def __init__(self, sanitizer): 1132932SN/A self.sanitizer = sanitizer 1142932SN/A self.string = "" 1152932SN/A self.explanation = [] 1163223SN/A 1172932SN/A def __call__(self, thing): 1189184Sandreas.hansson@arm.com self.string = self.sanitizer(thing) 1191681SN/A return self 1209184Sandreas.hansson@arm.com 1212932SN/A def __eq__(self, other): 1222932SN/A a = self.string 1239184Sandreas.hansson@arm.com b = _strip_and_dedent(other) 1249184Sandreas.hansson@arm.com if a == b: 1251681SN/A return True 1262932SN/A else: 1272932SN/A self.explanation = _make_explanation(a.splitlines(), b.splitlines()) 1281681SN/A return False 1292932SN/A 1302932SN/A 1318199SAli.Saidi@ARM.comdef _sanitize_general(s): 1328199SAli.Saidi@ARM.com s = s.strip() 1338199SAli.Saidi@ARM.com s = s.replace("pybind11_tests.", "m.") 1348519SAli.Saidi@ARM.com s = s.replace("unicode", "str") 1358519SAli.Saidi@ARM.com s = _long_marker.sub(r"\1", s) 1362932SN/A s = _unicode_marker.sub(r"\1", s) 1372932SN/A return s 1381681SN/A 1392932SN/A 1401681SN/Adef _sanitize_docstring(thing): 1412932SN/A s = thing.__doc__ 1422932SN/A s = _sanitize_general(s) 1432932SN/A return s 1449921Syasuko.eckert@amd.com 1459921Syasuko.eckert@amd.com 14610338SCurtis.Dunham@arm.com@pytest.fixture 1479921Syasuko.eckert@amd.comdef doc(): 1489921Syasuko.eckert@amd.com """Sanitize docstrings and add custom failure explanation""" 1499921Syasuko.eckert@amd.com return SanitizedString(_sanitize_docstring) 1509921Syasuko.eckert@amd.com 1519921Syasuko.eckert@amd.com 1529921Syasuko.eckert@amd.comdef _sanitize_message(thing): 1539921Syasuko.eckert@amd.com s = str(thing) 15412109SRekai.GonzalezAlberquilla@arm.com s = _sanitize_general(s) 15512109SRekai.GonzalezAlberquilla@arm.com s = _hexadecimal.sub("0", s) 15613610Sgiacomo.gabrielli@arm.com return s 15713610Sgiacomo.gabrielli@arm.com 1589921Syasuko.eckert@amd.com 1599921Syasuko.eckert@amd.com@pytest.fixture 1602932SN/Adef msg(): 1612932SN/A """Sanitize messages and add custom failure explanation""" 1621681SN/A return SanitizedString(_sanitize_message) 1634597Sbinkertn@umich.edu 16413559Snikos.nikoleris@arm.com 16513560Snikos.nikoleris@arm.com# noinspection PyUnusedLocal 16613560Snikos.nikoleris@arm.comdef pytest_assertrepr_compare(op, left, right): 1674597Sbinkertn@umich.edu """Hook to insert custom failure explanation""" 16813561Snikos.nikoleris@arm.com if hasattr(left, 'explanation'): 16913561Snikos.nikoleris@arm.com return left.explanation 1704597Sbinkertn@umich.edu 17113562Snikos.nikoleris@arm.com 17213562Snikos.nikoleris@arm.com@contextlib.contextmanager 1734597Sbinkertn@umich.edudef suppress(exception): 17413563Snikos.nikoleris@arm.com """Suppress the desired exception""" 1754303SN/A try: 17610785Sgope@wisc.edu yield 1779849Sandreas.hansson@arm.com except exception: 1789849Sandreas.hansson@arm.com pass 1798727Snilay@cs.wisc.edu 1808727Snilay@cs.wisc.edu 1818887Sgeoffrey.blake@arm.comdef gc_collect(): 1828887Sgeoffrey.blake@arm.com ''' Run the garbage collector twice (needed when running 1838887Sgeoffrey.blake@arm.com reference counting tests with PyPy) ''' 18413665Sandreas.sandberg@arm.com gc.collect() 1858887Sgeoffrey.blake@arm.com gc.collect() 1868887Sgeoffrey.blake@arm.com 1878887Sgeoffrey.blake@arm.com 1888887Sgeoffrey.blake@arm.comdef pytest_namespace(): 1898887Sgeoffrey.blake@arm.com """Add import suppression and test requirements to `pytest` namespace""" 1908887Sgeoffrey.blake@arm.com try: 1918887Sgeoffrey.blake@arm.com import numpy as np 1929132Satgutier@umich.edu except ImportError: 1938887Sgeoffrey.blake@arm.com np = None 1948887Sgeoffrey.blake@arm.com try: 19512563Sgabeblack@google.com import scipy 1968887Sgeoffrey.blake@arm.com except ImportError: 197 scipy = None 198 try: 199 from pybind11_tests.eigen import have_eigen 200 except ImportError: 201 have_eigen = False 202 pypy = platform.python_implementation() == "PyPy" 203 204 skipif = pytest.mark.skipif 205 return { 206 'suppress': suppress, 207 'requires_numpy': skipif(not np, reason="numpy is not installed"), 208 'requires_scipy': skipif(not np, reason="scipy is not installed"), 209 'requires_eigen_and_numpy': skipif(not have_eigen or not np, 210 reason="eigen and/or numpy are not installed"), 211 'requires_eigen_and_scipy': skipif(not have_eigen or not scipy, 212 reason="eigen and/or scipy are not installed"), 213 'unsupported_on_pypy': skipif(pypy, reason="unsupported on PyPy"), 214 'unsupported_on_py2': skipif(sys.version_info.major < 3, 215 reason="unsupported on Python 2.x"), 216 'gc_collect': gc_collect 217 } 218 219 220def _test_import_pybind11(): 221 """Early diagnostic for test module initialization errors 222 223 When there is an error during initialization, the first import will report the 224 real error while all subsequent imports will report nonsense. This import test 225 is done early (in the pytest configuration file, before any tests) in order to 226 avoid the noise of having all tests fail with identical error messages. 227 228 Any possible exception is caught here and reported manually *without* the stack 229 trace. This further reduces noise since the trace would only show pytest internals 230 which are not useful for debugging pybind11 module issues. 231 """ 232 # noinspection PyBroadException 233 try: 234 import pybind11_tests # noqa: F401 imported but unused 235 except Exception as e: 236 print("Failed to import pybind11_tests from pytest:") 237 print(" {}: {}".format(type(e).__name__, e)) 238 sys.exit(1) 239 240 241_test_import_pybind11() 242