From 52ea2693cf8d9204d5eab2c209c93faccb74bfa7 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 6 Jun 2026 19:23:52 -0400 Subject: [PATCH] test(conftest): use AppController.wait_for_warmup() to fix library import race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- tests/conftest.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index bac107ea..ab0a393e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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: