1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
# This file is part of Hypothesis, which may be found at
# https://github.com/HypothesisWorks/hypothesis/
#
# Copyright the Hypothesis Authors.
# Individual contributors are listed in AUTHORS.rst and the git log.
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.
import sys
import pytest
from hypothesis.internal.compat import PYPY
from hypothesis.internal.scrutineer import make_report
# We skip tracing for explanations under PyPy, where it has a large performance
# impact, or if there is already a trace function (e.g. coverage or a debugger)
pytestmark = pytest.mark.skipif(PYPY or sys.gettrace(), reason="See comment")
BUG_MARKER = "# BUG"
DEADLINE_PRELUDE = """
from datetime import timedelta
from hypothesis.errors import DeadlineExceeded
"""
PRELUDE = """
from hypothesis import Phase, given, settings, strategies as st
@settings(phases=tuple(Phase), derandomize=True)
"""
TRIVIAL = """
@given(st.integers())
def test_reports_branch_in_test(x):
if x > 10:
raise AssertionError # BUG
"""
MULTIPLE_BUGS = """
@given(st.integers(), st.integers())
def test_reports_branch_in_test(x, y):
if x > 10:
raise (AssertionError if x % 2 else Exception) # BUG
"""
FRAGMENTS = (
pytest.param(TRIVIAL, id="trivial"),
pytest.param(MULTIPLE_BUGS, id="multiple-bugs"),
)
def get_reports(file_contents, *, testdir):
# Takes the source code string with "# BUG" comments, and returns a list of
# multi-line report strings which we expect to see in explain-mode output.
# The list length is the number of explainable bugs, usually one.
test_file = str(testdir.makepyfile(file_contents))
pytest_stdout = str(testdir.runpytest_inprocess(test_file, "--tb=native").stdout)
crash = "AttributeError: module 'blib2to3.pygram' has no attribute 'python_symbols'"
if crash in pytest_stdout:
pytest.xfail(reason="upstream error in Black")
explanations = {
i: {(test_file, i)}
for i, line in enumerate(file_contents.splitlines())
if line.endswith(BUG_MARKER)
}
expected = [
("\n".join(r), "\n | ".join(r)) # single, ExceptionGroup
for r in make_report(explanations).values()
]
return pytest_stdout, expected
@pytest.mark.parametrize("code", FRAGMENTS)
def test_explanations(code, testdir):
pytest_stdout, expected = get_reports(PRELUDE + code, testdir=testdir)
assert len(expected) == code.count(BUG_MARKER)
for single, group in expected:
assert single in pytest_stdout or group in pytest_stdout
@pytest.mark.parametrize("code", FRAGMENTS)
def test_no_explanations_if_deadline_exceeded(code, testdir):
code = code.replace("AssertionError", "DeadlineExceeded(timedelta(), timedelta())")
pytest_stdout, _ = get_reports(DEADLINE_PRELUDE + PRELUDE + code, testdir=testdir)
assert "Explanation:" not in pytest_stdout
NO_SHOW_CONTEXTLIB = """
from contextlib import contextmanager
from hypothesis import given, strategies as st, Phase, settings
@contextmanager
def ctx():
yield
@settings(phases=list(Phase))
@given(st.integers())
def test(x):
with ctx():
assert x < 100
"""
@pytest.mark.skipif(PYPY, reason="Tracing is slow under PyPy")
def test_skips_uninformative_locations(testdir):
pytest_stdout, _ = get_reports(NO_SHOW_CONTEXTLIB, testdir=testdir)
assert "Explanation:" not in pytest_stdout
|