Add systemd resource budgets with TOML profiles

This commit is contained in:
ms2k 2026-10-05 19:19:05 +09:00
commit be8749a384

119
internal/budget/app_test.go Normal file
View file

@ -0,0 +1,119 @@
package budget
import (
"errors"
"io"
"reflect"
"strings"
"testing"
)
func TestApply(t *testing.T) {
f := newFake()
f.units = []unit{
{Name: "app-browser-123.scope", Active: "active"},
{Name: "app-org.example.Terminal.service", Active: "active"},
{Name: "containerd.service", Active: "active"},
{Name: "session-ui.service", Active: "active"},
{Name: "app-closed-123.scope", Active: "inactive"},
}
events := []string{}
f.events = &events
a := application{testConfig(), homePaths(t.TempDir()), f, func(args ...string) error {
events = append(events, strings.Join(args, " "))
return nil
}, io.Discard}
if err := a.apply(); err != nil {
t.Fatal(err)
}
want := []string{"daemon-reload", "set app-browser-123.scope", "set app-org.example.Terminal.service", "set containerd.service", "enable budget.service", "restart budget.service"}
if !reflect.DeepEqual(events, want) {
t.Fatalf("got %v, want %v", events, want)
}
if values(f.sets["app-browser-123.scope"])["MemoryMax"].Value() != uint64(8589934592) {
t.Fatal("default ceiling missing")
}
if len(f.sets) != 3 {
t.Fatal("session infrastructure or inactive apps were changed")
}
f.setErr = errors.New("set failed")
events = nil
if err := a.apply(); err != f.setErr {
t.Fatal(err)
}
if len(events) != 2 {
t.Fatal("continued after failure", events)
}
}
func TestSharedOverride(t *testing.T) {
f := newFake()
f.units = []unit{
{Name: "[email protected]", Active: "active"},
{Name: `app-editor\x2duri\[email protected]`, Active: "active"},
{Name: "budget-d123abc.scope", Active: "active"},
{Name: "app-other-123.scope", Active: "active"},
{Name: "[email protected]", Active: "inactive"},
}
f.groups = map[string]string{
"app-editor.slice": "/app.slice/app-editor.slice",
"[email protected]": "/app.slice/app-editor.slice/[email protected]",
`app-editor\x2duri\[email protected]`: "/app.slice/app-editor.slice/uri.service",
"budget-d123abc.scope": "/app.slice/app-editor.slice/budget-d123abc.scope",
"app-other-123.scope": "/app.slice/app-editor.slice-neighbor/app-other-123.scope",
}
a := application{config: testConfig(), ctl: f, out: io.Discard}
if err := a.override("compute", "editor"); err != nil {
t.Fatal(err)
}
want := []string{"app-editor.slice", "[email protected]", `app-editor\x2duri\[email protected]`, "budget-d123abc.scope"}
if !reflect.DeepEqual(keys(f.sets), want) {
t.Fatalf("got %v, want %v", keys(f.sets), want)
}
for name, props := range f.sets {
if values(props)["MemoryMax"].Value() != uint64(17179869184) {
t.Fatal(name, props)
}
}
if err := a.override("default", "app-editor.slice"); err != nil {
t.Fatal(err)
}
for name, props := range f.sets {
if values(props)["MemoryMax"].Value() != uint64(8589934592) {
t.Fatal(name, props)
}
}
}
func TestOverrideTargets(t *testing.T) {
f := newFake()
f.units = []unit{{Name: "app-flatpak-org.example.Download-123.scope", Active: "active"}}
a := application{config: testConfig(), ctl: f, out: io.Discard}
if err := a.override("unrestricted", "org.example.Download"); err != nil {
t.Fatal(err)
}
if len(f.sets) != 1 {
t.Fatal(f.sets)
}
if err := a.override("default", "no-such-app"); err == nil {
t.Fatal("missing app accepted")
}
if err := a.override("missing", "org.example.Download"); err == nil {
t.Fatal("missing profile accepted")
}
}
func TestAppOutput(t *testing.T) {
want := errors.New("output failed")
f := newFake()
a := application{
config: testConfig(), paths: homePaths(t.TempDir()), ctl: f,
run: func(...string) error { return nil }, out: failWriter{want},
}
if err := a.apply(); !errors.Is(err, want) {
t.Fatal(err)
}
if err := a.override("default", "app-example.service"); !errors.Is(err, want) {
t.Fatal(err)
}
}