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