From b48adc2a106e8d17d2e4bad4fdd1dbf7a87dbf9a Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Sun, 13 Sep 2026 12:43:14 +0800 Subject: [PATCH] emrg: name the unsynced environment, not a wrong interpreter, when pytest is missing --- Agent.md | 2 +- scripts/check-doc-count.py | 34 +++++++++++++++++++++++- tests/test_check_doc_count.py | 50 +++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/Agent.md b/Agent.md index eeca0681..a74295ed 100644 --- a/Agent.md +++ b/Agent.md @@ -119,7 +119,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design: pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.token; python -m emrg ``` -Python: `uv run pytest tests/ -v` (1594) — import check: `uv run python -c "from emrg.client.app import run_client" +Python: `uv run pytest tests/ -v` (1596) — import check: `uv run python -c "from emrg.client.app import run_client" GUI: `cd emrg/gui && npm test` (100: 44 daemon_client + 20 conn-manager + 7 integration + 7 nav-policy + 7 gui-state + 6 build-config + 4 boot-contract + 3 preload-api + 2 theme-guard) — syntax: `node --check main.js preload.js daemon_client.js` Renderer: `cd emrg/gui/renderer && npm run typecheck && npm test` (514: 5 snapshot-store + 9 utils + 3 ErrorBoundary + 2 App smoke + 11 commands + 4 copywriting + 11 i18n + 13 markdown + 21 transcript + 11 TranscriptView + 15 history + 31 composer + 41 Composer + 6 LinkDialog + 16 sidebar + 17 Sidebar + 9 fileTree + 9 FileTree + 16 resultPanel + 8 ResultPanel + 27 workspaceView + 10 WorkspaceView + 10 dialog + 6 Dialog + 9 ConfirmDialog + 9 RenameDialog + 10 dialogLists + 3 HelpDialog + 9 MemoryDialog + 6 SkillsDialog + 8 openSession + 6 WelcomeDialog + 9 OpenSessionDialog + 7 NewSessionDialog + 7 rewind + 8 RewindDialog + 7 GithubDeviceDialog + 18 daemonBridge + 7 DaemonBridgeProvider + 30 Shell + 15 DialogHost + 20 SettingsPanel + 6 TaskFormDialog + 5 RantDialog + 4 vendorMarkdown) + `npm run build` → `renderer/dist/` CI: `uv run pytest` (ubuntu + **windows-2025 matrix** — Windows pytest 回归在 PR CI 即失败,v0.2.29 教训 #725) + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文) diff --git a/scripts/check-doc-count.py b/scripts/check-doc-count.py index b640636e..59121a78 100644 --- a/scripts/check-doc-count.py +++ b/scripts/check-doc-count.py @@ -128,6 +128,16 @@ def _resolve_root() -> Path: # spelled that way sends the reader straight into a second failure. A hint is # only worth printing if it runs; keep the spelling here and let # tests/test_check_doc_count.py prove the other sites agree with it. +# +# Scope of that measurement, added 2026-09-13 (`cyc20260913-122923`) after +# walking into it: "this form exits 0" is a property of a **synced** checkout. +# The measurement above was taken in the main clone, which is synced. In a fresh +# worktree the same spelling exits 2 without measuring anything, because +# `uv run --no-sync` has created an empty `.venv` there and both `python` and +# `python3` resolve to it - so the spelling is necessary but not sufficient, and +# the hint it appears in must not be printed when the environment, not the +# interpreter choice, is what is missing. See the pytest-missing branch in +# `measured_count`. INVOCATION = "uv run --no-sync python3 scripts/check-doc-count.py" @@ -161,9 +171,31 @@ def measured_count() -> int: "(its output could not be decoded)" ) if proc.returncode != 0: + detail = (proc.stdout[-2000:] + proc.stderr[-2000:]).strip() + # Two causes, two remedies - and the second one is *not* "use the right + # interpreter". Measured 2026-09-13 (`cyc20260913-122923`) in a fresh + # review worktree: `uv run --no-sync` there had produced an empty `.venv` + # (`site-packages` holding only `_virtualenv.pth` and `_virtualenv.py`), + # and `python` and `python3` both resolve to it - so the invocation this + # tool prints as its own remedy failed with byte-identical output, rc 2, + # sending the reader in a circle. Reading that output as a wrong + # interpreter is the misdiagnosis: there is no interpreter in this + # checkout that has pytest, so advising a different one cannot help. + if "No module named pytest" in detail: + raise DocCountError( + "pytest is not installed in the interpreter running this tool " + f"({sys.executable}), so no test was collected:\n" + + detail + + "\n\nThis is an unsynced checkout, not a wrong-interpreter " + "problem - a fresh worktree or clone gets an empty `.venv`, and " + "`python` and `python3` both resolve to it, so re-running " + f"`{INVOCATION}` here fails identically. Run `uv sync` in this " + "checkout first, or run this tool from a checkout whose " + "environment is already synced." + ) raise DocCountError( f"pytest --collect-only failed (rc={proc.returncode}):\n" - + (proc.stdout[-2000:] + proc.stderr[-2000:]).strip() + + detail + "\n\nhint: run this with the project interpreter, e.g." f" `{INVOCATION}`" ) diff --git a/tests/test_check_doc_count.py b/tests/test_check_doc_count.py index 9254ced5..bda14ee5 100644 --- a/tests/test_check_doc_count.py +++ b/tests/test_check_doc_count.py @@ -654,6 +654,56 @@ class _Proc: assert f"`{canonical}`" in str(excinfo.value) +def test_a_missing_pytest_is_diagnosed_as_an_unsynced_checkout(monkeypatch) -> None: + """The remedy must not be the command that just failed. + + Measured state this pins (2026-09-13, `cyc20260913-122923`): in a fresh + review worktree the tool printed its own `INVOCATION` as the fix, and running + that spelling produced byte-identical output, rc 2 - `uv run --no-sync` had + left an empty `.venv` there and both `python` and `python3` resolve to it, so + "use the project interpreter" is a circle. The message must name the + environment instead. + """ + fresh = _load_module() + + class _Proc: + returncode = 1 + stdout = "" + stderr = "/some/checkout/.venv/bin/python3: No module named pytest\n" + + monkeypatch.setattr(fresh.subprocess, "run", lambda *a, **k: _Proc()) + with pytest.raises(fresh.DocCountError) as excinfo: + fresh.measured_count() + message = str(excinfo.value) + assert "No module named pytest" in message + assert "/some/checkout/.venv/bin/python3" in message, "the child's own words" + assert "unsynced" in message, "the cause, named" + assert "uv sync" in message, "a remedy that can actually work here" + + +def test_a_real_collection_failure_keeps_the_invocation_hint(monkeypatch) -> None: + """The other cause of the same non-zero exit: pytest ran, and it failed. + + Without this arm, treating every non-zero collection as an unsynced checkout + would be green - which would take the invocation hint away from the case it + was written for. + """ + fresh = _load_module() + + class _Proc: + returncode = 2 + stdout = "ERROR: file or directory not found: tests/\n" + stderr = "" + + monkeypatch.setattr(fresh.subprocess, "run", lambda *a, **k: _Proc()) + with pytest.raises(fresh.DocCountError) as excinfo: + fresh.measured_count() + message = str(excinfo.value) + assert f"`{fresh.INVOCATION}`" in message + assert "uv sync" not in message + assert "unsynced" not in message + + # --- which tree was measured -------------------------------------------------