conftest.py revision 12391
113559Snikos.nikoleris@arm.com"""pytest configuration
212109SRekai.GonzalezAlberquilla@arm.com
312109SRekai.GonzalezAlberquilla@arm.comExtends output capture as needed by pybind11: ignore constructors, optional unordered lines.
412109SRekai.GonzalezAlberquilla@arm.comAdds docstring and exceptions message sanitizers: ignore Python 2 vs 3 differences.
512109SRekai.GonzalezAlberquilla@arm.com"""
612109SRekai.GonzalezAlberquilla@arm.com
712109SRekai.GonzalezAlberquilla@arm.comimport pytest
812109SRekai.GonzalezAlberquilla@arm.comimport textwrap
912109SRekai.GonzalezAlberquilla@arm.comimport difflib
1012109SRekai.GonzalezAlberquilla@arm.comimport re
1112109SRekai.GonzalezAlberquilla@arm.comimport sys
1212109SRekai.GonzalezAlberquilla@arm.comimport contextlib
134486Sbinkertn@umich.eduimport platform
144486Sbinkertn@umich.eduimport gc
154486Sbinkertn@umich.edu
164486Sbinkertn@umich.edu_unicode_marker = re.compile(r'u(\'[^\']*\')')
174486Sbinkertn@umich.edu_long_marker = re.compile(r'([0-9])L')
184486Sbinkertn@umich.edu_hexadecimal = re.compile(r'0x[0-9a-fA-F]+')
194486Sbinkertn@umich.edu
204486Sbinkertn@umich.edu
214486Sbinkertn@umich.edudef _strip_and_dedent(s):
224486Sbinkertn@umich.edu    """For triple-quote strings"""
234486Sbinkertn@umich.edu    return textwrap.dedent(s.lstrip('\n').rstrip())
244486Sbinkertn@umich.edu
254486Sbinkertn@umich.edu
264486Sbinkertn@umich.edudef _split_and_sort(s):
274486Sbinkertn@umich.edu    """For output which does not require specific line order"""
284486Sbinkertn@umich.edu    return sorted(_strip_and_dedent(s).splitlines())
294486Sbinkertn@umich.edu
304486Sbinkertn@umich.edu
314486Sbinkertn@umich.edudef _make_explanation(a, b):
324486Sbinkertn@umich.edu    """Explanation for a failed assert -- the a and b arguments are List[str]"""
334486Sbinkertn@umich.edu    return ["--- actual / +++ expected"] + [line.strip('\n') for line in difflib.ndiff(a, b)]
344486Sbinkertn@umich.edu
354486Sbinkertn@umich.edu
364486Sbinkertn@umich.educlass Output(object):
374486Sbinkertn@umich.edu    """Basic output post-processing and comparison"""
384486Sbinkertn@umich.edu    def __init__(self, string):
394486Sbinkertn@umich.edu        self.string = string
404486Sbinkertn@umich.edu        self.explanation = []
4112563Sgabeblack@google.com
4212563Sgabeblack@google.com    def __str__(self):
436654Snate@binkert.org        return self.string
443102SN/A
453102SN/A    def __eq__(self, other):
4613665Sandreas.sandberg@arm.com        # Ignore constructor/destructor output which is prefixed with "###"
4713665Sandreas.sandberg@arm.com        a = [line for line in self.string.strip().splitlines() if not line.startswith("###")]
4813665Sandreas.sandberg@arm.com        b = _strip_and_dedent(other).splitlines()
4913665Sandreas.sandberg@arm.com        if a == b:
5013665Sandreas.sandberg@arm.com            return True
514486Sbinkertn@umich.edu        else:
5213559Snikos.nikoleris@arm.com            self.explanation = _make_explanation(a, b)
5313559Snikos.nikoleris@arm.com            return False
5413559Snikos.nikoleris@arm.com
5513560Snikos.nikoleris@arm.com
5613560Snikos.nikoleris@arm.comclass Unordered(Output):
5713560Snikos.nikoleris@arm.com    """Custom comparison for output without strict line ordering"""
5813563Snikos.nikoleris@arm.com    def __eq__(self, other):
5913563Snikos.nikoleris@arm.com        a = _split_and_sort(self.string)
6013563Snikos.nikoleris@arm.com        b = _split_and_sort(other)
612817SN/A        if a == b:
622817SN/A            return True
639341SAndreas.Sandberg@arm.com        else:
649341SAndreas.Sandberg@arm.com            self.explanation = _make_explanation(a, b)
659518SAndreas.Sandberg@ARM.com            return False
669518SAndreas.Sandberg@ARM.com
679518SAndreas.Sandberg@ARM.com
689518SAndreas.Sandberg@ARM.comclass Capture(object):
699518SAndreas.Sandberg@ARM.com    def __init__(self, capfd):
709518SAndreas.Sandberg@ARM.com        self.capfd = capfd
719518SAndreas.Sandberg@ARM.com        self.out = ""
729518SAndreas.Sandberg@ARM.com        self.err = ""
739518SAndreas.Sandberg@ARM.com
749518SAndreas.Sandberg@ARM.com    def __enter__(self):
759518SAndreas.Sandberg@ARM.com        self.capfd.readouterr()
769518SAndreas.Sandberg@ARM.com        return self
772932SN/A
781681SN/A    def __exit__(self, *_):
7911780Sarthur.perais@inria.fr        self.out, self.err = self.capfd.readouterr()
8013710Sgabor.dozsa@arm.com
8113710Sgabor.dozsa@arm.com    def __eq__(self, other):
8213710Sgabor.dozsa@arm.com        a = Output(self.out)
831681SN/A        b = other
849184Sandreas.hansson@arm.com        if a == b:
859184Sandreas.hansson@arm.com            return True
869184Sandreas.hansson@arm.com        else:
879184Sandreas.hansson@arm.com            self.explanation = a.explanation
889184Sandreas.hansson@arm.com            return False
892932SN/A
909982Satgutier@umich.edu    def __str__(self):
9110331Smitch.hayenga@arm.com        return self.out
9210331Smitch.hayenga@arm.com
932932SN/A    def __contains__(self, item):
949184Sandreas.hansson@arm.com        return item in self.out
959184Sandreas.hansson@arm.com
969184Sandreas.hansson@arm.com    @property
979184Sandreas.hansson@arm.com    def unordered(self):
989184Sandreas.hansson@arm.com        return Unordered(self.out)
992932SN/A
1001681SN/A    @property
1019184Sandreas.hansson@arm.com    def stderr(self):
1029184Sandreas.hansson@arm.com        return Output(self.err)
1039184Sandreas.hansson@arm.com
1049184Sandreas.hansson@arm.com
1052932SN/A@pytest.fixture
1061681SN/Adef capture(capsys):
1079184Sandreas.hansson@arm.com    """Extended `capsys` with context manager and custom equality operators"""
1082932SN/A    return Capture(capsys)
1099184Sandreas.hansson@arm.com
1102932SN/A
1119184Sandreas.hansson@arm.comclass SanitizedString(object):
1122932SN/A    def __init__(self, sanitizer):
1132932SN/A        self.sanitizer = sanitizer
1142932SN/A        self.string = ""
1152932SN/A        self.explanation = []
1163223SN/A
1172932SN/A    def __call__(self, thing):
1189184Sandreas.hansson@arm.com        self.string = self.sanitizer(thing)
1191681SN/A        return self
1209184Sandreas.hansson@arm.com
1212932SN/A    def __eq__(self, other):
1222932SN/A        a = self.string
1239184Sandreas.hansson@arm.com        b = _strip_and_dedent(other)
1249184Sandreas.hansson@arm.com        if a == b:
1251681SN/A            return True
1262932SN/A        else:
1272932SN/A            self.explanation = _make_explanation(a.splitlines(), b.splitlines())
1281681SN/A            return False
1292932SN/A
1302932SN/A
1318199SAli.Saidi@ARM.comdef _sanitize_general(s):
1328199SAli.Saidi@ARM.com    s = s.strip()
1338199SAli.Saidi@ARM.com    s = s.replace("pybind11_tests.", "m.")
1348519SAli.Saidi@ARM.com    s = s.replace("unicode", "str")
1358519SAli.Saidi@ARM.com    s = _long_marker.sub(r"\1", s)
1362932SN/A    s = _unicode_marker.sub(r"\1", s)
1372932SN/A    return s
1381681SN/A
1392932SN/A
1401681SN/Adef _sanitize_docstring(thing):
1412932SN/A    s = thing.__doc__
1422932SN/A    s = _sanitize_general(s)
1432932SN/A    return s
1449921Syasuko.eckert@amd.com
1459921Syasuko.eckert@amd.com
14610338SCurtis.Dunham@arm.com@pytest.fixture
1479921Syasuko.eckert@amd.comdef doc():
1489921Syasuko.eckert@amd.com    """Sanitize docstrings and add custom failure explanation"""
1499921Syasuko.eckert@amd.com    return SanitizedString(_sanitize_docstring)
1509921Syasuko.eckert@amd.com
1519921Syasuko.eckert@amd.com
1529921Syasuko.eckert@amd.comdef _sanitize_message(thing):
1539921Syasuko.eckert@amd.com    s = str(thing)
15412109SRekai.GonzalezAlberquilla@arm.com    s = _sanitize_general(s)
15512109SRekai.GonzalezAlberquilla@arm.com    s = _hexadecimal.sub("0", s)
15613610Sgiacomo.gabrielli@arm.com    return s
15713610Sgiacomo.gabrielli@arm.com
1589921Syasuko.eckert@amd.com
1599921Syasuko.eckert@amd.com@pytest.fixture
1602932SN/Adef msg():
1612932SN/A    """Sanitize messages and add custom failure explanation"""
1621681SN/A    return SanitizedString(_sanitize_message)
1634597Sbinkertn@umich.edu
16413559Snikos.nikoleris@arm.com
16513560Snikos.nikoleris@arm.com# noinspection PyUnusedLocal
16613560Snikos.nikoleris@arm.comdef pytest_assertrepr_compare(op, left, right):
1674597Sbinkertn@umich.edu    """Hook to insert custom failure explanation"""
16813561Snikos.nikoleris@arm.com    if hasattr(left, 'explanation'):
16913561Snikos.nikoleris@arm.com        return left.explanation
1704597Sbinkertn@umich.edu
17113562Snikos.nikoleris@arm.com
17213562Snikos.nikoleris@arm.com@contextlib.contextmanager
1734597Sbinkertn@umich.edudef suppress(exception):
17413563Snikos.nikoleris@arm.com    """Suppress the desired exception"""
1754303SN/A    try:
17610785Sgope@wisc.edu        yield
1779849Sandreas.hansson@arm.com    except exception:
1789849Sandreas.hansson@arm.com        pass
1798727Snilay@cs.wisc.edu
1808727Snilay@cs.wisc.edu
1818887Sgeoffrey.blake@arm.comdef gc_collect():
1828887Sgeoffrey.blake@arm.com    ''' Run the garbage collector twice (needed when running
1838887Sgeoffrey.blake@arm.com    reference counting tests with PyPy) '''
18413665Sandreas.sandberg@arm.com    gc.collect()
1858887Sgeoffrey.blake@arm.com    gc.collect()
1868887Sgeoffrey.blake@arm.com
1878887Sgeoffrey.blake@arm.com
1888887Sgeoffrey.blake@arm.comdef pytest_namespace():
1898887Sgeoffrey.blake@arm.com    """Add import suppression and test requirements to `pytest` namespace"""
1908887Sgeoffrey.blake@arm.com    try:
1918887Sgeoffrey.blake@arm.com        import numpy as np
1929132Satgutier@umich.edu    except ImportError:
1938887Sgeoffrey.blake@arm.com        np = None
1948887Sgeoffrey.blake@arm.com    try:
19512563Sgabeblack@google.com        import scipy
1968887Sgeoffrey.blake@arm.com    except ImportError:
197        scipy = None
198    try:
199        from pybind11_tests.eigen import have_eigen
200    except ImportError:
201        have_eigen = False
202    pypy = platform.python_implementation() == "PyPy"
203
204    skipif = pytest.mark.skipif
205    return {
206        'suppress': suppress,
207        'requires_numpy': skipif(not np, reason="numpy is not installed"),
208        'requires_scipy': skipif(not np, reason="scipy is not installed"),
209        'requires_eigen_and_numpy': skipif(not have_eigen or not np,
210                                           reason="eigen and/or numpy are not installed"),
211        'requires_eigen_and_scipy': skipif(not have_eigen or not scipy,
212                                           reason="eigen and/or scipy are not installed"),
213        'unsupported_on_pypy': skipif(pypy, reason="unsupported on PyPy"),
214        'unsupported_on_py2': skipif(sys.version_info.major < 3,
215                                     reason="unsupported on Python 2.x"),
216        'gc_collect': gc_collect
217    }
218
219
220def _test_import_pybind11():
221    """Early diagnostic for test module initialization errors
222
223    When there is an error during initialization, the first import will report the
224    real error while all subsequent imports will report nonsense. This import test
225    is done early (in the pytest configuration file, before any tests) in order to
226    avoid the noise of having all tests fail with identical error messages.
227
228    Any possible exception is caught here and reported manually *without* the stack
229    trace. This further reduces noise since the trace would only show pytest internals
230    which are not useful for debugging pybind11 module issues.
231    """
232    # noinspection PyBroadException
233    try:
234        import pybind11_tests  # noqa: F401 imported but unused
235    except Exception as e:
236        print("Failed to import pybind11_tests from pytest:")
237        print("  {}: {}".format(type(e).__name__, e))
238        sys.exit(1)
239
240
241_test_import_pybind11()
242