Files
bare-operating-system/packages/bare-os-coreutils/test/baretop-ui-helpers.test.mjs
T
2026-08-18 18:11:34 -04:00

244 lines
7.8 KiB
JavaScript

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/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
}