LazyProc.Call must-finds and panics on a missing export; the trimmed Tianyi cloud-PC image has no FreeMibIfTable2 in iphlpapi.dll, which killed the agent on its first collection after a successful register. Optional procs (GetSystemTimes/GlobalMemoryStatusEx/GetIfTable2Ex+ FreeMibIfTable2 as one gated pair) now resolve through sourceGate (Find once, missing = source disabled with one warning); the iface pair is gated together so a missing free proc never leaks the system table. collectMetrics additionally runs every source through guardedCall so any residual panic zeroes that source alone. Pure gate/guard logic lives in probe_guard.go (no build tag) with Linux-runnable regression tests.
64 lines
1.6 KiB
Go
64 lines
1.6 KiB
Go
package agent
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
// The degrade-don't-die mechanics behind V2.2.1 ①: on the trimmed
|
|
// Tianyi cloud-PC image the Windows sampler died on its first
|
|
// collection because a missing iphlpapi export made LazyProc.Call
|
|
// panic. These tests pin the two layers that replace that failure
|
|
// mode — a failed availability probe disables the source (probed
|
|
// once, not per poll), and a panicking source is zeroed, never
|
|
// propagated.
|
|
|
|
func TestSourceGateDisablesFailedProbeOnce(t *testing.T) {
|
|
var g sourceGate
|
|
calls := 0
|
|
probe := func() error {
|
|
calls++
|
|
return errors.New("procedure not found")
|
|
}
|
|
for i := range 3 {
|
|
if g.enabled("test-source", probe) {
|
|
t.Fatalf("call %d: gate enabled despite failed probe", i)
|
|
}
|
|
}
|
|
if calls != 1 {
|
|
t.Fatalf("probe ran %d times, want exactly 1 (cached verdict)", calls)
|
|
}
|
|
}
|
|
|
|
func TestSourceGateEnablesSuccessfulProbeOnce(t *testing.T) {
|
|
var g sourceGate
|
|
calls := 0
|
|
probe := func() error {
|
|
calls++
|
|
return nil
|
|
}
|
|
for i := range 3 {
|
|
if !g.enabled("test-source", probe) {
|
|
t.Fatalf("call %d: gate disabled despite successful probe", i)
|
|
}
|
|
}
|
|
if calls != 1 {
|
|
t.Fatalf("probe ran %d times, want exactly 1 (cached verdict)", calls)
|
|
}
|
|
}
|
|
|
|
func TestGuardedCallRecoversPanicToZero(t *testing.T) {
|
|
got := guardedCall("test-source", func() uint64 {
|
|
panic("Failed to find FreeMibIfTable2 procedure in iphlpapi.dll")
|
|
})
|
|
if got != 0 {
|
|
t.Fatalf("guardedCall after panic = %d, want zero value", got)
|
|
}
|
|
}
|
|
|
|
func TestGuardedCallPassesValueThrough(t *testing.T) {
|
|
if got := guardedCall("test-source", func() string { return "ok" }); got != "ok" {
|
|
t.Fatalf("guardedCall = %q, want %q", got, "ok")
|
|
}
|
|
}
|