From bcf115525519866247eb4a0ce1977771a74d2ad5 Mon Sep 17 00:00:00 2001 From: ozymandiashh <234437643+ozymandiashh@users.noreply.github.com> Date: Tue, 4 Aug 2026 03:38:18 +0300 Subject: [PATCH] test: fix the five environment-sensitive failures the first ubuntu run exposed - cli-durable-totals: the live fixture session was stamped at noon today, so every before-noon run saw it in the future; the provider-filtered path drops future instants while the all-provider path keeps the whole day, failing the parity assertion. Relative-and-clamped timestamps, the same fix project-filter-durable-totals got in 1596220. - parser (copilot, 2 cases): the fixture's fixed 2026-05-01 dates crossed copilot's durable 90-day age-out on 2026-07-30, so the first parse pruned the freshly-cached session. Relative timestamps. - parser-incremental-append: unlink-then-create let ext4 hand the freed inode straight back, breaking the new-inode premise. The replacement is now created beside the original and renamed over it. - parser-proxy-pricing: normalizeProxyPath folds case only on darwin and win32, deliberately; the test now asserts the platform-correct behavior on both kinds of filesystem instead of hardcoding macOS. - cli-status-menubar: the config-source filter case does real multi-parse work and needs more than the 5s default on shared runners; 30s cap. --- tests/cli-durable-totals.test.ts | 12 ++++++++++-- tests/cli-status-menubar.test.ts | 2 +- tests/parser-incremental-append.test.ts | 13 +++++++++---- tests/parser-proxy-pricing.test.ts | 9 +++++++-- tests/parser.test.ts | 12 +++++++++--- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/tests/cli-durable-totals.test.ts b/tests/cli-durable-totals.test.ts index f6b51f34..f50e9415 100644 --- a/tests/cli-durable-totals.test.ts +++ b/tests/cli-durable-totals.test.ts @@ -89,8 +89,16 @@ async function seedLiveTodaySession(): Promise { const projectDir = join(ROOT, 'home', '.claude', 'projects', 'p') await mkdir(projectDir, { recursive: true }) const now = new Date() - const ts = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 12, 0, 0).toISOString() - const ts2 = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 12, 30, 0).toISOString() + // Timestamps a few minutes OLD, clamped into today: a fixed wall-clock hour + // (12:00) is in the future whenever the suite runs before noon, and the + // instant-granular provider-filtered path drops future calls while the + // day-granular all-provider path keeps them, so the parity assertion failed + // for every before-noon run (ubuntu CI at 00:17 UTC included). Same fix as + // project-filter-durable-totals got in 1596220. + const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime() + const minutesAgo = (m: number): string => new Date(Math.max(midnight, now.getTime() - m * 60_000)).toISOString() + const ts = minutesAgo(40) + const ts2 = minutesAgo(10) const line = (id: string, t: string): string => JSON.stringify({ type: 'assistant', timestamp: t, diff --git a/tests/cli-status-menubar.test.ts b/tests/cli-status-menubar.test.ts index 0086212d..954f2a07 100644 --- a/tests/cli-status-menubar.test.ts +++ b/tests/cli-status-menubar.test.ts @@ -268,7 +268,7 @@ describe('codeburn status --format menubar-json', () => { } finally { await rm(home, { recursive: true, force: true }) } - }) + }, 30_000) it('keeps idle Claude config options visible for the selected period', async () => { const home = await mkdtemp(join(tmpdir(), 'codeburn-menubar-claude-config-idle-')) diff --git a/tests/parser-incremental-append.test.ts b/tests/parser-incremental-append.test.ts index 858c96d4..3c192146 100644 --- a/tests/parser-incremental-append.test.ts +++ b/tests/parser-incremental-append.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -import { mkdtemp, mkdir, writeFile, appendFile, readFile, rm, stat, unlink } from 'fs/promises' +import { mkdtemp, mkdir, writeFile, appendFile, readFile, rename, rm, stat, unlink } from 'fs/promises' import { join } from 'path' import { tmpdir } from 'os' @@ -286,14 +286,19 @@ describe('incremental append parsing', () => { await parseWith(warmCache) const inoBefore = (await stat(sessionPath)).ino - // Replace the file (new inode) with different, LARGER content. - await unlink(sessionPath) + // Replace the file (new inode) with different, LARGER content. The + // replacement is created BESIDE the original and renamed over it: an + // unlink-then-create lets ext4 hand the freed inode straight back, which + // broke the new-inode premise on Linux CI. Two files alive at once are + // guaranteed distinct inodes, and rename keeps the replacement's. const replaced = [ ...baseLines(), userLine('2026-05-01T12:00:00.000Z', 'brand new task'), asstLine('msg-z', '2026-05-01T12:00:02.000Z', { input_tokens: 500, output_tokens: 120 }, [readBlock('/z.ts')]), ].join('\n') + '\n' - await writeFile(sessionPath, replaced) + const replacementPath = sessionPath + '.replacement' + await writeFile(replacementPath, replaced) + await rename(replacementPath, sessionPath) expect((await stat(sessionPath)).ino).not.toBe(inoBefore) readLineCalls.length = 0 diff --git a/tests/parser-proxy-pricing.test.ts b/tests/parser-proxy-pricing.test.ts index 26df51b1..a4a2ebd0 100644 --- a/tests/parser-proxy-pricing.test.ts +++ b/tests/parser-proxy-pricing.test.ts @@ -40,9 +40,14 @@ describe('isProxiedPath: path matching rule', () => { expect(isProxiedPath('/Users/me/work/')).toBe(true) }) - it('is case-insensitive (macOS/Windows default filesystems)', () => { + it('folds case exactly where the default filesystem does (macOS/Windows yes, Linux no)', () => { + // normalizeProxyPath lowercases only on darwin/win32, deliberately: ext4 is + // case-sensitive and folding there could credit unrelated spend. Assert the + // platform-correct behavior instead of hardcoding the macOS one, which made + // this case fail on Linux CI by design. setProxyPaths(['/Users/Me/Work']) - expect(isProxiedPath('/users/me/work/acme')).toBe(true) + const foldsCase = process.platform === 'darwin' || process.platform === 'win32' + expect(isProxiedPath('/users/me/work/acme')).toBe(foldsCase) }) it('matches a Windows-style config against a forward-slash cwd', () => { diff --git a/tests/parser.test.ts b/tests/parser.test.ts index 211c41fa..b4dd0631 100644 --- a/tests/parser.test.ts +++ b/tests/parser.test.ts @@ -143,10 +143,16 @@ async function createJsonlSession( const dir = join(sessionStateDir, sessionId) await mkdir(dir, { recursive: true }) await writeFile(join(dir, 'workspace.yaml'), `id: ${sessionId}\ncwd: /home/user/testproj\n`) + // Relative timestamps: fixed calendar dates rot. The original '2026-05-01' + // crossed copilot's durable 90-day age-out on 2026-07-30, at which point the + // very first parse pruned the freshly-cached session and both durable tests + // started failing everywhere with "expected +0 to be 200". + const base = Date.now() - 5 * 24 * 60 * 60 * 1000 + const at = (offsetSec: number): string => new Date(base + offsetSec * 1000).toISOString() const lines = [ - JSON.stringify({ type: 'session.model_change', timestamp: '2026-05-01T10:00:00Z', data: { newModel: 'gpt-4.1' } }), - JSON.stringify({ type: 'user.message', timestamp: '2026-05-01T10:00:05Z', data: { content: 'hello', interactionId: 'int-1' } }), - JSON.stringify({ type: 'assistant.message', timestamp: '2026-05-01T10:00:10Z', data: { messageId: 'msg-1', outputTokens, interactionId: 'int-1', toolRequests: [] } }), + JSON.stringify({ type: 'session.model_change', timestamp: at(0), data: { newModel: 'gpt-4.1' } }), + JSON.stringify({ type: 'user.message', timestamp: at(5), data: { content: 'hello', interactionId: 'int-1' } }), + JSON.stringify({ type: 'assistant.message', timestamp: at(10), data: { messageId: 'msg-1', outputTokens, interactionId: 'int-1', toolRequests: [] } }), ] await writeFile(join(dir, 'events.jsonl'), lines.join('\n') + '\n') return join(dir, 'events.jsonl')