Pulse/internal/monitoring/vm_memory_selection_test.go

205 lines
4.9 KiB
Go

package monitoring
import (
"testing"
"github.com/rcourtman/pulse-go-rewrite/pkg/proxmox"
)
func TestSelectVMAvailableFromMemInfo(t *testing.T) {
giB := uint64(1024 * 1024 * 1024)
miB := uint64(1024 * 1024)
tests := []struct {
name string
memInfo *proxmox.VMMemInfo
wantAvailable uint64
wantSource string
wantTotalMinusUsed uint64
}{
{
name: "prefers explicit available",
memInfo: &proxmox.VMMemInfo{
Total: 16 * giB,
Used: 10 * giB,
Free: 2 * giB,
Available: 6 * giB,
Buffers: 512 * miB,
Cached: 2 * giB,
},
wantAvailable: 6 * giB,
wantSource: "meminfo-available",
wantTotalMinusUsed: 6 * giB,
},
{
name: "uses derived free buffers cached when consistent",
memInfo: &proxmox.VMMemInfo{
Total: 8 * giB,
Used: 4 * giB,
Free: 1536 * miB,
Buffers: 512 * miB,
Cached: 2 * giB,
},
wantAvailable: 4 * giB,
wantSource: "meminfo-derived",
wantTotalMinusUsed: 4 * giB,
},
{
name: "defers derived when total-used gap is materially larger",
memInfo: &proxmox.VMMemInfo{
Total: 64 * giB,
Used: 16 * giB,
Free: 6 * giB,
Buffers: 64 * miB,
Cached: 128 * miB,
},
wantAvailable: 0,
wantSource: "",
wantTotalMinusUsed: 48 * giB,
},
{
name: "skips free-only meminfo and keeps total-used fallback",
memInfo: &proxmox.VMMemInfo{
Total: 32 * giB,
Used: 22 * giB,
Free: 10 * giB,
},
wantAvailable: 0,
wantSource: "",
wantTotalMinusUsed: 10 * giB,
},
{
name: "nil meminfo",
memInfo: nil,
},
{
name: "keeps derived when gap is below tolerance",
memInfo: &proxmox.VMMemInfo{
Total: 1024 * miB,
Used: 256 * miB,
Free: 760 * miB,
Buffers: 4 * miB,
},
wantAvailable: 764 * miB,
wantSource: "meminfo-derived",
wantTotalMinusUsed: 768 * miB,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := selectVMAvailableFromMemInfo(tt.memInfo)
if got.Available != tt.wantAvailable {
t.Fatalf("available = %d, want %d", got.Available, tt.wantAvailable)
}
if got.Source != tt.wantSource {
t.Fatalf("source = %q, want %q", got.Source, tt.wantSource)
}
if got.TotalMinusUsed != tt.wantTotalMinusUsed {
t.Fatalf("totalMinusUsed = %d, want %d", got.TotalMinusUsed, tt.wantTotalMinusUsed)
}
})
}
}
func TestSelectVMLowTrustUsedMemory(t *testing.T) {
giB := uint64(1024 * 1024 * 1024)
tests := []struct {
name string
memTotal uint64
status *proxmox.VMStatus
wantUsed uint64
wantSource string
}{
{
name: "prefers status mem when it is plausible",
memTotal: 8 * giB,
status: &proxmox.VMStatus{
Mem: 3 * giB,
FreeMem: 5 * giB,
},
wantUsed: 3 * giB,
wantSource: "status-mem",
},
{
name: "falls back to status freemem when status mem is falsely saturated",
memTotal: 8 * giB,
status: &proxmox.VMStatus{
Mem: 8 * giB,
FreeMem: 5 * giB,
},
wantUsed: 3 * giB,
wantSource: "status-freemem",
},
{
name: "falls back to status freemem when status mem and freemem are materially inconsistent",
memTotal: 8 * giB,
status: &proxmox.VMStatus{
Mem: 7920 * 1024 * 1024,
FreeMem: 5 * giB,
},
wantUsed: 3 * giB,
wantSource: "status-freemem",
},
{
name: "uses status freemem when status mem is absent",
memTotal: 8 * giB,
status: &proxmox.VMStatus{
FreeMem: 6 * giB,
},
wantUsed: 2 * giB,
wantSource: "status-freemem",
},
{
name: "uses balloon total when deriving used from status freemem",
memTotal: 8 * giB,
status: &proxmox.VMStatus{
Balloon: 4 * giB,
FreeMem: 1 * giB,
},
wantUsed: 3 * giB,
wantSource: "status-freemem",
},
{
name: "uses balloon total when status mem is falsely saturated against balloon",
memTotal: 8 * giB,
status: &proxmox.VMStatus{
Mem: 4 * giB,
Balloon: 4 * giB,
FreeMem: 1 * giB,
},
wantUsed: 3 * giB,
wantSource: "status-freemem",
},
{
name: "returns empty selection without usable fields",
memTotal: 8 * giB,
status: &proxmox.VMStatus{},
wantUsed: 0,
wantSource: "",
},
{
name: "keeps status mem when freemem only shows tiny headroom",
memTotal: 8 * giB,
status: &proxmox.VMStatus{
Mem: 8*giB - (64 * 1024 * 1024),
FreeMem: 64 * 1024 * 1024,
},
wantUsed: 8*giB - (64 * 1024 * 1024),
wantSource: "status-mem",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := selectVMLowTrustUsedMemory(tt.memTotal, tt.status)
if got.Used != tt.wantUsed {
t.Fatalf("used = %d, want %d", got.Used, tt.wantUsed)
}
if got.Source != tt.wantSource {
t.Fatalf("source = %q, want %q", got.Source, tt.wantSource)
}
})
}
}