test(tools): pin daemon-pool worker arg shape for both stdlib contracts

Replace the cherry-picked 3.14-shape test with two interpreter-agnostic
invariants. The original test did `del pool._initializer` (raises on 3.14,
where the attribute never exists) and drove `_WorkItem.run()` with no
`ctx` (TypeError on 3.14), so it could only ever pass on 3.11-3.13 — the
exact interpreters where the bug does not occur.

The fake worker now records the args tuple and resolves the work item's
future directly, so the tests assert only on the shape the executor picks:
`(ref, ctx, queue)` when `_create_worker_context` exists (3.14+),
`(ref, queue, initializer, initargs)` when the legacy fields do (3.11-3.13).
Both run green on 3.11 and 3.14; the 3.14-shape test is red on main under
either interpreter (`AttributeError: ... no attribute '_initializer'`).

Refs #58596, #111813.
This commit is contained in:
teknium1
2026-09-15 11:50:40 -07:00
committed by Teknium
parent c9fa191334
commit 0262b376b5
+51 -14
View File
@@ -94,26 +94,63 @@ def test_submit_propagates_caller_contextvars():
pool.shutdown(wait=True)
def test_workers_support_worker_context_without_legacy_initializer_attrs(monkeypatch):
"""Python 3.14 creates a worker context instead of storing initializer fields."""
seen_contexts = []
def _capture_worker_args(monkeypatch, pool):
"""Swap the stdlib worker for one that records its args and resolves one item.
def worker_with_context(_executor_ref, worker_context, work_queue):
seen_contexts.append(worker_context)
work_queue.get().run()
The stdlib ``_worker`` signature differs between interpreters, so the fake
accepts anything and completes the work item's future directly — the test
then runs on 3.11 and 3.14 alike and asserts only on the arg shape chosen.
"""
seen = []
def fake_worker(*args):
seen.append(args)
pool._work_queue.get().future.set_result("done")
monkeypatch.setattr(daemon_pool, "_worker", fake_worker)
return seen
def test_worker_gets_context_when_executor_builds_worker_contexts(monkeypatch):
"""3.14+ shape (#58596, #111813): the executor exposes ``_create_worker_context``
and no ``_initializer``/``_initargs``; the worker must receive
``(executor_ref, ctx, work_queue)`` — reading the legacy fields raised
``AttributeError`` on every pool spawn."""
pool = DaemonThreadPoolExecutor(max_workers=1)
monkeypatch.setattr(daemon_pool, "_worker", worker_with_context)
monkeypatch.setattr(
pool, "_create_worker_context", lambda: "worker-context", raising=False
)
del pool._initializer
del pool._initargs
monkeypatch.setattr(pool, "_create_worker_context", lambda: "worker-context", raising=False)
monkeypatch.delattr(pool, "_initializer", raising=False)
monkeypatch.delattr(pool, "_initargs", raising=False)
seen = _capture_worker_args(monkeypatch, pool)
try:
assert pool.submit(lambda: "done").result(timeout=10) == "done"
assert seen_contexts == ["worker-context"]
assert pool.submit(lambda: None).result(timeout=10) == "done"
finally:
pool.shutdown(wait=True)
((executor_ref, ctx, work_queue),) = seen
assert executor_ref() is pool
assert ctx == "worker-context"
assert work_queue is pool._work_queue
def test_worker_gets_initializer_when_executor_stores_initializer_fields(monkeypatch):
"""3.11–3.13 shape: no ``_create_worker_context``; the worker must receive
``(executor_ref, work_queue, initializer, initargs)``."""
def init(*_):
return None
pool = DaemonThreadPoolExecutor(max_workers=1)
monkeypatch.delattr(pool, "_create_worker_context", raising=False)
monkeypatch.setattr(pool, "_initializer", init, raising=False)
monkeypatch.setattr(pool, "_initargs", (1, 2), raising=False)
seen = _capture_worker_args(monkeypatch, pool)
try:
assert pool.submit(lambda: None).result(timeout=10) == "done"
finally:
pool.shutdown(wait=True)
((executor_ref, work_queue, initializer, initargs),) = seen
assert executor_ref() is pool
assert work_queue is pool._work_queue
assert (initializer, initargs) == (init, (1, 2))
def _repo_root():