Private
Public Access
0
0

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:
2026-06-06 19:23:52 -04:00
parent 88fc42bbc0
commit 52ea2693cf
+23
View File
@@ -22,6 +22,29 @@ if project_root not in sys.path:
from defer.sugar import install from defer.sugar import install
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 from src.gui_2 import App
class VerificationLogger: class VerificationLogger: