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