package spe import ( "testing" "time" ) // decodeCSV marks the client connected on EVERY frame it parses, and the poll // goroutine can be mid-read while PowerOff runs. So the frame from a second ago // landed after PowerOff had marked the amp offline and put it straight back — // the console kept showing the amplifier on until the operator pressed OFF a // second time. func TestFrameInFlightDoesNotResurrectASwitchedOffAmp(t *testing.T) { c := &Client{} const frame = ",13K,O,R,A,1,05,1b,0r,M,0000, 0.00, 0.00, 1.3, 0.0, 26,000,000,N,N," // Normal running: a frame marks it connected and reads OPERATE. c.decodeCSV(frame) if !c.GetStatus().Connected || !c.GetStatus().Operate { t.Fatal("a status frame should mark the amp connected") } // The operator presses OFF. This is what PowerOff does, in order. c.statusMu.Lock() c.offUntil = time.Now().Add(3 * time.Second) c.statusMu.Unlock() c.setErr(errSwitchedOff{}) // A frame that was already on the wire arrives now. c.decodeCSV(frame) if c.GetStatus().Connected { t.Error("a frame from before the switch-off put the amp back on — this is the double-click on OFF") } } // An amp switched back on at its own front panel must reappear without OpsLog // being told, so the suppression has to expire rather than latch. func TestSuppressionExpires(t *testing.T) { c := &Client{} c.statusMu.Lock() c.offUntil = time.Now().Add(-time.Second) // window already past c.statusMu.Unlock() c.setErr(errSwitchedOff{}) c.decodeCSV(",13K,O,R,A,1,05,1b,0r,M,0000, 0.00, 0.00, 1.3, 0.0, 26,000,000,N,N,") if !c.GetStatus().Connected { t.Error("an amp answering after the window must come back on its own") } } type errSwitchedOff struct{} func (errSwitchedOff) Error() string { return "switched off" }