Skip to content

Commit 1c98d4b

Browse files
committed
test: replace generic smoke check with repo-specific runtime contract test
1 parent cc82f5d commit 1c98d4b

1 file changed

Lines changed: 130 additions & 73 deletions

File tree

tests/run_runtime_smoke.py

Lines changed: 130 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/usr/bin/env python3
2-
"""Repository runtime smoke test.
2+
"""Repo-specific runtime contract test.
33
4-
This test intentionally avoids external services and validates:
5-
1) repo has at least one runnable/build marker
6-
2) Python source files in repo compile successfully
4+
This validates that the repository's primary runtime module(s):
5+
- compile successfully
6+
- keep expected runtime entrypoints/routes/contracts
77
"""
88

99
from __future__ import annotations
@@ -12,80 +12,137 @@
1212
import py_compile
1313
import sys
1414

15-
REPO_ROOT = Path(__file__).resolve().parents[1]
16-
17-
SKIP_DIRS = {
18-
".git",
19-
".venv",
20-
"venv",
21-
"node_modules",
22-
"dist",
23-
"build",
24-
"__pycache__",
25-
}
26-
27-
28-
def has_runnable_marker(root: Path) -> bool:
29-
markers = [
30-
"package.json",
31-
"pyproject.toml",
32-
"requirements.txt",
33-
"setup.py",
34-
"manage.py",
35-
"pom.xml",
36-
"build.gradle",
37-
"build.gradle.kts",
38-
"go.mod",
39-
"Cargo.toml",
40-
"Dockerfile",
41-
"docker-compose.yml",
42-
"docker-compose.yaml",
43-
"Makefile",
44-
]
45-
for marker in markers:
46-
if list(root.rglob(marker)):
47-
return True
48-
49-
if list(root.rglob("*.csproj")) or list(root.rglob("*.sln")):
50-
return True
51-
52-
return False
53-
54-
55-
def iter_python_files(root: Path):
56-
for py_file in root.rglob("*.py"):
57-
rel = py_file.relative_to(root)
58-
if any(part in SKIP_DIRS for part in rel.parts):
59-
continue
60-
yield py_file
15+
ROOT = Path(__file__).resolve().parents[1]
16+
17+
18+
def require_file(path: str) -> Path:
19+
p = ROOT / path
20+
if not p.exists():
21+
raise AssertionError(f"Missing expected file: {path}")
22+
return p
23+
24+
25+
def compile_file(path: str) -> None:
26+
p = require_file(path)
27+
py_compile.compile(str(p), doraise=True)
28+
29+
30+
def read_text(path: str) -> str:
31+
return require_file(path).read_text()
32+
33+
34+
def require_contains(path: str, needle: str) -> None:
35+
text = read_text(path)
36+
if needle not in text:
37+
raise AssertionError(f"Expected marker not found in {path}: {needle}")
38+
39+
40+
def ok(msg: str) -> None:
41+
print(f"PASS: {msg}")
42+
43+
44+
def fail(msg: str) -> int:
45+
print(f"FAIL: {msg}")
46+
return 1
6147

6248

6349
def main() -> int:
64-
if not has_runnable_marker(REPO_ROOT):
65-
print("FAIL: no runnable/build marker found")
66-
return 1
50+
try:
51+
repo = ROOT.name
52+
53+
if repo == "FARM-Auth":
54+
compile_file("backend/main.py")
55+
require_contains("backend/main.py", "app = FastAPI()")
56+
require_contains("backend/main.py", "@app.on_event(\"startup\")")
57+
require_contains("backend/main.py", "get_users_router")
58+
require_contains("backend/main.py", "get_todo_router")
59+
ok("FARM-Auth runtime contracts")
60+
61+
elif repo == "FARM-Intro":
62+
compile_file("backend/main.py")
63+
require_contains("backend/main.py", "app = FastAPI()")
64+
require_contains("backend/main.py", "app.include_router(todo_router")
65+
require_contains("backend/main.py", "prefix=\"/task\"")
66+
ok("FARM-Intro runtime contracts")
67+
68+
elif repo == "a2a-mcp-mongodb-multiagents":
69+
compile_file("mcp/main.py")
70+
require_contains("mcp/main.py", "mcp = FastMCP(")
71+
require_contains("mcp/main.py", "@mcp.tool")
72+
require_contains("mcp/main.py", "async def connect_to_mongo")
73+
ok("a2a MCP runtime contracts")
74+
75+
elif repo == "beanie-example":
76+
compile_file("src/beaniecocktails/__init__.py")
77+
compile_file("src/beaniecocktails/scripts/init_db.py")
78+
require_contains("src/beaniecocktails/__init__.py", "app = FastAPI(lifespan=app_lifespan)")
79+
require_contains("src/beaniecocktails/__init__.py", "init_beanie(")
80+
require_contains("src/beaniecocktails/__init__.py", "app.include_router(cocktail_router")
81+
ok("beanie-example runtime contracts")
82+
83+
elif repo == "docbridge":
84+
compile_file("examples/why/why/__init__.py")
85+
require_contains("examples/why/why/__init__.py", "app = FastAPI(lifespan=db_lifespan)")
86+
require_contains("examples/why/why/__init__.py", "@app.get(\"/profiles/{user_id}\")")
87+
require_contains("examples/why/why/__init__.py", "class Profile(Document)")
88+
ok("docbridge runtime contracts")
89+
90+
elif repo == "farm-stack-to-do-app":
91+
compile_file("backend/src/todo/server.py")
92+
require_contains("backend/src/todo/server.py", "app = FastAPI(lifespan=lifespan")
93+
require_contains("backend/src/todo/server.py", "@app.get(\"/api/lists\")")
94+
require_contains("backend/src/todo/server.py", "@app.post(\"/api/lists\"")
95+
require_contains("backend/src/todo/server.py", "@app.patch(\"/api/lists/{list_id}/checked_state\")")
96+
ok("farm-stack-to-do-app runtime contracts")
97+
98+
elif repo == "hr_agentic_chatbot":
99+
compile_file("app.py")
100+
require_contains("app.py", "@cl.on_chat_start")
101+
require_contains("app.py", "@cl.on_message")
102+
require_contains("app.py", "create_workflow(")
103+
ok("hr_agentic_chatbot runtime contracts")
104+
105+
elif repo == "mongodb-atlas-fastapi":
106+
compile_file("app/main.py")
107+
require_contains("app/main.py", "app = FastAPI()")
108+
require_contains("app/main.py", "@app.get(\"/\", response_description=\"Student API HealthCheck\")")
109+
require_contains("app/main.py", "@app.get(\"/students\"")
110+
ok("mongodb-atlas-fastapi runtime contracts")
111+
112+
elif repo == "mongodb-with-starlette":
113+
compile_file("app.py")
114+
require_contains("app.py", "app = Starlette(")
115+
require_contains("app.py", "Route(\"/\", create_student, methods=[\"POST\"])")
116+
require_contains("app.py", "Route(\"/{id}\", delete_student, methods=[\"DELETE\"])")
117+
ok("mongodb-with-starlette runtime contracts")
118+
119+
elif repo == "mongodb-with-tornado":
120+
compile_file("app.py")
121+
require_contains("app.py", "class MainHandler(tornado.web.RequestHandler)")
122+
require_contains("app.py", "app = tornado.web.Application(")
123+
require_contains("app.py", "(r\"/(?P<student_id>\\w+)\", MainHandler)")
124+
ok("mongodb-with-tornado runtime contracts")
125+
126+
elif repo == "mongodb-with-sanic":
127+
compile_file("app.py")
128+
require_contains("app.py", "app = Sanic(__name__)")
129+
require_contains("app.py", "@app.route(\"/\", methods=[\"POST\"])")
130+
require_contains("app.py", "@app.route(\"/<id>\", methods=[\"DELETE\"])")
131+
ok("mongodb-with-sanic runtime contracts")
132+
133+
elif repo == "celeb-matcher-farm":
134+
compile_file("backend/src/server.py")
135+
require_contains("backend/src/server.py", "app = FastAPI(lifespan=lifespan")
136+
require_contains("backend/src/server.py", "@app.post(\"/api/search\")")
137+
require_contains("backend/src/server.py", "class SearchPayload(BaseModel)")
138+
ok("celeb-matcher-farm runtime contracts")
139+
140+
else:
141+
raise AssertionError(f"No repository-specific contract defined for: {repo}")
67142

68-
py_files = list(iter_python_files(REPO_ROOT))
69-
if not py_files:
70-
print("PASS: no python files to compile; marker checks passed")
71143
return 0
72-
73-
failures = []
74-
for py_file in py_files:
75-
try:
76-
py_compile.compile(str(py_file), doraise=True)
77-
except py_compile.PyCompileError as exc:
78-
failures.append((py_file, exc.msg))
79-
80-
if failures:
81-
print(f"FAIL: {len(failures)} python files failed to compile")
82-
for py_file, msg in failures[:20]:
83-
rel = py_file.relative_to(REPO_ROOT)
84-
print(f" - {rel}: {msg}")
85-
return 1
86-
87-
print(f"PASS: compiled {len(py_files)} python files")
88-
return 0
144+
except Exception as exc:
145+
return fail(str(exc))
89146

90147

91148
if __name__ == "__main__":

0 commit comments

Comments
 (0)