Refresh puter UI, power telemetry and demand-aware monitoring
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import XCTest
|
||||
@testable import puter
|
||||
|
||||
final class BatteryRegistryTests: XCTestCase {
|
||||
func testChargingSeparatesLoadInputAndCapability() {
|
||||
let result = BatteryRegistryReader.parse([
|
||||
"BatteryInstalled": true, "ExternalConnected": true, "IsCharging": true,
|
||||
"CurrentCapacity": 77, "Voltage": 12_500, "Amperage": 3_600,
|
||||
"PowerTelemetryData": ["SystemLoad": 15_000, "SystemPowerIn": 61_000],
|
||||
"AdapterDetails": ["Name": "140W USB-C Power Adapter", "Watts": 94,
|
||||
"AdapterVoltage": 20_000, "Current": 4_700,
|
||||
"UsbHvcMenu": [["MaxVoltage": 20_000, "MaxCurrent": 4_700]]]
|
||||
])
|
||||
XCTAssertTrue(result.isPresent)
|
||||
XCTAssertEqual(result.systemPowerWatts, 15)
|
||||
XCTAssertEqual(result.adapterInputWatts, 61)
|
||||
XCTAssertEqual(result.measuredBatteryWatts, 45)
|
||||
XCTAssertEqual(result.batteryFlowTitle, "Battery charging")
|
||||
XCTAssertEqual(result.adapter?.negotiatedWatts, 94)
|
||||
XCTAssertEqual(result.adapter?.profiles.count, 1)
|
||||
}
|
||||
|
||||
func testUnsignedNegativeCurrentAndDisconnectedStaleAdapter() {
|
||||
let result = BatteryRegistryReader.parse([
|
||||
"BatteryInstalled": true, "ExternalConnected": false, "Voltage": 12_000,
|
||||
"Amperage": NSNumber(value: UInt64(bitPattern: -2_000)),
|
||||
"PowerTelemetryData": ["SystemPowerIn": 50_000],
|
||||
"AdapterDetails": ["Watts": 94]
|
||||
])
|
||||
XCTAssertEqual(result.currentAmps, -2)
|
||||
XCTAssertEqual(result.measuredBatteryWatts, 24)
|
||||
XCTAssertEqual(result.batteryFlowTitle, "Battery discharge")
|
||||
XCTAssertNil(result.adapter)
|
||||
XCTAssertFalse(result.hasAdapterInput)
|
||||
XCTAssertEqual(result.adapterInputWatts, 0)
|
||||
}
|
||||
|
||||
func testZeroIsDifferentFromMissing() {
|
||||
let idle = BatteryRegistryReader.parse(["BatteryInstalled": true, "Voltage": 12_000, "Amperage": 0])
|
||||
XCTAssertEqual(idle.measuredBatteryWatts, 0)
|
||||
XCTAssertEqual(idle.batteryFlowTitle, "Battery idle")
|
||||
let absent = BatteryRegistryReader.parse([:])
|
||||
XCTAssertNil(absent.measuredBatteryWatts)
|
||||
XCTAssertFalse(absent.hasSystemPower)
|
||||
XCTAssertEqual(absent.powerSourceDescription, "External power")
|
||||
}
|
||||
|
||||
func testNestedCapacityOnNewerBatteryRegistry() {
|
||||
let result = BatteryRegistryReader.parse(["BatteryInstalled": true,
|
||||
"BatteryData": ["NominalChargeCapacity": 5630, "DesignCapacity": 6249, "RemainingCapacity": 4600]])
|
||||
XCTAssertEqual(result.fullChargeCapacityMAh, 5630)
|
||||
XCTAssertEqual(result.designCapacityMAh, 6249)
|
||||
XCTAssertEqual(result.currentCapacityMAh, 4600)
|
||||
XCTAssertEqual(result.healthPercent, 90.09, accuracy: 0.01)
|
||||
XCTAssertFalse(result.hasBatteryTemperature)
|
||||
}
|
||||
|
||||
func testInvalidPowerAndCurrentAreNotMeasurements() {
|
||||
let result = BatteryRegistryReader.parse([
|
||||
"BatteryInstalled": true, "Voltage": 12_000, "Amperage": 999_999,
|
||||
"PowerTelemetryData": ["SystemLoad": Double.nan, "SystemPowerIn": -10]
|
||||
])
|
||||
XCTAssertNil(result.measuredBatteryWatts)
|
||||
XCTAssertFalse(result.hasSystemPower)
|
||||
XCTAssertFalse(result.hasAdapterInput)
|
||||
}
|
||||
|
||||
func testOpenDropdownRequestsPowerButNotFullInventory() {
|
||||
let demand = SamplingDemand(active: false, section: .processes, speed: .normal,
|
||||
recording: false, alerts: false, menuBar: true, powerMenu: false,
|
||||
menuPresented: true)
|
||||
XCTAssertTrue(demand.hardware)
|
||||
XCTAssertTrue(demand.telemetry)
|
||||
XCTAssertFalse(demand.processInventory)
|
||||
XCTAssertEqual(demand.interval, .seconds(5))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import XCTest
|
||||
@testable import puter
|
||||
|
||||
final class ProcessGroupingTests: XCTestCase {
|
||||
@MainActor
|
||||
func testPrecomputedGroupsPreserveMemberSearchAndTotals() {
|
||||
let records = [record(41, path: "/Applications/Example.app/Contents/MacOS/Example"),
|
||||
record(42, path: "/Applications/Example.app/Contents/Helpers/Worker"),
|
||||
record(43, path: "/usr/bin/tool")]
|
||||
let groups = ProcessGrouping.groups(from: records, searchText: "")
|
||||
XCTAssertEqual(groups.count, 2)
|
||||
let app = ProcessGrouping.filtered(groups, searchText: "Example")
|
||||
XCTAssertEqual(app.count, 1)
|
||||
XCTAssertEqual(app.first?.processes.count, 2)
|
||||
XCTAssertEqual(app.first?.cpu, 4)
|
||||
let member = ProcessGrouping.filtered(groups, searchText: "42")
|
||||
XCTAssertEqual(member.first?.processes.map(\.pid), [42])
|
||||
XCTAssertEqual(member.first?.id, app.first?.id)
|
||||
XCTAssertTrue(ProcessGrouping.filtered(groups, searchText: "unmatched").isEmpty)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testPathCachePreservesSingletonsAndFreshTelemetry() {
|
||||
let first = ProcessGrouping.groups(from: [record(41, path: "/usr/bin/tool")], searchText: "")
|
||||
let second = ProcessGrouping.groups(from: [record(42, path: "/usr/bin/tool")], searchText: "")
|
||||
XCTAssertNotEqual(first.first?.id, second.first?.id)
|
||||
let mixedCase = ProcessGrouping.groups(from: [record(43, path: "/Applications/Test.APP/Contents/MacOS/Test")], searchText: "")
|
||||
XCTAssertEqual(mixedCase.first?.appPath, "/Applications/Test.app")
|
||||
}
|
||||
|
||||
private func record(_ pid: Int32, path: String) -> ProcessRecord {
|
||||
ProcessRecord(pid: pid, parentPID: 1, name: "worker", executablePath: path,
|
||||
user: "tester", cpu: 2, memoryPercent: 1, residentBytes: 1024,
|
||||
state: "S", elapsed: "00:01", cpuTime: 0, threadCount: 1,
|
||||
openFileCount: 0, architecture: "ARM64", priority: 31, nice: 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import XCTest
|
||||
@testable import puter
|
||||
|
||||
final class SamplingDemandTests: XCTestCase {
|
||||
func testNoConsumerMeansNoTimer() {
|
||||
let demand = make(active: false)
|
||||
XCTAssertNil(demand.interval)
|
||||
XCTAssertFalse(demand.processInventory)
|
||||
XCTAssertFalse(demand.hardware)
|
||||
XCTAssertFalse(demand.services)
|
||||
}
|
||||
|
||||
func testBackgroundMenuUsesLightweightCountersOnly() {
|
||||
let demand = make(active: false, menu: true)
|
||||
XCTAssertEqual(demand.interval, .seconds(10))
|
||||
XCTAssertTrue(demand.telemetry)
|
||||
XCTAssertFalse(demand.processInventory)
|
||||
XCTAssertFalse(demand.services)
|
||||
XCTAssertFalse(demand.hardware)
|
||||
}
|
||||
|
||||
func testSettingsWithoutConsumersIsIdle() {
|
||||
XCTAssertNil(make(section: .settings).interval)
|
||||
}
|
||||
|
||||
func testServicesOnlyScansVisibleServices() {
|
||||
let demand = make(section: .services)
|
||||
XCTAssertTrue(demand.services)
|
||||
XCTAssertFalse(demand.telemetry)
|
||||
XCTAssertFalse(demand.processInventory)
|
||||
XCTAssertEqual(demand.interval, .seconds(3))
|
||||
}
|
||||
|
||||
func testRecordingKeepsBackgroundTelemetry() {
|
||||
let demand = make(active: false, recording: true)
|
||||
XCTAssertTrue(demand.processInventory)
|
||||
XCTAssertTrue(demand.telemetry)
|
||||
XCTAssertTrue(demand.hardware)
|
||||
XCTAssertEqual(demand.interval, UpdateSpeed.normal.interval)
|
||||
}
|
||||
|
||||
func testPauseStopsEvenRecordingTimer() {
|
||||
XCTAssertNil(make(speed: .paused, recording: true, menu: true).interval)
|
||||
}
|
||||
|
||||
func testPowerMenuOnlyNeedsHardware() {
|
||||
let demand = make(active: false, menu: true, power: true)
|
||||
XCTAssertTrue(demand.hardware)
|
||||
XCTAssertFalse(demand.telemetry)
|
||||
XCTAssertFalse(demand.processInventory)
|
||||
}
|
||||
|
||||
func testAlertsRetainSystemSamplingWithoutProcessScan() {
|
||||
let demand = make(active: false, alerts: true)
|
||||
XCTAssertTrue(demand.telemetry)
|
||||
XCTAssertFalse(demand.processInventory)
|
||||
XCTAssertNotNil(demand.interval)
|
||||
}
|
||||
|
||||
private func make(active: Bool = true, section: TaskSection = .processes,
|
||||
speed: UpdateSpeed = .normal, recording: Bool = false,
|
||||
alerts: Bool = false, menu: Bool = false, power: Bool = false) -> SamplingDemand {
|
||||
SamplingDemand(active: active, section: section, speed: speed,
|
||||
recording: recording, alerts: alerts, menuBar: menu, powerMenu: power)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user