You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* test: fix timeouts
* fix: add git credentials to test
* docs: capture learnings
* ci: update workflow name to comply with org setup
* refactor: format fix
Copy file name to clipboardExpand all lines: .archgate/adrs/ARCH-005-testing-standards.md
+74-2Lines changed: 74 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -40,6 +40,8 @@ Use Bun's built-in test runner (`bun test`) for all tests. Test files go in `tes
40
40
- Use `tests/fixtures/` for sample data files
41
41
- Use temp directories (`mkdtemp`) for tests that write to the filesystem
42
42
- Clean up temp directories in `afterEach` or `afterAll`
43
+
-**Close external SDK instances** (servers, clients, transports) in `afterEach` or `afterAll` by calling their cleanup method (e.g., `await server.close()`). Manage their lifecycle in `beforeEach`/`afterEach` rather than inside individual test bodies so cleanup is guaranteed.
44
+
-**When a test creates a temp git repo and needs to call `git commit`, configure local user identity first** — CI runners have no global git config, so commits fail without explicit local identity. Set it with `await Bun.$\`git config user.email "test@test.com"\`.cwd(tempDir).quiet()`and`await Bun.$\`git config user.name "Test"\`.cwd(tempDir).quiet()`immediately after`git init`.
43
45
- Test public module interfaces, not private implementation details
44
46
- Use descriptive test names that explain the expected behavior
45
47
@@ -48,6 +50,8 @@ Use Bun's built-in test runner (`bun test`) for all tests. Test files go in `tes
48
50
- Don't test private implementation details — test the public API of each module
49
51
- Don't depend on network access in unit tests
50
52
- Don't leave temp files after test runs
53
+
-**Don't leave external SDK instances open after tests** — instances from libraries such as `@modelcontextprotocol/sdk` hold internal references (e.g., `AjvJsonSchemaValidator` backed by `ajv`) that keep Bun's event loop alive on Linux, causing `bun test` to hang indefinitely after all tests complete even though every test passes. Always call the cleanup method in `afterEach`.
54
+
-**Don't rely on globally-configured git identity in temp git repos** — always set `user.email` and `user.name` locally in any repo that makes commits. Omitting this works locally (where developers have global git config) but fails silently in CI, producing a cryptic `ShellPromise` error with no indication that git identity is the cause.
51
55
- Don't skip tests without a tracking issue
52
56
- Don't import test utilities from `node:test` — use Bun's built-in `bun:test` module
53
57
@@ -83,6 +87,53 @@ describe("runChecks", () => {
83
87
});
84
88
```
85
89
90
+
### Good Example — Temp Git Repo with Commits
91
+
92
+
```typescript
93
+
// tests/engine/git-files.test.ts
94
+
it("returns both staged and unstaged changes", async () => {
95
+
awaitBun.$`git init`.cwd(tempDir).quiet();
96
+
// GOOD: set local identity before any commit — CI has no global git config
-**Mitigation:** The project pins a specific Bun version via `.prototools` (currently 1.3.8). Test runner API changes are caught during controlled Bun upgrades with full test suite validation.
125
192
-**Coverage reporting gaps** — `bun test --coverage` may not report accurate coverage for all code paths, especially for dynamically imported modules.
126
193
-**Mitigation:** Coverage is a guideline (80% target), not a hard gate. Critical modules (engine, formats) are tested thoroughly regardless of coverage numbers.
194
+
-**Third-party SDK event loop retention** — External SDK instances that hold internal resource references (e.g., `AjvJsonSchemaValidator` inside `@modelcontextprotocol/sdk`) keep Bun's event loop alive on Linux after all tests complete, causing `bun test` to hang indefinitely. This does not surface on macOS (event loop drains normally there), making it a Linux-CI-only failure that is hard to reproduce locally.
195
+
-**Mitigation:** Always manage SDK lifecycle in `beforeEach`/`afterEach` and call the cleanup method (`close()`, `destroy()`, `disconnect()`) in `afterEach`. Add `timeout-minutes` to CI jobs as a safety net — the `code-pull-request.yml` job is set to 10 minutes to cap any future regressions.
127
196
128
197
## Compliance and Enforcement
129
198
130
199
### Automated Enforcement
131
200
132
201
-**Archgate rule**`ARCH-005/test-mirrors-src`: Scans all source files in `src/` and verifies a corresponding `.test.ts` file exists in `tests/`. Severity: `error`.
133
-
-**CI pipeline**: `bun test` runs on every pull request. Test failures block merge.
202
+
-**CI pipeline**: `bun test --timeout 60000` runs on every pull request. Test failures and per-test timeouts block merge. All workflow jobs have `timeout-minutes` set to prevent indefinite hangs.
134
203
135
204
### Manual Enforcement
136
205
137
206
Code reviewers MUST verify:
138
207
139
208
1. New source files have corresponding test files
140
209
2. Tests use temp directories for filesystem operations (no hardcoded paths)
141
-
3. Tests clean up after themselves (`afterEach`/`afterAll` cleanup)
210
+
3. Tests clean up after themselves (`afterEach`/`afterAll` cleanup) — including both temp directories and external SDK instances
211
+
4. Tests that instantiate SDK objects (servers, clients, connections) manage their lifecycle in `beforeEach`/`afterEach`, not inside individual test bodies
212
+
5. Tests that call `git commit` on a temp repo configure `user.email` and `user.name` locally before committing
142
213
143
214
## References
144
215
145
216
-[Bun test runner documentation](https://bun.sh/docs/cli/test)
146
217
-[ARCH-001 — Command Structure](./ARCH-001-command-structure.md) — In-process execution enables testing commands directly without process spawning
218
+
-[ARCH-006 — Dependency Policy](./ARCH-006-dependency-policy.md) — Third-party dependencies introduce runtime behaviors (like event loop retention) that must be accounted for in test teardown
Copy file name to clipboardExpand all lines: .claude/agent-memory/archgate-developer/MEMORY.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -26,6 +26,8 @@ Skipping steps 2 or 3 is a workflow violation. The user should NEVER have to inv
26
26
27
27
## Patterns & Fixes
28
28
29
+
-**`McpServer` event loop retention on Linux** — `new McpServer()` from `@modelcontextprotocol/sdk` creates an `AjvJsonSchemaValidator` (backed by `ajv` + `ajv-formats`) that keeps Bun's event loop alive on Linux after tests complete, causing `bun test` to hang for 30+ minutes. macOS drains the event loop fine — this is a Linux-CI-only failure. Fix: manage server lifecycle in `beforeEach`/`afterEach` and call `await server.close()` in `afterEach`. Also captured in ARCH-005 Don'ts.
30
+
-**`git commit` in temp repos requires local identity** — CI runners have no global `user.email`/`user.name` configured. Any test that runs `git commit` on a temp repo MUST call `git config user.email` and `git config user.name` locally after `git init`. Fails with a cryptic `ShellPromise` error in CI; passes locally. Also captured in ARCH-005 Do's.
29
31
-**Bun import cache-busting**: Bun caches `import()` per-process. For long-running processes (MCP server), append `?t=${Date.now()}` to the import path to force re-reading from disk. Applied in `src/engine/loader.ts`.
30
32
-**Never use `bunx prettier` directly** — Always use `bun run format` (to fix) or `bun run format:check` (to verify). Using `bunx prettier` can fail or use a different version than the project's devDependency. The same applies to all dev tools: prefer `bun run <script>` over `bunx <tool>` when a package.json script exists.
31
33
-**`Bun.Glob.match()` triggers oxlint `prefer-regexp-test`** — `Bun.Glob.match()` returns a boolean (not a RegExp), but oxlint can't tell. Suppress with `// oxlint-disable-next-line prefer-regexp-test -- Bun.Glob.match() returns boolean, not RegExp`.
0 commit comments