The power module is on every machine and its bar blocks apply only where the hardware is. Two capabilities read from the kernel let the controller compose them there and nowhere else: battery (a Battery supply not scoped to a device) and power-meter (a system battery, a powercap package counter, or a graphics device reporting its power).
100 lines
3.6 KiB
Go
100 lines
3.6 KiB
Go
package profile
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Defends novox/hq ADR 0255: a machine says whether a battery powers it and whether anything on it
|
|
// measures the power it draws, read from the kernel, so the controller composes a battery's or a power
|
|
// draw's block only where there is one.
|
|
|
|
func write(t *testing.T, path, content string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(content+"\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// sysfs points every power source at a directory of its own, empty unless a case fills it.
|
|
func sysfs(t *testing.T) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
old := [4]string{PowerSupplySource, PowercapSource, SeatSource, NvidiaSource}
|
|
PowerSupplySource = filepath.Join(dir, "power_supply")
|
|
PowercapSource = filepath.Join(dir, "powercap")
|
|
SeatSource = filepath.Join(dir, "drm")
|
|
NvidiaSource = filepath.Join(dir, "nvidia")
|
|
t.Cleanup(func() { PowerSupplySource, PowercapSource, SeatSource, NvidiaSource = old[0], old[1], old[2], old[3] })
|
|
return dir
|
|
}
|
|
|
|
func TestALaptopHasABatteryAndAMeter(t *testing.T) {
|
|
dir := sysfs(t)
|
|
write(t, dir+"/power_supply/ACAD/type", "Mains")
|
|
write(t, dir+"/power_supply/BAT1/type", "Battery")
|
|
write(t, dir+"/power_supply/BAT1/scope", "System")
|
|
write(t, dir+"/power_supply/BAT1/present", "1")
|
|
write(t, dir+"/powercap/intel-rapl:0/name", "package-0")
|
|
write(t, dir+"/powercap/intel-rapl:0/energy_uj", "1")
|
|
|
|
b := battery{}.Detect(context.Background())
|
|
if !b.Present || b.Detail != "BAT1" {
|
|
t.Fatalf("battery: %+v", b)
|
|
}
|
|
m := powerMeter{}.Detect(context.Background())
|
|
if !m.Present || m.Detail != "battery BAT1, processor package intel-rapl:0" {
|
|
t.Fatalf("power meter: %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestADevicesBatteryIsNotTheMachines(t *testing.T) {
|
|
// The case worth a test: a desktop with a wireless mouse reports a Battery, and a battery block
|
|
// composed for it would show the mouse's charge as the machine's.
|
|
dir := sysfs(t)
|
|
write(t, dir+"/power_supply/hidpp_battery_0/type", "Battery")
|
|
write(t, dir+"/power_supply/hidpp_battery_0/scope", "Device")
|
|
write(t, dir+"/power_supply/BAT0/type", "Battery")
|
|
write(t, dir+"/power_supply/BAT0/present", "0")
|
|
|
|
if b := (battery{}).Detect(context.Background()); b.Present {
|
|
t.Fatalf("a mouse's battery, or an empty bay, was taken for the machine's: %+v", b)
|
|
}
|
|
if m := (powerMeter{}).Detect(context.Background()); m.Present {
|
|
t.Fatalf("a device's battery was taken for a meter: %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestADesktopMeasuresItsProcessorAndItsGraphics(t *testing.T) {
|
|
dir := sysfs(t)
|
|
write(t, dir+"/powercap/intel-rapl:0/name", "package-0")
|
|
write(t, dir+"/powercap/intel-rapl:0/energy_uj", "1")
|
|
// A sub-domain is part of the package, not a second meter.
|
|
write(t, dir+"/powercap/intel-rapl:0:0/name", "core")
|
|
write(t, dir+"/powercap/intel-rapl:0:0/energy_uj", "1")
|
|
write(t, dir+"/drm/card1/device/hwmon/hwmon4/power1_average", "8000000")
|
|
write(t, dir+"/nvidia/0000:01:00.0/information", "x")
|
|
|
|
if b := (battery{}).Detect(context.Background()); b.Present {
|
|
t.Fatalf("a desktop has a battery: %+v", b)
|
|
}
|
|
m := powerMeter{}.Detect(context.Background())
|
|
if !m.Present || m.Detail != "processor package intel-rapl:0, graphics card1, graphics nvidia" {
|
|
t.Fatalf("power meter: %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestAMachineWithNothingToReadHasNeither(t *testing.T) {
|
|
sysfs(t)
|
|
b, m := battery{}.Detect(context.Background()), powerMeter{}.Detect(context.Background())
|
|
if b.Present || m.Present || b.How == "" || m.How == "" || !strings.Contains(m.Detail, "nothing") {
|
|
t.Fatalf("%+v %+v", b, m)
|
|
}
|
|
}
|