mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 11:31:46 +00:00
test: add tests for cmd/status and conclude merge
This commit is contained in:
@@ -86,20 +86,14 @@ func TestColorizeTempThresholds(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestColorizeTempStyleRanges(t *testing.T) {
|
func TestColorizeTempStyleRanges(t *testing.T) {
|
||||||
// Test that different temperature ranges use different styles
|
|
||||||
// We can't easily test the exact style applied, but we can verify
|
|
||||||
// the function returns consistent results for each range
|
|
||||||
|
|
||||||
normalTemp := colorizeTemp(40.0)
|
normalTemp := colorizeTemp(40.0)
|
||||||
warningTemp := colorizeTemp(65.0)
|
warningTemp := colorizeTemp(65.0)
|
||||||
dangerTemp := colorizeTemp(85.0)
|
dangerTemp := colorizeTemp(85.0)
|
||||||
|
|
||||||
// All should be non-empty and contain the formatted value
|
|
||||||
if normalTemp == "" || warningTemp == "" || dangerTemp == "" {
|
if normalTemp == "" || warningTemp == "" || dangerTemp == "" {
|
||||||
t.Fatal("colorizeTemp should not return empty strings")
|
t.Fatal("colorizeTemp should not return empty strings")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify formatting precision (one decimal place)
|
|
||||||
if !strings.Contains(normalTemp, "40.0") {
|
if !strings.Contains(normalTemp, "40.0") {
|
||||||
t.Errorf("normal temp should contain '40.0', got: %s", normalTemp)
|
t.Errorf("normal temp should contain '40.0', got: %s", normalTemp)
|
||||||
}
|
}
|
||||||
@@ -110,3 +104,93 @@ func TestColorizeTempStyleRanges(t *testing.T) {
|
|||||||
t.Errorf("danger temp should contain '85.0', got: %s", dangerTemp)
|
t.Errorf("danger temp should contain '85.0', got: %s", dangerTemp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCalculateHealthScoreEdgeCases(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cpu CPUStatus
|
||||||
|
mem MemoryStatus
|
||||||
|
disks []DiskStatus
|
||||||
|
diskIO DiskIOStatus
|
||||||
|
thermal ThermalStatus
|
||||||
|
wantMin int
|
||||||
|
wantMax int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all metrics at normal threshold",
|
||||||
|
cpu: CPUStatus{Usage: 30.0},
|
||||||
|
mem: MemoryStatus{UsedPercent: 50.0},
|
||||||
|
disks: []DiskStatus{{UsedPercent: 70.0}},
|
||||||
|
diskIO: DiskIOStatus{ReadRate: 25.0, WriteRate: 25.0},
|
||||||
|
thermal: ThermalStatus{CPUTemp: 60.0},
|
||||||
|
wantMin: 95,
|
||||||
|
wantMax: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "memory pressure warning only",
|
||||||
|
cpu: CPUStatus{Usage: 10.0},
|
||||||
|
mem: MemoryStatus{UsedPercent: 40.0, Pressure: "warn"},
|
||||||
|
disks: []DiskStatus{{UsedPercent: 40.0}},
|
||||||
|
diskIO: DiskIOStatus{ReadRate: 5.0, WriteRate: 5.0},
|
||||||
|
thermal: ThermalStatus{CPUTemp: 40.0},
|
||||||
|
wantMin: 90,
|
||||||
|
wantMax: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty disks array",
|
||||||
|
cpu: CPUStatus{Usage: 10.0},
|
||||||
|
mem: MemoryStatus{UsedPercent: 30.0},
|
||||||
|
disks: []DiskStatus{},
|
||||||
|
diskIO: DiskIOStatus{ReadRate: 5.0, WriteRate: 5.0},
|
||||||
|
thermal: ThermalStatus{CPUTemp: 40.0},
|
||||||
|
wantMin: 95,
|
||||||
|
wantMax: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero thermal data",
|
||||||
|
cpu: CPUStatus{Usage: 10.0},
|
||||||
|
mem: MemoryStatus{UsedPercent: 30.0},
|
||||||
|
disks: []DiskStatus{{UsedPercent: 40.0}},
|
||||||
|
diskIO: DiskIOStatus{ReadRate: 5.0, WriteRate: 5.0},
|
||||||
|
thermal: ThermalStatus{CPUTemp: 0},
|
||||||
|
wantMin: 95,
|
||||||
|
wantMax: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
score, _ := calculateHealthScore(tt.cpu, tt.mem, tt.disks, tt.diskIO, tt.thermal)
|
||||||
|
if score < tt.wantMin || score > tt.wantMax {
|
||||||
|
t.Errorf("calculateHealthScore() = %d, want range [%d, %d]", score, tt.wantMin, tt.wantMax)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatUptimeEdgeCases(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
secs uint64
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"zero seconds", 0, "0m"},
|
||||||
|
{"59 seconds", 59, "0m"},
|
||||||
|
{"one minute exact", 60, "1m"},
|
||||||
|
{"59 minutes 59 seconds", 3599, "59m"},
|
||||||
|
{"one hour exact", 3600, "1h 0m"},
|
||||||
|
{"one day exact", 86400, "1d 0h"},
|
||||||
|
{"one day one hour", 90000, "1d 1h"},
|
||||||
|
{"multiple days no hours", 172800, "2d 0h"},
|
||||||
|
{"large uptime", 31536000, "365d 0h"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := formatUptime(tt.secs)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("formatUptime(%d) = %q, want %q", tt.secs, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestFormatRate(t *testing.T) {
|
func TestFormatRate(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -368,3 +371,610 @@ func TestDiskLabel(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseInt(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
// Basic integers.
|
||||||
|
{"simple number", "123", 123},
|
||||||
|
{"zero", "0", 0},
|
||||||
|
{"single digit", "5", 5},
|
||||||
|
|
||||||
|
// With whitespace.
|
||||||
|
{"leading space", " 42", 42},
|
||||||
|
{"trailing space", "42 ", 42},
|
||||||
|
{"both spaces", " 42 ", 42},
|
||||||
|
|
||||||
|
// With non-numeric padding.
|
||||||
|
{"leading @", "@60", 60},
|
||||||
|
{"trailing Hz", "120Hz", 120},
|
||||||
|
{"both padding", "@60Hz", 60},
|
||||||
|
|
||||||
|
// Decimals (truncated to int).
|
||||||
|
{"decimal", "60.00", 60},
|
||||||
|
{"decimal with suffix", "119.88hz", 119},
|
||||||
|
|
||||||
|
// Edge cases.
|
||||||
|
{"empty string", "", 0},
|
||||||
|
{"only spaces", " ", 0},
|
||||||
|
{"no digits", "abc", 0},
|
||||||
|
{"negative strips sign", "-5", 5}, // Strips non-numeric prefix.
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := parseInt(tt.input)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("parseInt(%q) = %d, want %d", tt.input, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseRefreshRate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// Standard formats.
|
||||||
|
{"60Hz format", "Resolution: 1920x1080 @ 60Hz", "60Hz"},
|
||||||
|
{"120Hz format", "Resolution: 2560x1600 @ 120Hz", "120Hz"},
|
||||||
|
{"separated Hz", "Refresh Rate: 60 Hz", "60Hz"},
|
||||||
|
|
||||||
|
// Decimal refresh rates.
|
||||||
|
{"decimal Hz", "Resolution: 3840x2160 @ 59.94Hz", "59Hz"},
|
||||||
|
{"ProMotion", "Resolution: 3456x2234 @ 120.00Hz", "120Hz"},
|
||||||
|
|
||||||
|
// Multiple lines — picks highest valid.
|
||||||
|
{"multiple rates", "Display 1: 60Hz\nDisplay 2: 120Hz", "120Hz"},
|
||||||
|
|
||||||
|
// Edge cases.
|
||||||
|
{"empty string", "", ""},
|
||||||
|
{"no Hz found", "Resolution: 1920x1080", ""},
|
||||||
|
{"invalid Hz value", "Rate: abcHz", ""},
|
||||||
|
{"Hz too high filtered", "Rate: 600Hz", ""},
|
||||||
|
|
||||||
|
// Case insensitivity.
|
||||||
|
{"lowercase hz", "60hz", "60Hz"},
|
||||||
|
{"uppercase HZ", "60HZ", "60Hz"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := parseRefreshRate(tt.input)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("parseRefreshRate(%q) = %q, want %q", tt.input, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsNoiseInterface(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
// Noise interfaces (should return true).
|
||||||
|
{"loopback", "lo0", true},
|
||||||
|
{"awdl", "awdl0", true},
|
||||||
|
{"utun", "utun0", true},
|
||||||
|
{"llw", "llw0", true},
|
||||||
|
{"bridge", "bridge0", true},
|
||||||
|
{"gif", "gif0", true},
|
||||||
|
{"stf", "stf0", true},
|
||||||
|
{"xhc", "xhc0", true},
|
||||||
|
{"anpi", "anpi0", true},
|
||||||
|
{"ap", "ap1", true},
|
||||||
|
|
||||||
|
// Real interfaces (should return false).
|
||||||
|
{"ethernet", "en0", false},
|
||||||
|
{"wifi", "en1", false},
|
||||||
|
{"thunderbolt", "en5", false},
|
||||||
|
|
||||||
|
// Case insensitivity.
|
||||||
|
{"uppercase LO", "LO0", true},
|
||||||
|
{"mixed case Awdl", "Awdl0", true},
|
||||||
|
|
||||||
|
// Edge cases.
|
||||||
|
{"empty string", "", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := isNoiseInterface(tt.input)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("isNoiseInterface(%q) = %v, want %v", tt.input, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePMSet(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
raw string
|
||||||
|
health string
|
||||||
|
cycles int
|
||||||
|
capacity int
|
||||||
|
wantLen int
|
||||||
|
wantPct float64
|
||||||
|
wantStat string
|
||||||
|
wantTime string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "charging with time",
|
||||||
|
raw: `Now drawing from 'AC Power'
|
||||||
|
-InternalBattery-0 (id=1234) 85%; charging; 0:45 remaining present: true`,
|
||||||
|
health: "Good",
|
||||||
|
cycles: 150,
|
||||||
|
capacity: 92,
|
||||||
|
wantLen: 1,
|
||||||
|
wantPct: 85,
|
||||||
|
wantStat: "charging",
|
||||||
|
wantTime: "0:45",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discharging",
|
||||||
|
raw: `Now drawing from 'Battery Power'
|
||||||
|
-InternalBattery-0 (id=1234) 45%; discharging; 2:30 remaining present: true`,
|
||||||
|
health: "Normal",
|
||||||
|
cycles: 200,
|
||||||
|
capacity: 88,
|
||||||
|
wantLen: 1,
|
||||||
|
wantPct: 45,
|
||||||
|
wantStat: "discharging",
|
||||||
|
wantTime: "2:30",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fully charged",
|
||||||
|
raw: `Now drawing from 'AC Power'
|
||||||
|
-InternalBattery-0 (id=1234) 100%; charged; present: true`,
|
||||||
|
health: "Good",
|
||||||
|
cycles: 50,
|
||||||
|
capacity: 100,
|
||||||
|
wantLen: 1,
|
||||||
|
wantPct: 100,
|
||||||
|
wantStat: "charged",
|
||||||
|
wantTime: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty output",
|
||||||
|
raw: "",
|
||||||
|
health: "",
|
||||||
|
cycles: 0,
|
||||||
|
capacity: 0,
|
||||||
|
wantLen: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no battery line",
|
||||||
|
raw: "Now drawing from 'AC Power'\nNo batteries found.",
|
||||||
|
health: "",
|
||||||
|
cycles: 0,
|
||||||
|
capacity: 0,
|
||||||
|
wantLen: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := parsePMSet(tt.raw, tt.health, tt.cycles, tt.capacity)
|
||||||
|
if len(got) != tt.wantLen {
|
||||||
|
t.Errorf("parsePMSet() returned %d batteries, want %d", len(got), tt.wantLen)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.wantLen == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b := got[0]
|
||||||
|
if b.Percent != tt.wantPct {
|
||||||
|
t.Errorf("Percent = %v, want %v", b.Percent, tt.wantPct)
|
||||||
|
}
|
||||||
|
if b.Status != tt.wantStat {
|
||||||
|
t.Errorf("Status = %q, want %q", b.Status, tt.wantStat)
|
||||||
|
}
|
||||||
|
if b.TimeLeft != tt.wantTime {
|
||||||
|
t.Errorf("TimeLeft = %q, want %q", b.TimeLeft, tt.wantTime)
|
||||||
|
}
|
||||||
|
if b.Health != tt.health {
|
||||||
|
t.Errorf("Health = %q, want %q", b.Health, tt.health)
|
||||||
|
}
|
||||||
|
if b.CycleCount != tt.cycles {
|
||||||
|
t.Errorf("CycleCount = %d, want %d", b.CycleCount, tt.cycles)
|
||||||
|
}
|
||||||
|
if b.Capacity != tt.capacity {
|
||||||
|
t.Errorf("Capacity = %d, want %d", b.Capacity, tt.capacity)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProgressBar(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
percent float64
|
||||||
|
wantRune int
|
||||||
|
}{
|
||||||
|
{"zero percent", 0, 16},
|
||||||
|
{"negative clamped", -10, 16},
|
||||||
|
{"low percent", 25, 16},
|
||||||
|
{"half", 50, 16},
|
||||||
|
{"high percent", 75, 16},
|
||||||
|
{"full", 100, 16},
|
||||||
|
{"over 100 clamped", 150, 16},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := progressBar(tt.percent)
|
||||||
|
if len(got) == 0 {
|
||||||
|
t.Errorf("progressBar(%v) returned empty string", tt.percent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gotClean := stripANSI(got)
|
||||||
|
gotRuneCount := len([]rune(gotClean))
|
||||||
|
if gotRuneCount != tt.wantRune {
|
||||||
|
t.Errorf("progressBar(%v) rune count = %d, want %d", tt.percent, gotRuneCount, tt.wantRune)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBatteryProgressBar(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
percent float64
|
||||||
|
wantRune int
|
||||||
|
}{
|
||||||
|
{"zero percent", 0, 16},
|
||||||
|
{"negative clamped", -10, 16},
|
||||||
|
{"critical low", 15, 16},
|
||||||
|
{"low", 25, 16},
|
||||||
|
{"medium", 50, 16},
|
||||||
|
{"high", 75, 16},
|
||||||
|
{"full", 100, 16},
|
||||||
|
{"over 100 clamped", 120, 16},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := batteryProgressBar(tt.percent)
|
||||||
|
if len(got) == 0 {
|
||||||
|
t.Errorf("batteryProgressBar(%v) returned empty string", tt.percent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gotClean := stripANSI(got)
|
||||||
|
gotRuneCount := len([]rune(gotClean))
|
||||||
|
if gotRuneCount != tt.wantRune {
|
||||||
|
t.Errorf("batteryProgressBar(%v) rune count = %d, want %d", tt.percent, gotRuneCount, tt.wantRune)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorizeTemp(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
temp float64
|
||||||
|
}{
|
||||||
|
{"very low", 20.0},
|
||||||
|
{"low", 40.0},
|
||||||
|
{"normal threshold", 55.9},
|
||||||
|
{"at warn threshold", 56.0},
|
||||||
|
{"warn range", 65.0},
|
||||||
|
{"just below danger", 75.9},
|
||||||
|
{"at danger threshold", 76.0},
|
||||||
|
{"high", 85.0},
|
||||||
|
{"very high", 95.0},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := colorizeTemp(tt.temp)
|
||||||
|
if got == "" {
|
||||||
|
t.Errorf("colorizeTemp(%v) returned empty string", tt.temp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIoBar(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
rate float64
|
||||||
|
}{
|
||||||
|
{"zero", 0},
|
||||||
|
{"very low", 5},
|
||||||
|
{"low normal", 20},
|
||||||
|
{"at warn threshold", 30},
|
||||||
|
{"warn range", 50},
|
||||||
|
{"just below danger", 79},
|
||||||
|
{"at danger threshold", 80},
|
||||||
|
{"high", 100},
|
||||||
|
{"very high", 200},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := ioBar(tt.rate)
|
||||||
|
if got == "" {
|
||||||
|
t.Errorf("ioBar(%v) returned empty string", tt.rate)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gotClean := stripANSI(got)
|
||||||
|
gotRuneCount := len([]rune(gotClean))
|
||||||
|
if gotRuneCount != 5 {
|
||||||
|
t.Errorf("ioBar(%v) rune count = %d, want 5", tt.rate, gotRuneCount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMiniBar(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
percent float64
|
||||||
|
}{
|
||||||
|
{"zero", 0},
|
||||||
|
{"negative", -5},
|
||||||
|
{"low", 15},
|
||||||
|
{"at first step", 20},
|
||||||
|
{"mid", 50},
|
||||||
|
{"high", 75},
|
||||||
|
{"full", 100},
|
||||||
|
{"over 100", 120},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := miniBar(tt.percent)
|
||||||
|
if got == "" {
|
||||||
|
t.Errorf("miniBar(%v) returned empty string", tt.percent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gotClean := stripANSI(got)
|
||||||
|
gotRuneCount := len([]rune(gotClean))
|
||||||
|
if gotRuneCount != 5 {
|
||||||
|
t.Errorf("miniBar(%v) rune count = %d, want 5", tt.percent, gotRuneCount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatDiskLine(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
label string
|
||||||
|
disk DiskStatus
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty label defaults to DISK",
|
||||||
|
label: "",
|
||||||
|
disk: DiskStatus{UsedPercent: 50.5, Used: 100 << 30, Total: 200 << 30},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "internal disk",
|
||||||
|
label: "INTR",
|
||||||
|
disk: DiskStatus{UsedPercent: 67.2, Used: 336 << 30, Total: 500 << 30},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "external disk",
|
||||||
|
label: "EXTR1",
|
||||||
|
disk: DiskStatus{UsedPercent: 85.0, Used: 850 << 30, Total: 1000 << 30},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "low usage",
|
||||||
|
label: "INTR",
|
||||||
|
disk: DiskStatus{UsedPercent: 15.3, Used: 15 << 30, Total: 100 << 30},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := formatDiskLine(tt.label, tt.disk)
|
||||||
|
if got == "" {
|
||||||
|
t.Errorf("formatDiskLine(%q, ...) returned empty string", tt.label)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expectedLabel := tt.label
|
||||||
|
if expectedLabel == "" {
|
||||||
|
expectedLabel = "DISK"
|
||||||
|
}
|
||||||
|
if !contains(got, expectedLabel) {
|
||||||
|
t.Errorf("formatDiskLine(%q, ...) = %q, should contain label %q", tt.label, got, expectedLabel)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetScoreStyle(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
score int
|
||||||
|
}{
|
||||||
|
{"critical low", 10},
|
||||||
|
{"poor low", 25},
|
||||||
|
{"just below fair", 39},
|
||||||
|
{"at fair threshold", 40},
|
||||||
|
{"fair range", 50},
|
||||||
|
{"just below good", 59},
|
||||||
|
{"at good threshold", 60},
|
||||||
|
{"good range", 70},
|
||||||
|
{"just below excellent", 74},
|
||||||
|
{"at excellent threshold", 75},
|
||||||
|
{"excellent range", 85},
|
||||||
|
{"just below perfect", 89},
|
||||||
|
{"perfect", 90},
|
||||||
|
{"max", 100},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
style := getScoreStyle(tt.score)
|
||||||
|
if style.GetForeground() == nil {
|
||||||
|
t.Errorf("getScoreStyle(%d) returned style with no foreground color", tt.score)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxInt(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
a int
|
||||||
|
b int
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{"a greater", 10, 5, 10},
|
||||||
|
{"b greater", 3, 8, 8},
|
||||||
|
{"equal", 7, 7, 7},
|
||||||
|
{"negative a greater", -5, -10, -5},
|
||||||
|
{"negative b greater", -10, -5, -5},
|
||||||
|
{"zero vs positive", 0, 5, 5},
|
||||||
|
{"zero vs negative", 0, -5, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := maxInt(tt.a, tt.b)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("maxInt(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSparkline(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
history []float64
|
||||||
|
current float64
|
||||||
|
width int
|
||||||
|
wantLen int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty history",
|
||||||
|
history: []float64{},
|
||||||
|
current: 1.5,
|
||||||
|
width: 10,
|
||||||
|
wantLen: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "short history padded",
|
||||||
|
history: []float64{1.0, 2.0, 3.0},
|
||||||
|
current: 3.0,
|
||||||
|
width: 10,
|
||||||
|
wantLen: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact width",
|
||||||
|
history: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
|
||||||
|
current: 5.0,
|
||||||
|
width: 5,
|
||||||
|
wantLen: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "history longer than width",
|
||||||
|
history: []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0},
|
||||||
|
current: 10.0,
|
||||||
|
width: 5,
|
||||||
|
wantLen: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "low current value ok style",
|
||||||
|
history: []float64{1.0, 1.5, 2.0},
|
||||||
|
current: 2.0,
|
||||||
|
width: 5,
|
||||||
|
wantLen: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "medium current value warn style",
|
||||||
|
history: []float64{3.0, 4.0, 5.0},
|
||||||
|
current: 5.0,
|
||||||
|
width: 5,
|
||||||
|
wantLen: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "high current value danger style",
|
||||||
|
history: []float64{8.0, 9.0, 10.0},
|
||||||
|
current: 10.0,
|
||||||
|
width: 5,
|
||||||
|
wantLen: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all identical values flatline",
|
||||||
|
history: []float64{5.0, 5.0, 5.0, 5.0, 5.0},
|
||||||
|
current: 5.0,
|
||||||
|
width: 5,
|
||||||
|
wantLen: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero width edge case",
|
||||||
|
history: []float64{1.0, 2.0, 3.0},
|
||||||
|
current: 2.0,
|
||||||
|
width: 0,
|
||||||
|
wantLen: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "width of 1",
|
||||||
|
history: []float64{1.0, 2.0, 3.0},
|
||||||
|
current: 2.0,
|
||||||
|
width: 1,
|
||||||
|
wantLen: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := sparkline(tt.history, tt.current, tt.width)
|
||||||
|
if tt.width == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got == "" {
|
||||||
|
t.Errorf("sparkline() returned empty string")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gotClean := stripANSI(got)
|
||||||
|
if len([]rune(gotClean)) != tt.wantLen {
|
||||||
|
t.Errorf("sparkline() rune length = %d, want %d", len([]rune(gotClean)), tt.wantLen)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripANSI(s string) string {
|
||||||
|
var result strings.Builder
|
||||||
|
i := 0
|
||||||
|
for i < len(s) {
|
||||||
|
if i < len(s)-1 && s[i] == '\x1b' && s[i+1] == '[' {
|
||||||
|
i += 2
|
||||||
|
for i < len(s) && (s[i] < 'A' || s[i] > 'Z') && (s[i] < 'a' || s[i] > 'z') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i < len(s) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.WriteByte(s[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(s, substr string) bool {
|
||||||
|
return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || containsMiddle(s, substr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsMiddle(s, substr string) bool {
|
||||||
|
for i := 0; i <= len(s)-len(substr); i++ {
|
||||||
|
if s[i:i+len(substr)] == substr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user