93 lines
4.4 KiB
JavaScript
93 lines
4.4 KiB
JavaScript
// Index tests (C2-05a): the cache key and its bound, failed reads, mapped-only contents, and the rebuild
|
|
// time on a 3,000-file fixture. Fixtures are made up and built under the scratch area.
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdirSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { performance } from 'node:perf_hooks';
|
|
import { clearIndexCache, indexCacheSize, indexFor } from '../src/index-build.mjs';
|
|
import { git, mapping, MARKER } from './helpers.mjs';
|
|
import { note, scratchRepo } from './query-fixture.mjs';
|
|
|
|
const FILES = {
|
|
'Book One/Characters/Mara Vell.md': note('character', 'Mara Vell', MARKER),
|
|
'Book One/Chapters/Chapter 01.md': '# One\n[[Mara Vell]]\n',
|
|
'Inbox Note.md': `${MARKER}\n`,
|
|
};
|
|
|
|
// A mapping whose harbor universe has the given display name. Each name gives a new cache key.
|
|
const named = name => mapping(e => ({ ...e, universes: { ...e.universes, harbor: { ...e.universes.harbor, name } } }));
|
|
|
|
test('the same commit, repo path and mapping return the same index, and only mapped files are in it', async t => {
|
|
clearIndexCache();
|
|
const { repo, commit } = scratchRepo(t, FILES);
|
|
const a = await indexFor({ repo, commit, mapping: mapping() });
|
|
const b = await indexFor({ repo, commit, mapping: mapping() });
|
|
assert.equal(a, b);
|
|
assert.equal(indexCacheSize(), 1);
|
|
assert.equal(a.commit, commit);
|
|
assert.ok(a.files.has('Book One/Characters/Mara Vell.md'));
|
|
assert.ok(!a.files.has('Inbox Note.md'));
|
|
assert.deepEqual(a.names.get('mara vell'), ['Book One/Characters/Mara Vell.md']);
|
|
assert.deepEqual(a.linksIn.get('Book One/Characters/Mara Vell.md').map(l => l.path), ['Book One/Chapters/Chapter 01.md']);
|
|
});
|
|
|
|
test('a different repo path, mapping or commit gets its own index', async t => {
|
|
clearIndexCache();
|
|
const { root, repo, commit } = scratchRepo(t, FILES);
|
|
const base = await indexFor({ repo, commit, mapping: mapping() });
|
|
const alias = join(root, 'alias');
|
|
symlinkSync(repo, alias);
|
|
assert.notEqual(await indexFor({ repo: alias, commit, mapping: mapping() }), base);
|
|
assert.notEqual(await indexFor({ repo, commit, mapping: named('Renamed Harbor') }), base);
|
|
mkdirSync(join(repo, 'Book One/Places'), { recursive: true });
|
|
writeFileSync(join(repo, 'Book One/Places/New.md'), note('place', 'New'));
|
|
git(repo, 'add', '-A');
|
|
git(repo, 'commit', '-qm', 'second');
|
|
const next = git(repo, 'rev-parse', 'HEAD');
|
|
const later = await indexFor({ repo, commit: next, mapping: mapping() });
|
|
assert.notEqual(later, base);
|
|
assert.equal(later.commit, next);
|
|
assert.ok(later.files.has('Book One/Places/New.md'));
|
|
});
|
|
|
|
test('the cache holds at most eight indexes and evicts the least recently used', async t => {
|
|
clearIndexCache();
|
|
const { repo, commit } = scratchRepo(t, FILES);
|
|
const maps = Array.from({ length: 9 }, (_, i) => named(`Harbor ${i}`));
|
|
const first = await indexFor({ repo, commit, mapping: maps[0] });
|
|
for (const m of maps.slice(1)) await indexFor({ repo, commit, mapping: m });
|
|
assert.equal(indexCacheSize(), 8);
|
|
assert.notEqual(await indexFor({ repo, commit, mapping: maps[0] }), first);
|
|
assert.equal(indexCacheSize(), 8);
|
|
});
|
|
|
|
test('a failed read is not kept in the cache', async t => {
|
|
clearIndexCache();
|
|
const { repo } = scratchRepo(t, FILES);
|
|
await assert.rejects(indexFor({ repo, commit: '0'.repeat(40), mapping: mapping() }));
|
|
assert.equal(indexCacheSize(), 0);
|
|
});
|
|
|
|
test('a missing or unchecked mapping is refused before any read', async t => {
|
|
clearIndexCache();
|
|
const { repo, commit } = scratchRepo(t, FILES);
|
|
await assert.rejects(indexFor({ repo, commit, mapping: undefined }), { code: 'library-mapping-invalid' });
|
|
await assert.rejects(indexFor({ repo, commit, mapping: { kind: 'hybrid' } }), { code: 'library-mapping-invalid' });
|
|
});
|
|
|
|
test('a 3,000-file rebuild completes and its time is reported', { timeout: 300_000 }, async t => {
|
|
clearIndexCache();
|
|
const folders = ['Characters', 'Places', 'Research'];
|
|
const files = {};
|
|
for (let i = 0; i < 3000; i++) {
|
|
files[`Book One/${folders[i % 3]}/Note ${i}.md`] = note('character', `Note ${i}`, `Body ${i} [[Note ${(i + 1) % 3000}]]`);
|
|
}
|
|
const { repo, commit } = scratchRepo(t, files);
|
|
const started = performance.now();
|
|
const index = await indexFor({ repo, commit, mapping: mapping() });
|
|
const ms = performance.now() - started;
|
|
t.diagnostic(`3,000-file index rebuild: ${ms.toFixed(0)} ms`);
|
|
assert.equal(index.paths.length, 3000);
|
|
});
|