package hub import ( "context" "os" "path/filepath" "testing" ) // writeSysfs creates path under root with the given content (sysfs files are tiny text files). func writeSysfs(t *testing.T, path, content string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatalf("mkdir: %v", err) } if err := os.WriteFile(path, []byte(content), 0o644); err != nil { t.Fatalf("write %s: %v", path, err) } } // A coretemp hwmon with a "Package id 0" label must win over a per-core sensor. func TestSysfsTempReader_HwmonPackagePreferred(t *testing.T) { root := t.TempDir() dir := filepath.Join(root, "class", "hwmon", "hwmon0") writeSysfs(t, filepath.Join(dir, "name"), "coretemp\n") // temp1 = Package id 0 = 47°C; temp2 = Core 0 = 52°C. The package must be chosen. writeSysfs(t, filepath.Join(dir, "temp1_input"), "47000\n") writeSysfs(t, filepath.Join(dir, "temp1_label"), "Package id 0\n") writeSysfs(t, filepath.Join(dir, "temp2_input"), "52000\n") writeSysfs(t, filepath.Join(dir, "temp2_label"), "Core 0\n") got := SysfsTempReader{Root: root}.CPUTempC(context.Background()) if got == nil || *got != 47 { t.Fatalf("CPUTempC = %v, want 47", got) } } // With no package label, the first readable hwmon input is used. func TestSysfsTempReader_HwmonFirstInputFallback(t *testing.T) { root := t.TempDir() dir := filepath.Join(root, "class", "hwmon", "hwmon0") writeSysfs(t, filepath.Join(dir, "name"), "k10temp\n") writeSysfs(t, filepath.Join(dir, "temp1_input"), "39000\n") // Tctl, no label got := SysfsTempReader{Root: root}.CPUTempC(context.Background()) if got == nil || *got != 39 { t.Fatalf("CPUTempC = %v, want 39", got) } } // A non-CPU hwmon (e.g. a NIC) must be ignored; the thermal-zone CPU sensor is used instead. func TestSysfsTempReader_ThermalZoneByType(t *testing.T) { root := t.TempDir() // hwmon is a non-CPU driver → skipped. nic := filepath.Join(root, "class", "hwmon", "hwmon0") writeSysfs(t, filepath.Join(nic, "name"), "iwlwifi\n") writeSysfs(t, filepath.Join(nic, "temp1_input"), "60000\n") // thermal zones: acpitz (40°C) + x86_pkg_temp (55°C). The CPU package type must win. z0 := filepath.Join(root, "class", "thermal", "thermal_zone0") writeSysfs(t, filepath.Join(z0, "type"), "acpitz\n") writeSysfs(t, filepath.Join(z0, "temp"), "40000\n") z1 := filepath.Join(root, "class", "thermal", "thermal_zone1") writeSysfs(t, filepath.Join(z1, "type"), "x86_pkg_temp\n") writeSysfs(t, filepath.Join(z1, "temp"), "55000\n") got := SysfsTempReader{Root: root}.CPUTempC(context.Background()) if got == nil || *got != 55 { t.Fatalf("CPUTempC = %v, want 55 (x86_pkg_temp), got %v", got, got) } } // The headline graceful-null case: a host that exposes NO sensor (empty /sys) returns nil, and // no error propagates (CPUTempC has no error return — a missing sensor is "n/a", never a failure). func TestSysfsTempReader_GracefulNullWhenAbsent(t *testing.T) { root := t.TempDir() // empty: no hwmon, no thermal zones if got := (SysfsTempReader{Root: root}).CPUTempC(context.Background()); got != nil { t.Fatalf("CPUTempC on a sensorless host = %v, want nil", got) } } // Out-of-range / garbage readings degrade to nil rather than reporting a bogus temperature. func TestSysfsTempReader_RejectsImplausibleValues(t *testing.T) { root := t.TempDir() z := filepath.Join(root, "class", "thermal", "thermal_zone0") writeSysfs(t, filepath.Join(z, "type"), "x86_pkg_temp\n") writeSysfs(t, filepath.Join(z, "temp"), "0\n") // 0 m°C → implausible → ignored if got := (SysfsTempReader{Root: root}).CPUTempC(context.Background()); got != nil { t.Fatalf("CPUTempC on a 0°C reading = %v, want nil", got) } }