feat: add tray control, priority restoration and session logging
This commit is contained in:
1 parent
4f8f6218ad
commit
4880aa46dc
25 files changed
+3072
-676
No files matched your search
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user