mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-03-21 21:47:32 +02:00
Add busy count for integration tests
Integration tests need to be notified when Lazygit is idle so they can progress to the next assertion / user action.
This commit is contained in:
parent
631cf1e873
commit
6c4e7ee972
@ -79,7 +79,7 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
|
|||||||
if self.pauseBackgroundThreads {
|
if self.pauseBackgroundThreads {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_ = function()
|
self.gui.c.OnWorker(func() { _ = function() })
|
||||||
case <-stop:
|
case <-stop:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ func NewSuggestionsContext(
|
|||||||
c *ContextCommon,
|
c *ContextCommon,
|
||||||
) *SuggestionsContext {
|
) *SuggestionsContext {
|
||||||
state := &SuggestionsContextState{
|
state := &SuggestionsContextState{
|
||||||
AsyncHandler: tasks.NewAsyncHandler(),
|
AsyncHandler: tasks.NewAsyncHandler(c.OnWorker),
|
||||||
}
|
}
|
||||||
getModel := func() []*types.Suggestion {
|
getModel := func() []*types.Suggestion {
|
||||||
return state.Suggestions
|
return state.Suggestions
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/status"
|
"github.com/jesseduffield/lazygit/pkg/gui/status"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AppStatusHelper struct {
|
type AppStatusHelper struct {
|
||||||
@ -28,7 +27,7 @@ func (self *AppStatusHelper) Toast(message string) {
|
|||||||
|
|
||||||
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
||||||
func (self *AppStatusHelper) WithWaitingStatus(message string, f func() error) {
|
func (self *AppStatusHelper) WithWaitingStatus(message string, f func() error) {
|
||||||
go utils.Safe(func() {
|
self.c.OnWorker(func() {
|
||||||
self.statusMgr().WithWaitingStatus(message, func() {
|
self.statusMgr().WithWaitingStatus(message, func() {
|
||||||
self.renderAppStatus()
|
self.renderAppStatus()
|
||||||
|
|
||||||
@ -50,7 +49,7 @@ func (self *AppStatusHelper) GetStatusString() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *AppStatusHelper) renderAppStatus() {
|
func (self *AppStatusHelper) renderAppStatus() {
|
||||||
go utils.Safe(func() {
|
self.c.OnWorker(func() {
|
||||||
ticker := time.NewTicker(time.Millisecond * 50)
|
ticker := time.NewTicker(time.Millisecond * 50)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
|
@ -90,7 +90,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
func() {
|
func() {
|
||||||
if options.Mode == types.ASYNC {
|
if options.Mode == types.ASYNC {
|
||||||
go utils.Safe(f)
|
self.c.OnWorker(f)
|
||||||
} else {
|
} else {
|
||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
@ -206,7 +206,7 @@ func getModeName(mode types.RefreshMode) string {
|
|||||||
func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
|
func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
|
||||||
switch self.c.State().GetRepoState().GetStartupStage() {
|
switch self.c.State().GetRepoState().GetStartupStage() {
|
||||||
case types.INITIAL:
|
case types.INITIAL:
|
||||||
go utils.Safe(func() {
|
self.c.OnWorker(func() {
|
||||||
_ = self.refreshReflogCommits()
|
_ = self.refreshReflogCommits()
|
||||||
self.refreshBranches()
|
self.refreshBranches()
|
||||||
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
|
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
|
||||||
|
@ -816,7 +816,7 @@ func (self *LocalCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
|
|||||||
context := self.context()
|
context := self.context()
|
||||||
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
||||||
context.SetLimitCommits(false)
|
context.SetLimitCommits(false)
|
||||||
go utils.Safe(func() {
|
self.c.OnWorker(func() {
|
||||||
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
|
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
|
||||||
_ = self.c.Error(err)
|
_ = self.c.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SubCommitsController struct {
|
type SubCommitsController struct {
|
||||||
@ -60,7 +59,7 @@ func (self *SubCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
|
|||||||
context := self.context()
|
context := self.context()
|
||||||
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
||||||
context.SetLimitCommits(false)
|
context.SetLimitCommits(false)
|
||||||
go utils.Safe(func() {
|
self.c.OnWorker(func() {
|
||||||
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUB_COMMITS}}); err != nil {
|
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUB_COMMITS}}); err != nil {
|
||||||
_ = self.c.Error(err)
|
_ = self.c.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,10 @@ func (gui *Gui) WatchFilesForChanges() {
|
|||||||
}
|
}
|
||||||
// only refresh if we're not already
|
// only refresh if we're not already
|
||||||
if !gui.IsRefreshingFiles {
|
if !gui.IsRefreshingFiles {
|
||||||
_ = gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
gui.c.OnUIThread(func() error {
|
||||||
|
// TODO: find out if refresh needs to be run on the UI thread
|
||||||
|
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// watch for errors
|
// watch for errors
|
||||||
|
@ -130,6 +130,8 @@ type Gui struct {
|
|||||||
|
|
||||||
c *helpers.HelperCommon
|
c *helpers.HelperCommon
|
||||||
helpers *helpers.Helpers
|
helpers *helpers.Helpers
|
||||||
|
|
||||||
|
integrationTest integrationTypes.IntegrationTest
|
||||||
}
|
}
|
||||||
|
|
||||||
type StateAccessor struct {
|
type StateAccessor struct {
|
||||||
@ -472,6 +474,7 @@ func NewGui(
|
|||||||
func(message string, f func() error) { gui.helpers.AppStatus.WithWaitingStatus(message, f) },
|
func(message string, f func() error) { gui.helpers.AppStatus.WithWaitingStatus(message, f) },
|
||||||
func(message string) { gui.helpers.AppStatus.Toast(message) },
|
func(message string) { gui.helpers.AppStatus.Toast(message) },
|
||||||
func() string { return gui.Views.Confirmation.TextArea.GetContent() },
|
func() string { return gui.Views.Confirmation.TextArea.GetContent() },
|
||||||
|
func(f func()) { gui.c.OnWorker(f) },
|
||||||
)
|
)
|
||||||
|
|
||||||
guiCommon := &guiCommon{gui: gui, IPopupHandler: gui.PopupHandler}
|
guiCommon := &guiCommon{gui: gui, IPopupHandler: gui.PopupHandler}
|
||||||
@ -620,7 +623,8 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
|
|||||||
|
|
||||||
gui.c.Log.Info("starting main loop")
|
gui.c.Log.Info("starting main loop")
|
||||||
|
|
||||||
gui.handleTestMode(startArgs.IntegrationTest)
|
// setting here so we can use it in layout.go
|
||||||
|
gui.integrationTest = startArgs.IntegrationTest
|
||||||
|
|
||||||
return gui.g.MainLoop()
|
return gui.g.MainLoop()
|
||||||
}
|
}
|
||||||
@ -779,16 +783,15 @@ func (gui *Gui) showInitialPopups(tasks []func(chan struct{}) error) {
|
|||||||
gui.waitForIntro.Add(len(tasks))
|
gui.waitForIntro.Add(len(tasks))
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
|
|
||||||
go utils.Safe(func() {
|
gui.c.OnWorker(func() {
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
task := task
|
if err := task(done); err != nil {
|
||||||
go utils.Safe(func() {
|
_ = gui.c.Error(err)
|
||||||
if err := task(done); err != nil {
|
}
|
||||||
_ = gui.c.Error(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
gui.g.DecrementBusyCount()
|
||||||
<-done
|
<-done
|
||||||
|
gui.g.IncrementBusyCount()
|
||||||
gui.waitForIntro.Done()
|
gui.waitForIntro.Done()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -796,9 +799,10 @@ func (gui *Gui) showInitialPopups(tasks []func(chan struct{}) error) {
|
|||||||
|
|
||||||
func (gui *Gui) showIntroPopupMessage(done chan struct{}) error {
|
func (gui *Gui) showIntroPopupMessage(done chan struct{}) error {
|
||||||
onConfirm := func() error {
|
onConfirm := func() error {
|
||||||
done <- struct{}{}
|
|
||||||
gui.c.GetAppState().StartupPopupVersion = StartupPopupVersion
|
gui.c.GetAppState().StartupPopupVersion = StartupPopupVersion
|
||||||
return gui.c.SaveAppState()
|
err := gui.c.SaveAppState()
|
||||||
|
done <- struct{}{}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.c.Confirm(types.ConfirmOpts{
|
return gui.c.Confirm(types.ConfirmOpts{
|
||||||
@ -828,6 +832,10 @@ func (gui *Gui) onUIThread(f func() error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) onWorker(f func()) {
|
||||||
|
gui.g.OnWorker(f)
|
||||||
|
}
|
||||||
|
|
||||||
func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||||
return gui.helpers.WindowArrangement.GetWindowDimensions(informationStr, appStatus)
|
return gui.helpers.WindowArrangement.GetWindowDimensions(informationStr, appStatus)
|
||||||
}
|
}
|
||||||
|
@ -136,6 +136,10 @@ func (self *guiCommon) OnUIThread(f func() error) {
|
|||||||
self.gui.onUIThread(f)
|
self.gui.onUIThread(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *guiCommon) OnWorker(f func()) {
|
||||||
|
self.gui.onWorker(f)
|
||||||
|
}
|
||||||
|
|
||||||
func (self *guiCommon) RenderToMainViews(opts types.RefreshMainOpts) error {
|
func (self *guiCommon) RenderToMainViews(opts types.RefreshMainOpts) error {
|
||||||
return self.gui.refreshMainViews(opts)
|
return self.gui.refreshMainViews(opts)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,8 @@ import (
|
|||||||
// this gives our integration test a way of interacting with the gui for sending keypresses
|
// this gives our integration test a way of interacting with the gui for sending keypresses
|
||||||
// and reading state.
|
// and reading state.
|
||||||
type GuiDriver struct {
|
type GuiDriver struct {
|
||||||
gui *Gui
|
gui *Gui
|
||||||
|
isIdleChan chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ integrationTypes.GuiDriver = &GuiDriver{}
|
var _ integrationTypes.GuiDriver = &GuiDriver{}
|
||||||
@ -40,6 +41,9 @@ func (self *GuiDriver) PressKey(keyStr string) {
|
|||||||
tcell.NewEventKey(tcellKey, r, tcell.ModNone),
|
tcell.NewEventKey(tcellKey, r, tcell.ModNone),
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// wait until lazygit is idle (i.e. all processing is done) before continuing
|
||||||
|
<-self.isIdleChan
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *GuiDriver) Keys() config.KeybindingConfig {
|
func (self *GuiDriver) Keys() config.KeybindingConfig {
|
||||||
@ -71,7 +75,10 @@ func (self *GuiDriver) Fail(message string) {
|
|||||||
self.gui.g.Close()
|
self.gui.g.Close()
|
||||||
// need to give the gui time to close
|
// need to give the gui time to close
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
fmt.Fprintln(os.Stderr, fullMessage)
|
_, err := fmt.Fprintln(os.Stderr, fullMessage)
|
||||||
|
if err != nil {
|
||||||
|
panic("Test failed. Failed writing to stderr")
|
||||||
|
}
|
||||||
panic("Test failed")
|
panic("Test failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +114,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gui.handleTestMode()
|
||||||
|
|
||||||
gui.ViewsSetup = true
|
gui.ViewsSetup = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
gctx "github.com/jesseduffield/lazygit/pkg/gui/context"
|
gctx "github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
||||||
"github.com/sasha-s/go-deadlock"
|
"github.com/sasha-s/go-deadlock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,6 +24,7 @@ type PopupHandler struct {
|
|||||||
withWaitingStatusFn func(message string, f func() error)
|
withWaitingStatusFn func(message string, f func() error)
|
||||||
toastFn func(message string)
|
toastFn func(message string)
|
||||||
getPromptInputFn func() string
|
getPromptInputFn func() string
|
||||||
|
onWorker func(func())
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ types.IPopupHandler = &PopupHandler{}
|
var _ types.IPopupHandler = &PopupHandler{}
|
||||||
@ -39,6 +39,7 @@ func NewPopupHandler(
|
|||||||
withWaitingStatusFn func(message string, f func() error),
|
withWaitingStatusFn func(message string, f func() error),
|
||||||
toastFn func(message string),
|
toastFn func(message string),
|
||||||
getPromptInputFn func() string,
|
getPromptInputFn func() string,
|
||||||
|
onWorker func(func()),
|
||||||
) *PopupHandler {
|
) *PopupHandler {
|
||||||
return &PopupHandler{
|
return &PopupHandler{
|
||||||
Common: common,
|
Common: common,
|
||||||
@ -51,6 +52,7 @@ func NewPopupHandler(
|
|||||||
withWaitingStatusFn: withWaitingStatusFn,
|
withWaitingStatusFn: withWaitingStatusFn,
|
||||||
toastFn: toastFn,
|
toastFn: toastFn,
|
||||||
getPromptInputFn: getPromptInputFn,
|
getPromptInputFn: getPromptInputFn,
|
||||||
|
onWorker: onWorker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +143,7 @@ func (self *PopupHandler) WithLoaderPanel(message string, f func() error) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
go utils.Safe(func() {
|
self.onWorker(func() {
|
||||||
if err := f(); err != nil {
|
if err := f(); err != nil {
|
||||||
self.Log.Error(err)
|
self.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ func (gui *Gui) newStringTask(view *gocui.View, str string) error {
|
|||||||
func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error {
|
func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error {
|
||||||
manager := gui.getManager(view)
|
manager := gui.getManager(view)
|
||||||
|
|
||||||
f := func(stop chan struct{}) error {
|
f := func(tasks.TaskOpts) error {
|
||||||
gui.c.SetViewContent(view, str)
|
gui.c.SetViewContent(view, str)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -65,7 +65,7 @@ func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error {
|
|||||||
func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX int, originY int) error {
|
func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX int, originY int) error {
|
||||||
manager := gui.getManager(view)
|
manager := gui.getManager(view)
|
||||||
|
|
||||||
f := func(stop chan struct{}) error {
|
f := func(tasks.TaskOpts) error {
|
||||||
gui.c.SetViewContent(view, str)
|
gui.c.SetViewContent(view, str)
|
||||||
_ = view.SetOrigin(originX, originY)
|
_ = view.SetOrigin(originX, originY)
|
||||||
return nil
|
return nil
|
||||||
@ -81,7 +81,7 @@ func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX in
|
|||||||
func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) error {
|
func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) error {
|
||||||
manager := gui.getManager(view)
|
manager := gui.getManager(view)
|
||||||
|
|
||||||
f := func(stop chan struct{}) error {
|
f := func(tasks.TaskOpts) error {
|
||||||
gui.c.ResetViewOrigin(view)
|
gui.c.ResetViewOrigin(view)
|
||||||
gui.c.SetViewContent(view, str)
|
gui.c.SetViewContent(view, str)
|
||||||
return nil
|
return nil
|
||||||
@ -130,6 +130,8 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
|
|||||||
func() {
|
func() {
|
||||||
_ = view.SetOrigin(0, 0)
|
_ = view.SetOrigin(0, 0)
|
||||||
},
|
},
|
||||||
|
gui.c.GocuiGui().IncrementBusyCount,
|
||||||
|
gui.c.GocuiGui().DecrementBusyCount,
|
||||||
)
|
)
|
||||||
gui.viewBufferManagerMap[view.Name()] = manager
|
gui.viewBufferManagerMap[view.Name()] = manager
|
||||||
}
|
}
|
||||||
|
@ -7,29 +7,39 @@ import (
|
|||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/integration/components"
|
"github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntegrationTest interface {
|
type IntegrationTest interface {
|
||||||
Run(guiAdapter *GuiDriver)
|
Run(*GuiDriver)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) handleTestMode(test integrationTypes.IntegrationTest) {
|
func (gui *Gui) handleTestMode() {
|
||||||
|
test := gui.integrationTest
|
||||||
if os.Getenv(components.SANDBOX_ENV_VAR) == "true" {
|
if os.Getenv(components.SANDBOX_ENV_VAR) == "true" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if test != nil {
|
if test != nil {
|
||||||
go func() {
|
isIdleChan := make(chan struct{})
|
||||||
time.Sleep(time.Millisecond * 100)
|
|
||||||
|
|
||||||
test.Run(&GuiDriver{gui: gui})
|
gui.c.GocuiGui().AddIdleListener(isIdleChan)
|
||||||
|
|
||||||
|
waitUntilIdle := func() {
|
||||||
|
<-isIdleChan
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
waitUntilIdle()
|
||||||
|
|
||||||
|
test.Run(&GuiDriver{gui: gui, isIdleChan: isIdleChan})
|
||||||
|
|
||||||
gui.g.Update(func(*gocui.Gui) error {
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
return gocui.ErrQuit
|
return gocui.ErrQuit
|
||||||
})
|
})
|
||||||
|
|
||||||
|
waitUntilIdle()
|
||||||
|
|
||||||
time.Sleep(time.Second * 1)
|
time.Sleep(time.Second * 1)
|
||||||
|
|
||||||
log.Fatal("gocui should have already exited")
|
log.Fatal("gocui should have already exited")
|
||||||
|
@ -77,6 +77,9 @@ type IGuiCommon interface {
|
|||||||
// Only necessary to call if you're not already on the UI thread i.e. you're inside a goroutine.
|
// Only necessary to call if you're not already on the UI thread i.e. you're inside a goroutine.
|
||||||
// All controller handlers are executed on the UI thread.
|
// All controller handlers are executed on the UI thread.
|
||||||
OnUIThread(f func() error)
|
OnUIThread(f func() error)
|
||||||
|
// Runs a function in a goroutine. Use this whenever you want to run a goroutine and keep track of the fact
|
||||||
|
// that lazygit is still busy.
|
||||||
|
OnWorker(f func())
|
||||||
|
|
||||||
// returns the gocui Gui struct. There is a good chance you don't actually want to use
|
// returns the gocui Gui struct. There is a good chance you don't actually want to use
|
||||||
// this struct and instead want to use another method above
|
// this struct and instead want to use another method above
|
||||||
|
@ -13,6 +13,8 @@ type assertionHelper struct {
|
|||||||
|
|
||||||
// milliseconds we'll wait when an assertion fails.
|
// milliseconds we'll wait when an assertion fails.
|
||||||
func retryWaitTimes() []int {
|
func retryWaitTimes() []int {
|
||||||
|
return []int{0}
|
||||||
|
|
||||||
if os.Getenv("LONG_WAIT_BEFORE_FAIL") == "true" {
|
if os.Getenv("LONG_WAIT_BEFORE_FAIL") == "true" {
|
||||||
// CI has limited hardware, may be throttled, runs tests in parallel, etc, so we
|
// CI has limited hardware, may be throttled, runs tests in parallel, etc, so we
|
||||||
// give it more leeway compared to when we're running things locally.
|
// give it more leeway compared to when we're running things locally.
|
||||||
|
@ -61,6 +61,7 @@ var Reword = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
t.Views().Commits().
|
t.Views().Commits().
|
||||||
Lines(
|
Lines(
|
||||||
Contains(wipCommitMessage),
|
Contains(wipCommitMessage),
|
||||||
|
Contains(commitMessage),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -62,7 +62,7 @@ var DiffAndApplyPatch = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
Tap(func() {
|
Tap(func() {
|
||||||
t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Contains("Exit diff mode")).Confirm()
|
t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Contains("Exit diff mode")).Confirm()
|
||||||
|
|
||||||
t.Views().Information().Content(DoesNotContain("Building patch"))
|
t.Views().Information().Content(Contains("Building patch"))
|
||||||
}).
|
}).
|
||||||
Press(keys.Universal.CreatePatchOptionsMenu)
|
Press(keys.Universal.CreatePatchOptionsMenu)
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ var SquashFixupsAboveFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
Content(Contains("Are you sure you want to create a fixup! commit for commit")).
|
Content(Contains("Are you sure you want to create a fixup! commit for commit")).
|
||||||
Confirm()
|
Confirm()
|
||||||
}).
|
}).
|
||||||
NavigateToLine(Contains("commit 01")).
|
NavigateToLine(Contains("commit 01").DoesNotContain("fixup!")).
|
||||||
Press(keys.Commits.SquashAboveCommits).
|
Press(keys.Commits.SquashAboveCommits).
|
||||||
Tap(func() {
|
Tap(func() {
|
||||||
t.ExpectPopup().Confirmation().
|
t.ExpectPopup().Confirmation().
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package tasks
|
package tasks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
||||||
"github.com/sasha-s/go-deadlock"
|
"github.com/sasha-s/go-deadlock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,11 +17,13 @@ type AsyncHandler struct {
|
|||||||
lastId int
|
lastId int
|
||||||
mutex deadlock.Mutex
|
mutex deadlock.Mutex
|
||||||
onReject func()
|
onReject func()
|
||||||
|
onWorker func(func())
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAsyncHandler() *AsyncHandler {
|
func NewAsyncHandler(onWorker func(func())) *AsyncHandler {
|
||||||
return &AsyncHandler{
|
return &AsyncHandler{
|
||||||
mutex: deadlock.Mutex{},
|
mutex: deadlock.Mutex{},
|
||||||
|
onWorker: onWorker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +33,7 @@ func (self *AsyncHandler) Do(f func() func()) {
|
|||||||
id := self.currentId
|
id := self.currentId
|
||||||
self.mutex.Unlock()
|
self.mutex.Unlock()
|
||||||
|
|
||||||
go utils.Safe(func() {
|
self.onWorker(func() {
|
||||||
after := f()
|
after := f()
|
||||||
self.handle(after, id)
|
self.handle(after, id)
|
||||||
})
|
})
|
||||||
|
@ -12,7 +12,10 @@ func TestAsyncHandler(t *testing.T) {
|
|||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
|
|
||||||
handler := NewAsyncHandler()
|
onWorker := func(f func()) {
|
||||||
|
go f()
|
||||||
|
}
|
||||||
|
handler := NewAsyncHandler(onWorker)
|
||||||
handler.onReject = func() {
|
handler.onReject = func() {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,10 @@ type ViewBufferManager struct {
|
|||||||
refreshView func()
|
refreshView func()
|
||||||
onEndOfInput func()
|
onEndOfInput func()
|
||||||
|
|
||||||
|
// see docs/dev/Busy.md
|
||||||
|
incrementBusyCount func()
|
||||||
|
decrementBusyCount func()
|
||||||
|
|
||||||
// if the user flicks through a heap of items, with each one
|
// if the user flicks through a heap of items, with each one
|
||||||
// spawning a process to render something to the main view,
|
// spawning a process to render something to the main view,
|
||||||
// it can slow things down quite a bit. In these situations we
|
// it can slow things down quite a bit. In these situations we
|
||||||
@ -76,15 +80,19 @@ func NewViewBufferManager(
|
|||||||
refreshView func(),
|
refreshView func(),
|
||||||
onEndOfInput func(),
|
onEndOfInput func(),
|
||||||
onNewKey func(),
|
onNewKey func(),
|
||||||
|
incrementBusyCount func(),
|
||||||
|
decrementBusyCount func(),
|
||||||
) *ViewBufferManager {
|
) *ViewBufferManager {
|
||||||
return &ViewBufferManager{
|
return &ViewBufferManager{
|
||||||
Log: log,
|
Log: log,
|
||||||
writer: writer,
|
writer: writer,
|
||||||
beforeStart: beforeStart,
|
beforeStart: beforeStart,
|
||||||
refreshView: refreshView,
|
refreshView: refreshView,
|
||||||
onEndOfInput: onEndOfInput,
|
onEndOfInput: onEndOfInput,
|
||||||
readLines: make(chan LinesToRead, 1024),
|
readLines: make(chan LinesToRead, 1024),
|
||||||
onNewKey: onNewKey,
|
onNewKey: onNewKey,
|
||||||
|
incrementBusyCount: incrementBusyCount,
|
||||||
|
decrementBusyCount: decrementBusyCount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,13 +102,22 @@ func (self *ViewBufferManager) ReadLines(n int) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// note: onDone may be called twice
|
func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), prefix string, linesToRead LinesToRead, onDoneFn func()) func(TaskOpts) error {
|
||||||
func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), prefix string, linesToRead LinesToRead, onDone func()) func(chan struct{}) error {
|
return func(opts TaskOpts) error {
|
||||||
return func(stop chan struct{}) error {
|
var onDoneOnce sync.Once
|
||||||
var once sync.Once
|
var onFirstPageShownOnce sync.Once
|
||||||
var onDoneWrapper func()
|
|
||||||
if onDone != nil {
|
onFirstPageShown := func() {
|
||||||
onDoneWrapper = func() { once.Do(onDone) }
|
onFirstPageShownOnce.Do(func() {
|
||||||
|
opts.InitialContentLoaded()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onDone := func() {
|
||||||
|
if onDoneFn != nil {
|
||||||
|
onDoneOnce.Do(onDoneFn)
|
||||||
|
}
|
||||||
|
onFirstPageShown()
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.throttle {
|
if self.throttle {
|
||||||
@ -109,7 +126,8 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
|||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-stop:
|
case <-opts.Stop:
|
||||||
|
onDone()
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@ -119,7 +137,7 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
|||||||
timeToStart := time.Since(startTime)
|
timeToStart := time.Since(startTime)
|
||||||
|
|
||||||
go utils.Safe(func() {
|
go utils.Safe(func() {
|
||||||
<-stop
|
<-opts.Stop
|
||||||
// we use the time it took to start the program as a way of checking if things
|
// we use the time it took to start the program as a way of checking if things
|
||||||
// are running slow at the moment. This is admittedly a crude estimate, but
|
// are running slow at the moment. This is admittedly a crude estimate, but
|
||||||
// the point is that we only want to throttle when things are running slow
|
// the point is that we only want to throttle when things are running slow
|
||||||
@ -132,9 +150,7 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for pty's we need to call onDone here so that cmd.Wait() doesn't block forever
|
// for pty's we need to call onDone here so that cmd.Wait() doesn't block forever
|
||||||
if onDoneWrapper != nil {
|
onDone()
|
||||||
onDoneWrapper()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
loadingMutex := deadlock.Mutex{}
|
loadingMutex := deadlock.Mutex{}
|
||||||
@ -153,7 +169,7 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
|||||||
ticker := time.NewTicker(time.Millisecond * 200)
|
ticker := time.NewTicker(time.Millisecond * 200)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
select {
|
select {
|
||||||
case <-stop:
|
case <-opts.Stop:
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
loadingMutex.Lock()
|
loadingMutex.Lock()
|
||||||
@ -182,12 +198,12 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
|||||||
outer:
|
outer:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-stop:
|
case <-opts.Stop:
|
||||||
break outer
|
break outer
|
||||||
case linesToRead := <-self.readLines:
|
case linesToRead := <-self.readLines:
|
||||||
for i := 0; i < linesToRead.Total; i++ {
|
for i := 0; i < linesToRead.Total; i++ {
|
||||||
select {
|
select {
|
||||||
case <-stop:
|
case <-opts.Stop:
|
||||||
break outer
|
break outer
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@ -219,6 +235,7 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
refreshViewIfStale()
|
refreshViewIfStale()
|
||||||
|
onFirstPageShown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,10 +248,8 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// calling onDoneWrapper here again in case the program ended on its own accord
|
// calling this here again in case the program ended on its own accord
|
||||||
if onDoneWrapper != nil {
|
onDone()
|
||||||
onDoneWrapper()
|
|
||||||
}
|
|
||||||
|
|
||||||
close(done)
|
close(done)
|
||||||
})
|
})
|
||||||
@ -272,8 +287,30 @@ func (self *ViewBufferManager) Close() {
|
|||||||
// 1) command based, where the manager can be asked to read more lines, but the command can be killed
|
// 1) command based, where the manager can be asked to read more lines, but the command can be killed
|
||||||
// 2) string based, where the manager can also be asked to read more lines
|
// 2) string based, where the manager can also be asked to read more lines
|
||||||
|
|
||||||
func (self *ViewBufferManager) NewTask(f func(stop chan struct{}) error, key string) error {
|
type TaskOpts struct {
|
||||||
|
// Channel that tells the task to stop, because another task wants to run.
|
||||||
|
Stop chan struct{}
|
||||||
|
|
||||||
|
// Only for tasks which are long-running, where we read more lines sporadically.
|
||||||
|
// We use this to keep track of when a user's action is complete (i.e. all views
|
||||||
|
// have been refreshed to display the results of their action)
|
||||||
|
InitialContentLoaded func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *ViewBufferManager) NewTask(f func(TaskOpts) error, key string) error {
|
||||||
|
self.incrementBusyCount()
|
||||||
|
|
||||||
|
var decrementCounterOnce sync.Once
|
||||||
|
|
||||||
|
decrementCounter := func() {
|
||||||
|
decrementCounterOnce.Do(func() {
|
||||||
|
self.decrementBusyCount()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
go utils.Safe(func() {
|
go utils.Safe(func() {
|
||||||
|
defer decrementCounter()
|
||||||
|
|
||||||
self.taskIDMutex.Lock()
|
self.taskIDMutex.Lock()
|
||||||
self.newTaskID++
|
self.newTaskID++
|
||||||
taskID := self.newTaskID
|
taskID := self.newTaskID
|
||||||
@ -286,9 +323,9 @@ func (self *ViewBufferManager) NewTask(f func(stop chan struct{}) error, key str
|
|||||||
self.taskIDMutex.Unlock()
|
self.taskIDMutex.Unlock()
|
||||||
|
|
||||||
self.waitingMutex.Lock()
|
self.waitingMutex.Lock()
|
||||||
defer self.waitingMutex.Unlock()
|
|
||||||
|
|
||||||
if taskID < self.newTaskID {
|
if taskID < self.newTaskID {
|
||||||
|
self.waitingMutex.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,13 +344,13 @@ func (self *ViewBufferManager) NewTask(f func(stop chan struct{}) error, key str
|
|||||||
|
|
||||||
self.stopCurrentTask = func() { once.Do(onStop) }
|
self.stopCurrentTask = func() { once.Do(onStop) }
|
||||||
|
|
||||||
go utils.Safe(func() {
|
self.waitingMutex.Unlock()
|
||||||
if err := f(stop); err != nil {
|
|
||||||
self.Log.Error(err) // might need an onError callback
|
|
||||||
}
|
|
||||||
|
|
||||||
close(notifyStopped)
|
if err := f(TaskOpts{Stop: stop, InitialContentLoaded: decrementCounter}); err != nil {
|
||||||
})
|
self.Log.Error(err) // might need an onError callback
|
||||||
|
}
|
||||||
|
|
||||||
|
close(notifyStopped)
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -19,6 +19,11 @@ func getCounter() (func(), func() int) {
|
|||||||
return func() { counter++ }, func() int { return counter }
|
return func() { counter++ }, func() int { return counter }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getIncDecCounter(initialValue int) (func(), func(), func() int) {
|
||||||
|
counter := initialValue
|
||||||
|
return func() { counter++ }, func() { counter-- }, func() int { return counter }
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewCmdTaskInstantStop(t *testing.T) {
|
func TestNewCmdTaskInstantStop(t *testing.T) {
|
||||||
writer := bytes.NewBuffer(nil)
|
writer := bytes.NewBuffer(nil)
|
||||||
beforeStart, getBeforeStartCallCount := getCounter()
|
beforeStart, getBeforeStartCallCount := getCounter()
|
||||||
@ -26,6 +31,7 @@ func TestNewCmdTaskInstantStop(t *testing.T) {
|
|||||||
onEndOfInput, getOnEndOfInputCallCount := getCounter()
|
onEndOfInput, getOnEndOfInputCallCount := getCounter()
|
||||||
onNewKey, getOnNewKeyCallCount := getCounter()
|
onNewKey, getOnNewKeyCallCount := getCounter()
|
||||||
onDone, getOnDoneCallCount := getCounter()
|
onDone, getOnDoneCallCount := getCounter()
|
||||||
|
incBusyCount, decBusyCount, getBusyCount := getIncDecCounter(1)
|
||||||
|
|
||||||
manager := NewViewBufferManager(
|
manager := NewViewBufferManager(
|
||||||
utils.NewDummyLog(),
|
utils.NewDummyLog(),
|
||||||
@ -34,6 +40,8 @@ func TestNewCmdTaskInstantStop(t *testing.T) {
|
|||||||
refreshView,
|
refreshView,
|
||||||
onEndOfInput,
|
onEndOfInput,
|
||||||
onNewKey,
|
onNewKey,
|
||||||
|
incBusyCount,
|
||||||
|
decBusyCount,
|
||||||
)
|
)
|
||||||
|
|
||||||
stop := make(chan struct{})
|
stop := make(chan struct{})
|
||||||
@ -49,7 +57,7 @@ func TestNewCmdTaskInstantStop(t *testing.T) {
|
|||||||
|
|
||||||
fn := manager.NewCmdTask(start, "prefix\n", LinesToRead{20, -1}, onDone)
|
fn := manager.NewCmdTask(start, "prefix\n", LinesToRead{20, -1}, onDone)
|
||||||
|
|
||||||
_ = fn(stop)
|
_ = fn(TaskOpts{Stop: stop, InitialContentLoaded: decBusyCount})
|
||||||
|
|
||||||
callCountExpectations := []struct {
|
callCountExpectations := []struct {
|
||||||
expected int
|
expected int
|
||||||
@ -68,6 +76,10 @@ func TestNewCmdTaskInstantStop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if getBusyCount() != 0 {
|
||||||
|
t.Errorf("expected busy count to be 0, got %d", getBusyCount())
|
||||||
|
}
|
||||||
|
|
||||||
expectedContent := ""
|
expectedContent := ""
|
||||||
actualContent := writer.String()
|
actualContent := writer.String()
|
||||||
if actualContent != expectedContent {
|
if actualContent != expectedContent {
|
||||||
@ -82,6 +94,7 @@ func TestNewCmdTask(t *testing.T) {
|
|||||||
onEndOfInput, getOnEndOfInputCallCount := getCounter()
|
onEndOfInput, getOnEndOfInputCallCount := getCounter()
|
||||||
onNewKey, getOnNewKeyCallCount := getCounter()
|
onNewKey, getOnNewKeyCallCount := getCounter()
|
||||||
onDone, getOnDoneCallCount := getCounter()
|
onDone, getOnDoneCallCount := getCounter()
|
||||||
|
incBusyCount, decBusyCount, getBusyCount := getIncDecCounter(1)
|
||||||
|
|
||||||
manager := NewViewBufferManager(
|
manager := NewViewBufferManager(
|
||||||
utils.NewDummyLog(),
|
utils.NewDummyLog(),
|
||||||
@ -90,6 +103,8 @@ func TestNewCmdTask(t *testing.T) {
|
|||||||
refreshView,
|
refreshView,
|
||||||
onEndOfInput,
|
onEndOfInput,
|
||||||
onNewKey,
|
onNewKey,
|
||||||
|
incBusyCount,
|
||||||
|
decBusyCount,
|
||||||
)
|
)
|
||||||
|
|
||||||
stop := make(chan struct{})
|
stop := make(chan struct{})
|
||||||
@ -109,7 +124,7 @@ func TestNewCmdTask(t *testing.T) {
|
|||||||
close(stop)
|
close(stop)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
_ = fn(stop)
|
_ = fn(TaskOpts{Stop: stop, InitialContentLoaded: decBusyCount})
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
@ -130,6 +145,10 @@ func TestNewCmdTask(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if getBusyCount() != 0 {
|
||||||
|
t.Errorf("expected busy count to be 0, got %d", getBusyCount())
|
||||||
|
}
|
||||||
|
|
||||||
expectedContent := "prefix\ntest\n"
|
expectedContent := "prefix\ntest\n"
|
||||||
actualContent := writer.String()
|
actualContent := writer.String()
|
||||||
if actualContent != expectedContent {
|
if actualContent != expectedContent {
|
||||||
@ -208,6 +227,8 @@ func TestNewCmdTaskRefresh(t *testing.T) {
|
|||||||
lineCountsOnRefresh = append(lineCountsOnRefresh, strings.Count(writer.String(), "\n"))
|
lineCountsOnRefresh = append(lineCountsOnRefresh, strings.Count(writer.String(), "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decBusyCount := func() {}
|
||||||
|
|
||||||
manager := NewViewBufferManager(
|
manager := NewViewBufferManager(
|
||||||
utils.NewDummyLog(),
|
utils.NewDummyLog(),
|
||||||
writer,
|
writer,
|
||||||
@ -215,6 +236,8 @@ func TestNewCmdTaskRefresh(t *testing.T) {
|
|||||||
refreshView,
|
refreshView,
|
||||||
func() {},
|
func() {},
|
||||||
func() {},
|
func() {},
|
||||||
|
func() {},
|
||||||
|
decBusyCount,
|
||||||
)
|
)
|
||||||
|
|
||||||
stop := make(chan struct{})
|
stop := make(chan struct{})
|
||||||
@ -234,7 +257,7 @@ func TestNewCmdTaskRefresh(t *testing.T) {
|
|||||||
close(stop)
|
close(stop)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
_ = fn(stop)
|
_ = fn(TaskOpts{Stop: stop, InitialContentLoaded: decBusyCount})
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user