106 lines
4 KiB
Go
106 lines
4 KiB
Go
package hoststats
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestSampleOncePreservesLastGoodDiskReadingOnFailure guards the bug
|
|
// reported live: a failed disk-space sample used to silently reset
|
|
// DiskFreeBytes/DiskTotalBytes to 0 while still marking the overall
|
|
// snapshot Ready -- rendering as "0 bytes free" (indistinguishable from an
|
|
// actually-full disk) instead of "no reading right now". A failed sample
|
|
// must keep the last known-good reading and report DiskReady=false.
|
|
func TestSampleOncePreservesLastGoodDiskReadingOnFailure(t *testing.T) {
|
|
p := &Poller{diskFreeBytesFn: func(string) (int64, int64, error) {
|
|
return 1234, 5678, nil
|
|
}}
|
|
p.sampleOnce()
|
|
first := p.Snapshot()
|
|
if !first.DiskReady || first.DiskFreeBytes != 1234 || first.DiskTotalBytes != 5678 {
|
|
t.Fatalf("first snapshot = %+v, want a successful disk reading", first)
|
|
}
|
|
|
|
p.diskFreeBytesFn = func(string) (int64, int64, error) {
|
|
return 0, 0, errors.New("disk stat failed")
|
|
}
|
|
p.sampleOnce()
|
|
second := p.Snapshot()
|
|
if second.DiskReady {
|
|
t.Fatal("DiskReady = true after a failed sample, want false")
|
|
}
|
|
if second.DiskFreeBytes != 1234 || second.DiskTotalBytes != 5678 {
|
|
t.Fatalf("disk fields after failed sample = (%d, %d), want the preserved (1234, 5678)", second.DiskFreeBytes, second.DiskTotalBytes)
|
|
}
|
|
// CPU/memory sampling must still complete and mark Ready, independent
|
|
// of the disk failure.
|
|
if !second.Ready {
|
|
t.Fatal("Ready = false after a disk-only failure, want true (CPU/mem still sampled)")
|
|
}
|
|
}
|
|
|
|
// TestSampleOnceNeverReadyWithoutAnyPriorSuccess confirms a disk read that
|
|
// has NEVER succeeded (not just failed after a prior success) still reports
|
|
// DiskReady=false with zero-value fields, not a fabricated 0-bytes-free
|
|
// reading.
|
|
func TestSampleOnceNeverReadyWithoutAnyPriorSuccess(t *testing.T) {
|
|
p := &Poller{diskFreeBytesFn: func(string) (int64, int64, error) {
|
|
return 0, 0, errors.New("never worked")
|
|
}}
|
|
p.sampleOnce()
|
|
snap := p.Snapshot()
|
|
if snap.DiskReady {
|
|
t.Fatal("DiskReady = true with no successful sample ever, want false")
|
|
}
|
|
if !snap.Ready {
|
|
t.Fatal("Ready = false, want true (CPU/mem sampling is independent of disk)")
|
|
}
|
|
}
|
|
|
|
// TestNewPollerWalksUpToNearestExistingAncestor guards the other half of the
|
|
// fix: a configured disk path that doesn't exist yet (e.g. an S3-backend
|
|
// deployment's local blob staging directory, only created on first upload)
|
|
// must not permanently break disk stats -- NewPoller walks up to the
|
|
// nearest existing ancestor instead of handing GetDiskFreeSpaceEx/statfs a
|
|
// path they will always fail to stat.
|
|
func TestNewPollerWalksUpToNearestExistingAncestor(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
missing := filepath.Join(tmp, "not-created-yet", "nested", "deeper")
|
|
|
|
p := NewPoller(missing)
|
|
|
|
if p.diskPath != tmp {
|
|
t.Fatalf("resolved disk path = %q, want the nearest existing ancestor %q", p.diskPath, tmp)
|
|
}
|
|
if _, err := os.Stat(p.diskPath); err != nil {
|
|
t.Fatalf("resolved disk path %q does not exist: %v", p.diskPath, err)
|
|
}
|
|
}
|
|
|
|
// TestNewPollerResolvesRelativePathToAbsolute confirms a relative diskPath
|
|
// (as configured today, e.g. "data/blobs") no longer silently depends on
|
|
// whatever the process's current directory happens to be at NewPoller time.
|
|
func TestNewPollerResolvesRelativePathToAbsolute(t *testing.T) {
|
|
p := NewPoller(".")
|
|
if !filepath.IsAbs(p.diskPath) {
|
|
t.Fatalf("resolved disk path %q is not absolute", p.diskPath)
|
|
}
|
|
}
|
|
|
|
// TestRunSamplesImmediatelyOnStart confirms Run's documented behavior
|
|
// (sampleOnce before entering the ticker loop) without relying on any
|
|
// ticker firing or a busy-wait: cancel the context right away and check the
|
|
// synchronous first sample already landed by the time Run returns.
|
|
func TestRunSamplesImmediatelyOnStart(t *testing.T) {
|
|
p := &Poller{diskFreeBytesFn: func(string) (int64, int64, error) { return 1, 1, nil }}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
p.Run(ctx, time.Hour)
|
|
if !p.Snapshot().Ready {
|
|
t.Fatal("Run did not produce a ready snapshot from its initial sample")
|
|
}
|
|
}
|