Skip to content

Commit a9d60e1

Browse files
committed
fix: defer contrib discovery in root makefile
1 parent f63b56f commit a9d60e1

2 files changed

Lines changed: 174 additions & 4 deletions

File tree

Makefile

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: help sync openapi-spec openapi-spec-check test test-extras test-all models-test test-models test-sdk lint lint-fix typecheck check build build-models build-server build-sdk publish publish-models publish-server publish-sdk hooks-install hooks-uninstall prepush evaluators-test evaluators-lint evaluators-lint-fix evaluators-typecheck evaluators-build contrib-test contrib-lint contrib-lint-fix contrib-typecheck contrib-build sdk-ts-generate sdk-ts-overlay-test sdk-ts-name-check sdk-ts-generate-check sdk-ts-build sdk-ts-test sdk-ts-lint sdk-ts-typecheck sdk-ts-release-check sdk-ts-publish-dry-run sdk-ts-publish telemetry-test telemetry-lint telemetry-lint-fix telemetry-typecheck telemetry-build telemetry-publish
1+
.PHONY: help sync openapi-spec openapi-spec-check test test-extras test-all scripts-test models-test test-models test-sdk lint lint-fix typecheck check build build-models build-server build-sdk publish publish-models publish-server publish-sdk hooks-install hooks-uninstall prepush evaluators-test evaluators-lint evaluators-lint-fix evaluators-typecheck evaluators-build contrib-test contrib-lint contrib-lint-fix contrib-typecheck contrib-build sdk-ts-generate sdk-ts-overlay-test sdk-ts-name-check sdk-ts-generate-check sdk-ts-build sdk-ts-test sdk-ts-lint sdk-ts-typecheck sdk-ts-release-check sdk-ts-publish-dry-run sdk-ts-publish telemetry-test telemetry-lint telemetry-lint-fix telemetry-typecheck telemetry-build telemetry-publish
22

33
# Workspace package names
44
PACK_MODELS := agent-control-models
@@ -19,11 +19,11 @@ TELEMETRY_DIR := telemetry
1919
EVALUATORS_DIR := evaluators/builtin
2020
CONTRIB_DIR := evaluators/contrib
2121
UI_DIR := ui
22-
CONTRIB_PACKAGE_NAMES := $(shell python3 scripts/contrib_packages.py names)
2322

2423
define run-contrib-target
2524
@set -e; \
26-
for package in $(CONTRIB_PACKAGE_NAMES); do \
25+
packages=$$(python3 scripts/contrib_packages.py names); \
26+
for package in $$packages; do \
2727
$(MAKE) -C $(CONTRIB_DIR)/$$package $(1); \
2828
done
2929
endef
@@ -42,6 +42,7 @@ help:
4242
@echo ""
4343
@echo "Test:"
4444
@echo " make test - run tests for core packages and all discovered contrib evaluators"
45+
@echo " make scripts-test - run root contrib packaging contract tests"
4546
@echo " make models-test - run shared model tests with coverage"
4647
@echo " make test-extras - run tests for all discovered contrib evaluators"
4748
@echo " make test-all - alias for make test"
@@ -92,7 +93,10 @@ openapi-spec-check: openapi-spec
9293
# Test
9394
# ---------------------------
9495

95-
test: models-test telemetry-test server-test engine-test sdk-test evaluators-test contrib-test
96+
test: scripts-test models-test telemetry-test server-test engine-test sdk-test evaluators-test contrib-test
97+
98+
scripts-test:
99+
uv run --with pytest pytest scripts/tests -q
96100

97101
models-test:
98102
cd $(MODELS_DIR) && uv run pytest --cov=src --cov-report=xml:../coverage-models.xml -q
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
"""Tests for contrib package discovery and verification."""
2+
3+
from __future__ import annotations
4+
5+
import importlib.util
6+
import sys
7+
from pathlib import Path
8+
from textwrap import dedent
9+
from types import ModuleType
10+
11+
import pytest
12+
13+
SCRIPT_PATH = Path(__file__).resolve().parents[1] / "contrib_packages.py"
14+
15+
16+
def _load_module() -> ModuleType:
17+
"""Load the contrib package script as a module for testing."""
18+
19+
module_name = "contrib_packages_under_test"
20+
spec = importlib.util.spec_from_file_location(module_name, SCRIPT_PATH)
21+
if spec is None or spec.loader is None:
22+
raise RuntimeError(f"Unable to load module from {SCRIPT_PATH}")
23+
24+
module = importlib.util.module_from_spec(spec)
25+
sys.modules[module_name] = module
26+
spec.loader.exec_module(module)
27+
return module
28+
29+
30+
def _write_text(path: Path, contents: str) -> None:
31+
"""Write a text file, creating parent directories first."""
32+
33+
path.parent.mkdir(parents=True, exist_ok=True)
34+
path.write_text(dedent(contents).strip() + "\n")
35+
36+
37+
def _write_fake_repo(
38+
root: Path,
39+
*,
40+
include_version_entry: bool = True,
41+
include_builtin_extra: bool = True,
42+
include_builtin_source: bool = True,
43+
) -> None:
44+
"""Create a minimal repo layout that exercises contrib package wiring."""
45+
46+
version_entry = (
47+
'"evaluators/contrib/example/pyproject.toml:project.version"'
48+
if include_version_entry
49+
else ""
50+
)
51+
extra_entry = (
52+
'example = ["agent-control-evaluator-example>=1.0.0"]'
53+
if include_builtin_extra
54+
else ""
55+
)
56+
source_entry = (
57+
'agent-control-evaluator-example = { path = "../contrib/example", editable = true }'
58+
if include_builtin_source
59+
else ""
60+
)
61+
62+
_write_text(
63+
root / "pyproject.toml",
64+
f"""
65+
[project]
66+
name = "agent-control"
67+
version = "1.0.0"
68+
69+
[tool.semantic_release]
70+
version_toml = [
71+
"pyproject.toml:project.version",
72+
{version_entry}
73+
]
74+
""",
75+
)
76+
_write_text(
77+
root / "evaluators" / "builtin" / "pyproject.toml",
78+
f"""
79+
[project]
80+
name = "agent-control-evaluators"
81+
version = "1.0.0"
82+
83+
[project.optional-dependencies]
84+
dev = []
85+
{extra_entry}
86+
87+
[tool.uv.sources]
88+
agent-control-models = {{ workspace = true }}
89+
{source_entry}
90+
""",
91+
)
92+
_write_text(
93+
root / "evaluators" / "contrib" / "example" / "pyproject.toml",
94+
"""
95+
[project]
96+
name = "agent-control-evaluator-example"
97+
version = "1.0.0"
98+
99+
[project.entry-points."agent_control.evaluators"]
100+
example = "agent_control_evaluator_example:ExampleEvaluator"
101+
""",
102+
)
103+
104+
105+
def test_discover_contrib_packages_skips_template_and_non_packages(
106+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
107+
) -> None:
108+
# Given: a fake repo with one real contrib package plus ignored directories
109+
module = _load_module()
110+
repo_root = tmp_path / "repo"
111+
_write_fake_repo(repo_root)
112+
(repo_root / "evaluators" / "contrib" / "template").mkdir(parents=True)
113+
(repo_root / "evaluators" / "contrib" / "notes").mkdir(parents=True)
114+
monkeypatch.setattr(module, "REPO_ROOT", repo_root)
115+
monkeypatch.setattr(module, "CONTRIB_ROOT", repo_root / "evaluators" / "contrib")
116+
117+
# When: discovering contrib packages
118+
packages = module.discover_contrib_packages()
119+
120+
# Then: only the real package is returned
121+
assert [package.name for package in packages] == ["example"]
122+
assert packages[0].directory == "evaluators/contrib/example"
123+
assert packages[0].package == "agent-control-evaluator-example"
124+
125+
126+
def test_verify_contrib_packages_reports_missing_root_and_builtin_wiring(
127+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
128+
) -> None:
129+
# Given: a contrib package that is missing version, extra, and source wiring
130+
module = _load_module()
131+
repo_root = tmp_path / "repo"
132+
_write_fake_repo(
133+
repo_root,
134+
include_version_entry=False,
135+
include_builtin_extra=False,
136+
include_builtin_source=False,
137+
)
138+
monkeypatch.setattr(module, "REPO_ROOT", repo_root)
139+
monkeypatch.setattr(module, "CONTRIB_ROOT", repo_root / "evaluators" / "contrib")
140+
141+
# When: verifying the contrib package wiring
142+
packages = module.discover_contrib_packages()
143+
errors = module.verify_contrib_packages(packages)
144+
145+
# Then: the missing contract pieces are reported explicitly
146+
assert any("Missing semantic-release version wiring" in error for error in errors)
147+
assert any("Missing builtin extra" in error for error in errors)
148+
assert any("Missing uv source" in error for error in errors)
149+
150+
151+
def test_verify_contrib_packages_accepts_complete_wiring(
152+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
153+
) -> None:
154+
# Given: a contrib package with complete root and builtin wiring
155+
module = _load_module()
156+
repo_root = tmp_path / "repo"
157+
_write_fake_repo(repo_root)
158+
monkeypatch.setattr(module, "REPO_ROOT", repo_root)
159+
monkeypatch.setattr(module, "CONTRIB_ROOT", repo_root / "evaluators" / "contrib")
160+
161+
# When: verifying the contrib package wiring
162+
packages = module.discover_contrib_packages()
163+
errors = module.verify_contrib_packages(packages)
164+
165+
# Then: the wiring is accepted without errors
166+
assert errors == []

0 commit comments

Comments
 (0)