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