Files
Notes/tests/unit/fs-handler-lock.test.js
2026-10-16 16:34:15 +08:00

90 lines
3.2 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.
// src/app.js onFilesChanged —— handler 并发闸(audit P1-4)
//
// 背景:原代码
// const myToken = ++fsHandlerToken; // 自增但还没 SET
// if (inFlightToken !== 0) { return; } // 检查还是 0
// inFlightToken = myToken; // 才 SET
// 两行间是 microtask 窗口:burst 50 events 时多个 callback 排在同一 microtask
// queue 里,每个都看到 inFlightToken === 0,全部过闸 → handler 并发跑
// (applyEntries 双跑、scanDir 双发、UI 闪烁)。
//
// 修复:抽 makeLock() 工厂 → { tryClaim, release, current }。tryClaim 是同步
// 「check-and-set」原子操作:if 锁空闲则 SET 并返新 token,否则返 null。
// 在 callback 入口 tryClaim → null 直接 return → 不再丢事件,统一存到
// pendingRescanRel(既有逻辑保留)。
//
// 测试策略:直接 import 工厂函数,模拟 burst 场景验证「N 次 tryClaim 只有
// 第一次成功」。
import { describe, it, expect } from 'vitest';
import { makeLock } from '../../src/fs-handler-lock.js';
describe('fs-handler lock(audit P1-4)', () => {
it('初始空闲 → tryClaim 返非零 token,current 反映该 token', () => {
const lock = makeLock();
const t = lock.tryClaim();
expect(t).toBeGreaterThan(0);
expect(lock.current()).toBe(t);
});
it('已占用时 tryClaim 返 null,current 不变', () => {
const lock = makeLock();
const t1 = lock.tryClaim();
const t2 = lock.tryClaim();
expect(t1).toBeGreaterThan(0);
expect(t2).toBe(null);
expect(lock.current()).toBe(t1);
});
it('release(token) → 锁重新空闲,tryClaim 能拿到新 token', () => {
const lock = makeLock();
const t1 = lock.tryClaim();
expect(lock.release(t1)).toBe(true);
expect(lock.current()).toBe(0);
const t2 = lock.tryClaim();
expect(t2).toBeGreaterThan(0);
expect(t2).not.toBe(t1);
});
it('release(非当前 token) → 返 false,锁不被错误释放', () => {
const lock = makeLock();
const t1 = lock.tryClaim();
expect(lock.release(99999)).toBe(false);
expect(lock.current()).toBe(t1);
});
it('burst 50 次 tryClaim → 只有 1 次成功', () => {
// 关键测试:模拟 microtask 批量场景
const lock = makeLock();
const results = Array.from({ length: 50 }, () => lock.tryClaim());
const successes = results.filter((r) => r !== null);
expect(successes).toHaveLength(1);
// 其余 49 次全部 null
expect(results.filter((r) => r === null)).toHaveLength(49);
});
it('release → tryClaim → 再 burst:交替可串行化多批事件', () => {
const lock = makeLock();
for (let batch = 0; batch < 5; batch++) {
const t = lock.tryClaim();
expect(t).not.toBe(null);
// 本批 10 次并发全应被拒
for (let i = 0; i < 10; i++) {
expect(lock.tryClaim()).toBe(null);
}
// 释放
expect(lock.release(t)).toBe(true);
}
});
it('token 自增:每次成功 tryClaim 返的 token 都不同', () => {
const lock = makeLock();
const t1 = lock.tryClaim();
lock.release(t1);
const t2 = lock.tryClaim();
lock.release(t2);
const t3 = lock.tryClaim();
expect(new Set([t1, t2, t3]).size).toBe(3);
});
});