Files
nadir-agent/internal/modules/system/info_test.go
T
2026-06-22 16:06:57 +02:00

178 lines
5.3 KiB
Go

package system
import (
"os"
"path/filepath"
"testing"
)
func TestReadHwmonTemps(t *testing.T) {
root := t.TempDir()
// k10temp: CPU, labelled Tctl.
write(t, root, "hwmon0", "name", "k10temp")
write(t, root, "hwmon0", "temp1_input", "62125")
write(t, root, "hwmon0", "temp1_label", "Tctl")
// amdgpu: GPU, no label -> falls back to chip name. Also a sensor reading
// an implausible value that must be dropped.
write(t, root, "hwmon1", "name", "amdgpu")
write(t, root, "hwmon1", "temp1_input", "56000")
write(t, root, "hwmon1", "temp2_input", "-150") // disabled placeholder
// empty input -> skipped without error.
write(t, root, "hwmon2", "name", "cros_ec")
write(t, root, "hwmon2", "temp1_input", "")
got := readHwmonTemps(root)
if len(got) != 2 {
t.Fatalf("want 2 temps, got %d: %+v", len(got), got)
}
if got[0].Chip != "k10temp" || got[0].Label != "Tctl" || got[0].Celsius != 62.125 {
t.Errorf("cpu sensor wrong: %+v", got[0])
}
if got[1].Chip != "amdgpu" || got[1].Label != "amdgpu" || got[1].Celsius != 56 {
t.Errorf("gpu sensor wrong (label should fall back to chip): %+v", got[1])
}
}
func write(t *testing.T, root, chip, file, val string) {
t.Helper()
dir := filepath.Join(root, chip)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, file), []byte(val), 0o644); err != nil {
t.Fatal(err)
}
}
func TestParseMeminfo(t *testing.T) {
data := []byte(`MemTotal: 16000 kB
MemFree: 2000 kB
MemAvailable: 8000 kB
SwapTotal: 4000 kB
SwapFree: 3000 kB
`)
m := parseMeminfo(data)
const kb = 1024
if m.TotalBytes != 16000*kb {
t.Errorf("TotalBytes = %d", m.TotalBytes)
}
if m.AvailableBytes != 8000*kb {
t.Errorf("AvailableBytes = %d", m.AvailableBytes)
}
if m.UsedBytes != (16000-8000)*kb {
t.Errorf("UsedBytes = %d, want %d", m.UsedBytes, (16000-8000)*kb)
}
if m.SwapTotalBytes != 4000*kb || m.SwapFreeBytes != 3000*kb {
t.Errorf("swap parsed wrong: %+v", m)
}
}
func TestParseLoadavg(t *testing.T) {
l := parseLoadavg("0.42 0.55 0.61 1/234 5678")
if l.Load1 != 0.42 || l.Load5 != 0.55 || l.Load15 != 0.61 {
t.Errorf("got %+v", l)
}
bad := parseLoadavg("garbage")
if bad.Load1 != 0 || bad.Load5 != 0 || bad.Load15 != 0 {
t.Error("short input should yield zero LoadInfo")
}
}
func TestCpuFreqMHz(t *testing.T) {
root := t.TempDir()
// Hardware limits live under cpu0 only.
write(t, root, "cpu0/cpufreq", "cpuinfo_min_freq", "400000") // 400 MHz
write(t, root, "cpu0/cpufreq", "cpuinfo_max_freq", "5137000") // 5137 MHz
// Per-core current frequencies (kHz). Core 2 has the peak.
write(t, root, "cpu0/cpufreq", "scaling_cur_freq", "2500000")
write(t, root, "cpu1/cpufreq", "scaling_cur_freq", "1100000")
write(t, root, "cpu2/cpufreq", "scaling_cur_freq", "3200000")
write(t, root, "cpu3/cpufreq", "scaling_cur_freq", "2800000")
min, max, cur := cpuFreqMHz(root)
if min != 400 {
t.Errorf("min = %d, want 400", min)
}
if max != 5137 {
t.Errorf("max = %d, want 5137", max)
}
if cur != 3200 {
t.Errorf("cur = %d, want 3200 (peak across cores)", cur)
}
// Absent cpufreq → all zeros.
emptyRoot := t.TempDir()
min, max, cur = cpuFreqMHz(emptyRoot)
if min != 0 || max != 0 || cur != 0 {
t.Errorf("absent cpufreq: got min=%d max=%d cur=%d, want all 0", min, max, cur)
}
}
func TestCPUModel(t *testing.T) {
x86 := "processor\t: 0\nmodel name\t: AMD Ryzen 7 7840U\ncpu MHz\t: 3000\n"
if got := cpuModel(x86); got != "AMD Ryzen 7 7840U" {
t.Errorf("x86: got %q", got)
}
arm := "processor\t: 0\nModel\t: Raspberry Pi 5 Model B\n"
if got := cpuModel(arm); got != "Raspberry Pi 5 Model B" {
t.Errorf("arm fallback: got %q", got)
}
if got := cpuModel("processor\t: 0\n"); got != "" {
t.Errorf("no model: got %q", got)
}
}
func TestReadProcStat(t *testing.T) {
content := `cpu 100 20 30 400 10 5 3 2 0 0
cpu0 50 10 15 200 5 3 1 1 0 0
cpu1 50 10 15 200 5 2 2 1 0 0
intr 12345
`
f := filepath.Join(t.TempDir(), "stat")
if err := os.WriteFile(f, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
cores := readProcStat(f)
if len(cores) != 2 {
t.Fatalf("want 2 cores, got %d", len(cores))
}
// cpu0: user=50 nice=10 sys=15 idle=200 iowait=5 irq=3 softirq=1 steal=1 guest=0 guest_nice=0
// total = 50+10+15+200+5+3+1+1+0+0 = 285
// idle = 200 + 5 = 205
if cores[0].core != 0 {
t.Errorf("core index = %d, want 0", cores[0].core)
}
if cores[0].total != 285 {
t.Errorf("core0 total = %d, want 285", cores[0].total)
}
if cores[0].idle != 205 {
t.Errorf("core0 idle = %d, want 205", cores[0].idle)
}
}
func TestComputeUsage(t *testing.T) {
prev := []cpuCoreTicks{
{core: 0, total: 1000, idle: 800},
{core: 1, total: 1000, idle: 900},
}
cur := []cpuCoreTicks{
{core: 0, total: 1100, idle: 850}, // 100 total delta, 50 idle delta → 50% usage
{core: 1, total: 1200, idle: 950}, // 200 total delta, 50 idle delta → 75% usage
}
usage := computeUsage(prev, cur)
if len(usage) != 2 {
t.Fatalf("want 2 entries, got %d", len(usage))
}
if usage[0].Core != 0 || usage[0].UsagePct != 50.0 {
t.Errorf("core0: got %+v, want 50%%", usage[0])
}
if usage[1].Core != 1 || usage[1].UsagePct != 75.0 {
t.Errorf("core1: got %+v, want 75%%", usage[1])
}
// Empty prev → empty result.
if got := computeUsage(nil, cur); len(got) != 0 {
t.Errorf("nil prev should yield empty, got %d", len(got))
}
}