An APU's integrated graphics and Intel's second (MMIO) view of a package made a machine report a power meter the sampler would not read, so its bar could only say not measured.
134 lines
4.9 KiB
Go
134 lines
4.9 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-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+"/drm/card1/device/mem_info_vram_vendor", "samsung")
|
|
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-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)
|
|
}
|
|
}
|
|
|
|
func TestAPackageShownTwiceIsOneMeterAndIntegratedGraphicsAreNone(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")
|
|
write(t, dir+"/powercap/intel-rapl-mmio:0/name", "package-0")
|
|
write(t, dir+"/powercap/intel-rapl-mmio:0/energy_uj", "1")
|
|
// An APU's graphics: power reported, no memory of its own.
|
|
write(t, dir+"/drm/card1/device/hwmon/hwmon5/power1_average", "19000000")
|
|
m := powerMeter{}.Detect(context.Background())
|
|
if !m.Present || m.Detail != "processor package-0" {
|
|
t.Fatalf("power meter: %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestOnlyIntegratedGraphicsMeasureNothing(t *testing.T) {
|
|
dir := sysfs(t)
|
|
write(t, dir+"/drm/card0/device/hwmon/hwmon1/power1_input", "5000000")
|
|
if m := (powerMeter{}).Detect(context.Background()); m.Present {
|
|
t.Fatalf("integrated graphics were taken for a meter: %+v", m)
|
|
}
|
|
}
|
|
|
|
func TestABatteryOfUnknownScopeIsTheMachines(t *testing.T) {
|
|
// Many laptops' firmware says nothing of scope; the kernel reports Unknown. Only Device is a
|
|
// peripheral's.
|
|
dir := sysfs(t)
|
|
write(t, dir+"/power_supply/BAT0/type", "Battery")
|
|
write(t, dir+"/power_supply/BAT0/scope", "Unknown")
|
|
if b := (battery{}).Detect(context.Background()); !b.Present || b.Detail != "BAT0" {
|
|
t.Fatalf("battery: %+v", b)
|
|
}
|
|
}
|