Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add the option --randomly-seed-per-test to use a different seed for each test #617

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -2,6 +2,10 @@
Changelog
=========

* Add the option ``--randomly-seed-per-test`` to use a different seed for each test.

Resolves `Issue #600 <https://github.com/pytest-dev/pytest-randomly/issues/600>`__

3.15.0 (2023-08-15)
-------------------

Expand Down
3 changes: 3 additions & 0 deletions README.rst
Expand Up @@ -159,6 +159,9 @@ You can disable behaviours you don't like with the following flags:
* ``--randomly-dont-reset-seed`` - turn off the reset of ``random.seed()`` at
the start of every test
* ``--randomly-dont-reorganize`` - turn off the shuffling of the order of tests
* ``--randomly-dont-seed-per-test`` - turn off each test having a unique seed.
Each test will be seeded with the same seed.


The plugin appears to Pytest with the name 'randomly'. To disable it
altogether, you can use the ``-p`` argument, for example:
Expand Down
19 changes: 18 additions & 1 deletion src/pytest_randomly/__init__.py
Expand Up @@ -111,6 +111,15 @@ def pytest_addoption(parser: Parser) -> None:
default=True,
help="Stop pytest-randomly from randomly reorganizing the test order.",
)
group._addoption(
"--randomly-dont-seed-per-test",
action="store_false",
dest="randomly_seed_per_test",
default=True,
help="""Use a different seed for each test. Can be helpful for getting
different random data in each test, but still having reproducible
tests. Default behaviour: False.""",
)


def pytest_configure(config: Config) -> None:
Expand Down Expand Up @@ -209,9 +218,17 @@ def pytest_runtest_setup(item: Item) -> None:
_reseed(item.config, -1)


def seed_from_string(string: str) -> int:
return int(hashlib.md5(string.encode()).hexdigest(), 16)


def pytest_runtest_call(item: Item) -> None:
if item.config.getoption("randomly_reset_seed"):
_reseed(item.config)
if item.config.getoption("randomly_seed_per_test"):
test_offset = seed_from_string(item.nodeid) + 100
else:
test_offset = 0
_reseed(item.config, offset=test_offset)


def pytest_runtest_teardown(item: Item) -> None:
Expand Down
48 changes: 38 additions & 10 deletions tests/test_pytest_randomly.py
Expand Up @@ -78,7 +78,33 @@ def test_b():
assert test_b.num == test_a.num
"""
)
out = ourtester.runpytest("--randomly-dont-reorganize")
out = ourtester.runpytest(
"--randomly-dont-reorganize", "--randomly-dont-seed-per-test"
)
out.assert_outcomes(passed=2, failed=0)


def test_it_uses_different_random_seed_per_test(ourtester):
"""
Run a pair of tests that generate a number and assert they produce
different numbers.
"""
ourtester.makepyfile(
test_one="""
import random

def test_a():
test_a.num = random.random()
if hasattr(test_b, 'num'):
assert test_a.num != test_b.num

def test_b():
test_b.num = random.random()
if hasattr(test_a, 'num'):
assert test_b.num != test_a.num
"""
)
out = ourtester.runpytest()
out.assert_outcomes(passed=2, failed=0)


Expand Down Expand Up @@ -601,7 +627,7 @@ def test_two(myfixture):
assert random.getstate() == state_at_seed_two
"""
)
args = ["--randomly-seed=2"]
args = ["--randomly-seed=2", "--randomly-dont-seed-per-test"]

out = ourtester.runpytest(*args)
out.assert_outcomes(passed=2)
Expand Down Expand Up @@ -633,7 +659,7 @@ def test_b():
"""
)

out = ourtester.runpytest("--randomly-seed=1")
out = ourtester.runpytest("--randomly-seed=1", "--randomly-dont-seed-per-test")
out.assert_outcomes(passed=2)


Expand All @@ -645,10 +671,10 @@ def test_faker(ourtester):
fake = Faker()

def test_one():
assert fake.name() == 'Ryan Gallagher'
assert fake.name() == 'Justin Richard'

def test_two():
assert fake.name() == 'Ryan Gallagher'
assert fake.name() == 'Tiffany Williams'
"""
)

Expand Down Expand Up @@ -692,7 +718,7 @@ def test_b():
"""
)

out = ourtester.runpytest("--randomly-seed=1")
out = ourtester.runpytest("--randomly-seed=1", "--randomly-dont-seed-per-test")
out.assert_outcomes(passed=2)


Expand All @@ -702,10 +728,10 @@ def test_numpy(ourtester):
import numpy as np

def test_one():
assert np.random.rand() == 0.417022004702574
assert np.random.rand() == 0.46479378116435255

def test_two():
assert np.random.rand() == 0.417022004702574
assert np.random.rand() == 0.6413112443155088
"""
)

Expand Down Expand Up @@ -765,7 +791,7 @@ def fake_entry_points(*, group):
entry_points.append(_FakeEntryPoint("test_seeder", reseed))

# Need to run in-process so that monkeypatching works
pytester.runpytest_inprocess("--randomly-seed=1")
pytester.runpytest_inprocess("--randomly-seed=1", "--randomly-dont-seed-per-test")
assert reseed.mock_calls == [
mock.call(1),
mock.call(1),
Expand All @@ -775,7 +801,9 @@ def fake_entry_points(*, group):
]

reseed.mock_calls[:] = []
pytester.runpytest_inprocess("--randomly-seed=424242")
pytester.runpytest_inprocess(
"--randomly-seed=424242", "--randomly-dont-seed-per-test"
)
assert reseed.mock_calls == [
mock.call(424242),
mock.call(424242),
Expand Down