Read chat logs incrementally and drain retention (Refs #62)
This commit is contained in:
+66
-2
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { appendFileSync, mkdirSync, renameSync, rmSync, statSync, utimesSync, writeFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
|
||||
@@ -12,7 +12,7 @@ process.env.REL_DEST = 'runtime/plutonium/storage/t5/raw/scripts/sp';
|
||||
process.env.BETA_ARCHIVE_DIR = resolve(TMP_ROOT, 'beta-archive');
|
||||
process.env.DB_PATH = resolve(TMP_ROOT, 'test.db');
|
||||
|
||||
const { resolveLogPath, tailLog, isLogKey } = await import(
|
||||
const { resolveLogPath, tailLog, readLogChunk, isLogKey } = await import(
|
||||
'../../src/lib/server/fleet/logs.js'
|
||||
);
|
||||
|
||||
@@ -94,3 +94,67 @@ describe('tailLog', () => {
|
||||
expect(tail?.lines).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('incremental log reads', () => {
|
||||
const path = resolve(LOG_DIR, 'games_sp.log');
|
||||
async function read(cursor?: import('../../src/lib/server/fleet/logs.js').LogCursor) {
|
||||
const chunk = await readLogChunk(path, cursor);
|
||||
if (!chunk || !('cursor' in chunk)) throw new Error('Expected a byte chunk');
|
||||
return { ...chunk, bytes: Buffer.from(chunk.contentBase64, 'base64') };
|
||||
}
|
||||
|
||||
it('reads only appended bytes and returns no body for unchanged files', async () => {
|
||||
expect(await readLogChunk(path)).toBeNull();
|
||||
writeFileSync(path, 'first\n');
|
||||
const first = await read();
|
||||
expect(first.bytes.toString()).toBe('first\n');
|
||||
expect(first.reset).toBe(true);
|
||||
const unchanged = await read(first.cursor);
|
||||
expect(unchanged.contentBase64).toBe('');
|
||||
expect(unchanged.cursor).toEqual(first.cursor);
|
||||
appendFileSync(path, 'café\n');
|
||||
const appended = await read(first.cursor);
|
||||
expect(appended.bytes.toString()).toBe('café\n');
|
||||
expect(appended.reset).toBe(false);
|
||||
expect(appended.cursor.offset).toBe(Buffer.byteLength('first\ncafé\n'));
|
||||
});
|
||||
|
||||
it('resets after rotation, truncation and copy-truncate regrowth with a restored mtime', async () => {
|
||||
const header = 'same game header\n'.repeat(30);
|
||||
writeFileSync(path, header + 'original\n');
|
||||
let chunk = await read();
|
||||
renameSync(path, path + '.1');
|
||||
writeFileSync(path, header + 'rotated!\n');
|
||||
chunk = await read(chunk.cursor);
|
||||
expect(chunk.reset).toBe(true);
|
||||
expect(chunk.bytes.toString()).toBe(header + 'rotated!\n');
|
||||
const mtime = statSync(path).mtime;
|
||||
writeFileSync(path, header + 'rewritten and longer\n');
|
||||
utimesSync(path, mtime, mtime);
|
||||
chunk = await read(chunk.cursor);
|
||||
expect(chunk.reset).toBe(true);
|
||||
expect(chunk.bytes.toString()).toBe(header + 'rewritten and longer\n');
|
||||
writeFileSync(path, 'short\n');
|
||||
chunk = await read(chunk.cursor);
|
||||
expect(chunk.reset).toBe(true);
|
||||
expect(chunk.bytes.toString()).toBe('short\n');
|
||||
});
|
||||
|
||||
it('bounds initial reads and drains appended backlogs even when the file stops changing', async () => {
|
||||
const window = 256 * 1024;
|
||||
writeFileSync(path, 'x'.repeat(window + 10));
|
||||
const initial = await read();
|
||||
expect(initial.bytes.length).toBe(window);
|
||||
expect(initial.truncated).toBe(true);
|
||||
const appended = Buffer.from('y'.repeat(window + 20));
|
||||
appendFileSync(path, appended);
|
||||
const first = await read(initial.cursor);
|
||||
const second = await read(first.cursor);
|
||||
expect(first.reset).toBe(false);
|
||||
expect(first.bytes.length).toBe(window);
|
||||
expect(first.cursor.offset).toBeLessThan(first.size);
|
||||
expect(second.bytes.length).toBe(20);
|
||||
expect(Buffer.concat([first.bytes, second.bytes])).toEqual(appended);
|
||||
expect((await read(second.cursor)).contentBase64).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user