test: add direct unit tests for _start_input_reader_thread (#1056)#1057
test: add direct unit tests for _start_input_reader_thread (#1056)#1057
_start_input_reader_thread (#1056)#1057Conversation
Add TestStartInputReaderThread class with five isolated tests: - daemon flag assertion (regression guard for issue #1012) - normal input stripped and queued - EOFError → _FALLBACK_EOF sentinel - KeyboardInterrupt → _FALLBACK_EOF sentinel (previously untested) - unexpected exception → _FALLBACK_EOF + warning logged Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds direct unit-test coverage for the interactive-mode fallback input reader thread to prevent regressions like the prior CI hang and to validate sentinel + logging behavior across error paths.
Changes:
- Adds a new
TestStartInputReaderThreadtest class with five focused unit tests for_start_input_reader_thread. - Verifies the thread is created as
daemon=True, and validates queue behavior for normal input,EOFError,KeyboardInterrupt, and unexpected exceptions (includinglogger.warning).
| inputs = iter([" hello ", EOFError()]) | ||
|
|
||
| def _fake_input() -> str: | ||
| val = next(inputs) | ||
| if isinstance(val, BaseException): | ||
| raise val | ||
| return val # type: ignore[return-value] | ||
|
|
There was a problem hiding this comment.
_fake_input() needs a # type: ignore[return-value] because inputs is untyped; in strict typing it’s better to type inputs as an iterator/iterable of str | BaseException (or similar) so pyright can narrow after the isinstance(..., BaseException) check and the ignore can be removed.
- Collapse two EOFError monkeypatch.setattr calls to single lines (exactly 88 chars, within ruff format line-length limit) - Remove unnecessary `# type: ignore[return-value]` (isinstance already narrows val to str) - Add `assert warn_spy.call_args is not None` guard before accessing .args to satisfy pyright reportOptionalMemberAccess Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI Fix AppliedFixed three issues causing CI failures:
Warning
|
|
❌ Pipeline orchestrator: CI still failing after ci-fixer attempt. Marking as stuck for human review. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Manual unstick: applied |
| captured_daemon: list[bool] = [] | ||
| _real_thread_init = threading.Thread.__init__ | ||
|
|
||
| def _spy_init(self_thread: threading.Thread, *args: Any, **kwargs: Any) -> None: | ||
| _real_thread_init(self_thread, *args, **kwargs) | ||
| if self_thread.name == "input-fallback": | ||
| captured_daemon.append(self_thread.daemon) | ||
|
|
||
| monkeypatch.setattr(threading.Thread, "__init__", _spy_init) |
There was a problem hiding this comment.
test_thread_is_daemon monkeypatches threading.Thread.__init__ globally and filters by self_thread.name == "input-fallback", which makes the test more brittle (e.g., if the thread name changes) and can potentially affect other threads created during the test. Prefer patching the constructor in the module under test (e.g. copilot_usage.cli.threading.Thread) or wrapping threading.Thread to capture the daemon kwarg directly, avoiding reliance on the thread name and minimizing global side effects.
| captured_daemon: list[bool] = [] | |
| _real_thread_init = threading.Thread.__init__ | |
| def _spy_init(self_thread: threading.Thread, *args: Any, **kwargs: Any) -> None: | |
| _real_thread_init(self_thread, *args, **kwargs) | |
| if self_thread.name == "input-fallback": | |
| captured_daemon.append(self_thread.daemon) | |
| monkeypatch.setattr(threading.Thread, "__init__", _spy_init) | |
| captured_daemon: list[bool | None] = [] | |
| _real_thread = threading.Thread | |
| def _spy_thread(*args: Any, **kwargs: Any) -> threading.Thread: | |
| captured_daemon.append(kwargs.get("daemon")) | |
| return _real_thread(*args, **kwargs) | |
| monkeypatch.setattr("copilot_usage.cli.threading.Thread", _spy_thread) |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
The annotated iter() expression fits within 88 chars (79 + 8 indent = 87), so ruff format expects it on a single line without a trailing comma. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI Fix Appliedruff format: Collapsed the multi-line Warning
|
Refactor test_thread_is_daemon to patch copilot_usage.cli.threading.Thread instead of globally patching threading.Thread.__init__. This avoids reliance on the thread name for filtering and minimizes global side effects. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
|
❌ Pipeline orchestrator: review-response loop reached 5 rounds. Marking as stuck for human review. |
Closes #1056
Summary
Adds
TestStartInputReaderThreadclass intests/copilot_usage/test_cli.pywith five isolated unit tests for_start_input_reader_thread:test_thread_is_daemondaemon=True— regression guard for the CI hang from issue #1012test_normal_input_is_stripped_and_queuedtest_eoferror_puts_fallback_sentinelEOFError→_FALLBACK_EOFon queuetest_keyboard_interrupt_puts_fallback_sentinelKeyboardInterrupt→_FALLBACK_EOFon queue (previously untested at any level)test_unexpected_exception_puts_fallback_and_logs_warningRuntimeError→_FALLBACK_EOF+logger.warningcalledApproach
builtins.inputto inject specific behaviorsthreading.Thread.__init__to capturedaemonbefore the thread startsWarning
The following domains were blocked by the firewall during workflow execution:
astral.shpypi.orgTo allow these domains, add them to the
network.allowedlist in your workflow frontmatter:See Network Configuration for more information.