From cd0aedafcdec1b24dea924d8924b3b29af29d598 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 16:03:19 +0000 Subject: [PATCH] test(testkit): cover the env-driven E2E ports instead of a fixed list The ports test parsed a hard-coded array that no longer exists. It now runs the script on a fake host whose fuser finds nothing, and checks the default ports, the per-runner override, the rejection of a non-numeric setting, and that the workflow picks the ports before freeing them. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u --- packages/testkit/src/e2e-ports.test.ts | 66 +++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/packages/testkit/src/e2e-ports.test.ts b/packages/testkit/src/e2e-ports.test.ts index eac409a..247407c 100644 --- a/packages/testkit/src/e2e-ports.test.ts +++ b/packages/testkit/src/e2e-ports.test.ts @@ -1,9 +1,12 @@ -import { readFileSync } from "node:fs"; +import { execFileSync } from "node:child_process"; +import { chmodSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; const repoRoot = path.resolve(import.meta.dirname, "../../.."); const scriptPath = path.join(repoRoot, "scripts/free-e2e-ports.sh"); +const script = readFileSync(scriptPath, "utf8"); const playwrightWorkflow = readFileSync( path.join(repoRoot, ".github/workflows/playwright.yml"), "utf8", @@ -15,20 +18,62 @@ function portsFromShellArray(source: string, name: string): number[] { return [...match![1].matchAll(/\b(\d+)\b/g)].map(([, port]) => Number(port)); } +/** Runs the script on a fake Linux host whose fuser never finds a listener, so nothing is killed. */ +function runScript(env: Record): { status: number; output: string } { + const fakeBin = mkdtempSync(path.join(tmpdir(), "evoo-e2e-ports-")); + for (const [name, body] of [ + ["uname", "echo Linux"], + ["fuser", "exit 1"], + ] as const) { + const file = path.join(fakeBin, name); + writeFileSync(file, `#!/usr/bin/env bash\n${body}\n`); + chmodSync(file, 0o755); + } + try { + const output = execFileSync("bash", [scriptPath], { + env: { PATH: `${fakeBin}:${process.env.PATH}`, ...env }, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }); + return { status: 0, output }; + } catch (error) { + const failure = error as { status: number; stdout: string; stderr: string }; + return { status: failure.status, output: `${failure.stdout}${failure.stderr}` }; + } +} + +function announcedPorts(output: string): number[] { + const match = output.match(/E2E ports \(([^)]*)\)/); + expect(match, "announced E2E ports").toBeTruthy(); + return match![1].split(" ").map(Number); +} + describe("free-e2e-ports.sh", () => { - const script = readFileSync(scriptPath, "utf8"); - - it("declares dedicated E2E ports separate from live dev ports", () => { + it("keeps the live dev ports apart from the default E2E ports", () => { const livePorts = portsFromShellArray(script, "LIVE_PORTS"); - const e2ePorts = portsFromShellArray(script, "E2E_PORTS"); - expect(livePorts).toEqual([3100, 5173]); + + const run = runScript({}); + expect(run.status).toBe(0); + const e2ePorts = announcedPorts(run.output); expect(e2ePorts).toEqual([3110, 3111, 5180, 4321]); for (const livePort of livePorts) { expect(e2ePorts).not.toContain(livePort); } }); + it("follows the harness port settings so runners on one host never touch each other", () => { + const run = runScript({ API_PORT: "3310", WEB_PORT: "5380", WWW_PORT: "4521" }); + expect(run.status).toBe(0); + expect(announcedPorts(run.output)).toEqual([3310, 3311, 5380, 4521]); + }); + + it("refuses a port setting that is not a number", () => { + const run = runScript({ API_PORT: "abc" }); + expect(run.status).toBe(1); + expect(run.output).toContain("API_PORT must be a port number"); + }); + it("uses fuser and skips non-Linux hosts", () => { expect(script).toContain('fuser -k "$spec"'); expect(script).toContain('uname -s)" != "Linux"'); @@ -44,4 +89,13 @@ describe("free-e2e-ports.sh", () => { playwrightWorkflow.indexOf("Run marketing homepage E2E"), ); }); + + it("gets its ports from the workflow step that picks them per runner", () => { + const pickStep = playwrightWorkflow.indexOf("Pick E2E ports for this runner"); + expect(pickStep).toBeGreaterThan(-1); + expect(pickStep).toBeLessThan(playwrightWorkflow.indexOf("free-e2e-ports.sh")); + for (const setting of ["API_PORT=", "WEB_PORT=", "WWW_PORT="]) { + expect(playwrightWorkflow).toContain(setting); + } + }); });