update
This commit is contained in:
127
tests/unit/main-log-writer.test.js
Normal file
127
tests/unit/main-log-writer.test.js
Normal file
@@ -0,0 +1,127 @@
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user