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
31 changes: 30 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,36 @@ faulthandler_timeout = 60
# ~90.4% measured at landing (coverage 7.15.2) - it exists to catch an
# actual coverage collapse, not to gate every incremental dip.
[tool.coverage.run]
source = ["backend", "graphlink_plugins"]
# EVERY shipped package plus EVERY shipped loose module - kept in step with
# pyproject's own [tool.setuptools] manifest by
# tests/test_coverage_scope.py, which fails when the two drift.
#
# It used to be just backend + graphlink_plugins. Two of the four shipped
# PACKAGES were outside the floor entirely, and so were all 23 root modules -
# including api_provider.py, the code that talks to every model endpoint.
# Measured at the time of widening: 21,606 statements, 86% covered, so the
# 85% floor holds unchanged. provider_runtime is the weak spot inside it
# (ollama_scan 8%, llama_cpp_scan 10%, gemini_transport 29%) and that is
# precisely the point - those numbers were invisible before, and a
# regression in them now counts against the floor.
#
# The root modules are named WITHOUT a .py suffix. coverage's `source` takes
# packages and directories, and silently ignores anything else - the first
# version of this list wrote "api_provider.py" and measured exactly nothing
# new (statement count stayed at 18,763 instead of rising to 21,606). A
# config that looks right and gates nothing is the failure this list was
# widened to fix, so: names, not paths.
source = [
"backend", "graphlink_plugins", "provider_runtime", "settings_store", "contracts",
"api_provider", "graphlink_artifact_agent", "graphlink_audio",
"graphlink_chart_data", "graphlink_chart_rendering", "graphlink_chat_agent",
"graphlink_desktop", "graphlink_execution_guard", "graphlink_grid_view_settings",
"graphlink_memory", "graphlink_migrations", "graphlink_model_catalog",
"graphlink_navigation_pins", "graphlink_note_agent", "graphlink_process_env",
"graphlink_prompts", "graphlink_scratch_dirs", "graphlink_secrets",
"graphlink_settings_store", "graphlink_task_config", "graphlink_token_estimator",
"graphlink_version", "graphlink_wire_schema",
]
omit = ["*/tests/*", "*/__pycache__/*"]

[tool.coverage.report]
Expand Down
92 changes: 92 additions & 0 deletions tests/test_coverage_scope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""The coverage floor has to measure everything that ships.

[tool.coverage.run].source used to be `["backend", "graphlink_plugins"]`, while
[tool.setuptools] ships FOUR packages and 23 loose root modules. Two shipped
packages - provider_runtime and settings_store - and every root module,
including api_provider.py (the code that talks to every model endpoint), sat
outside the 85% floor entirely. A file nothing measures can regress to zero
and the gate stays green.

Both lists are hand-maintained in the same file, which is the shape this
codebase keeps getting bitten by: a set asserted by hand, the other set
growing, and nothing failing. So they are compared here instead.

Deliberately compares against the SHIPPING manifest rather than against
"every .py in the repo": tools/, mutation_tests/ and the test suites are not
shipped and have no business inside a production coverage floor.
"""

from __future__ import annotations

import tomllib
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[1]
PYPROJECT = REPO_ROOT / "pyproject.toml"

# Measured but not shipped: contracts/ is build-time codegen (it generates the
# TS types the SPA imports), and it has real tests, so it belongs in the floor
# even though no wheel carries it.
_MEASURED_BUT_NOT_SHIPPED = {"contracts"}


def _config() -> dict:
return tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))


def test_every_shipped_package_is_inside_the_coverage_floor():
config = _config()
source = set(config["tool"]["coverage"]["run"]["source"])
# "backend*" -> "backend"
shipped = {
entry.rstrip("*")
for entry in config["tool"]["setuptools"]["packages"]["find"]["include"]
}
missing = sorted(shipped - source)
assert not missing, (
f"shipped packages outside [tool.coverage.run].source: {missing}. "
"A package nothing measures can regress to zero with the gate still green."
)


def test_every_shipped_root_module_is_inside_the_coverage_floor():
config = _config()
source = set(config["tool"]["coverage"]["run"]["source"])
shipped = set(config["tool"]["setuptools"]["py-modules"])
missing = sorted(shipped - source)
assert not missing, (
f"shipped root modules outside [tool.coverage.run].source: {missing}"
)


def test_the_coverage_source_lists_nothing_that_does_not_exist():
"""The other direction: a renamed or deleted module left in the list
silently measures nothing, which reads as coverage it does not have.

Accepts both forms because coverage's `source` names a package/directory
OR a top-level module: `backend` is a directory, `api_provider` resolves
to api_provider.py."""
config = _config()
stale = [
entry for entry in config["tool"]["coverage"]["run"]["source"]
if not (REPO_ROOT / entry).exists() and not (REPO_ROOT / f"{entry}.py").exists()
]
assert not stale, f"[tool.coverage.run].source names things that do not exist: {stale}"


def test_the_source_list_adds_nothing_beyond_what_ships():
"""Guards against quietly padding the floor with well-covered code that
is not part of the product."""
config = _config()
source = set(config["tool"]["coverage"]["run"]["source"])
shipped = {
entry.rstrip("*")
for entry in config["tool"]["setuptools"]["packages"]["find"]["include"]
}
shipped |= set(config["tool"]["setuptools"]["py-modules"])
shipped |= _MEASURED_BUT_NOT_SHIPPED
unexpected = sorted(source - shipped)
assert not unexpected, (
f"[tool.coverage.run].source measures things that do not ship: {unexpected}. "
"If that is deliberate, add it to _MEASURED_BUT_NOT_SHIPPED with a reason."
)
37 changes: 37 additions & 0 deletions tests/test_register_function_length.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,43 @@ def test_no_register_function_exceeds_the_300_line_cap():
)


# Below this much headroom, report it. A cap that everything sits just under
# is a cap that will bind on whoever happens to touch one of those functions
# next, for a reason that has nothing to do with their change - the same
# erosion web_ui/scripts/check-bundle-size.mjs kept suffering, where a ratchet
# with 323 bytes left "passed" right up until an unrelated two-line bug fix
# tripped it. 10% of the cap (30 lines) is enough warning to split
# deliberately rather than under duress.
HEADROOM_WARN_LINES = MAX_REGISTER_FUNCTION_LINES // 10


def test_the_number_of_register_functions_near_the_cap_is_not_growing():
"""A cap everything sits just under will bind on whoever happens to touch
one of those functions next, for a reason that has nothing to do with
their change. That is the erosion web_ui/scripts/check-bundle-size.mjs
kept suffering - a ratchet with 323 bytes left "passed" right up until an
unrelated two-line bug fix tripped it.

Four functions were within 30 lines of the cap when this was added (294,
290, 285, 283), and splitting a 294-line registration function is real
work that should be scheduled rather than forced. So this does not fail
on those four - it fails on a FIFTH, which is the signal that the
pressure is growing rather than being paid down.

Lower the recorded count as they are split. Never raise it."""
tight = sorted(
(MAX_REGISTER_FUNCTION_LINES - length, f"{path.relative_to(REPO_ROOT).as_posix()}::{node.name}", length)
for path, node, length in _register_functions()
if MAX_REGISTER_FUNCTION_LINES - length < HEADROOM_WARN_LINES
)
assert len(tight) <= 4, (
f"{len(tight)} register* functions are now within {HEADROOM_WARN_LINES} lines of the "
f"{MAX_REGISTER_FUNCTION_LINES}-line cap, up from the 4 recorded here. Split one "
"before adding another:\n "
+ "\n ".join(f"{length} lines ({headroom} left) {name}" for headroom, name, length in tight)
)


def test_at_least_one_register_function_is_found():
# A collection bug (wrong glob, wrong name-matching predicate) would
# make the test above vacuously pass with zero offenders - this
Expand Down
Loading