package buildcpu import ( "os" "path/filepath" "reflect" "testing" ) // The cap's words, a node's share, the unit quota and the threads a step is given, in one table (issue 1806). func TestCapQuotaAndThreads(t *testing.T) { for _, c := range []struct { name, setting, node string cores, slots int percent, quota int threads int refused bool }{ {name: "default for shanks", setting: Defaults, node: "shanks", cores: 24, slots: 3, percent: 50, quota: 1200, threads: 4}, {name: "default for ace", setting: Defaults, node: "ace", cores: 24, slots: 2, percent: 75, quota: 1800, threads: 9}, {name: "default for g14", setting: Defaults, node: "g14", cores: 16, slots: 1, percent: 50, quota: 800, threads: 8}, {name: "a node not named", setting: Defaults, node: "novox", cores: 8, slots: 1, percent: DefaultPercent, quota: 400, threads: 4}, {name: "percent sign and spaces", setting: " shanks = 25% ", node: "shanks", cores: 24, slots: 3, percent: 25, quota: 600, threads: 2}, {name: "one processor at least", setting: "tiny=10", node: "tiny", cores: 2, slots: 4, percent: 10, quota: 100, threads: 1}, {name: "zero slots counts as one", setting: "x=100", node: "x", cores: 4, slots: 0, percent: 100, quota: 400, threads: 4}, {name: "zero refused", setting: "shanks=0", node: "shanks", refused: true}, {name: "over a hundred refused", setting: "shanks=150", node: "shanks", refused: true}, {name: "no number refused", setting: "shanks", node: "shanks", refused: true}, {name: "twice refused", setting: "a=1,a=2", node: "a", refused: true}, } { t.Run(c.name, func(t *testing.T) { p, err := Of(c.setting, c.node) if c.refused { if err == nil { t.Fatalf("Of(%q) = %d, want refused", c.setting, p) } return } if err != nil { t.Fatal(err) } if p != c.percent { t.Fatalf("percent = %d, want %d", p, c.percent) } if q := Quota(p, c.cores); q != c.quota { t.Errorf("Quota = %d, want %d", q, c.quota) } if th := Threads(p, c.cores, c.slots); th != c.threads { t.Errorf("Threads = %d, want %d", th, c.threads) } }) } caps, _ := Parse("ace=75,shanks=50%") if got := Format(caps); got != "ace=75,shanks=50" { t.Errorf("Format = %q", got) } if got := WithEnv([]string{"A=1", "GOMAXPROCS=24"}, 3); !reflect.DeepEqual(got, []string{"A=1", "GOMAXPROCS=3", "UV_THREADPOOL_SIZE=3"}) { t.Errorf("WithEnv = %q", got) } } // What the kernel holds a process to is read from its cgroup and stat files, not assumed. func TestReadInForce(t *testing.T) { dir := t.TempDir() cg := filepath.Join(dir, "cg") unit := filepath.Join(cg, "system.slice", "mesh-build-go-runtime.service") if err := os.MkdirAll(unit, 0o755); err != nil { t.Fatal(err) } for name, v := range map[string]string{"cpu.idle": "1\n", "cpu.weight": "0\n", "cpu.max": "1200000 100000\n", "io.weight": "default 1\n"} { if err := os.WriteFile(filepath.Join(unit, name), []byte(v), 0o644); err != nil { t.Fatal(err) } } self := filepath.Join(dir, "cgroup") stat := filepath.Join(dir, "stat") _ = os.WriteFile(self, []byte("0::/system.slice/mesh-build-go-runtime.service\n"), 0o644) // A command with a space and a parenthesis in it, then nice 19 in field 19. _ = os.WriteFile(stat, []byte("42 (mesh (build) go) S 1 42 42 0 -1 4194560 1 0 0 0 0 0 0 0 39 19 8 0 1 2 3\n"), 0o644) cgroupRoot, procSelfCgroup, procSelfStat = cg, self, stat t.Cleanup(func() { cgroupRoot, procSelfCgroup, procSelfStat = "/sys/fs/cgroup", "/proc/self/cgroup", "/proc/self/stat" }) got := ReadInForce() want := InForce{Nice: 19, Cgroup: "/system.slice/mesh-build-go-runtime.service", CPUIdle: "1", CPUWeight: "0", CPUMax: "1200000 100000", Quota: 1200, IOWeight: "default 1"} if !reflect.DeepEqual(got, want) { t.Fatalf("ReadInForce = %+v\nwant %+v", got, want) } if q := quotaOf("max 100000"); q != 0 { t.Errorf("no quota read as %d", q) } }