Skip to content

Commit be8183c

Browse files
committed
better cost attribution
1 parent fefce87 commit be8183c

3 files changed

Lines changed: 90 additions & 0 deletions

File tree

hypothesis-python/RELEASE.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
RELEASE_TYPE: patch
2+
3+
This patch teaches our pytest plugin to :ref:` find interesting constants <v6.131.1>`
4+
when pytest is collecting tests, to avoid arbitrarily attributing the latency
5+
to whichever test function happened to be executed first (:issue:`4627`).

hypothesis-python/src/_hypothesis_pytestplugin.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,9 +427,21 @@ def pytest_collection_modifyitems(items):
427427

428428
from hypothesis import is_hypothesis_test
429429

430+
has_hypothesis_tests = False
430431
for item in items:
431432
if isinstance(item, pytest.Function) and is_hypothesis_test(item.obj):
432433
item.add_marker("hypothesis")
434+
has_hypothesis_tests = True
435+
436+
if has_hypothesis_tests:
437+
# Collect local constants now, during test collection, so that this
438+
# time is not attributed to whatever test happens to run first.
439+
# (see https://github.com/HypothesisWorks/hypothesis/issues/4627)
440+
# We might miss weird wrapped uses (if there are no other PBTs!),
441+
# in order to avoid the latency from inefficient 3rd party plugins.
442+
from hypothesis.internal.conjecture.providers import _get_local_constants
443+
444+
_get_local_constants()
433445

434446
def pytest_sessionstart(session):
435447
# Note: may be called multiple times, so we can go negative
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# This file is part of Hypothesis, which may be found at
2+
# https://github.com/HypothesisWorks/hypothesis/
3+
#
4+
# Copyright the Hypothesis Authors.
5+
# Individual contributors are listed in AUTHORS.rst and the git log.
6+
#
7+
# This Source Code Form is subject to the terms of the Mozilla Public License,
8+
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
9+
# obtain one at https://mozilla.org/MPL/2.0/.
10+
11+
import re
12+
13+
import pytest
14+
15+
pytest_plugins = "pytester"
16+
17+
18+
CONFTEST = """
19+
import time
20+
import hypothesis.internal.conjecture.providers as providers
21+
22+
_called = False
23+
24+
def slow_get_local_constants():
25+
global _called
26+
if not _called:
27+
_called = True
28+
time.sleep(0.1)
29+
return providers._local_constants
30+
31+
providers._get_local_constants = slow_get_local_constants
32+
providers._sys_modules_len = None
33+
"""
34+
35+
36+
TESTSUITE = """
37+
from hypothesis import given, Phase, settings
38+
from hypothesis.strategies import integers
39+
from hypothesis.internal.conjecture.providers import _get_local_constants
40+
41+
def test_first():
42+
# Force constant collection to happen during this test
43+
_get_local_constants()
44+
45+
@given(integers())
46+
@settings(phases=[Phase.generate], max_examples=1, database=None)
47+
def test_second(x):
48+
pass
49+
"""
50+
51+
52+
@pytest.mark.parametrize("plugin_disabled", [False, True])
53+
def test_constant_collection_timing(testdir, plugin_disabled):
54+
# See https://github.com/HypothesisWorks/hypothesis/issues/4627
55+
testdir.makeconftest(CONFTEST)
56+
testdir.makepyfile(TESTSUITE)
57+
58+
args = ["--durations=0", "-vv"]
59+
if plugin_disabled:
60+
args += ["-p", "no:hypothesispytest"]
61+
62+
result = testdir.runpytest(*args)
63+
result.assert_outcomes(passed=2)
64+
65+
output = "\n".join(result.stdout.lines)
66+
match = re.search(r"([\d.]+)s call\s+\S+::test_first", output)
67+
assert match, f"Could not find test_first timing in:\n{output}"
68+
test_first_time = float(match.group(1))
69+
70+
if plugin_disabled:
71+
assert test_first_time >= 0.05, f"took {test_first_time}s, expected >= 0.05s"
72+
else:
73+
assert test_first_time < 0.05, f"took {test_first_time}s, expected < 0.05s"

0 commit comments

Comments
 (0)