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