Files
Notes/tests/unit/main-log-writer.test.js
2026-10-16 16:34:15 +08:00

128 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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();
});
});