ci: improve frontend verification and review timeouts (#3634)

This commit is contained in:
Carlos Florêncio
2026-09-13 12:06:08 +01:00
committed by GitHub
parent bd7974dc8d
commit 2c4058c305
25 changed files with 1936 additions and 81 deletions
@@ -31,6 +31,16 @@ def workflow_step(workflow: str, name: str) -> str:
return remainder.partition("\n - name:")[0]
def job_block(workflow: str, job: str, next_job: str | None) -> str:
marker = f" {job}:\n"
_, separator, remainder = workflow.partition(marker)
if not separator:
raise AssertionError(f"Workflow has no {job} job")
if next_job is None:
return remainder
return remainder.partition(f"\n {next_job}:\n")[0]
class ClaudeCodeReviewWorkflowContractTest(unittest.TestCase):
def test_review_workflow_ignores_pr_updates(self) -> None:
workflow = WORKFLOW.read_text(encoding="utf-8")
@@ -67,6 +77,27 @@ class ClaudeCodeReviewWorkflowContractTest(unittest.TestCase):
workflow,
)
def test_claude_execution_jobs_have_a_thirty_minute_budget(self) -> None:
review_workflow = WORKFLOW.read_text(encoding="utf-8")
for job, next_job in (
("claude-review-same-repo", "label-allowlisted-fork"),
("claude-review-fork", None),
):
block = job_block(review_workflow, job, next_job)
self.assertRegex(
block,
r"runs-on: ubuntu-latest\n timeout-minutes: 30\n",
f"{job} must bound Claude execution at 30 minutes",
)
mention_workflow = MENTION_WORKFLOW.read_text(encoding="utf-8")
block = job_block(mention_workflow, "claude", None)
self.assertRegex(
block,
r"runs-on: ubuntu-latest\n timeout-minutes: 30\n",
"the shared interactive Claude job must bound execution at 30 minutes",
)
def test_manual_pr_review_does_not_checkout_untrusted_content(self) -> None:
workflow = MENTION_WORKFLOW.read_text(encoding="utf-8")
@@ -107,7 +107,7 @@ class ExternalRunnerWorkflowContractTest(unittest.TestCase):
self.assertIn("runs-on: ubuntu-latest" if job == "postgres-boot" else "runs-on: windows-latest", protected)
self.assertNotIn("runner_plan", protected)
def test_frontend_uses_planner_for_test_and_gate(self) -> None:
def test_frontend_uses_planner_for_unsharded_tests_and_gate(self) -> None:
workflow = self.assert_planner(
"frontend-tests.yml",
"frontend",
@@ -121,6 +121,9 @@ class ExternalRunnerWorkflowContractTest(unittest.TestCase):
"runs-on: ${{ fromJSON(needs.runner_plan.outputs.plan).frontend_runner }}",
job_block(workflow, "frontend", "frontend-gate"),
)
frontend = job_block(workflow, "frontend", "frontend-gate")
self.assertNotIn("matrix:", frontend)
self.assertNotIn("frontend_tests", workflow)
self.assertIn(
"runs-on: ${{ fromJSON(needs.runner_plan.outputs.plan).frontend_gate_runner }}",
job_block(workflow, "frontend-gate", None),
@@ -40,6 +40,22 @@ def run_tests_step(workflow: str) -> str:
return remainder.partition(NEXT_STEP_MARKER)[0]
def frontend_job(workflow: str) -> str:
"""Return the frontend job body, up to the required gate."""
_, separator, remainder = workflow.partition(" frontend:\n")
if not separator:
raise AssertionError("frontend-tests.yml has no frontend job")
return remainder.partition("\n frontend-gate:")[0]
def frontend_gate_job(workflow: str) -> str:
"""Return the required frontend gate body."""
_, separator, remainder = workflow.partition(" frontend-gate:\n")
if not separator:
raise AssertionError("frontend-tests.yml has no frontend-gate job")
return remainder
def trigger_block(workflow: str, trigger: str) -> str:
"""Return `trigger`'s block under `on:`, up to the next top-level key."""
block = re.search(rf"(?m)^ {trigger}:\n(?:^ {{4}}.*\n|^\n)*", workflow)
@@ -49,6 +65,78 @@ def trigger_block(workflow: str, trigger: str) -> str:
class FrontendTestsWorkflowContractTest(unittest.TestCase):
def test_cache_resolves_the_container_pnpm_store_before_restore(self) -> None:
workflow = WORKFLOW.read_text(encoding="utf-8")
job = frontend_job(workflow)
resolve_index = job.find("- name: Resolve pnpm store path")
cache_index = job.find("- name: Cache pnpm store")
install_index = job.find("- name: Install dependencies")
self.assertGreaterEqual(resolve_index, 0, "store resolution step is missing")
self.assertGreaterEqual(cache_index, 0, "cache step is missing")
self.assertGreaterEqual(install_index, 0, "dependency installation step is missing")
self.assertLess(resolve_index, cache_index)
self.assertLess(cache_index, install_index)
resolve_step = job[resolve_index:cache_index]
cache_step = job[cache_index:install_index]
self.assertIn("id: pnpm-store", resolve_step)
self.assertIn("pnpm store path --silent", resolve_step)
self.assertIn('pnpm --version', resolve_step)
self.assertIn('mkdir -p "${STORE_PATH}"', resolve_step)
self.assertIn('printf \'path=%s\\n\' "${STORE_PATH}"', resolve_step)
self.assertIn("path: ${{ steps.pnpm-store.outputs.path }}", cache_step)
self.assertIn("runner.os", cache_step)
self.assertIn("runner.arch", cache_step)
self.assertIn("steps.pnpm-store.outputs.version", cache_step)
self.assertIn("hashFiles('apps/pnpm-lock.yaml')", cache_step)
self.assertIn(
"restore-keys: pnpm-${{ runner.os }}-${{ runner.arch }}-${{ steps.pnpm-store.outputs.version }}-",
cache_step,
)
self.assertIn("continue-on-error: true", cache_step)
def test_cache_failure_does_not_replace_frozen_installation(self) -> None:
workflow = WORKFLOW.read_text(encoding="utf-8")
job = frontend_job(workflow)
cache_index = job.index("- name: Cache pnpm store")
install_index = job.index("- name: Install dependencies")
install_step = job[install_index:].partition(NEXT_STEP_MARKER)[0]
self.assertLess(cache_index, install_index)
self.assertIn("pnpm install --frozen-lockfile", install_step)
def test_frontend_job_keeps_unsharded_unit_tests_and_static_checks(self) -> None:
workflow = WORKFLOW.read_text(encoding="utf-8")
job = frontend_job(workflow)
self.assertIn("- name: Run tests\n", job)
self.assertIn("NODE_ENV: production", job)
self.assertIn("pnpm --filter @kandev/web test", job)
self.assertIn("- name: Run lint\n", job)
self.assertIn("- name: Run typecheck\n", job)
self.assertIn("- name: Build\n", job)
self.assertNotIn("FRONTEND_TEST_SHARD", job)
self.assertNotIn("matrix:", job)
def test_frontend_tests_remain_outside_the_planner_until_adoption_evidence(self) -> None:
workflow = WORKFLOW.read_text(encoding="utf-8")
planner = workflow.partition(" changes:\n")[0]
self.assertNotIn('"name":"frontend_tests"', planner)
self.assertNotIn(" frontend_tests:\n", workflow)
def test_required_gate_requires_every_frontend_verification_result(self) -> None:
workflow = WORKFLOW.read_text(encoding="utf-8")
gate = frontend_gate_job(workflow)
self.assertIn(
"needs: [changes, frontend, runner_plan]",
gate,
)
self.assertNotIn("FRONTEND_TESTS_RESULT:", gate)
self.assertNotIn("Frontend test matrix finished with result:", gate)
def test_test_step_reproduces_the_production_environment(self) -> None:
step = run_tests_step(WORKFLOW.read_text(encoding="utf-8"))
+17
View File
@@ -23,6 +23,11 @@ E2E_FAMILIES = [
]
FRONTEND_FAMILIES = [
{"name": "frontend", "tier": "standard"},
{
"name": "frontend_tests",
"tier": "standard",
"instances": [{"shard": 1, "total": 2}, {"shard": 2, "total": 2}],
},
{"name": "frontend_gate", "tier": "light"},
]
BACKEND_MATRIX_FAMILY = [
@@ -62,6 +67,12 @@ class RunnerPlanTest(unittest.TestCase):
self.assertEqual(plan.plan["frontend_runner"], "ubuntu-latest")
self.assertEqual(plan.plan["frontend_gate_runner"], "ubuntu-latest")
self.assertTrue(
all(
item["runner"] == "ubuntu-latest"
for item in plan.plan["frontend_tests_matrix"]["include"]
)
)
def test_matrix_percentage_uses_floor_and_is_stable(self) -> None:
first = runner_plan.build_plan(
@@ -217,6 +228,12 @@ class RunnerPlanTest(unittest.TestCase):
self.assertEqual(plan.plan["frontend_runner"], "standard-runner")
self.assertEqual(plan.plan["frontend_gate_runner"], "light-runner")
self.assertTrue(
all(
item["runner"] == "standard-runner"
for item in plan.plan["frontend_tests_matrix"]["include"]
)
)
if __name__ == "__main__":