diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2022-08-09 21:11:41 +1000 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2022-08-13 13:52:13 +1000 |
commit | d890238c7bcbdd62e7158df0c1f3f0e5c0b05b66 (patch) | |
tree | e753258bc980b968bee27eaf97c7566c5c317923 /pkg/gui | |
parent | 46ae55f91e4feab67b01fcd63631dbaf47b3665f (diff) |
move input and assert into integration tests package
Diffstat (limited to 'pkg/gui')
-rw-r--r-- | pkg/gui/assert.go | 109 | ||||
-rw-r--r-- | pkg/gui/gui.go | 17 | ||||
-rw-r--r-- | pkg/gui/gui_adapter_impl.go | 3 | ||||
-rw-r--r-- | pkg/gui/gui_test.go | 7 | ||||
-rw-r--r-- | pkg/gui/input.go | 166 | ||||
-rw-r--r-- | pkg/gui/test_mode.go | 88 | ||||
-rw-r--r-- | pkg/gui/types/main_args.go | 5 | ||||
-rw-r--r-- | pkg/gui/types/test.go | 27 |
8 files changed, 118 insertions, 304 deletions
diff --git a/pkg/gui/assert.go b/pkg/gui/assert.go deleted file mode 100644 index 253278220..000000000 --- a/pkg/gui/assert.go +++ /dev/null @@ -1,109 +0,0 @@ -package gui - -import ( - "fmt" - "strings" - "time" - - guiTypes "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/integration/types" -) - -type AssertImpl struct { - gui *Gui -} - -var _ types.Assert = &AssertImpl{} - -func (self *AssertImpl) WorkingTreeFileCount(expectedCount int) { - self.assertWithRetries(func() (bool, string) { - actualCount := len(self.gui.State.Model.Files) - - return actualCount == expectedCount, fmt.Sprintf( - "Expected %d changed working tree files, but got %d", - expectedCount, actualCount, - ) - }) -} - -func (self *AssertImpl) CommitCount(expectedCount int) { - self.assertWithRetries(func() (bool, string) { - actualCount := len(self.gui.State.Model.Commits) - - return actualCount == expectedCount, fmt.Sprintf( - "Expected %d commits present, but got %d", - expectedCount, actualCount, - ) - }) -} - -func (self *AssertImpl) HeadCommitMessage(expectedMessage string) { - self.assertWithRetries(func() (bool, string) { - if len(self.gui.State.Model.Commits) == 0 { - return false, "Expected at least one commit to be present" - } - - headCommit := self.gui.State.Model.Commits[0] - if headCommit.Name != expectedMessage { - return false, fmt.Sprintf( - "Expected commit message to be '%s', but got '%s'", - expectedMessage, headCommit.Name, - ) - } - - return true, "" - }) -} - -func (self *AssertImpl) CurrentViewName(expectedViewName string) { - self.assertWithRetries(func() (bool, string) { - actual := self.gui.currentViewName() - return actual == expectedViewName, fmt.Sprintf("Expected current view name to be '%s', but got '%s'", expectedViewName, actual) - }) -} - -func (self *AssertImpl) CurrentBranchName(expectedViewName string) { - self.assertWithRetries(func() (bool, string) { - actual := self.gui.helpers.Refs.GetCheckedOutRef().Name - return actual == expectedViewName, fmt.Sprintf("Expected current branch name to be '%s', but got '%s'", expectedViewName, actual) - }) -} - -func (self *AssertImpl) InListContext() { - self.assertWithRetries(func() (bool, string) { - currentContext := self.gui.currentContext() - _, ok := currentContext.(guiTypes.IListContext) - return ok, fmt.Sprintf("Expected current context to be a list context, but got %s", currentContext.GetKey()) - }) -} - -func (self *AssertImpl) SelectedLineContains(text string) { - self.assertWithRetries(func() (bool, string) { - line := self.gui.currentContext().GetView().SelectedLine() - return strings.Contains(line, text), fmt.Sprintf("Expected selected line to contain '%s', but got '%s'", text, line) - }) -} - -func (self *AssertImpl) assertWithRetries(test func() (bool, string)) { - waitTimes := []int{0, 1, 5, 10, 200, 500, 1000} - - var message string - for _, waitTime := range waitTimes { - time.Sleep(time.Duration(waitTime) * time.Millisecond) - - var ok bool - ok, message = test() - if ok { - return - } - } - - self.Fail(message) -} - -func (self *AssertImpl) Fail(message string) { - self.gui.g.Close() - // need to give the gui time to close - time.Sleep(time.Millisecond * 100) - panic(message) -} diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index f7c8926f5..11c8af78b 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -31,7 +31,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands" "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/integration" "github.com/jesseduffield/lazygit/pkg/tasks" "github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/updates" @@ -418,14 +417,14 @@ var RuneReplacements = map[rune]string{ graph.CommitSymbol: "o", } -func (gui *Gui) initGocui(headless bool) (*gocui.Gui, error) { - recordEvents := integration.RecordingEvents() +func (gui *Gui) initGocui(headless bool, test types.Test) (*gocui.Gui, error) { + recordEvents := RecordingEvents() playMode := gocui.NORMAL if recordEvents { playMode = gocui.RECORDING - } else if integration.Replaying() { + } else if Replaying() { playMode = gocui.REPLAYING - } else if integration.IntegrationTestName() != "" { + } else if test != nil { playMode = gocui.REPLAYING_NEW } @@ -478,7 +477,7 @@ func (gui *Gui) viewTabMap() map[string][]context.TabView { // Run: setup the gui with keybindings and start the mainloop func (gui *Gui) Run(startArgs types.StartArgs) error { - g, err := gui.initGocui(integration.Headless()) + g, err := gui.initGocui(Headless(), startArgs.Test) if err != nil { return err } @@ -493,7 +492,7 @@ func (gui *Gui) Run(startArgs types.StartArgs) error { }) deadlock.Opts.Disable = !gui.Debug - gui.handleTestMode() + gui.handleTestMode(startArgs.Test) gui.g.OnSearchEscape = gui.onSearchEscape if err := gui.Config.ReloadUserConfig(); err != nil { @@ -580,7 +579,7 @@ func (gui *Gui) RunAndHandleError(startArgs types.StartArgs) error { } } - if err := integration.SaveRecording(gui.g.Recording); err != nil { + if err := SaveRecording(gui.g.Recording); err != nil { return err } @@ -614,7 +613,7 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool, gui.Mutexes.SubprocessMutex.Lock() defer gui.Mutexes.SubprocessMutex.Unlock() - if integration.Replaying() { + if Replaying() { // we do not yet support running subprocesses within integration tests. So if // we're replaying an integration test and we're inside this method, something // has gone wrong, so we should fail diff --git a/pkg/gui/gui_adapter_impl.go b/pkg/gui/gui_adapter_impl.go index dc811f92e..427b8eb47 100644 --- a/pkg/gui/gui_adapter_impl.go +++ b/pkg/gui/gui_adapter_impl.go @@ -9,7 +9,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui/keybindings" "github.com/jesseduffield/lazygit/pkg/gui/types" - integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" ) // this gives our integration test a way of interacting with the gui for sending keypresses @@ -18,7 +17,7 @@ type GuiAdapterImpl struct { gui *Gui } -var _ integrationTypes.GuiAdapter = &GuiAdapterImpl{} +var _ types.GuiAdapter = &GuiAdapterImpl{} func (self *GuiAdapterImpl) PressKey(keyStr string) { key := keybindings.GetKey(keyStr) diff --git a/pkg/gui/gui_test.go b/pkg/gui/gui_test.go index 2565392e6..2d698d34c 100644 --- a/pkg/gui/gui_test.go +++ b/pkg/gui/gui_test.go @@ -36,20 +36,19 @@ func Test(t *testing.T) { err := integration.RunTestsNew( t.Logf, runCmdHeadless, - func(test types.Test, f func(*testing.T) error) { + func(test types.Test, f func() error) { defer func() { testNumber += 1 }() if testNumber%parallelTotal != parallelIndex { return } t.Run(test.Name(), func(t *testing.T) { - err := f(t) + err := f() assert.NoError(t, err) }) }, mode, - func(t *testing.T, expected string, actual string, prefix string) { - t.Helper() + func(expected string, actual string, prefix string) { assert.Equal(t, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual)) }, includeSkipped, diff --git a/pkg/gui/input.go b/pkg/gui/input.go deleted file mode 100644 index 111d0de8e..000000000 --- a/pkg/gui/input.go +++ /dev/null @@ -1,166 +0,0 @@ -package gui - -import ( - "fmt" - "strings" - "time" - - "github.com/gdamore/tcell/v2" - "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/gui/keybindings" - guiTypes "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/integration/types" -) - -type InputImpl struct { - gui *Gui - keys config.KeybindingConfig - assert types.Assert - pushKeyDelay int -} - -func NewInputImpl(gui *Gui, keys config.KeybindingConfig, assert types.Assert, pushKeyDelay int) *InputImpl { - return &InputImpl{ - gui: gui, - keys: keys, - assert: assert, - pushKeyDelay: pushKeyDelay, - } -} - -var _ types.Input = &InputImpl{} - -func (self *InputImpl) PressKeys(keyStrs ...string) { - for _, keyStr := range keyStrs { - self.pressKey(keyStr) - } -} - -func (self *InputImpl) pressKey(keyStr string) { - self.Wait(self.pushKeyDelay) - - key := keybindings.GetKey(keyStr) - - var r rune - var tcellKey tcell.Key - switch v := key.(type) { - case rune: - r = v - tcellKey = tcell.KeyRune - case gocui.Key: - tcellKey = tcell.Key(v) - } - - self.gui.g.ReplayedEvents.Keys <- gocui.NewTcellKeyEventWrapper( - tcell.NewEventKey(tcellKey, r, tcell.ModNone), - 0, - ) -} - -func (self *InputImpl) SwitchToStatusWindow() { - self.pressKey(self.keys.Universal.JumpToBlock[0]) -} - -func (self *InputImpl) SwitchToFilesWindow() { - self.pressKey(self.keys.Universal.JumpToBlock[1]) -} - -func (self *InputImpl) SwitchToBranchesWindow() { - self.pressKey(self.keys.Universal.JumpToBlock[2]) -} - -func (self *InputImpl) SwitchToCommitsWindow() { - self.pressKey(self.keys.Universal.JumpToBlock[3]) -} - -func (self *InputImpl) SwitchToStashWindow() { - self.pressKey(self.keys.Universal.JumpToBlock[4]) -} - -func (self *InputImpl) Type(content string) { - for _, char := range content { - self.pressKey(string(char)) - } -} - -func (self *InputImpl) Confirm() { - self.pressKey(self.keys.Universal.Confirm) -} - -func (self *InputImpl) Cancel() { - self.pressKey(self.keys.Universal.Return) -} - -func (self *InputImpl) Select() { - self.pressKey(self.keys.Universal.Select) -} - -func (self *InputImpl) NextItem() { - self.pressKey(self.keys.Universal.NextItem) -} - -func (self *InputImpl) PreviousItem() { - self.pressKey(self.keys.Universal.PrevItem) -} - -func (self *InputImpl) ContinueMerge() { - self.PressKeys(self.keys.Universal.CreateRebaseOptionsMenu) - self.assert.SelectedLineContains("continue") - self.Confirm() -} - -func (self *InputImpl) ContinueRebase() { - self.ContinueMerge() -} - -func (self *InputImpl) Wait(milliseconds int) { - time.Sleep(time.Duration(milliseconds) * time.Millisecond) -} - -func (self *InputImpl) log(message string) { - self.gui.c.LogAction(message) -} - -// NOTE: this currently assumes that ViewBufferLines returns all the lines that can be accessed. -// If this changes in future, we'll need to update this code to first attempt to find the item -// in the current page and failing that, jump to the top of the view and iterate through all of it, -// looking for the item. -func (self *InputImpl) NavigateToListItemContainingText(text string) { - self.assert.InListContext() - - currentContext := self.gui.currentContext().(guiTypes.IListContext) - view := currentContext.GetView() - - // first we look for a duplicate on the current screen. We won't bother looking beyond that though. - matchCount := 0 - matchIndex := -1 - for i, line := range view.ViewBufferLines() { - if strings.Contains(line, text) { - matchCount++ - matchIndex = i - } - } - if matchCount > 1 { - self.assert.Fail(fmt.Sprintf("Found %d matches for %s, expected only a single match", matchCount, text)) - } - if matchCount == 1 { - selectedLineIdx := view.SelectedLineIdx() - if selectedLineIdx == matchIndex { - return - } - if selectedLineIdx < matchIndex { - for i := selectedLineIdx; i < matchIndex; i++ { - self.NextItem() - } - return - } else { - for i := selectedLineIdx; i > matchIndex; i-- { - self.PreviousItem() - } - return - } - } - - self.assert.Fail(fmt.Sprintf("Could not find item containing text: %s", text)) -} diff --git a/pkg/gui/test_mode.go b/pkg/gui/test_mode.go index 0252aa198..942e7824e 100644 --- a/pkg/gui/test_mode.go +++ b/pkg/gui/test_mode.go @@ -1,12 +1,15 @@ package gui import ( - "fmt" + "encoding/json" + "io/ioutil" "log" + "os" + "strconv" "time" "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -14,14 +17,8 @@ type IntegrationTest interface { Run(guiAdapter *GuiAdapterImpl) } -func (gui *Gui) handleTestMode() { - if integration.PlayingIntegrationTest() { - test, ok := integration.CurrentIntegrationTest() - - if !ok { - panic(fmt.Sprintf("test %s not found", integration.IntegrationTestName())) - } - +func (gui *Gui) handleTestMode(test types.Test) { + if test != nil { go func() { time.Sleep(time.Millisecond * 100) @@ -42,14 +39,14 @@ func (gui *Gui) handleTestMode() { }) } - if integration.Replaying() { + if Replaying() { gui.g.RecordingConfig = gocui.RecordingConfig{ - Speed: integration.GetRecordingSpeed(), + Speed: GetRecordingSpeed(), Leeway: 100, } var err error - gui.g.Recording, err = integration.LoadRecording() + gui.g.Recording, err = LoadRecording() if err != nil { panic(err) } @@ -60,3 +57,68 @@ func (gui *Gui) handleTestMode() { }) } } + +func Headless() bool { + return os.Getenv("HEADLESS") != "" +} + +// OLD integration test format stuff + +func Replaying() bool { + return os.Getenv("REPLAY_EVENTS_FROM") != "" +} + +func RecordingEvents() bool { + return recordEventsTo() != "" +} + +func recordEventsTo() string { + return os.Getenv("RECORD_EVENTS_TO") +} + +func GetRecordingSpeed() float64 { + // humans are slow so this speeds things up. + speed := 1.0 + envReplaySpeed := os.Getenv("SPEED") + if envReplaySpeed != "" { + var err error + speed, err = strconv.ParseFloat(envReplaySpeed, 64) + if err != nil { + log.Fatal(err) + } + } + return speed +} + +func LoadRecording() (*gocui.Recording, error) { + path := os.Getenv("REPLAY_EVENTS_FROM") + + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + recording := &gocui.Recording{} + + err = json.Unmarshal(data, &recording) + if err != nil { + return nil, err + } + + return recording, nil +} + +func SaveRecording(recording *gocui.Recording) error { + if !RecordingEvents() { + return nil + } + + jsonEvents, err := json.Marshal(recording) + if err != nil { + return err + } + + path := recordEventsTo() + + return ioutil.WriteFile(path, jsonEvents, 0o600) +} diff --git a/pkg/gui/types/main_args.go b/pkg/gui/types/main_args.go index b055b3736..7d9b9fbb7 100644 --- a/pkg/gui/types/main_args.go +++ b/pkg/gui/types/main_args.go @@ -6,6 +6,8 @@ type StartArgs struct { FilterPath string // GitArg determines what context we open in GitArg GitArg + // integration test (only relevant when invoking lazygit in the context of an integration test) + Test Test } type GitArg string @@ -18,9 +20,10 @@ const ( GitArgStash GitArg = "stash" ) -func NewStartArgs(filterPath string, gitArg GitArg) StartArgs { +func NewStartArgs(filterPath string, gitArg GitArg, test Test) StartArgs { return StartArgs{ FilterPath: filterPath, GitArg: gitArg, + Test: test, } } diff --git a/pkg/gui/types/test.go b/pkg/gui/types/test.go new file mode 100644 index 000000000..55c1d50a8 --- /dev/null +++ b/pkg/gui/types/test.go @@ -0,0 +1,27 @@ +package types + +import ( + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/config" +) + +type Test interface { + Run(GuiAdapter) + SetupConfig(config *config.AppConfig) +} + +// this is the interface through which our integration tests interact with the lazygit gui +type GuiAdapter interface { + PressKey(string) + Keys() config.KeybindingConfig + CurrentContext() Context + Model() *Model + Fail(message string) + // These two log methods are for the sake of debugging while testing. There's no need to actually + // commit any logging. + // logs to the normal place that you log to i.e. viewable with `lazygit --logs` + Log(message string) + // logs in the actual UI (in the commands panel) + LogUI(message string) + CheckedOutRef() *models.Branch +} |