chadtmiller.com/knotless
git that lives in your Atmosphere account
1
10
The coverage gate names the lines it counts as missed, and no others
harness/cov.mjs+41−36
| @@ -495,11 +495,13 @@ const gate = () => { | ||
return r.status ?? 1 | ||
} | ||
| ||
/// Says each line no run reached, by file and by the function it is in. | ||
/// The table adds up each function's lines, each generic taken at its | ||
/// best instance, so a line one function misses counts though another | ||
/// reaches it, and the merged views (lcov, text) show it reached: the | ||
/// lines are read here as the table counts them, function by function. | ||
/// Says each line the gate counts as missed, by file and by the function | ||
/// it is in. The table counts a function's lines once for all its | ||
/// instances, a generic's or a macro's, at the instance that reaches the | ||
/// most of them, and counts a line again for each function it is in: the | ||
/// lines are read here the same way, so the list is the table's misses | ||
/// and no more, though another instance of a generic reaches a line its | ||
/// best one does not. | ||
const missed = (short) => { | ||
const json = join(report, 'missed.json') | ||
spawnSync( | ||
| @@ -508,40 +510,43 @@ const missed = (short) => { | ||
{ cwd: root, env: { ...env, CARGO_LLVM_COV_TARGET_DIR: report, CARGO_LLVM_COV_BUILD_DIR: report }, stdio: 'inherit' }, | ||
) | ||
if (!existsSync(json)) return | ||
const seen = new Set() | ||
console.log('Uncovered:') | ||
const functions = JSON.parse(readFileSync(json, 'utf8')).data[0].functions | ||
// Where each function some run called starts, to tell one never called | ||
// from another instance of it that was. | ||
const starts = (f) => f.filenames.map((file, i) => [relative(root, file), f.regions.find((r) => r[5] === i)]) | ||
const called = new Set(functions.filter((f) => f.count).flatMap((f) => starts(f).filter(([, r]) => r).map(([at, r]) => `${at}:${r[0]}`))) | ||
for (const f of functions) { | ||
if (!f.count) { | ||
// A function no run called, a closure most often: where it starts. | ||
for (const [at, first] of starts(f)) { | ||
const key = first && `${at}:${first[0]}` | ||
if (!key || called.has(key) || seen.has(key) || !short.some((s) => at.endsWith(s))) continue | ||
seen.add(key) | ||
console.log(` ${key} in ${f.name}, never called`) | ||
} | ||
// A region: line and column it starts and ends at, its count, its | ||
// file, the file it expands, and its kind, 0 being code. | ||
const at = (f) => relative(root, f.filenames[0]) | ||
const first = (f) => f.regions.find((r) => r[5] === 0) | ||
// The lines an instance misses: where each code region starts that no | ||
// reached region in the same file covers. A macro's call is an | ||
// expansion region, and one reached reaches its line. | ||
const misses = (f) => { | ||
const here = f.regions.filter((r) => r[5] === 0) | ||
const lines = here.filter((r) => r[7] === 0).map((r) => r[0]) | ||
return [...new Set(lines.filter((l) => !here.some((r) => r[4] > 0 && r[0] <= l && l <= r[2])))] | ||
} | ||
// The instances of one function start at the same place. | ||
const groups = new Map() | ||
for (const f of JSON.parse(readFileSync(json, 'utf8')).data[0].functions) { | ||
const start = first(f) | ||
if (!start || !short.some((s) => at(f).endsWith(s))) continue | ||
const key = `${at(f)}:${start[0]}:${start[1]}` | ||
groups.set(key, [...(groups.get(key) ?? []), f]) | ||
} | ||
const out = [] | ||
for (const fs of groups.values()) { | ||
const f = fs[0] | ||
if (!fs.some((f) => f.count)) { | ||
out.push([at(f), first(f)[0], f.name, ', never called']) | ||
continue | ||
} | ||
// A region: line and column it starts and ends at, its count, its | ||
// file, the file it expands, and its kind, 0 being code. | ||
for (const [i, file] of f.filenames.entries()) { | ||
const at = relative(root, file) | ||
if (!short.some((s) => at.endsWith(s))) continue | ||
// A macro's call is an expansion region: one reached reaches its line. | ||
const here = f.regions.filter((r) => r[5] === i) | ||
for (const [start] of here.filter((r) => r[7] === 0)) { | ||
if (here.some((r) => r[4] > 0 && r[0] <= start && start <= r[2])) continue | ||
const key = `${at}:${start}` | ||
if (seen.has(key)) continue | ||
seen.add(key) | ||
console.log(` ${key} in ${f.name}`) | ||
} | ||
} | ||
const best = fs | ||
.filter((f) => f.count) | ||
.map((f) => ({ f, lines: misses(f) })) | ||
.reduce((a, b) => (b.lines.length < a.lines.length ? b : a)) | ||
for (const l of best.lines) out.push([at(best.f), l, best.f.name, '']) | ||
} | ||
console.log('Uncovered:') | ||
const rows = out.map(([file, line, name, never]) => [file, line, name + never]) | ||
rows.sort((a, b) => a[0].localeCompare(b[0]) || a[1] - b[1]) | ||
for (const [file, line, name] of rows) console.log(` ${file}:${line} in ${name}`) | ||
} | ||
| ||
let status = gate() | ||