-
-
Notifications
You must be signed in to change notification settings - Fork 0
319 lines (273 loc) · 12.3 KB
/
Copy pathe2e.yml
File metadata and controls
319 lines (273 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# SPDX-License-Identifier: MPL-2.0
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
#
# Full test suite for BoJ Server: E2E, aspect tests, and benchmarks.
# Covers all merge requirement categories: P2P (zig-test.yml), E2E (here),
# aspect, execution, lifecycle, and benchmarks.
name: E2E + Aspect + Bench
# Was workflow-level path-filtered: required checks that never ran on PRs
# touching none of its paths, leaving them "Expected" and the PR blocked.
# Now always runs; the `changes` job gates the heavy jobs, which when skipped
# report SUCCESS to required checks.
on:
push:
branches: [main, master, develop]
pull_request:
branches: [main, master]
workflow_dispatch:
permissions: read-all
concurrency:
group: e2e-${{ github.ref }}
cancel-in-progress: true
jobs:
changes:
name: Detect relevant changes
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
run: ${{ steps.detect.outputs.run }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- id: detect
env:
EVENT: ${{ github.event_name }}
BASE: ${{ github.base_ref }}
BEFORE: ${{ github.event.before }}
run: |
set -uo pipefail
run=true
if [ "$EVENT" = pull_request ]; then
git fetch --no-tags --depth=200 origin "$BASE" 2>/dev/null \
&& changed=$(git diff --name-only "origin/${BASE}...HEAD" 2>/dev/null) \
&& { printf '%s\n' "$changed" | grep -qE '^adapter/|^cartridges/|^ffi/|^mcp-bridge/|^tests/|^src/' && run=true || run=false; }
elif [ "$EVENT" = push ] && [ -n "$BEFORE" ] && [ "$BEFORE" != 0000000000000000000000000000000000000000 ]; then
changed=$(git diff --name-only "${BEFORE}...${GITHUB_SHA}" 2>/dev/null) \
&& { printf '%s\n' "$changed" | grep -qE '^adapter/|^cartridges/|^ffi/|^mcp-bridge/|^tests/|^src/' && run=true || run=false; }
fi
printf 'run=%s\n' "$run" >> "$GITHUB_OUTPUT"
echo "relevant=$run; changed files:"; printf '%s\n' "${changed:-<none computed>}"
# ─── End-to-End: Full REST + MCP Bridge ────────────────────────────
e2e-full:
name: E2E — Full REST + MCP Bridge
needs: changes
if: needs.changes.outputs.run == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Zig
uses: goto-bus-stop/setup-zig@9566bb3e8749893055694249726756f25e099b30 # v2
with:
version: 0.15.1
- name: Install Deno
uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
with:
deno-version: v2.x
- name: Install Elixir + OTP
# tests/e2e_full.sh requires `mix` on PATH to start the Elixir
# backend (elixir/ — `mix run --no-halt`). Pinned to match the
# estate convention (see hypatia-scan.yml across the org).
uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.18.2
with:
elixir-version: '1.18'
otp-version: '27'
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y curl jq
- name: Fetch Elixir deps
working-directory: elixir
run: mix deps.get
- name: Compile Elixir deps
# Pre-compile so the in-test `mix run --no-halt` doesn't have
# to do it on the critical path of the 60s server-start window.
working-directory: elixir
run: mix compile
- name: Build FFI libraries
run: |
# Subshell keeps the cd contained so the cartridge loop below runs
# from the repo root (otherwise its glob never matches). `zig build
# invoke` builds the boj-invoke CLI the Elixir Invoker shells out to
# for tool dispatch — without it every FFI invoke returns {} (cli_missing).
(cd ffi/zig && zig build && zig build invoke)
for cart in cartridges/*/ffi; do
if [ -f "$cart/build.zig" ]; then (cd "$cart" && zig build) || true; fi
done
- name: Run E2E full test suite
run: bash tests/e2e_full.sh
- name: Upload test logs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: e2e-full-logs
path: /tmp/boj-e2e-test.*
retention-days: 7
# ─── End-to-End: Order Ticket (FFI layer) ──────────────────────────
e2e-order-ticket:
name: E2E — Order Ticket (FFI layer)
needs: changes
if: needs.changes.outputs.run == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Zig
uses: goto-bus-stop/setup-zig@9566bb3e8749893055694249726756f25e099b30 # v2
with:
version: 0.15.1
- name: Build FFI libraries
run: cd ffi/zig && zig build
- name: Run order ticket E2E
run: bash tests/order_ticket_e2e.sh
# ─── Aspect Tests: Cross-Cutting Concerns ──────────────────────────
aspect-tests:
name: Aspect — Thread Safety + ABI Contract + SPDX
needs: changes
if: needs.changes.outputs.run == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run aspect tests
run: bash tests/aspect_tests.sh
# ─── Benchmarks: Performance Regression Detection ──────────────────
benchmarks:
name: Bench — FFI Catalogue + Mount/Unmount + Hash
needs: changes
if: needs.changes.outputs.run == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Zig
uses: goto-bus-stop/setup-zig@9566bb3e8749893055694249726756f25e099b30 # v2
with:
version: 0.15.1
- name: Build FFI libraries
run: cd ffi/zig && zig build
- name: Run benchmarks
run: cd ffi/zig && zig build bench
- name: Upload benchmark results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: benchmark-results
path: ffi/zig/zig-out/bench*
retention-days: 30
# ─── Bridge Tests: unit suite + boot smoke × {Node, Deno, Bun} ─────
# The README blesses three runtimes for the zero-dependency bridge:
# Deno (documented runtime), Node (the npx install path every client
# quickstart uses), and Bun (zero-install). Only Deno is exercised by
# development, so a runtime leak in the other two ships silently — a
# bare `Deno.env` reference once broke the entire npx path at import
# time (fixed in PR #211). This job runs the full unit suite AND an
# MCP initialize → tools/list boot smoke of main.js under each
# runtime. The smoke matters separately: the unit suite imports lib/
# but never boots main.js, whose stdio/exit paths are runtime-gated.
bridge-tests:
name: Bridge — ${{ matrix.runtime }} (unit + boot smoke)
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
include:
- runtime: node
unit: node --test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/http_transport_test.js mcp-bridge/tests/path_claims_test.js
boot: node mcp-bridge/main.js
- runtime: deno
unit: deno test --allow-read --allow-env --allow-run --allow-net mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/http_transport_test.js mcp-bridge/tests/path_claims_test.js
# Scoped perms matching main.js's shebang, NOT -A: the boot smoke
# must exercise the exact permission set a real install uses, or it
# would mask a missing-grant bug that scoped users would hit.
boot: deno run --allow-net --allow-env --allow-read mcp-bridge/main.js
- runtime: bun
unit: bun test mcp-bridge/tests/dispatch_test.js mcp-bridge/tests/http_transport_test.js mcp-bridge/tests/path_claims_test.js
boot: bun mcp-bridge/main.js
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
# Node is needed on every leg: it is the subject on the node
# leg and the boot-smoke orchestrator on all three.
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '22'
- name: Install Deno
if: matrix.runtime == 'deno'
uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
with:
deno-version: v2.x
- name: Install Bun
if: matrix.runtime == 'bun'
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: '1.x'
- name: Unit suite (${{ matrix.runtime }})
run: ${{ matrix.unit }}
- name: Boot smoke (${{ matrix.runtime }})
run: node mcp-bridge/tests/boot_smoke.js ${{ matrix.boot }}
# ─── Bench Bridge: mcp-bridge JS perf (path-claims) ────────────────
# Separate job from `benchmarks` above because the bridge has no Zig
# toolchain dependency — keeps logs untangled and lets the JS bench
# run in parallel on its own runner. Posts a sticky PR comment so
# deltas across pushes are visible inline.
bench-bridge:
name: Bench — mcp-bridge (path-claims)
needs: changes
if: needs.changes.outputs.run == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
# Per-job override: only this step needs to write PR comments.
# Workflow-level permissions stay read-all.
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '22'
- name: Run bridge bench
run: node mcp-bridge/tests/path_claims_bench.js | tee bench-bridge.txt
- name: Upload bridge bench artifact
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: bench-bridge-results
path: bench-bridge.txt
retention-days: 30
- name: Sticky PR comment with bench numbers
if: github.event_name == 'pull_request'
# Advisory — a comment failure must never gate the bench job.
# Same reasoning as the hypatia-scan PR-comment step.
continue-on-error: true
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v7
with:
script: |
const fs = require('fs');
const MARKER = '<!-- bench-bridge:path-claims -->';
const output = fs.readFileSync('bench-bridge.txt', 'utf8');
const body = `${MARKER}\n## 🏁 path-claims bench\n\nCommit \`${context.sha.slice(0, 7)}\`\n\n<details open><summary>Numbers</summary>\n\n\`\`\`\n${output}\n\`\`\`\n</details>\n\n*Host-dependent — compare deltas across commits, not absolute values.*`;
const { owner, repo } = context.repo;
const issue_number = context.issue.number;
const existing = await github.paginate(
github.rest.issues.listComments,
{ owner, repo, issue_number, per_page: 100 },
);
const prior = existing.find((c) => c.body && c.body.includes(MARKER));
if (prior) {
await github.rest.issues.updateComment({
owner, repo, comment_id: prior.id, body,
});
} else {
await github.rest.issues.createComment({
owner, repo, issue_number, body,
});
}