use jiff::{SignedDuration, Timestamp};
use linux_kernel_panic_parser::{LineKind, PanicLog, parse};
const FIRST: &str = include_str!("../example-logs/1.log");
const SECOND: &str = include_str!("../example-logs/2.log");
fn line<'a>(log: &'a PanicLog, prefix: &str) -> &'a linux_kernel_panic_parser::LogLine {
let Some(line) = log
.lines()
.iter()
.find(|line| line.message().starts_with(prefix))
else {
panic!("missing fixture line with prefix {prefix:?}");
};
line
}
#[test]
fn real_logs_are_structured_and_lossless() {
for (text, cpu, pid, frames, version) in [
(
FIRST,
3,
1757,
10,
"6.12.67-6.12.2.3-amd64-dee77ff0713177fc",
),
(SECOND, 78, 1589370, 65, "6.12.67-amd64-9efba7083c7"),
] {
let log = parse(text);
assert_eq!(log.to_string(), text);
assert_eq!(parse(log.to_string()), log);
assert_eq!(
log.panic_messages().collect::<Vec<_>>(),
["Fatal exception"]
);
for line in log.lines() {
assert!(line.timestamp().is_some(), "{}", line.as_str());
assert!(line.uptime().is_some(), "{}", line.as_str());
if !line.message().is_empty() {
assert_ne!(line.kind(), &LineKind::Unknown, "{}", line.as_str());
}
}
let LineKind::Cpu {
cpu: actual_cpu,
uid,
pid: actual_pid,
details,
} = line(&log, "CPU:").kind()
else {
panic!()
};
assert_eq!((*actual_cpu, *uid, *actual_pid), (cpu, Some(0), pid));
assert!(details.contains(version));
assert_eq!(
log.lines()
.iter()
.filter(|l| matches!(l.kind(), LineKind::Frame(_)))
.count(),
frames
);
assert_eq!(
line(&log, "Rebooting").kind(),
&LineKind::Reboot {
delay: SignedDuration::from_secs(30)
}
);
}
}
#[test]
fn wall_time_is_distinct_from_uptime() -> Result<(), jiff::Error> {
let log = parse(FIRST);
assert_eq!(
log.lines()[0].timestamp(),
Some(Timestamp::from_second(1791049802)?)
);
assert_eq!(
log.lines()[0].uptime(),
Some(SignedDuration::new(694, 613_706_000))
);
assert!(log.lines()[0].message().starts_with("Oops:"));
let log = parse(SECOND);
let tail = line(&log, "Kernel Offset:");
assert_eq!(tail.timestamp(), Some(Timestamp::from_second(1791034612)?));
assert_eq!(
tail.uptime(),
Some(SignedDuration::new(3873810, 448_508_000))
);
let log = parse("[1791049802] message");
assert_eq!(log.lines()[0].timestamp(), None);
assert_eq!(
log.lines()[0].uptime(),
Some(SignedDuration::from_secs(1791049802))
);
Ok(())
}
#[test]
fn fault_symbols_register_decorations_and_modules() {
let log = parse(FIRST);
let LineKind::Symbol {
label,
segment,
frame,
} = line(&log, "RIP:").kind()
else {
panic!()
};
assert_eq!(label, "RIP");
assert_eq!(segment.as_deref(), Some("0010"));
assert_eq!(frame.symbol, "decay_va_pool_node");
assert_eq!(frame.offset, "0x10f");
assert_eq!(frame.size, "0x410");
let LineKind::Registers(registers) = line(&log, "RSP:").kind() else {
panic!()
};
assert_eq!(registers[0].value, "0018:ffffd1bc80413d98");
assert_eq!(registers[1].name, "EFLAGS");
let LineKind::Registers(registers) = line(&log, "FS:").kind() else {
panic!()
};
assert_eq!(registers[0].value, "0000000000000000(0000)");
assert_eq!(registers[1].value, "ffff8f58a5ac0000(0000)");
assert_eq!(registers[2].name, "knlGS");
let LineKind::Modules {
modules,
continuation,
..
} = line(&log, "Modules linked in:").kind()
else {
panic!()
};
assert!(!continuation);
assert!(modules.iter().any(|m| m == "zfs(O)"));
let LineKind::Modules {
modules,
continuation,
..
} = line(&log, "i2c_core raid_class").kind()
else {
panic!()
};
assert!(*continuation);
assert_eq!(modules.last().map(String::as_str), Some("ipmi_msghandler"));
let log = parse(SECOND);
let LineKind::Modules {
last_unloaded,
continuation,
..
} = line(&log, "ipmi_devintf").kind()
else {
panic!()
};
assert!(*continuation);
assert_eq!(last_unloaded.as_deref(), Some("kvm"));
let LineKind::Frame(frame) = line(&log, "pmem_do_write+").kind() else {
panic!()
};
assert_eq!(frame.suffix, "[nd_pmem]");
let LineKind::Registers(entries) = line(&log, "PGD ").kind() else {
panic!()
};
assert_eq!(entries.len(), 5);
assert_eq!(entries[0].name, "PGD");
assert_eq!(entries[4].value, "800fffc32fcf5062");
}
#[test]
fn malformed_new_formats_and_context_boundaries() {
for text in [
"RSP: 0018:",
"FS: abc(0000",
"CPU: 3 UID: x PID: 1 Comm: task",
"Rebooting in 9999999999999999999999 seconds..",
] {
let log = parse(text);
assert_eq!(log.lines()[0].kind(), &LineKind::Unknown);
assert_eq!(log.to_string(), text);
assert_eq!(parse(log.to_string()), log);
}
let log = parse("Modules linked in: zfs(O)\nspl(O) [last unloaded: kvm]\n\nplain words\n");
assert!(matches!(
log.lines()[1].kind(),
LineKind::Modules {
continuation: true,
..
}
));
assert_eq!(log.lines()[3].kind(), &LineKind::Unknown);
let log = parse("ipmi_devintf ipmi_msghandler [last unloaded: kvm]");
assert_eq!(log.lines()[0].kind(), &LineKind::Unknown);
}