Files
mesh-catalog/modules/disk-load/cmd/disk-load/tools_test.go
T
jochen 6fdf5293eb
mesh/merge-gate pass: builds new: modules/disk-load, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
Add disk-load: no tool said a machine's iowait, disk load or who does the I/O (hq issue 312)
2026-10-08 10:47:41 +02:00

201 lines
6.6 KiB
Go

package main
import (
"encoding/json"
"os"
"sort"
"strings"
"testing"
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
)
func tool(t *testing.T, m *Machine, name string) stdio.Tool {
t.Helper()
for _, tl := range Tools(m) {
if tl.Name == name {
return tl
}
}
t.Fatalf("no tool %s", name)
return stdio.Tool{}
}
func TestTheManifestNamesExactlyTheToolsTheBundleServesAndNoMachine(t *testing.T) {
raw, err := os.ReadFile("../../module.json")
if err != nil {
t.Fatal(err)
}
var m struct {
Tools []string `json:"tools"`
Resources []any `json:"resources"`
}
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
var served []string
for _, tl := range Tools(newFake(t).machine()) {
served = append(served, tl.Name)
if !strings.HasSuffix(tl.Description, "(r)") {
t.Errorf("%s only reads and does not say so", tl.Name)
}
}
sort.Strings(served)
sort.Strings(m.Tools)
if strings.Join(served, ",") != strings.Join(m.Tools, ",") {
t.Fatalf("served %v, listed %v", served, m.Tools)
}
if len(m.Resources) != 0 {
t.Errorf("the module only reads, and declares %d resources", len(m.Resources))
}
for _, banned := range []string{"g14", "novox", "ace", "shanks", "jochen", "/home/"} {
if strings.Contains(string(raw), banned) {
t.Errorf("the manifest says %q", banned)
}
}
}
func TestDiskLoadSamplesTwoSecondsAndNamesFiveProcessesByDefault(t *testing.T) {
f := aServerUnderWrites(t)
out, err := tool(t, f.machine(), "disk_load").Run(map[string]any{})
if err != nil {
t.Fatal(err)
}
l := out.(Load)
if f.waited != 2*time.Second || l.WindowS != 2 || len(l.Processes) != 3 || len(l.Units) != 3 || len(l.Filesystems) != 3 {
t.Errorf("waited %s, %+v", f.waited, l)
}
}
func TestTheWindowIsAWholeNumberOfSecondsHeldAtTen(t *testing.T) {
f := aServerUnderWrites(t)
load := tool(t, f.machine(), "disk_load")
for _, bad := range []any{0.0, -1.0, 1.5, "soon", true} {
if _, err := load.Run(map[string]any{"seconds": bad}); err == nil {
t.Errorf("seconds %v was accepted", bad)
}
}
if _, err := load.Run(map[string]any{"seconds": 30.0}); err != nil || f.waited != 10*time.Second {
t.Errorf("30 s was not held at 10: waited %s, %v", f.waited, err)
}
if _, err := load.Run(map[string]any{"processes": -1.0}); err == nil {
t.Error("processes -1 was accepted")
}
}
func TestDiskTopRanksUnitsFromTheirCgroupsCountingEachByteOnce(t *testing.T) {
f := aServerUnderWrites(t)
out, err := tool(t, f.machine(), "disk_top").Run(map[string]any{})
if err != nil {
t.Fatal(err)
}
if out.(map[string]any)["by"] != "unit" || f.waited != 2*time.Second {
t.Errorf("the default: %+v, waited %s", out, f.waited)
}
got := out.(map[string]any)["top"].([]Process)
want := []Process{
{Unit: "docker-" + store + ".scope", Container: "store-check", ReadMiBs: 5, WriteMiBs: 50, ReadMiB: 10, WriteMiB: 100, ReadIOPS: 100, WriteIOPS: 500},
{Unit: "editor.scope", WriteMiBs: 2.5, WriteMiB: 5, WriteIOPS: 25},
{Unit: "systemd-journald.service", WriteMiBs: 1, WriteMiB: 2, WriteIOPS: 10},
}
if g, w := must(json.Marshal(got)), must(json.Marshal(want)); string(g) != string(w) {
t.Errorf("by unit\n got %s\nwant %s", g, w)
}
}
func TestDiskTopByContainerNamesOnlyContainers(t *testing.T) {
out, err := tool(t, aServerUnderWrites(t).machine(), "disk_top").Run(map[string]any{"by": "container", "seconds": 1.0})
if err != nil {
t.Fatal(err)
}
got := out.(map[string]any)["top"].([]Process)
if len(got) != 1 || got[0].Container != "store-check" || got[0].Unit != "" || got[0].WriteMiBs != 100 {
t.Errorf("by container %+v", got)
}
}
func TestDiskTopByProcessSaysWhatItCouldNotRead(t *testing.T) {
f := aServerUnderWrites(t)
top := tool(t, f.machine(), "disk_top")
out, err := top.Run(map[string]any{"by": "process", "limit": 2.0})
if err != nil {
t.Fatal(err)
}
got := out.(map[string]any)["top"].([]Process)
if len(got) != 2 || got[0].PID != 100 || got[1].PID != 101 || got[1].ReadMiBs != 5 || got[1].Command != "postgres" {
t.Errorf("by process %+v", got)
}
if out.(map[string]any)["processes_unreadable"] != 1 {
t.Errorf("unreadable not said: %+v", out)
}
if _, err := top.Run(map[string]any{"by": "disk"}); err == nil {
t.Error("by=disk was accepted")
}
}
func TestAContainerTheRuntimeCannotNameIsSaidByItsShortID(t *testing.T) {
f := aServerUnderWrites(t)
delete(f.answers, "docker ps --no-trunc --format {{.ID}}\t{{.Names}}")
out, err := tool(t, f.machine(), "disk_top").Run(map[string]any{"by": "process"})
if err != nil {
t.Fatal(err)
}
if got := out.(map[string]any)["top"].([]Process); got[0].Container != store[:12] {
t.Errorf("%+v", got[0])
}
if n := len(f.calls); n != 1 {
t.Errorf("the runtime was asked %d times, not once", n)
}
}
func TestFilesystemsAreSaidOnceWithTheirOtherMounts(t *testing.T) {
out, err := tool(t, aServerUnderWrites(t).machine(), "disk_filesystems").Run(nil)
if err != nil {
t.Fatal(err)
}
fss := out.(map[string]any)["filesystems"].([]Filesystem)
var mounts []string
for _, fs := range fss {
mounts = append(mounts, fs.Mount)
}
if strings.Join(mounts, ",") != "/,/boot,/mnt/back up" {
t.Fatalf("mounts %v: proc, tmpfs and overlay are not stored data, and a second mount is not a second filesystem", mounts)
}
root := fss[0]
if strings.Join(root.AlsoAt, ",") != "/var/lib/docker" || root.Device != "dm-0" || root.Type != "ext4" ||
root.SizeGiB != 100 || root.UsedGiB != 90 || root.AvailGiB != 5 || root.UsedPct != 94.7 || root.InodesUsed != 40 {
t.Errorf("root %+v", root)
}
if fss[1].Device != "nvme0n1p1" || fss[1].UsedPct != 50 || fss[1].InodesUsed != 0 {
t.Errorf("boot %+v", fss[1])
}
if fss[2].Unanswered == "" {
t.Errorf("a filesystem statfs could not read says so: %+v", fss[2])
}
said := strings.Join(out.(map[string]any)["said"].([]string), "\n")
if !strings.Contains(said, "/ (ext4, /dev/mapper/root): 94.7 % used, 5.0 GiB free of 100.0 GiB — NEARLY FULL") ||
!strings.Contains(said, "/boot (vfat, /dev/nvme0n1p1): 50.0 % used, 0.5 GiB free of 1.0 GiB\n") {
t.Errorf("said:\n%s", said)
}
}
func TestTheMountTablesEscapesAreUndone(t *testing.T) {
for in, want := range map[string]string{`/a\040b`: "/a b", `/tab\011x`: "/tab\tx", `/plain`: "/plain", `/end\04`: `/end\04`, `/back\134slash`: `/back\slash`} {
if got := unescape(in); got != want {
t.Errorf("%q: %q, not %q", in, got, want)
}
}
}
func TestDiskDevicesAnswersWithoutWaiting(t *testing.T) {
f := aServerUnderWrites(t)
out, err := tool(t, f.machine(), "disk_devices").Run(map[string]any{})
if err != nil {
t.Fatal(err)
}
if f.waited != 0 || len(out.(map[string]any)["devices"].([]DeviceInfo)) != 3 {
t.Errorf("waited %s, %+v", f.waited, out)
}
}