From b34691777d7a5b851f55a3d1085fdb8df9a07f32 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 4 Oct 2026 16:21:28 +0200 Subject: [PATCH 1/2] Add a test for the task that ReadToEnd holds while it calls back ReadToEnd marks its task as done before it calls then. Its callers enqueue their follow-up work onto the UI thread from then, so for a moment in between, no task is busy. Co-Authored-By: Claude Opus 5.5 (1M context) --- pkg/tasks/tasks_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/tasks/tasks_test.go b/pkg/tasks/tasks_test.go index c50a54cac..2c2806b8e 100644 --- a/pkg/tasks/tasks_test.go +++ b/pkg/tasks/tasks_test.go @@ -522,3 +522,28 @@ func TestQueuedReadRequestsAreAnsweredWhenTheTaskStops(t *testing.T) { assert.EqualValues(t, 2, answered.Load()) } + +// 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 +// in between, and an integration test would carry on before the follow-up work is done. +func TestReadToEndHoldsItsTaskUntilThenReturns(t *testing.T) { + noop := func() {} + task := gocui.NewFakeTask() + + manager := NewViewBufferManager( + utils.NewDummyLog(), bytes.NewBuffer(nil), noop, noop, noop, noop, noop, noop, + func() gocui.Task { return task }, + func(f func()) error { f(); return nil }, + ) + + // With no command task serving read requests, ReadToEnd answers right away. + var statusDuringThen gocui.TaskStatus + manager.ReadToEnd(func() { statusDuringThen = task.Status() }) + + /* EXPECTED: + assert.Equal(t, gocui.TaskStatusBusy, statusDuringThen) + ACTUAL: */ + assert.Equal(t, gocui.TaskStatusDone, statusDuringThen) + assert.Equal(t, gocui.TaskStatusDone, task.Status()) +} From 8588cd0b4e611f8d0c47552919f149897df27d52 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 4 Oct 2026 16:24:53 +0200 Subject: [PATCH 2/2] Keep ReadToEnd's task busy until its callback has returned The integration test filter_and_search/search_a_long_diff fails now and then on CI with "Expected search prompt to be focused". Pressing "/" in the focused main view reads the rest of the diff with ReadToEnd. Its callback opens the prompt by enqueueing the work onto the UI thread. ReadToEnd marks its task as done before calling the callback, so for a moment no task is busy. If the idle wait of the test driver wakes up in that moment, the test checks for the prompt before it is open. Adding a sleep between the two calls makes the test fail every time. The same gap exists when going to the bottom of a view, since that callback enqueues its work onto the UI thread too. Call the callback first, and mark the task as done after it returns. The work that the callback enqueues holds a task of its own from the moment it is enqueued, so lazygit no longer counts as idle in between. Co-Authored-By: Claude Opus 5.5 (1M context) --- pkg/tasks/tasks.go | 6 ++++-- pkg/tasks/tasks_test.go | 3 --- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/tasks/tasks.go b/pkg/tasks/tasks.go index deaeee042..af935b085 100644 --- a/pkg/tasks/tasks.go +++ b/pkg/tasks/tasks.go @@ -213,13 +213,15 @@ func (self *ViewBufferManager) StartLoading() { func (self *ViewBufferManager) ReadToEnd(then func()) { // The reading happens on the task's own goroutine, and the caller hears about - // it through then, so lazygit must not count as idle in between. + // it through then, so lazygit must not count as idle in between. The task is + // done only once then has returned, because then typically hands its work to + // the UI thread, and that work counts as busy only once it is enqueued. task := self.newGocuiTask() answered := func() { - task.Done() if then != nil { then() } + task.Done() } request := LinesToRead{Total: -1, InitialRefreshAfter: -1, Then: answered} diff --git a/pkg/tasks/tasks_test.go b/pkg/tasks/tasks_test.go index 2c2806b8e..6cc1cf9d6 100644 --- a/pkg/tasks/tasks_test.go +++ b/pkg/tasks/tasks_test.go @@ -541,9 +541,6 @@ func TestReadToEndHoldsItsTaskUntilThenReturns(t *testing.T) { var statusDuringThen gocui.TaskStatus manager.ReadToEnd(func() { statusDuringThen = task.Status() }) - /* EXPECTED: assert.Equal(t, gocui.TaskStatusBusy, statusDuringThen) - ACTUAL: */ - assert.Equal(t, gocui.TaskStatusDone, statusDuringThen) assert.Equal(t, gocui.TaskStatusDone, task.Status()) }