mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-11-06 21:08:56 -04:00
Add a test for a read to the end that a re-render interrupts
Some actions read the content of the main view to the end before they act on it. For example, the jump-to-file menu lists the files of the diff. If the task reading the content is replaced by a re-render while it reads, these actions act on the part that has been read so far, so the menu shows only some of the files. A background fetch re-renders the main view, so this happens now and then. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4c2ce238a9
commit
ebd53371eb
@@ -881,6 +881,73 @@ func TestQueuedReadRequestsAreAnsweredWhenTheTaskStops(t *testing.T) {
|
||||
assert.EqualValues(t, 2, answered.Load())
|
||||
}
|
||||
|
||||
// A caller of ReadToEnd acts on the whole of what the view shows once it is answered,
|
||||
// such as listing the files of the diff in a menu. If a re-render replaces the task
|
||||
// that is reading the content, the content is the re-render's, so whoever is waiting
|
||||
// is answered once that has been read to the end, whether the replaced task was
|
||||
// already reading to the end for them or hadn't got to their request yet.
|
||||
func TestReadToEndWaitingWhenARerenderReplacesTheTask(t *testing.T) {
|
||||
writer := bytes.NewBuffer(nil)
|
||||
linesWritten := func() int { return strings.Count(writer.String(), "\n") }
|
||||
firstPaint := make(chan struct{}, 1)
|
||||
|
||||
manager := NewViewBufferManager(
|
||||
utils.NewDummyLog(),
|
||||
writer,
|
||||
func() {}, // beforeStart
|
||||
func() {}, // refreshView
|
||||
func() {}, // onEndOfInput
|
||||
func() {}, // resetOrigin
|
||||
writer.Reset,
|
||||
func() { firstPaint <- struct{}{} },
|
||||
func() gocui.Task { return gocui.NewFakeTask() },
|
||||
// no UI thread in the test; run the view mutations inline
|
||||
func(f func()) error { f(); return nil },
|
||||
)
|
||||
defer manager.Close()
|
||||
|
||||
startTask := func(reader io.Reader) {
|
||||
start := func() (Cmd, io.Reader) {
|
||||
// not actually starting this because it's not necessary
|
||||
return ExecCmd{Cmd: exec.Command("blah")}, reader
|
||||
}
|
||||
linesToRead := LinesToRead{Total: 1, InitialRefreshAfter: 1}
|
||||
_ = manager.NewTask(manager.NewCmdTask(start, nil, linesToRead, nil), "cmd")
|
||||
}
|
||||
|
||||
var mutex sync.Mutex
|
||||
linesWrittenWhenAnswered := []int{}
|
||||
var answered sync.WaitGroup
|
||||
readToEnd := func() {
|
||||
answered.Add(1)
|
||||
manager.ReadToEnd(func() {
|
||||
mutex.Lock()
|
||||
linesWrittenWhenAnswered = append(linesWrittenWhenAnswered, linesWritten())
|
||||
mutex.Unlock()
|
||||
answered.Done()
|
||||
})
|
||||
}
|
||||
|
||||
// The render reads the line it was asked for, and stalls while reading to the end
|
||||
// for the first caller. The second one's request waits behind it.
|
||||
stalled := BlockingLineReader{linesToYield: 3, blocked: make(chan struct{}), unblock: make(chan struct{})}
|
||||
defer close(stalled.unblock)
|
||||
startTask(&stalled)
|
||||
<-firstPaint
|
||||
readToEnd()
|
||||
<-stalled.blocked
|
||||
readToEnd()
|
||||
|
||||
// A re-render of the same content, as a refresh does.
|
||||
startTask(&BlankLineReader{totalLinesToYield: 50})
|
||||
answered.Wait()
|
||||
|
||||
/* EXPECTED:
|
||||
assert.Equal(t, []int{50, 50}, linesWrittenWhenAnswered)
|
||||
ACTUAL: */
|
||||
assert.Equal(t, []int{3, 3}, linesWrittenWhenAnswered)
|
||||
}
|
||||
|
||||
// The callers of ReadToEnd hand their follow-up work to the UI thread from then, and
|
||||
// that work holds a task of its own from the moment it is enqueued. If the task that
|
||||
// ReadToEnd holds were done before then runs, lazygit would count as idle for a moment
|
||||
|
||||
Reference in New Issue
Block a user