Files
wehub-resource-sync b4fbd6fe9f
Deploy Site / deploy-vercel (push) Has been skipped
Deploy Site / deploy-docs (push) Has been skipped
Build Skills Index / build-index (push) Has been skipped
CI / Deny unrelated histories (push) Has been skipped
CI / Detect affected areas (push) Successful in 27m35s
CI / OSV scan (push) Failing after 4s
CI / Build&Test Docker image (push) Successful in 9s
CI / Supply-chain scan (push) Has been skipped
CI / Lint Docker scripts (push) Failing after 5m13s
CI / Check contributors (push) Failing after 12m8s
CI / Docs Site (push) Failing after 12m8s
CI / TypeScript (push) Failing after 12m8s
CI / Python lints (push) Failing after 12m9s
CI / Python tests (push) Failing after 12m9s
CI / Check uv.lock (push) Failing after 23m22s
CI / CI timing report (push) Has been cancelled
Build Skills Index / trigger-deploy (push) Has been cancelled
CI / All required checks pass (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:56:03 +08:00

148 lines
4.1 KiB
Python

"""Tests for agent.thread_scoped_output.thread_scoped_silence.
Behaviour contract: a thread inside ``thread_scoped_silence()`` has its
stdout/stderr routed to devnull, while every OTHER thread keeps writing to the
real stream — even concurrently, while the first thread is still inside the
context. This is the property the old process-global
``contextlib.redirect_stdout(devnull)`` violated (issue #55769 / #55925).
"""
import io
import sys
import threading
import time
from agent.thread_scoped_output import thread_scoped_silence
def _run_with_real_stream(fn):
"""Bind a StringIO as the real stdout, run fn, return what reached it."""
real_out = io.StringIO()
orig = sys.stdout
sys.stdout = real_out
try:
fn()
finally:
sys.stdout = orig
return real_out.getvalue()
def test_current_thread_is_silenced():
def body():
with thread_scoped_silence():
print("dropped")
print("kept")
captured = _run_with_real_stream(body)
assert "dropped" not in captured
assert "kept" in captured
def test_concurrent_thread_keeps_output_during_silence_window():
"""A loud thread writing WHILE another thread is silenced must survive."""
inside_silence = threading.Event()
loud_done = threading.Event()
def silenced_worker():
with thread_scoped_silence():
print("SILENCED")
inside_silence.set()
# Hold the silence window until the loud thread has written.
loud_done.wait(timeout=2.0)
def loud_worker():
inside_silence.wait(timeout=2.0)
print("LOUD")
loud_done.set()
def body():
t1 = threading.Thread(target=silenced_worker)
t2 = threading.Thread(target=loud_worker)
t1.start()
t2.start()
t1.join(timeout=3.0)
t2.join(timeout=3.0)
captured = _run_with_real_stream(body)
assert "SILENCED" not in captured
assert "LOUD" in captured
def test_stderr_is_also_routed_per_thread():
real_err = io.StringIO()
orig = sys.stderr
sys.stderr = real_err
try:
with thread_scoped_silence():
sys.stderr.write("err-dropped\n")
sys.stderr.write("err-kept\n")
finally:
sys.stderr = orig
out = real_err.getvalue()
assert "err-dropped" not in out
assert "err-kept" in out
def test_nested_silence_same_thread_composes():
def body():
with thread_scoped_silence():
with thread_scoped_silence():
print("inner")
# Still inside the OUTER context — depth-counted, so this thread
# remains silenced after the inner context exits.
print("after-inner")
print("after-outer")
captured = _run_with_real_stream(body)
assert "inner" not in captured
assert "after-inner" not in captured
assert "after-outer" in captured
def test_unsilence_cleans_up_after_exit():
"""After the context exits, the calling thread writes to the real stream."""
seen = []
def body():
with thread_scoped_silence():
pass
print("post")
seen.append("post")
captured = _run_with_real_stream(body)
assert "post" in captured
assert seen == ["post"]
def test_many_concurrent_silenced_and_loud_threads():
"""Stress: interleaved silenced/loud threads keep their respective fates."""
start = threading.Event()
results_lock = threading.Lock()
def silenced(i):
start.wait(timeout=2.0)
with thread_scoped_silence():
print(f"S{i}")
time.sleep(0.05)
def loud(i):
start.wait(timeout=2.0)
time.sleep(0.02)
print(f"L{i}")
def body():
threads = []
for i in range(5):
threads.append(threading.Thread(target=silenced, args=(i,)))
threads.append(threading.Thread(target=loud, args=(i,)))
for t in threads:
t.start()
start.set()
for t in threads:
t.join(timeout=3.0)
captured = _run_with_real_stream(body)
for i in range(5):
assert f"S{i}" not in captured, f"silenced S{i} leaked"
assert f"L{i}" in captured, f"loud L{i} swallowed"