Files
Ignacy Łątka d45306d7f3 test(tool-server): reap three temp dirs an assertion failure strands (#1089)
Fixes #1077

**Cause** - three tool-server tests remove their temp dir as the *last
statement of the test body*, so any assertion above it throwing abandons
the directory in the shared system temp dir.

**Fix** - a module-scope list pushed to at the mint site and drained in
an `afterEach` (the shape #1057 used), replacing the trailing `rm`.
`render-annotations.test.ts` had no hook at all;
`workspace-reader.test.ts`'s describe-level `afterEach` becomes
redundant once `createTempDir` registers every dir it hands out, so it
goes.

Test-only. No tool, CLI, config key, flow file or user-facing capability
changes, so no docs update.

**Verified** - break one assertion per file, run under a scoped
`TMPDIR`, list what survives.

| file | before | after |
|---|---|---|
| `metro/source-resolver-empty-root.test.ts` | `real-app-ouDGWh` |
*(none)* |
| `react-profiler/render-annotations.test.ts` |
`argent-render-annotations-…zmt1onhl4wj`, `…sriq0gyjyo` | *(none)* |
| `workspace-reader.test.ts` | `ws-reader-test-SIWls6` | *(none)* |

Same failure count on both sides (1, 2, 1) - only the leftovers differ.

<details>
<summary>Repro, both sides</summary>

Mutations applied to each file (`git show HEAD:<path>` for the "before"
column, the branch for "after"):

```
sed -i '' 's/expect(out).toContain("IN_PROJECT");/expect(out).toContain("NOPE");/' \
  test/metro/source-resolver-empty-root.test.ts
sed -i '' 's/expect(after50).toContain("(t=192.3s)");/expect(after50).toContain("(t=999.9s)");/;
           s/expect(report).not.toMatch(\/> After:\/);/expect(report).toMatch(\/> After:\/);/' \
  test/react-profiler/render-annotations.test.ts
sed -i '' 's/expect(snap.lockfile).toBe(lockName);/expect(snap.lockfile).toBe("NOPE");/' \
  test/workspace-reader.test.ts
```

Each run, from `packages/tool-server`:

```
d=/tmp/scope/<label>; rm -rf $d; mkdir -p $d
TMPDIR=$d TMP=$d TEMP=$d npx vitest run <file>
ls -A $d
```

Before (unfixed, mutated):

```
unfixed_sr:  Tests 1 failed | 4 passed   ->  node-compile-cache  real-app-ouDGWh
unfixed_ra:  Tests 2 failed              ->  node-compile-cache
                                             argent-render-annotations-1788965985023-zmt1onhl4wj
                                             argent-render-annotations-1788965985027-sriq0gyjyo
unfixed_wr:  Tests 1 failed | 34 passed  ->  node-compile-cache  ws-reader-test-SIWls6
```

After (this branch, same mutations): identical failure counts,
`node-compile-cache` only.
</details>

<details>
<summary>Checks</summary>

From the worktree root:

- `npx tsc --build` - clean
- `npm run typecheck:tests -w @argent/tool-server` - clean
- `npm test -w @argent/tool-server` - `368 passed (368)` files, `4832
passed | 1 skipped`
- `npx prettier --write` on the three files - unchanged

Class sweep: scanned every `*.test.ts` under `packages/tool-server/test`
for an `rm`/`rmSync`/`unlink` as the final statement of an `it` body.
Besides these three, only `react-profiler/dump.test.ts` and
`screen-recording.test.ts` match, and both already point `os.tmpdir()`
at a per-test scratch reaped in `afterEach`, so their unlinks strand
nothing. The same shape in other packages is #1074 / #1075 and is left
to those.
</details>

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Tests**
- Improved test cleanup by consistently removing temporary and debug
directories after each test.
- Consolidated cleanup handling across source resolution, profiler
annotations, and workspace reader tests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 20:17:00 +02:00

114 lines
3.5 KiB
TypeScript

import { describe, it, expect, afterEach } from "vitest";
import { promises as fs } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { renderProfilingReport } from "../../src/utils/react-profiler/pipeline/05-render";
import type { HotCommitSummary } from "../../src/utils/react-profiler/types/output";
import type { SessionContext } from "../../src/utils/react-profiler/types/pipeline";
// `summary.timestampMs` and `annotation.offsetMs` are both "ms since
// profile-start", so the renderer compares them directly, with no anchor.
const COMMIT_0_OFFSET = 17_587;
const COMMIT_50_OFFSET = 192_287;
const SUMMARIES: HotCommitSummary[] = [
{
commitIndex: 0,
timestampMs: COMMIT_0_OFFSET,
totalRenderMs: 100,
isMargin: false,
tier: "hot",
components: [],
totalComponentCount: 0,
},
{
commitIndex: 50,
timestampMs: COMMIT_50_OFFSET,
totalRenderMs: 100,
isMargin: false,
tier: "hot",
components: [],
totalComponentCount: 0,
},
];
const ANNOTATIONS = [
{ offsetMs: 173_110, label: "scroll feed" },
{ offsetMs: 192_071, label: "tap post" },
];
const SESSION_CONTEXT: SessionContext = {
reactCompilerEnabled: false,
strictModeEnabled: false,
buildMode: "dev",
rnArchitecture: "bridgeless",
projectRoot: "/tmp/fake-project",
platform: "ios",
};
const debugDirs: string[] = [];
afterEach(async () => {
for (const dir of debugDirs.splice(0)) await fs.rm(dir, { recursive: true, force: true });
});
async function makeDebugDir(): Promise<string> {
const dir = join(
tmpdir(),
`argent-render-annotations-${Date.now()}-${Math.random().toString(36).slice(2)}`
);
await fs.mkdir(dir, { recursive: true });
debugDirs.push(dir);
return dir;
}
describe("renderProfilingReport — annotation reference frame", () => {
it("matches the annotation immediately preceding a commit (regression for the user's scenario)", async () => {
const debugDir = await makeDebugDir();
const { report } = await renderProfilingReport({
hotCommitSummaries: SUMMARIES,
componentFindings: [],
sessionContext: SESSION_CONTEXT,
recordingMs: 200_000,
anyRuntimeCompilerDetected: false,
reactCommits: 51,
annotations: ANNOTATIONS,
debugDir,
});
// 216ms after "tap post".
const commit50Idx = report.indexOf("### Commit #50");
expect(commit50Idx).toBeGreaterThanOrEqual(0);
const after50 = report.slice(commit50Idx, commit50Idx + 200);
expect(after50).toMatch(/> After: "tap post" \(0\.2s prior\)/);
expect(after50).not.toContain('"scroll feed"');
// commit #0 (t=17.6s) precedes every annotation.
const commit0Idx = report.indexOf("### Commit #0");
expect(commit0Idx).toBeGreaterThanOrEqual(0);
const between = report.slice(commit0Idx, commit50Idx);
expect(between).not.toMatch(/> After:/);
expect(between).toContain("(t=17.6s)");
expect(after50).toContain("(t=192.3s)");
});
it("renders without annotations when none are provided", async () => {
const debugDir = await makeDebugDir();
const { report } = await renderProfilingReport({
hotCommitSummaries: SUMMARIES,
componentFindings: [],
sessionContext: SESSION_CONTEXT,
recordingMs: 200_000,
anyRuntimeCompilerDetected: false,
reactCommits: 51,
debugDir,
});
expect(report).toContain("### Commit #50");
expect(report).toContain("### Commit #0");
expect(report).not.toMatch(/> After:/);
});
});