feat(drive-health): manage collector with plugin lifecycle (#129)

This commit is contained in:
Gustavo Rosa
2026-07-28 21:17:07 -04:00
committed by GitHub
parent bc687510d4
commit c21c569be1
7 changed files with 191 additions and 10 deletions
+65
View File
@@ -13,6 +13,8 @@ local watchers = {}
local pendingProbeCallback = nil
local probeCalls = 0
local probeAction = nil
local failNextLaunch = false
local nextAsyncResult = nil
local collectorEnabled = mode == "raw-cache" or mode == "outdated-raw-cache"
local available = {
@@ -75,6 +77,7 @@ noctalia = {
writeFile = function(path, contents) files[path] = contents return true end,
log = function(message) table.insert(logs, message) end,
notify = function(title, body) table.insert(notifications, { title = title, body = body }) end,
notifyError = function(title, body) table.insert(notifications, { title = title, body = body, error = true }) end,
tr = translate,
formatTime = function(_pattern, _epoch) return "22:13:20" end,
setUpdateInterval = function(_milliseconds) end,
@@ -96,6 +99,19 @@ noctalia = {
runAsync = function(command, callback, _timeout)
launchedCommand = command
table.insert(launchedCommands, command)
if failNextLaunch then
failNextLaunch = false
return false
end
if callback == nil then
return true
end
if nextAsyncResult ~= nil then
local result = nextAsyncResult
nextAsyncResult = nil
callback(result)
return true
end
if command:match("lsblk=ok") then
probeCalls = probeCalls + 1
if probeAction == "pending" then
@@ -147,6 +163,55 @@ source = source:gsub("local function publishError", "function publishError")
source = source:gsub("([%a_][%w_]*) %+%= ([^\n]+)", "%1 = %1 + %2")
assert(load(source, "@collector.luau"))()
if mode == "lifecycle" then
collectorEnabled = true
files["/usr/local/libexec/noctalia-drive-health/collect_raw.sh"] = "installed"
files["/usr/local/libexec/noctalia-drive-health/manage-collector.sh"] = "installed"
files["/usr/local/libexec/noctalia-drive-health/uninstall-collector.sh"] = "installed"
launchedCommands = {}
onEnable()
assert(launchedCommands[1]
== "pkexec '/usr/local/libexec/noctalia-drive-health/manage-collector.sh' start",
"plugin enable did not start the installed collector")
onExit(0, "disable")
assert(launchedCommands[2]
== "pkexec '/usr/local/libexec/noctalia-drive-health/manage-collector.sh' pause",
"plugin disable did not pause the installed collector")
onExit(0, "uninstall")
assert(launchedCommands[3]
== "pkexec '/usr/local/libexec/noctalia-drive-health/uninstall-collector.sh'",
"plugin uninstall did not remove the installed collector")
files["/usr/local/libexec/noctalia-drive-health/uninstall-collector.sh"] = nil
onExit(0, "uninstall")
assert(launchedCommands[4]:match("^pkexec /bin/sh %-c ")
and launchedCommands[4]:match("/usr/local/libexec/noctalia%-drive%-health")
and not launchedCommands[4]:match("/mock/plugin"),
"legacy uninstall fallback depends on files removed with the plugin")
local notificationCount = #notifications
failNextLaunch = true
onExit(0, "disable")
assert(#notifications == notificationCount + 1 and notifications[#notifications].error == true,
"detached lifecycle launch failure was not reported")
nextAsyncResult = { exitCode = 126, stdout = "", stderr = "", timedOut = false }
onEnable()
assert(#notifications == notificationCount + 2
and logs[#logs]:match("privileged_action%.cancelled"),
"onEnable authorization cancellation was not reported")
onExit(0, "reload")
onExit(15, "shutdown")
assert(#launchedCommands == 6, "reload or shutdown changed the collector service")
print("collector lifecycle test passed")
return
end
local snapshot = assert(state.collector_snapshot, "collector did not publish an initialization snapshot")
local dependencies = assert(snapshot.dependencies, "snapshot has no dependency state")
+12 -5
View File
@@ -167,6 +167,13 @@ local function findNodeWithProp(value, kind, property, expected)
return nil
end
local function clickNodeWithProp(value, kind, property, expected)
local target = assert(findNodeWithProp(value, kind, property, expected),
"could not find " .. kind .. " with " .. property .. "=" .. tostring(expected))
assert(type(target.props.onClick) == "function", "matching node has no closure callback")
target.props.onClick()
end
onOpen({})
assert(rendered ~= nil and not containsText(rendered, "collector.title"),
"healthy collector consumed panel space")
@@ -223,7 +230,7 @@ state.snapshot.system_collector.enabled = true
state.snapshot.system_collector.status = "healthy"
state.snapshot.system_collector.helper_available = true
watchers.snapshot(state.snapshot)
onDrive1Clicked()
clickNodeWithProp(rendered, "button", "tooltip", "panel.expand")
assert(containsText(rendered, "self_test.title"), "expanded self-test card did not render")
assert(containsText(rendered, "metrics.mounted_at / · /home/example"),
"expanded drive card omitted its mounted folders")
@@ -241,10 +248,10 @@ assert(testProgress.props.progress == 0.37 and testProgress.props.value == nil,
"running self-test used an invalid progress property")
assert(containsText(rendered, "history.title"), "expanded history graph did not render")
assert(containsText(rendered, "preferences.edit"), "drive preference action did not render")
onDrive1Clicked()
clickNodeWithProp(rendered, "button", "tooltip", "panel.collapse")
assert(not containsText(rendered, "self_test.title"), "drive details did not collapse")
assert(not containsText(rendered, "history.title"), "drive history remained visible after collapse")
onDrive1Clicked()
clickNodeWithProp(rendered, "button", "tooltip", "panel.expand")
state.snapshot.disks[1].self_test_state = "passed"
state.snapshot.disks[1].self_test_status = "Previous test passed"
state.snapshot.disks[1].self_test_completion_percent = nil
@@ -364,7 +371,7 @@ watchers.snapshot(state.snapshot)
assert(containsText(rendered, "alerts.dismiss_all"), "dismiss-all alert action did not render")
assert(findNodeWithProp(rendered, "button", "tooltip", "alerts.dismiss") ~= nil,
"per-alert dismiss action did not render")
onDismissAlert1Clicked()
clickNodeWithProp(rendered, "button", "tooltip", "alerts.dismiss")
assert(state.dismiss_alert_request.id == "SERIAL1:temperature",
"per-alert dismiss action targeted the wrong issue")
onDismissAllAlertsClicked()
@@ -377,7 +384,7 @@ assert(not containsText(rendered, "alerts.active_title")
and findNodeWithProp(rendered, "button", "tooltip", "alerts.dismiss") == nil,
"empty alert state kept an alert card or dismiss controls")
onDrive2Clicked()
clickNodeWithProp(rendered, "button", "tooltip", "panel.expand")
assert(containsText(rendered, "metrics.start_stop_count")
and containsText(rendered, "metrics.load_cycle_count")
and containsText(rendered, "metrics.interface_crc_errors"),