fix(bonfire): kill a live view on a signal only after it has started (IN-12, quick-261028-rqd)

This commit is contained in:
Jakub Zych
2026-10-28 20:37:30 +01:00
parent 6efb9a4997
commit f91d70d05e
4 changed files with 147 additions and 20 deletions

View File

@@ -769,3 +769,86 @@ func TestErrorPrintsAboveView(t *testing.T) {
}
}
}
// TestSignalRelayWaitsForStart (IN-12): the signal relay kills the program
// only once it has started (its Init closed ready) or its Run has ended,
// so a signal during the view's start-up still restores the cursor; then
// it waits for the program, unregisters and raises the signal again.
func TestSignalRelayWaitsForStart(t *testing.T) {
type relayRun struct {
sigs chan os.Signal
quit, ready, finished chan struct{}
killed, unregistered chan struct{}
raised chan os.Signal
done chan struct{}
}
start := func() *relayRun {
r := &relayRun{
sigs: make(chan os.Signal, 1), quit: make(chan struct{}),
ready: make(chan struct{}), finished: make(chan struct{}),
killed: make(chan struct{}, 1), unregistered: make(chan struct{}, 1),
raised: make(chan os.Signal, 2), done: make(chan struct{}),
}
go func() {
defer close(r.done)
relaySignals(r.sigs, r.quit, r.ready, r.finished,
func() { r.killed <- struct{}{} },
func() { r.unregistered <- struct{}{} },
func(sig os.Signal) { r.raised <- sig })
}()
return r
}
wait := func(t *testing.T, ch <-chan struct{}, what string) {
t.Helper()
select {
case <-ch:
case <-time.After(5 * time.Second):
t.Fatalf("%s did not happen", what)
}
}
t.Run("signal before start", func(t *testing.T) {
r := start()
r.sigs <- os.Interrupt
select {
case <-r.killed:
t.Fatal("killed before the program started")
case <-time.After(100 * time.Millisecond):
}
close(r.ready)
wait(t, r.killed, "kill after ready")
select {
case sig := <-r.raised:
t.Fatalf("raised %v before the program finished", sig)
case <-time.After(50 * time.Millisecond):
}
close(r.finished)
wait(t, r.done, "relay end")
wait(t, r.unregistered, "unregister")
if len(r.raised) != 1 {
t.Fatalf("raised %d signals, want 1", len(r.raised))
}
if sig := <-r.raised; sig != os.Interrupt {
t.Errorf("raised %v, want %v", sig, os.Interrupt)
}
})
t.Run("program ended without starting", func(t *testing.T) {
r := start()
r.sigs <- os.Interrupt
close(r.finished)
wait(t, r.done, "relay end")
if len(r.raised) != 1 {
t.Fatalf("raised %d signals, want 1", len(r.raised))
}
})
t.Run("quit", func(t *testing.T) {
r := start()
close(r.quit)
wait(t, r.done, "relay end")
if len(r.killed) != 0 || len(r.raised) != 0 {
t.Errorf("quit killed %d times and raised %d signals", len(r.killed), len(r.raised))
}
})
}