193 lines
6.2 KiB
JavaScript
193 lines
6.2 KiB
JavaScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { join } from "node:path";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import { DiskRecordsCache, diskCacheKey } from "../out/indexer/diskCache.js";
|
|
|
|
const identity = {
|
|
projectDir: "C:/proj",
|
|
sdkDir: "C:/sdk",
|
|
indexSageXml: true,
|
|
additionalDataSearchPaths: [],
|
|
builtmodsDirs: ["C:/sdk/builtmods"],
|
|
};
|
|
|
|
const sampleRecords = {
|
|
assets: [{ type: "GameObject", id: "TankA", line: 3 }],
|
|
defines: [{ name: "HP", value: "100", line: 2 }],
|
|
includes: [{ type: "all", source: "Units.xml", line: 4 }],
|
|
rootXiIncludes: [],
|
|
nestedXiIncludes: [],
|
|
references: [],
|
|
};
|
|
|
|
function stampOf(file) {
|
|
const s = fs.statSync(file);
|
|
return {
|
|
mtimeMs: s.mtimeMs,
|
|
size: s.size,
|
|
birthtimeMs: s.birthtimeMs,
|
|
ctimeMs: s.ctimeMs,
|
|
};
|
|
}
|
|
|
|
function makeTmp(t) {
|
|
const tmp = fs.mkdtempSync(join(os.tmpdir(), "ra3-diskcache-"));
|
|
t.after(() => fs.rmSync(tmp, { recursive: true, force: true }));
|
|
return tmp;
|
|
}
|
|
|
|
test("disk cache roundtrip keeps records and leaves no temp file", async (t) => {
|
|
const tmp = makeTmp(t);
|
|
const file = join(tmp, "a.xml");
|
|
fs.writeFileSync(file, "0123456789");
|
|
const filePath = join(tmp, "index-records.json.gz");
|
|
const cache = new DiskRecordsCache(filePath, identity);
|
|
|
|
await cache.save([
|
|
[
|
|
file.toLowerCase(),
|
|
{
|
|
stat: stampOf(file),
|
|
records: sampleRecords,
|
|
kind: "full",
|
|
contentHash: "abc123",
|
|
},
|
|
],
|
|
]);
|
|
assert.equal(fs.existsSync(`${filePath}.tmp`), false, "atomic write leaves no temp");
|
|
|
|
const { records, stats } = await cache.loadValidated();
|
|
assert.equal(stats.fileExists, true);
|
|
assert.equal(stats.keyMatched, true);
|
|
assert.equal(stats.loaded, 1);
|
|
assert.equal(stats.validated, 1);
|
|
assert.equal(stats.dropped, 0);
|
|
assert.equal(records.length, 1);
|
|
assert.deepEqual(records[0].records, sampleRecords);
|
|
assert.equal(records[0].contentHash, "abc123");
|
|
});
|
|
|
|
test("stat mismatch drops the cached entry", async (t) => {
|
|
const tmp = makeTmp(t);
|
|
const file = join(tmp, "a.xml");
|
|
fs.writeFileSync(file, "0123456789");
|
|
const filePath = join(tmp, "index-records.json.gz");
|
|
const cache = new DiskRecordsCache(filePath, identity);
|
|
await cache.save([
|
|
[file.toLowerCase(), { stat: stampOf(file), records: sampleRecords, kind: "full" }],
|
|
]);
|
|
|
|
const past = new Date(Date.now() - 60000);
|
|
fs.utimesSync(file, past, past);
|
|
const { records, stats } = await cache.loadValidated();
|
|
assert.equal(stats.validated, 0);
|
|
assert.equal(stats.dropped, 1);
|
|
assert.equal(records.length, 0);
|
|
});
|
|
|
|
test("identity mismatch ignores the cache", async (t) => {
|
|
const tmp = makeTmp(t);
|
|
const file = join(tmp, "a.xml");
|
|
fs.writeFileSync(file, "0123456789");
|
|
const filePath = join(tmp, "index-records.json.gz");
|
|
const cache = new DiskRecordsCache(filePath, identity);
|
|
await cache.save([
|
|
[file.toLowerCase(), { stat: stampOf(file), records: sampleRecords, kind: "full" }],
|
|
]);
|
|
|
|
const other = new DiskRecordsCache(filePath, {
|
|
...identity,
|
|
sdkDir: "D:/other-sdk",
|
|
});
|
|
const { records, stats } = await other.loadValidated();
|
|
assert.equal(stats.fileExists, true);
|
|
assert.equal(stats.keyMatched, false);
|
|
assert.equal(records.length, 0);
|
|
});
|
|
|
|
test("corrupt cache file yields an empty result", async (t) => {
|
|
const tmp = makeTmp(t);
|
|
const filePath = join(tmp, "index-records.json.gz");
|
|
fs.writeFileSync(filePath, "this is not gzip json");
|
|
const cache = new DiskRecordsCache(filePath, identity);
|
|
const { records, stats } = await cache.loadValidated();
|
|
assert.equal(stats.fileExists, true);
|
|
assert.equal(records.length, 0);
|
|
});
|
|
|
|
test("clear removes the cache file", async (t) => {
|
|
const tmp = makeTmp(t);
|
|
const file = join(tmp, "a.xml");
|
|
fs.writeFileSync(file, "0123456789");
|
|
const filePath = join(tmp, "index-records.json.gz");
|
|
const cache = new DiskRecordsCache(filePath, identity);
|
|
await cache.save([
|
|
[file.toLowerCase(), { stat: stampOf(file), records: sampleRecords, kind: "full" }],
|
|
]);
|
|
assert.ok(fs.existsSync(filePath));
|
|
await cache.clear();
|
|
assert.equal(fs.existsSync(filePath), false);
|
|
});
|
|
|
|
test("diskCacheKey differs when the identity changes", () => {
|
|
const a = diskCacheKey(identity);
|
|
const b = diskCacheKey({ ...identity, indexSageXml: false });
|
|
assert.notEqual(a, b);
|
|
assert.equal(a, diskCacheKey(identity));
|
|
});
|
|
|
|
test("load returns records without stat validation", async (t) => {
|
|
const tmp = makeTmp(t);
|
|
const file = join(tmp, "a.xml");
|
|
fs.writeFileSync(file, "0123456789");
|
|
const filePath = join(tmp, "index-records.json.gz");
|
|
const cache = new DiskRecordsCache(filePath, identity);
|
|
await cache.save([
|
|
[
|
|
file.toLowerCase(),
|
|
{ stat: stampOf(file), records: sampleRecords, kind: "full" },
|
|
],
|
|
]);
|
|
|
|
const { records, stats } = await cache.load();
|
|
assert.equal(records.length, 1);
|
|
assert.equal(stats.fileExists, true);
|
|
assert.equal(stats.keyMatched, true);
|
|
assert.equal(stats.loaded, 1);
|
|
assert.equal(stats.validated, 0);
|
|
assert.equal(stats.dropped, 0);
|
|
assert.ok(stats.loadMs >= 0);
|
|
});
|
|
|
|
test("validate reports changed/missing entries and keeps valid ones", async (t) => {
|
|
const tmp = makeTmp(t);
|
|
const a = join(tmp, "a.xml");
|
|
const b = join(tmp, "b.xml");
|
|
fs.writeFileSync(a, "0123456789");
|
|
fs.writeFileSync(b, "0123456789");
|
|
const filePath = join(tmp, "index-records.json.gz");
|
|
const cache = new DiskRecordsCache(filePath, identity);
|
|
await cache.save([
|
|
[a.toLowerCase(), { stat: stampOf(a), records: sampleRecords, kind: "full" }],
|
|
[b.toLowerCase(), { stat: stampOf(b), records: sampleRecords, kind: "full" }],
|
|
]);
|
|
|
|
const { records } = await cache.load();
|
|
const past = new Date(Date.now() - 60000);
|
|
fs.utimesSync(b, past, past);
|
|
const progress = [];
|
|
const { stats, kept, invalidKeys } = await cache.validate(records, (done, total) => {
|
|
progress.push([done, total]);
|
|
});
|
|
assert.equal(stats.validated, 1);
|
|
assert.equal(stats.dropped, 1);
|
|
assert.equal(kept.length, 1);
|
|
assert.equal(kept[0].key, a.toLowerCase());
|
|
assert.deepEqual(invalidKeys, [b.toLowerCase()]);
|
|
assert.ok(stats.validateMs >= 0);
|
|
assert.deepEqual(progress[progress.length - 1], [1, 2]);
|
|
assert.ok(progress.every(([done], i) => i === 0 || done >= progress[i - 1][0]));
|
|
});
|