repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/sysinfo.test.js
Rawconst assert = require('assert');
const S = require('./sysinfo.js');
// ============================================================================
// Fixtures captured from the host (2012 ThinkPad X230) via /proc, df, systemctl, ps, hwmon.
// ============================================================================
const STAT0 = 'cpu 5430899 3815162 1035595 41251079 269664 239859 55955 0 0 0\ncpu0 1 2 3 4';
// second snapshot: +100 system, +100 idle => totalDelta 200, idleDelta 100 => usage 0.5
const STAT1 = 'cpu 5430899 3815162 1035695 41251179 269664 239859 55955 0 0 0\ncpu0 9 9 9 9';
const MEMINFO = [
'MemTotal: 16176232 kB',
'MemFree: 9155632 kB',
'MemAvailable: 12945364 kB',
'Buffers: 680 kB',
'SwapTotal: 33554428 kB',
'SwapFree: 26795148 kB'
].join('\n');
const NETDEV = [
'Inter-| Receive | Transmit',
' face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed',
' lo: 54663953 73730 0 0 0 0 0 0 54663953 73730 0 0 0 0 0 0',
'enp0s25: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0',
' wlan0: 2445277803 4752440 0 129580 0 0 0 0 5890720566 5360204 0 82 0 0 0 0'
].join('\n');
const DISKSTATS = [
' 8 0 sda 9645 2012 90857 763 42944 11803 281531 93778 0 74294 161809 0 0 0 0 39089 67268',
' 8 1 sda1 234 2012 8145 99 12 1 11 3 0 56 102 0 0 0 0 0 0',
' 7 0 loop0 10 0 80 1 0 0 0 0 0 0 0',
' 259 0 nvme0n1 287018 21005 15602232 437312 519616 172971 20543952 3947826 0 1207756 4536137 13779 0 34278912 73655 19543 77343'
].join('\n');
const LOADAVG = '0.76 0.64 0.56 1/714 710938';
const DF = [
'Filesystem 1-blocks Used Available Capacity Mounted on',
'/dev/sda3 590718976000 31650119680 556774883328 6% /',
'tmpfs 8282230784 5876457472 2405773312 71% /tmp',
'/dev/sda1 1071624192 193921024 877703168 19% /boot',
'/dev/sda3 590718976000 31650119680 556774883328 6% /home'
].join('\n');
const FAILED_USER = 'app-Handy@autostart.service not-found failed failed app-Handy@autostart.service';
const FAILED_NONE = '';
const PS = [
' PID COMMAND %CPU %MEM',
' 710984 rtk 77.7 0.0',
' 493586 claude 2.7 2.8',
' 619330 firefox 1.7 2.9'
].join('\n');
// ============================================================================
// cpuDelta — first call (no prev), steady-state delta, garbage guard
// ============================================================================
let cd0 = S.cpuDelta(null, STAT0);
assert.strictEqual(cd0.usage, 0, 'first call usage 0');
assert.strictEqual(cd0.next.idle, 41251079 + 269664, 'idle = idle + iowait');
assert.strictEqual(cd0.next.total, 5430899 + 3815162 + 1035595 + 41251079 + 269664 + 239859 + 55955, 'total = sum');
let cd1 = S.cpuDelta(cd0.next, STAT1);
assert.strictEqual(cd1.usage, 0.5, 'delta 200 total / 100 non-idle => 0.5');
let cdg = S.cpuDelta(cd0.next, 'garbage no numbers here');
assert.strictEqual(cdg.usage, 0, 'garbage -> usage 0');
assert.strictEqual(cdg.next, cd0.next, 'garbage keeps prev snapshot');
// ============================================================================
// parseMeminfo — bytes (kB*1024) + usage ratios
// ============================================================================
let mem = S.parseMeminfo(MEMINFO);
assert.strictEqual(mem.memTotal, 16176232 * 1024, 'memTotal bytes');
assert.strictEqual(mem.memAvailable, 12945364 * 1024, 'memAvailable bytes (prefers MemAvailable)');
assert.strictEqual(mem.swapTotal, 33554428 * 1024, 'swapTotal bytes');
assert.strictEqual(mem.swapFree, 26795148 * 1024, 'swapFree bytes');
assert.ok(Math.abs(mem.memoryUsage - (16176232 - 12945364) / 16176232) < 1e-9, 'memoryUsage ratio');
assert.ok(Math.abs(mem.swapUsage - (33554428 - 26795148) / 33554428) < 1e-9, 'swapUsage ratio');
// no-swap machine -> swapUsage 0, no divide-by-zero
assert.strictEqual(S.parseMeminfo('MemTotal: 100 kB\nMemAvailable: 50 kB').swapUsage, 0, 'no swap -> 0');
// ============================================================================
// parseNetDev — sum non-lo
// ============================================================================
let net = S.parseNetDev(NETDEV);
assert.strictEqual(net.rx, 0 + 2445277803, 'rx sums enp0s25 + wlan0, excludes lo');
assert.strictEqual(net.tx, 0 + 5890720566, 'tx sums enp0s25 + wlan0, excludes lo');
// ============================================================================
// parseDiskstats — sectors*512, skip loop/ram AND partitions (avoid double-count)
// ============================================================================
let disk = S.parseDiskstats(DISKSTATS);
// whole-disk only: sda=90857, nvme0n1=15602232 ; sda1 partition + loop0 skipped
assert.strictEqual(disk.readBytes, (90857 + 15602232) * 512, 'readBytes whole-disk sum *512');
assert.strictEqual(disk.writeBytes, (281531 + 20543952) * 512, 'writeBytes whole-disk sum *512');
// ============================================================================
// rate — per-second, guards reset + bad interval
// ============================================================================
assert.strictEqual(S.rate(1000, 3000, 2), 1000, 'rate (3000-1000)/2');
assert.strictEqual(S.rate(5000, 1000, 2), 0, 'counter reset -> 0');
assert.strictEqual(S.rate(0, 100, 0), 0, 'zero interval -> 0');
// ============================================================================
// parseLoadavg
// ============================================================================
let la = S.parseLoadavg(LOADAVG);
assert.strictEqual(la.one, 0.76);
assert.strictEqual(la.five, 0.64);
assert.strictEqual(la.fifteen, 0.56);
// ============================================================================
// parseDf — skip header, parse percent, capture source + target
// ============================================================================
let df = S.parseDf(DF);
assert.strictEqual(df.length, 4, 'four real rows, header skipped');
assert.deepStrictEqual(df[0], { source: '/dev/sda3', target: '/', usedPercent: 6 });
assert.deepStrictEqual(df[1], { source: 'tmpfs', target: '/tmp', usedPercent: 71 });
assert.strictEqual(df[3].target, '/home');
// ============================================================================
// parseFailedUnits
// ============================================================================
assert.deepStrictEqual(S.parseFailedUnits(FAILED_USER), ['app-Handy@autostart.service']);
assert.deepStrictEqual(S.parseFailedUnits(FAILED_NONE), [], 'empty -> no units');
// ============================================================================
// parsePsTop — skip header (non-numeric pid)
// ============================================================================
let ps = S.parsePsTop(PS);
assert.strictEqual(ps.length, 3, 'header dropped');
assert.deepStrictEqual(ps[0], { pid: 710984, name: 'rtk', cpu: 77.7, mem: 0.0 });
assert.strictEqual(ps[1].name, 'claude');
// ============================================================================
// parseTemp / parseFan
// ============================================================================
assert.strictEqual(S.parseTemp('65000'), 65, 'millidegrees -> C');
assert.strictEqual(S.parseTemp(''), null, 'empty temp -> null');
assert.strictEqual(S.parseFan('3816'), 3816, 'fan rpm');
assert.strictEqual(S.parseFan(' '), null, 'empty fan -> null');
// ============================================================================
// formatBytes / formatRate — verbatim thresholds
// ============================================================================
assert.strictEqual(S.formatBytes(512), '512 B');
assert.strictEqual(S.formatBytes(2048), '2.0 KiB');
assert.strictEqual(S.formatBytes(5 * 1048576), '5.0 MiB');
assert.strictEqual(S.formatBytes(3 * 1073741824), '3.0 GiB');
assert.strictEqual(S.formatBytes(-5), '0 B', 'negative clamps');
assert.strictEqual(S.formatRate(2048), '2.0 KiB/s');
// ============================================================================
// pushRing — append + cap, returns new array, original untouched
// ============================================================================
let r = [];
for (let i = 1; i <= 5; i++) r = S.pushRing(r, i, 3);
assert.deepStrictEqual(r, [3, 4, 5], 'capped to last 3');
let orig = [1, 2];
let r2 = S.pushRing(orig, 3, 32);
assert.deepStrictEqual(orig, [1, 2], 'input not mutated');
assert.deepStrictEqual(r2, [1, 2, 3], 'appended copy');
// ============================================================================
// detectIssues — empty all-clear, then each rule: absent / below / above
// ============================================================================
const CORES = 4;
function base() {
// a fully-healthy state: every check present and below threshold
return {
failedUnits: [],
cpuTemperatureC: 55, tempHighC: 87, degradationReason: null,
diskUsage: [{ source: '/dev/sda3', target: '/', usedPercent: 6 }],
memoryUsage: 0.5, swapGrowing: false,
healthPercentage: 95, isLaptopBattery: true,
loadOne: 1.0, cores: CORES,
updateCount: 0
};
}
function ids(state) { return S.detectIssues(state).map(function (i) { return i.id; }); }
function find(state, id) { return S.detectIssues(state).filter(function (i) { return i.id === id; })[0]; }
// all-clear -> [] (no synthetic ok row; that's a view concern)
assert.deepStrictEqual(S.detectIssues(base()), [], 'healthy -> no issues');
assert.deepStrictEqual(S.detectIssues({}), [], 'empty state -> no issues (all absent)');
// --- Failed services (error) ---
{
let s = base(); s.failedUnits = ['app-Handy@autostart.service'];
let f = find(s, 'failed-services');
assert.strictEqual(f.severity, 'error', 'failed -> error');
assert.deepStrictEqual(f.action, { kind: 'systemctl-status', units: ['app-Handy@autostart.service'] });
let absent = base(); delete absent.failedUnits;
assert.strictEqual(ids(absent).indexOf('failed-services'), -1, 'no failedUnits -> skip');
}
// --- Thermal: absent / below 87 / >=87 warning / degradation error ---
{
let absent = base(); absent.cpuTemperatureC = null; absent.degradationReason = null;
assert.strictEqual(ids(absent).indexOf('thermal'), -1, 'no temp + no signal -> skip');
let below = base(); below.cpuTemperatureC = 86;
assert.strictEqual(ids(below).indexOf('thermal'), -1, '86 < 87 -> none');
let at = base(); at.cpuTemperatureC = 87;
assert.strictEqual(find(at, 'thermal').severity, 'warning', '87 >= 87 -> warning');
let deg = base(); deg.cpuTemperatureC = 60; deg.degradationReason = 'ThermalLimit';
assert.strictEqual(find(deg, 'thermal').severity, 'error', 'degradation thermal -> error even when cool');
let degOther = base(); degOther.degradationReason = 'PowerSupply';
assert.strictEqual(ids(degOther).indexOf('thermal'), -1, 'non-thermal degradation -> skip');
}
// --- Low disk: absent / 89 none / 90 warn / 94 warn / 95 error ; per-source dedupe ---
{
let absent = base(); delete absent.diskUsage;
assert.strictEqual(ids(absent).indexOf('low-disk:/dev/sda3'), -1, 'no diskUsage -> skip');
function diskAt(pct) { let s = base(); s.diskUsage = [{ source: '/dev/sda3', target: '/', usedPercent: pct }]; return s; }
assert.strictEqual(S.detectIssues(diskAt(89)).length, 0, '89 -> none');
assert.strictEqual(find(diskAt(90), 'low-disk:/dev/sda3').severity, 'warning', '90 -> warning');
assert.strictEqual(find(diskAt(94), 'low-disk:/dev/sda3').severity, 'warning', '94 -> warning');
assert.strictEqual(find(diskAt(95), 'low-disk:/dev/sda3').severity, 'error', '95 -> error');
// same source mounted twice over threshold -> one issue (worst)
let dup = base();
dup.diskUsage = [
{ source: '/dev/sda3', target: '/', usedPercent: 91 },
{ source: '/dev/sda3', target: '/home', usedPercent: 96 }
];
let dupIssues = S.detectIssues(dup).filter(function (i) { return i.id.indexOf('low-disk') === 0; });
assert.strictEqual(dupIssues.length, 1, 'same device -> one disk issue');
assert.strictEqual(dupIssues[0].severity, 'error', 'worst mount wins');
}
// --- Memory pressure: needs >=0.9 AND swapGrowing ---
{
let high = base(); high.memoryUsage = 0.95; high.swapGrowing = true;
assert.strictEqual(find(high, 'mem-pressure').severity, 'warning', '0.95 + growing -> warning');
let noGrow = base(); noGrow.memoryUsage = 0.95; noGrow.swapGrowing = false;
assert.strictEqual(ids(noGrow).indexOf('mem-pressure'), -1, 'high mem but no swap growth -> none');
let low = base(); low.memoryUsage = 0.5; low.swapGrowing = true;
assert.strictEqual(ids(low).indexOf('mem-pressure'), -1, 'low mem -> none');
let absent = base(); absent.memoryUsage = null;
assert.strictEqual(ids(absent).indexOf('mem-pressure'), -1, 'no mem reading -> skip');
}
// --- Battery health tiers: 80 none, 79 warn, 60 warn, 59 error, absent/no-battery skip ---
{
function bat(h) { let s = base(); s.healthPercentage = h; return s; }
assert.strictEqual(S.detectIssues(bat(80)).filter(i => i.id === 'battery-health').length, 0, '80 -> none');
assert.strictEqual(find(bat(79), 'battery-health').severity, 'warning', '79 -> warning');
assert.strictEqual(find(bat(60), 'battery-health').severity, 'warning', '60 -> warning');
assert.strictEqual(find(bat(59), 'battery-health').severity, 'error', '59 -> error');
let absent = base(); absent.healthPercentage = null;
assert.strictEqual(ids(absent).indexOf('battery-health'), -1, 'no health -> skip');
let desktop = bat(50); desktop.isLaptopBattery = false;
assert.strictEqual(ids(desktop).indexOf('battery-health'), -1, 'no laptop battery -> skip');
}
// --- High load: loadOne > cores (strict) ---
{
let eq = base(); eq.loadOne = 4; eq.cores = 4;
assert.strictEqual(ids(eq).indexOf('high-load'), -1, 'load == cores -> none (strict >)');
let over = base(); over.loadOne = 4.1; over.cores = 4;
assert.strictEqual(find(over, 'high-load').severity, 'warning', '4.1 > 4 -> warning');
let absent = base(); absent.loadOne = null;
assert.strictEqual(ids(absent).indexOf('high-load'), -1, 'no load -> skip');
}
// --- Updates: 0 none, 1 info, absent (n/a) skip ---
{
let zero = base(); zero.updateCount = 0;
assert.strictEqual(ids(zero).indexOf('updates'), -1, '0 updates -> none');
let one = base(); one.updateCount = 1;
assert.strictEqual(find(one, 'updates').severity, 'info', '1 update -> info');
let absent = base(); absent.updateCount = null;
assert.strictEqual(ids(absent).indexOf('updates'), -1, 'updateCount n/a -> skip');
}
// --- Sort order: error -> warning -> info ---
{
let s = base();
s.failedUnits = ['x.service']; // error
s.cpuTemperatureC = 90; // warning (thermal)
s.updateCount = 3; // info
let order = S.detectIssues(s).map(i => i.severity);
let ranks = order.map(sev => sev === 'error' ? 0 : sev === 'warning' ? 1 : 2);
for (let i = 1; i < ranks.length; i++) assert.ok(ranks[i] >= ranks[i - 1], 'sorted error->warning->info');
assert.strictEqual(order[0], 'error');
assert.strictEqual(order[order.length - 1], 'info');
}
console.log('sysinfo.test.js: ALL PASS');