feat: 0.9.0 忽略规则、增量缓存跨会话持久化、快照基线、字符级预算自适应与性能基准E2E - 忽略规则(gitignore简化子集:目录整树剪枝不枚举不计数、扩展名通配与名字匹配、随偏好持久化)、扫描增量缓存落盘userData跨会话自动注入(LRU 8对、防御性解析、渲染端零改动)、快照基线(保存/加载JSON对照六类状态变迁统计与条目标注、对照随重扫刷新、报告附快照口径)、字符级变更组预算随输入规模自适应(下限与固定值一致小输入零变化、大输入按总组流半数放宽上限100万封顶)、大文件性能基准E2E(5万行<15s与300KB GBK<10s耗时断言)+新功能E2E、文档与版本号同步
This commit is contained in:
@@ -581,3 +581,116 @@ describe('scanFolders - 增量重扫(previous 缓存)', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('scanFolders - 忽略规则', () => {
|
||||
it('目录规则整树剪枝:子树不枚举、不计数、不出现在结果与缓存', async () => {
|
||||
const l = await mkdtemp(join(tmpdir(), 'difflens-ign1-l-'))
|
||||
const r = await mkdtemp(join(tmpdir(), 'difflens-ign1-r-'))
|
||||
try {
|
||||
await writeFile(join(l, 'keep.txt'), 'a')
|
||||
await writeFile(join(r, 'keep.txt'), 'a')
|
||||
// 深层子树:node_modules/pkg/dist/x.js(全部应被剪枝)
|
||||
for (const d of [l, r]) {
|
||||
await mkdir(join(d, 'node_modules', 'pkg', 'dist'), { recursive: true })
|
||||
await writeFile(join(d, 'node_modules', 'pkg', 'dist', 'x.js'), 'x')
|
||||
await writeFile(join(d, 'node_modules', 'pkg', 'y.js'), 'y')
|
||||
}
|
||||
const res = await scanFolders(l, r, { ignore: ['node_modules/'] })
|
||||
expect(res.entries.map((e) => e.rel)).toEqual(['keep.txt'])
|
||||
expect(res.total).toBe(1)
|
||||
expect(res.truncated).toBe(false)
|
||||
expect(res.cache.map((c) => c.rel)).toEqual(['keep.txt'])
|
||||
} finally {
|
||||
await rm(l, { recursive: true, force: true })
|
||||
await rm(r, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('扩展名通配与名字匹配跳过文件(大小写不敏感)', async () => {
|
||||
const l = await mkdtemp(join(tmpdir(), 'difflens-ign2-l-'))
|
||||
const r = await mkdtemp(join(tmpdir(), 'difflens-ign2-r-'))
|
||||
try {
|
||||
for (const d of [l, r]) {
|
||||
await writeFile(join(d, 'keep.txt'), 'a')
|
||||
await writeFile(join(d, 'debug.LOG'), 'x')
|
||||
await writeFile(join(d, 'temp'), 't')
|
||||
await writeFile(join(d, 'other.txt'), 'o')
|
||||
}
|
||||
const res = await scanFolders(l, r, { ignore: ['*.log', 'temp'] })
|
||||
expect(res.entries.map((e) => e.rel).sort()).toEqual(['keep.txt', 'other.txt'])
|
||||
expect(res.total).toBe(2)
|
||||
} finally {
|
||||
await rm(l, { recursive: true, force: true })
|
||||
await rm(r, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('被忽略文件不占用 maxFiles:原本截断的场景忽略后完整扫描', async () => {
|
||||
const l = await mkdtemp(join(tmpdir(), 'difflens-ign3-l-'))
|
||||
const r = await mkdtemp(join(tmpdir(), 'difflens-ign3-r-'))
|
||||
try {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await writeFile(join(l, `f${i}.txt`), String(i))
|
||||
await writeFile(join(r, `f${i}.txt`), String(i))
|
||||
}
|
||||
for (const d of [l, r]) {
|
||||
await mkdir(join(d, 'dist'), { recursive: true })
|
||||
for (let i = 0; i < 5; i++) await writeFile(join(d, 'dist', `b${i}.txt`), 'b')
|
||||
}
|
||||
// 无忽略 + maxFiles=6:截断
|
||||
const noIgnore = await scanFolders(l, r, { maxFiles: 6 })
|
||||
expect(noIgnore.truncated).toBe(true)
|
||||
// 忽略 dist/ 后仅 5 个文件:完整扫描
|
||||
const res = await scanFolders(l, r, { maxFiles: 6, ignore: ['dist/'] })
|
||||
expect(res.truncated).toBe(false)
|
||||
expect(res.total).toBe(5)
|
||||
expect(res.entries).toHaveLength(5)
|
||||
} finally {
|
||||
await rm(l, { recursive: true, force: true })
|
||||
await rm(r, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('忽略规则变化后重扫:缓存未命中的重新纳入条目走全量判定', async () => {
|
||||
const l = await mkdtemp(join(tmpdir(), 'difflens-ign4-l-'))
|
||||
const r = await mkdtemp(join(tmpdir(), 'difflens-ign4-r-'))
|
||||
try {
|
||||
await writeFile(join(l, 'a.txt'), 'a')
|
||||
await writeFile(join(r, 'a.txt'), 'a')
|
||||
await mkdir(join(l, 'dist'), { recursive: true })
|
||||
await mkdir(join(r, 'dist'), { recursive: true })
|
||||
await writeFile(join(l, 'dist/x.txt'), 'x')
|
||||
await writeFile(join(r, 'dist/x.txt'), 'x')
|
||||
// 第一次:忽略 dist/
|
||||
const first = await scanFolders(l, r, { ignore: ['dist/'] })
|
||||
expect(first.entries.map((e) => e.rel)).toEqual(['a.txt'])
|
||||
// 第二次:去掉忽略规则 + 携带缓存 → dist/x.txt 未命中缓存,全量判定 same
|
||||
const second = await scanFolders(l, r, { previous: first.cache })
|
||||
expect(byRel(second.entries, 'a.txt')?.status).toBe('same')
|
||||
expect(byRel(second.entries, 'dist/x.txt')?.status).toBe('same')
|
||||
expect(second.cache.map((c) => c.rel).sort()).toEqual(['a.txt', 'dist/x.txt'])
|
||||
} finally {
|
||||
await rm(l, { recursive: true, force: true })
|
||||
await rm(r, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('空规则数组与全非法规则不产生任何剪枝(行为与无规则一致)', async () => {
|
||||
const l = await mkdtemp(join(tmpdir(), 'difflens-ign5-l-'))
|
||||
const r = await mkdtemp(join(tmpdir(), 'difflens-ign5-r-'))
|
||||
try {
|
||||
for (const d of [l, r]) {
|
||||
await writeFile(join(d, 'a.txt'), 'a')
|
||||
await mkdir(join(d, 'node_modules'), { recursive: true })
|
||||
await writeFile(join(d, 'node_modules', 'x.js'), 'x')
|
||||
}
|
||||
const empty = await scanFolders(l, r, { ignore: [] })
|
||||
expect(empty.entries).toHaveLength(2)
|
||||
const invalid = await scanFolders(l, r, { ignore: ['# note', 'a/b', '**'] })
|
||||
expect(invalid.entries).toHaveLength(2)
|
||||
} finally {
|
||||
await rm(l, { recursive: true, force: true })
|
||||
await rm(r, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user