You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							227 lines
						
					
					
						
							6.0 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							227 lines
						
					
					
						
							6.0 KiB
						
					
					
				
								"""pytest configuration
							 | 
						|
								
							 | 
						|
								Extends output capture as needed by pybind11: ignore constructors, optional unordered lines.
							 | 
						|
								Adds docstring and exceptions message sanitizers: ignore Python 2 vs 3 differences.
							 | 
						|
								"""
							 | 
						|
								
							 | 
						|
								import pytest
							 | 
						|
								import textwrap
							 | 
						|
								import difflib
							 | 
						|
								import re
							 | 
						|
								import sys
							 | 
						|
								import contextlib
							 | 
						|
								
							 | 
						|
								_unicode_marker = re.compile(r'u(\'[^\']*\')')
							 | 
						|
								_long_marker = re.compile(r'([0-9])L')
							 | 
						|
								_hexadecimal = re.compile(r'0x[0-9a-fA-F]+')
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								def _strip_and_dedent(s):
							 | 
						|
								    """For triple-quote strings"""
							 | 
						|
								    return textwrap.dedent(s.lstrip('\n').rstrip())
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								def _split_and_sort(s):
							 | 
						|
								    """For output which does not require specific line order"""
							 | 
						|
								    return sorted(_strip_and_dedent(s).splitlines())
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								def _make_explanation(a, b):
							 | 
						|
								    """Explanation for a failed assert -- the a and b arguments are List[str]"""
							 | 
						|
								    return ["--- actual / +++ expected"] + [line.strip('\n') for line in difflib.ndiff(a, b)]
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								class Output(object):
							 | 
						|
								    """Basic output post-processing and comparison"""
							 | 
						|
								    def __init__(self, string):
							 | 
						|
								        self.string = string
							 | 
						|
								        self.explanation = []
							 | 
						|
								
							 | 
						|
								    def __str__(self):
							 | 
						|
								        return self.string
							 | 
						|
								
							 | 
						|
								    def __eq__(self, other):
							 | 
						|
								        # Ignore constructor/destructor output which is prefixed with "###"
							 | 
						|
								        a = [line for line in self.string.strip().splitlines() if not line.startswith("###")]
							 | 
						|
								        b = _strip_and_dedent(other).splitlines()
							 | 
						|
								        if a == b:
							 | 
						|
								            return True
							 | 
						|
								        else:
							 | 
						|
								            self.explanation = _make_explanation(a, b)
							 | 
						|
								            return False
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								class Unordered(Output):
							 | 
						|
								    """Custom comparison for output without strict line ordering"""
							 | 
						|
								    def __eq__(self, other):
							 | 
						|
								        a = _split_and_sort(self.string)
							 | 
						|
								        b = _split_and_sort(other)
							 | 
						|
								        if a == b:
							 | 
						|
								            return True
							 | 
						|
								        else:
							 | 
						|
								            self.explanation = _make_explanation(a, b)
							 | 
						|
								            return False
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								class Capture(object):
							 | 
						|
								    def __init__(self, capfd):
							 | 
						|
								        self.capfd = capfd
							 | 
						|
								        self.out = ""
							 | 
						|
								        self.err = ""
							 | 
						|
								
							 | 
						|
								    def __enter__(self):
							 | 
						|
								        self.capfd.readouterr()
							 | 
						|
								        return self
							 | 
						|
								
							 | 
						|
								    def __exit__(self, *_):
							 | 
						|
								        self.out, self.err = self.capfd.readouterr()
							 | 
						|
								
							 | 
						|
								    def __eq__(self, other):
							 | 
						|
								        a = Output(self.out)
							 | 
						|
								        b = other
							 | 
						|
								        if a == b:
							 | 
						|
								            return True
							 | 
						|
								        else:
							 | 
						|
								            self.explanation = a.explanation
							 | 
						|
								            return False
							 | 
						|
								
							 | 
						|
								    def __str__(self):
							 | 
						|
								        return self.out
							 | 
						|
								
							 | 
						|
								    def __contains__(self, item):
							 | 
						|
								        return item in self.out
							 | 
						|
								
							 | 
						|
								    @property
							 | 
						|
								    def unordered(self):
							 | 
						|
								        return Unordered(self.out)
							 | 
						|
								
							 | 
						|
								    @property
							 | 
						|
								    def stderr(self):
							 | 
						|
								        return Output(self.err)
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								@pytest.fixture
							 | 
						|
								def capture(capfd):
							 | 
						|
								    """Extended `capfd` with context manager and custom equality operators"""
							 | 
						|
								    return Capture(capfd)
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								class SanitizedString(object):
							 | 
						|
								    def __init__(self, sanitizer):
							 | 
						|
								        self.sanitizer = sanitizer
							 | 
						|
								        self.string = ""
							 | 
						|
								        self.explanation = []
							 | 
						|
								
							 | 
						|
								    def __call__(self, thing):
							 | 
						|
								        self.string = self.sanitizer(thing)
							 | 
						|
								        return self
							 | 
						|
								
							 | 
						|
								    def __eq__(self, other):
							 | 
						|
								        a = self.string
							 | 
						|
								        b = _strip_and_dedent(other)
							 | 
						|
								        if a == b:
							 | 
						|
								            return True
							 | 
						|
								        else:
							 | 
						|
								            self.explanation = _make_explanation(a.splitlines(), b.splitlines())
							 | 
						|
								            return False
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								def _sanitize_general(s):
							 | 
						|
								    s = s.strip()
							 | 
						|
								    s = s.replace("pybind11_tests.", "m.")
							 | 
						|
								    s = s.replace("unicode", "str")
							 | 
						|
								    s = _long_marker.sub(r"\1", s)
							 | 
						|
								    s = _unicode_marker.sub(r"\1", s)
							 | 
						|
								    return s
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								def _sanitize_docstring(thing):
							 | 
						|
								    s = thing.__doc__
							 | 
						|
								    s = _sanitize_general(s)
							 | 
						|
								    return s
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								@pytest.fixture
							 | 
						|
								def doc():
							 | 
						|
								    """Sanitize docstrings and add custom failure explanation"""
							 | 
						|
								    return SanitizedString(_sanitize_docstring)
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								def _sanitize_message(thing):
							 | 
						|
								    s = str(thing)
							 | 
						|
								    s = _sanitize_general(s)
							 | 
						|
								    s = _hexadecimal.sub("0", s)
							 | 
						|
								    return s
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								@pytest.fixture
							 | 
						|
								def msg():
							 | 
						|
								    """Sanitize messages and add custom failure explanation"""
							 | 
						|
								    return SanitizedString(_sanitize_message)
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								# noinspection PyUnusedLocal
							 | 
						|
								def pytest_assertrepr_compare(op, left, right):
							 | 
						|
								    """Hook to insert custom failure explanation"""
							 | 
						|
								    if hasattr(left, 'explanation'):
							 | 
						|
								        return left.explanation
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								@contextlib.contextmanager
							 | 
						|
								def suppress(exception):
							 | 
						|
								    """Suppress the desired exception"""
							 | 
						|
								    try:
							 | 
						|
								        yield
							 | 
						|
								    except exception:
							 | 
						|
								        pass
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								def pytest_namespace():
							 | 
						|
								    """Add import suppression and test requirements to `pytest` namespace"""
							 | 
						|
								    try:
							 | 
						|
								        import numpy as np
							 | 
						|
								    except ImportError:
							 | 
						|
								        np = None
							 | 
						|
								    try:
							 | 
						|
								        import scipy
							 | 
						|
								    except ImportError:
							 | 
						|
								        scipy = None
							 | 
						|
								    try:
							 | 
						|
								        from pybind11_tests import have_eigen
							 | 
						|
								    except ImportError:
							 | 
						|
								        have_eigen = False
							 | 
						|
								
							 | 
						|
								    skipif = pytest.mark.skipif
							 | 
						|
								    return {
							 | 
						|
								        'suppress': suppress,
							 | 
						|
								        'requires_numpy': skipif(not np, reason="numpy is not installed"),
							 | 
						|
								        'requires_scipy': skipif(not np, reason="scipy is not installed"),
							 | 
						|
								        'requires_eigen_and_numpy': skipif(not have_eigen or not np,
							 | 
						|
								                                           reason="eigen and/or numpy are not installed"),
							 | 
						|
								        'requires_eigen_and_scipy': skipif(not have_eigen or not scipy,
							 | 
						|
								                                           reason="eigen and/or scipy are not installed"),
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								def _test_import_pybind11():
							 | 
						|
								    """Early diagnostic for test module initialization errors
							 | 
						|
								
							 | 
						|
								    When there is an error during initialization, the first import will report the
							 | 
						|
								    real error while all subsequent imports will report nonsense. This import test
							 | 
						|
								    is done early (in the pytest configuration file, before any tests) in order to
							 | 
						|
								    avoid the noise of having all tests fail with identical error messages.
							 | 
						|
								
							 | 
						|
								    Any possible exception is caught here and reported manually *without* the stack
							 | 
						|
								    trace. This further reduces noise since the trace would only show pytest internals
							 | 
						|
								    which are not useful for debugging pybind11 module issues.
							 | 
						|
								    """
							 | 
						|
								    # noinspection PyBroadException
							 | 
						|
								    try:
							 | 
						|
								        import pybind11_tests  # noqa: F401 imported but unused
							 | 
						|
								    except Exception as e:
							 | 
						|
								        print("Failed to import pybind11_tests from pytest:")
							 | 
						|
								        print("  {}: {}".format(type(e).__name__, e))
							 | 
						|
								        sys.exit(1)
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								_test_import_pybind11()
							 |