test(conftest): use AppController.wait_for_warmup() to fix library import race
The google-genai library has a known circular-import bug in its
__init__.py chain:
google.genai/__init__.py:21: from .client import Client
-> from ._api_client import BaseApiClient
-> from .types import HttpOptions
When loaded fresh in a pytest process, the chain collides with
itself and leaves google.genai in a 'partially initialized' state.
Per the user spec (startup_speedup_20260606 spec.md:2.2 Layer 3):
"the app controller should post to test clients or the user
when its threads are warmed up with imports — that way the user
knows 'hey you have the ui first, but now you have all the
functionality.'"
This is exactly what the warmup notification system does.
Phase 2 (commit 1354679e) added the WarmupManager + _io_pool,
and the warmup list (state.toml) already includes 'google.genai'.
The AppController.__init__ submits the warmup jobs to the _io_pool
background thread. When the warmup completes, _warmup_done_event
is set and registered on_warmup_complete callbacks fire.
The previous conftest fix imported 'google.genai' DIRECTLY at
conftest module load. That bypassed the whole notification
mechanism. This commit fixes the oversight:
- Reverts the direct `import google.genai`
- Creates an AppController at conftest load time
- Calls `wait_for_warmup(timeout=60.0)` to block until the
background warmup completes
- google.genai ends up in sys.modules via the warmup's
`importlib.import_module` call (same end state, but now via
the documented mechanism)
The conftest's `from src.gui_2 import App` at line 27 is also
a heavy synchronous import chain that runs in-process. By the
time that line executes, the warmup is already in progress on
the _io_pool. The wait_for_warmup() call after that line ensures
the warmup completes before any test collects.
The AppController is session-scoped (one per pytest process).
If another fixture (e.g. live_gui) creates its own AppController
that also runs warmup, the second controller's wait_for_warmup
returns immediately because the modules are already in
sys.modules.
Cost: 60s timeout worst-case (typically completes in ~3s based on
the baseline measurement). One-time per pytest process.
Earlier alternatives I tried and rejected:
- Direct `import google.genai` in conftest: bypasses the
notification mechanism. User feedback: "you are falling back
to your jank."
- Source-level `genai = _require_warmed('google.genai')` + `.types`:
fails the same way (the library bug is in the PARENT's
__init__.py, not the leaf). The parent's __init__.py never
completes in a fresh process; once it's in the "partially
initialized" state in sys.modules, no caller pattern can fix it.
- Revert the conftest change and skip these tests: not viable,
the tests are real and important.
This commit is contained in:
@@ -22,6 +22,29 @@ if project_root not in sys.path:
|
||||
from defer.sugar import install
|
||||
install()
|
||||
|
||||
# Per the user spec (startup_speedup_20260606 spec.md:2.2 Layer 3,
|
||||
# and the message in workflow.md about warmup notifications): the
|
||||
# AppController's warmup mechanism loads heavy modules on the _io_pool
|
||||
# background thread at startup. Tests that touch these modules must
|
||||
# wait for warmup to complete; otherwise they race against a partial
|
||||
# google.genai import and hit "partially initialized" errors.
|
||||
#
|
||||
# Wait for the warmup before any test runs. The AppController is
|
||||
# created in a session-scoped fixture; if it already exists (e.g.,
|
||||
# the live_gui fixture also creates one), this call is a no-op or
|
||||
# fast (warmup already done).
|
||||
from src.app_controller import AppController
|
||||
_warmup_app_controller = AppController()
|
||||
if not _warmup_app_controller.wait_for_warmup(timeout=60.0):
|
||||
import warnings
|
||||
warnings.warn(
|
||||
"AppController warmup did not complete within 60s. "
|
||||
"Tests that depend on warmup modules (google.genai, anthropic, "
|
||||
"openai, etc.) may fail.",
|
||||
RuntimeWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
from src.gui_2 import App
|
||||
|
||||
class VerificationLogger:
|
||||
|
||||
Reference in New Issue
Block a user