mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-24 10:13:41 -05:00
Quitting with confirmOnQuit set hung for three seconds and printed "cannot kill child process", but only with a clean working tree. Closing the confirmation pops the context before running its handler, so the files panel is re-focused and re-renders the main view, and only then does the handler return ErrQuit. With no changed files that render is a string task, whose whole body is one hop to the UI thread — a hop that is never served, because the handler's ErrQuit has meanwhile brought the main loop down. The task can't finish, so the ViewBufferManager.Close that follows waits for it until it times out. (With changed files it's a command task instead, and every blocking point in one of those selects on the stop channel, so Close gets through.) A wait for the UI thread now ends when the loop does. That also covers the command task's own hops, which are stopped only in between them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
44 lines
1.1 KiB
Go
44 lines
1.1 KiB
Go
package gocui
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// errStillWaiting stands in for the result of a wait that hasn't produced one.
|
|
var errStillWaiting = errors.New("still waiting")
|
|
|
|
// resultOrTimeout reports what a wait returned, or errStillWaiting if it hasn't
|
|
// returned by the time we give up on it.
|
|
func resultOrTimeout(result chan error) error {
|
|
select {
|
|
case err := <-result:
|
|
return err
|
|
case <-time.After(time.Second):
|
|
return errStillWaiting
|
|
}
|
|
}
|
|
|
|
// A worker waiting for the UI thread must not be left parked there once the
|
|
// main loop has stopped: nothing will ever run its callback, and the shutdown
|
|
// that follows blocks until such workers have finished (see
|
|
// tasks.ViewBufferManager.Close).
|
|
func TestOnUIThreadAndWaitGivesUpWhenTheLoopExits(t *testing.T) {
|
|
g := newTestGui(t)
|
|
|
|
// Closing this is what MainLoop returning does. From here on nothing
|
|
// dequeues user events, so the callback below is never going to run.
|
|
close(g.loopExited)
|
|
|
|
result := make(chan error, 1)
|
|
go func() {
|
|
result <- g.OnUIThreadAndWait(func() {})
|
|
}()
|
|
|
|
err := resultOrTimeout(result)
|
|
assert.ErrorIs(t, err, ErrLoopExited)
|
|
}
|