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).
104 lines
2.5 KiB
Go
104 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// fake is a machine for a test: a tree standing in for /, and a runner answering from a table and
|
|
// recording every command it was asked to run.
|
|
type fake struct {
|
|
t *testing.T
|
|
root string
|
|
mu sync.Mutex
|
|
answers map[string]string
|
|
fails map[string]error
|
|
calls []string
|
|
}
|
|
|
|
func newFake(t *testing.T) *fake {
|
|
t.Helper()
|
|
return &fake{t: t, root: t.TempDir(), answers: map[string]string{}, fails: map[string]error{}}
|
|
}
|
|
|
|
func (f *fake) machine() *Machine { return &Machine{Root: f.root, Run: f.run} }
|
|
|
|
func (f *fake) run(_ context.Context, name string, args ...string) (string, error) {
|
|
line := strings.TrimSpace(name + " " + strings.Join(args, " "))
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.calls = append(f.calls, line)
|
|
if err, ok := f.fails[line]; ok {
|
|
return "", err
|
|
}
|
|
if out, ok := f.answers[line]; ok {
|
|
return out, nil
|
|
}
|
|
if err, ok := f.fails[name]; ok {
|
|
return "", err
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
func (f *fake) called(line string) bool {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
for _, c := range f.calls {
|
|
if c == line {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (f *fake) callsLike(prefix string) []string {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
var out []string
|
|
for _, c := range f.calls {
|
|
if strings.HasPrefix(c, prefix) {
|
|
out = append(out, c)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// file writes a file under the fake root.
|
|
func (f *fake) file(path, content string) {
|
|
f.t.Helper()
|
|
full := filepath.Join(f.root, path)
|
|
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
|
|
f.t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(full, []byte(content), 0o644); err != nil {
|
|
f.t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// supply writes one power supply's attributes.
|
|
func (f *fake) supply(name string, attrs map[string]string) {
|
|
for k, v := range attrs {
|
|
f.file("/sys/class/power_supply/"+name+"/"+k, v+"\n")
|
|
}
|
|
}
|
|
|
|
// onMains and onBattery are this model's two states as measured on 2026-10-04.
|
|
func (f *fake) onMains() {
|
|
f.supply("ACAD", map[string]string{"type": "Mains", "online": "1"})
|
|
f.supply("BAT1", map[string]string{"type": "Battery", "status": "Not charging", "capacity": "80"})
|
|
}
|
|
|
|
func (f *fake) onBattery() {
|
|
f.supply("ACAD", map[string]string{"type": "Mains", "online": "0"})
|
|
f.supply("BAT1", map[string]string{"type": "Battery", "status": "Discharging", "capacity": "79"})
|
|
}
|
|
|
|
var notFound = &exec.Error{Name: "x", Err: exec.ErrNotFound}
|
|
|
|
const profileGetBalanced = "Active profile: Balanced\n\nAC profile Balanced\nBattery profile Quiet\n"
|