summaryrefslogtreecommitdiff
path: root/internal/display/display_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-16 22:53:17 +0200
committerPaul Buetow <paul@buetow.org>2026-02-16 22:53:17 +0200
commit6798b669464d828c241554647b4fff68a62ca91d (patch)
tree706fc2da37b27b89649afe650ec699170191ecb1 /internal/display/display_test.go
parent971928faff0c100ef591c2d0e92e94b9f46ae71a (diff)
Add global I/O avg line (hotkey i) and m/n hotkey aliases
Add a pink 1px line drawn from the top showing mean iowait+IRQ+softIRQ across all hosts, toggled with hotkey i and persistable to ~/.loadbarsrc. Also add m as alias for 2 (memory toggle) and n as alias for 3 (network toggle) for easier single-hand operation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/display/display_test.go')
-rw-r--r--internal/display/display_test.go196
1 files changed, 196 insertions, 0 deletions
diff --git a/internal/display/display_test.go b/internal/display/display_test.go
index f0377b5..734e144 100644
--- a/internal/display/display_test.go
+++ b/internal/display/display_test.go
@@ -710,6 +710,23 @@ func TestHandleKey_ToggleMem(t *testing.T) {
assertPixelColor(t, surface, 110, 95, constants.DarkGrey, 5, "mem bar RAM after toggle")
}
+func TestHandleKey_ToggleMemAlias(t *testing.T) {
+ cfg := defaultTestConfig()
+ state := newRunState(cfg, 200, 100)
+ if state.showMem {
+ t.Fatal("expected showMem=false initially")
+ }
+ // 'm' should toggle mem just like '2'
+ handleKey(sdl.K_m, nil, cfg, state)
+ if !state.showMem {
+ t.Fatal("expected showMem=true after pressing m")
+ }
+ handleKey(sdl.K_m, nil, cfg, state)
+ if state.showMem {
+ t.Fatal("expected showMem=false after pressing m again")
+ }
+}
+
func TestHandleKey_ToggleNet(t *testing.T) {
renderer, surface, cfg, state, src := newHotkeyTestEnv(t, false, false, false)
defer renderer.Destroy()
@@ -729,6 +746,23 @@ func TestHandleKey_ToggleNet(t *testing.T) {
assertPixelColor(t, surface, 110, 2, constants.LightGreen, 5, "net bar RX after toggle")
}
+func TestHandleKey_ToggleNetAlias(t *testing.T) {
+ cfg := defaultTestConfig()
+ state := newRunState(cfg, 200, 100)
+ if state.showNet {
+ t.Fatal("expected showNet=false initially")
+ }
+ // 'n' should toggle net just like '3'
+ handleKey(sdl.K_n, nil, cfg, state)
+ if !state.showNet {
+ t.Fatal("expected showNet=true after pressing n")
+ }
+ handleKey(sdl.K_n, nil, cfg, state)
+ if state.showNet {
+ t.Fatal("expected showNet=false after pressing n again")
+ }
+}
+
func TestHandleKey_ToggleExtended(t *testing.T) {
renderer, surface, cfg, state, src := newHotkeyTestEnv(t, false, false, false)
defer renderer.Destroy()
@@ -1070,3 +1104,165 @@ func TestHandleKey_ArrowResize(t *testing.T) {
t.Errorf("expected winH=1 (clamped), got %d", state.winH)
}
}
+
+// makeCPUPairWithIO creates a (prev, cur) pair where the delta yields the desired
+// system, user, idle, iowait, irq, and softirq percentages.
+func makeCPUPairWithIO(systemPct, userPct, idlePct, iowaitPct, irqPct, softirqPct float64) (prev, cur collector.CPULine) {
+ const base = 1000
+ const delta = 1000
+ prev = collector.CPULine{Idle: base}
+ dSys := int64(systemPct * float64(delta) / 100)
+ dUser := int64(userPct * float64(delta) / 100)
+ dIdle := int64(idlePct * float64(delta) / 100)
+ dIowait := int64(iowaitPct * float64(delta) / 100)
+ dIRQ := int64(irqPct * float64(delta) / 100)
+ dSoftIRQ := int64(softirqPct * float64(delta) / 100)
+ dNice := delta - dSys - dUser - dIdle - dIowait - dIRQ - dSoftIRQ
+ if dNice < 0 {
+ dNice = 0
+ }
+ cur = collector.CPULine{
+ System: prev.System + dSys,
+ User: prev.User + dUser,
+ Idle: prev.Idle + dIdle,
+ Nice: prev.Nice + dNice,
+ Iowait: prev.Iowait + dIowait,
+ IRQ: prev.IRQ + dIRQ,
+ SoftIRQ: prev.SoftIRQ + dSoftIRQ,
+ }
+ return prev, cur
+}
+
+func TestHandleKey_ToggleIOAvgLine(t *testing.T) {
+ cfg := defaultTestConfig()
+ state := newRunState(cfg, 200, 100)
+ if state.showIOAvgLine {
+ t.Fatal("expected showIOAvgLine=false initially")
+ }
+ handleKey(sdl.K_i, nil, cfg, state)
+ if !state.showIOAvgLine {
+ t.Fatal("expected showIOAvgLine=true after pressing i")
+ }
+ handleKey(sdl.K_i, nil, cfg, state)
+ if state.showIOAvgLine {
+ t.Fatal("expected showIOAvgLine=false after pressing i again")
+ }
+}
+
+func TestGlobalIOAvgLine_SingleHost(t *testing.T) {
+ // One host with 20% iowait + 5% irq + 5% softirq = 30% → pink line at y=30 from top
+ const w, h int32 = 100, 100
+
+ renderer, surface, err := createTestRenderer(w, h)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer renderer.Destroy()
+ defer surface.Free()
+
+ prev, cur := makeCPUPairWithIO(10, 10, 20, 20, 5, 5)
+ cfg := defaultTestConfig()
+
+ src := &mockSource{
+ data: map[string]*stats.HostStats{
+ "host1": {CPU: map[string]collector.CPULine{"cpu": cur}},
+ },
+ }
+
+ state := newRunState(cfg, w, h)
+ state.showIOAvgLine = true
+ state.prevCPU["host1;cpu"] = prev
+
+ drawFrame(renderer, src, cfg, state)
+
+ // Pink line at y=30 (30% from top in a 100px window)
+ assertPixelColor(t, surface, 50, 30, constants.Pink, 3, "IO avg line at y=30")
+ // Spans full width
+ assertPixelColor(t, surface, 0, 30, constants.Pink, 3, "IO avg line at x=0")
+ assertPixelColor(t, surface, 99, 30, constants.Pink, 3, "IO avg line at x=99")
+}
+
+func TestGlobalIOAvgLine_MultiHost(t *testing.T) {
+ // Two hosts: host1=30% IO, host2=0% IO → average 15% → pink line at y=15
+ const w, h int32 = 100, 100
+
+ renderer, surface, err := createTestRenderer(w, h)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer renderer.Destroy()
+ defer surface.Free()
+
+ prev1, cur1 := makeCPUPairWithIO(10, 10, 20, 20, 5, 5) // 30% IO
+ prev2, cur2 := makeCPUPair(40, 40, 20) // 0% IO
+
+ cfg := defaultTestConfig()
+
+ src := &mockSource{
+ data: map[string]*stats.HostStats{
+ "alpha": {CPU: map[string]collector.CPULine{"cpu": cur1}},
+ "beta": {CPU: map[string]collector.CPULine{"cpu": cur2}},
+ },
+ }
+
+ state := newRunState(cfg, w, h)
+ state.showIOAvgLine = true
+ state.prevCPU["alpha;cpu"] = prev1
+ state.prevCPU["beta;cpu"] = prev2
+
+ drawFrame(renderer, src, cfg, state)
+
+ // Average 15% → pink line at y=15
+ assertPixelColor(t, surface, 50, 15, constants.Pink, 3, "IO avg line at y=15")
+}
+
+func TestGlobalIOAvgLine_Disabled(t *testing.T) {
+ // With showIOAvgLine=false, no pink line should appear
+ const w, h int32 = 100, 100
+
+ renderer, surface, err := createTestRenderer(w, h)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer renderer.Destroy()
+ defer surface.Free()
+
+ prev, cur := makeCPUPairWithIO(10, 10, 20, 20, 5, 5) // 30% IO
+ cfg := defaultTestConfig()
+
+ src := &mockSource{
+ data: map[string]*stats.HostStats{
+ "host1": {CPU: map[string]collector.CPULine{"cpu": cur}},
+ },
+ }
+
+ state := newRunState(cfg, w, h)
+ state.showIOAvgLine = false
+ state.prevCPU["host1;cpu"] = prev
+
+ drawFrame(renderer, src, cfg, state)
+
+ // At y=30, there should be no pink line
+ r, g, b := getPixelColor(surface, 50, 30)
+ if r == constants.Pink.R && g == constants.Pink.G && b == constants.Pink.B {
+ t.Errorf("expected no pink IO avg line at y=30 when disabled, got RGB(%d,%d,%d)", r, g, b)
+ }
+}
+
+func TestHandleKey_WriteConfig_IOAvgLine(t *testing.T) {
+ // Verify that 'w' hotkey persists showIOAvgLine to config
+ tmpDir := t.TempDir()
+ origHome := os.Getenv("HOME")
+ os.Setenv("HOME", tmpDir)
+ defer os.Setenv("HOME", origHome)
+
+ cfg := defaultTestConfig()
+ state := newRunState(cfg, 200, 100)
+ state.showIOAvgLine = true
+
+ handleKey(sdl.K_w, nil, cfg, state)
+
+ if !cfg.ShowIOAvgLine {
+ t.Error("expected ShowIOAvgLine=true in config after 'w'")
+ }
+}