Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design:
pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg
```

Python: `uv run pytest tests/ -v` (480) — import check: `uv run python -c "from emrg.client.app import run_client"`
Python: `uv run pytest tests/ -v` (484) — import check: `uv run python -c "from emrg.client.app import run_client"`
GUI: `cd emrg/gui && npm test` (86: 22 daemon_client + 22 app-commands + 17 renderer smoke + 15 i18n + 7 integration + 3 commands) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js`
CI: `uv run pytest` + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ EMRG doesn't just keep up — it catches up on its own.
git clone https://github.com/argszero/emrg.git
cd emrg
uv sync # install deps
uv run pytest tests/ -v # run tests (currently 480 items)
uv run pytest tests/ -v # run tests (currently 484 items)
uv run python -m emrg # launch TUI
# CI includes actionlint workflow gate (#444): workflow parse errors fail PR CI

Expand Down
74 changes: 74 additions & 0 deletions tests/test_doc_counts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Guard against the recurring README/Agent.md test-count drift.

Pattern history: #426 -> #430 -> #510 -> #511. Every time tests are added or
removed, the documented counts drift and require a follow-up doc PR. This
module asserts the documented Python count matches the real collection, and
that the documented GUI breakdown sums to its headline number.
"""

import re
import subprocess
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent


def _collected_pytest_count() -> int:
"""Run pytest in collect-only mode and parse the total."""
out = subprocess.check_output(
[__import__("sys").executable, "-m", "pytest", "--collect-only", "-q"],
cwd=str(REPO_ROOT),
text=True,
stderr=subprocess.STDOUT,
)
m = re.search(r"(\d+) tests? collected", out)
assert m, f"could not parse collected count from pytest output:\n{out[-2000:]}"
return int(m.group(1))


def _gui_breakdowns() -> list[tuple[str, int, list[int]]]:
"""Extract (label, headline, parts) for every documented GUI count."""
found = []
for doc in ("README.md", "Agent.md"):
text = (REPO_ROOT / doc).read_text(encoding="utf-8")
for line in text.splitlines():
if "npm test" not in line:
continue
m = re.search(r"\((\d+): ([^)]+)\)", line)
if not m:
continue
headline = int(m.group(1))
# each breakdown part starts with its count ("22 daemon_client + ...");
# take the first number per part (avoids false digits inside names like i18n)
parts = [
int(re.match(r"\s*(\d+)", part).group(1))
for part in m.group(2).split("+")
if re.match(r"\s*\d+", part)
]
found.append((f"{doc}: {line.strip()[:70]}", headline, parts))
return found


def test_python_count_matches_docs() -> None:
collected = _collected_pytest_count()
for doc in ("README.md", "Agent.md"):
text = (REPO_ROOT / doc).read_text(encoding="utf-8")
# README: "run tests (currently N items)" | Agent.md: "pytest tests/ -v` (N)"
m = re.search(r"currently (\d+) items", text) or re.search(
r"uv run pytest tests/ -v` \((\d+)\)", text
)
assert m, f"no documented Python count found in {doc}"
documented = int(m.group(1))
assert documented == collected, (
f"{doc} documents {documented} Python tests but {collected} are collected "
f"(--collect-only). Sync the doc (and this guard) when adding/removing tests."
)


def test_gui_breakdown_sums_to_headline() -> None:
breakdowns = _gui_breakdowns()
assert breakdowns, "no GUI test breakdowns found in README.md/Agent.md"
for label, headline, parts in breakdowns:
assert sum(parts) == headline, (
f"{label}: breakdown {parts} sums to {sum(parts)} but headline says {headline}"
)
Loading