import test from 'brittle' import { readFile } from 'node:fs/promises' import { createContext, runInContext } from 'node:vm' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' const __dirname = dirname(fileURLToPath(import.meta.url)) const helpersPath = join(__dirname, '../lib/baretop-ui-helpers.js') const largeNetFixture = join( __dirname, 'fixtures/baretop-large-net-summary.json' ) test('bareTopParseLoadavg extracts floats and task counts', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const la = ctx.bareTopParseLoadavg('2.5 1.0 0.5 3/100') t.ok(la) t.is(la.one, 2.5) t.is(la.running, 3) t.is(la.total, 100) }) test('bareTopFormatFixedColumns pads and truncates', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const line = ctx.bareTopFormatFixedColumns( ['12', 'hello-world-extra', 'x'], [4, 6, 2], ['r', 'l', 'l'], 80 ) t.ok(line.includes('12')) t.ok(line.includes('hello')) }) test('bareTopSortProcessRows stable by pid', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const rows = [ { pid: 3, name: 'c', state: 'running' }, { pid: 1, name: 'a', state: 'sleeping' } ] const byName = ctx.bareTopSortProcessRows(rows, 'name', true) t.is(bareTopProcessPidLocal(byName[0]), 1) const byPid = ctx.bareTopSortProcessRows(rows, 'pid', true) t.is(bareTopProcessPidLocal(byPid[0]), 1) }) test('bareTopSortProcessRows orders by startedAtMs when sortKey is time', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const rows = [ { pid: 2, name: 'b', startedAtMs: 2000 }, { pid: 1, name: 'a', startedAtMs: 5000 } ] const asc = ctx.bareTopSortProcessRows(rows, 'time', true) t.is(bareTopProcessPidLocal(asc[0]), 2) const desc = ctx.bareTopSortProcessRows(rows, 'time', false) t.is(bareTopProcessPidLocal(desc[0]), 1) }) test('bareTopScrollSliceLines shifts first visible line with scrollTop', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const wrapped = ['L0', 'L1', 'L2', 'L3', 'L4'] t.is(ctx.bareTopScrollSliceLines(wrapped, 0, 2).join('|'), 'L0|L1') t.is(ctx.bareTopScrollSliceLines(wrapped, 2, 2).join('|'), 'L2|L3') t.is(ctx.bareTopScrollSliceLines(wrapped, 2, 2)[0], 'L2') }) test('bareTopOverviewLines keeps ASCII section lines within cols', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const cols = 80 const snap = { metricsLive: { session: { execLineCount: 1, pipelineBytesTotal: 2, execLineWallMsTotal: 3 }, peers: 1, pipeline: { maxBytes: 1024, maxDepth: 4 }, delegateInflight: { http: 1 }, kernelCounters: { a: 5, b: 2 } }, resources: null, fairnessSnapshot: null, subprocessBridge: null, hostStats: null, extra: {}, healthBreakdown: 'base=100 =>100', healthScore: 100 } const ov = ctx.bareTopOverviewLines(snap, { cols, na: 'N/A', compact: true, deltaMode: false, prevMetrics: null, nowMs: 1700000000000, asciiSep: true, flattenCap: () => [' (stub)'], ringProto: [1, 2, 3], protomuxSpark: false, sparkW: 8, sparkAscii: true, logSpark: false, braille: false, healthDetail: false, healthBreakdown: '', splitLeftCol: 0, splitMiniProc: false, layoutVersion: 'test' }) for (const line of ov.lines) { t.ok( line.length <= cols, 'line longer than cols: ' + line.length + ' > ' + cols ) } }) test('bareTopNetTabLines sorts interfaces by rx+tx', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const lines = ctx.bareTopNetTabLines( { interfaces: [ { name: 'lo', rxBytes: 1, txBytes: 1 }, { name: 'eth0', rxBytes: 100, txBytes: 50 } ] }, 100 ) t.ok(lines.some((l) => l.includes('interfaces'))) t.ok(lines.some((l) => l.includes('eth0'))) }) test('bareTopNetTabLines expands nested objects (no [object Object])', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const raw = await readFile( join(__dirname, 'fixtures/baretop-net-summary-nested.json'), 'utf8' ) const net = JSON.parse(raw) const lines = ctx.bareTopNetTabLines(net, 100, { maxLines: 200 }) const blob = lines.join('\n') t.ok(!blob.includes('[object Object]'), 'should not stringify objects blindly') t.ok(blob.includes('replicationQueue'), 'section for queue') t.ok(blob.includes('peerFirewallStats'), 'section for firewall') t.ok(blob.includes('bsdSocketGuestBridge'), 'section for bridge') t.ok(blob.includes('acceptedSessionCount') || blob.includes('10'), 'firewall scalar') }) test('bareTopNetTabLines respects maxLines cap', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const big = { a: 1 } for (let i = 0; i < 80; i++) big['k' + i] = i const lines = ctx.bareTopNetTabLines(big, 80, { maxLines: 12 }) t.ok(lines.length <= 12) t.ok(lines[lines.length - 1].includes('truncated')) }) test('bareTopSortProcessRows supports nice and cpu keys', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const rows = [ { pid: 1, name: 'a', nice: 0 }, { pid: 2, name: 'b', nice: 5 } ] const byNice = ctx.bareTopSortProcessRows(rows, 'nice', true) t.is(bareTopProcessPidLocal(byNice[0]), 1) const rows2 = [ { pid: 1, name: 'a', cpuPct: 10 }, { pid: 2, name: 'b', cpuPct: 99 } ] const byCpu = ctx.bareTopSortProcessRows(rows2, 'cpu', false) t.is(bareTopProcessPidLocal(byCpu[0]), 2) }) test('bareTopLimitsMergedLines flattens quotas and rlimits', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const snap = { extra: { quotas: { pipe: 8 }, rlimits: { nofile: 1024 } } } const lines = ctx.bareTopLimitsMergedLines(snap) t.ok(lines.some((l) => l.includes('pipe=8'))) t.ok(lines.some((l) => l.includes('nofile=1024'))) }) test('bareTopDelegateInflightTableLines right-aligns counts', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const lines = ctx.bareTopDelegateInflightTableLines({ a: 3, b: 12 }, 5, 8) t.ok(lines[0].includes('b') && lines[0].includes('12')) t.ok(lines[1].includes('a') && lines[1].includes('3')) }) test('bareTopParseMeminfoMetrics derives MemTotal and MemAvailable', async (t) => { const code = await readFile(helpersPath, 'utf8') const ctx = createContext({}) runInContext(code, ctx) const m = ctx.bareTopParseMeminfoMetrics( 'MemTotal: 8000000 kB MemAvailable: 2000000 kB' ) t.ok(m) t.is(m.memTotal, 8000000) t.is(m.memAvail, 2000000) }) test('large net summary fixture benchmark stays within budget', async (t) => { const code = await readFile(helpersPath, 'utf8') const netRaw = await readFile(largeNetFixture, 'utf8') const net = JSON.parse(netRaw) const ctx = createContext({}) runInContext(code, ctx) const started = Date.now() let lines = [] for (let i = 0; i < 200; i++) { lines = ctx.bareTopNetTabLines(net, 120, { maxLines: 240 }) } const elapsed = Date.now() - started t.ok(lines.length > 10) t.ok(elapsed < 350, 'net fixture benchmark elapsed=' + elapsed + 'ms') }) function bareTopProcessPidLocal(row) { const n = Number(row.pid) return Number.isFinite(n) ? n : 0 }