[AUTO-INF-3B] features/environment.py uses # type: ignore comments in _install_fast_sleep_patch() violating CONTRIBUTING.md strict typing policy

Removed 6 `# type: ignore` suppressions from `_install_fast_sleep_patch()` in `features/environment.py`.
Used `cast(Any, module)` + direct attribute assignment instead of `setattr()` (which ruff B010 disallows) and `# type: ignore` comments.
Added `from collections.abc import Callable` and `from typing import cast` imports.
Added new feature file `features/test_infra_sleep_patch.feature` with 4 scenarios verifying the behavior.
Added new steps file `features/steps/test_infra_sleep_patch_steps.py`.

ISSUES CLOSED: #9993
This commit is contained in:
2026-04-19 13:21:06 +00:00
committed by Forgejo
parent 05fbf99b1f
commit 5812e0599a
3 changed files with 127 additions and 17 deletions
+32 -17
View File
@@ -7,8 +7,9 @@ import re
import shutil
import sys
import tempfile
from collections.abc import Callable
from pathlib import Path
from typing import Any
from typing import Any, cast
from behave.model import Scenario, Status
@@ -392,11 +393,16 @@ def _install_fast_sleep_patch() -> None:
operations fail deterministically, so the long sleeps are pure overhead
(~1 s per retry cycle x hundreds of scenarios = minutes of wasted time).
Both functions are replaced with capped versions (10 ms). The originals
are saved as ``time._original_sleep`` / ``asyncio._original_sleep`` and can
be called directly by any test step that needs a genuine delay (e.g.
CircuitBreaker recovery-timeout tests that need real wall-clock advancement
past a 100 ms threshold).
Both functions are replaced with capped versions (<=10 ms). The originals
are stored on the modules as ``_original_sleep`` and can be retrieved with
``getattr(time, "_original_sleep", time.sleep)`` by any test step that
needs a genuine delay (e.g. CircuitBreaker recovery-timeout tests that
need real wall-clock advancement past a 100 ms threshold).
``cast(Any, module)`` is used to assign dynamic attributes without
``# type: ignore`` suppressions: the ``features/`` directory is excluded
from Pyright's ``include`` list, so the cast is a documentation aid rather
than a runtime necessity.
"""
import asyncio
import time
@@ -404,22 +410,31 @@ def _install_fast_sleep_patch() -> None:
_MAX_SLEEP = 0.01 # 10 ms cap
# --- synchronous time.sleep ---
# Store the original in a typed local variable so the inner closure can
# call it directly. cast(Any, time) lets us assign _original_sleep and
# replace sleep without attr-defined / assignment type errors.
if not callable(getattr(time, "_original_sleep", None)):
time._original_sleep = time.sleep # type: ignore[attr-defined]
_orig_time_sleep: Callable[[float], None] = time.sleep
_time_mod: Any = cast(Any, time)
_time_mod._original_sleep = _orig_time_sleep
def _capped_sleep(seconds: float) -> None:
time._original_sleep(min(seconds, _MAX_SLEEP)) # type: ignore[attr-defined]
_orig_time_sleep(min(seconds, _MAX_SLEEP))
time.sleep = _capped_sleep # type: ignore[assignment]
_time_mod.sleep = _capped_sleep
# --- asynchronous asyncio.sleep ---
# Same pattern: capture the original in a typed local variable, then use
# cast(Any, asyncio) to assign _original_sleep and replace sleep.
if not callable(getattr(asyncio, "_original_sleep", None)):
asyncio._original_sleep = asyncio.sleep # type: ignore[attr-defined]
_orig_asyncio_sleep = asyncio.sleep
_asyncio_mod: Any = cast(Any, asyncio)
_asyncio_mod._original_sleep = _orig_asyncio_sleep
async def _capped_async_sleep(seconds: float, result: object = None) -> object:
return await asyncio._original_sleep(min(seconds, _MAX_SLEEP), result) # type: ignore[attr-defined]
return await _orig_asyncio_sleep(min(seconds, _MAX_SLEEP), result)
asyncio.sleep = _capped_async_sleep # type: ignore[assignment]
_asyncio_mod.sleep = _capped_async_sleep
def _ensure_template_db() -> None:
@@ -478,11 +493,11 @@ def _install_template_db_patch() -> None:
def _fast_init_or_upgrade(self: Any, **kwargs: Any) -> None:
"""Replace Alembic migrations with fast alternatives.
- Process-global cache hit immediate return (no work at all)
- Non-SQLite databases fall through to original
- In-memory SQLite ``Base.metadata.create_all()`` + alembic stamp
- File-based SQLite with matching prefix copy template
- Everything else fall through to original
- Process-global cache hit -> immediate return (no work at all)
- Non-SQLite databases -> fall through to original
- In-memory SQLite -> ``Base.metadata.create_all()`` + alembic stamp
- File-based SQLite with matching prefix -> copy template
- Everything else -> fall through to original
"""
db_url: str = getattr(self, "database_url", "")
@@ -0,0 +1,72 @@
"""Step definitions for the fast sleep patch type-safe implementation tests.
These scenarios verify the observable behaviour of ``_install_fast_sleep_patch()``
after the ``# type: ignore`` suppressions were replaced with type-safe
``setattr()`` calls and local typed variables (issue #9993).
"""
from __future__ import annotations
import asyncio
import time
from behave import then, when
from behave.runner import Context
from features.environment import _install_fast_sleep_patch
@when("I call time.sleep with {seconds:f} seconds")
def step_call_time_sleep(context: Context, seconds: float) -> None:
"""Call the (patched) time.sleep and record elapsed wall-clock time."""
start = time.monotonic()
time.sleep(seconds)
context.elapsed_seconds = time.monotonic() - start
@then("the call should complete in under 500ms")
def step_call_completes_quickly(context: Context) -> None:
"""Assert the patched sleep completed well under the requested duration."""
elapsed: float = context.elapsed_seconds
assert elapsed < 0.5, (
f"Expected patched time.sleep to complete in under 500ms, "
f"but it took {elapsed * 1000:.1f}ms"
)
@then("time._original_sleep should be a callable")
def step_time_original_sleep_callable(context: Context) -> None:
"""Assert time._original_sleep was stored by the patch and is callable."""
original = getattr(time, "_original_sleep", None)
assert callable(original), (
f"Expected time._original_sleep to be callable after patch installation, "
f"got {original!r}"
)
@then("asyncio._original_sleep should be a callable")
def step_asyncio_original_sleep_callable(context: Context) -> None:
"""Assert asyncio._original_sleep was stored by the patch and is callable."""
original = getattr(asyncio, "_original_sleep", None)
assert callable(original), (
f"Expected asyncio._original_sleep to be callable after patch installation, "
f"got {original!r}"
)
@when("I call _install_fast_sleep_patch a second time")
def step_call_patch_second_time(context: Context) -> None:
"""Record the current _original_sleep, then call the patch again."""
context.original_sleep_before_second_call = getattr(time, "_original_sleep", None)
_install_fast_sleep_patch()
@then("time._original_sleep should remain the same callable after the second call")
def step_original_sleep_unchanged(context: Context) -> None:
"""Assert idempotency: a second patch call must not replace _original_sleep."""
original_after = getattr(time, "_original_sleep", None)
assert original_after is context.original_sleep_before_second_call, (
"Expected time._original_sleep to remain the same callable after a "
"second call to _install_fast_sleep_patch() (idempotency guard), "
"but it was replaced"
)
+23
View File
@@ -0,0 +1,23 @@
@mock_only
Feature: Fast sleep patch — type-safe implementation
As a CleverAgents developer
I want _install_fast_sleep_patch() to cap sleep durations without type suppressions
So that Pyright strict mode passes and test execution remains fast
# These scenarios verify the observable behaviour of _install_fast_sleep_patch()
# after the # type: ignore suppressions were replaced with type-safe setattr()
# calls and local typed variables (issue #9993).
Scenario: time.sleep is capped at the 10ms maximum
When I call time.sleep with 5.0 seconds
Then the call should complete in under 500ms
Scenario: time._original_sleep is accessible for tests that need real delays
Then time._original_sleep should be a callable
Scenario: asyncio._original_sleep is accessible for tests that need real delays
Then asyncio._original_sleep should be a callable
Scenario: _install_fast_sleep_patch is idempotent when called multiple times
When I call _install_fast_sleep_patch a second time
Then time._original_sleep should remain the same callable after the second call