Repository navigation
495 lines (419 loc) · 19.7 KB
/
Copy pathunit-tests.yml
File metadata and controls
495 lines (419 loc) · 19.7 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
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
name: Unit Tests
on:
push:
branches: ["*"]
pull_request:
branches: ["*"]
jobs:
# Build frontend once and share across all jobs
build-frontend:
name: Build Frontend
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Node.js
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
run: cd frontend && npm ci
- name: Build frontend
run: cd frontend && npm run build
- name: Upload frontend artifact
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: frontend-dist
path: frontend/dist/
retention-days: 1
verify-oas:
name: Verify OpenAPI Artifacts
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.26"
- name: Install swag
run: go install github.com/swaggo/swag/v2/cmd/swag@v2.0.0-rc4
- name: Regenerate and verify OpenAPI spec
run: scripts/verify-oas.sh
# Review round 8, finding 5: proves the recursive process-tree walk
# (descendant_pids, scripts/descendant-pids.sh) the E2E cleanup trap uses
# to reap only what a run itself spawned. Hermetic (spawns only its own
# sleep/bash processes) — no build, frontend or binary needed — so it runs
# standalone rather than piggybacking on the `test` job below. Before this,
# neither this nor the heavier test-e2e-cleanup-check.sh (see
# release-qa-gate.yml's suite-api-e2e job) was wired into any workflow, so
# a regression reintroducing the blanket `pkill -f "mcpproxy.*serve"` this
# PR removes, or breaking descendant_pids' recursion, would pass CI
# undetected.
test-descendant-pids:
name: Descendant-PIDs Unit Test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Run descendant_pids unit test
run: make test-descendant-pids
# Spec 109 SC-005 (T147): no first-party surface calls POST
# /servers/{id}/unquarantine, and the one-click approvals 109-f removed stay
# removed. Hermetic grep; the self-test proves the check can fail.
check-no-unquarantine-callers:
name: No Unquarantine Callers
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Self-test the checker
run: bash scripts/check-no-unquarantine-callers.test.sh
- name: Check the repository
run: bash scripts/check-no-unquarantine-callers.sh
# Spec 116 FR-009 (T002): storage's upstreams rows are written only by the
# config commit (internal/runtime/commit*.go). Hermetic grep; the self-test
# proves the check can fail.
check-upstream-write-sites:
name: Upstream Write Sites
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Self-test the checker
run: bash scripts/check-upstream-write-sites.test.sh
- name: Check the repository
run: bash scripts/check-upstream-write-sites.sh
test:
name: Unit Tests
needs: [build-frontend, verify-oas]
runs-on: ${{ matrix.os }}
env:
GO111MODULE: "on"
strategy:
matrix:
# On PRs: test only ubuntu-latest. On main/push: test all OSes.
# Go is pinned to "1.26" because go.mod requires go 1.26.0 — older
# toolchains can't build the module, and auto-downloading 1.26.x under
# setup-go's module cache collides ("tar: ... File exists").
os: ${{ github.event_name == 'pull_request' && fromJSON('["ubuntu-latest"]') || fromJSON('["ubuntu-latest", "macos-latest", "windows-latest"]') }}
go-version: ["1.26"]
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: ${{ matrix.go-version }}
cache: true
- name: Download dependencies
run: go mod download
- name: Verify dependencies
run: go mod verify
# Spec 083: the bench/arms tscg tests spawn the committed Node shim
# (bench/tscg/shim.mjs) and hard-fail (never skip) under CI per FR-006, so
# the tscg runtime must be provisioned wherever `go test ./...` runs.
- name: Set up Node.js (TSCG bench arm)
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: bench/tscg/package-lock.json
- name: Install pinned TSCG shim dependencies (FR-006)
run: npm ci --prefix bench/tscg
- name: Download frontend artifact
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: frontend-dist
path: frontend/dist
- name: Copy frontend dist for embedding
if: matrix.os != 'windows-latest'
run: |
rm -rf web/frontend
mkdir -p web/frontend
cp -r frontend/dist web/frontend/
- name: Copy frontend dist for embedding (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
if (Test-Path web/frontend) { Remove-Item -Recurse -Force web/frontend }
New-Item -ItemType Directory -Force -Path web/frontend
Copy-Item -Recurse frontend/dist web/frontend/
# Warm the tiktoken vocabulary ONCE, before the parallel test run.
#
# tiktoken-go downloads the BPE on first use into a shared temp dir and
# finishes with an atomic rename. `go test ./...` runs packages in
# parallel, so several test binaries race to download the same file, and
# on Windows the loser hits "Access is denied" on the rename and the
# package fails — observed as TestBaseline_CountToolWithSchemaParity
# flaking in bench/arms with no code change anywhere near it.
#
# Setting TIKTOKEN_CACHE_DIR alone does NOT fix this: it only names a
# cache, and an empty one still races. Populating it first does, because
# every later tokenizer construction is a cache hit with no rename.
- name: Warm the tiktoken vocabulary cache
run: go run ./bench/cmd/warmtiktoken
env:
TIKTOKEN_CACHE_DIR: ${{ runner.temp }}/tiktoken
# internal/server alone takes 450-600s on windows-latest under -race with
# atomic coverage (the E2E workflow's -short, coverage-free run of the
# same package takes ~450s), so the default 10m package timeout is one
# slow runner away from "panic: test timed out" — which is how the leg
# failed on the first green-again push after #1266. It has since grown to
# ~1100-1200s, so the bound is 40m to leave real headroom.
- name: Run unit tests (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: go test -v -race -timeout 40m '-coverprofile=coverage.out' -covermode=atomic '-skip=E2E|Binary|MCPProtocol|TestInfoEndpoint|TestGracefulShutdownNoPanic|TestSocketInfoEndpoint' ./...
env:
TIKTOKEN_CACHE_DIR: ${{ runner.temp }}/tiktoken
# internal/server under -race takes ~10 minutes on the macOS runner and
# grows with every spec's tests, so the default 10m package timeout
# ("panic: test timed out") is no longer enough there; match the
# Windows leg's 20m.
- name: Run unit tests (Unix)
if: matrix.os != 'windows-latest'
run: go test -v -race -timeout 20m -coverprofile=coverage.out -skip "E2E|Binary|MCPProtocol|TestInfoEndpoint|TestGracefulShutdownNoPanic|TestSocketInfoEndpoint" ./...
env:
TIKTOKEN_CACHE_DIR: ${{ runner.temp }}/tiktoken
- name: Generate coverage report (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: go tool cover '-html=coverage.out' -o coverage.html
- name: Generate coverage report (Unix)
if: matrix.os != 'windows-latest'
run: go tool cover -html=coverage.out -o coverage.html
- name: Upload coverage to Codecov
if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.26'
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
file: ./coverage.out
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
- name: Upload coverage artifacts
if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.26'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: coverage-report
path: |
coverage.out
coverage.html
# Advisory shuffle lane: the same test set as "Unit Tests" on ubuntu, run
# with -shuffle=on so tests that only pass because of file/declaration order
# fail here instead of by luck. Motivation: #1222 — two fixtures flipped the
# process-global registry SSRF allow-policy and never restored it, and the
# CWE-918 regression test passed only because its file sorted first.
#
# This job is deliberately NOT in the branch-protection required list, so a
# newly exposed order coupling in an unrelated package does not block a PR
# while the tail of such tests is being found. Promote it to required once
# it has a run of green results on main.
#
# Reproducing a failure: with -shuffle on, every package prints its seed as
# the first line of its output, e.g. `-test.shuffle 1757400000000000000`.
# Re-run the failing package with that exact seed to get the same order:
# go test -race -count=1 -shuffle=<seed> ./internal/<pkg>/
# (add the -skip regex below if the failing test lives next to E2E tests).
# -shuffle is not one of go test's cacheable flags, so the replay is never
# served from the result cache; -count=1 is only the idiomatic belt-and-
# braces bypass for when flags get trimmed while bisecting. The seed fixes
# the test ORDER only — not goroutine scheduling or map iteration — and the
# blocked-value tests also depend on the host (hostname, $HOME basename).
test-shuffle:
name: Unit Tests (shuffle)
needs: [build-frontend]
runs-on: ubuntu-latest
env:
GO111MODULE: "on"
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.26"
cache: true
- name: Download dependencies
run: go mod download
# Same reason as in the "Unit Tests" job: bench/arms tscg tests hard-fail
# without the pinned Node shim wherever `go test ./...` runs (Spec 083).
- name: Set up Node.js (TSCG bench arm)
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: bench/tscg/package-lock.json
- name: Install pinned TSCG shim dependencies (FR-006)
run: npm ci --prefix bench/tscg
- name: Download frontend artifact
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: frontend-dist
path: frontend/dist
- name: Copy frontend dist for embedding
run: |
rm -rf web/frontend
mkdir -p web/frontend
cp -r frontend/dist web/frontend/
# Same tiktoken download race as in the "Unit Tests" job; see the comment
# there. Warm the cache once so parallel test binaries never rename.
- name: Warm the tiktoken vocabulary cache
run: go run ./bench/cmd/warmtiktoken
env:
TIKTOKEN_CACHE_DIR: ${{ runner.temp }}/tiktoken
# Keep the flags identical to the required ubuntu lane except for
# -shuffle=on (and no coverage — Codecov already gets the ordered run).
- name: Run unit tests (shuffled)
run: go test -v -race -timeout 20m -shuffle=on -skip "E2E|Binary|MCPProtocol|TestInfoEndpoint|TestGracefulShutdownNoPanic|TestSocketInfoEndpoint" ./...
env:
TIKTOKEN_CACHE_DIR: ${{ runner.temp }}/tiktoken
lint:
name: Lint
needs: [build-frontend, verify-oas]
runs-on: ubuntu-latest
env:
GO111MODULE: "on"
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.26"
- name: Download frontend artifact
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: frontend-dist
path: frontend/dist
- name: Copy frontend dist for embedding
run: |
rm -rf web/frontend
mkdir -p web/frontend
cp -r frontend/dist web/frontend/
- name: golangci-lint
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with:
version: v2.9.0
args: --config .github/.golangci.yml --timeout=10m
# skip-cache: the action's cross-run analysis cache produced stale
# staticcheck facts (SA5011 on t.Fatal no-return sites) that do not
# reproduce with the same version+config on a clean run. Cold runs
# cost ~2-3 min extra; correctness wins. Revisit after upstream
# cache-invalidation fix.
skip-cache: true
# Second pass under the server build tag (Spec 107 FR-047). Everything in
# internal/serveredition/ and every *_serveredition.go file is behind
# `//go:build server`, so the bare run above never sees it; without this
# step the server edition ships unlinted. Same config, same version — the
# only difference is the tag set.
- name: golangci-lint (server edition)
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with:
version: v2.9.0
args: --config .github/.golangci.yml --build-tags server --timeout=10m
skip-cache: true
server-edition:
name: Server Edition
runs-on: ubuntu-latest
env:
GO111MODULE: "on"
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.26"
cache: true
- name: Download dependencies
run: go mod download
- name: Verify dependencies
run: go mod verify
- name: Build server edition
run: go build -tags server ./cmd/mcpproxy
# internal/server is now in the list below and constructs the tiktoken
# tokenizer; warm the vocabulary once so parallel packages hit the cache
# (same reason as the unit-tests job above).
- name: Warm the tiktoken vocabulary cache
run: go run ./bench/cmd/warmtiktoken
env:
TIKTOKEN_CACHE_DIR: ${{ runner.temp }}/tiktoken
# internal/oauth is where the redaction rules live, and half of them are
# build-tagged: serverfields_serveredition.go holds the mask decisions for
# the `auth_broker` block, whose fields do not exist in the personal
# edition. Its canaries — the walk over config.ServerConfig that fails
# when a text-carrying leaf has no decision — therefore only compile under
# `-tags server`. Without this package in the list they never ran, and the
# guarantee the #1161 fix rests on (a field added to the tagged block is
# masked because the walk reaches it) was unenforced in CI.
#
# internal/server, internal/httpapi and internal/storage carry the
# server-edition wiring (serveredition_wire.go, the tagged handlers, the
# agent-token store) and were only ever *built* under the tag, never
# race-tested (Spec 107 FR-047). The -skip regex is the one the unit-test
# job uses (see "Run unit tests" above): it drops the binary-spawning
# internal/server tests that otherwise hang to the package timeout.
- name: Test server edition packages
run: go test -race -tags server -timeout 20m -skip "E2E|Binary|MCPProtocol|TestInfoEndpoint|TestGracefulShutdownNoPanic|TestSocketInfoEndpoint" ./internal/serveredition/... ./internal/config/... ./internal/oauth/... ./internal/server/... ./internal/httpapi/... ./internal/storage/...
env:
TIKTOKEN_CACHE_DIR: ${{ runner.temp }}/tiktoken
build:
name: Build
needs: [build-frontend, verify-oas]
runs-on: ${{ matrix.os }}
env:
GO111MODULE: "on"
strategy:
matrix:
# On PRs: build only on ubuntu-latest
# On main/push: build on all platforms
os: ${{ github.event_name == 'pull_request' && fromJSON('["ubuntu-latest"]') || fromJSON('["ubuntu-latest", "macos-latest", "windows-latest"]') }}
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.26"
cache: true
- name: Download frontend artifact
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: frontend-dist
path: frontend/dist
- name: Copy frontend dist for embedding (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
if (Test-Path web/frontend) { Remove-Item -Recurse -Force web/frontend }
New-Item -ItemType Directory -Force -Path web/frontend
Copy-Item -Recurse frontend/dist web/frontend/
- name: Copy frontend dist for embedding (Unix)
if: matrix.os != 'windows-latest'
run: |
rm -rf web/frontend
mkdir -p web/frontend
cp -r frontend/dist web/frontend/
- name: Build (Windows)
if: matrix.os == 'windows-latest'
run: go build -v -o mcpproxy.exe ./cmd/mcpproxy
- name: Build (Unix)
if: matrix.os != 'windows-latest'
run: go build -v ./cmd/mcpproxy
- name: Build for different architectures (Linux only)
if: matrix.os == 'ubuntu-latest'
run: |
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o mcpproxy-linux-amd64 ./cmd/mcpproxy
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o mcpproxy-linux-arm64 ./cmd/mcpproxy
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o mcpproxy-darwin-amd64 ./cmd/mcpproxy || echo "macOS amd64 cross-compilation may fail (expected)"
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o mcpproxy-darwin-arm64 ./cmd/mcpproxy || echo "macOS arm64 cross-compilation may fail (expected)"
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o mcpproxy-windows-amd64.exe ./cmd/mcpproxy
- name: Upload build artifacts
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: binaries
path: mcpproxy-*