diff --git a/tests/e2e/test_cache_rebuild.py b/tests/e2e/test_cache_rebuild.py index ed17e240..a6494f33 100644 --- a/tests/e2e/test_cache_rebuild.py +++ b/tests/e2e/test_cache_rebuild.py @@ -90,8 +90,16 @@ def _wait_until_serving(base: str, proc: subprocess.Popen, deadline: float) -> N raise TimeoutError("server never reached the serving state") -def _generate(base: str) -> str: - """A short non-thinking completion — the point is that the engine still runs, not what it says.""" +def _generate(base: str) -> int: + """Tokens the engine decoded -- the point is that it still runs, not what it says. + + Counts rather than reads the reply. Which field the text lands in depends on the + checkpoint: ``enable_thinking`` is read by the qwen/glm/gemma/dsv4 templates and is + inert for gpt-oss, whose Harmony channels are not gated by it, so its trace goes to + ``reasoning_content`` and ``content`` can be empty on a perfectly healthy engine. + ``usage.completion_tokens`` is accumulated from the scheduler's own acks, upstream of + every reasoning parser, so it says decode ran without depending on any of that. + """ code, body = _post( base, "/v1/chat/completions", @@ -104,7 +112,7 @@ def _generate(base: str) -> str: }, ) assert code == 200, body - return str(body["choices"][0]["message"]["content"]) + return int(body["usage"]["completion_tokens"]) @pytest.mark.skipif(not torch.cuda.is_available(), reason="cache rebuild e2e needs CUDA") @@ -142,7 +150,7 @@ def test_cache_rebuild_resizes_a_live_engine_and_keeps_serving(tmp_path): geometry = _get(base, "/v1/cache/status")["geometry"] assert geometry["num_pages"] == BOOT_PAGES - assert _generate(base) # a baseline generation, before anything is torn down + assert _generate(base) > 0 # a baseline generation, before anything is torn down # 1. Grow the KV pool. The reply carries the geometry the engine actually landed on. code, reply = _post(base, "/v1/cache/rebuild", {"num_pages": GROWN_PAGES}) @@ -153,7 +161,7 @@ def test_cache_rebuild_resizes_a_live_engine_and_keeps_serving(tmp_path): assert status["geometry"]["num_pages"] == GROWN_PAGES assert status["last_rebuild"]["status"] == "ok" # Graphs were re-captured against the new pool: decoding still works. - assert _generate(base) + assert _generate(base) > 0 # 2. The second axis, if this checkpoint has a GDN state pool. Resizing it moves a # different pool through the same teardown/re-capture path. @@ -166,7 +174,7 @@ def test_cache_rebuild_resizes_a_live_engine_and_keeps_serving(tmp_path): assert geometry["num_mamba_slots"] == grown_slots # A pool-only resize must not disturb the pool the previous rebuild grew. assert geometry["num_pages"] == GROWN_PAGES - assert _generate(base) + assert _generate(base) > 0 # 3. An unfittable target is rejected BEFORE anything is freed, and the engine that was # serving a moment ago keeps serving on its old cache. @@ -177,7 +185,7 @@ def test_cache_rebuild_resizes_a_live_engine_and_keeps_serving(tmp_path): status = _get(base, "/v1/cache/status") assert status["state"] == "serving" assert status["geometry"]["num_pages"] == GROWN_PAGES # untouched by the rejection - assert _generate(base) + assert _generate(base) > 0 finally: proc.terminate() try: