Files
bee/audit/internal/platform/platform_stress_test.go
T
Mikhail ChusavitinandClaude Sonnet 5 198567dffe fix: surface CPU thermal throttling in status and SAT results
A CPU that had thermally throttled (sysfs thermal_throttle counter >
0) still reported status "OK" everywhere: dmidecode-derived CPU status
only distinguishes populated/enabled/disabled and never looked at the
throttle flag the collector already recorded next to it, and neither
SAT path meant to catch this actually could:

- The routine "cpu" SAT pack (RunCPUAcceptancePack) only checked
  lscpu/sensors/stress-ng exit codes — stress-ng exits 0 whether or
  not the CPU throttled while running it, so an 89°C/throttled CPU
  right after a "successful" run still showed cpu:all as OK in
  component-status.json.
- The more thorough platform-stress test already detected throttling
  and fan-spindown correctly, but wrote its verdict as "Overall: FAIL
  — ..." with no "=", which parseSATKV can't parse — so even a real
  detected throttle event never reached the component-status DB.

Fixes:
- cpu_telemetry.go: escalate a CPU's status to Warning (only-escalate,
  same severity ranking already used elsewhere) when Throttled is set.
- sat.go: add a before/after thermal-throttle-counter check job around
  the "cpu" pack's stress-ng run, so a throttle event during the run
  fails that job and (via the existing FAILED->Warning DB mapping)
  flips cpu:all to Warning.
- platform_stress.go: emit a machine-readable overall_status= line
  alongside the human-readable verdict so platform-stress results
  actually reach ApplySATResultToDB.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 17:41:40 +03:00

85 lines
2.5 KiB
Go

package platform
import (
"runtime"
"strings"
"testing"
)
func TestPlatformStressCPUThreadsOverride(t *testing.T) {
t.Setenv("BEE_PLATFORM_STRESS_THREADS", "7")
if got := platformStressCPUThreads(); got != 7 {
t.Fatalf("platformStressCPUThreads=%d want 7", got)
}
}
func TestPlatformStressCPUThreadsDefaultLeavesHeadroom(t *testing.T) {
t.Setenv("BEE_PLATFORM_STRESS_THREADS", "")
got := platformStressCPUThreads()
if got < 1 {
t.Fatalf("platformStressCPUThreads=%d want >= 1", got)
}
if got > runtime.NumCPU() {
t.Fatalf("platformStressCPUThreads=%d want <= NumCPU=%d", got, runtime.NumCPU())
}
if runtime.NumCPU() > 2 && got >= runtime.NumCPU() {
t.Fatalf("platformStressCPUThreads=%d want headroom below NumCPU=%d", got, runtime.NumCPU())
}
}
func TestPlatformStressMemoryMBOverride(t *testing.T) {
t.Setenv("BEE_PLATFORM_STRESS_MB", "8192")
if got := platformStressMemoryMB(); got != 8192 {
t.Fatalf("platformStressMemoryMB=%d want 8192", got)
}
}
// TestWritePlatformSummaryOverallStatusKV verifies writePlatformSummary emits
// a parseable "overall_status=" line matching its human-readable "Overall:"
// verdict. ApplySATResultToDB/parseSATKV (internal/app/component_status_db.go)
// only recognizes "key=value" lines — a summary with only "Overall: FAIL —
// ..." (no "=") silently never reaches component-status.json, so a real
// thermal-throttle FAIL detected by this test would never show up as cpu:all
// going Warning.
func TestWritePlatformSummaryOverallStatusKV(t *testing.T) {
opts := PlatformStressOptions{Cycles: []PlatformStressCycle{{LoadSec: 60, IdleSec: 30}}}
tests := []struct {
name string
analyses []cycleAnalysis
want string
}{
{
name: "clean run reports OK",
analyses: []cycleAnalysis{{maxCPUTemp: 70, maxGPUTemp: 60}},
want: "overall_status=OK",
},
{
name: "throttle detected reports FAILED",
analyses: []cycleAnalysis{{maxCPUTemp: 95, throttled: true}},
want: "overall_status=FAILED",
},
{
name: "fast fan spindown reports FAILED",
analyses: []cycleAnalysis{{fanAtCutAvg: 8000, fanMin15s: 2000, fanDropPct: 75}},
want: "overall_status=FAILED",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
summary := writePlatformSummary(opts, tt.analyses)
found := false
for _, line := range strings.Split(summary, "\n") {
if line == tt.want {
found = true
break
}
}
if !found {
t.Fatalf("summary missing %q:\n%s", tt.want, summary)
}
})
}
}