A test that slept a fixed time and then asserted what a goroutine, process or watcher did failed under the checker's load. Each now waits for its positive evidence (a started baseline, a call counted, a state reached) within 30 s, and the concurrency-limit tests meet at a barrier instead of overlapping by sleep. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
191 lines
5.9 KiB
Go
191 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestTheLoadMustBeSustainedToBoostAndToRelaxAndBetweenTheLinesNothingMoves(t *testing.T) {
|
|
var p Policy
|
|
at := time.Now()
|
|
mains := Source{OnAC: true, Reason: "ACAD (Mains) is online"}
|
|
for i := 0; i < SustainSamples-1; i++ {
|
|
p.Observe(90, at)
|
|
}
|
|
if p.Decide(mains).Profile != ProfileOnAC {
|
|
t.Fatal("boosted before the load was sustained")
|
|
}
|
|
p.Observe(35, at) // between the lines breaks the run
|
|
p.Observe(90, at)
|
|
if p.Boosted {
|
|
t.Fatal("a broken run still counted")
|
|
}
|
|
for i := 0; i < SustainSamples; i++ {
|
|
p.Observe(CPUHighPercent, at)
|
|
}
|
|
if d := p.Decide(mains); d.Profile != ProfileUnderLoad {
|
|
t.Fatalf("%+v", d)
|
|
}
|
|
p.Observe(35, at)
|
|
if !p.Boosted {
|
|
t.Fatal("load between the lines relaxed the boost")
|
|
}
|
|
for i := 0; i < RelaxSamples; i++ {
|
|
p.Observe(5, at)
|
|
}
|
|
if p.Decide(mains).Profile != ProfileOnAC {
|
|
t.Fatal("did not relax after a sustained low")
|
|
}
|
|
p.Boosted = true
|
|
if d := p.Decide(Source{OnAC: false, Reason: "BAT1 is discharging"}); d.Profile != ProfileOnBattery {
|
|
t.Fatalf("battery: %+v", d)
|
|
}
|
|
}
|
|
|
|
func TestIOWaitIsIdle(t *testing.T) {
|
|
a, err := ParseProcStat("cpu 100 0 100 700 100 0 0 0 0 0\ncpu0 1 2 3\n")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, _ := ParseProcStat("cpu 150 0 150 700 200 0 0 0 0 0\n")
|
|
busy, ok := Busy(a, b)
|
|
if !ok || busy != 50 {
|
|
t.Fatalf("%v %v", busy, ok)
|
|
}
|
|
if _, ok := Busy(b, b); ok {
|
|
t.Fatal("no time passed and a load was answered")
|
|
}
|
|
if _, err := ParseProcStat("intr 1 2"); err == nil {
|
|
t.Fatal("a file without the cpu line was read")
|
|
}
|
|
}
|
|
|
|
func switcherOn(t *testing.T) (*fake, *Switcher, *[]map[string]any) {
|
|
f := newFake(t)
|
|
f.onMains()
|
|
f.file("/proc/stat", "cpu 0 0 0 0 0 0 0 0\n")
|
|
var emitted []map[string]any
|
|
sw := NewSwitcher(f.machine(), func(_ string, body any) error {
|
|
emitted = append(emitted, body.(map[string]any))
|
|
return nil
|
|
})
|
|
return f, sw, &emitted
|
|
}
|
|
|
|
func TestStartingIsNotAReasonToSwitch(t *testing.T) {
|
|
f, sw, emitted := switcherOn(t)
|
|
sw.Evaluate(context.Background(), false)
|
|
if calls := f.callsLike("asusctl profile set"); len(calls) != 0 || len(*emitted) != 0 {
|
|
t.Fatalf("the first decision acted: %v %v", calls, *emitted)
|
|
}
|
|
}
|
|
|
|
func TestAChangeOfPowerSourceSwitchesOnceAndPublishesIt(t *testing.T) {
|
|
f, sw, emitted := switcherOn(t)
|
|
ctx := context.Background()
|
|
sw.Evaluate(ctx, false)
|
|
f.onBattery()
|
|
sw.Evaluate(ctx, false)
|
|
sw.Evaluate(ctx, true) // nothing changed: nothing done, and on battery nothing sampled
|
|
if calls := f.callsLike("asusctl profile set"); len(calls) != 1 || calls[0] != "asusctl profile set Quiet" {
|
|
t.Fatalf("%v", calls)
|
|
}
|
|
if len(*emitted) != 1 || (*emitted)[0]["profile"] != "Quiet" || (*emitted)[0]["from"] != "Balanced" {
|
|
t.Fatalf("%v", *emitted)
|
|
}
|
|
f.onMains()
|
|
sw.Evaluate(ctx, false)
|
|
if calls := f.callsLike("asusctl profile set"); len(calls) != 2 || calls[1] != "asusctl profile set Balanced" {
|
|
t.Fatalf("%v", calls)
|
|
}
|
|
}
|
|
|
|
func TestSustainedLoadOnMainsBoostsFromSamples(t *testing.T) {
|
|
f, sw, _ := switcherOn(t)
|
|
ctx := context.Background()
|
|
sw.Evaluate(ctx, false)
|
|
var user int
|
|
for i := 1; i <= SustainSamples; i++ {
|
|
user += 90
|
|
f.file("/proc/stat", "cpu "+itoa(user)+" 0 0 "+itoa(i*10)+" 0 0 0 0\n")
|
|
sw.Evaluate(ctx, true)
|
|
}
|
|
if !f.called("asusctl profile set Performance") {
|
|
t.Fatalf("%v", f.calls)
|
|
}
|
|
}
|
|
|
|
func TestAHoldKeepsTheProfileUntilItEndsAndAFailureIsTriedAgain(t *testing.T) {
|
|
f, sw, _ := switcherOn(t)
|
|
ctx := context.Background()
|
|
now := time.Now()
|
|
sw.now = func() time.Time { return now }
|
|
sw.Evaluate(ctx, false)
|
|
f.onBattery()
|
|
sw.Evaluate(ctx, false) // source change ends any hold; switches to Quiet
|
|
sw.Hold("Performance", time.Hour)
|
|
f.fails["asusctl profile set Balanced"] = errors.New("asusd is restarting")
|
|
f.onMains()
|
|
sw.Evaluate(ctx, false) // a change of source: the hold ends, the switch is attempted and fails
|
|
if r := sw.Report(); r.LastError == "" || r.Held != "" {
|
|
t.Fatalf("%+v", r)
|
|
}
|
|
delete(f.fails, "asusctl profile set Balanced")
|
|
sw.Evaluate(ctx, false)
|
|
if r := sw.Report(); r.LastApplied != "Balanced" || r.LastError != "" {
|
|
t.Fatalf("not tried again: %+v", r)
|
|
}
|
|
// A hold on the same source keeps a new decision from acting until it ends.
|
|
sw.Hold("Quiet", time.Hour)
|
|
sw.policy.Boosted = true
|
|
sw.Evaluate(ctx, false)
|
|
if f.called("asusctl profile set Performance") {
|
|
t.Fatal("switched during a hold")
|
|
}
|
|
now = now.Add(2 * time.Hour)
|
|
sw.Evaluate(ctx, false)
|
|
if !f.called("asusctl profile set Performance") {
|
|
t.Fatal("did not act once the hold ended")
|
|
}
|
|
}
|
|
|
|
func TestAsusdsSettingsAreSetOnlyWhereTheyDiffer(t *testing.T) {
|
|
f := newFake(t)
|
|
f.answers["asusctl battery info"] = "Current battery charge limit: 100%\n"
|
|
f.answers["asusctl profile get"] = "Active profile: Balanced\nAC profile Performance\nBattery profile Quiet\n"
|
|
sw := NewSwitcher(f.machine(), nil)
|
|
sw.AssertVendorSettings(context.Background())
|
|
if !f.called("asusctl battery limit 80") || !f.called("asusctl profile set -a Balanced") || f.called("asusctl profile set -b Quiet") {
|
|
t.Fatalf("%v", f.calls)
|
|
}
|
|
}
|
|
|
|
func TestTheSwitcherDoesNotActOnAnotherModel(t *testing.T) {
|
|
f := newFake(t)
|
|
f.file("/sys/class/dmi/id/product_family", "ROG Strix\n")
|
|
sw := NewSwitcher(f.machine(), nil)
|
|
done := make(chan struct{})
|
|
go func() { sw.Run(context.Background()); close(done) }()
|
|
select {
|
|
case <-done:
|
|
case <-time.After(30 * time.Second):
|
|
t.Fatal("the switcher ran on another model")
|
|
}
|
|
if r := sw.Report(); r.Running || r.Disabled == "" || len(f.calls) != 0 {
|
|
t.Fatalf("%+v %v", r, f.calls)
|
|
}
|
|
}
|
|
|
|
func TestOnlyPowerSupplyUeventsWake(t *testing.T) {
|
|
yes := []byte("change@/devices/LNXSYSTM:00/ACPI0003:00/power_supply/ACAD\x00ACTION=change\x00SUBSYSTEM=power_supply\x00POWER_SUPPLY_ONLINE=0\x00")
|
|
no := []byte("change@/devices/virtual/net/wlan0\x00ACTION=change\x00SUBSYSTEM=net\x00")
|
|
if !powerSupplyEvent(yes) || powerSupplyEvent(no) {
|
|
t.Fatal("the uevent filter")
|
|
}
|
|
}
|
|
|
|
func itoa(n int) string { return strconv.Itoa(n) }
|