diff options
Diffstat (limited to 'pkg/integration')
-rw-r--r-- | pkg/integration/env.go | 27 | ||||
-rw-r--r-- | pkg/integration/helpers/assert.go | 106 | ||||
-rw-r--r-- | pkg/integration/helpers/input.go | 153 | ||||
-rw-r--r-- | pkg/integration/helpers/shell.go (renamed from pkg/integration/shell.go) | 2 | ||||
-rw-r--r-- | pkg/integration/helpers/test_impl.go | 107 | ||||
-rw-r--r-- | pkg/integration/integration.go | 8 | ||||
-rw-r--r-- | pkg/integration/integration_tests/branch/suggestions.go | 3 | ||||
-rw-r--r-- | pkg/integration/integration_tests/commit/commit.go | 3 | ||||
-rw-r--r-- | pkg/integration/integration_tests/commit/new_branch.go | 3 | ||||
-rw-r--r-- | pkg/integration/integration_tests/interactive_rebase/one.go | 3 | ||||
-rw-r--r-- | pkg/integration/types/types.go | 103 |
11 files changed, 407 insertions, 111 deletions
diff --git a/pkg/integration/env.go b/pkg/integration/env.go index 0c0d0b196..6102923fc 100644 --- a/pkg/integration/env.go +++ b/pkg/integration/env.go @@ -2,9 +2,9 @@ package integration import ( "os" - "strconv" "github.com/jesseduffield/generics/slices" + "github.com/jesseduffield/lazygit/pkg/integration/integration_tests" "github.com/jesseduffield/lazygit/pkg/integration/types" ) @@ -18,35 +18,20 @@ func IntegrationTestName() string { return os.Getenv("LAZYGIT_TEST_NAME") } +func PlayingIntegrationTest() bool { + return IntegrationTestName() != "" +} + func CurrentIntegrationTest() (types.Test, bool) { if !PlayingIntegrationTest() { return nil, false } - return slices.Find(Tests, func(test types.Test) bool { + return slices.Find(integration_tests.Tests, func(test types.Test) bool { return test.Name() == IntegrationTestName() }) } -func PlayingIntegrationTest() bool { - return IntegrationTestName() != "" -} - -// this is the delay in milliseconds between keypresses -// defaults to zero -func KeyPressDelay() int { - delayStr := os.Getenv("KEY_PRESS_DELAY") - if delayStr == "" { - return 0 - } - - delay, err := strconv.Atoi(delayStr) - if err != nil { - panic(err) - } - return delay -} - // OLD integration test format stuff func Replaying() bool { diff --git a/pkg/integration/helpers/assert.go b/pkg/integration/helpers/assert.go new file mode 100644 index 000000000..eced1187d --- /dev/null +++ b/pkg/integration/helpers/assert.go @@ -0,0 +1,106 @@ +package helpers + +import ( + "fmt" + "strings" + "time" + + "github.com/jesseduffield/lazygit/pkg/gui/types" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" +) + +type AssertImpl struct { + gui integrationTypes.GuiAdapter +} + +var _ integrationTypes.Assert = &AssertImpl{} + +func (self *AssertImpl) WorkingTreeFileCount(expectedCount int) { + self.assertWithRetries(func() (bool, string) { + actualCount := len(self.gui.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.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.Model().Commits) == 0 { + return false, "Expected at least one commit to be present" + } + + headCommit := self.gui.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.CurrentContext().GetView().Name() + 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.CheckedOutRef().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.(types.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.Fail(message) +} diff --git a/pkg/integration/helpers/input.go b/pkg/integration/helpers/input.go new file mode 100644 index 000000000..fa4875ea1 --- /dev/null +++ b/pkg/integration/helpers/input.go @@ -0,0 +1,153 @@ +package helpers + +import ( + "fmt" + "strings" + "time" + + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gui/types" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" +) + +type InputImpl struct { + gui integrationTypes.GuiAdapter + keys config.KeybindingConfig + assert integrationTypes.Assert + pushKeyDelay int +} + +func NewInputImpl(gui integrationTypes.GuiAdapter, keys config.KeybindingConfig, assert integrationTypes.Assert, pushKeyDelay int) *InputImpl { + return &InputImpl{ + gui: gui, + keys: keys, + assert: assert, + pushKeyDelay: pushKeyDelay, + } +} + +var _ integrationTypes.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) + + self.gui.PressKey(keyStr) +} + +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) LogUI(message string) { + self.gui.LogUI(message) +} + +func (self *InputImpl) Log(message string) { + self.gui.LogUI(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().(types.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/integration/shell.go b/pkg/integration/helpers/shell.go index 70da9c27b..fdfb0d231 100644 --- a/pkg/integration/shell.go +++ b/pkg/integration/helpers/shell.go @@ -1,4 +1,4 @@ -package integration +package helpers import ( "fmt" diff --git a/pkg/integration/helpers/test_impl.go b/pkg/integration/helpers/test_impl.go new file mode 100644 index 000000000..7d9efa56f --- /dev/null +++ b/pkg/integration/helpers/test_impl.go @@ -0,0 +1,107 @@ +package helpers + +import ( + "os" + "strconv" + "strings" + + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/integration/types" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +type TestImpl struct { + name string + description string + extraCmdArgs string + skip bool + setupRepo func(shell types.Shell) + setupConfig func(config *config.AppConfig) + run func( + shell types.Shell, + input types.Input, + assert types.Assert, + keys config.KeybindingConfig, + ) +} + +type NewTestArgs struct { + Description string + SetupRepo func(shell types.Shell) + SetupConfig func(config *config.AppConfig) + Run func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) + ExtraCmdArgs string + Skip bool +} + +func NewTest(args NewTestArgs) *TestImpl { + return &TestImpl{ + name: testNameFromFilePath(), + description: args.Description, + extraCmdArgs: args.ExtraCmdArgs, + skip: args.Skip, + setupRepo: args.SetupRepo, + setupConfig: args.SetupConfig, + run: args.Run, + } +} + +var _ types.Test = (*TestImpl)(nil) + +func (self *TestImpl) Name() string { + return self.name +} + +func (self *TestImpl) Description() string { + return self.description +} + +func (self *TestImpl) ExtraCmdArgs() string { + return self.extraCmdArgs +} + +func (self *TestImpl) Skip() bool { + return self.skip +} + +func (self *TestImpl) SetupConfig(config *config.AppConfig) { + self.setupConfig(config) +} + +func (self *TestImpl) SetupRepo(shell types.Shell) { + self.setupRepo(shell) +} + +// I want access to all contexts, the model, the ability to press a key, the ability to log, +func (self *TestImpl) Run( + gui types.GuiAdapter, +) { + shell := &ShellImpl{} + assert := &AssertImpl{gui: gui} + keys := gui.Keys() + input := NewInputImpl(gui, keys, assert, KeyPressDelay()) + + self.run(shell, input, assert, keys) +} + +func testNameFromFilePath() string { + path := utils.FilePath(3) + name := strings.Split(path, "integration/integration_tests/")[1] + + return name[:len(name)-len(".go")] +} + +// this is the delay in milliseconds between keypresses +// defaults to zero +func KeyPressDelay() int { + delayStr := os.Getenv("KEY_PRESS_DELAY") + if delayStr == "" { + return 0 + } + + delay, err := strconv.Atoi(delayStr) + if err != nil { + panic(err) + } + return delay +} diff --git a/pkg/integration/integration.go b/pkg/integration/integration.go index 853dae0d9..4119f47a3 100644 --- a/pkg/integration/integration.go +++ b/pkg/integration/integration.go @@ -10,15 +10,13 @@ import ( "testing" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/integration/helpers" "github.com/jesseduffield/lazygit/pkg/integration/integration_tests" "github.com/jesseduffield/lazygit/pkg/integration/types" ) // this is the integration runner for the new and improved integration interface -// re-exporting this so that clients only need to import one package -var Tests = integration_tests.Tests - func RunTestsNew( logf func(format string, formatArgs ...interface{}), runCmd func(cmd *exec.Cmd) error, @@ -41,7 +39,7 @@ func RunTestsNew( return err } - for _, test := range Tests { + for _, test := range integration_tests.Tests { test := test fnWrapper(test, func(t *testing.T) error { //nolint: thelper @@ -141,7 +139,7 @@ func createFixtureNew(test types.Test, actualDir string, rootDir string) error { panic(err) } - shell := &ShellImpl{} + shell := &helpers.ShellImpl{} shell.RunCommand("git init") shell.RunCommand(`git config user.email "CI@example.com"`) shell.RunCommand(`git config user.name "CI"`) diff --git a/pkg/integration/integration_tests/branch/suggestions.go b/pkg/integration/integration_tests/branch/suggestions.go index 47c360514..a925280e7 100644 --- a/pkg/integration/integration_tests/branch/suggestions.go +++ b/pkg/integration/integration_tests/branch/suggestions.go @@ -2,10 +2,11 @@ package branch import ( "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/integration/helpers" "github.com/jesseduffield/lazygit/pkg/integration/types" ) -var Suggestions = types.NewTest(types.NewTestArgs{ +var Suggestions = helpers.NewTest(helpers.NewTestArgs{ Description: "Checking out a branch with name suggestions", ExtraCmdArgs: "", Skip: false, diff --git a/pkg/integration/integration_tests/commit/commit.go b/pkg/integration/integration_tests/commit/commit.go index f0af9462a..9fd3bb356 100644 --- a/pkg/integration/integration_tests/commit/commit.go +++ b/pkg/integration/integration_tests/commit/commit.go @@ -2,10 +2,11 @@ package commit import ( "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/integration/helpers" "github.com/jesseduffield/lazygit/pkg/integration/types" ) -var Commit = types.NewTest(types.NewTestArgs{ +var Commit = helpers.NewTest(helpers.NewTestArgs{ Description: "Staging a couple files and committing", ExtraCmdArgs: "", Skip: false, diff --git a/pkg/integration/integration_tests/commit/new_branch.go b/pkg/integration/integration_tests/commit/new_branch.go index 218e95180..9669937fa 100644 --- a/pkg/integration/integration_tests/commit/new_branch.go +++ b/pkg/integration/integration_tests/commit/new_branch.go @@ -2,10 +2,11 @@ package commit import ( "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/integration/helpers" "github.com/jesseduffield/lazygit/pkg/integration/types" ) -var NewBranch = types.NewTest(types.NewTestArgs{ +var NewBranch = helpers.NewTest(helpers.NewTestArgs{ Description: "Creating a new branch from a commit", ExtraCmdArgs: "", Skip: false, diff --git a/pkg/integration/integration_tests/interactive_rebase/one.go b/pkg/integration/integration_tests/interactive_rebase/one.go index 92780b24d..d8899569c 100644 --- a/pkg/integration/integration_tests/interactive_rebase/one.go +++ b/pkg/integration/integration_tests/interactive_rebase/one.go @@ -2,10 +2,11 @@ package interactive_rebase import ( "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/integration/helpers" "github.com/jesseduffield/lazygit/pkg/integration/types" ) -var One = types.NewTest(types.NewTestArgs{ +var One = helpers.NewTest(helpers.NewTestArgs{ Description: "Begins an interactive rebase, then fixups, drops, and squashes some commits", ExtraCmdArgs: "", Skip: false, diff --git a/pkg/integration/types/types.go b/pkg/integration/types/types.go index 60fd4f353..2e6ea34f6 100644 --- a/pkg/integration/types/types.go +++ b/pkg/integration/types/types.go @@ -1,12 +1,17 @@ package types import ( - "strings" - + "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/jesseduffield/lazygit/pkg/gui/types" ) +// TODO: refactor this so that we don't have code spread around so much. We want +// our TestImpl struct to take the dependencies it needs from the gui and then +// create the input, assert, shell structs itself. That way, we can potentially +// ditch these interfaces so that we don't need to keep updating them every time +// we add a method to the concrete struct. + type Test interface { Name() string Description() string @@ -16,7 +21,7 @@ type Test interface { // so that they appear when lazygit runs SetupConfig(config *config.AppConfig) // this is called upon lazygit starting - Run(Shell, Input, Assert, config.KeybindingConfig) + Run(GuiAdapter) // e.g. '-debug' ExtraCmdArgs() string // for tests that are flakey and when we don't have time to fix them @@ -81,6 +86,7 @@ type Input interface { } // through this interface we assert on the state of the lazygit gui +// implementation is at pkg/gui/assert.go type Assert interface { WorkingTreeFileCount(int) CommitCount(int) @@ -93,80 +99,17 @@ type Assert interface { Fail(errorMessage string) } -type TestImpl struct { - name string - description string - extraCmdArgs string - skip bool - setupRepo func(shell Shell) - setupConfig func(config *config.AppConfig) - run func( - shell Shell, - input Input, - assert Assert, - keys config.KeybindingConfig, - ) -} - -type NewTestArgs struct { - Description string - SetupRepo func(shell Shell) - SetupConfig func(config *config.AppConfig) - Run func(shell Shell, input Input, assert Assert, keys config.KeybindingConfig) - ExtraCmdArgs string - Skip bool -} - -func NewTest(args NewTestArgs) *TestImpl { - return &TestImpl{ - name: testNameFromFilePath(), - description: args.Description, - extraCmdArgs: args.ExtraCmdArgs, - skip: args.Skip, - setupRepo: args.SetupRepo, - setupConfig: args.SetupConfig, - run: args.Run, - } -} - -var _ Test = (*TestImpl)(nil) - -func (self *TestImpl) Name() string { - return self.name -} - -func (self *TestImpl) Description() string { - return self.description -} - -func (self *TestImpl) ExtraCmdArgs() string { - return self.extraCmdArgs -} - -func (self *TestImpl) Skip() bool { - return self.skip -} - -func (self *TestImpl) SetupConfig(config *config.AppConfig) { - self.setupConfig(config) -} - -func (self *TestImpl) SetupRepo(shell Shell) { - self.setupRepo(shell) -} - -func (self *TestImpl) Run( - shell Shell, - input Input, - assert Assert, - keys config.KeybindingConfig, -) { - self.run(shell, input, assert, keys) -} - -func testNameFromFilePath() string { - path := utils.FilePath(3) - name := strings.Split(path, "integration/integration_tests/")[1] - - return name[:len(name)-len(".go")] +type GuiAdapter interface { + PressKey(string) + Keys() config.KeybindingConfig + CurrentContext() types.Context + Model() *types.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 } |