summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2021-11-02 06:30:56 -0400
committerNed Batchelder <ned@nedbatchelder.com>2021-11-02 06:57:26 -0400
commit5a6e090cf98e65050c372e0e4a58d42b8ced54f6 (patch)
treea1719691f81cc47a743c7995c3461538a5551a4f
parent75cd551a5200c97a08029abb5ad4c6d62141a328 (diff)
downloadpython-coveragepy-git-5a6e090cf98e65050c372e0e4a58d42b8ced54f6.tar.gz
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.
-rw-r--r--tests/conftest.py57
1 files changed, 50 insertions, 7 deletions
diff --git a/tests/conftest.py b/tests/conftest.py
index cef008c8..16c2c162 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,6 +7,7 @@ Pytest auto configuration.
This module is run automatically by pytest, to define and enable fixtures.
"""
+import itertools
import os
import sys
import warnings
@@ -71,16 +72,58 @@ def reset_sys_path():
sys.path[:] = sys_path
+TRACK_TESTS = False
+TEST_TXT = "/tmp/tests.txt"
+
+def pytest_sessionstart():
+ """Run once at the start of the test session."""
+ if TRACK_TESTS: # pragma: debugging
+ with open(TEST_TXT, "w") as testtxt:
+ print("Starting:", file=testtxt)
+
+
+def write_test_name(prefix):
+ """For tracking where and when tests are running."""
+ if TRACK_TESTS: # pragma: debugging
+ with open(TEST_TXT, "a") as testtxt:
+ worker = os.environ.get('PYTEST_XDIST_WORKER', 'none')
+ test = os.environ.get("PYTEST_CURRENT_TEST", "unknown")
+ print(f"{prefix} {worker}: {test}", file=testtxt, flush=True)
+
+
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item):
- """Convert StopEverything into skipped tests."""
+ """Run once for each test."""
+ write_test_name(">")
+
+ # Convert StopEverything into skipped tests.
outcome = yield
if outcome.excinfo and issubclass(outcome.excinfo[0], StopEverything): # pragma: only jython
pytest.skip(f"Skipping {item.nodeid} for StopEverything: {outcome.excinfo[1]}")
- # For diagnosing test running:
- if 0:
- with open("/tmp/tests.txt", "a") as proctxt:
- worker = os.environ.get('PYTEST_XDIST_WORKER', 'none')
- test = os.environ.get("PYTEST_CURRENT_TEST", "unknown")
- print(f"{worker}: {test}", file=proctxt, flush=True)
+ write_test_name("<")
+
+
+def interleaved(firsts, rest, n):
+ """Interleave the firsts among the rest so that they occur each n items."""
+ num = sum(len(l) for l in firsts) + len(rest)
+ lists = firsts + [rest] * (n - len(firsts))
+ listcycle = itertools.cycle(lists)
+
+ while num:
+ alist = next(listcycle) # pylint: disable=stop-iteration-return
+ if alist:
+ yield alist.pop()
+ num -= 1
+
+def pytest_collection_modifyitems(items):
+ """Re-order the collected tests."""
+ # Trick the xdist scheduler to put all of the VirtualenvTest tests on the
+ # same worker by sprinkling them into the collected items every Nth place.
+ virt = set(i for i in items if "VirtualenvTest" in i.nodeid)
+ rest = [i for i in items if i not in virt]
+ nworkers = int(os.environ.get("PYTEST_XDIST_WORKER_COUNT", 4))
+ items[:] = interleaved([virt], rest, nworkers)
+ if TRACK_TESTS: # pragma: debugging
+ with open("/tmp/items.txt", "w") as f:
+ print("\n".join(i.nodeid for i in items), file=f)