Skip to content

Commit 63a5cca

Browse files
morlutocursoragent
andauthored
refactor(test-ci): overhaul test suite and CI plan architecture (#1172)
* refactor(test-ci): overhaul test suite and CI plan architecture Introduce an authoritative plan_manifest with a topology compiler, typed RuntimeTestProfile/resource contracts, and reference metadata without checker authority. Demote over-hydrated fixtures, split mixed composition/MCP/process ownership, localize complete-runtime plugins, and add inventory plus fail-closed helpers so lanes stop compensating for each other. Co-authored-by: morluto <morluto@users.noreply.github.com> * refactor(test-ci): enforce closure and collapse dual-source planners Make plan_manifest the sole authority for impact suppressions, lane lists, and CI boolean keys; retire reverse-import exact selection; enforce resource-closure at collection; demote unjustified authorized runtimes; and compile execution profiles into topology with fail-closed inventory. Co-authored-by: morluto <morluto@users.noreply.github.com> * refactor(test-ci): drop profile bridge and own planner modules Delete the unused tests.support execution-profile re-export, own tools/test_plan in impact rules, and keep lint/type gates green. Co-authored-by: morluto <morluto@users.noreply.github.com> * test(test-ci): use compiled [[lanes]] shape in architecture fixtures Align architecture-policy fixtures with the single array topology format after removing dual-shape TOML parsing. Co-authored-by: morluto <morluto@users.noreply.github.com> * refactor(test-ci): delete redundant bridges instead of demoting Remove sympy/graph public-API bridge modules that only wrapped COMPUTED complete-runtime calls, collapse graph installation to an attached-only authority-absence contract, and drop the authorized-runtime escape marker. Co-authored-by: morluto <morluto@users.noreply.github.com> * refactor(test-ci): tighten authority signals and share runtime owners Replace substring verify/authority detection with token-safe checks so UNVERIFIED and bare .verify IDs no longer justify authorized fixtures, centralize complete-runtime ownership, demote enumeration recovery to attached runtime, and delete unused fail_closed helpers. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(test-ci): allowlist process-lane -O adapter guard subprocess The polynomial adapter guard was moved into the process boundary so it can own an optimized-interpreter subprocess check; register that fixture in the subprocess confinement allowlist. Co-authored-by: morluto <morluto@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com>
1 parent c5ef954 commit 63a5cca

59 files changed

Lines changed: 3290 additions & 1259 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/ci-impact.json

Lines changed: 223 additions & 85 deletions
Large diffs are not rendered by default.

.github/scripts/classify-ci-paths

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,25 @@ TOPOLOGY = ROOT / "tests" / "topology.toml"
2121
_SCRIPTS_DIR = str(Path(__file__).resolve().parent)
2222
if _SCRIPTS_DIR not in sys.path:
2323
sys.path.insert(0, _SCRIPTS_DIR)
24+
if str(ROOT) not in sys.path:
25+
sys.path.insert(0, str(ROOT))
2426
from _ci_paths import normalize_paths, path_values # noqa: E402
27+
from tools.test_plan.ci_outputs import ( # noqa: E402
28+
boolean_run_keys,
29+
matrix_lane_names,
30+
python_lane_names,
31+
suite_names,
32+
)
2533

26-
# More-specific ownership rules suppress the general python-source catch-all
27-
# when both match the same path (fnmatch '*' crosses '/').
28-
_SUPPRESSED_WHEN_PRESENT = {
29-
"domain-mathematical-sources": frozenset({"python-source"}),
30-
"npm-facing-adapter": frozenset({"python-source"}),
31-
"ci-planning-tools": frozenset({"ci-scripts"}),
32-
"benchmark-ci-automation": frozenset({"ci-scripts", "ci-automation"}),
33-
"topology-runner-tool": frozenset({"test-topology-runners"}),
34-
"documentation-command-tool": frozenset({"test-topology-runners"}),
35-
"benchmark-static-tool": frozenset({"test-topology-runners"}),
36-
}
34+
35+
def _suppression_map(manifest: dict[str, Any]) -> dict[str, frozenset[str]]:
36+
mapping: dict[str, frozenset[str]] = {}
37+
for rule in manifest.get("rules", ()):
38+
name = str(rule.get("name", ""))
39+
suppresses = rule.get("suppresses", ())
40+
if name and isinstance(suppresses, list) and suppresses:
41+
mapping[name] = frozenset(str(item) for item in suppresses)
42+
return mapping
3743

3844

3945
def load_manifest() -> dict[str, Any]:
@@ -71,7 +77,7 @@ def suites_for_path(path: str, manifest: dict[str, Any]) -> set[str]:
7177
if matching_rules:
7278
matched_names = {str(rule["name"]) for rule in matching_rules}
7379
suppressed: set[str] = set()
74-
for specific, generals in _SUPPRESSED_WHEN_PRESENT.items():
80+
for specific, generals in _suppression_map(manifest).items():
7581
if specific in matched_names:
7682
suppressed.update(generals)
7783
return {
@@ -140,17 +146,7 @@ def classify(
140146
deployment_for_path(path, manifest) for path in paths
141147
)
142148

143-
python_lanes = (
144-
"unit",
145-
"component",
146-
"domain",
147-
"composition",
148-
"storage",
149-
"process",
150-
"mcp",
151-
"provider",
152-
"e2e",
153-
)
149+
python_lanes = python_lane_names(manifest)
154150
selected_python_lanes = tuple(lane for lane in python_lanes if lane in suites)
155151
if exhaustive:
156152
classification = "exhaustive"
@@ -170,13 +166,7 @@ def classify(
170166
classification = "selective"
171167

172168
semantic_lanes = {f"run-{lane}": lane in suites for lane in python_lanes}
173-
matrix_lanes = (
174-
"component",
175-
"storage",
176-
"mcp",
177-
"provider",
178-
"e2e",
179-
)
169+
matrix_lanes = matrix_lane_names(manifest)
180170
selected_matrix_lanes = [lane for lane in matrix_lanes if lane in suites]
181171
plan: dict[str, str] = {
182172
"classification": classification,
@@ -191,17 +181,14 @@ def classify(
191181
"run-coverage": str(exhaustive and set(python_lanes) <= suites).lower(),
192182
"run-compatibility": str(exhaustive and set(python_lanes) <= suites).lower(),
193183
}
194-
for suite in (
195-
"lean",
196-
"provider",
197-
"npm",
198-
"static",
199-
"build",
200-
"security",
201-
"duplicate",
202-
"docs",
203-
):
184+
python_set = set(python_lanes)
185+
for suite in suite_names(manifest):
186+
if suite in python_set:
187+
continue
204188
plan[f"run-{suite}"] = str(suite in suites).lower()
189+
# Ensure every catalog boolean key exists even if suite filtering changes.
190+
for key in boolean_run_keys(manifest):
191+
plan.setdefault(key, "false")
205192
return plan
206193

207194

.github/scripts/plan-local-tests

Lines changed: 38 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@
44
from __future__ import annotations
55

66
import argparse
7-
import ast
87
import fnmatch
98
import json
109
import os
1110
import shlex
1211
import subprocess
1312
import sys
1413
import time
14+
import tomllib
1515
from pathlib import Path
1616
from typing import NamedTuple
1717

1818
ROOT = Path(__file__).resolve().parents[2]
1919
CLASSIFIER = ROOT / ".github" / "scripts" / "classify-ci-paths"
2020
OWNERSHIP = ROOT / ".github" / "local-test-ownership.json"
21+
TOPOLOGY_MANIFEST = ROOT / "tests" / "topology.toml"
2122

2223
# The planner scripts live without a package context; make the sibling helper
2324
# importable both when run as a subprocess and when loaded in-process by tests.
@@ -62,6 +63,23 @@ def _catalog() -> dict[str, dict[str, object]]:
6263
return catalog
6364

6465

66+
def _topology_lanes() -> tuple[str, ...]:
67+
"""Return pytest topology lane names from the checked-in manifest."""
68+
69+
raw = tomllib.loads(TOPOLOGY_MANIFEST.read_text(encoding="utf-8"))
70+
lanes = raw.get("lanes", [])
71+
if not isinstance(lanes, list):
72+
raise ValueError("tests/topology.toml lanes must be a list")
73+
names: list[str] = []
74+
for lane in lanes:
75+
if not isinstance(lane, dict) or not isinstance(lane.get("name"), str):
76+
raise ValueError("tests/topology.toml lane entries require a name")
77+
names.append(lane["name"])
78+
if not names:
79+
raise ValueError("tests/topology.toml must declare at least one lane")
80+
return tuple(names)
81+
82+
6583
_CATALOG = _catalog()
6684
COMMANDS = {
6785
name: (str(entry["command"]),)
@@ -70,18 +88,7 @@ COMMANDS = {
7088
}
7189
DEPLOY_COMMAND = str(_CATALOG["deploy"]["command"])
7290

73-
TOPOLOGY_LANES = (
74-
"unit",
75-
"component",
76-
"domain",
77-
"composition",
78-
"storage",
79-
"process",
80-
"mcp",
81-
"provider",
82-
"lean",
83-
"e2e",
84-
)
91+
TOPOLOGY_LANES = _topology_lanes()
8592

8693
# Independent local gates. Security, duplicate-code, compatibility, coverage,
8794
# and platform matrices remain hosted-CI evidence.
@@ -103,6 +110,7 @@ INFRASTRUCTURE_PREFIXES = (
103110
".pre-commit-config.yaml",
104111
".jscpd.json",
105112
"tests/topology.toml",
113+
"tests/plan_manifest.toml",
106114
)
107115

108116

@@ -213,74 +221,6 @@ def changed_entries(base: str) -> list[Change]:
213221
return sorted(entries, key=lambda entry: (entry.path, entry.status))
214222

215223

216-
def source_module(path: str) -> str | None:
217-
source = Path(path)
218-
try:
219-
relative = source.relative_to("src")
220-
except ValueError:
221-
return None
222-
if relative.suffix != ".py":
223-
return None
224-
parts = list(relative.with_suffix("").parts)
225-
if parts[-1] == "__init__":
226-
parts.pop()
227-
return ".".join(parts)
228-
229-
230-
def imports(path: Path) -> set[str] | None:
231-
try:
232-
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
233-
except (OSError, SyntaxError, UnicodeError):
234-
return None
235-
imported: set[str] = set()
236-
for node in ast.walk(tree):
237-
if isinstance(node, ast.Import):
238-
imported.update(alias.name for alias in node.names)
239-
elif isinstance(node, ast.ImportFrom):
240-
module = node.module
241-
if node.level:
242-
try:
243-
relative = path.relative_to(ROOT / "src")
244-
except ValueError:
245-
continue
246-
module_parts = list(relative.with_suffix("").parts)
247-
# Relative imports are resolved from the containing package.
248-
# For __init__.py that package is still its parent directory;
249-
# retaining "__init__" would invent modules such as
250-
# jacobian.pkg.__init__.leaf.
251-
package_parts = module_parts[:-1]
252-
drop = node.level - 1
253-
if drop > len(package_parts):
254-
return None
255-
resolved = package_parts[: len(package_parts) - drop]
256-
if module:
257-
resolved.extend(module.split("."))
258-
module = ".".join(resolved)
259-
if module:
260-
imported.add(module)
261-
imported.update(
262-
f"{module}.{alias.name}"
263-
for alias in node.names
264-
if alias.name != "*"
265-
)
266-
return imported
267-
268-
269-
def directly_imports(imported: set[str], module: str) -> bool:
270-
return any(
271-
imported_module == module or imported_module.startswith(f"{module}.")
272-
for imported_module in imported
273-
)
274-
275-
276-
def test_files() -> list[Path]:
277-
return sorted((ROOT / "tests").rglob("test_*.py"))
278-
279-
280-
def source_files() -> list[Path]:
281-
return sorted((ROOT / "src").rglob("*.py"))
282-
283-
284224
def ownership_overrides() -> dict[str, list[str]] | None:
285225
try:
286226
data = json.loads(OWNERSHIP.read_text(encoding="utf-8"))
@@ -305,15 +245,18 @@ def ownership_overrides() -> dict[str, list[str]] | None:
305245

306246

307247
def exact_tests(entries: list[Change]) -> tuple[list[str], str | None]:
308-
"""Return focused pytest selectors, or a reason selection is ambiguous."""
248+
"""Return focused pytest selectors, or a reason selection is ambiguous.
249+
250+
Exact ownership is limited to documentation skips, ownership overrides, and
251+
changed ``tests/**/test_*.py`` paths. Source modules and other paths fall
252+
back to the selected topology lanes without reverse-import scanning.
253+
"""
309254
if not entries:
310255
return [], None
311256
overrides = ownership_overrides()
312257
if overrides is None:
313258
return [], ".github/local-test-ownership.json: invalid manifest"
314259
selected: set[str] = set()
315-
parsed_tests: dict[Path, set[str]] = {}
316-
parsed_sources: dict[Path, set[str]] = {}
317260

318261
for entry in entries:
319262
path = entry.path
@@ -337,55 +280,25 @@ def exact_tests(entries: list[Change]) -> tuple[list[str], str | None]:
337280
selected.update(override_nodes)
338281
continue
339282

283+
if (
284+
path.startswith("tests/")
285+
and path.endswith(".py")
286+
and Path(path).name.startswith("test_")
287+
):
288+
selected.add(path)
289+
continue
290+
340291
if (
341292
path in HIGH_IMPACT_PATHS
342-
or path.startswith(".github/")
343293
or path.endswith("/conftest.py")
294+
or is_infrastructure_path(path)
344295
):
345296
return [], f"{path}: shared infrastructure has broad impact"
346297

347-
if path.startswith("tests/") and path.endswith(".py"):
348-
if Path(path).name.startswith("test_"):
349-
selected.add(path)
350-
continue
298+
if path.startswith("tests/"):
351299
return [], f"{path}: test support code has broad impact"
352300

353-
module = source_module(path)
354-
if module is None:
355-
return [], f"{path}: no exact Python ownership evidence"
356-
357-
source_path = ROOT / path
358-
if not source_path.is_file():
359-
return [], f"{path}: source is unavailable"
360-
361-
for candidate in source_files():
362-
if candidate == source_path:
363-
continue
364-
imported = parsed_sources.get(candidate)
365-
if imported is None:
366-
imported = imports(candidate)
367-
if imported is None:
368-
return [], f"{candidate.relative_to(ROOT)}: import scan failed"
369-
parsed_sources[candidate] = imported
370-
if directly_imports(imported, module):
371-
return [], (
372-
f"{path}: imported by {candidate.relative_to(ROOT)}; "
373-
"transitive impact is ambiguous"
374-
)
375-
376-
direct = set()
377-
for candidate in test_files():
378-
imported = parsed_tests.get(candidate)
379-
if imported is None:
380-
imported = imports(candidate)
381-
if imported is None:
382-
return [], f"{candidate.relative_to(ROOT)}: import scan failed"
383-
parsed_tests[candidate] = imported
384-
if directly_imports(imported, module):
385-
direct.add(candidate.relative_to(ROOT).as_posix())
386-
if not direct:
387-
return [], f"{path}: no direct importing tests"
388-
selected.update(direct)
301+
return [], f"{path}: no exact ownership; use lane fallback"
389302

390303
return sorted(selected), None
391304

@@ -413,8 +326,6 @@ def _lane_for_path(path: str) -> str | None:
413326
if not manifest.is_file():
414327
return None
415328
try:
416-
import tomllib
417-
418329
raw = tomllib.loads(manifest.read_text(encoding="utf-8"))
419330
except (OSError, ValueError):
420331
return None

0 commit comments

Comments
 (0)