feat: add tray control, priority restoration and session logging

This commit is contained in:
lzrdblzzrd committed 2026-10-04 19:00:59 +03:00
1 parent 4f8f6218ad
commit 4880aa46dc
25 files changed
+3072 -676

No files matched your search

+204
View File
@@ -0,0 +1,204 @@
//go:build windows
package main
import (
"errors"
"fmt"
"sort"
"syscall"
"testing"
"time"
)
// Only OS boundaries are simulated. The real production controller is exercised.
type fakeProcess struct {
info procInfo
alive, readable, writable bool
failSet, failRead bool
coerced uint32
writes int
}
type fakeBackend struct {
procs map[processKey]*fakeProcess
handles map[syscall.Handle]*fakeProcess
next syscall.Handle
snapshotErr bool
onRead func(*fakeProcess)
snapshots int
}
func newFake() *fakeBackend {
return &fakeBackend{procs: make(map[processKey]*fakeProcess), handles: make(map[syscall.Handle]*fakeProcess)}
}
func (f *fakeBackend) add(pid uint32, rss uint64, prio uint32) *fakeProcess {
p := &fakeProcess{info: procInfo{PID: pid, Key: processKey{pid, uint64(pid)}, Name: fmt.Sprintf("Process%d.exe", pid), RSS: rss, Prio: prio}, alive: true, readable: true, writable: true}
f.procs[p.info.Key] = p
return p
}
func (f *fakeBackend) Snapshot() (processSnapshot, error) {
f.snapshots++
if f.snapshotErr {
return processSnapshot{}, errors.New("snapshot unavailable")
}
out := processSnapshot{PIDs: make(map[uint32]bool)}
keys := make([]processKey, 0, len(f.procs))
for key := range f.procs {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool { return keys[i].PID < keys[j].PID })
for _, key := range keys {
p := f.procs[key]
if !p.alive {
continue
}
out.PIDs[key.PID] = true
if !p.readable {
continue
}
info := p.info
f.next++
info.Handle = f.next
info.Waitable = true
f.handles[info.Handle] = p
out.Processes = append(out.Processes, info)
}
return out, nil
}
func (f *fakeBackend) Alive(p *procInfo) (bool, error) {
v, ok := f.handles[p.Handle]
if !ok {
return false, errors.New("invalid handle")
}
return v.alive, nil
}
func (f *fakeBackend) Priority(p *procInfo) (uint32, error) {
v, ok := f.handles[p.Handle]
if !ok || !v.alive || v.failRead {
return 0, errors.New("priority unavailable")
}
if f.onRead != nil {
f.onRead(v)
}
return v.info.Prio, nil
}
func (f *fakeBackend) SetPriority(p *procInfo, target uint32) error {
v, ok := f.handles[p.Handle]
if !ok || !v.alive || !v.writable || v.failSet {
return errors.New("access denied")
}
v.writes++
v.info.Prio = target
if v.coerced != 0 {
v.info.Prio = v.coerced
}
return nil
}
func (f *fakeBackend) Close(p *procInfo) {
if p.Handle == 0 {
panic("double close")
}
if _, ok := f.handles[p.Handle]; !ok {
panic("invalid close")
}
delete(f.handles, p.Handle)
p.Handle = 0
}
func testController(f *fakeBackend) *controller {
c := newController(f, 100, 200, false, time.Minute)
c.now = func() time.Time { return time.Unix(1000, 0) }
return c
}
func assertPriority(t *testing.T, p *fakeProcess, want uint32) {
t.Helper()
if p.info.Prio != want {
t.Fatalf("%s: got %s; want %s", p.info.Name, prioName(p.info.Prio), prioName(want))
}
}
func (f *fakeBackend) child(pid, parent uint32, created uint64) *fakeProcess {
p := f.add(pid, 50, PriorityClassIdle)
delete(f.procs, p.info.Key)
p.info.Key.Created = created
p.info.ParentPID = parent
f.procs[p.info.Key] = p
return p
}
func TestControllerInheritedIdleDescendantsRecover(t *testing.T) {
f := newFake()
game := f.add(10, 250, PriorityClassNormal)
parent := f.add(20, 50, PriorityClassBelowNormal)
c := testController(f)
c.Step()
child := f.child(30, 20, 116444736000000000+1001*10000000)
grandchild := f.child(40, 30, 116444736000000000+1002*10000000)
oldIdle := f.child(50, 20, 116444736000000000+999*10000000)
unrelated := f.child(60, 0, 116444736000000000+1001*10000000)
c.now = func() time.Time { return time.Unix(1003, 0) }
c.Step()
game.alive = false
c.Step()
assertPriority(t, parent, PriorityClassBelowNormal)
assertPriority(t, child, PriorityClassBelowNormal)
assertPriority(t, grandchild, PriorityClassBelowNormal)
assertPriority(t, oldIdle, PriorityClassIdle)
assertPriority(t, unrelated, PriorityClassIdle)
c.Shutdown()
assertClosed(t, f)
}
func assertClosed(t *testing.T, f *fakeBackend) {
t.Helper()
if len(f.handles) != 0 {
t.Fatalf("leaked %d owned handles", len(f.handles))
}
}
func TestControllerShutdownRestoresHeavyAndBackground(t *testing.T) {
f := newFake()
game := f.add(10, 250, PriorityClassNormal)
background := f.add(20, 50, PriorityClassBelowNormal)
c := testController(f)
c.Step()
assertPriority(t, game, PriorityClassHigh)
assertPriority(t, background, PriorityClassIdle)
report := c.Shutdown()
if len(report.Errors) != 0 {
t.Fatal(report.Errors)
}
assertPriority(t, game, PriorityClassNormal)
assertPriority(t, background, PriorityClassBelowNormal)
assertClosed(t, f)
}
func TestControllerGameExitRestoresBackground(t *testing.T) {
f := newFake()
game := f.add(10, 250, PriorityClassNormal)
background := f.add(20, 50, PriorityClassBelowNormal)
c := testController(f)
c.Step()
game.alive = false
c.Step()
assertPriority(t, background, PriorityClassBelowNormal)
c.Shutdown()
assertClosed(t, f)
}
func TestControllerRestoreFailureDoesNotBlockOtherProcesses(t *testing.T) {
f := newFake()
game := f.add(10, 250, PriorityClassNormal)
blocked := f.add(20, 50, PriorityClassNormal)
healthy := f.add(30, 50, PriorityClassBelowNormal)
c := testController(f)
c.Step()
game.alive = false
blocked.failSet = true
newHeavy := f.add(40, 150, PriorityClassNormal)
c.Step()
assertPriority(t, healthy, PriorityClassBelowNormal)
assertPriority(t, newHeavy, PriorityClassHigh)
blocked.failSet = false
r := c.Shutdown()
if r.Unrestored != 1 {
t.Fatal("blocked restoration incorrectly reported successful", r)
}
assertPriority(t, blocked, PriorityClassIdle)
assertPriority(t, newHeavy, PriorityClassNormal)
assertClosed(t, f)
}