Public source validation / validate (push) Failing after 3m8s
200 lines
6.6 KiB
Go
200 lines
6.6 KiB
Go
package hostcollect
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func readFixture(t *testing.T, elements ...string) []byte {
|
|
t.Helper()
|
|
path := filepath.Join(append([]string{"testdata"}, elements...)...)
|
|
data, err := os.ReadFile(path) //nolint:gosec // fixture path is test-controlled
|
|
if err != nil {
|
|
t.Fatalf("read fixture %s: %v", path, err)
|
|
}
|
|
return data
|
|
}
|
|
|
|
func TestParseProcStatReadsAggregateAndCores(t *testing.T) {
|
|
sample, err := parseProcStat(readFixture(t, "proc-healthy", "stat"))
|
|
if err != nil {
|
|
t.Fatalf("parseProcStat returned error: %v", err)
|
|
}
|
|
// user+nice+system+idle+iowait+irq+softirq+steal, with guest and guest_nice left
|
|
// out because the kernel already counts guest time inside user and nice.
|
|
const wantTotal = 1234567 + 8901 + 234567 + 45678901 + 12345 + 0 + 6789 + 1234
|
|
if sample.all.total != wantTotal {
|
|
t.Fatalf("aggregate total = %d, want %d", sample.all.total, wantTotal)
|
|
}
|
|
if sample.all.idle != 45678901 || sample.all.iowait != 12345 {
|
|
t.Fatalf("unexpected idle/iowait: %+v", sample.all)
|
|
}
|
|
if len(sample.cores) != 2 {
|
|
t.Fatalf("cores = %d, want 2", len(sample.cores))
|
|
}
|
|
}
|
|
|
|
func TestParseProcStatToleratesOldKernelsAndGarbage(t *testing.T) {
|
|
sample, err := parseProcStat(readFixture(t, "proc-messy", "stat"))
|
|
if err != nil {
|
|
t.Fatalf("parseProcStat returned error: %v", err)
|
|
}
|
|
if sample.all.total != 1000+200+300+4000 {
|
|
t.Fatalf("unexpected total for a four column kernel: %d", sample.all.total)
|
|
}
|
|
// cpu0 and cpu2 parse; "cpu-bogus" and the non-numeric "cpu9" line do not.
|
|
if len(sample.cores) != 2 {
|
|
t.Fatalf("cores = %d, want 2", len(sample.cores))
|
|
}
|
|
}
|
|
|
|
func TestParseProcStatRequiresAggregate(t *testing.T) {
|
|
if _, err := parseProcStat([]byte("intr 1 2 3\nctxt 4\n")); err == nil {
|
|
t.Fatal("expected an error when /proc/stat has no cpu line")
|
|
}
|
|
}
|
|
|
|
func TestUtilisationComputesDeltaNotAbsoluteValue(t *testing.T) {
|
|
previous := cpuTimes{total: 1000, idle: 800, iowait: 100}
|
|
current := cpuTimes{total: 1200, idle: 900, iowait: 150}
|
|
busy, iowait, ok := utilisation(previous, current)
|
|
if !ok {
|
|
t.Fatal("expected a usable delta")
|
|
}
|
|
if busy != 25 {
|
|
t.Fatalf("busy = %v, want 25", busy)
|
|
}
|
|
if iowait != 25 {
|
|
t.Fatalf("iowait = %v, want 25", iowait)
|
|
}
|
|
}
|
|
|
|
func TestUtilisationRejectsCounterWrapAndStandstill(t *testing.T) {
|
|
cases := map[string]struct{ previous, current cpuTimes }{
|
|
"total wrapped": {cpuTimes{total: 18446744073709551000, idle: 10, iowait: 1}, cpuTimes{total: 400, idle: 20, iowait: 2}},
|
|
"idle wrapped": {cpuTimes{total: 1000, idle: 900, iowait: 10}, cpuTimes{total: 1100, idle: 5, iowait: 11}},
|
|
"iowait wrapped": {cpuTimes{total: 1000, idle: 800, iowait: 100}, cpuTimes{total: 1100, idle: 850, iowait: 4}},
|
|
"no elapsed": {cpuTimes{total: 1000, idle: 800, iowait: 100}, cpuTimes{total: 1000, idle: 800, iowait: 100}},
|
|
"idle exceeds": {cpuTimes{total: 1000, idle: 800, iowait: 100}, cpuTimes{total: 1010, idle: 900, iowait: 120}},
|
|
}
|
|
for name, testCase := range cases {
|
|
t.Run(name, func(t *testing.T) {
|
|
if _, _, ok := utilisation(testCase.previous, testCase.current); ok {
|
|
t.Fatal("expected the delta to be rejected as unusable")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseMeminfoConvertsKibibytes(t *testing.T) {
|
|
memory, err := parseMeminfo(readFixture(t, "proc-healthy", "meminfo"))
|
|
if err != nil {
|
|
t.Fatalf("parseMeminfo returned error: %v", err)
|
|
}
|
|
if memory.TotalBytes != 32819484*1024 {
|
|
t.Fatalf("total = %d, want %d", memory.TotalBytes, 32819484*1024)
|
|
}
|
|
if memory.AvailableBytes != 24680240*1024 {
|
|
t.Fatalf("available = %d", memory.AvailableBytes)
|
|
}
|
|
if memory.SwapTotalBytes != 8388604*1024 || memory.SwapUsedBytes != (8388604-8000000)*1024 {
|
|
t.Fatalf("unexpected swap: %+v", memory)
|
|
}
|
|
}
|
|
|
|
func TestParseMeminfoFallsBackWhenMemAvailableIsAbsent(t *testing.T) {
|
|
memory, err := parseMeminfo(readFixture(t, "proc-messy", "meminfo"))
|
|
if err != nil {
|
|
t.Fatalf("parseMeminfo returned error: %v", err)
|
|
}
|
|
want := uint64(123456+23456+2000000+100000) * 1024
|
|
if memory.AvailableBytes != want {
|
|
t.Fatalf("available = %d, want %d", memory.AvailableBytes, want)
|
|
}
|
|
if memory.SwapTotalBytes != 0 || memory.SwapUsedBytes != 0 {
|
|
t.Fatalf("swapless host reported swap: %+v", memory)
|
|
}
|
|
}
|
|
|
|
func TestParseMeminfoRequiresMemTotal(t *testing.T) {
|
|
if _, err := parseMeminfo([]byte("MemFree: 100 kB\n")); err == nil {
|
|
t.Fatal("expected an error without MemTotal")
|
|
}
|
|
}
|
|
|
|
func TestParseLoadAverage(t *testing.T) {
|
|
load, err := parseLoadAverage(readFixture(t, "proc-healthy", "loadavg"))
|
|
if err != nil {
|
|
t.Fatalf("parseLoadAverage returned error: %v", err)
|
|
}
|
|
if load.One != 1.52 || load.Five != 2.08 || load.Fifteen != 2.35 {
|
|
t.Fatalf("unexpected load: %+v", load)
|
|
}
|
|
for _, malformed := range []string{"not-a-load\n", "1.0 2.0\n", "-1 2 3\n", ""} {
|
|
if _, err := parseLoadAverage([]byte(malformed)); err == nil {
|
|
t.Fatalf("expected an error for %q", malformed)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseUptime(t *testing.T) {
|
|
seconds, err := parseUptime(readFixture(t, "proc-healthy", "uptime"))
|
|
if err != nil {
|
|
t.Fatalf("parseUptime returned error: %v", err)
|
|
}
|
|
if seconds != 351282.31 {
|
|
t.Fatalf("uptime = %v", seconds)
|
|
}
|
|
for _, malformed := range []string{"", "nonsense\n", "-5 10\n", "999999999999 1\n"} {
|
|
if _, err := parseUptime([]byte(malformed)); err == nil {
|
|
t.Fatalf("expected an error for %q", malformed)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseNetDevHandlesMissingSpaceAfterColon(t *testing.T) {
|
|
interfaces := parseNetDev(readFixture(t, "proc-healthy", "net", "dev"))
|
|
byName := map[string]uint64{}
|
|
for _, item := range interfaces {
|
|
byName[item.Name] = item.RxBytes
|
|
}
|
|
// eth0's receive counter runs straight into the colon in the fixture.
|
|
if byName["eth0"] != 18446744073709551615 {
|
|
t.Fatalf("eth0 rx = %d", byName["eth0"])
|
|
}
|
|
if _, present := byName["wlan0"]; present {
|
|
t.Fatal("a row with a non-numeric counter must be dropped, not zeroed")
|
|
}
|
|
if _, present := byName["tap0"]; present {
|
|
t.Fatal("a truncated row must be dropped")
|
|
}
|
|
if len(interfaces) != 3 {
|
|
t.Fatalf("interfaces = %d, want lo, eth0 and br0", len(interfaces))
|
|
}
|
|
for _, item := range interfaces {
|
|
if item.Name == "eth0" && (item.RxErrors != 12 || item.RxDrops != 3 || item.TxErrors != 1 || item.TxDrops != 7) {
|
|
t.Fatalf("unexpected eth0 error counters: %+v", item)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseMountsUnescapesOctalSequences(t *testing.T) {
|
|
entries := parseMounts(readFixture(t, "proc-healthy", "mounts"))
|
|
found := false
|
|
for _, entry := range entries {
|
|
if entry.mount == "/mnt/disks/Media Backup" {
|
|
found = true
|
|
if entry.fsType != "btrfs" {
|
|
t.Fatalf("unexpected fs type: %q", entry.fsType)
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("expected the escaped mount point to be decoded")
|
|
}
|
|
if len(entries) != 12 {
|
|
t.Fatalf("entries = %d, want 12", len(entries))
|
|
}
|
|
}
|