Merge pull request #1075 from kaitranntt/kai/chore/1071-ci-perf-budget

ci: add fast test budget warning
This commit is contained in:
Kai (Tam Nhu) Tran
2026-04-23 20:00:03 -04:00
committed by GitHub
4 changed files with 66 additions and 12 deletions
+34 -2
View File
@@ -95,6 +95,7 @@ jobs:
needs: [build] needs: [build]
env: env:
BUN_INSTALL_CACHE_DIR: ${{ github.workspace }}/.bun/install/cache BUN_INSTALL_CACHE_DIR: ${{ github.workspace }}/.bun/install/cache
FAST_TEST_BUDGET_SECONDS: '90'
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -125,8 +126,39 @@ jobs:
name: dist name: dist
path: dist/ path: dist/
- name: Test - name: Test fast bucket with perf budget
run: bun run test:all shell: bash
run: |
set -euo pipefail
start_epoch=$(date +%s)
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))
{
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
exit "$test_status"
- name: Test slow bucket
run: bun run test:slow
- name: Test CLI e2e - name: Test CLI e2e
env: env:
@@ -256,12 +256,12 @@ describe('openai proxy daemon lifecycle', () => {
}, 35000); }, 35000);
it('fails when an explicit port is already occupied', async () => { it('fails when an explicit port is already occupied', async () => {
const occupiedPort = await getPort();
const server = Bun.serve({ const server = Bun.serve({
port: occupiedPort, port: 0,
hostname: '127.0.0.1', hostname: '127.0.0.1',
fetch: () => new Response('busy'), fetch: () => new Response('busy'),
}); });
const occupiedPort = server.port;
try { try {
const settingsPath = path.join(tempDir, 'occupied-explicit.settings.json'); 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 () => { it('fails when a configured profile port is already occupied', async () => {
const occupiedPort = await getPort();
const server = Bun.serve({ const server = Bun.serve({
port: occupiedPort, port: 0,
hostname: '127.0.0.1', hostname: '127.0.0.1',
fetch: () => new Response('busy'), fetch: () => new Response('busy'),
}); });
const occupiedPort = server.port;
try { try {
mutateUnifiedConfig((config) => { mutateUnifiedConfig((config) => {
@@ -347,12 +347,12 @@ describe('openai proxy daemon lifecycle', () => {
}); });
it('falls back when a configured shared proxy.port is occupied', async () => { it('falls back when a configured shared proxy.port is occupied', async () => {
const occupiedPort = await getPort();
const server = Bun.serve({ const server = Bun.serve({
port: occupiedPort, port: 0,
hostname: '127.0.0.1', hostname: '127.0.0.1',
fetch: () => new Response('busy'), fetch: () => new Response('busy'),
}); });
const occupiedPort = server.port;
try { try {
mutateUnifiedConfig((config) => { mutateUnifiedConfig((config) => {
@@ -396,12 +396,12 @@ describe('openai proxy daemon lifecycle', () => {
it('keeps the existing proxy running if replacement startup fails', async () => { it('keeps the existing proxy running if replacement startup fails', async () => {
const firstPort = await getPort(); const firstPort = await getPort();
const occupiedPort = await getPort();
const busyServer = Bun.serve({ const busyServer = Bun.serve({
port: occupiedPort, port: 0,
hostname: '127.0.0.1', hostname: '127.0.0.1',
fetch: () => new Response('busy'), fetch: () => new Response('busy'),
}); });
const occupiedPort = busyServer.port;
try { try {
const settingsPath = path.join(tempDir, 'rollback.settings.json'); const settingsPath = path.join(tempDir, 'rollback.settings.json');
@@ -27,7 +27,14 @@ describe('push ci workflow', () => {
expect(workflow).toContain("cmd: 'bun run lint'"); expect(workflow).toContain("cmd: 'bun run lint'");
expect(workflow).toContain("cmd: 'bun run format:check'"); expect(workflow).toContain("cmd: 'bun run format:check'");
expect(workflow).toContain('run: bun run build:all'); 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('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');
expect(workflow).toContain("CCS_E2E_SKIP_BUILD: '1'"); expect(workflow).toContain("CCS_E2E_SKIP_BUILD: '1'");
expect(workflow).toContain('run: bun run test:e2e'); expect(workflow).toContain('run: bun run test:e2e');
}); });
@@ -165,6 +165,7 @@ let stripAnthropicRoutingEnv: typeof import('../../../src/utils/shell-executor')
let stripClaudeCodeEnv: typeof import('../../../src/utils/shell-executor').stripClaudeCodeEnv; let stripClaudeCodeEnv: typeof import('../../../src/utils/shell-executor').stripClaudeCodeEnv;
let HeadlessExecutor: typeof import('../../../src/delegation/headless-executor').HeadlessExecutor; let HeadlessExecutor: typeof import('../../../src/delegation/headless-executor').HeadlessExecutor;
let SharedManager: typeof import('../../../src/management/shared-manager').default; let SharedManager: typeof import('../../../src/management/shared-manager').default;
let stopOpenAICompatProxy: typeof import('../../../src/proxy/proxy-daemon').stopOpenAICompatProxy;
beforeAll(async () => { beforeAll(async () => {
registerChildProcessMock(); registerChildProcessMock();
@@ -179,6 +180,9 @@ beforeAll(async () => {
const headless = await import('../../../src/delegation/headless-executor'); const headless = await import('../../../src/delegation/headless-executor');
HeadlessExecutor = headless.HeadlessExecutor; HeadlessExecutor = headless.HeadlessExecutor;
const proxyDaemon = await import('../../../src/proxy/proxy-daemon');
stopOpenAICompatProxy = proxyDaemon.stopOpenAICompatProxy;
}); });
afterAll(() => { afterAll(() => {
@@ -208,7 +212,14 @@ describe('CLAUDECODE environment stripping', () => {
baselineSighupListeners = process.listeners('SIGHUP'); 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 }); Object.defineProperty(process, 'platform', { value: originalPlatform });
delete process.env.CLAUDECODE; delete process.env.CLAUDECODE;
delete process.env.claudecode; delete process.env.claudecode;
@@ -253,6 +264,10 @@ describe('CLAUDECODE environment stripping', () => {
process.removeListener('SIGHUP', listener as (...args: unknown[]) => void); process.removeListener('SIGHUP', listener as (...args: unknown[]) => void);
} }
} }
if (tempCcsHome) {
fs.rmSync(tempCcsHome, { recursive: true, force: true });
}
}); });
it('stripClaudeCodeEnv removes CLAUDECODE case-insensitively', () => { it('stripClaudeCodeEnv removes CLAUDECODE case-insensitively', () => {