Skip to content

Commit 6684a23

Browse files
authored
Make flaky test reruns draw fresh random values (#494)
pytest-randomly reseeds the global RNG at the start of every test call phase, including reruns triggered by the flaky plugin. A randomized test that fails for the session seed therefore fails identically on all retries, making @flaky.flaky useless and causing spurious CI failures. Add a fresh_seed_each_run decorator that mixes the rerun attempt number into the seed inside the test call (a fixture cannot do this, since pytest-randomly reseeds after fixture setup), apply it to all flaky tests, and add retries to the scaling-invariance test which had none.
1 parent 567a1b4 commit 6684a23

4 files changed

Lines changed: 47 additions & 0 deletions

File tree

adaptive/tests/flaky_utils.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""Make ``flaky`` reruns work with ``pytest-randomly``."""
2+
3+
import functools as ft
4+
import random
5+
from collections import Counter
6+
7+
import numpy as np
8+
9+
_attempts: Counter = Counter()
10+
11+
12+
def fresh_seed_each_run(func):
13+
"""Make ``@flaky.flaky`` reruns draw new random values.
14+
15+
``pytest-randomly`` reseeds the global RNG at the start of every test
16+
call phase — including reruns triggered by the ``flaky`` plugin — so a
17+
randomized test that fails for the session seed fails identically on
18+
every rerun, making the retries useless. Reseeding cannot happen in a
19+
fixture (``pytest-randomly`` reseeds in ``pytest_runtest_call``, after
20+
fixture setup), so this mixes the attempt number into the seed at the
21+
start of the test call itself. Each rerun gets new draws while the
22+
whole sequence stays reproducible via ``--randomly-seed``.
23+
24+
Apply directly on the test function, below ``@flaky.flaky`` and any
25+
parametrization.
26+
"""
27+
28+
@ft.wraps(func)
29+
def wrapper(*args, **kwargs):
30+
key = (func.__qualname__, repr(args), repr(kwargs))
31+
attempt = _attempts[key]
32+
_attempts[key] += 1
33+
if attempt:
34+
seed = (random.getrandbits(32) + attempt) % 2**32
35+
random.seed(seed)
36+
np.random.seed(seed)
37+
return func(*args, **kwargs)
38+
39+
return wrapper

adaptive/tests/test_average_learner.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from adaptive.learner import AverageLearner
77
from adaptive.runner import simple
8+
from adaptive.tests.flaky_utils import fresh_seed_each_run
89

910

1011
def f_unused(seed):
@@ -28,6 +29,7 @@ def test_only_returns_new_points():
2829

2930

3031
@flaky.flaky(max_runs=5)
32+
@fresh_seed_each_run
3133
def test_avg_std_and_npoints():
3234
learner = AverageLearner(f_unused, atol=None, rtol=0.01)
3335

adaptive/tests/test_learner1d.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from adaptive.learner import Learner1D
1010
from adaptive.learner.learner1D import curvature_loss_function
1111
from adaptive.runner import BlockingRunner, simple
12+
from adaptive.tests.flaky_utils import fresh_seed_each_run
1213

1314

1415
def flat_middle(x):
@@ -259,6 +260,7 @@ def test_ask_does_not_return_known_points_when_returning_bounds():
259260

260261

261262
@flaky.flaky(max_runs=3)
263+
@fresh_seed_each_run
262264
def test_tell_many():
263265
def f(x, offset=0.123214):
264266
a = 0.01

adaptive/tests/test_learners.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
)
3030
from adaptive.learner.learner1D import with_pandas
3131
from adaptive.runner import simple
32+
from adaptive.tests.flaky_utils import fresh_seed_each_run
3233

3334
LOSS_FUNCTIONS = {
3435
Learner1D: (
@@ -514,7 +515,9 @@ def test_expected_loss_improvement_is_less_than_total_loss(
514515

515516
# XXX: This *should* pass (https://github.com/python-adaptive/adaptive/issues/55)
516517
# but we xfail it now, as Learner2D will be deprecated anyway
518+
@flaky.flaky(max_runs=5)
517519
@run_with(Learner1D, xfail(Learner2D), LearnerND, AverageLearner1D)
520+
@fresh_seed_each_run
518521
def test_learner_performance_is_invariant_under_scaling(
519522
learner_type, f, learner_kwargs
520523
):
@@ -583,6 +586,7 @@ def scale_x(x):
583586
SequenceLearner,
584587
with_all_loss_functions=False,
585588
)
589+
@fresh_seed_each_run
586590
def test_balancing_learner(learner_type, f, learner_kwargs):
587591
"""Test if the BalancingLearner works with the different types of learners."""
588592
learners = [

0 commit comments

Comments
 (0)