Test the scope defaults, the backlog gate and the database reader
The gate tests use a canned shell runner, and two of them build a real slopo-shaped SQLite file to prove the warm-database hole is closed: the summary line says zero new units while the database says 8,607 unembedded, and the gate must believe the database. Those fixtures insert in one transaction. Row-by-row they were 8,607 individual fsyncs, which took two minutes and timed the suite out - worth knowing before writing another one.
This commit is contained in:
1 parent
e149b6a977
commit
22be1e819a
3 files changed
+334
-3
No files matched your search
@@ -0,0 +1,146 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { test } from 'node:test'
|
||||
|
||||
import {
|
||||
DEFAULT_SOURCE_EXCLUDES,
|
||||
budgetRemedy,
|
||||
checkBudget,
|
||||
effectiveExcludes,
|
||||
estimateDuration,
|
||||
parseIndexOutput,
|
||||
readIndexCounts,
|
||||
} from '../lib/scope.js'
|
||||
|
||||
test('default excludes cover the vendored and generated directories', () => {
|
||||
// The measured 13x blow-up came from node_modules alone; these are the
|
||||
// defaults that must exist for the gate to be reachable at all.
|
||||
for (const pattern of ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/__pycache__/**']) {
|
||||
assert.ok(DEFAULT_SOURCE_EXCLUDES.includes(pattern), `missing default exclude ${pattern}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('defaults apply unless explicitly turned off', () => {
|
||||
assert.deepEqual(effectiveExcludes({}), [...DEFAULT_SOURCE_EXCLUDES])
|
||||
assert.deepEqual(effectiveExcludes({ useDefaultExcludes: true }), [...DEFAULT_SOURCE_EXCLUDES])
|
||||
assert.deepEqual(effectiveExcludes({ useDefaultExcludes: false }), [])
|
||||
})
|
||||
|
||||
test('a user list is ADDED to the defaults, not substituted for them', () => {
|
||||
// A settings patch replaces a whole config value, so if sourceDirExclude were
|
||||
// the only knob, setting it to ['ui/**'] would silently stop excluding
|
||||
// node_modules and bring the 114k-unit embed straight back.
|
||||
const excludes = effectiveExcludes({ sourceDirExclude: ['ui/**', 'src/controllers/**'] })
|
||||
assert.ok(excludes.includes('**/node_modules/**'))
|
||||
assert.ok(excludes.includes('ui/**'))
|
||||
assert.ok(excludes.includes('src/controllers/**'))
|
||||
})
|
||||
|
||||
test('duplicates collapse and junk entries are dropped', () => {
|
||||
const excludes = effectiveExcludes({
|
||||
sourceDirExclude: ['ui/**', 'ui/**', '', 42, null, '**/node_modules/**'],
|
||||
})
|
||||
assert.equal(excludes.filter((entry) => entry === 'ui/**').length, 1)
|
||||
assert.equal(excludes.filter((entry) => entry === '**/node_modules/**').length, 1)
|
||||
assert.ok(excludes.every((entry) => typeof entry === 'string' && entry !== ''))
|
||||
})
|
||||
|
||||
test('parseIndexOutput reads slopo 0.8.0s own summary line', () => {
|
||||
assert.deepEqual(parseIndexOutput('Indexed 3 code units from 2 files (0 unchanged, 0 removed).'), {
|
||||
units: 3,
|
||||
files: 2,
|
||||
})
|
||||
assert.deepEqual(parseIndexOutput('indexing...\nIndexed 113,768 code units from 2,104 files'), {
|
||||
units: 113768,
|
||||
files: 2104,
|
||||
})
|
||||
})
|
||||
|
||||
test('parseIndexOutput returns undefined rather than guessing', () => {
|
||||
assert.equal(parseIndexOutput(''), undefined)
|
||||
assert.equal(parseIndexOutput('Indexing code...\n'), undefined)
|
||||
assert.equal(parseIndexOutput(undefined), undefined)
|
||||
})
|
||||
|
||||
test('checkBudget allows anything at or under the budget', () => {
|
||||
assert.equal(checkBudget({ units: 1519, files: 165 }, 5000, []).allowed, true)
|
||||
assert.equal(checkBudget({ units: 5000, files: 100 }, 5000, []).allowed, true)
|
||||
})
|
||||
|
||||
test('checkBudget refuses an oversized scope and says the numbers', () => {
|
||||
const decision = checkBudget({ units: 113_768, files: 1300 }, 5000, [])
|
||||
assert.equal(decision.allowed, false)
|
||||
assert.match(decision.reason, /113,768 code units/)
|
||||
assert.match(decision.reason, /1,300 files/)
|
||||
assert.match(decision.reason, /hours/)
|
||||
assert.match(decision.reason, /against a budget of 5,000/)
|
||||
})
|
||||
|
||||
test('a budget of zero removes the limit, and an unreadable count is never a refusal', () => {
|
||||
assert.equal(checkBudget({ units: 999_999, files: 1 }, 0, []).allowed, true)
|
||||
assert.equal(checkBudget(undefined, 5000, []).allowed, true)
|
||||
assert.equal(checkBudget({ units: 999_999, files: 1 }, Number.NaN, []).allowed, true)
|
||||
})
|
||||
|
||||
test('estimateDuration is human-scaled', () => {
|
||||
assert.match(estimateDuration(40), /seconds/)
|
||||
assert.match(estimateDuration(1519), /minutes/)
|
||||
assert.match(estimateDuration(113_768), /hours/)
|
||||
})
|
||||
|
||||
test('the remedy names both levers and the current state', () => {
|
||||
const many = budgetRemedy(5000, ['a', 'b'])
|
||||
assert.match(many, /sourceDirExclude/)
|
||||
assert.match(many, /2 patterns are excluded/)
|
||||
assert.match(many, /maxIndexUnits/)
|
||||
assert.match(many, /node_modules/)
|
||||
assert.match(budgetRemedy(5000, []), /nothing is currently excluded/)
|
||||
})
|
||||
|
||||
/** Build a throwaway slopo-shaped database. */
|
||||
async function fakeDb({ units, embedded, files }) {
|
||||
const { mkdtempSync } = await import('node:fs')
|
||||
const { tmpdir } = await import('node:os')
|
||||
const { join } = await import('node:path')
|
||||
const { DatabaseSync } = await import('node:sqlite')
|
||||
const dir = mkdtempSync(join(tmpdir(), 'dsh-slopo-db-'))
|
||||
const path = join(dir, 'slopo.db')
|
||||
const db = new DatabaseSync(path)
|
||||
for (const table of ['files', 'code_units', 'embeddings']) db.exec(`create table ${table} (id integer primary key)`)
|
||||
const fill = (table, count) => {
|
||||
const insert = db.prepare(`insert into ${table} (id) values (?)`)
|
||||
for (let index = 0; index < count; index += 1) insert.run(index)
|
||||
}
|
||||
// One transaction: without it each insert is its own fsync, and 8,607 of
|
||||
// them on this filesystem took two minutes.
|
||||
db.exec('begin')
|
||||
fill('files', files)
|
||||
fill('code_units', units)
|
||||
fill('embeddings', embedded)
|
||||
db.exec('commit')
|
||||
db.close()
|
||||
return path
|
||||
}
|
||||
|
||||
test('readIndexCounts reads the real backlog out of the database', async () => {
|
||||
// This is the signal the gate actually needs: `Indexed N` counts only what was
|
||||
// newly indexed, so it is useless on a warm database.
|
||||
const path = await fakeDb({ units: 8607, embedded: 0, files: 562 })
|
||||
assert.deepEqual(readIndexCounts(path), { units: 8607, embedded: 0, unembedded: 8607, files: 562 })
|
||||
})
|
||||
|
||||
test('readIndexCounts reports what is LEFT to embed, not what is in scope', async () => {
|
||||
const path = await fakeDb({ units: 1519, embedded: 1519, files: 165 })
|
||||
const counts = readIndexCounts(path)
|
||||
assert.equal(counts.units, 1519)
|
||||
assert.equal(counts.unembedded, 0, 'a fully embedded index has no backlog')
|
||||
})
|
||||
|
||||
test('readIndexCounts returns undefined rather than throwing on a bad file', async () => {
|
||||
assert.equal(readIndexCounts('/nonexistent/definitely/not/here.db'), undefined)
|
||||
const { mkdtempSync, writeFileSync } = await import('node:fs')
|
||||
const { tmpdir } = await import('node:os')
|
||||
const { join } = await import('node:path')
|
||||
const notADb = join(mkdtempSync(join(tmpdir(), 'dsh-slopo-junk-')), 'junk.db')
|
||||
writeFileSync(notADb, 'this is not a sqlite file')
|
||||
assert.equal(readIndexCounts(notADb), undefined)
|
||||
})
|
||||
Reference in new issue
Block a user