diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2021-11-02 06:30:56 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2021-11-02 06:57:26 -0400 |
commit | 5a6e090cf98e65050c372e0e4a58d42b8ced54f6 (patch) | |
tree | a1719691f81cc47a743c7995c3461538a5551a4f | |
parent | 75cd551a5200c97a08029abb5ad4c6d62141a328 (diff) | |
download | python-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.py | 57 |
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) |