package platform import ( "path/filepath" "testing" "time" ) func TestParseFanSpeeds(t *testing.T) { raw := "FAN1 | 2400.000 | RPM | ok\nFAN2 | 1800 RPM | ok | ok\nFAN3 | na | RPM | ns\n" got := parseFanSpeeds(raw) if len(got) != 2 { t.Fatalf("fans=%d want 2 (%v)", len(got), got) } if got[0].Name != "FAN1" || got[0].RPM != 2400 { t.Fatalf("fan0=%+v", got[0]) } if got[1].Name != "FAN2" || got[1].RPM != 1800 { t.Fatalf("fan1=%+v", got[1]) } } func TestFirstFanInputValue(t *testing.T) { feature := map[string]any{ "fan1_input": 9200.0, } got, ok := firstFanInputValue(feature) if !ok || got != 9200 { t.Fatalf("got=%v ok=%v", got, ok) } } func TestParseFanDutyCyclePctSensorsJSON(t *testing.T) { raw := []byte(`{ "chip0": { "fan1": {"input": 9000}, "pwm1": {"input": 128}, "pwm1_enable": {"input": 1} }, "chip1": { "pwm2": {"input": 64} } }`) got, ok := parseFanDutyCyclePctSensorsJSON(raw) if !ok { t.Fatalf("expected duty cycle telemetry to be parsed") } if got < 57 || got > 58 { t.Fatalf("got=%v want ~57.1", got) } } func TestEstimateFanDutyCyclePctFromObservation(t *testing.T) { t.Parallel() oldPath := fanObservationStatePath oldState := fanObservation oldInit := fanObservationInit oldCandidates := fanPeakCandidates fanObservationStatePath = filepath.Join(t.TempDir(), "fan-observation.json") fanObservation = fanObservationState{} fanObservationInit = false fanPeakCandidates = make(map[string]fanPeakCandidate) t.Cleanup(func() { fanObservationStatePath = oldPath fanObservation = oldState fanObservationInit = oldInit fanPeakCandidates = oldCandidates }) start := time.Unix(100, 0) updateFanObservation([]FanReading{{Name: "FAN1", RPM: 5000}}, start) if _, ok := estimateFanDutyCyclePctFromObservation([]FanReading{{Name: "FAN1", RPM: 2500}}); ok { t.Fatalf("single-sample spike should not establish observed max") } updateFanObservation([]FanReading{{Name: "FAN1", RPM: 5200}}, start.Add(500*time.Millisecond)) updateFanObservation([]FanReading{{Name: "FAN1", RPM: 5100}}, start.Add(1500*time.Millisecond)) got, ok := estimateFanDutyCyclePctFromObservation([]FanReading{{Name: "FAN1", RPM: 2600}}) if !ok { t.Fatalf("expected estimated duty cycle from persisted observed max") } if got < 43 || got > 44 { t.Fatalf("got=%v want ~43.3", got) } fanObservation = fanObservationState{} fanObservationInit = false fanPeakCandidates = make(map[string]fanPeakCandidate) got, ok = estimateFanDutyCyclePctFromObservation([]FanReading{{Name: "FAN1", RPM: 2600}}) if !ok { t.Fatalf("expected persisted observed max to be reloaded from disk") } if got < 43 || got > 44 { t.Fatalf("reloaded got=%v want ~43.3", got) } } func TestParseDCMIPowerReading(t *testing.T) { raw := ` Instantaneous power reading: 512 Watts Minimum during sampling period: 498 Watts ` if got := parseDCMIPowerReading(raw); got != 512 { t.Fatalf("parseDCMIPowerReading()=%v want 512", got) } } func TestEffectiveSystemPowerReading(t *testing.T) { now := time.Now() cache := cachedPowerReading{Value: 480, UpdatedAt: now.Add(-5 * time.Second)} got, updated := effectiveSystemPowerReading(cache, 0, "", "", "", now) if got != 480 { t.Fatalf("got=%v want cached 480", got) } if updated.Value != 480 { t.Fatalf("updated=%+v", updated) } got, updated = effectiveSystemPowerReading(cache, 530, "dcmi", "fallback", "test", now) if got != 530 { t.Fatalf("got=%v want 530", got) } if updated.Value != 530 { t.Fatalf("updated=%+v", updated) } expired := cachedPowerReading{Value: 480, UpdatedAt: now.Add(-systemPowerHoldTTL - time.Second)} got, _ = effectiveSystemPowerReading(expired, 0, "", "", "", now) if got != 0 { t.Fatalf("expired cache returned %v want 0", got) } }