244 lines
7.8 KiB
JavaScript
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-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
|
|
}
|