use crate::core::doctor::{CheckStatus, DoctorCheck};
pub const CHECK_NAME: &str = "daemon_version";
pub fn parse_version_triple(raw: &str) -> Option<(u64, u64, u64)> {
let mut parts = raw.split('.');
let major = parts.next()?.parse::<u64>().ok()?;
let minor = parts.next()?.parse::<u64>().ok()?;
let patch_raw = parts.next()?;
let patch_digits: String = patch_raw
.chars()
.take_while(|c| c.is_ascii_digit())
.collect();
if patch_digits.is_empty() {
return None;
}
let patch = patch_digits.parse::<u64>().ok()?;
Some((major, minor, patch))
}
pub fn check_daemon_version_staleness(
installed: &str,
daemon_reported: &str,
restart_hint: &str,
) -> DoctorCheck {
if daemon_reported.is_empty() {
return DoctorCheck::new(
CHECK_NAME,
CheckStatus::Warn,
format!(
"daemon did not report a version on /health — it likely predates this build \
(installed binary is v{installed}); restart the daemon (`{restart_hint}`)"
),
);
}
if daemon_reported == installed {
return DoctorCheck::new(
CHECK_NAME,
CheckStatus::Ok,
format!("running daemon (v{daemon_reported}) matches the installed binary"),
);
}
match (
parse_version_triple(installed),
parse_version_triple(daemon_reported),
) {
(Some(installed_triple), Some(daemon_triple)) if daemon_triple < installed_triple => {
DoctorCheck::new(
CHECK_NAME,
CheckStatus::Warn,
format!(
"running daemon is older than installed binary — restart the daemon \
(daemon: v{daemon_reported}, installed: v{installed}); run \
`{restart_hint}`"
),
)
}
(Some(installed_triple), Some(daemon_triple)) if daemon_triple > installed_triple => {
DoctorCheck::new(
CHECK_NAME,
CheckStatus::Warn,
format!(
"running daemon (v{daemon_reported}) is NEWER than the installed binary \
(v{installed}) — the installed binary may have been downgraded"
),
)
}
(Some(_), Some(_)) => DoctorCheck::new(
CHECK_NAME,
CheckStatus::Ok,
format!("running daemon (v{daemon_reported}) matches the installed binary"),
),
_ => DoctorCheck::new(
CHECK_NAME,
CheckStatus::Warn,
format!(
"could not compare daemon version `{daemon_reported}` against installed \
binary version `{installed}` — restart the daemon if unsure \
(`{restart_hint}`)"
),
),
}
}
#[cfg(test)]
mod tests {
use super::*;
const HINT: &str = "launchctl kickstart -k gui/$(id -u)/com.trusty.mpm";
#[test]
fn parse_version_triple_plain() {
assert_eq!(parse_version_triple("0.42.1"), Some((0, 42, 1)));
assert_eq!(parse_version_triple("12.0.7"), Some((12, 0, 7)));
}
#[test]
fn parse_version_triple_prerelease_suffix() {
assert_eq!(parse_version_triple("0.42.0-beta"), Some((0, 42, 0)));
}
#[test]
fn parse_version_triple_rejects_malformed() {
assert_eq!(parse_version_triple(""), None);
assert_eq!(parse_version_triple("0.42"), None);
assert_eq!(parse_version_triple("not-a-version"), None);
}
#[test]
fn staleness_ok_when_versions_match() {
let check = check_daemon_version_staleness("0.42.0", "0.42.0", HINT);
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
assert_eq!(check.name, CHECK_NAME);
}
#[test]
fn staleness_warns_when_daemon_older() {
let check = check_daemon_version_staleness("0.42.0", "0.41.9", HINT);
assert_eq!(check.status, CheckStatus::Warn);
assert!(check.message.contains("older than installed binary"));
assert!(check.message.contains(HINT), "message: {}", check.message);
}
#[test]
fn staleness_uses_the_caller_restart_hint() {
for (installed, reported) in [("0.42.0", "0.41.9"), ("0.42.0", ""), ("0.42.0", "garbage")] {
let msg = check_daemon_version_staleness(installed, reported, HINT).message;
assert!(msg.contains(HINT), "message: {msg}");
assert!(
!msg.contains("tm restart"),
"must not leak the old hardcoded verb: {msg}"
);
}
}
#[test]
fn staleness_warns_when_daemon_newer() {
let check = check_daemon_version_staleness("0.41.9", "0.42.0", HINT);
assert_eq!(check.status, CheckStatus::Warn);
assert!(check.message.contains("NEWER"));
}
#[test]
fn staleness_warns_when_daemon_version_empty() {
let check = check_daemon_version_staleness("0.42.0", "", HINT);
assert_eq!(check.status, CheckStatus::Warn);
assert!(check.message.contains("did not report a version"));
}
#[test]
fn staleness_warns_when_unparseable() {
let check = check_daemon_version_staleness("0.42.0", "garbage", HINT);
assert_eq!(check.status, CheckStatus::Warn);
assert!(check.message.contains("could not compare"));
}
#[test]
fn staleness_ok_when_triples_match_but_raw_strings_differ() {
let check = check_daemon_version_staleness("0.42.0", "0.42.0-beta", HINT);
assert_eq!(check.status, CheckStatus::Ok, "message: {}", check.message);
assert_eq!(check.name, CHECK_NAME);
}
}