From 4c677507d0e568418a79c6c6893f62e2fe560049 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Thu, 23 Apr 2026 17:25:48 -0400 Subject: [PATCH 1/2] ci: add fast test budget warning --- .github/workflows/push-ci.yml | 29 +++++++++++++++++-- .../proxy/daemon-lifecycle.test.ts | 16 +++++----- .../scripts/github/push-ci-workflow.test.ts | 7 ++++- .../utils/claudecode-env-stripping.test.ts | 17 ++++++++++- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/.github/workflows/push-ci.yml b/.github/workflows/push-ci.yml index 2aef1166..2fa9361c 100644 --- a/.github/workflows/push-ci.yml +++ b/.github/workflows/push-ci.yml @@ -95,6 +95,7 @@ jobs: needs: [build] env: BUN_INSTALL_CACHE_DIR: ${{ github.workspace }}/.bun/install/cache + FAST_TEST_BUDGET_SECONDS: '90' steps: - name: Checkout code uses: actions/checkout@v4 @@ -125,8 +126,32 @@ jobs: name: dist path: dist/ - - name: Test - run: bun run test:all + - name: Test fast bucket with perf budget + shell: bash + run: | + set -euo pipefail + + start_epoch=$(date +%s) + bun run test:fast + end_epoch=$(date +%s) + elapsed_seconds=$((end_epoch - start_epoch)) + + { + echo "### test:fast perf budget" + echo "" + echo "- Elapsed: ${elapsed_seconds}s" + echo "- Budget: ${FAST_TEST_BUDGET_SECONDS}s" + } >> "$GITHUB_STEP_SUMMARY" + + if (( elapsed_seconds > FAST_TEST_BUDGET_SECONDS )); then + echo "::warning::test:fast took ${elapsed_seconds}s (budget ${FAST_TEST_BUDGET_SECONDS}s) - check for undeclared slow tests in scripts/run-test-bucket.js" + echo "- Result: warning emitted. Check for undeclared slow tests in scripts/run-test-bucket.js." >> "$GITHUB_STEP_SUMMARY" + else + echo "- Result: within budget." >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Test slow bucket + run: bun run test:slow - name: Test CLI e2e env: diff --git a/tests/integration/proxy/daemon-lifecycle.test.ts b/tests/integration/proxy/daemon-lifecycle.test.ts index 0667c254..0d694a3c 100644 --- a/tests/integration/proxy/daemon-lifecycle.test.ts +++ b/tests/integration/proxy/daemon-lifecycle.test.ts @@ -256,12 +256,12 @@ describe('openai proxy daemon lifecycle', () => { }, 35000); it('fails when an explicit port is already occupied', async () => { - const occupiedPort = await getPort(); const server = Bun.serve({ - port: occupiedPort, + port: 0, hostname: '127.0.0.1', fetch: () => new Response('busy'), }); + const occupiedPort = server.port; try { const settingsPath = path.join(tempDir, 'occupied-explicit.settings.json'); @@ -298,12 +298,12 @@ describe('openai proxy daemon lifecycle', () => { }); it('fails when a configured profile port is already occupied', async () => { - const occupiedPort = await getPort(); const server = Bun.serve({ - port: occupiedPort, + port: 0, hostname: '127.0.0.1', fetch: () => new Response('busy'), }); + const occupiedPort = server.port; try { mutateUnifiedConfig((config) => { @@ -347,12 +347,12 @@ describe('openai proxy daemon lifecycle', () => { }); it('falls back when a configured shared proxy.port is occupied', async () => { - const occupiedPort = await getPort(); const server = Bun.serve({ - port: occupiedPort, + port: 0, hostname: '127.0.0.1', fetch: () => new Response('busy'), }); + const occupiedPort = server.port; try { mutateUnifiedConfig((config) => { @@ -396,12 +396,12 @@ describe('openai proxy daemon lifecycle', () => { it('keeps the existing proxy running if replacement startup fails', async () => { const firstPort = await getPort(); - const occupiedPort = await getPort(); const busyServer = Bun.serve({ - port: occupiedPort, + port: 0, hostname: '127.0.0.1', fetch: () => new Response('busy'), }); + const occupiedPort = busyServer.port; try { const settingsPath = path.join(tempDir, 'rollback.settings.json'); diff --git a/tests/unit/scripts/github/push-ci-workflow.test.ts b/tests/unit/scripts/github/push-ci-workflow.test.ts index f498e014..e7d68ebc 100644 --- a/tests/unit/scripts/github/push-ci-workflow.test.ts +++ b/tests/unit/scripts/github/push-ci-workflow.test.ts @@ -27,7 +27,12 @@ describe('push ci workflow', () => { expect(workflow).toContain("cmd: 'bun run lint'"); expect(workflow).toContain("cmd: 'bun run format:check'"); expect(workflow).toContain('run: bun run build:all'); - expect(workflow).toContain('run: bun run test:all'); + expect(workflow).toContain("FAST_TEST_BUDGET_SECONDS: '90'"); + expect(workflow).toContain('name: Test fast bucket with perf budget'); + expect(workflow).toContain('bun run test:fast'); + expect(workflow).toContain('::warning::test:fast took ${elapsed_seconds}s'); + expect(workflow).toContain('scripts/run-test-bucket.js'); + expect(workflow).toContain('run: bun run test:slow'); expect(workflow).toContain("CCS_E2E_SKIP_BUILD: '1'"); expect(workflow).toContain('run: bun run test:e2e'); }); diff --git a/tests/unit/utils/claudecode-env-stripping.test.ts b/tests/unit/utils/claudecode-env-stripping.test.ts index a4212c83..bb64e71d 100644 --- a/tests/unit/utils/claudecode-env-stripping.test.ts +++ b/tests/unit/utils/claudecode-env-stripping.test.ts @@ -165,6 +165,7 @@ let stripAnthropicRoutingEnv: typeof import('../../../src/utils/shell-executor') let stripClaudeCodeEnv: typeof import('../../../src/utils/shell-executor').stripClaudeCodeEnv; let HeadlessExecutor: typeof import('../../../src/delegation/headless-executor').HeadlessExecutor; let SharedManager: typeof import('../../../src/management/shared-manager').default; +let stopOpenAICompatProxy: typeof import('../../../src/proxy/proxy-daemon').stopOpenAICompatProxy; beforeAll(async () => { registerChildProcessMock(); @@ -179,6 +180,9 @@ beforeAll(async () => { const headless = await import('../../../src/delegation/headless-executor'); HeadlessExecutor = headless.HeadlessExecutor; + + const proxyDaemon = await import('../../../src/proxy/proxy-daemon'); + stopOpenAICompatProxy = proxyDaemon.stopOpenAICompatProxy; }); afterAll(() => { @@ -208,7 +212,14 @@ describe('CLAUDECODE environment stripping', () => { baselineSighupListeners = process.listeners('SIGHUP'); }); - afterEach(() => { + afterEach(async () => { + const tempCcsHome = process.env.CCS_HOME?.startsWith(os.tmpdir()) + ? process.env.CCS_HOME + : undefined; + if (tempCcsHome) { + await stopOpenAICompatProxy(); + } + Object.defineProperty(process, 'platform', { value: originalPlatform }); delete process.env.CLAUDECODE; delete process.env.claudecode; @@ -253,6 +264,10 @@ describe('CLAUDECODE environment stripping', () => { process.removeListener('SIGHUP', listener as (...args: unknown[]) => void); } } + + if (tempCcsHome) { + fs.rmSync(tempCcsHome, { recursive: true, force: true }); + } }); it('stripClaudeCodeEnv removes CLAUDECODE case-insensitively', () => { From aa4a05e85922c787cada4dc1111469a1bcfdcfd6 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Thu, 23 Apr 2026 19:52:12 -0400 Subject: [PATCH 2/2] ci: preserve fast test budget summary on failure --- .github/workflows/push-ci.yml | 9 ++++++++- tests/unit/scripts/github/push-ci-workflow.test.ts | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/push-ci.yml b/.github/workflows/push-ci.yml index 2fa9361c..8a2ab204 100644 --- a/.github/workflows/push-ci.yml +++ b/.github/workflows/push-ci.yml @@ -132,7 +132,12 @@ jobs: set -euo pipefail start_epoch=$(date +%s) - bun run test:fast + test_status=0 + if bun run test:fast; then + test_status=0 + else + test_status=$? + fi end_epoch=$(date +%s) elapsed_seconds=$((end_epoch - start_epoch)) @@ -150,6 +155,8 @@ jobs: echo "- Result: within budget." >> "$GITHUB_STEP_SUMMARY" fi + exit "$test_status" + - name: Test slow bucket run: bun run test:slow diff --git a/tests/unit/scripts/github/push-ci-workflow.test.ts b/tests/unit/scripts/github/push-ci-workflow.test.ts index e7d68ebc..c3313427 100644 --- a/tests/unit/scripts/github/push-ci-workflow.test.ts +++ b/tests/unit/scripts/github/push-ci-workflow.test.ts @@ -30,6 +30,8 @@ describe('push ci workflow', () => { expect(workflow).toContain("FAST_TEST_BUDGET_SECONDS: '90'"); expect(workflow).toContain('name: Test fast bucket with perf budget'); expect(workflow).toContain('bun run test:fast'); + expect(workflow).toContain('test_status=$?'); + expect(workflow).toContain('exit "$test_status"'); expect(workflow).toContain('::warning::test:fast took ${elapsed_seconds}s'); expect(workflow).toContain('scripts/run-test-bucket.js'); expect(workflow).toContain('run: bun run test:slow');