128 lines
4.6 KiB
JavaScript
128 lines
4.6 KiB
JavaScript
// P2-10 / P2-12(audit):主进程日志改写异步缓冲。
|
||
//
|
||
// 旧实现:console.log/warn/error 重写层每行 appendFileSync + mkdirSync ——
|
||
// 同步 IO 阻塞 main 事件循环(AI 流式高频日志时尤甚),且顶层同步 fs。
|
||
//
|
||
// 新模块 main/main-log-writer.js:createMainLogWriter({ logDir, logFile,
|
||
// fsImpl, flushMs, maxBuffered, setTimeoutImpl, clearTimeoutImpl }) →
|
||
// write(line) 入缓冲;超 maxBuffered 丢最老;调度 flush
|
||
// flush() mkdir(recursive) + appendFile 一把写出;失败静默丢弃
|
||
// dispose() 取消待发 flush(测试 / 退出用)
|
||
//
|
||
// fsImpl 默认 fs.promises;时钟 / 定时器可注入,单测同步推进。
|
||
|
||
import { describe, it, expect, vi } from 'vitest';
|
||
const { createMainLogWriter } = require('../../main/main-log-writer.js');
|
||
|
||
function fakeFs() {
|
||
const calls = { mkdir: [], append: [] };
|
||
return {
|
||
calls,
|
||
mkdir: async (dir, opts) => { calls.mkdir.push([dir, opts]); },
|
||
appendFile: async (file, data) => { calls.append.push([file, data]); },
|
||
};
|
||
}
|
||
|
||
function fakeTimers() {
|
||
const pending = [];
|
||
return {
|
||
pending,
|
||
setTimeout: (fn, ms) => { const h = { fn, ms, unref() {} }; pending.push(h); return h; },
|
||
clearTimeout: (h) => { const i = pending.indexOf(h); if (i >= 0) pending.splice(i, 1); },
|
||
fireAll: () => { const fns = pending.splice(0); for (const h of fns) h.fn(); },
|
||
};
|
||
}
|
||
|
||
describe('createMainLogWriter —— P2-10 异步缓冲日志', () => {
|
||
it('write 只入缓冲,flush 后一把 appendFile', async () => {
|
||
const fs = fakeFs();
|
||
const w = createMainLogWriter({ logDir: '/d', logFile: '/d/a.log', fsImpl: fs });
|
||
w.write('l1\n');
|
||
w.write('l2\n');
|
||
expect(fs.calls.append).toEqual([]);
|
||
await w.flush();
|
||
expect(fs.calls.mkdir).toEqual([['/d', { recursive: true }]]);
|
||
expect(fs.calls.append).toEqual([['/d/a.log', 'l1\nl2\n']]);
|
||
});
|
||
|
||
it('flush 期间缓冲清空;空缓冲 flush 是 no-op', async () => {
|
||
const fs = fakeFs();
|
||
const w = createMainLogWriter({ logDir: '/d', logFile: '/d/a.log', fsImpl: fs });
|
||
await w.flush();
|
||
expect(fs.calls.append).toEqual([]);
|
||
w.write('x\n');
|
||
await w.flush();
|
||
await w.flush();
|
||
expect(fs.calls.append).toEqual([['/d/a.log', 'x\n']]);
|
||
});
|
||
|
||
it('mkdir 只第一次做(缓存 promise)', async () => {
|
||
const fs = fakeFs();
|
||
const w = createMainLogWriter({ logDir: '/d', logFile: '/d/a.log', fsImpl: fs });
|
||
w.write('a\n');
|
||
await w.flush();
|
||
w.write('b\n');
|
||
await w.flush();
|
||
expect(fs.calls.mkdir.length).toBe(1);
|
||
expect(fs.calls.append.length).toBe(2);
|
||
});
|
||
|
||
it('写失败静默丢弃,不抛错,缓冲不残留', async () => {
|
||
const fs = {
|
||
mkdir: async () => {},
|
||
appendFile: async () => { throw new Error('disk full'); },
|
||
};
|
||
const w = createMainLogWriter({ logDir: '/d', logFile: '/d/a.log', fsImpl: fs });
|
||
w.write('boom\n');
|
||
await expect(w.flush()).resolves.toBeUndefined();
|
||
// 缓冲已清,不会无限重试
|
||
await w.flush();
|
||
});
|
||
|
||
it('缓冲超 maxBuffered 丢最老行', async () => {
|
||
const fs = fakeFs();
|
||
const w = createMainLogWriter({
|
||
logDir: '/d', logFile: '/d/a.log', fsImpl: fs, maxBuffered: 3,
|
||
});
|
||
w.write('1\n'); w.write('2\n'); w.write('3\n'); w.write('4\n');
|
||
await w.flush();
|
||
expect(fs.calls.append).toEqual([['/d/a.log', '2\n3\n4\n']]);
|
||
});
|
||
|
||
it('write 调度一次 flush 定时器(不重复排)', async () => {
|
||
const fs = fakeFs();
|
||
const timers = fakeTimers();
|
||
const w = createMainLogWriter({
|
||
logDir: '/d', logFile: '/d/a.log', fsImpl: fs,
|
||
setTimeoutImpl: timers.setTimeout, clearTimeoutImpl: timers.clearTimeout,
|
||
flushMs: 250,
|
||
});
|
||
w.write('a\n');
|
||
w.write('b\n');
|
||
expect(timers.pending.length).toBe(1);
|
||
expect(timers.pending[0].ms).toBe(250);
|
||
timers.fireAll();
|
||
await w.flush(); // 等 in-flight 落地
|
||
expect(fs.calls.append.length).toBe(1);
|
||
expect(fs.calls.append[0][1]).toBe('a\nb\n');
|
||
});
|
||
|
||
it('dispose 取消待发 flush', () => {
|
||
const timers = fakeTimers();
|
||
const w = createMainLogWriter({
|
||
logDir: '/d', logFile: '/d/a.log', fsImpl: fakeFs(),
|
||
setTimeoutImpl: timers.setTimeout, clearTimeoutImpl: timers.clearTimeout,
|
||
});
|
||
w.write('a\n');
|
||
expect(timers.pending.length).toBe(1);
|
||
w.dispose();
|
||
expect(timers.pending.length).toBe(0);
|
||
});
|
||
|
||
it('默认 fsImpl / 定时器下 write 不抛错', () => {
|
||
const w = createMainLogWriter({ logDir: '/nonexistent-dir-x', logFile: '/nonexistent-dir-x/a.log' });
|
||
expect(() => w.write('hello\n')).not.toThrow();
|
||
w.dispose();
|
||
});
|
||
});
|