Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/system-problems.test.js

Raw
var assert = require("assert");
var Problems = require("./system-problems.js");

var BOOT_ID = "0123456789abcdef0123456789abcdef";
var MICROS = "1781526896123456";

function record(cursor, fields) {
    var entry = {
        __CURSOR: cursor,
        _BOOT_ID: BOOT_ID,
        __REALTIME_TIMESTAMP: MICROS
    };
    var names = Object.keys(fields || {});
    for (var i = 0; i < names.length; i++) entry[names[i]] = fields[names[i]];
    if (entry._SYSTEMD_UNIT === undefined) {
        if (entry.MESSAGE_ID === 'fc2e22bc6ee647b6b90729ab34a250b1') entry._SYSTEMD_UNIT = 'systemd-coredump@0-42-0.service';
        if (entry._COMM === 'systemd-oomd') entry._SYSTEMD_UNIT = 'systemd-oomd.service';
    }
    return JSON.stringify(entry);
}

// The query is read-only, bounded, metadata-only, and ORs trusted sources.
var command = Problems.journalCommand();
assert.strictEqual(command[0], "journalctl");
assert.ok(command.indexOf("--boot=0") >= 0);
assert.ok(command.indexOf("--since=-24h") >= 0);
assert.ok(command.indexOf("--lines=100") >= 0);
assert.ok(command.indexOf("--output=json") >= 0);
assert.ok(command.indexOf("--no-pager") >= 0);
assert.ok(command.indexOf("--truncate-newline") >= 0);
assert.ok(command.some(function (arg) { return arg.indexOf("--grep=^") === 0; }));
assert.ok(command.indexOf("MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1") >= 0);
assert.ok(command.indexOf("_TRANSPORT=kernel") >= 0);
assert.ok(command.indexOf("_COMM=systemd-oomd") >= 0);
assert.strictEqual(command.filter(function (arg) { return arg === "+"; }).length, 2);
var fieldsArg = command.filter(function (arg) { return arg.indexOf("--output-fields=") === 0; })[0];
assert.ok(fieldsArg.indexOf("COREDUMP_EXE") >= 0);
assert.ok(fieldsArg.indexOf("COREDUMP_COMM") >= 0);
assert.ok(fieldsArg.indexOf("COREDUMP_PID") >= 0);
assert.ok(fieldsArg.indexOf("COREDUMP_SIGNAL_NAME") >= 0);
assert.ok(fieldsArg.indexOf("COREDUMP_UNIT") >= 0);
assert.ok(fieldsArg.indexOf("COREDUMP_USER_UNIT") >= 0);
assert.ok(fieldsArg.indexOf("COREDUMP_UID") >= 0);
assert.strictEqual(fieldsArg.indexOf("COREDUMP_CMDLINE"), -1);
assert.strictEqual(fieldsArg.indexOf("COREDUMP_ENVIRON"), -1);
assert.strictEqual(fieldsArg.indexOf("COREDUMP="), -1);
assert.strictEqual(fieldsArg.indexOf("COREDUMP_PACKAGE_JSON"), -1);

// Recorded crash metadata is preferred over systemd-coredump's own process metadata.
var crash = Problems.parseJournal(record("s=crash", {
    MESSAGE_ID: "fc2e22bc6ee647b6b90729ab34a250b1",
    MESSAGE: "Process 4242 (browser) of user 1000 dumped core.\nStack trace follows",
    COREDUMP_EXE: "/usr/bin/browser",
    COREDUMP_COMM: "browser",
    COREDUMP_PID: "4242",
    COREDUMP_SIGNAL_NAME: "SIGSEGV",
    COREDUMP_UNIT: "browser.service",
    COREDUMP_USER_UNIT: "app-browser.scope",
    COREDUMP_UID: "1000",
    COREDUMP_CMDLINE: "browser --secret",
    COREDUMP_ENVIRON: "TOKEN=secret",
    COREDUMP: "core bytes"
}));
assert.strictEqual(crash.ok, true);
assert.strictEqual(crash.issues.length, 1);
var crashIssue = crash.issues[0];
assert.strictEqual(crashIssue.id, "journal:s=crash");
assert.strictEqual(crashIssue.severity, "warning");
assert.strictEqual(crashIssue.icon, "");
assert.strictEqual(crashIssue.label, "process crashed");
assert.strictEqual(crashIssue.context.source, "crash");
assert.strictEqual(crashIssue.context.metadata.executable, "/usr/bin/browser");
assert.strictEqual(crashIssue.context.metadata.comm, "browser");
assert.strictEqual(crashIssue.context.metadata.pid, "4242");
assert.strictEqual(crashIssue.context.metadata.signal, "SIGSEGV");
assert.strictEqual(crashIssue.context.metadata.unit, "browser.service");
assert.strictEqual(crashIssue.context.metadata.userUnit, "app-browser.scope");
assert.strictEqual(crashIssue.context.metadata.uid, "1000");
assert.strictEqual(crashIssue.action.kind, "journal-event");
assert.strictEqual(crashIssue.action.bootId, BOOT_ID);
assert.strictEqual(crashIssue.action.timestamp, crashIssue.context.timestamp);
assert.ok(/^\d{4}-\d\d-\d\dT/.test(crashIssue.detail));
assert.strictEqual(JSON.stringify(crashIssue).indexOf("secret"), -1);
assert.strictEqual(JSON.stringify(crashIssue).indexOf("Stack trace"), -1);

// Kernel OOM classification requires both the trusted transport and exact killed-process form.
var kernel = Problems.parseJournal(record("s=kernel", {
    _TRANSPORT: "kernel",
    MESSAGE: "Memory cgroup out of memory: Killed process 987 (render worker) total-vm:1234kB"
}));
assert.strictEqual(kernel.ok, true);
assert.strictEqual(kernel.issues[0].context.source, "kernel-oom");
assert.strictEqual(kernel.issues[0].context.metadata.comm, "render worker");
assert.strictEqual(kernel.issues[0].context.metadata.pid, "987");
assert.strictEqual(kernel.issues[0].context.metadata.signal, "SIGKILL");
assert.strictEqual(Problems.parseJournal(record("s=fake-kernel", {
    _TRANSPORT: "stdout",
    MESSAGE: "Out of memory: Killed process 1 (fake) total-vm:1kB"
})).issues.length, 0);
assert.strictEqual(Problems.parseJournal(record("s=kernel-noise", {
    _TRANSPORT: "kernel",
    MESSAGE: "error: unrelated kernel failure"
})).issues.length, 0);

// systemd-oomd messages require its trusted comm and an actual kill message.
var oomd = Problems.parseJournal(record("s=oomd", {
    _COMM: "systemd-oomd",
    MESSAGE: "Killed /user.slice/user-1000.slice/app.slice/browser.scope due to memory pressure for /user.slice"
}));
assert.strictEqual(oomd.ok, true);
assert.strictEqual(oomd.issues[0].context.source, "oomd");
assert.strictEqual(oomd.issues[0].context.metadata.target, "/user.slice/user-1000.slice/app.slice/browser.scope");
assert.strictEqual(Problems.parseJournal(record("s=fake-oomd", {
    _COMM: "other",
    MESSAGE: "Killed /user.slice/fake due to memory pressure"
})).issues.length, 0);
assert.strictEqual(Problems.parseJournal(record("s=oomd-error", {
    _COMM: "systemd-oomd",
    MESSAGE: "Failed to update monitored memory pressure candidates"
})).issues.length, 0);

assert.strictEqual(Problems.parseJournal(record("s=oomd-marked", {
    _COMM: "systemd-oomd",
    MESSAGE: "Marked /user.slice/example.scope for killing due to memory pressure"
})).issues.length, 0, 'a candidate is not a confirmed kill');

assert.strictEqual(Problems.parseJournal(record('s=forged-crash', {
    MESSAGE_ID: 'fc2e22bc6ee647b6b90729ab34a250b1', _SYSTEMD_UNIT: 'user@1000.service'
})).issues.length, 0, 'client-supplied MESSAGE_ID is insufficient');
assert.strictEqual(Problems.parseJournal(record('s=forged-oomd', {
    _COMM: 'systemd-oomd', _SYSTEMD_UNIT: 'user@1000.service', MESSAGE: 'Killed /fake.scope due to memory pressure'
})).issues.length, 0, 'an unprivileged process name is insufficient');
assert.strictEqual(crashIssue.action.cursor, crashIssue.context.cursor);

// Newest incidents lead regardless of journal output order.
var older = JSON.parse(record('s=older', {MESSAGE_ID: 'fc2e22bc6ee647b6b90729ab34a250b1'}));
var newer = Object.assign({}, older, {__CURSOR: 's=newer', __REALTIME_TIMESTAMP: String(Number(MICROS) + 1000000)});
assert.deepStrictEqual(Problems.parseJournal(JSON.stringify(older) + '\n' + JSON.stringify(newer)).issues.map(issue => issue.id), ['journal:s=newer', 'journal:s=older']);

// Empty input is a valid all-clear; malformed input or required event metadata is not.
assert.deepStrictEqual(Problems.parseJournal("\n\r\n"), { ok: true, issues: [] });
assert.strictEqual(Problems.parseJournal("{not json").ok, false);
assert.strictEqual(Problems.parseJournal(JSON.stringify({
    MESSAGE_ID: "fc2e22bc6ee647b6b90729ab34a250b1",
    _SYSTEMD_UNIT: 'systemd-coredump@0-42-0.service'
})).ok, false);
assert.deepStrictEqual(Problems.parseJournal(JSON.stringify({ MESSAGE: "unrelated error" })), { ok: true, issues: [] });

// A cursor identifies one event even if duplicate journal rows are supplied.
var duplicate = record("s=same", {
    MESSAGE_ID: "fc2e22bc6ee647b6b90729ab34a250b1",
    COREDUMP_COMM: "one"
});
var deduped = Problems.parseJournal(duplicate + "\n" + duplicate);
assert.strictEqual(deduped.ok, true);
assert.strictEqual(deduped.issues.length, 1);

// Untrusted display metadata is control-free and bounded; omitted sensitive fields stay omitted.
var hostile = Problems.parseJournal(record("s=hostile", {
    MESSAGE_ID: "fc2e22bc6ee647b6b90729ab34a250b1",
    COREDUMP_COMM: "bad\u0000name\n" + new Array(500).join("x"),
    COREDUMP_EXE: "/tmp/evil\u001b[31m" + new Array(500).join("y"),
    COREDUMP_UNIT: "evil\tunit.service" + new Array(500).join("z"),
    COREDUMP_PID: "12\n34",
    COREDUMP_UID: "not-a-uid",
    COREDUMP_SIGNAL_NAME: "SIGSEGV\u0007",
    COREDUMP_CMDLINE: "must-not-survive",
    COREDUMP_ENVIRON: "PASSWORD=must-not-survive"
}));
assert.strictEqual(hostile.ok, true);
var hostileIssue = hostile.issues[0];
var hostileJson = JSON.stringify(hostileIssue);
assert.strictEqual(/[\x00-\x1f\x7f-\x9f]/.test(hostileIssue.detail), false);
assert.strictEqual(/[\x00-\x1f\x7f-\x9f]/.test(hostileIssue.context.metadata.comm), false);
assert.strictEqual(/[\x00-\x1f\x7f-\x9f]/.test(hostileIssue.context.metadata.executable), false);
assert.strictEqual(/[\x00-\x1f\x7f-\x9f]/.test(hostileIssue.context.metadata.unit), false);
assert.ok(hostileIssue.detail.length <= 320);
assert.ok(hostileIssue.context.metadata.comm.length <= 160);
assert.ok(hostileIssue.context.metadata.executable.length <= 240);
assert.strictEqual(hostile.issues[0].context.metadata.pid, "");
assert.strictEqual(hostile.issues[0].context.metadata.uid, "");
assert.strictEqual(hostile.issues[0].context.metadata.signal, "");
assert.strictEqual(hostileJson.indexOf("must-not-survive"), -1);

// Parser output remains bounded even if fed more than the command's maximum.
var many = [];
for (var n = 0; n < 105; n++) {
    many.push(record("s=many-" + n, {
        MESSAGE_ID: "fc2e22bc6ee647b6b90729ab34a250b1",
        COREDUMP_COMM: "app"
    }));
}
assert.strictEqual(Problems.parseJournal(many.join("\n")).issues.length, 100);
assert.strictEqual(Problems.parseJournal(many.join("\n") + "\n{broken").ok, false);

// Grouping is derived, lossless, newest-first, and stable as the window changes.
function appCrash(cursor, overrides) {
    return record(cursor, Object.assign({
        MESSAGE_ID: 'fc2e22bc6ee647b6b90729ab34a250b1',
        COREDUMP_EXE: '/usr/bin/editor', COREDUMP_UID: '1000',
        COREDUMP_COMM: 'editor', COREDUMP_PID: '10', COREDUMP_SIGNAL_NAME: 'SIGSEGV'
    }, overrides));
}
const groupInput = Problems.parseJournal([
    appCrash('s=old', {COREDUMP_SIGNAL_NAME: 'SIGABRT'}),
    appCrash('s=new', {__REALTIME_TIMESTAMP: String(Number(MICROS) + 2000000), COREDUMP_PID: '20'}),
    appCrash('s=other-app', {COREDUMP_EXE: '/opt/editor'}),
    appCrash('s=other-user', {COREDUMP_UID: '1001'}),
    appCrash('s=missing-exe', {COREDUMP_EXE: ''}),
    appCrash('s=missing-exe-2', {COREDUMP_EXE: ''}),
    appCrash('s=missing-uid', {COREDUMP_UID: ''}),
    appCrash('s=missing-uid-2', {COREDUMP_UID: ''}),
    record('s=oom-same-name', {_TRANSPORT: 'kernel', MESSAGE: 'Out of memory: Killed process 10 (editor) total-vm:1kB'})
].join('\n')).issues;
const originalInput = JSON.stringify(groupInput);
const grouped = Problems.groupIncidents(groupInput);
assert.strictEqual(grouped.length, 8);
assert.strictEqual(grouped[0].id, 'crashes:["1000","/usr/bin/editor"]');
assert.deepStrictEqual(grouped[0].events.map(event => event.cursor), ['s=new', 's=old']);
assert.deepStrictEqual(grouped[0].events.map(event => event.metadata.signal), ['SIGSEGV', 'SIGABRT']);
assert.deepStrictEqual(grouped[0].context, groupInput[0].context);
assert.deepStrictEqual(grouped[0].action, groupInput[0].action, 'open inspects the latest exact event');
assert.strictEqual(JSON.stringify(groupInput), originalInput, 'never mutate journal-owned records');
assert.strictEqual(Problems.groupIncidents(groupInput.filter(issue => issue.context.cursor !== 's=new'))[0].id, grouped[0].id);
assert.strictEqual(Problems.groupIncidents(groupInput.filter(issue => issue.context.cursor !== 's=old'))[0].id, grouped[0].id);
assert.deepStrictEqual(Problems.groupIncidents([]), []);
for (const executable of ['/tmp/editor\tname', '/tmp/' + 'a'.repeat(240), 'relative/editor']) {
    const ambiguous = Problems.parseJournal([appCrash('s=a', {COREDUMP_EXE: executable}), appCrash('s=b', {COREDUMP_EXE: executable})].join('\n')).issues;
    assert.strictEqual(Problems.groupIncidents(ambiguous).length, 2, 'lossy identity never merges applications');
}
const duplicateCrash = appCrash('s=duplicate');
assert.strictEqual(Problems.groupIncidents(Problems.parseJournal(duplicateCrash + '\n' + duplicateCrash).issues)[0].events.length, 1);
const capped = Problems.groupIncidents(Problems.parseJournal(Array.from({length: 105}, (_, i) => appCrash('s=bounded-' + i)).join('\n')).issues);
assert.strictEqual(capped.length, 1);
assert.strictEqual(capped[0].events.length, 100, 'every retained occurrence, without a second per-group truncation');

console.log("system-problems.test.js: ALL PASS");