Skip to content

Commit cb9b861

Browse files
authored
Move from MD5 to CRC32 for hashing test IDs (#688)
Fixes #686.
1 parent bd697bd commit cb9b861

File tree

3 files changed

+58
-49
lines changed

3 files changed

+58
-49
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ Unreleased
1414
`PR #687 <https://github.com/pytest-dev/pytest-randomly/issues/687>`__.
1515
Thanks to Bryce Drennan for the suggestion in `Issue #600 <https://github.com/pytest-dev/pytest-randomly/issues/600>`__ and initial implementation in `PR #617 <https://github.com/pytest-dev/pytest-randomly/pull/617>`__.
1616

17+
* Move from MD5 to CRC32 for hashing test IDs, as it’s 5x faster and we don’t need cryptographic security.
18+
19+
`Issue #686 <https://github.com/pytest-dev/pytest-randomly/issues/686>`__.
20+
1721
3.16.0 (2024-10-25)
1822
-------------------
1923

src/pytest_randomly/__init__.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from itertools import groupby
99
from types import ModuleType
1010
from typing import Any, Callable, TypeVar
11+
from zlib import crc32
1112

1213
from _pytest.config import Config
1314
from _pytest.config.argparsing import Parser
@@ -198,17 +199,17 @@ def pytest_report_header(config: Config) -> str:
198199

199200
def pytest_runtest_setup(item: Item) -> None:
200201
if item.config.getoption("randomly_reset_seed"):
201-
_reseed(item.config, int.from_bytes(_md5(item.nodeid), "big") - 1)
202+
_reseed(item.config, _crc32(item.nodeid) - 1)
202203

203204

204205
def pytest_runtest_call(item: Item) -> None:
205206
if item.config.getoption("randomly_reset_seed"):
206-
_reseed(item.config, int.from_bytes(_md5(item.nodeid), "big"))
207+
_reseed(item.config, _crc32(item.nodeid))
207208

208209

209210
def pytest_runtest_teardown(item: Item) -> None:
210211
if item.config.getoption("randomly_reset_seed"):
211-
_reseed(item.config, int.from_bytes(_md5(item.nodeid), "big") + 1)
212+
_reseed(item.config, _crc32(item.nodeid) + 1)
212213

213214

214215
@hookimpl(tryfirst=True)
@@ -227,11 +228,11 @@ def pytest_collection_modifyitems(config: Config, items: list[Item]) -> None:
227228
)
228229
)
229230

230-
def _module_key(module_item: tuple[ModuleType | None, list[Item]]) -> bytes:
231+
def _module_key(module_item: tuple[ModuleType | None, list[Item]]) -> int:
231232
module, _items = module_item
232233
if module is None:
233-
return _md5(f"{seed}::None")
234-
return _md5(f"{seed}::{module.__name__}")
234+
return _crc32(f"{seed}::None")
235+
return _crc32(f"{seed}::{module.__name__}")
235236

236237
modules_items.sort(key=_module_key)
237238

@@ -248,19 +249,19 @@ def _get_module(item: Item) -> ModuleType | None:
248249
def _shuffle_by_class(items: list[Item], seed: int) -> list[Item]:
249250
klasses_items: list[tuple[type[Any] | None, list[Item]]] = []
250251

251-
def _item_key(item: Item) -> bytes:
252-
return _md5(f"{seed}::{item.nodeid}")
252+
def _item_key(item: Item) -> int:
253+
return _crc32(f"{seed}::{item.nodeid}")
253254

254255
for klass, group in groupby(items, _get_cls):
255256
klass_items = list(group)
256257
klass_items.sort(key=_item_key)
257258
klasses_items.append((klass, klass_items))
258259

259-
def _cls_key(klass_items: tuple[type[Any] | None, list[Item]]) -> bytes:
260+
def _cls_key(klass_items: tuple[type[Any] | None, list[Item]]) -> int:
260261
klass, items = klass_items
261262
if klass is None:
262-
return _md5(f"{seed}::None")
263-
return _md5(f"{seed}::{klass.__module__}.{klass.__qualname__}")
263+
return _crc32(f"{seed}::None")
264+
return _crc32(f"{seed}::{klass.__module__}.{klass.__qualname__}")
264265

265266
klasses_items.sort(key=_cls_key)
266267

@@ -282,19 +283,15 @@ def reduce_list_of_lists(lists: list[list[T]]) -> list[T]:
282283

283284

284285
@lru_cache
285-
def _md5(string: str) -> bytes:
286-
hasher = hashlib.md5(usedforsecurity=False)
287-
hasher.update(string.encode())
288-
return hasher.digest()
286+
def _crc32(string: str) -> int:
287+
return crc32(string.encode())
289288

290289

291290
if have_faker: # pragma: no branch
292291

293292
@fixture(autouse=True)
294293
def faker_seed(pytestconfig: Config, request: SubRequest) -> int:
295-
print(type(request))
296-
result: int = pytestconfig.getoption("randomly_seed") + int.from_bytes(
297-
_md5(request.node.nodeid),
298-
"big",
294+
result: int = pytestconfig.getoption("randomly_seed") + _crc32(
295+
request.node.nodeid
299296
)
300297
return result

tests/test_pytest_randomly.py

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,10 @@ def test_it():
248248

249249
out.assert_outcomes(passed=4, failed=0)
250250
assert out.outlines[9:13] == [
251+
"test_c.py::test_it PASSED",
251252
"test_b.py::test_it PASSED",
252-
"test_a.py::test_it PASSED",
253253
"test_d.py::test_it PASSED",
254-
"test_c.py::test_it PASSED",
254+
"test_a.py::test_it PASSED",
255255
]
256256

257257

@@ -268,10 +268,10 @@ def test_it():
268268

269269
out.assert_outcomes(passed=4, failed=0)
270270
assert out.outlines[9:13] == [
271+
"test_c.py::test_it PASSED",
271272
"test_b.py::test_it PASSED",
272-
"test_a.py::test_it PASSED",
273273
"test_d.py::test_it PASSED",
274-
"test_c.py::test_it PASSED",
274+
"test_a.py::test_it PASSED",
275275
]
276276

277277

@@ -308,9 +308,9 @@ def test_d(self):
308308
out.assert_outcomes(passed=4, failed=0)
309309
assert out.outlines[9:13] == [
310310
"test_one.py::D::test_d PASSED",
311-
"test_one.py::B::test_b PASSED",
312-
"test_one.py::C::test_c PASSED",
313311
"test_one.py::A::test_a PASSED",
312+
"test_one.py::C::test_c PASSED",
313+
"test_one.py::B::test_b PASSED",
314314
]
315315

316316

@@ -341,8 +341,8 @@ def test_d(self):
341341
assert out.outlines[9:13] == [
342342
"test_one.py::T::test_c PASSED",
343343
"test_one.py::T::test_b PASSED",
344-
"test_one.py::T::test_a PASSED",
345344
"test_one.py::T::test_d PASSED",
345+
"test_one.py::T::test_a PASSED",
346346
]
347347

348348

@@ -368,10 +368,10 @@ def test_d():
368368

369369
out.assert_outcomes(passed=4, failed=0)
370370
assert out.outlines[9:13] == [
371-
"test_one.py::test_c PASSED",
371+
"test_one.py::test_d PASSED",
372372
"test_one.py::test_a PASSED",
373+
"test_one.py::test_c PASSED",
373374
"test_one.py::test_b PASSED",
374-
"test_one.py::test_d PASSED",
375375
]
376376

377377

@@ -402,10 +402,10 @@ def test_d():
402402

403403
out.assert_outcomes(passed=4, failed=0)
404404
assert out.outlines[9:13] == [
405-
"test_one.py::test_c PASSED",
405+
"test_one.py::test_d PASSED",
406406
"test_one.py::test_a PASSED",
407+
"test_one.py::test_c PASSED",
407408
"test_one.py::test_b PASSED",
408-
"test_one.py::test_d PASSED",
409409
]
410410

411411

@@ -528,7 +528,15 @@ def test_b():
528528
assert 0
529529
"""
530530
)
531-
out = ourtester.runpytest("-v", "--randomly-seed=1", "--stepwise")
531+
out = ourtester.runpytest("-v", "--randomly-seed=8")
532+
out.assert_outcomes(failed=2)
533+
# Ensure test_b runs first
534+
assert out.outlines[9:11] == [
535+
"test_one.py::test_b FAILED",
536+
"test_one.py::test_a FAILED",
537+
]
538+
539+
out = ourtester.runpytest("--randomly-seed=8", "--stepwise")
532540
out.assert_outcomes(failed=1)
533541

534542
# Now make test_b pass
@@ -543,9 +551,9 @@ def test_b():
543551
"""
544552
)
545553
shutil.rmtree(ourtester.path / "__pycache__")
546-
out = ourtester.runpytest("-v", "--randomly-seed=1", "--stepwise")
554+
out = ourtester.runpytest("--randomly-seed=8", "--stepwise")
547555
out.assert_outcomes(passed=1, failed=1)
548-
out = ourtester.runpytest("-v", "--randomly-seed=1", "--stepwise")
556+
out = ourtester.runpytest("--randomly-seed=8", "--stepwise")
549557
out.assert_outcomes(failed=1)
550558

551559

@@ -579,11 +587,11 @@ def test_factory_boy(ourtester):
579587
from factory.random import randgen
580588
581589
def test_a():
582-
assert randgen.random() == 0.9988532989147809
590+
assert randgen.random() == 0.17867277194477893
583591
584592
585593
def test_b():
586-
assert randgen.random() == 0.18032546798434612
594+
assert randgen.random() == 0.8026272812225962
587595
"""
588596
)
589597

@@ -599,10 +607,10 @@ def test_faker(ourtester):
599607
fake = Faker()
600608
601609
def test_one():
602-
assert fake.name() == 'Mrs. Lisa Ryan'
610+
assert fake.name() == 'Kimberly Powell'
603611
604612
def test_two():
605-
assert fake.name() == 'Kaitlyn Mitchell'
613+
assert fake.name() == 'Thomas Moyer PhD'
606614
"""
607615
)
608616

@@ -614,10 +622,10 @@ def test_faker_fixture(ourtester):
614622
ourtester.makepyfile(
615623
test_one="""
616624
def test_one(faker):
617-
assert faker.name() == 'Mrs. Lisa Ryan'
625+
assert faker.name() == 'Kimberly Powell'
618626
619627
def test_two(faker):
620-
assert faker.name() == 'Kaitlyn Mitchell'
628+
assert faker.name() == 'Thomas Moyer PhD'
621629
"""
622630
)
623631

@@ -634,10 +642,10 @@ def test_model_bakery(ourtester):
634642
from model_bakery.random_gen import gen_slug
635643
636644
def test_a():
637-
assert gen_slug(10) == 'XjpU5br7ej'
645+
assert gen_slug(10) == 'whwhAKeQYE'
638646
639647
def test_b():
640-
assert gen_slug(10) == 'xJHS-PD_WT'
648+
assert gen_slug(10) == 'o2N4p5UAXd'
641649
"""
642650
)
643651

@@ -651,10 +659,10 @@ def test_numpy(ourtester):
651659
import numpy as np
652660
653661
def test_one():
654-
assert np.random.rand() == 0.36687834264514585
662+
assert np.random.rand() == 0.1610140063074521
655663
656664
def test_two():
657-
assert np.random.rand() == 0.7050715833365834
665+
assert np.random.rand() == 0.6896867238957805
658666
"""
659667
)
660668

@@ -718,19 +726,19 @@ def fake_entry_points(*, group):
718726
assert reseed.mock_calls == [
719727
mock.call(1),
720728
mock.call(1),
721-
mock.call(116362448262735926321257785636175308268),
722-
mock.call(116362448262735926321257785636175308269),
723-
mock.call(116362448262735926321257785636175308270),
729+
mock.call(2964001072),
730+
mock.call(2964001073),
731+
mock.call(2964001074),
724732
]
725733

726734
reseed.mock_calls[:] = []
727735
pytester.runpytest_inprocess("--randomly-seed=424242")
728736
assert reseed.mock_calls == [
729737
mock.call(424242),
730738
mock.call(424242),
731-
mock.call(116362448262735926321257785636175732509),
732-
mock.call(116362448262735926321257785636175732510),
733-
mock.call(116362448262735926321257785636175732511),
739+
mock.call(2964425313),
740+
mock.call(2964425314),
741+
mock.call(2964425315),
734742
]
735743

736744

0 commit comments

Comments
 (0)