The laptop model's hardware module and a memory-pressure module for any machine (hq research 027/03, 026/05, to-be 42 phase 3). The predecessor's polling auto-profile and mem-guard user scripts become each module's own Go code launched by the node runtime (ADR 0198): a profile switcher woken by the kernel's power-supply uevents, and a guard that warns on RAM, swap or PSI before systemd-oomd acts, on the desktop over the account's bus and always as an event. supergfxctl and triggerhappy are kept as found (research 027 Q1).
147 lines
4.7 KiB
Go
147 lines
4.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// What asusctl 6.4 and supergfxctl 5.2 said on the laptop on 2026-10-04.
|
|
const fanCurveQuiet = `
|
|
Fan curves for Quiet
|
|
|
|
[
|
|
(
|
|
fan: CPU,
|
|
pwm: (2, 0, 10, 20, 35, 55, 80, 100),
|
|
temp: (35, 45, 50, 55, 60, 65, 70, 80),
|
|
enabled: true,
|
|
),
|
|
(
|
|
fan: GPU,
|
|
pwm: (0, 0, 10, 20, 35, 65, 90, 115),
|
|
temp: (35, 45, 50, 55, 60, 65, 70, 80),
|
|
enabled: false,
|
|
),
|
|
]
|
|
`
|
|
|
|
const asusdRon = `(
|
|
charge_control_end_threshold: 80,
|
|
base_charge_control_end_threshold: 0,
|
|
disable_nvidia_powerd_on_battery: true,
|
|
ac_command: "supergfxctl -m Hybrid",
|
|
bat_command: "supergfxctl -m Integrated",
|
|
platform_profile_linked_epp: true,
|
|
platform_profile_on_battery: Quiet,
|
|
change_platform_profile_on_battery: true,
|
|
platform_profile_on_ac: Balanced,
|
|
change_platform_profile_on_ac: true,
|
|
ac_profile_tunings: {
|
|
Quiet: (
|
|
enabled: false,
|
|
group: {},
|
|
),
|
|
},
|
|
)`
|
|
|
|
func TestAsusctlsAnswersAreRead(t *testing.T) {
|
|
p, err := ParseProfileGet(profileGetBalanced)
|
|
if err != nil || p.Active != "Balanced" || p.OnAC != "Balanced" || p.Battery != "Quiet" {
|
|
t.Fatalf("%+v %v", p, err)
|
|
}
|
|
if _, err := ParseProfileGet("something else"); err == nil {
|
|
t.Fatal("an answer with no profile was read as one")
|
|
}
|
|
if l, err := ParseLeds("Current keyboard led brightness: High\n"); err != nil || l != "high" {
|
|
t.Fatalf("%q %v", l, err)
|
|
}
|
|
curves := ParseFanCurves(fanCurveQuiet)
|
|
if len(curves) != 2 || curves[0].Fan != "CPU" || curves[0].PWM[7] != 100 || curves[0].Temp[0] != 35 || curves[1].Enabled {
|
|
t.Fatalf("%+v", curves)
|
|
}
|
|
if got := ParseSupported("[Integrated, Hybrid, AsusMuxDgpu]\n"); strings.Join(got, ",") != "Integrated,Hybrid,AsusMuxDgpu" {
|
|
t.Fatalf("%v", got)
|
|
}
|
|
}
|
|
|
|
func TestAsusdsFileIsReadForWhatDecidesTheModulesCodeAndNothingNested(t *testing.T) {
|
|
c := ParseAsusdRon(asusdRon)
|
|
if *c.ChargeLimit != 80 || c.ProfileOnAC != "Balanced" || c.ProfileOnBattery != "Quiet" ||
|
|
c.ACCommand != "supergfxctl -m Hybrid" || c.BatteryCommand != "supergfxctl -m Integrated" ||
|
|
!*c.ChangesProfileOnAC || !*c.DisablesPowerdOnBatt {
|
|
t.Fatalf("%+v", c)
|
|
}
|
|
}
|
|
|
|
func TestAMissingVendorClientIsNamedWithWhyItIsMissing(t *testing.T) {
|
|
f := newFake(t)
|
|
f.fails["supergfxctl"] = notFound
|
|
_, err := f.machine().GPU(context.Background())
|
|
if err == nil || !strings.Contains(err.Error(), "research 027") {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
f.fails["asusctl"] = notFound
|
|
_, err = f.machine().Profile(context.Background())
|
|
if err == nil || !strings.Contains(err.Error(), "package resource installs it") {
|
|
t.Fatalf("%v", err)
|
|
}
|
|
}
|
|
|
|
func TestAGPUModeIsSetOnlyWhenTheMachineSupportsItAndAsusdsSwitchingIsSaid(t *testing.T) {
|
|
f := newFake(t)
|
|
f.answers["supergfxctl -g"] = "Hybrid\n"
|
|
f.answers["supergfxctl -s"] = "[Integrated, Hybrid, AsusMuxDgpu]\n"
|
|
f.file("/etc/asusd/asusd.ron", asusdRon)
|
|
m := f.machine()
|
|
if _, err := GPUModeTool(context.Background(), m, map[string]any{"mode": "Vfio"}); err == nil {
|
|
t.Fatal("an unsupported mode was accepted")
|
|
}
|
|
out, err := GPUModeTool(context.Background(), m, map[string]any{"mode": "integrated"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !f.called("supergfxctl -m Integrated") {
|
|
t.Fatalf("calls %v", f.calls)
|
|
}
|
|
if _, said := out.(map[string]any)["asusd_switches_it"]; !said {
|
|
t.Fatalf("asusd's own switching of the mode was not said: %+v", out)
|
|
}
|
|
}
|
|
|
|
func TestTheChargeLimitIsBoundedAndSetThroughAsusd(t *testing.T) {
|
|
f := newFake(t)
|
|
f.answers["asusctl battery info"] = "Current battery charge limit: 60%\n"
|
|
m := f.machine()
|
|
for _, bad := range []any{float64(10), float64(101), "x", 55.5} {
|
|
if _, err := ChargeLimitTool(context.Background(), m, map[string]any{"limit": bad}); err == nil {
|
|
t.Errorf("limit %v was accepted", bad)
|
|
}
|
|
}
|
|
out, err := ChargeLimitTool(context.Background(), m, map[string]any{"limit": float64(60)})
|
|
if err != nil || !f.called("asusctl battery limit 60") || out.(map[string]any)["asusd_limit_percent"] != 60 {
|
|
t.Fatalf("%+v %v %v", out, err, f.calls)
|
|
}
|
|
}
|
|
|
|
func TestAProfileSetByToolIsHeldAndAnUnknownOneRefused(t *testing.T) {
|
|
f := newFake(t)
|
|
f.onMains()
|
|
f.answers["asusctl profile get"] = profileGetBalanced
|
|
m := f.machine()
|
|
sw := NewSwitcher(m, nil)
|
|
if _, err := ProfileTool(context.Background(), m, sw, map[string]any{"profile": "Turbo"}); err == nil {
|
|
t.Fatal("an unknown profile was accepted")
|
|
}
|
|
out, err := ProfileTool(context.Background(), m, sw, map[string]any{"profile": "performance", "hold_minutes": float64(30)})
|
|
if err != nil || !f.called("asusctl profile set Performance") {
|
|
t.Fatalf("%v %v", err, f.calls)
|
|
}
|
|
if _, held := out.(map[string]any)["held_until"]; !held {
|
|
t.Fatalf("not held: %+v", out)
|
|
}
|
|
if r := sw.Report(); r.Held != "Performance" {
|
|
t.Fatalf("%+v", r)
|
|
}
|
|
}
|