From d26760cb08960f03e903defdecd5a8141642c5dd Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Mon, 6 Apr 2026 11:00:49 +0100 Subject: [PATCH 01/14] Add snapshot tests for impersonation traceback investigation Snapshot tests capturing full pytest output for scenarios from issue #4681 where hypothesis's function impersonation (co_filename/co_firstlineno replacement in reflection.py) affects tracebacks. Uses pytester to run real pytest sessions and normalizes unstable elements (timing, seeds) while preserving test file line references as source content annotations. Co-Authored-By: Claude Opus 4.6 (1M context) --- hypothesis-python/tests/snapshot/__init__.py | 9 + .../test_impersonation_investigation.ambr | 196 +++++++++++++++ .../test_impersonation_investigation.py | 224 ++++++++++++++++++ 3 files changed, 429 insertions(+) create mode 100644 hypothesis-python/tests/snapshot/__init__.py create mode 100644 hypothesis-python/tests/snapshot/__snapshots__/test_impersonation_investigation.ambr create mode 100644 hypothesis-python/tests/snapshot/test_impersonation_investigation.py diff --git a/hypothesis-python/tests/snapshot/__init__.py b/hypothesis-python/tests/snapshot/__init__.py new file mode 100644 index 0000000000..fcb1ac6538 --- /dev/null +++ b/hypothesis-python/tests/snapshot/__init__.py @@ -0,0 +1,9 @@ +# 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/. diff --git a/hypothesis-python/tests/snapshot/__snapshots__/test_impersonation_investigation.ambr b/hypothesis-python/tests/snapshot/__snapshots__/test_impersonation_investigation.ambr new file mode 100644 index 0000000000..b0577a632d --- /dev/null +++ b/hypothesis-python/tests/snapshot/__snapshots__/test_impersonation_investigation.ambr @@ -0,0 +1,196 @@ +# serializer version: 1 +# name: test_deep_traceback + ''' + ============================= test session starts ============================== + collected 1 item + + test_deep_traceback.py F [100%] + + =================================== FAILURES =================================== + _____________________________ test_deep_traceback ______________________________ + + @settings(database=None, derandomize=True) + > @given(st.integers()) + ^^^ + + test_deep_traceback.py: # @given(st.integers()) + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + + x = 0 + + @settings(database=None, derandomize=True) + @given(st.integers()) + def test_deep_traceback(x): + > helper(x) + + test_deep_traceback.py: # helper(x) + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + + x = 0 + + def helper(x): + > raise ValueError("boom") + E ValueError: boom + E Falsifying example: test_deep_traceback( + E x=0, # or any other generated value + E ) + + test_deep_traceback.py: # raise ValueError("boom") ValueError + ============================== 1 failed in T =============================== + ''' +# --- +# name: test_healthcheck_traceback + ''' + ============================= test session starts ============================== + collected 1 item + + test_healthcheck_traceback.py F [100%] + + =================================== FAILURES =================================== + _____________________ test_healthcheck_traceback_is_hidden _____________________ + + @given(integers().map(lambda x: time.sleep(0.2))) + > def test_healthcheck_traceback_is_hidden(x): + ^^^ + E hypothesis.errors.FailedHealthCheck: Input generation is slow: Hypothesis only generated N valid inputs after T seconds. + E + E count | fraction | slowest draws (seconds) + x | + E + E This could be for a few reasons: + E 1. This strategy could be generating too much data per input. Try decreasing the amount of data generated, for example by decreasing the minimum size of collection strategies like st.lists(). + E 2. Some other expensive computation could be running during input generation. For example, if @st.composite or st.data() is interspersed with an expensive computation, HealthCheck.too_slow is likely to trigger. If this computation is unrelated to input generation, move it elsewhere. Otherwise, try making it more efficient, or disable this health check if that is not possible. + E + E If you expect input generation to take this long, you can disable this health check with @settings(suppress_health_check=[HealthCheck.too_slow]). See https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.HealthCheck for details. + + test_healthcheck_traceback.py: # def test_healthcheck_traceback_is_hidden(x): FailedHealthCheck + ---------------------------------- Hypothesis ---------------------------------- + You can reproduce this failure by adding @seed(0) to this test, or by running pytest with --hypothesis-seed=0. + ============================== 1 failed in T =============================== + ''' +# --- +# name: test_issue_basic_traceback + ''' + ============================= test session starts ============================== + collected 1 item + + test_issue_basic_traceback.py F [100%] + + =================================== FAILURES =================================== + _____________________________ test_basic_traceback _____________________________ + + @settings(database=None, derandomize=True) + > @given(st.none()) + ^^^ + + test_issue_basic_traceback.py: # @given(st.none()) + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + + _ = None + + @settings(database=None, derandomize=True) + @given(st.none()) + def test_basic_traceback(_): + > 1/0 + E ZeroDivisionError: division by zero + E Falsifying example: test_basic_traceback( + E _=None, + E ) + + test_issue_basic_traceback.py: # 1/0 ZeroDivisionError + ============================== 1 failed in T =============================== + ''' +# --- +# name: test_issue_lambda_traceback + ''' + ============================= test session starts ============================== + collected 1 item + + test_issue_lambda_traceback.py F [100%] + + =================================== FAILURES =================================== + ____________________________ test_lambda_traceback _____________________________ + + @settings(database=None, derandomize=True) + > @given(st.none()) + ^^^ + + test_issue_lambda_traceback.py: # @given(st.none()) + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + + _ = None + + @settings(database=None, derandomize=True) + @given(st.none()) + def test_lambda_traceback(_): + f = lambda: """Hi!""" + > 1/0 + E ZeroDivisionError: division by zero + E Falsifying example: test_lambda_traceback( + E _=None, + E ) + + test_issue_lambda_traceback.py: # 1/0 ZeroDivisionError + ============================== 1 failed in T =============================== + ''' +# --- +# name: test_issue_wrong_function_traceback + ''' + ============================= test session starts ============================== + collected 1 item + + test_issue_wrong_function_traceback.py F [100%] + + =================================== FAILURES =================================== + _____________________________ test_wrong_function ______________________________ + + arguments = (), kwargs = {} + + @settings(database=None, derandomize=True) + @given(st.none()) + > def test_wrong_function(): + ^^^^^^^^^^^^^^^^^^ + E hypothesis.errors.InvalidArgument: Too many positional arguments for test_wrong_function() were passed to @given - expected at most 0 arguments, but got 1 (none(),) + + test_issue_wrong_function_traceback.py: # def test_wrong_function(): InvalidArgument + ============================== 1 failed in T =============================== + ''' +# --- +# name: test_map_failure_traceback + ''' + ============================= test session starts ============================== + collected 1 item + + test_map_failure_traceback.py F [100%] + + =================================== FAILURES =================================== + _______________________________ test_map_failure _______________________________ + + @settings(database=None, derandomize=True) + > @given(st.integers().map(lambda x: 1/0)) + ^^^ + + test_map_failure_traceback.py: # @given(st.integers().map(lambda x: 1/0)) + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + + x = 0 + + > @given(st.integers().map(lambda x: 1/0)) + ^^^ + E ZeroDivisionError: division by zero + E while generating 'x' from integers().map(lambda x: 1 / 0) + + test_map_failure_traceback.py: # @given(st.integers().map(lambda x: 1/0)) ZeroDivisionError + ============================== 1 failed in T =============================== + ''' +# --- +# name: test_simple_passing + ''' + ============================= test session starts ============================== + collected 1 item + + test_simple_passing.py . [100%] + + ============================== 1 passed in T =============================== + ''' +# --- diff --git a/hypothesis-python/tests/snapshot/test_impersonation_investigation.py b/hypothesis-python/tests/snapshot/test_impersonation_investigation.py new file mode 100644 index 0000000000..3056245324 --- /dev/null +++ b/hypothesis-python/tests/snapshot/test_impersonation_investigation.py @@ -0,0 +1,224 @@ +# 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/. + +"""Snapshot tests investigating impersonation traceback issues. + +These tests capture the full pytest output for various scenarios where +hypothesis's function impersonation (co_filename/co_firstlineno replacement) +affects tracebacks. See https://github.com/HypothesisWorks/hypothesis/issues/4681 +""" + +import re +import textwrap + + +from hypothesis._settings import _CI_VARS + +pytest_plugins = "pytester" + + +def normalize_output(output, test_filename, test_source_lines): + """Normalize unstable parts of pytest output for snapshot comparison. + + Line numbers from the test file are replaced with the actual source line + content, making the snapshots self-documenting about what the traceback is + pointing at. Line numbers from other files are replaced with NN. + """ + + def replace_lineno(m): + filename = m.group(1) + lineno_str = m.group(2) + trailing = m.group(3) or "" + if filename.endswith(test_filename): + lineno = int(lineno_str) + if 1 <= lineno <= len(test_source_lines): + line_content = test_source_lines[lineno - 1].strip() + else: + line_content = f"" + return f"{filename}: # {line_content}{trailing}" + return f"{filename}:NN:{trailing}" + + output = re.sub( + r"([\w./\\-]+\.py):(\d+):(\s.*)?$", + replace_lineno, + output, + flags=re.MULTILINE, + ) + output = re.sub(r", line \d+,", ", line NN,", output) + # Normalize seeds + output = re.sub(r"@seed\(\d+\)", "@seed(0)", output) + output = re.sub(r"--hypothesis-seed=\d+", "--hypothesis-seed=0", output) + # Normalize patch file paths + output = re.sub( + r"`git apply .hypothesis/patches/[^`]+`", + "`git apply .hypothesis/patches/PATCH`", + output, + ) + # Normalize timing in healthcheck output + output = re.sub( + r"only generated \d+ valid inputs after [\d.]+ seconds", + "only generated N valid inputs after T seconds", + output, + ) + output = re.sub( + r"(count \| fraction \| slowest draws \(seconds\))\n.*", + r"\1\n x | ", + output, + ) + # Normalize test session timing + output = re.sub(r"in [\d.]+s =", "in T =", output) + return output + + +def get_failure_output(testdir, test_code, *extra_args): + """Run a test file via pytester and return normalized failure output.""" + source = textwrap.dedent(test_code).strip() + source_lines = source.splitlines() + script = testdir.makepyfile(source) + result = testdir.runpytest(script, "--tb=long", "--no-header", "-rN", *extra_args) + raw = "\n".join(result.stdout.lines) + return normalize_output(raw, script.basename, source_lines) + + +# ---- Issue #4681 Example 1: Basic ZeroDivisionError ---- +# The impersonation causes an intermediate frame to appear pointing at the +# user's function definition line rather than actual hypothesis internals. + +ISSUE_EXAMPLE_BASIC = """ +from hypothesis import given, settings +import hypothesis.strategies as st + +@settings(database=None, derandomize=True) +@given(st.none()) +def test_basic_traceback(_): + 1/0 +""" + + +def test_issue_basic_traceback(testdir, snapshot): + assert get_failure_output(testdir, ISSUE_EXAMPLE_BASIC) == snapshot + + +# ---- Issue #4681 Example 2: Lambda false positive ---- +# When an impersonated function contains a lambda, the carets in the traceback +# erroneously point to content inside the lambda. + +ISSUE_EXAMPLE_LAMBDA = """ +from hypothesis import given, settings +import hypothesis.strategies as st + +@settings(database=None, derandomize=True) +@given(st.none()) +def test_lambda_traceback(_): + f = lambda: \"\"\"Hi!\"\"\" + 1/0 +""" + + +def test_issue_lambda_traceback(testdir, snapshot): + assert get_failure_output(testdir, ISSUE_EXAMPLE_LAMBDA) == snapshot + + +# ---- Issue #4681 Example 3: Wrong function name ---- +# When a user forgets a parameter in their test signature, the traceback claims +# code was executed inside `wrapped_test` at a line in the user's file. + +ISSUE_EXAMPLE_WRONG_FUNCTION = """ +from hypothesis import given, settings +import hypothesis.strategies as st + +@settings(database=None, derandomize=True) +@given(st.none()) +def test_wrong_function(): + 1/0 +""" + + +def test_issue_wrong_function_traceback(testdir, snapshot): + assert get_failure_output(testdir, ISSUE_EXAMPLE_WRONG_FUNCTION) == snapshot + + +# ---- Healthcheck traceback hiding ---- +# This is the test from test_capture.py that verifies hypothesis internals +# are hidden from the traceback when a HealthCheck fails. + +HEALTHCHECK_TRACEBACK = """ +from hypothesis import given, settings +from hypothesis.strategies import integers +import time +@given(integers().map(lambda x: time.sleep(0.2))) +def test_healthcheck_traceback_is_hidden(x): + pass +""" + + +def test_healthcheck_traceback(testdir, monkeypatch, snapshot): + for key in _CI_VARS: + monkeypatch.delenv(key, raising=False) + assert get_failure_output(testdir, HEALTHCHECK_TRACEBACK) == snapshot + + +# ---- Successful test (baseline) ---- +# A simple passing test to confirm no extraneous traceback output. + +SIMPLE_PASSING = """ +from hypothesis import given, settings +import hypothesis.strategies as st + +@settings(database=None, derandomize=True) +@given(st.none()) +def test_simple_passing(_): + pass +""" + + +def test_simple_passing(testdir, snapshot): + assert get_failure_output(testdir, SIMPLE_PASSING) == snapshot + + +# ---- Multiple frames in traceback ---- +# Test that exercises a deeper call stack to see how impersonation +# affects the intermediate frames. + +DEEP_TRACEBACK = """ +from hypothesis import given, settings +import hypothesis.strategies as st + +def helper(x): + raise ValueError("boom") + +@settings(database=None, derandomize=True) +@given(st.integers()) +def test_deep_traceback(x): + helper(x) +""" + + +def test_deep_traceback(testdir, snapshot): + assert get_failure_output(testdir, DEEP_TRACEBACK) == snapshot + + +# ---- Map with traceback ---- +# Test that exercises a .map() call that fails, to see how the +# impersonation affects the traceback through map internals. + +MAP_FAILURE = """ +from hypothesis import given, settings +import hypothesis.strategies as st + +@settings(database=None, derandomize=True) +@given(st.integers().map(lambda x: 1/0)) +def test_map_failure(x): + pass +""" + + +def test_map_failure_traceback(testdir, snapshot): + assert get_failure_output(testdir, MAP_FAILURE) == snapshot From 61a56353fe83b6cdbb92a37a87a2e6bdd038b898 Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Mon, 6 Apr 2026 11:28:17 +0100 Subject: [PATCH 02/14] Strip __hypothesistracebackhide__ frames from entire traceback Previously, get_trimmed_traceback only removed hypothesis frames from the beginning of the traceback chain. Now it also removes frames with __hypothesistracebackhide__ (or from hypothesis source files) from the middle of the traceback, so impersonated wrapper frames don't leak through as phantom intermediate frames. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/hypothesis/internal/escalation.py | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/escalation.py b/hypothesis-python/src/hypothesis/internal/escalation.py index 031a99004c..f9d330965c 100644 --- a/hypothesis-python/src/hypothesis/internal/escalation.py +++ b/hypothesis-python/src/hypothesis/internal/escalation.py @@ -80,14 +80,27 @@ def get_trimmed_traceback( ) ): return tb - while tb.tb_next is not None and ( - # If the frame is from one of our files, it's been added by Hypothesis. - is_hypothesis_file(getsourcefile(tb.tb_frame) or getfile(tb.tb_frame)) - # But our `@proxies` decorator overrides the source location, - # so we check for an attribute it injects into the frame too. - or tb.tb_frame.f_globals.get("__hypothesistracebackhide__") is True - ): + + def _is_hypothesis_frame(frame): + return ( + is_hypothesis_file(getsourcefile(frame) or getfile(frame)) + or frame.f_globals.get("__hypothesistracebackhide__") is True + ) + + # Strip leading hypothesis frames + while tb.tb_next is not None and _is_hypothesis_frame(tb.tb_frame): tb = tb.tb_next + + # Strip any remaining hypothesis frames from the middle of the traceback + prev = tb + current = tb.tb_next + while current is not None: + if current.tb_next is not None and _is_hypothesis_frame(current.tb_frame): + prev.tb_next = current.tb_next + else: + prev = current + current = prev.tb_next + return tb From 8ef357d4b3500abf510c2fa55999b06215fdfea3 Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Mon, 6 Apr 2026 11:32:42 +0100 Subject: [PATCH 03/14] Move file to right directory --- .../__snapshots__/test_impersonation_investigation.ambr | 0 .../{snapshot => snapshots}/test_impersonation_investigation.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename hypothesis-python/tests/{snapshot => snapshots}/__snapshots__/test_impersonation_investigation.ambr (100%) rename hypothesis-python/tests/{snapshot => snapshots}/test_impersonation_investigation.py (100%) diff --git a/hypothesis-python/tests/snapshot/__snapshots__/test_impersonation_investigation.ambr b/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr similarity index 100% rename from hypothesis-python/tests/snapshot/__snapshots__/test_impersonation_investigation.ambr rename to hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr diff --git a/hypothesis-python/tests/snapshot/test_impersonation_investigation.py b/hypothesis-python/tests/snapshots/test_impersonation_investigation.py similarity index 100% rename from hypothesis-python/tests/snapshot/test_impersonation_investigation.py rename to hypothesis-python/tests/snapshots/test_impersonation_investigation.py From 3c5cd864c74560b5466a962b564e3411bee675f1 Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Mon, 6 Apr 2026 11:46:06 +0100 Subject: [PATCH 04/14] Add PR #1582 motivating examples to impersonation snapshot tests Add snapshot tests for the two examples from PR #1582 (the commit that introduced traceback trimming): a simple assertion failure and a multiple-failures case with different exception types per branch. Also normalize absolute paths in output for stability. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../test_impersonation_investigation.ambr | 74 +++++++++++++++++++ .../test_impersonation_investigation.py | 48 +++++++++++- 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr b/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr index b0577a632d..60389a4492 100644 --- a/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr +++ b/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr @@ -184,6 +184,80 @@ ============================== 1 failed in T =============================== ''' # --- +# name: test_pr1582_multiple_failures + ''' + ============================= test session starts ============================== + collected 1 item + + test_pr1582_multiple_failures.py F [100%] + + =================================== FAILURES =================================== + ____________________________ test_multiple_failures ____________________________ + + Exception Group Traceback (most recent call last): + | File "test_pr1582_multiple_failures.py", line NN, in test_multiple_failures + | @given(st.integers()) + | ^^^ + .../hypothesis/core.py", line NN, in wrapped_test + | raise the_error_hypothesis_found + | ExceptionGroup: Hypothesis found 2 distinct failures. (2 sub-exceptions) + +-+---------------- 1 ---------------- + | Traceback (most recent call last): + | File "test_pr1582_multiple_failures.py", line NN, in test_multiple_failures + | raise RuntimeError("This number is too small!") + | RuntimeError: This number is too small! + | Falsifying example: test_multiple_failures( + | x=-101, + | ) + | Explanation: + | These lines were always and only run by failing examples: + | test_pr1582_multiple_failures.py:10 + +---------------- 2 ---------------- + | Traceback (most recent call last): + | File "test_pr1582_multiple_failures.py", line NN, in test_multiple_failures + | raise ValueError("This number is too big!") + | ValueError: This number is too big! + | Falsifying example: test_multiple_failures( + | x=101, + | ) + | Explanation: + | These lines were always and only run by failing examples: + | test_pr1582_multiple_failures.py:8 + +------------------------------------ + ============================== 1 failed in T =============================== + ''' +# --- +# name: test_pr1582_simple + ''' + ============================= test session starts ============================== + collected 1 item + + test_pr1582_simple.py F [100%] + + =================================== FAILURES =================================== + _________________________________ test_simple __________________________________ + + @settings(database=None, derandomize=True) + > @given(st.integers()) + ^^^ + + test_pr1582_simple.py: # @given(st.integers()) + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + + x = 0 + + @settings(database=None, derandomize=True) + @given(st.integers()) + def test_simple(x): + > assert x + E assert 0 + E Falsifying example: test_simple( + E x=0, + E ) + + test_pr1582_simple.py: # assert x AssertionError + ============================== 1 failed in T =============================== + ''' +# --- # name: test_simple_passing ''' ============================= test session starts ============================== diff --git a/hypothesis-python/tests/snapshots/test_impersonation_investigation.py b/hypothesis-python/tests/snapshots/test_impersonation_investigation.py index 3056245324..51fb5c6e43 100644 --- a/hypothesis-python/tests/snapshots/test_impersonation_investigation.py +++ b/hypothesis-python/tests/snapshots/test_impersonation_investigation.py @@ -18,7 +18,6 @@ import re import textwrap - from hypothesis._settings import _CI_VARS pytest_plugins = "pytester" @@ -52,6 +51,12 @@ def replace_lineno(m): flags=re.MULTILINE, ) output = re.sub(r", line \d+,", ", line NN,", output) + # Normalize hypothesis source paths to a stable prefix + output = re.sub( + r'(?:File ")?.*/src/hypothesis/', + ".../hypothesis/", + output, + ) # Normalize seeds output = re.sub(r"@seed\(\d+\)", "@seed(0)", output) output = re.sub(r"--hypothesis-seed=\d+", "--hypothesis-seed=0", output) @@ -84,6 +89,9 @@ def get_failure_output(testdir, test_code, *extra_args): script = testdir.makepyfile(source) result = testdir.runpytest(script, "--tb=long", "--no-header", "-rN", *extra_args) raw = "\n".join(result.stdout.lines) + # Replace the full absolute path of the test file with just the basename, + # so exception group tracebacks and explain sections are stable. + raw = raw.replace(str(script), script.basename) return normalize_output(raw, script.basename, source_lines) @@ -222,3 +230,41 @@ def test_map_failure(x): def test_map_failure_traceback(testdir, snapshot): assert get_failure_output(testdir, MAP_FAILURE) == snapshot + + +# ---- PR #1582 motivating examples ---- +# These are the examples from the PR that introduced traceback trimming. +# test_simple: a basic assertion failure (the simplest possible case) +# test_multiple_failures: different exception types from branches + +PR1582_SIMPLE = """ +from hypothesis import given, settings +import hypothesis.strategies as st + +@settings(database=None, derandomize=True) +@given(st.integers()) +def test_simple(x): + assert x +""" + + +def test_pr1582_simple(testdir, snapshot): + assert get_failure_output(testdir, PR1582_SIMPLE) == snapshot + + +PR1582_MULTIPLE_FAILURES = """ +from hypothesis import given, settings +import hypothesis.strategies as st + +@settings(database=None, derandomize=True) +@given(st.integers()) +def test_multiple_failures(x): + if x > 100: + raise ValueError("This number is too big!") + elif x < -100: + raise RuntimeError("This number is too small!") +""" + + +def test_pr1582_multiple_failures(testdir, snapshot): + assert get_failure_output(testdir, PR1582_MULTIPLE_FAILURES) == snapshot From 9723a5beeffa980bf003cd34ec2d30bef306a93d Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Mon, 6 Apr 2026 12:05:21 +0100 Subject: [PATCH 05/14] Remove co_filename/co_firstlineno impersonation, hide wrapper via __tracebackhide__ Remove the code object replacement in impersonate() that caused incorrect source lines in tracebacks (issue #4681). Instead, add __tracebackhide__ to the COPY_SIGNATURE_SCRIPT wrapper so pytest hides this pure-forwarding frame entirely. This fixes the phantom traceback frames showing wrong source lines for normal test failures. Two edge cases remain where all frames are hidden: FailedHealthCheck (no user code ran) and ExceptionGroup/multiple failures (the outer traceback has no user frames after hiding). These need the __tracebackhide__ in wrapped_test to be rethought separately. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/hypothesis/internal/reflection.py | 10 +- .../test_impersonation_investigation.ambr | 246 +++++++++++------- 2 files changed, 161 insertions(+), 95 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/reflection.py b/hypothesis-python/src/hypothesis/internal/reflection.py index f462b10fcf..2c42396ab4 100644 --- a/hypothesis-python/src/hypothesis/internal/reflection.py +++ b/hypothesis-python/src/hypothesis/internal/reflection.py @@ -357,6 +357,7 @@ def source_exec_as_module(source: str) -> ModuleType: def accept({funcname}): def {name}{signature}: + __tracebackhide__ = True return {funcname}({invocation}) return {name} """.lstrip() @@ -468,12 +469,9 @@ def impersonate(target): """ def accept(f): - # Lie shamelessly about where this code comes from, to hide the hypothesis - # internals from pytest, ipython, and other runtime introspection. - f.__code__ = f.__code__.replace( - co_filename=target.__code__.co_filename, - co_firstlineno=target.__code__.co_firstlineno, - ) + # Hide hypothesis internals from tracebacks via pytest's mechanism. + # Previously this replaced co_filename/co_firstlineno on the code object, + # but that caused incorrect source lines in tracebacks (see #4681). f.__name__ = target.__name__ f.__module__ = target.__module__ f.__doc__ = target.__doc__ diff --git a/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr b/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr index 60389a4492..11aa1a4cf5 100644 --- a/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr +++ b/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr @@ -9,13 +9,6 @@ =================================== FAILURES =================================== _____________________________ test_deep_traceback ______________________________ - @settings(database=None, derandomize=True) - > @given(st.integers()) - ^^^ - - test_deep_traceback.py: # @given(st.integers()) - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - x = 0 @settings(database=None, derandomize=True) @@ -49,21 +42,17 @@ =================================== FAILURES =================================== _____________________ test_healthcheck_traceback_is_hidden _____________________ - @given(integers().map(lambda x: time.sleep(0.2))) - > def test_healthcheck_traceback_is_hidden(x): - ^^^ E hypothesis.errors.FailedHealthCheck: Input generation is slow: Hypothesis only generated N valid inputs after T seconds. - E - E count | fraction | slowest draws (seconds) + + count | fraction | slowest draws (seconds) x | - E - E This could be for a few reasons: - E 1. This strategy could be generating too much data per input. Try decreasing the amount of data generated, for example by decreasing the minimum size of collection strategies like st.lists(). - E 2. Some other expensive computation could be running during input generation. For example, if @st.composite or st.data() is interspersed with an expensive computation, HealthCheck.too_slow is likely to trigger. If this computation is unrelated to input generation, move it elsewhere. Otherwise, try making it more efficient, or disable this health check if that is not possible. - E - E If you expect input generation to take this long, you can disable this health check with @settings(suppress_health_check=[HealthCheck.too_slow]). See https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.HealthCheck for details. - - test_healthcheck_traceback.py: # def test_healthcheck_traceback_is_hidden(x): FailedHealthCheck + + This could be for a few reasons: + 1. This strategy could be generating too much data per input. Try decreasing the amount of data generated, for example by decreasing the minimum size of collection strategies like st.lists(). + 2. Some other expensive computation could be running during input generation. For example, if @st.composite or st.data() is interspersed with an expensive computation, HealthCheck.too_slow is likely to trigger. If this computation is unrelated to input generation, move it elsewhere. Otherwise, try making it more efficient, or disable this health check if that is not possible. + + If you expect input generation to take this long, you can disable this health check with @settings(suppress_health_check=[HealthCheck.too_slow]). See https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.HealthCheck for details. + All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames. ---------------------------------- Hypothesis ---------------------------------- You can reproduce this failure by adding @seed(0) to this test, or by running pytest with --hypothesis-seed=0. ============================== 1 failed in T =============================== @@ -79,13 +68,6 @@ =================================== FAILURES =================================== _____________________________ test_basic_traceback _____________________________ - @settings(database=None, derandomize=True) - > @given(st.none()) - ^^^ - - test_issue_basic_traceback.py: # @given(st.none()) - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - _ = None @settings(database=None, derandomize=True) @@ -111,13 +93,6 @@ =================================== FAILURES =================================== ____________________________ test_lambda_traceback _____________________________ - @settings(database=None, derandomize=True) - > @given(st.none()) - ^^^ - - test_issue_lambda_traceback.py: # @given(st.none()) - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - _ = None @settings(database=None, derandomize=True) @@ -146,13 +121,12 @@ arguments = (), kwargs = {} - @settings(database=None, derandomize=True) - @given(st.none()) - > def test_wrong_function(): - ^^^^^^^^^^^^^^^^^^ - E hypothesis.errors.InvalidArgument: Too many positional arguments for test_wrong_function() were passed to @given - expected at most 0 arguments, but got 1 (none(),) + @impersonate(test) + def wrapped_test(*arguments, **kwargs): # pragma: no cover # coverage limitation + > raise exc(message) + E hypothesis.errors.InvalidArgument: Too many positional arguments for test_wrong_function() were passed to @given - expected at most 0 arguments, but got 1 (none(),) - test_issue_wrong_function_traceback.py: # def test_wrong_function(): InvalidArgument + .../hypothesis/core.py:NN: InvalidArgument ============================== 1 failed in T =============================== ''' # --- @@ -166,13 +140,6 @@ =================================== FAILURES =================================== _______________________________ test_map_failure _______________________________ - @settings(database=None, derandomize=True) - > @given(st.integers().map(lambda x: 1/0)) - ^^^ - - test_map_failure_traceback.py: # @given(st.integers().map(lambda x: 1/0)) - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - x = 0 > @given(st.integers().map(lambda x: 1/0)) @@ -189,41 +156,149 @@ ============================= test session starts ============================== collected 1 item - test_pr1582_multiple_failures.py F [100%] - - =================================== FAILURES =================================== - ____________________________ test_multiple_failures ____________________________ - + Exception Group Traceback (most recent call last): - | File "test_pr1582_multiple_failures.py", line NN, in test_multiple_failures - | @given(st.integers()) - | ^^^ - .../hypothesis/core.py", line NN, in wrapped_test - | raise the_error_hypothesis_found - | ExceptionGroup: Hypothesis found 2 distinct failures. (2 sub-exceptions) - +-+---------------- 1 ---------------- - | Traceback (most recent call last): - | File "test_pr1582_multiple_failures.py", line NN, in test_multiple_failures - | raise RuntimeError("This number is too small!") - | RuntimeError: This number is too small! - | Falsifying example: test_multiple_failures( - | x=-101, - | ) - | Explanation: - | These lines were always and only run by failing examples: - | test_pr1582_multiple_failures.py:10 - +---------------- 2 ---------------- - | Traceback (most recent call last): - | File "test_pr1582_multiple_failures.py", line NN, in test_multiple_failures - | raise ValueError("This number is too big!") - | ValueError: This number is too big! - | Falsifying example: test_multiple_failures( - | x=101, - | ) - | Explanation: - | These lines were always and only run by failing examples: - | test_pr1582_multiple_failures.py:8 - +------------------------------------ - ============================== 1 failed in T =============================== + test_pr1582_multiple_failures.py + INTERNALERROR> Traceback (most recent call last): + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/main.py", line NN, in wrap_session + INTERNALERROR> session.exitstatus = doit(config, session) or 0 + INTERNALERROR> ~~~~^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/main.py", line NN, in _main + INTERNALERROR> config.hook.pytest_runtestloop(session=session) + INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_hooks.py", line NN, in __call__ + INTERNALERROR> return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) + INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_manager.py", line NN, in _hookexec + INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) + INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> raise exception + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> teardown.throw(exception) + INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/logging.py", line NN, in pytest_runtestloop + INTERNALERROR> return (yield) # Run all the tests. + INTERNALERROR> ^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> teardown.throw(exception) + INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/terminal.py", line NN, in pytest_runtestloop + INTERNALERROR> result = yield + INTERNALERROR> ^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> res = hook_impl.function(*args) + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/main.py", line NN, in pytest_runtestloop + INTERNALERROR> item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) + INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_hooks.py", line NN, in __call__ + INTERNALERROR> return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) + INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_manager.py", line NN, in _hookexec + INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) + INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> raise exception + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> teardown.throw(exception) + INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/warnings.py", line NN, in pytest_runtest_protocol + INTERNALERROR> return (yield) + INTERNALERROR> ^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> teardown.throw(exception) + INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/assertion/__init__.py", line NN, in pytest_runtest_protocol + INTERNALERROR> return (yield) + INTERNALERROR> ^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> teardown.throw(exception) + INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/unittest.py", line NN, in pytest_runtest_protocol + INTERNALERROR> return (yield) + INTERNALERROR> ^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> teardown.throw(exception) + INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/faulthandler.py", line NN, in pytest_runtest_protocol + INTERNALERROR> return (yield) + INTERNALERROR> ^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> res = hook_impl.function(*args) + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/runner.py", line NN, in pytest_runtest_protocol + INTERNALERROR> runtestprotocol(item, nextitem=nextitem) + INTERNALERROR> ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/runner.py", line NN, in runtestprotocol + INTERNALERROR> reports.append(call_and_report(item, "call", log)) + INTERNALERROR> ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/runner.py", line NN, in call_and_report + INTERNALERROR> report: TestReport = ihook.pytest_runtest_makereport(item=item, call=call) + INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_hooks.py", line NN, in __call__ + INTERNALERROR> return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) + INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_manager.py", line NN, in _hookexec + INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) + INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> raise exception + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> teardown.throw(exception) + INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/tmpdir.py", line NN, in pytest_runtest_makereport + INTERNALERROR> rep = yield + INTERNALERROR> ^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> teardown.throw(exception) + INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in run_old_style_hookwrapper + INTERNALERROR> teardown.send(result) + INTERNALERROR> ~~~~~~~~~~~~~^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/src/_hypothesis_pytestplugin.py", line NN, in pytest_runtest_makereport + INTERNALERROR> report = (yield).get_result() + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_result.py", line NN, in get_result + INTERNALERROR> raise exc.with_traceback(tb) + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in run_old_style_hookwrapper + INTERNALERROR> res = yield + INTERNALERROR> ^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> teardown.throw(exception) + INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/skipping.py", line NN, in pytest_runtest_makereport + INTERNALERROR> rep = yield + INTERNALERROR> ^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall + INTERNALERROR> res = hook_impl.function(*args) + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/runner.py", line NN, in pytest_runtest_makereport + INTERNALERROR> return TestReport.from_item_and_call(item, call) + INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/reports.py", line NN, in from_item_and_call + INTERNALERROR> longrepr = _format_failed_longrepr(item, call, excinfo) + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/reports.py", line NN, in _format_failed_longrepr + INTERNALERROR> longrepr = item.repr_failure(excinfo) + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/python.py", line NN, in repr_failure + INTERNALERROR> return self._repr_failure_py(excinfo, style=style) + INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/nodes.py", line NN, in _repr_failure_py + INTERNALERROR> return excinfo.getrepr( + INTERNALERROR> ~~~~~~~~~~~~~~~^ + INTERNALERROR> funcargs=True, + INTERNALERROR> ^^^^^^^^^^^^^^ + INTERNALERROR> ...<5 lines>... + INTERNALERROR> truncate_args=truncate_args, + INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + INTERNALERROR> ) + INTERNALERROR> ^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/_code/code.py", line NN, in getrepr + INTERNALERROR> return fmt.repr_excinfo(self) + INTERNALERROR> ~~~~~~~~~~~~~~~~^^^^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/_code/code.py", line NN, in repr_excinfo + INTERNALERROR> traceback[0]._rawentry, + INTERNALERROR> ~~~~~~~~~^^^ + INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/_code/code.py", line NN, in __getitem__ + INTERNALERROR> return super().__getitem__(key) + INTERNALERROR> ~~~~~~~~~~~~~~~~~~~^^^^^ + INTERNALERROR> IndexError: list index out of range + + ============================== 1 warning in T ============================== ''' # --- # name: test_pr1582_simple @@ -236,13 +311,6 @@ =================================== FAILURES =================================== _________________________________ test_simple __________________________________ - @settings(database=None, derandomize=True) - > @given(st.integers()) - ^^^ - - test_pr1582_simple.py: # @given(st.integers()) - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - x = 0 @settings(database=None, derandomize=True) From fafeff16bf3d08a0d7a2ac68db1c6b4964e8f9ae Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Mon, 6 Apr 2026 12:16:22 +0100 Subject: [PATCH 06/14] Extract _reraise_trimmed_error so the raise line is visible in pytest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The raise inside wrapped_test was hidden by __tracebackhide__ = True. Previously the co_filename impersonation made pytest treat this frame as user code, bypassing __tracebackhide__. With impersonation removed, the raise needs to be in a separate function without __tracebackhide__ so pytest shows it. Also remove the fragile line-counting healthcheck traceback test from test_capture.py — this is now covered by the snapshot test, and update test_traceback_elision.py frame count for the extra frame. Co-Authored-By: Claude Opus 4.6 (1M context) --- hypothesis-python/src/hypothesis/core.py | 22 +- .../tests/cover/test_traceback_elision.py | 10 +- .../tests/pytest/test_capture.py | 39 --- .../test_impersonation_investigation.ambr | 268 ++++++++---------- 4 files changed, 135 insertions(+), 204 deletions(-) diff --git a/hypothesis-python/src/hypothesis/core.py b/hypothesis-python/src/hypothesis/core.py index beb28c539a..9a0e699def 100644 --- a/hypothesis-python/src/hypothesis/core.py +++ b/hypothesis-python/src/hypothesis/core.py @@ -1619,6 +1619,15 @@ def shortlex_key(error): return result +def _reraise_trimmed_error(the_error_hypothesis_found): + """Re-raise the error with its trimmed traceback. + + This is a separate function so that ``wrapped_test``'s + ``__tracebackhide__ = True`` doesn't hide this frame from pytest. + """ + raise the_error_hypothesis_found + + def _raise_to_user( errors_to_report, settings, target_lines, trailer="", *, unsound_backend=None ): @@ -2248,20 +2257,15 @@ def wrapped_test(*arguments, **kwargs): f"{pytest_extra_msg}." ) report(msg) - # The dance here is to avoid showing users long tracebacks - # full of Hypothesis internals they don't care about. - # We have to do this inline, to avoid adding another - # internal stack frame just when we've removed the rest. - # - # Using a variable for our trimmed error ensures that the line - # which will actually appear in tracebacks is as clear as - # possible - "raise the_error_hypothesis_found". + # Trim the traceback to remove hypothesis internals, + # then re-raise from a function without __tracebackhide__ + # so this frame is visible in pytest output. the_error_hypothesis_found = e.with_traceback( None if isinstance(e, BaseExceptionGroup) else get_trimmed_traceback() ) - raise the_error_hypothesis_found + _reraise_trimmed_error(the_error_hypothesis_found) if not (ran_explicit_examples or state.ever_executed): raise SKIP_BECAUSE_NO_EXAMPLES diff --git a/hypothesis-python/tests/cover/test_traceback_elision.py b/hypothesis-python/tests/cover/test_traceback_elision.py index 2deb02cd01..1b9931fed5 100644 --- a/hypothesis-python/tests/cover/test_traceback_elision.py +++ b/hypothesis-python/tests/cover/test_traceback_elision.py @@ -29,9 +29,11 @@ def simplest_failure(x): simplest_failure() except ValueError as e: tb = traceback.extract_tb(e.__traceback__) - # Unless in debug mode, Hypothesis adds 1 frame - the least possible! - # (4 frames: this one, simplest_failure, internal frame, assert False) + # Unless in debug mode, Hypothesis adds 2 frames: + # _reraise_trimmed_error and wrapped_test. + # (5 frames: this one, simplest_failure, _reraise_trimmed_error, + # wrapped_test, raise ValueError) if verbosity < Verbosity.debug and not env_value: - assert len(tb) == 4 + assert len(tb) == 5 else: - assert len(tb) >= 5 + assert len(tb) >= 6 diff --git a/hypothesis-python/tests/pytest/test_capture.py b/hypothesis-python/tests/pytest/test_capture.py index ac750c81ec..8141094aa3 100644 --- a/hypothesis-python/tests/pytest/test_capture.py +++ b/hypothesis-python/tests/pytest/test_capture.py @@ -10,7 +10,6 @@ import pytest -from hypothesis._settings import _CI_VARS from hypothesis.internal.compat import WINDOWS, escape_unicode_characters pytest_plugins = "pytester" @@ -71,44 +70,6 @@ def test_output_emitting_unicode(testdir, monkeypatch): assert result.ret == 0 -def get_line_num(token, lines, skip_n=0): - skipped = 0 - for i, line in enumerate(lines): - if token in line: - if skip_n == skipped: - return i - else: - skipped += 1 - raise AssertionError( - f"Token {token!r} not found (skipped {skipped} of planned {skip_n} skips)" - ) - - -TRACEBACKHIDE_HEALTHCHECK = """ -from hypothesis import given, settings -from hypothesis.strategies import integers -import time -@given(integers().map(lambda x: time.sleep(0.2))) -def test_healthcheck_traceback_is_hidden(x): - pass -""" - - -def test_healthcheck_traceback_is_hidden(testdir, monkeypatch): - for key in _CI_VARS: - monkeypatch.delenv(key, raising=False) - - script = testdir.makepyfile(TRACEBACKHIDE_HEALTHCHECK) - lines = testdir.runpytest(script, "--verbose").stdout.lines - def_token = "__ test_healthcheck_traceback_is_hidden __" - timeout_token = ": FailedHealthCheck" - def_line = get_line_num(def_token, lines) - timeout_line = get_line_num(timeout_token, lines) - # 16 on pytest 8.4.0 combined with py3{9, 10} or 3.13 free-threading (but - # not with 3.13 normal??) - assert timeout_line - def_line in {15, 16} - - COMPOSITE_IS_NOT_A_TEST = """ from hypothesis.strategies import composite, none @composite diff --git a/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr b/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr index 11aa1a4cf5..cf5ffd6926 100644 --- a/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr +++ b/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr @@ -9,6 +9,19 @@ =================================== FAILURES =================================== _____________________________ test_deep_traceback ______________________________ + the_error_hypothesis_found = ValueError('boom') + + def _reraise_trimmed_error(the_error_hypothesis_found): + """Re-raise the error with its trimmed traceback. + + This is a separate function so that ``wrapped_test``'s + ``__tracebackhide__ = True`` doesn't hide this frame from pytest. + """ + > raise the_error_hypothesis_found + + .../hypothesis/core.py:NN: + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + x = 0 @settings(database=None, derandomize=True) @@ -42,17 +55,27 @@ =================================== FAILURES =================================== _____________________ test_healthcheck_traceback_is_hidden _____________________ - E hypothesis.errors.FailedHealthCheck: Input generation is slow: Hypothesis only generated N valid inputs after T seconds. + the_error_hypothesis_found = FailedHealthCheck('Input generation is slow: Hypothesis only generated N valid inputs after T seconds.\n\n cou...ck.too_slow]). See https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.HealthCheck for details.') + + def _reraise_trimmed_error(the_error_hypothesis_found): + """Re-raise the error with its trimmed traceback. - count | fraction | slowest draws (seconds) + This is a separate function so that ``wrapped_test``'s + ``__tracebackhide__ = True`` doesn't hide this frame from pytest. + """ + > raise the_error_hypothesis_found + E hypothesis.errors.FailedHealthCheck: Input generation is slow: Hypothesis only generated N valid inputs after T seconds. + E + E count | fraction | slowest draws (seconds) x | - - This could be for a few reasons: - 1. This strategy could be generating too much data per input. Try decreasing the amount of data generated, for example by decreasing the minimum size of collection strategies like st.lists(). - 2. Some other expensive computation could be running during input generation. For example, if @st.composite or st.data() is interspersed with an expensive computation, HealthCheck.too_slow is likely to trigger. If this computation is unrelated to input generation, move it elsewhere. Otherwise, try making it more efficient, or disable this health check if that is not possible. - - If you expect input generation to take this long, you can disable this health check with @settings(suppress_health_check=[HealthCheck.too_slow]). See https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.HealthCheck for details. - All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames. + E + E This could be for a few reasons: + E 1. This strategy could be generating too much data per input. Try decreasing the amount of data generated, for example by decreasing the minimum size of collection strategies like st.lists(). + E 2. Some other expensive computation could be running during input generation. For example, if @st.composite or st.data() is interspersed with an expensive computation, HealthCheck.too_slow is likely to trigger. If this computation is unrelated to input generation, move it elsewhere. Otherwise, try making it more efficient, or disable this health check if that is not possible. + E + E If you expect input generation to take this long, you can disable this health check with @settings(suppress_health_check=[HealthCheck.too_slow]). See https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.HealthCheck for details. + + .../hypothesis/core.py:NN: FailedHealthCheck ---------------------------------- Hypothesis ---------------------------------- You can reproduce this failure by adding @seed(0) to this test, or by running pytest with --hypothesis-seed=0. ============================== 1 failed in T =============================== @@ -68,6 +91,19 @@ =================================== FAILURES =================================== _____________________________ test_basic_traceback _____________________________ + the_error_hypothesis_found = ZeroDivisionError('division by zero') + + def _reraise_trimmed_error(the_error_hypothesis_found): + """Re-raise the error with its trimmed traceback. + + This is a separate function so that ``wrapped_test``'s + ``__tracebackhide__ = True`` doesn't hide this frame from pytest. + """ + > raise the_error_hypothesis_found + + .../hypothesis/core.py:NN: + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ = None @settings(database=None, derandomize=True) @@ -93,6 +129,19 @@ =================================== FAILURES =================================== ____________________________ test_lambda_traceback _____________________________ + the_error_hypothesis_found = ZeroDivisionError('division by zero') + + def _reraise_trimmed_error(the_error_hypothesis_found): + """Re-raise the error with its trimmed traceback. + + This is a separate function so that ``wrapped_test``'s + ``__tracebackhide__ = True`` doesn't hide this frame from pytest. + """ + > raise the_error_hypothesis_found + + .../hypothesis/core.py:NN: + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ = None @settings(database=None, derandomize=True) @@ -140,6 +189,19 @@ =================================== FAILURES =================================== _______________________________ test_map_failure _______________________________ + the_error_hypothesis_found = ZeroDivisionError('division by zero') + + def _reraise_trimmed_error(the_error_hypothesis_found): + """Re-raise the error with its trimmed traceback. + + This is a separate function so that ``wrapped_test``'s + ``__tracebackhide__ = True`` doesn't hide this frame from pytest. + """ + > raise the_error_hypothesis_found + + .../hypothesis/core.py:NN: + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + x = 0 > @given(st.integers().map(lambda x: 1/0)) @@ -156,149 +218,38 @@ ============================= test session starts ============================== collected 1 item - test_pr1582_multiple_failures.py - INTERNALERROR> Traceback (most recent call last): - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/main.py", line NN, in wrap_session - INTERNALERROR> session.exitstatus = doit(config, session) or 0 - INTERNALERROR> ~~~~^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/main.py", line NN, in _main - INTERNALERROR> config.hook.pytest_runtestloop(session=session) - INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_hooks.py", line NN, in __call__ - INTERNALERROR> return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) - INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_manager.py", line NN, in _hookexec - INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) - INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> raise exception - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> teardown.throw(exception) - INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/logging.py", line NN, in pytest_runtestloop - INTERNALERROR> return (yield) # Run all the tests. - INTERNALERROR> ^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> teardown.throw(exception) - INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/terminal.py", line NN, in pytest_runtestloop - INTERNALERROR> result = yield - INTERNALERROR> ^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> res = hook_impl.function(*args) - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/main.py", line NN, in pytest_runtestloop - INTERNALERROR> item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) - INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_hooks.py", line NN, in __call__ - INTERNALERROR> return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) - INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_manager.py", line NN, in _hookexec - INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) - INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> raise exception - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> teardown.throw(exception) - INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/warnings.py", line NN, in pytest_runtest_protocol - INTERNALERROR> return (yield) - INTERNALERROR> ^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> teardown.throw(exception) - INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/assertion/__init__.py", line NN, in pytest_runtest_protocol - INTERNALERROR> return (yield) - INTERNALERROR> ^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> teardown.throw(exception) - INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/unittest.py", line NN, in pytest_runtest_protocol - INTERNALERROR> return (yield) - INTERNALERROR> ^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> teardown.throw(exception) - INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/faulthandler.py", line NN, in pytest_runtest_protocol - INTERNALERROR> return (yield) - INTERNALERROR> ^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> res = hook_impl.function(*args) - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/runner.py", line NN, in pytest_runtest_protocol - INTERNALERROR> runtestprotocol(item, nextitem=nextitem) - INTERNALERROR> ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/runner.py", line NN, in runtestprotocol - INTERNALERROR> reports.append(call_and_report(item, "call", log)) - INTERNALERROR> ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/runner.py", line NN, in call_and_report - INTERNALERROR> report: TestReport = ihook.pytest_runtest_makereport(item=item, call=call) - INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_hooks.py", line NN, in __call__ - INTERNALERROR> return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) - INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_manager.py", line NN, in _hookexec - INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) - INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> raise exception - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> teardown.throw(exception) - INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/tmpdir.py", line NN, in pytest_runtest_makereport - INTERNALERROR> rep = yield - INTERNALERROR> ^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> teardown.throw(exception) - INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in run_old_style_hookwrapper - INTERNALERROR> teardown.send(result) - INTERNALERROR> ~~~~~~~~~~~~~^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/src/_hypothesis_pytestplugin.py", line NN, in pytest_runtest_makereport - INTERNALERROR> report = (yield).get_result() - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_result.py", line NN, in get_result - INTERNALERROR> raise exc.with_traceback(tb) - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in run_old_style_hookwrapper - INTERNALERROR> res = yield - INTERNALERROR> ^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> teardown.throw(exception) - INTERNALERROR> ~~~~~~~~~~~~~~^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/skipping.py", line NN, in pytest_runtest_makereport - INTERNALERROR> rep = yield - INTERNALERROR> ^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/pluggy/_callers.py", line NN, in _multicall - INTERNALERROR> res = hook_impl.function(*args) - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/runner.py", line NN, in pytest_runtest_makereport - INTERNALERROR> return TestReport.from_item_and_call(item, call) - INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/reports.py", line NN, in from_item_and_call - INTERNALERROR> longrepr = _format_failed_longrepr(item, call, excinfo) - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/reports.py", line NN, in _format_failed_longrepr - INTERNALERROR> longrepr = item.repr_failure(excinfo) - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/python.py", line NN, in repr_failure - INTERNALERROR> return self._repr_failure_py(excinfo, style=style) - INTERNALERROR> ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/nodes.py", line NN, in _repr_failure_py - INTERNALERROR> return excinfo.getrepr( - INTERNALERROR> ~~~~~~~~~~~~~~~^ - INTERNALERROR> funcargs=True, - INTERNALERROR> ^^^^^^^^^^^^^^ - INTERNALERROR> ...<5 lines>... - INTERNALERROR> truncate_args=truncate_args, - INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - INTERNALERROR> ) - INTERNALERROR> ^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/_code/code.py", line NN, in getrepr - INTERNALERROR> return fmt.repr_excinfo(self) - INTERNALERROR> ~~~~~~~~~~~~~~~~^^^^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/_code/code.py", line NN, in repr_excinfo - INTERNALERROR> traceback[0]._rawentry, - INTERNALERROR> ~~~~~~~~~^^^ - INTERNALERROR> File "/Users/drmaciver/Projects/hypothesis/hypothesis-python/.venv/lib/python3.14/site-packages/_pytest/_code/code.py", line NN, in __getitem__ - INTERNALERROR> return super().__getitem__(key) - INTERNALERROR> ~~~~~~~~~~~~~~~~~~~^^^^^ - INTERNALERROR> IndexError: list index out of range - - ============================== 1 warning in T ============================== + test_pr1582_multiple_failures.py F [100%] + + =================================== FAILURES =================================== + ____________________________ test_multiple_failures ____________________________ + + Exception Group Traceback (most recent call last): + .../hypothesis/core.py", line NN, in _reraise_trimmed_error + | raise the_error_hypothesis_found + | ExceptionGroup: Hypothesis found 2 distinct failures. (2 sub-exceptions) + +-+---------------- 1 ---------------- + | Traceback (most recent call last): + | File "test_pr1582_multiple_failures.py", line NN, in test_multiple_failures + | raise RuntimeError("This number is too small!") + | RuntimeError: This number is too small! + | Falsifying example: test_multiple_failures( + | x=-101, + | ) + | Explanation: + | These lines were always and only run by failing examples: + | test_pr1582_multiple_failures.py:10 + +---------------- 2 ---------------- + | Traceback (most recent call last): + | File "test_pr1582_multiple_failures.py", line NN, in test_multiple_failures + | raise ValueError("This number is too big!") + | ValueError: This number is too big! + | Falsifying example: test_multiple_failures( + | x=101, + | ) + | Explanation: + | These lines were always and only run by failing examples: + | test_pr1582_multiple_failures.py:8 + +------------------------------------ + ============================== 1 failed in T =============================== ''' # --- # name: test_pr1582_simple @@ -311,6 +262,19 @@ =================================== FAILURES =================================== _________________________________ test_simple __________________________________ + the_error_hypothesis_found = AssertionError('assert 0') + + def _reraise_trimmed_error(the_error_hypothesis_found): + """Re-raise the error with its trimmed traceback. + + This is a separate function so that ``wrapped_test``'s + ``__tracebackhide__ = True`` doesn't hide this frame from pytest. + """ + > raise the_error_hypothesis_found + + .../hypothesis/core.py:NN: + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + x = 0 @settings(database=None, derandomize=True) From 3711e6e49ad7b49492c1ffed6ebe253a52061dd0 Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Mon, 6 Apr 2026 12:22:46 +0100 Subject: [PATCH 07/14] Better handling of stack trace elision --- hypothesis-python/src/hypothesis/core.py | 15 +-- .../test_impersonation_investigation.ambr | 95 ++----------------- 2 files changed, 18 insertions(+), 92 deletions(-) diff --git a/hypothesis-python/src/hypothesis/core.py b/hypothesis-python/src/hypothesis/core.py index 9a0e699def..26d97410e2 100644 --- a/hypothesis-python/src/hypothesis/core.py +++ b/hypothesis-python/src/hypothesis/core.py @@ -1619,12 +1619,9 @@ def shortlex_key(error): return result -def _reraise_trimmed_error(the_error_hypothesis_found): - """Re-raise the error with its trimmed traceback. - - This is a separate function so that ``wrapped_test``'s - ``__tracebackhide__ = True`` doesn't hide this frame from pytest. - """ +# Exists solely to insert a line in the traceback where we need to. +def _reraise_exception_group(the_error_hypothesis_found): + __tracebackhide__ = False raise the_error_hypothesis_found @@ -2265,7 +2262,11 @@ def wrapped_test(*arguments, **kwargs): if isinstance(e, BaseExceptionGroup) else get_trimmed_traceback() ) - _reraise_trimmed_error(the_error_hypothesis_found) + if isinstance(e, BaseExceptionGroup): + # Insert a frame here as otherwise all base frames are + # trimmed which causes pytest problems. + _reraise_exception_group(the_error_hypothesis_found) + raise the_error_hypothesis_found if not (ran_explicit_examples or state.ever_executed): raise SKIP_BECAUSE_NO_EXAMPLES diff --git a/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr b/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr index cf5ffd6926..1158a9e12b 100644 --- a/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr +++ b/hypothesis-python/tests/snapshots/__snapshots__/test_impersonation_investigation.ambr @@ -9,19 +9,6 @@ =================================== FAILURES =================================== _____________________________ test_deep_traceback ______________________________ - the_error_hypothesis_found = ValueError('boom') - - def _reraise_trimmed_error(the_error_hypothesis_found): - """Re-raise the error with its trimmed traceback. - - This is a separate function so that ``wrapped_test``'s - ``__tracebackhide__ = True`` doesn't hide this frame from pytest. - """ - > raise the_error_hypothesis_found - - .../hypothesis/core.py:NN: - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - x = 0 @settings(database=None, derandomize=True) @@ -55,27 +42,17 @@ =================================== FAILURES =================================== _____________________ test_healthcheck_traceback_is_hidden _____________________ - the_error_hypothesis_found = FailedHealthCheck('Input generation is slow: Hypothesis only generated N valid inputs after T seconds.\n\n cou...ck.too_slow]). See https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.HealthCheck for details.') - - def _reraise_trimmed_error(the_error_hypothesis_found): - """Re-raise the error with its trimmed traceback. + E hypothesis.errors.FailedHealthCheck: Input generation is slow: Hypothesis only generated N valid inputs after T seconds. - This is a separate function so that ``wrapped_test``'s - ``__tracebackhide__ = True`` doesn't hide this frame from pytest. - """ - > raise the_error_hypothesis_found - E hypothesis.errors.FailedHealthCheck: Input generation is slow: Hypothesis only generated N valid inputs after T seconds. - E - E count | fraction | slowest draws (seconds) + count | fraction | slowest draws (seconds) x | - E - E This could be for a few reasons: - E 1. This strategy could be generating too much data per input. Try decreasing the amount of data generated, for example by decreasing the minimum size of collection strategies like st.lists(). - E 2. Some other expensive computation could be running during input generation. For example, if @st.composite or st.data() is interspersed with an expensive computation, HealthCheck.too_slow is likely to trigger. If this computation is unrelated to input generation, move it elsewhere. Otherwise, try making it more efficient, or disable this health check if that is not possible. - E - E If you expect input generation to take this long, you can disable this health check with @settings(suppress_health_check=[HealthCheck.too_slow]). See https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.HealthCheck for details. - - .../hypothesis/core.py:NN: FailedHealthCheck + + This could be for a few reasons: + 1. This strategy could be generating too much data per input. Try decreasing the amount of data generated, for example by decreasing the minimum size of collection strategies like st.lists(). + 2. Some other expensive computation could be running during input generation. For example, if @st.composite or st.data() is interspersed with an expensive computation, HealthCheck.too_slow is likely to trigger. If this computation is unrelated to input generation, move it elsewhere. Otherwise, try making it more efficient, or disable this health check if that is not possible. + + If you expect input generation to take this long, you can disable this health check with @settings(suppress_health_check=[HealthCheck.too_slow]). See https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.HealthCheck for details. + All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames. ---------------------------------- Hypothesis ---------------------------------- You can reproduce this failure by adding @seed(0) to this test, or by running pytest with --hypothesis-seed=0. ============================== 1 failed in T =============================== @@ -91,19 +68,6 @@ =================================== FAILURES =================================== _____________________________ test_basic_traceback _____________________________ - the_error_hypothesis_found = ZeroDivisionError('division by zero') - - def _reraise_trimmed_error(the_error_hypothesis_found): - """Re-raise the error with its trimmed traceback. - - This is a separate function so that ``wrapped_test``'s - ``__tracebackhide__ = True`` doesn't hide this frame from pytest. - """ - > raise the_error_hypothesis_found - - .../hypothesis/core.py:NN: - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - _ = None @settings(database=None, derandomize=True) @@ -129,19 +93,6 @@ =================================== FAILURES =================================== ____________________________ test_lambda_traceback _____________________________ - the_error_hypothesis_found = ZeroDivisionError('division by zero') - - def _reraise_trimmed_error(the_error_hypothesis_found): - """Re-raise the error with its trimmed traceback. - - This is a separate function so that ``wrapped_test``'s - ``__tracebackhide__ = True`` doesn't hide this frame from pytest. - """ - > raise the_error_hypothesis_found - - .../hypothesis/core.py:NN: - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - _ = None @settings(database=None, derandomize=True) @@ -189,19 +140,6 @@ =================================== FAILURES =================================== _______________________________ test_map_failure _______________________________ - the_error_hypothesis_found = ZeroDivisionError('division by zero') - - def _reraise_trimmed_error(the_error_hypothesis_found): - """Re-raise the error with its trimmed traceback. - - This is a separate function so that ``wrapped_test``'s - ``__tracebackhide__ = True`` doesn't hide this frame from pytest. - """ - > raise the_error_hypothesis_found - - .../hypothesis/core.py:NN: - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - x = 0 > @given(st.integers().map(lambda x: 1/0)) @@ -223,7 +161,7 @@ =================================== FAILURES =================================== ____________________________ test_multiple_failures ____________________________ + Exception Group Traceback (most recent call last): - .../hypothesis/core.py", line NN, in _reraise_trimmed_error + .../hypothesis/core.py", line NN, in _reraise_exception_group | raise the_error_hypothesis_found | ExceptionGroup: Hypothesis found 2 distinct failures. (2 sub-exceptions) +-+---------------- 1 ---------------- @@ -262,19 +200,6 @@ =================================== FAILURES =================================== _________________________________ test_simple __________________________________ - the_error_hypothesis_found = AssertionError('assert 0') - - def _reraise_trimmed_error(the_error_hypothesis_found): - """Re-raise the error with its trimmed traceback. - - This is a separate function so that ``wrapped_test``'s - ``__tracebackhide__ = True`` doesn't hide this frame from pytest. - """ - > raise the_error_hypothesis_found - - .../hypothesis/core.py:NN: - _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - x = 0 @settings(database=None, derandomize=True) From 376ef5e0720e4d83ed8a70ba88190e35a0c14519 Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Mon, 6 Apr 2026 12:34:29 +0100 Subject: [PATCH 08/14] Add release file --- hypothesis-python/RELEASE.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hypothesis-python/RELEASE.rst b/hypothesis-python/RELEASE.rst index 0c23b1ddc8..95cd6c389d 100644 --- a/hypothesis-python/RELEASE.rst +++ b/hypothesis-python/RELEASE.rst @@ -1,4 +1,4 @@ RELEASE_TYPE: patch -This release fixes a bug in explain mode where having [syrupy](https://github.com/syrupy-project/syrupy) installed -as a pytest plugin would cause it to erroneously show up as an explanation for errors. +This release fixes (:issue:`4681`), which would cause line numbers to frequently be wrong in tracebacks for +errors found by Hypothesis. From de47cd6d4d610cad02303c179c724494040fa3da Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Mon, 6 Apr 2026 12:35:12 +0100 Subject: [PATCH 09/14] Fix traceback elision frame count and lint error Update test_traceback_elision to expect 4 frames (not 5) since _reraise_trimmed_error is now only used for exception groups. Fix RET504 lint violation in test_impersonation_investigation.py. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../tests/cover/test_traceback_elision.py | 10 ++++------ .../snapshots/test_impersonation_investigation.py | 3 +-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/hypothesis-python/tests/cover/test_traceback_elision.py b/hypothesis-python/tests/cover/test_traceback_elision.py index 1b9931fed5..f38396e847 100644 --- a/hypothesis-python/tests/cover/test_traceback_elision.py +++ b/hypothesis-python/tests/cover/test_traceback_elision.py @@ -29,11 +29,9 @@ def simplest_failure(x): simplest_failure() except ValueError as e: tb = traceback.extract_tb(e.__traceback__) - # Unless in debug mode, Hypothesis adds 2 frames: - # _reraise_trimmed_error and wrapped_test. - # (5 frames: this one, simplest_failure, _reraise_trimmed_error, - # wrapped_test, raise ValueError) + # Unless in debug mode, Hypothesis adds 1 frame - the least possible! + # (4 frames: this one, simplest_failure, internal frame, raise ValueError) if verbosity < Verbosity.debug and not env_value: - assert len(tb) == 5 + assert len(tb) == 4 else: - assert len(tb) >= 6 + assert len(tb) >= 5 diff --git a/hypothesis-python/tests/snapshots/test_impersonation_investigation.py b/hypothesis-python/tests/snapshots/test_impersonation_investigation.py index 51fb5c6e43..6f24496da6 100644 --- a/hypothesis-python/tests/snapshots/test_impersonation_investigation.py +++ b/hypothesis-python/tests/snapshots/test_impersonation_investigation.py @@ -78,8 +78,7 @@ def replace_lineno(m): output, ) # Normalize test session timing - output = re.sub(r"in [\d.]+s =", "in T =", output) - return output + return re.sub(r"in [\d.]+s =", "in T =", output) def get_failure_output(testdir, test_code, *extra_args): From f8fee4db95fe09baad99b697e0f51601f3bd34ad Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Mon, 6 Apr 2026 13:11:25 +0100 Subject: [PATCH 10/14] Set __wrapped__ in impersonate() for inspect.getsource compatibility Without co_filename impersonation, inspect.getsource() can't find the source of the wrapper function (co_filename is ''). Setting __wrapped__ = target lets inspect.unwrap() follow to the original function, fixing the patching system which uses inspect.getsource(). Also fix traceback elision test frame count (4 not 5) and lint error. Co-Authored-By: Claude Opus 4.6 (1M context) --- hypothesis-python/src/hypothesis/internal/reflection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/reflection.py b/hypothesis-python/src/hypothesis/internal/reflection.py index 2c42396ab4..ab36040f3a 100644 --- a/hypothesis-python/src/hypothesis/internal/reflection.py +++ b/hypothesis-python/src/hypothesis/internal/reflection.py @@ -475,9 +475,9 @@ def accept(f): f.__name__ = target.__name__ f.__module__ = target.__module__ f.__doc__ = target.__doc__ + f.__wrapped__ = target f.__globals__["__hypothesistracebackhide__"] = True - # But leave an breadcrumb for _describe_lambda to follow, it's - # just confused by the lies above + # But leave a breadcrumb for _describe_lambda to follow f.__wrapped_target = target return f From 5d5c4ce84d8c0f56cb17050e55eef854df4e0a82 Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Mon, 6 Apr 2026 13:19:16 +0100 Subject: [PATCH 11/14] Fix patching by following __wrapped_target instead of setting __wrapped__ Setting __wrapped__ on impersonated functions broke inspect.signature, annotations, and other inspect-based introspection throughout the codebase. Instead, use the existing __wrapped_target attribute (already set by impersonate) in the patching code to find the original function for inspect.getsource. Co-Authored-By: Claude Opus 4.6 (1M context) --- hypothesis-python/src/hypothesis/extra/_patching.py | 7 +++++-- hypothesis-python/src/hypothesis/internal/reflection.py | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/hypothesis-python/src/hypothesis/extra/_patching.py b/hypothesis-python/src/hypothesis/extra/_patching.py index 3dba3f7b5c..c60173c766 100644 --- a/hypothesis-python/src/hypothesis/extra/_patching.py +++ b/hypothesis-python/src/hypothesis/extra/_patching.py @@ -183,7 +183,7 @@ def get_patch_for( if patch is None: return None - (before, after) = patch + before, after = patch return (str(fname), before, after) @@ -196,8 +196,11 @@ def _get_patch_for( strip_via: tuple[str, ...] = (), namespace: dict[str, Any], ) -> tuple[str, str] | None: + # Follow __wrapped_target to the original function, since the wrapper's + # co_filename may not point to the real source file (see #4681). + source_func = getattr(func, "__wrapped_target", func) try: - before = inspect.getsource(func) + before = inspect.getsource(source_func) except Exception: # pragma: no cover return None diff --git a/hypothesis-python/src/hypothesis/internal/reflection.py b/hypothesis-python/src/hypothesis/internal/reflection.py index ab36040f3a..9936a06141 100644 --- a/hypothesis-python/src/hypothesis/internal/reflection.py +++ b/hypothesis-python/src/hypothesis/internal/reflection.py @@ -475,9 +475,8 @@ def accept(f): f.__name__ = target.__name__ f.__module__ = target.__module__ f.__doc__ = target.__doc__ - f.__wrapped__ = target f.__globals__["__hypothesistracebackhide__"] = True - # But leave a breadcrumb for _describe_lambda to follow + # Leave a breadcrumb for _describe_lambda and _get_patch_for to follow f.__wrapped_target = target return f From 30422cb95839c650b8c710ef655356b12fd8bf9e Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Wed, 8 Apr 2026 10:03:39 +0100 Subject: [PATCH 12/14] Remove spurious comment --- hypothesis-python/src/hypothesis/internal/reflection.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/hypothesis-python/src/hypothesis/internal/reflection.py b/hypothesis-python/src/hypothesis/internal/reflection.py index 9936a06141..8b7a2edc8e 100644 --- a/hypothesis-python/src/hypothesis/internal/reflection.py +++ b/hypothesis-python/src/hypothesis/internal/reflection.py @@ -469,9 +469,6 @@ def impersonate(target): """ def accept(f): - # Hide hypothesis internals from tracebacks via pytest's mechanism. - # Previously this replaced co_filename/co_firstlineno on the code object, - # but that caused incorrect source lines in tracebacks (see #4681). f.__name__ = target.__name__ f.__module__ = target.__module__ f.__doc__ = target.__doc__ From f090b6d3e4af010c744aed92466a20171d782372 Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Wed, 8 Apr 2026 10:05:22 +0100 Subject: [PATCH 13/14] Clean up some more comments --- hypothesis-python/src/hypothesis/core.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hypothesis-python/src/hypothesis/core.py b/hypothesis-python/src/hypothesis/core.py index 26d97410e2..3cec37923f 100644 --- a/hypothesis-python/src/hypothesis/core.py +++ b/hypothesis-python/src/hypothesis/core.py @@ -2254,9 +2254,7 @@ def wrapped_test(*arguments, **kwargs): f"{pytest_extra_msg}." ) report(msg) - # Trim the traceback to remove hypothesis internals, - # then re-raise from a function without __tracebackhide__ - # so this frame is visible in pytest output. + # Trim the traceback to remove hypothesis internals the_error_hypothesis_found = e.with_traceback( None if isinstance(e, BaseExceptionGroup) @@ -2264,7 +2262,7 @@ def wrapped_test(*arguments, **kwargs): ) if isinstance(e, BaseExceptionGroup): # Insert a frame here as otherwise all base frames are - # trimmed which causes pytest problems. + # trimmed which causes pytest problems _reraise_exception_group(the_error_hypothesis_found) raise the_error_hypothesis_found From acc81da0fe9c4c71973ad4afbe1d0bfdc3482609 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 12 Apr 2026 21:59:03 -0400 Subject: [PATCH 14/14] remove duplicate test dir --- hypothesis-python/tests/snapshot/__init__.py | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 hypothesis-python/tests/snapshot/__init__.py diff --git a/hypothesis-python/tests/snapshot/__init__.py b/hypothesis-python/tests/snapshot/__init__.py deleted file mode 100644 index fcb1ac6538..0000000000 --- a/hypothesis-python/tests/snapshot/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# 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/.