Skip to content

Commit 5a6e090

Browse files
committed
test: force VirtualenvTest onto one worker to save time
VirtualenvTest is slow because it has a session-scoped fixture that takes 10s to run. If all of those tests go to the same worker, we can reuse that fixture. If they go to different workers, we have to spend that time more than once. This is a hack which seems to work, but no guarantees into the future. Also, I don't know why the VirtualenvTests aren't run first on their worker. Time saved: about 10%, from ~50s to ~45s.
1 parent 75cd551 commit 5a6e090

File tree

1 file changed

+50
-7
lines changed

1 file changed

+50
-7
lines changed

tests/conftest.py

+50-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
This module is run automatically by pytest, to define and enable fixtures.
88
"""
99

10+
import itertools
1011
import os
1112
import sys
1213
import warnings
@@ -71,16 +72,58 @@ def reset_sys_path():
7172
sys.path[:] = sys_path
7273

7374

75+
TRACK_TESTS = False
76+
TEST_TXT = "/tmp/tests.txt"
77+
78+
def pytest_sessionstart():
79+
"""Run once at the start of the test session."""
80+
if TRACK_TESTS: # pragma: debugging
81+
with open(TEST_TXT, "w") as testtxt:
82+
print("Starting:", file=testtxt)
83+
84+
85+
def write_test_name(prefix):
86+
"""For tracking where and when tests are running."""
87+
if TRACK_TESTS: # pragma: debugging
88+
with open(TEST_TXT, "a") as testtxt:
89+
worker = os.environ.get('PYTEST_XDIST_WORKER', 'none')
90+
test = os.environ.get("PYTEST_CURRENT_TEST", "unknown")
91+
print(f"{prefix} {worker}: {test}", file=testtxt, flush=True)
92+
93+
7494
@pytest.hookimpl(hookwrapper=True)
7595
def pytest_runtest_call(item):
76-
"""Convert StopEverything into skipped tests."""
96+
"""Run once for each test."""
97+
write_test_name(">")
98+
99+
# Convert StopEverything into skipped tests.
77100
outcome = yield
78101
if outcome.excinfo and issubclass(outcome.excinfo[0], StopEverything): # pragma: only jython
79102
pytest.skip(f"Skipping {item.nodeid} for StopEverything: {outcome.excinfo[1]}")
80103

81-
# For diagnosing test running:
82-
if 0:
83-
with open("/tmp/tests.txt", "a") as proctxt:
84-
worker = os.environ.get('PYTEST_XDIST_WORKER', 'none')
85-
test = os.environ.get("PYTEST_CURRENT_TEST", "unknown")
86-
print(f"{worker}: {test}", file=proctxt, flush=True)
104+
write_test_name("<")
105+
106+
107+
def interleaved(firsts, rest, n):
108+
"""Interleave the firsts among the rest so that they occur each n items."""
109+
num = sum(len(l) for l in firsts) + len(rest)
110+
lists = firsts + [rest] * (n - len(firsts))
111+
listcycle = itertools.cycle(lists)
112+
113+
while num:
114+
alist = next(listcycle) # pylint: disable=stop-iteration-return
115+
if alist:
116+
yield alist.pop()
117+
num -= 1
118+
119+
def pytest_collection_modifyitems(items):
120+
"""Re-order the collected tests."""
121+
# Trick the xdist scheduler to put all of the VirtualenvTest tests on the
122+
# same worker by sprinkling them into the collected items every Nth place.
123+
virt = set(i for i in items if "VirtualenvTest" in i.nodeid)
124+
rest = [i for i in items if i not in virt]
125+
nworkers = int(os.environ.get("PYTEST_XDIST_WORKER_COUNT", 4))
126+
items[:] = interleaved([virt], rest, nworkers)
127+
if TRACK_TESTS: # pragma: debugging
128+
with open("/tmp/items.txt", "w") as f:
129+
print("\n".join(i.nodeid for i in items), file=f)

0 commit comments

Comments
 (0)