diff --git a/main.go b/main.go index 8684b8cd6..6faf41ddf 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,8 @@ import ( "github.com/integrii/flaggy" "github.com/jesseduffield/lazygit/pkg/app" + "github.com/jesseduffield/lazygit/pkg/integration" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" "github.com/jesseduffield/lazygit/pkg/utils" "github.com/samber/lo" ) @@ -24,8 +26,9 @@ var ( func main() { cliArgs := parseCliArgsAndEnvVars() buildInfo := getBuildInfo() + integrationTest := getIntegrationTest() - app.Start(cliArgs, buildInfo, nil) + app.Start(cliArgs, buildInfo, integrationTest) } func parseCliArgsAndEnvVars() *app.CliArgs { @@ -129,3 +132,20 @@ func getBuildInfo() *app.BuildInfo { return buildInfo } + +func getIntegrationTest() integrationTypes.IntegrationTest { + integrationTestName := os.Getenv("LAZYGIT_TEST_NAME") + if integrationTestName == "" { + return nil + } + + // unsetting so that if we run lazygit in as a 'daemon' we don't think we're trying to run a test again + os.Unsetenv("LAZYGIT_TEST_NAME") + for _, candidateTest := range integration.Tests { + if candidateTest.Name() == integrationTestName { + return candidateTest + } + } + + panic("Could not find integration test with name: " + integrationTestName) +} diff --git a/pkg/app/app.go b/pkg/app/app.go index a45ffb118..3a1c127de 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -14,6 +14,7 @@ import ( "github.com/go-errors/errors" "github.com/jesseduffield/generics/slices" + appTypes "github.com/jesseduffield/lazygit/pkg/app/types" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/git_config" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" @@ -22,7 +23,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/constants" "github.com/jesseduffield/lazygit/pkg/env" "github.com/jesseduffield/lazygit/pkg/gui" - "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/updates" ) @@ -41,7 +41,7 @@ type App struct { func Run( config config.AppConfigurer, common *common.Common, - startArgs types.StartArgs, + startArgs appTypes.StartArgs, ) { app, err := NewApp(config, common) @@ -217,7 +217,7 @@ func (app *App) setupRepo() (bool, error) { return false, nil } -func (app *App) Run(startArgs types.StartArgs) error { +func (app *App) Run(startArgs appTypes.StartArgs) error { err := app.Gui.RunAndHandleError(startArgs) return err } diff --git a/pkg/app/run.go b/pkg/app/entry_point.go similarity index 82% rename from pkg/app/run.go rename to pkg/app/entry_point.go index b1e2c396f..551b959a6 100644 --- a/pkg/app/run.go +++ b/pkg/app/entry_point.go @@ -10,9 +10,9 @@ import ( "strings" "github.com/jesseduffield/lazygit/pkg/app/daemon" + appTypes "github.com/jesseduffield/lazygit/pkg/app/types" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/env" - "github.com/jesseduffield/lazygit/pkg/gui/types" integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" "github.com/jesseduffield/lazygit/pkg/logs" "gopkg.in/yaml.v3" @@ -40,12 +40,7 @@ type BuildInfo struct { BuildSource string } -// only used when running integration tests -type TestConfig struct { - Test integrationTypes.Test -} - -func Start(cliArgs *CliArgs, buildInfo *BuildInfo, test integrationTypes.Test) { +func Start(cliArgs *CliArgs, buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTest) { if cliArgs.RepoPath != "" { if cliArgs.WorkTree != "" || cliArgs.GitDir != "" { log.Fatal("--path option is incompatible with the --work-tree and --git-dir options") @@ -118,8 +113,8 @@ func Start(cliArgs *CliArgs, buildInfo *BuildInfo, test integrationTypes.Test) { log.Fatal(err.Error()) } - if test != nil { - test.SetupConfig(appConfig) + if integrationTest != nil { + integrationTest.SetupConfig(appConfig) } common, err := NewCommon(appConfig) @@ -134,23 +129,23 @@ func Start(cliArgs *CliArgs, buildInfo *BuildInfo, test integrationTypes.Test) { parsedGitArg := parseGitArg(cliArgs.GitArg) - Run(appConfig, common, types.NewStartArgs(cliArgs.FilterPath, parsedGitArg, test)) + Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, integrationTest)) } -func parseGitArg(gitArg string) types.GitArg { - typedArg := types.GitArg(gitArg) +func parseGitArg(gitArg string) appTypes.GitArg { + typedArg := appTypes.GitArg(gitArg) // using switch so that linter catches when a new git arg value is defined but not handled here switch typedArg { - case types.GitArgNone, types.GitArgStatus, types.GitArgBranch, types.GitArgLog, types.GitArgStash: + case appTypes.GitArgNone, appTypes.GitArgStatus, appTypes.GitArgBranch, appTypes.GitArgLog, appTypes.GitArgStash: return typedArg } permittedValues := []string{ - string(types.GitArgStatus), - string(types.GitArgBranch), - string(types.GitArgLog), - string(types.GitArgStash), + string(appTypes.GitArgStatus), + string(appTypes.GitArgBranch), + string(appTypes.GitArgLog), + string(appTypes.GitArgStash), } log.Fatalf("Invalid git arg value: '%s'. Must be one of the following values: %s. e.g. 'lazygit status'. See 'lazygit --help'.", diff --git a/pkg/gui/types/main_args.go b/pkg/app/types/types.go similarity index 64% rename from pkg/gui/types/main_args.go rename to pkg/app/types/types.go index 7d9b9fbb7..002111087 100644 --- a/pkg/gui/types/main_args.go +++ b/pkg/app/types/types.go @@ -1,4 +1,8 @@ -package types +package app + +import ( + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" +) // StartArgs is the struct that represents some things we want to do on program start type StartArgs struct { @@ -7,7 +11,7 @@ type StartArgs struct { // 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 + IntegrationTest integrationTypes.IntegrationTest } type GitArg string @@ -20,10 +24,10 @@ const ( GitArgStash GitArg = "stash" ) -func NewStartArgs(filterPath string, gitArg GitArg, test Test) StartArgs { +func NewStartArgs(filterPath string, gitArg GitArg, test integrationTypes.IntegrationTest) StartArgs { return StartArgs{ - FilterPath: filterPath, - GitArg: gitArg, - Test: test, + FilterPath: filterPath, + GitArg: gitArg, + IntegrationTest: test, } } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 11c8af78b..8be5a4a4d 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -10,6 +10,7 @@ import ( "time" "github.com/jesseduffield/gocui" + appTypes "github.com/jesseduffield/lazygit/pkg/app/types" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_config" @@ -31,6 +32,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands" "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/types" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" "github.com/jesseduffield/lazygit/pkg/tasks" "github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/updates" @@ -213,7 +215,7 @@ const ( COMPLETE ) -func (gui *Gui) onNewRepo(startArgs types.StartArgs, reuseState bool) error { +func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, reuseState bool) error { var err error gui.git, err = commands.NewGitCommand( gui.Common, @@ -245,7 +247,7 @@ func (gui *Gui) onNewRepo(startArgs types.StartArgs, reuseState bool) error { // it gets a bit confusing to land back in the status panel when visiting a repo // you've already switched from. There's no doubt some easy way to make the UX // optimal for all cases but I'm too lazy to think about what that is right now -func (gui *Gui) resetState(startArgs types.StartArgs, reuseState bool) { +func (gui *Gui) resetState(startArgs appTypes.StartArgs, reuseState bool) { currentDir, err := os.Getwd() if reuseState { @@ -300,28 +302,28 @@ func (gui *Gui) resetState(startArgs types.StartArgs, reuseState bool) { gui.RepoStateMap[Repo(currentDir)] = gui.State } -func initialScreenMode(startArgs types.StartArgs) WindowMaximisation { - if startArgs.FilterPath != "" || startArgs.GitArg != types.GitArgNone { +func initialScreenMode(startArgs appTypes.StartArgs) WindowMaximisation { + if startArgs.FilterPath != "" || startArgs.GitArg != appTypes.GitArgNone { return SCREEN_HALF } else { return SCREEN_NORMAL } } -func initialContext(contextTree *context.ContextTree, startArgs types.StartArgs) types.IListContext { +func initialContext(contextTree *context.ContextTree, startArgs appTypes.StartArgs) types.IListContext { var initialContext types.IListContext = contextTree.Files if startArgs.FilterPath != "" { initialContext = contextTree.LocalCommits - } else if startArgs.GitArg != types.GitArgNone { + } else if startArgs.GitArg != appTypes.GitArgNone { switch startArgs.GitArg { - case types.GitArgStatus: + case appTypes.GitArgStatus: initialContext = contextTree.Files - case types.GitArgBranch: + case appTypes.GitArgBranch: initialContext = contextTree.Branches - case types.GitArgLog: + case appTypes.GitArgLog: initialContext = contextTree.LocalCommits - case types.GitArgStash: + case appTypes.GitArgStash: initialContext = contextTree.Stash default: panic("unhandled git arg") @@ -417,7 +419,7 @@ var RuneReplacements = map[rune]string{ graph.CommitSymbol: "o", } -func (gui *Gui) initGocui(headless bool, test types.Test) (*gocui.Gui, error) { +func (gui *Gui) initGocui(headless bool, test integrationTypes.IntegrationTest) (*gocui.Gui, error) { recordEvents := RecordingEvents() playMode := gocui.NORMAL if recordEvents { @@ -476,8 +478,8 @@ 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(Headless(), startArgs.Test) +func (gui *Gui) Run(startArgs appTypes.StartArgs) error { + g, err := gui.initGocui(Headless(), startArgs.IntegrationTest) if err != nil { return err } @@ -492,7 +494,7 @@ func (gui *Gui) Run(startArgs types.StartArgs) error { }) deadlock.Opts.Disable = !gui.Debug - gui.handleTestMode(startArgs.Test) + gui.handleTestMode(startArgs.IntegrationTest) gui.g.OnSearchEscape = gui.onSearchEscape if err := gui.Config.ReloadUserConfig(); err != nil { @@ -553,7 +555,7 @@ func (gui *Gui) Run(startArgs types.StartArgs) error { return gui.g.MainLoop() } -func (gui *Gui) RunAndHandleError(startArgs types.StartArgs) error { +func (gui *Gui) RunAndHandleError(startArgs appTypes.StartArgs) error { gui.stopChan = make(chan struct{}) return utils.SafeWithError(func() error { if err := gui.Run(startArgs); err != nil { diff --git a/pkg/gui/gui_adapter_impl.go b/pkg/gui/gui_adapter.go similarity index 68% rename from pkg/gui/gui_adapter_impl.go rename to pkg/gui/gui_adapter.go index 427b8eb47..5566f8b0f 100644 --- a/pkg/gui/gui_adapter_impl.go +++ b/pkg/gui/gui_adapter.go @@ -9,17 +9,18 @@ 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 // and reading state. -type GuiAdapterImpl struct { +type GuiAdapter struct { gui *Gui } -var _ types.GuiAdapter = &GuiAdapterImpl{} +var _ integrationTypes.GuiAdapter = &GuiAdapter{} -func (self *GuiAdapterImpl) PressKey(keyStr string) { +func (self *GuiAdapter) PressKey(keyStr string) { key := keybindings.GetKey(keyStr) var r rune @@ -38,19 +39,19 @@ func (self *GuiAdapterImpl) PressKey(keyStr string) { ) } -func (self *GuiAdapterImpl) Keys() config.KeybindingConfig { +func (self *GuiAdapter) Keys() config.KeybindingConfig { return self.gui.Config.GetUserConfig().Keybinding } -func (self *GuiAdapterImpl) CurrentContext() types.Context { +func (self *GuiAdapter) CurrentContext() types.Context { return self.gui.c.CurrentContext() } -func (self *GuiAdapterImpl) Model() *types.Model { +func (self *GuiAdapter) Model() *types.Model { return self.gui.State.Model } -func (self *GuiAdapterImpl) Fail(message string) { +func (self *GuiAdapter) Fail(message string) { self.gui.g.Close() // need to give the gui time to close time.Sleep(time.Millisecond * 100) @@ -58,15 +59,15 @@ func (self *GuiAdapterImpl) Fail(message string) { } // logs to the normal place that you log to i.e. viewable with `lazygit --logs` -func (self *GuiAdapterImpl) Log(message string) { +func (self *GuiAdapter) Log(message string) { self.gui.c.Log.Warn(message) } // logs in the actual UI (in the commands panel) -func (self *GuiAdapterImpl) LogUI(message string) { +func (self *GuiAdapter) LogUI(message string) { self.gui.c.LogAction(message) } -func (self *GuiAdapterImpl) CheckedOutRef() *models.Branch { +func (self *GuiAdapter) CheckedOutRef() *models.Branch { return self.gui.helpers.Refs.GetCheckedOutRef() } diff --git a/pkg/gui/recent_repos_panel.go b/pkg/gui/recent_repos_panel.go index 73d6e54c5..705461726 100644 --- a/pkg/gui/recent_repos_panel.go +++ b/pkg/gui/recent_repos_panel.go @@ -9,6 +9,7 @@ import ( "sync" "github.com/jesseduffield/generics/slices" + appTypes "github.com/jesseduffield/lazygit/pkg/app/types" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/env" "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" @@ -152,7 +153,7 @@ func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error { gui.Mutexes.RefreshingFilesMutex.Lock() defer gui.Mutexes.RefreshingFilesMutex.Unlock() - return gui.onNewRepo(types.StartArgs{}, reuse) + return gui.onNewRepo(appTypes.StartArgs{}, reuse) } // updateRecentRepoList registers the fact that we opened lazygit in this repo, diff --git a/pkg/gui/test_mode.go b/pkg/gui/test_mode.go index 942e7824e..e9d596e80 100644 --- a/pkg/gui/test_mode.go +++ b/pkg/gui/test_mode.go @@ -9,20 +9,20 @@ import ( "time" "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/types" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" "github.com/jesseduffield/lazygit/pkg/utils" ) type IntegrationTest interface { - Run(guiAdapter *GuiAdapterImpl) + Run(guiAdapter *GuiAdapter) } -func (gui *Gui) handleTestMode(test types.Test) { +func (gui *Gui) handleTestMode(test integrationTypes.IntegrationTest) { if test != nil { go func() { time.Sleep(time.Millisecond * 100) - test.Run(&GuiAdapterImpl{gui: gui}) + test.Run(&GuiAdapter{gui: gui}) gui.g.Update(func(*gocui.Gui) error { return gocui.ErrQuit diff --git a/pkg/gui/types/test.go b/pkg/gui/types/test.go deleted file mode 100644 index 55c1d50a8..000000000 --- a/pkg/gui/types/test.go +++ /dev/null @@ -1,27 +0,0 @@ -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 -} diff --git a/pkg/integration/README.md b/pkg/integration/README.md new file mode 100644 index 000000000..55481ebe6 --- /dev/null +++ b/pkg/integration/README.md @@ -0,0 +1,11 @@ +# Integration Tests + +There's a lot happening in this package so it's worth a proper explanation. + +This package is for integration testing: that is, actually running a real lazygit session and having a robot pretend to be a human user and then making assertions that everything works as expected. + +There are three ways to invoke a test: + +1. go run pkg/integration/runner/main.go commit/new_branch +2. go test pkg/integration/integration_test.go +3. diff --git a/pkg/integration/integration_old.go b/pkg/integration/deprecated/integration.go similarity index 99% rename from pkg/integration/integration_old.go rename to pkg/integration/deprecated/integration.go index 579892e3b..761c978e0 100644 --- a/pkg/integration/integration_old.go +++ b/pkg/integration/deprecated/integration.go @@ -1,4 +1,4 @@ -package integration +package deprecated import ( "encoding/json" diff --git a/pkg/gui/old_gui_test.go b/pkg/integration/deprecated/integration_test.go similarity index 79% rename from pkg/gui/old_gui_test.go rename to pkg/integration/deprecated/integration_test.go index 12e33432d..e66cc861a 100644 --- a/pkg/gui/old_gui_test.go +++ b/pkg/integration/deprecated/integration_test.go @@ -1,14 +1,18 @@ //go:build !windows // +build !windows -package gui +package deprecated import ( "fmt" + "io" + "io/ioutil" "os" + "os/exec" + "strconv" "testing" - "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/creack/pty" "github.com/stretchr/testify/assert" ) @@ -41,7 +45,7 @@ func TestOld(t *testing.T) { t.Skip("Skipping integration tests in short mode") } - mode := integration.GetModeFromEnv() + mode := GetModeFromEnv() speedEnv := os.Getenv("SPEED") includeSkipped := os.Getenv("INCLUDE_SKIPPED") != "" @@ -49,10 +53,10 @@ func TestOld(t *testing.T) { parallelIndex := tryConvert(os.Getenv("PARALLEL_INDEX"), 0) testNumber := 0 - err := integration.RunTests( + err := RunTests( t.Logf, runCmdHeadless, - func(test *integration.Test, f func(*testing.T) error) { + func(test *Test, f func(*testing.T) error) { defer func() { testNumber += 1 }() if testNumber%parallelTotal != parallelIndex { return @@ -74,3 +78,29 @@ func TestOld(t *testing.T) { assert.NoError(t, err) } + +func tryConvert(numStr string, defaultVal int) int { + num, err := strconv.Atoi(numStr) + if err != nil { + return defaultVal + } + + return num +} + +func runCmdHeadless(cmd *exec.Cmd) error { + cmd.Env = append( + cmd.Env, + "HEADLESS=true", + "TERM=xterm", + ) + + f, err := pty.StartWithSize(cmd, &pty.Winsize{Rows: 100, Cols: 100}) + if err != nil { + return err + } + + _, _ = io.Copy(ioutil.Discard, f) + + return f.Close() +} diff --git a/test/runner_old/main.go b/pkg/integration/deprecated/runner/main.go similarity index 85% rename from test/runner_old/main.go rename to pkg/integration/deprecated/runner/main.go index ea6af59e8..18398a450 100644 --- a/test/runner_old/main.go +++ b/pkg/integration/deprecated/runner/main.go @@ -7,11 +7,11 @@ import ( "os/exec" "testing" - "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/jesseduffield/lazygit/pkg/integration/deprecated" "github.com/stretchr/testify/assert" ) -// Deprecated: This file is part of the old way of doing things. See test/runner_new/main.go for the new way +// Deprecated: This file is part of the old way of doing things. See pkg/integration/runner/main.go for the new way // see docs/Integration_Tests.md // This file can be invoked directly, but you might find it easier to go through @@ -22,15 +22,15 @@ import ( // as an env var. func main() { - mode := integration.GetModeFromEnv() + mode := deprecated.GetModeFromEnv() speedEnv := os.Getenv("SPEED") includeSkipped := os.Getenv("INCLUDE_SKIPPED") == "true" selectedTestName := os.Args[1] - err := integration.RunTests( + err := deprecated.RunTests( log.Printf, runCmdInTerminal, - func(test *integration.Test, f func(*testing.T) error) { + func(test *deprecated.Test, f func(*testing.T) error) { if selectedTestName != "" && test.Name != selectedTestName { return } diff --git a/test/lazyintegration/main.go b/pkg/integration/deprecated/tui/main.go similarity index 97% rename from test/lazyintegration/main.go rename to pkg/integration/deprecated/tui/main.go index f62d92fbe..aa698dc8e 100644 --- a/test/lazyintegration/main.go +++ b/pkg/integration/deprecated/tui/main.go @@ -10,21 +10,23 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/gui" "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/jesseduffield/lazygit/pkg/integration/deprecated" "github.com/jesseduffield/lazygit/pkg/secureexec" ) +// Deprecated. See lazy_integration for the new approach. + // this program lets you manage integration tests in a TUI. See docs/Integration_Tests.md for more info. type App struct { - tests []*integration.Test + tests []*deprecated.Test itemIdx int testDir string editing bool g *gocui.Gui } -func (app *App) getCurrentTest() *integration.Test { +func (app *App) getCurrentTest() *deprecated.Test { if len(app.tests) > 0 { return app.tests[app.itemIdx] } @@ -49,7 +51,7 @@ func (app *App) refreshTests() { } func (app *App) loadTests() { - tests, err := integration.LoadTests(app.testDir) + tests, err := deprecated.LoadTests(app.testDir) if err != nil { log.Panicln(err) } @@ -61,7 +63,7 @@ func (app *App) loadTests() { } func main() { - rootDir := integration.GetRootDirectory() + rootDir := deprecated.GetRootDirectory() testDir := filepath.Join(rootDir, "test", "integration") app := &App{testDir: testDir} diff --git a/pkg/integration/env.go b/pkg/integration/env.go deleted file mode 100644 index 89f25d85e..000000000 --- a/pkg/integration/env.go +++ /dev/null @@ -1,29 +0,0 @@ -package integration - -import ( - "os" - - "github.com/jesseduffield/generics/slices" - "github.com/jesseduffield/lazygit/pkg/integration/integration_tests" - "github.com/jesseduffield/lazygit/pkg/integration/types" -) - -// NEW integration test format stuff - -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(integration_tests.Tests, func(test types.Test) bool { - return test.Name() == IntegrationTestName() - }) -} diff --git a/pkg/integration/helpers/assert.go b/pkg/integration/helpers/assert.go index 491bca348..41b280bcb 100644 --- a/pkg/integration/helpers/assert.go +++ b/pkg/integration/helpers/assert.go @@ -9,13 +9,17 @@ import ( integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" ) -type AssertImpl struct { - gui types.GuiAdapter +// through this struct we assert on the state of the lazygit gui + +type Assert struct { + gui integrationTypes.GuiAdapter } -var _ integrationTypes.Assert = &AssertImpl{} +func NewAssert(gui integrationTypes.GuiAdapter) *Assert { + return &Assert{gui: gui} +} -func (self *AssertImpl) WorkingTreeFileCount(expectedCount int) { +func (self *Assert) WorkingTreeFileCount(expectedCount int) { self.assertWithRetries(func() (bool, string) { actualCount := len(self.gui.Model().Files) @@ -26,7 +30,7 @@ func (self *AssertImpl) WorkingTreeFileCount(expectedCount int) { }) } -func (self *AssertImpl) CommitCount(expectedCount int) { +func (self *Assert) CommitCount(expectedCount int) { self.assertWithRetries(func() (bool, string) { actualCount := len(self.gui.Model().Commits) @@ -37,7 +41,7 @@ func (self *AssertImpl) CommitCount(expectedCount int) { }) } -func (self *AssertImpl) HeadCommitMessage(expectedMessage string) { +func (self *Assert) 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" @@ -55,21 +59,21 @@ func (self *AssertImpl) HeadCommitMessage(expectedMessage string) { }) } -func (self *AssertImpl) CurrentViewName(expectedViewName string) { +func (self *Assert) 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) { +func (self *Assert) 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() { +func (self *Assert) InListContext() { self.assertWithRetries(func() (bool, string) { currentContext := self.gui.CurrentContext() _, ok := currentContext.(types.IListContext) @@ -77,14 +81,14 @@ func (self *AssertImpl) InListContext() { }) } -func (self *AssertImpl) SelectedLineContains(text string) { +func (self *Assert) 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)) { +func (self *Assert) assertWithRetries(test func() (bool, string)) { waitTimes := []int{0, 1, 5, 10, 200, 500, 1000} var message string @@ -101,6 +105,7 @@ func (self *AssertImpl) assertWithRetries(test func() (bool, string)) { self.Fail(message) } -func (self *AssertImpl) Fail(message string) { +// for when you just want to fail the test yourself +func (self *Assert) Fail(message string) { self.gui.Fail(message) } diff --git a/pkg/integration/helpers/input.go b/pkg/integration/helpers/input.go index f23a08688..8c34fab5c 100644 --- a/pkg/integration/helpers/input.go +++ b/pkg/integration/helpers/input.go @@ -10,15 +10,15 @@ import ( integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" ) -type InputImpl struct { - gui types.GuiAdapter +type Impl struct { + gui integrationTypes.GuiAdapter keys config.KeybindingConfig - assert integrationTypes.Assert + assert *Assert pushKeyDelay int } -func NewInputImpl(gui types.GuiAdapter, keys config.KeybindingConfig, assert integrationTypes.Assert, pushKeyDelay int) *InputImpl { - return &InputImpl{ +func NewInput(gui integrationTypes.GuiAdapter, keys config.KeybindingConfig, assert *Assert, pushKeyDelay int) *Impl { + return &Impl{ gui: gui, keys: keys, assert: assert, @@ -26,93 +26,106 @@ func NewInputImpl(gui types.GuiAdapter, keys config.KeybindingConfig, assert int } } -var _ integrationTypes.Input = &InputImpl{} - -func (self *InputImpl) PressKeys(keyStrs ...string) { +// key is something like 'w' or ''. It's best not to pass a direct value, +// but instead to go through the default user config to get a more meaningful key name +func (self *Impl) PressKeys(keyStrs ...string) { for _, keyStr := range keyStrs { self.pressKey(keyStr) } } -func (self *InputImpl) pressKey(keyStr string) { +func (self *Impl) pressKey(keyStr string) { self.Wait(self.pushKeyDelay) self.gui.PressKey(keyStr) } -func (self *InputImpl) SwitchToStatusWindow() { +func (self *Impl) SwitchToStatusWindow() { self.pressKey(self.keys.Universal.JumpToBlock[0]) } -func (self *InputImpl) SwitchToFilesWindow() { +func (self *Impl) SwitchToFilesWindow() { self.pressKey(self.keys.Universal.JumpToBlock[1]) } -func (self *InputImpl) SwitchToBranchesWindow() { +func (self *Impl) SwitchToBranchesWindow() { self.pressKey(self.keys.Universal.JumpToBlock[2]) } -func (self *InputImpl) SwitchToCommitsWindow() { +func (self *Impl) SwitchToCommitsWindow() { self.pressKey(self.keys.Universal.JumpToBlock[3]) } -func (self *InputImpl) SwitchToStashWindow() { +func (self *Impl) SwitchToStashWindow() { self.pressKey(self.keys.Universal.JumpToBlock[4]) } -func (self *InputImpl) Type(content string) { +func (self *Impl) Type(content string) { for _, char := range content { self.pressKey(string(char)) } } -func (self *InputImpl) Confirm() { +// i.e. pressing enter +func (self *Impl) Confirm() { self.pressKey(self.keys.Universal.Confirm) } -func (self *InputImpl) Cancel() { +// i.e. pressing escape +func (self *Impl) Cancel() { self.pressKey(self.keys.Universal.Return) } -func (self *InputImpl) Select() { +// i.e. pressing space +func (self *Impl) Select() { self.pressKey(self.keys.Universal.Select) } -func (self *InputImpl) NextItem() { +// i.e. pressing down arrow +func (self *Impl) NextItem() { self.pressKey(self.keys.Universal.NextItem) } -func (self *InputImpl) PreviousItem() { +// i.e. pressing up arrow +func (self *Impl) PreviousItem() { self.pressKey(self.keys.Universal.PrevItem) } -func (self *InputImpl) ContinueMerge() { +func (self *Impl) ContinueMerge() { self.PressKeys(self.keys.Universal.CreateRebaseOptionsMenu) self.assert.SelectedLineContains("continue") self.Confirm() } -func (self *InputImpl) ContinueRebase() { +func (self *Impl) ContinueRebase() { self.ContinueMerge() } -func (self *InputImpl) Wait(milliseconds int) { +// for when you want to allow lazygit to process something before continuing +func (self *Impl) Wait(milliseconds int) { time.Sleep(time.Duration(milliseconds) * time.Millisecond) } -func (self *InputImpl) LogUI(message string) { +func (self *Impl) LogUI(message string) { self.gui.LogUI(message) } -func (self *InputImpl) Log(message string) { +func (self *Impl) Log(message string) { self.gui.LogUI(message) } +// this will look for a list item in the current panel and if it finds it, it will +// enter the keypresses required to navigate to it. +// The test will fail if: +// - the user is not in a list item +// - no list item is found containing the given text +// - multiple list items are found containing the given text in the initial page of items +// // 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) { +func (self *Impl) NavigateToListItemContainingText(text string) { self.assert.InListContext() currentContext := self.gui.CurrentContext().(types.IListContext) diff --git a/pkg/integration/helpers/shell.go b/pkg/integration/helpers/shell.go index fdfb0d231..b70a8ffaa 100644 --- a/pkg/integration/helpers/shell.go +++ b/pkg/integration/helpers/shell.go @@ -5,16 +5,20 @@ import ( "io/ioutil" "os" - "github.com/jesseduffield/lazygit/pkg/integration/types" "github.com/jesseduffield/lazygit/pkg/secureexec" "github.com/mgutz/str" ) -type ShellImpl struct{} +// this is for running shell commands, mostly for the sake of setting up the repo +// but you can also run the commands from within lazygit to emulate things happening +// in the background. +type Shell struct{} -var _ types.Shell = &ShellImpl{} +func NewShell() *Shell { + return &Shell{} +} -func (s *ShellImpl) RunCommand(cmdStr string) types.Shell { +func (s *Shell) RunCommand(cmdStr string) *Shell { args := str.ToArgv(cmdStr) cmd := secureexec.Command(args[0], args[1:]...) cmd.Env = os.Environ() @@ -27,7 +31,7 @@ func (s *ShellImpl) RunCommand(cmdStr string) types.Shell { return s } -func (s *ShellImpl) CreateFile(path string, content string) types.Shell { +func (s *Shell) CreateFile(path string, content string) *Shell { err := ioutil.WriteFile(path, []byte(content), 0o644) if err != nil { panic(fmt.Sprintf("error creating file: %s\n%s", path, err)) @@ -36,33 +40,37 @@ func (s *ShellImpl) CreateFile(path string, content string) types.Shell { return s } -func (s *ShellImpl) NewBranch(name string) types.Shell { +func (s *Shell) NewBranch(name string) *Shell { return s.RunCommand("git checkout -b " + name) } -func (s *ShellImpl) GitAdd(path string) types.Shell { +func (s *Shell) GitAdd(path string) *Shell { return s.RunCommand(fmt.Sprintf("git add \"%s\"", path)) } -func (s *ShellImpl) GitAddAll() types.Shell { +func (s *Shell) GitAddAll() *Shell { return s.RunCommand("git add -A") } -func (s *ShellImpl) Commit(message string) types.Shell { +func (s *Shell) Commit(message string) *Shell { return s.RunCommand(fmt.Sprintf("git commit -m \"%s\"", message)) } -func (s *ShellImpl) EmptyCommit(message string) types.Shell { +func (s *Shell) EmptyCommit(message string) *Shell { return s.RunCommand(fmt.Sprintf("git commit --allow-empty -m \"%s\"", message)) } -func (s *ShellImpl) CreateFileAndAdd(fileName string, fileContents string) types.Shell { +// convenience method for creating a file and adding it +func (s *Shell) CreateFileAndAdd(fileName string, fileContents string) *Shell { return s. CreateFile(fileName, fileContents). GitAdd(fileName) } -func (s *ShellImpl) CreateNCommits(n int) types.Shell { +// creates commits 01, 02, 03, ..., n with a new file in each +// The reason for padding with zeroes is so that it's easier to do string +// matches on the commit messages when there are many of them +func (s *Shell) CreateNCommits(n int) *Shell { for i := 1; i <= n; i++ { s.CreateFileAndAdd( fmt.Sprintf("file%02d.txt", i), diff --git a/pkg/integration/helpers/test_impl.go b/pkg/integration/helpers/test.go similarity index 56% rename from pkg/integration/helpers/test_impl.go rename to pkg/integration/helpers/test.go index eaec83561..6e5c45886 100644 --- a/pkg/integration/helpers/test_impl.go +++ b/pkg/integration/helpers/test.go @@ -6,37 +6,40 @@ import ( "strings" "github.com/jesseduffield/lazygit/pkg/config" - guiTypes "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/integration/types" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" "github.com/jesseduffield/lazygit/pkg/utils" ) -type TestImpl struct { +// Test describes an integration tests that will be run against the lazygit gui. + +type Test struct { name string description string extraCmdArgs string skip bool - setupRepo func(shell types.Shell) + setupRepo func(shell *Shell) setupConfig func(config *config.AppConfig) run func( - shell types.Shell, - input types.Input, - assert types.Assert, + shell *Shell, + input *Impl, + assert *Assert, keys config.KeybindingConfig, ) } +var _ integrationTypes.IntegrationTest = &Test{} + type NewTestArgs struct { Description string - SetupRepo func(shell types.Shell) + SetupRepo func(shell *Shell) SetupConfig func(config *config.AppConfig) - Run func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) + Run func(shell *Shell, input *Impl, assert *Assert, keys config.KeybindingConfig) ExtraCmdArgs string Skip bool } -func NewTest(args NewTestArgs) *TestImpl { - return &TestImpl{ +func NewTest(args NewTestArgs) *Test { + return &Test{ name: testNameFromFilePath(), description: args.Description, extraCmdArgs: args.ExtraCmdArgs, @@ -47,45 +50,48 @@ func NewTest(args NewTestArgs) *TestImpl { } } -var _ types.Test = (*TestImpl)(nil) - -func (self *TestImpl) Name() string { +func (self *Test) Name() string { return self.name } -func (self *TestImpl) Description() string { +func (self *Test) Description() string { return self.description } -func (self *TestImpl) ExtraCmdArgs() string { +func (self *Test) ExtraCmdArgs() string { return self.extraCmdArgs } -func (self *TestImpl) Skip() bool { +func (self *Test) Skip() bool { return self.skip } -func (self *TestImpl) SetupConfig(config *config.AppConfig) { +func (self *Test) SetupConfig(config *config.AppConfig) { self.setupConfig(config) } -func (self *TestImpl) SetupRepo(shell types.Shell) { +func (self *Test) SetupRepo(shell *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 guiTypes.GuiAdapter) { - shell := &ShellImpl{} - assert := &AssertImpl{gui: gui} +func (self *Test) Run(gui integrationTypes.GuiAdapter) { + shell := NewShell() + assert := NewAssert(gui) keys := gui.Keys() - input := NewInputImpl(gui, keys, assert, KeyPressDelay()) + input := NewInput(gui, keys, assert, KeyPressDelay()) self.run(shell, input, assert, keys) + + if KeyPressDelay() > 0 { + // the dev would want to see the final state if they're running in slow mode + input.Wait(2000) + } } func testNameFromFilePath() string { path := utils.FilePath(3) - name := strings.Split(path, "integration/integration_tests/")[1] + name := strings.Split(path, "integration/tests/")[1] return name[:len(name)-len(".go")] } diff --git a/pkg/integration/integration.go b/pkg/integration/integration.go index de440c4ea..cb2b79861 100644 --- a/pkg/integration/integration.go +++ b/pkg/integration/integration.go @@ -4,26 +4,49 @@ import ( "errors" "fmt" "io/ioutil" + "log" "os" "os/exec" "path/filepath" - "runtime/debug" + "strings" - "github.com/jesseduffield/lazygit/pkg/app" + "github.com/jesseduffield/generics/slices" "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" + "github.com/jesseduffield/lazygit/pkg/integration/tests" + "github.com/stretchr/testify/assert" ) // this is the integration runner for the new and improved integration interface +var Tests = tests.Tests + +type Mode int + +const ( + // Default: if a snapshot test fails, the we'll be asked whether we want to update it + ASK_TO_UPDATE_SNAPSHOT = iota + // fails the test if the snapshots don't match + CHECK_SNAPSHOT + // runs the test and updates the snapshot + UPDATE_SNAPSHOT + // This just makes use of the setup step of the test to get you into + // a lazygit session. Then you'll be able to do whatever you want. Useful + // when you want to test certain things without needing to manually set + // up the situation yourself. + // fails the test if the snapshots don't match + SANDBOX +) + +type ( + logf func(format string, formatArgs ...interface{}) +) + func RunTestsNew( - logf func(format string, formatArgs ...interface{}), + logf logf, runCmd func(cmd *exec.Cmd) error, - fnWrapper func(test types.Test, f func() error), + fnWrapper func(test *helpers.Test, f func() error), mode Mode, - onFail func(expected string, actual string, prefix string), includeSkipped bool, ) error { rootDir := GetRootDirectory() @@ -40,7 +63,7 @@ func RunTestsNew( return err } - for _, test := range integration_tests.Tests { + for _, test := range Tests { test := test fnWrapper(test, func() error { //nolint: thelper @@ -66,62 +89,55 @@ func RunTestsNew( configDir := filepath.Join(testPath, "used_config") - err = runLazygit(test, testPath, rootDir) + cmd, err := getLazygitCommandNew(test, testPath, rootDir) if err != nil { return err } - if mode == UPDATE_SNAPSHOT { - // create/update snapshot - err = oscommands.CopyDir(actualDir, expectedDir) - if err != nil { - return err - } - - if err := renameSpecialPaths(expectedDir); err != nil { - return err - } - - logf("%s", "updated snapshot") - } else { - if err := validateSameRepos(expectedDir, actualDir); err != nil { - return err - } - - // iterate through each repo in the expected dir and comparet to the corresponding repo in the actual dir - expectedFiles, err := ioutil.ReadDir(expectedDir) - if err != nil { - return err - } - - for _, f := range expectedFiles { - if !f.IsDir() { - return errors.New("unexpected file (as opposed to directory) in integration test 'expected' directory") - } - - // get corresponding file name from actual dir - actualRepoPath := filepath.Join(actualDir, f.Name()) - expectedRepoPath := filepath.Join(expectedDir, f.Name()) - - actualRepo, expectedRepo, err := generateSnapshots(actualRepoPath, expectedRepoPath) - if err != nil { - return err - } - - if expectedRepo != actualRepo { - // get the log file and print it - bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log")) - if err != nil { - return err - } - logf("%s", string(bytes)) - - onFail(expectedRepo, actualRepo, f.Name()) - } - } + err = runCmd(cmd) + if err != nil { + return err } - logf("test passed: %s", test.Name()) + switch mode { + case UPDATE_SNAPSHOT: + if err := updateSnapshot(logf, actualDir, expectedDir); err != nil { + return err + } + logf("Test passed: %s", test.Name()) + case CHECK_SNAPSHOT: + if err := compareSnapshots(logf, configDir, actualDir, expectedDir, test.Name()); err != nil { + return err + } + logf("Test passed: %s", test.Name()) + case ASK_TO_UPDATE_SNAPSHOT: + if _, err := os.Stat(expectedDir); os.IsNotExist(err) { + if err := updateSnapshot(logf, actualDir, expectedDir); err != nil { + return err + } + logf("No existing snapshot found for %s. Created snapshot.", test.Name()) + + return nil + } + + if err := compareSnapshots(logf, configDir, actualDir, expectedDir, test.Name()); err != nil { + logf("%s", err) + + // prompt user whether to update the snapshot (Y/N) + if promptUserToUpdateSnapshot() { + if err := updateSnapshot(logf, actualDir, expectedDir); err != nil { + return err + } + logf("Snapshot updated: %s", test.Name()) + } else { + return err + } + } + + logf("Test passed: %s", test.Name()) + case SANDBOX: + logf("Session exited") + } return nil }) @@ -130,12 +146,95 @@ func RunTestsNew( return nil } -func createFixtureNew(test types.Test, actualDir string, rootDir string) error { +func promptUserToUpdateSnapshot() bool { + fmt.Println("Test failed. Update snapshot? (y/n)") + var input string + fmt.Scanln(&input) + return input == "y" +} + +func updateSnapshot(logf logf, actualDir string, expectedDir string) error { + // create/update snapshot + err := oscommands.CopyDir(actualDir, expectedDir) + if err != nil { + return err + } + + if err := renameSpecialPaths(expectedDir); err != nil { + return err + } + + return err +} + +func compareSnapshots(logf logf, configDir string, actualDir string, expectedDir string, testName string) error { + // there are a couple of reasons we're not generating the snapshot in expectedDir directly: + // Firstly we don't want to have to revert our .git file back to .git_keep. + // Secondly, the act of calling git commands like 'git status' actually changes the index + // for some reason, and we don't want to leave your lazygit working tree dirty as a result. + expectedDirCopy := filepath.Join(os.TempDir(), "expected_dir_test", testName) + err := oscommands.CopyDir(expectedDir, expectedDirCopy) + if err != nil { + return err + } + + defer func() { + err := os.RemoveAll(expectedDirCopy) + if err != nil { + panic(err) + } + }() + + if err := restoreSpecialPaths(expectedDirCopy); err != nil { + return err + } + + err = validateSameRepos(expectedDirCopy, actualDir) + if err != nil { + return err + } + + // iterate through each repo in the expected dir and comparet to the corresponding repo in the actual dir + expectedFiles, err := ioutil.ReadDir(expectedDirCopy) + if err != nil { + return err + } + + for _, f := range expectedFiles { + if !f.IsDir() { + return errors.New("unexpected file (as opposed to directory) in integration test 'expected' directory") + } + + // get corresponding file name from actual dir + actualRepoPath := filepath.Join(actualDir, f.Name()) + expectedRepoPath := filepath.Join(expectedDirCopy, f.Name()) + + actualRepo, expectedRepo, err := generateSnapshots(actualRepoPath, expectedRepoPath) + if err != nil { + return err + } + + if expectedRepo != actualRepo { + // get the log file and print it + bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log")) + if err != nil { + return err + } + logf("%s", string(bytes)) + + return errors.New(getDiff(f.Name(), actualRepo, expectedRepo)) + } + } + + return nil +} + +func createFixtureNew(test *helpers.Test, actualDir string, rootDir string) error { if err := os.Chdir(actualDir); err != nil { panic(err) } - shell := &helpers.ShellImpl{} + shell := helpers.NewShell() shell.RunCommand("git init") shell.RunCommand(`git config user.email "CI@example.com"`) shell.RunCommand(`git config user.name "CI"`) @@ -150,7 +249,9 @@ func createFixtureNew(test types.Test, actualDir string, rootDir string) error { return nil } -func runLazygit(test types.Test, testPath string, rootDir string) error { +func getLazygitCommandNew(test *helpers.Test, testPath string, rootDir string) (*exec.Cmd, error) { + osCommand := oscommands.NewDummyOSCommand() + templateConfigDir := filepath.Join(rootDir, "test", "default_test_config") actualRepoDir := filepath.Join(testPath, "actual", "repo") @@ -158,38 +259,278 @@ func runLazygit(test types.Test, testPath string, rootDir string) error { err := os.RemoveAll(configDir) if err != nil { - return err + return nil, err } err = oscommands.CopyDir(templateConfigDir, configDir) + if err != nil { + return nil, err + } + + cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s %s", tempLazygitPath(), configDir, actualRepoDir, test.ExtraCmdArgs()) + + cmdObj := osCommand.Cmd.New(cmdStr) + + cmdObj.AddEnvVars(fmt.Sprintf("LAZYGIT_TEST_NAME=%s", test.Name())) + + return cmdObj.GetCmd(), nil +} + +func GetModeFromEnv() Mode { + switch os.Getenv("MODE") { + case "", "ask": + return ASK_TO_UPDATE_SNAPSHOT + case "check": + return CHECK_SNAPSHOT + case "updateSnapshot": + return UPDATE_SNAPSHOT + case "sandbox": + return SANDBOX + default: + log.Fatalf("unknown test mode: %s, must be one of [test, record, updateSnapshot, sandbox]", os.Getenv("MODE")) + panic("unreachable") + } +} + +func GetRootDirectory() string { + path, err := os.Getwd() + if err != nil { + panic(err) + } + + for { + _, err := os.Stat(filepath.Join(path, ".git")) + + if err == nil { + return path + } + + if !os.IsNotExist(err) { + panic(err) + } + + path = filepath.Dir(path) + + if path == "/" { + log.Fatal("must run in lazygit folder or child folder") + } + } +} + +func tempLazygitPath() string { + return filepath.Join("/tmp", "lazygit", "test_lazygit") +} + +func generateSnapshots(actualDir string, expectedDir string) (string, string, error) { + actual, err := generateSnapshot(actualDir) + if err != nil { + return "", "", err + } + + expected, err := generateSnapshot(expectedDir) + if err != nil { + return "", "", err + } + + return actual, expected, nil +} + +// note that we don't actually store this snapshot in the lazygit repo. +// Instead we store the whole expected git repo of our test, so that +// we can easily change what we want to compare without needing to regenerate +// snapshots for each test. +func generateSnapshot(dir string) (string, error) { + osCommand := oscommands.NewDummyOSCommand() + + _, err := os.Stat(filepath.Join(dir, ".git")) + if err != nil { + return "git directory not found", nil + } + + snapshot := "" + + cmdStrs := []string{ + `remote show -n origin`, // remote branches + // TODO: find a way to bring this back without breaking tests + // `ls-remote origin`, + `status`, // file tree + `log --pretty=%B|%an|%ae -p -1`, // log + `tag -n`, // tags + `stash list`, // stash + `submodule foreach 'git status'`, // submodule status + `submodule foreach 'git log --pretty=%B -p -1'`, // submodule log + `submodule foreach 'git tag -n'`, // submodule tags + `submodule foreach 'git stash list'`, // submodule stash + } + + for _, cmdStr := range cmdStrs { + // ignoring error for now. If there's an error it could be that there are no results + output, _ := osCommand.Cmd.New(fmt.Sprintf("git -C %s %s", dir, cmdStr)).RunWithOutput() + + snapshot += fmt.Sprintf("git %s:\n%s\n", cmdStr, output) + } + + snapshot += "files in repo:\n" + err = filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + if f.IsDir() { + if f.Name() == ".git" { + return filepath.SkipDir + } + return nil + } + + bytes, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + relativePath, err := filepath.Rel(dir, path) + if err != nil { + return err + } + snapshot += fmt.Sprintf("path: %s\ncontent:\n%s\n", relativePath, string(bytes)) + + return nil + }) + + if err != nil { + return "", err + } + + return snapshot, nil +} + +func getPathsToRename(dir string, needle string, contains string) []string { + pathsToRename := []string{} + + err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + if f.Name() == needle && (contains == "" || strings.Contains(path, contains)) { + pathsToRename = append(pathsToRename, path) + } + + return nil + }) + if err != nil { + panic(err) + } + + return pathsToRename +} + +var specialPathMappings = []struct{ original, new, contains string }{ + // git refuses to track .git or .gitmodules in subdirectories so we need to rename them + {".git", ".git_keep", ""}, + {".gitmodules", ".gitmodules_keep", ""}, + // we also need git to ignore the contents of our test gitignore files so that + // we actually commit files that are ignored within the test. + {".gitignore", "lg_ignore_file", ""}, + // this is the .git/info/exclude file. We're being a little more specific here + // so that we don't accidentally mess with some other file named 'exclude' in the test. + {"exclude", "lg_exclude_file", ".git/info/exclude"}, +} + +func renameSpecialPaths(dir string) error { + for _, specialPath := range specialPathMappings { + for _, path := range getPathsToRename(dir, specialPath.original, specialPath.contains) { + err := os.Rename(path, filepath.Join(filepath.Dir(path), specialPath.new)) + if err != nil { + return err + } + } + } + + return nil +} + +func restoreSpecialPaths(dir string) error { + for _, specialPath := range specialPathMappings { + for _, path := range getPathsToRename(dir, specialPath.new, specialPath.contains) { + err := os.Rename(path, filepath.Join(filepath.Dir(path), specialPath.original)) + if err != nil { + return err + } + } + } + + return nil +} + +// validates that the actual and expected dirs have the same repo names (doesn't actually check the contents of the repos) +func validateSameRepos(expectedDir string, actualDir string) error { + // iterate through each repo in the expected dir and compare to the corresponding repo in the actual dir + expectedFiles, err := ioutil.ReadDir(expectedDir) if err != nil { return err } - // TODO: support test.ExtraCmdArgs in some form. - cliArgs := &app.CliArgs{ - Debug: true, - UseConfigDir: configDir, - RepoPath: actualRepoDir, + var actualFiles []os.FileInfo + actualFiles, err = ioutil.ReadDir(actualDir) + if err != nil { + return err } - buildInfo := &app.BuildInfo{ - Commit: "1234abc", - Date: "2020-01-01", - Version: "1.0.0", - BuildSource: "unknown", + expectedFileNames := slices.Map(expectedFiles, getFileName) + actualFileNames := slices.Map(actualFiles, getFileName) + if !slices.Equal(expectedFileNames, actualFileNames) { + return fmt.Errorf("expected and actual repo dirs do not match: expected: %s, actual: %s", expectedFileNames, actualFileNames) } - return convertPanicToError(func() { app.Start(cliArgs, buildInfo, test) }) -} - -func convertPanicToError(f func()) (err error) { //nolint: nakedret - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("Lazygit panicked. Stacktrace:: \n" + string(debug.Stack())) - } - }() - - f() - return nil } + +func getFileName(f os.FileInfo) string { + return f.Name() +} + +func findOrCreateDir(path string) { + _, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + err = os.MkdirAll(path, 0o777) + if err != nil { + panic(err) + } + } else { + panic(err) + } + } +} + +func prepareIntegrationTestDir(actualDir string) { + // remove contents of integration test directory + dir, err := ioutil.ReadDir(actualDir) + if err != nil { + if os.IsNotExist(err) { + err = os.Mkdir(actualDir, 0o777) + if err != nil { + panic(err) + } + } else { + panic(err) + } + } + for _, d := range dir { + os.RemoveAll(filepath.Join(actualDir, d.Name())) + } +} + +func getDiff(prefix string, expected string, actual string) string { + mockT := &MockTestingT{} + assert.Equal(mockT, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual)) + return mockT.err +} + +type MockTestingT struct { + err string +} + +func (self *MockTestingT) Errorf(format string, args ...interface{}) { + self.err += fmt.Sprintf(format, args...) +} diff --git a/pkg/gui/gui_test.go b/pkg/integration/integration_test.go similarity index 73% rename from pkg/gui/gui_test.go rename to pkg/integration/integration_test.go index 2d698d34c..3e9da8fe9 100644 --- a/pkg/gui/gui_test.go +++ b/pkg/integration/integration_test.go @@ -1,13 +1,12 @@ //go:build !windows // +build !windows -package gui +package integration // this is the new way of running tests. See pkg/integration/integration_tests/commit.go // for an example import ( - "fmt" "io" "io/ioutil" "os" @@ -16,27 +15,26 @@ import ( "testing" "github.com/creack/pty" - "github.com/jesseduffield/lazygit/pkg/integration" - "github.com/jesseduffield/lazygit/pkg/integration/types" + "github.com/jesseduffield/lazygit/pkg/integration/helpers" "github.com/stretchr/testify/assert" ) -func Test(t *testing.T) { +func TestIntegration(t *testing.T) { if testing.Short() { t.Skip("Skipping integration tests in short mode") } - mode := integration.GetModeFromEnv() + mode := GetModeFromEnv() includeSkipped := os.Getenv("INCLUDE_SKIPPED") != "" parallelTotal := tryConvert(os.Getenv("PARALLEL_TOTAL"), 1) parallelIndex := tryConvert(os.Getenv("PARALLEL_INDEX"), 0) testNumber := 0 - err := integration.RunTestsNew( + err := RunTestsNew( t.Logf, runCmdHeadless, - func(test types.Test, f func() error) { + func(test *helpers.Test, f func() error) { defer func() { testNumber += 1 }() if testNumber%parallelTotal != parallelIndex { return @@ -48,9 +46,6 @@ func Test(t *testing.T) { }) }, mode, - 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/integration/integration_tests/tests.go b/pkg/integration/integration_tests/tests.go deleted file mode 100644 index 752929698..000000000 --- a/pkg/integration/integration_tests/tests.go +++ /dev/null @@ -1,19 +0,0 @@ -package integration_tests - -import ( - "github.com/jesseduffield/lazygit/pkg/integration/integration_tests/branch" - "github.com/jesseduffield/lazygit/pkg/integration/integration_tests/commit" - "github.com/jesseduffield/lazygit/pkg/integration/integration_tests/interactive_rebase" - - "github.com/jesseduffield/lazygit/pkg/integration/types" -) - -// Here is where we lists the actual tests that will run. When you create a new test, -// be sure to add it to this list. - -var Tests = []types.Test{ - commit.Commit, - commit.NewBranch, - branch.Suggestions, - interactive_rebase.One, -} diff --git a/test/runner/main.go b/pkg/integration/runner/main.go similarity index 69% rename from test/runner/main.go rename to pkg/integration/runner/main.go index eef767564..07d1bf28f 100644 --- a/test/runner/main.go +++ b/pkg/integration/runner/main.go @@ -1,15 +1,12 @@ package main import ( - "fmt" "log" "os" "os/exec" "github.com/jesseduffield/lazygit/pkg/integration" - "github.com/jesseduffield/lazygit/pkg/integration/integration_tests" - "github.com/jesseduffield/lazygit/pkg/integration/types" - "github.com/stretchr/testify/assert" + "github.com/jesseduffield/lazygit/pkg/integration/helpers" ) // see docs/Integration_Tests.md @@ -28,7 +25,7 @@ func main() { // check if our given test name actually exists if selectedTestName != "" { found := false - for _, test := range integration_tests.Tests { + for _, test := range integration.Tests { if test.Name() == selectedTestName { found = true break @@ -42,7 +39,7 @@ func main() { err := integration.RunTestsNew( log.Printf, runCmdInTerminal, - func(test types.Test, f func() error) { + func(test *helpers.Test, f func() error) { if selectedTestName != "" && test.Name() != selectedTestName { return } @@ -51,9 +48,6 @@ func main() { } }, mode, - func(expected string, actual string, prefix string) { //nolint:thelper - assert.Equal(MockTestingT{}, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual)) - }, includeSkipped, ) if err != nil { @@ -61,12 +55,6 @@ func main() { } } -type MockTestingT struct{} - -func (t MockTestingT) Errorf(format string, args ...interface{}) { - fmt.Printf(format, args...) -} - func runCmdInTerminal(cmd *exec.Cmd) error { cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin diff --git a/pkg/integration/integration_tests/branch/suggestions.go b/pkg/integration/tests/branch/suggestions.go similarity index 83% rename from pkg/integration/integration_tests/branch/suggestions.go rename to pkg/integration/tests/branch/suggestions.go index a925280e7..edceb5e8e 100644 --- a/pkg/integration/integration_tests/branch/suggestions.go +++ b/pkg/integration/tests/branch/suggestions.go @@ -3,7 +3,6 @@ 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 = helpers.NewTest(helpers.NewTestArgs{ @@ -11,7 +10,7 @@ var Suggestions = helpers.NewTest(helpers.NewTestArgs{ ExtraCmdArgs: "", Skip: false, SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell types.Shell) { + SetupRepo: func(shell *helpers.Shell) { shell. EmptyCommit("my commit message"). NewBranch("new-branch"). @@ -21,7 +20,7 @@ var Suggestions = helpers.NewTest(helpers.NewTestArgs{ NewBranch("other-new-branch-2"). NewBranch("other-new-branch-3") }, - Run: func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) { + Run: func(shell *helpers.Shell, input *helpers.Impl, assert *helpers.Assert, keys config.KeybindingConfig) { input.SwitchToBranchesWindow() input.PressKeys(keys.Branches.CheckoutBranchByName) diff --git a/pkg/integration/integration_tests/commit/commit.go b/pkg/integration/tests/commit/commit.go similarity index 78% rename from pkg/integration/integration_tests/commit/commit.go rename to pkg/integration/tests/commit/commit.go index 9fd3bb356..932d9cf6f 100644 --- a/pkg/integration/integration_tests/commit/commit.go +++ b/pkg/integration/tests/commit/commit.go @@ -3,7 +3,6 @@ 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 = helpers.NewTest(helpers.NewTestArgs{ @@ -11,11 +10,11 @@ var Commit = helpers.NewTest(helpers.NewTestArgs{ ExtraCmdArgs: "", Skip: false, SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell types.Shell) { + SetupRepo: func(shell *helpers.Shell) { shell.CreateFile("myfile", "myfile content") shell.CreateFile("myfile2", "myfile2 content") }, - Run: func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) { + Run: func(shell *helpers.Shell, input *helpers.Impl, assert *helpers.Assert, keys config.KeybindingConfig) { assert.CommitCount(0) input.Select() diff --git a/pkg/integration/integration_tests/commit/new_branch.go b/pkg/integration/tests/commit/new_branch.go similarity index 80% rename from pkg/integration/integration_tests/commit/new_branch.go rename to pkg/integration/tests/commit/new_branch.go index 9669937fa..e1e5090e8 100644 --- a/pkg/integration/integration_tests/commit/new_branch.go +++ b/pkg/integration/tests/commit/new_branch.go @@ -3,7 +3,6 @@ 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 = helpers.NewTest(helpers.NewTestArgs{ @@ -11,13 +10,13 @@ var NewBranch = helpers.NewTest(helpers.NewTestArgs{ ExtraCmdArgs: "", Skip: false, SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell types.Shell) { + SetupRepo: func(shell *helpers.Shell) { shell. EmptyCommit("commit 1"). EmptyCommit("commit 2"). EmptyCommit("commit 3") }, - Run: func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) { + Run: func(shell *helpers.Shell, input *helpers.Impl, assert *helpers.Assert, keys config.KeybindingConfig) { assert.CommitCount(3) input.SwitchToCommitsWindow() diff --git a/pkg/integration/integration_tests/interactive_rebase/one.go b/pkg/integration/tests/interactive_rebase/one.go similarity index 84% rename from pkg/integration/integration_tests/interactive_rebase/one.go rename to pkg/integration/tests/interactive_rebase/one.go index d8899569c..d901b9c47 100644 --- a/pkg/integration/integration_tests/interactive_rebase/one.go +++ b/pkg/integration/tests/interactive_rebase/one.go @@ -3,7 +3,6 @@ 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 = helpers.NewTest(helpers.NewTestArgs{ @@ -11,11 +10,11 @@ var One = helpers.NewTest(helpers.NewTestArgs{ ExtraCmdArgs: "", Skip: false, SetupConfig: func(config *config.AppConfig) {}, - SetupRepo: func(shell types.Shell) { + SetupRepo: func(shell *helpers.Shell) { shell. CreateNCommits(5) // these will appears at commit 05, 04, 04, down to 01 }, - Run: func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) { + Run: func(shell *helpers.Shell, input *helpers.Impl, assert *helpers.Assert, keys config.KeybindingConfig) { input.SwitchToCommitsWindow() assert.CurrentViewName("commits") diff --git a/pkg/integration/tests/tests.go b/pkg/integration/tests/tests.go new file mode 100644 index 000000000..c2e360e43 --- /dev/null +++ b/pkg/integration/tests/tests.go @@ -0,0 +1,18 @@ +package tests + +import ( + "github.com/jesseduffield/lazygit/pkg/integration/helpers" + "github.com/jesseduffield/lazygit/pkg/integration/tests/branch" + "github.com/jesseduffield/lazygit/pkg/integration/tests/commit" + "github.com/jesseduffield/lazygit/pkg/integration/tests/interactive_rebase" +) + +// Here is where we lists the actual tests that will run. When you create a new test, +// be sure to add it to this list. + +var Tests = []*helpers.Test{ + commit.Commit, + commit.NewBranch, + branch.Suggestions, + interactive_rebase.One, +} diff --git a/pkg/integration/tui/main.go b/pkg/integration/tui/main.go new file mode 100644 index 000000000..023c764b5 --- /dev/null +++ b/pkg/integration/tui/main.go @@ -0,0 +1,323 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/gui" + "github.com/jesseduffield/lazygit/pkg/gui/style" + "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/jesseduffield/lazygit/pkg/integration/helpers" + "github.com/jesseduffield/lazygit/pkg/secureexec" +) + +// this program lets you manage integration tests in a TUI. See docs/Integration_Tests.md for more info. + +type App struct { + tests []*helpers.Test + itemIdx int + testDir string + filtering bool + g *gocui.Gui +} + +func (app *App) getCurrentTest() *helpers.Test { + if len(app.tests) > 0 { + return app.tests[app.itemIdx] + } + return nil +} + +func (app *App) refreshTests() { + app.loadTests() + app.g.Update(func(*gocui.Gui) error { + listView, err := app.g.View("list") + if err != nil { + return err + } + + listView.Clear() + for _, test := range app.tests { + fmt.Fprintln(listView, test.Name()) + } + + return nil + }) +} + +func (app *App) loadTests() { + app.tests = integration.Tests + if app.itemIdx > len(app.tests)-1 { + app.itemIdx = len(app.tests) - 1 + } +} + +func main() { + rootDir := integration.GetRootDirectory() + testDir := filepath.Join(rootDir, "test", "integration") + + app := &App{testDir: testDir} + app.loadTests() + + g, err := gocui.NewGui(gocui.OutputTrue, false, gocui.NORMAL, false, gui.RuneReplacements) + if err != nil { + log.Panicln(err) + } + + g.Cursor = false + + app.g = g + + g.SetManagerFunc(app.layout) + + if err := g.SetKeybinding("list", gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + if app.itemIdx > 0 { + app.itemIdx-- + } + listView, err := g.View("list") + if err != nil { + return err + } + listView.FocusPoint(0, app.itemIdx) + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", 'q', gocui.ModNone, quit); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", 's', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + currentTest := app.getCurrentTest() + if currentTest == nil { + return nil + } + + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=sandbox go run pkg/integration/runner/main.go %s", currentTest.Name())) + app.runSubprocess(cmd) + + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + currentTest := app.getCurrentTest() + if currentTest == nil { + return nil + } + + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true go run pkg/integration/runner/main.go %s", currentTest.Name())) + app.runSubprocess(cmd) + + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", 't', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + currentTest := app.getCurrentTest() + if currentTest == nil { + return nil + } + + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true KEY_PRESS_DELAY=200 go run pkg/integration/runner/main.go %s", currentTest.Name())) + app.runSubprocess(cmd) + + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", 'o', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + currentTest := app.getCurrentTest() + if currentTest == nil { + return nil + } + + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("code -r pkg/integration/tests/%s", currentTest.Name())) + if err := cmd.Run(); err != nil { + return err + } + + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", 'O', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + currentTest := app.getCurrentTest() + if currentTest == nil { + return nil + } + + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("code test/integration_new/%s", currentTest.Name())) + if err := cmd.Run(); err != nil { + return err + } + + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", '/', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + app.filtering = true + if _, err := g.SetCurrentView("editor"); err != nil { + return err + } + editorView, err := g.View("editor") + if err != nil { + return err + } + editorView.Clear() + + return nil + }); err != nil { + log.Panicln(err) + } + + // not using the editor yet, but will use it to help filter the list + if err := g.SetKeybinding("editor", gocui.KeyEsc, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + app.filtering = false + if _, err := g.SetCurrentView("list"); err != nil { + return err + } + + return nil + }); err != nil { + log.Panicln(err) + } + + err = g.MainLoop() + g.Close() + switch err { + case gocui.ErrQuit: + return + default: + log.Panicln(err) + } +} + +func (app *App) runSubprocess(cmd *exec.Cmd) { + if err := gocui.Screen.Suspend(); err != nil { + panic(err) + } + + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + log.Println(err.Error()) + } + cmd.Stdin = nil + cmd.Stderr = nil + cmd.Stdout = nil + + fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint("press enter to return")) + fmt.Scanln() // wait for enter press + + if err := gocui.Screen.Resume(); err != nil { + panic(err) + } +} + +func (app *App) layout(g *gocui.Gui) error { + maxX, maxY := g.Size() + descriptionViewHeight := 7 + keybindingsViewHeight := 3 + editorViewHeight := 3 + if !app.filtering { + editorViewHeight = 0 + } else { + descriptionViewHeight = 0 + keybindingsViewHeight = 0 + } + g.Cursor = app.filtering + g.FgColor = gocui.ColorGreen + listView, err := g.SetView("list", 0, 0, maxX-1, maxY-descriptionViewHeight-keybindingsViewHeight-editorViewHeight-1, 0) + if err != nil { + if err.Error() != "unknown view" { + return err + } + listView.Highlight = true + listView.Clear() + for _, test := range app.tests { + fmt.Fprintln(listView, test.Name()) + } + listView.Title = "Tests" + listView.FgColor = gocui.ColorDefault + if _, err := g.SetCurrentView("list"); err != nil { + return err + } + } + + descriptionView, err := g.SetViewBeneath("description", "list", descriptionViewHeight) + if err != nil { + if err.Error() != "unknown view" { + return err + } + descriptionView.Title = "Test description" + descriptionView.Wrap = true + descriptionView.FgColor = gocui.ColorDefault + } + + keybindingsView, err := g.SetViewBeneath("keybindings", "description", keybindingsViewHeight) + if err != nil { + if err.Error() != "unknown view" { + return err + } + keybindingsView.Title = "Keybindings" + keybindingsView.Wrap = true + keybindingsView.FgColor = gocui.ColorDefault + fmt.Fprintln(keybindingsView, "up/down: navigate, enter: run test, t: run test slow, s: sandbox, o: open test file, shift+o: open test snapshot directory, forward-slash: filter") + } + + editorView, err := g.SetViewBeneath("editor", "keybindings", editorViewHeight) + if err != nil { + if err.Error() != "unknown view" { + return err + } + editorView.Title = "Filter" + editorView.FgColor = gocui.ColorDefault + editorView.Editable = true + } + + currentTest := app.getCurrentTest() + if currentTest == nil { + return nil + } + + descriptionView.Clear() + fmt.Fprint(descriptionView, currentTest.Description()) + + if err := g.SetKeybinding("list", gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + if app.itemIdx < len(app.tests)-1 { + app.itemIdx++ + } + + listView, err := g.View("list") + if err != nil { + return err + } + listView.FocusPoint(0, app.itemIdx) + return nil + }); err != nil { + log.Panicln(err) + } + + return nil +} + +func quit(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit +} diff --git a/pkg/integration/types/types.go b/pkg/integration/types/types.go index ad4c942ea..da25b8f9d 100644 --- a/pkg/integration/types/types.go +++ b/pkg/integration/types/types.go @@ -1,99 +1,31 @@ package types import ( + "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/config" "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. +// these interfaces are used by the gui package so that it knows what it needs +// to provide to a test in order for the test to run. -type Test interface { - Name() string - Description() string - // this is called before lazygit is run, for the sake of preparing the repo - SetupRepo(Shell) - // this gives you the default config and lets you set whatever values on it you like, - // so that they appear when lazygit runs +type IntegrationTest interface { + Run(GuiAdapter) SetupConfig(config *config.AppConfig) - // this is called upon lazygit starting - Run(types.GuiAdapter) - // e.g. '-debug' - ExtraCmdArgs() string - // for tests that are flakey and when we don't have time to fix them - Skip() bool } -// this is for running shell commands, mostly for the sake of setting up the repo -// but you can also run the commands from within lazygit to emulate things happening -// in the background. -// Implementation is at pkg/integration/shell.go -type Shell interface { - RunCommand(command string) Shell - CreateFile(name string, content string) Shell - NewBranch(branchName string) Shell - GitAdd(path string) Shell - GitAddAll() Shell - Commit(message string) Shell - EmptyCommit(message string) Shell - // convenience method for creating a file and adding it - CreateFileAndAdd(fileName string, fileContents string) Shell - // creates commits 01, 02, 03, ..., n with a new file in each - // The reason for padding with zeroes is so that it's easier to do string - // matches on the commit messages when there are many of them - CreateNCommits(n int) Shell -} - -// through this interface our test interacts with the lazygit gui -// Implementation is at pkg/gui/input.go -type Input interface { - // key is something like 'w' or ''. It's best not to pass a direct value, - // but instead to go through the default user config to get a more meaningful key name - PressKeys(keys ...string) - // for typing into a popup prompt - Type(content string) - // for when you want to allow lazygit to process something before continuing - Wait(milliseconds int) - // going straight to a particular side window - SwitchToStatusWindow() - SwitchToFilesWindow() - SwitchToBranchesWindow() - SwitchToCommitsWindow() - SwitchToStashWindow() - // i.e. pressing enter - Confirm() - // i.e. pressing escape - Cancel() - // i.e. pressing space - Select() - // i.e. pressing down arrow - NextItem() - // i.e. pressing up arrow - PreviousItem() - // this will look for a list item in the current panel and if it finds it, it will - // enter the keypresses required to navigate to it. - // The test will fail if: - // - the user is not in a list item - // - no list item is found containing the given text - // - multiple list items are found containing the given text in the initial page of items - NavigateToListItemContainingText(text string) - ContinueRebase() - ContinueMerge() -} - -// 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) - HeadCommitMessage(string) - CurrentViewName(expectedViewName string) - CurrentBranchName(expectedBranchName string) - InListContext() - SelectedLineContains(text string) - // for when you just want to fail the test yourself - Fail(errorMessage string) +// this is the interface through which our integration tests interact with the lazygit gui +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 } diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/index b/test/integration_new/commit/commit/expected/repo/.git_keep/index index d72d7c026..31a81c209 100644 Binary files a/test/integration_new/commit/commit/expected/repo/.git_keep/index and b/test/integration_new/commit/commit/expected/repo/.git_keep/index differ diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/logs/HEAD b/test/integration_new/commit/commit/expected/repo/.git_keep/logs/HEAD index cc260688d..925bffbb2 100644 --- a/test/integration_new/commit/commit/expected/repo/.git_keep/logs/HEAD +++ b/test/integration_new/commit/commit/expected/repo/.git_keep/logs/HEAD @@ -1 +1 @@ -0000000000000000000000000000000000000000 460150760ff1f381c3f5769b919cb73107c5871a CI 1659863059 +1000 commit (initial): my commit message +0000000000000000000000000000000000000000 944b9ea58bef8f6352c3a081a1d0037125bcaabc CI 1660133266 +1000 commit (initial): my commit message diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/logs/refs/heads/master b/test/integration_new/commit/commit/expected/repo/.git_keep/logs/refs/heads/master index cc260688d..925bffbb2 100644 --- a/test/integration_new/commit/commit/expected/repo/.git_keep/logs/refs/heads/master +++ b/test/integration_new/commit/commit/expected/repo/.git_keep/logs/refs/heads/master @@ -1 +1 @@ -0000000000000000000000000000000000000000 460150760ff1f381c3f5769b919cb73107c5871a CI 1659863059 +1000 commit (initial): my commit message +0000000000000000000000000000000000000000 944b9ea58bef8f6352c3a081a1d0037125bcaabc CI 1660133266 +1000 commit (initial): my commit message diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/objects/46/0150760ff1f381c3f5769b919cb73107c5871a b/test/integration_new/commit/commit/expected/repo/.git_keep/objects/46/0150760ff1f381c3f5769b919cb73107c5871a deleted file mode 100644 index 4792ad56d..000000000 Binary files a/test/integration_new/commit/commit/expected/repo/.git_keep/objects/46/0150760ff1f381c3f5769b919cb73107c5871a and /dev/null differ diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/objects/94/4b9ea58bef8f6352c3a081a1d0037125bcaabc b/test/integration_new/commit/commit/expected/repo/.git_keep/objects/94/4b9ea58bef8f6352c3a081a1d0037125bcaabc new file mode 100644 index 000000000..edba03fb8 Binary files /dev/null and b/test/integration_new/commit/commit/expected/repo/.git_keep/objects/94/4b9ea58bef8f6352c3a081a1d0037125bcaabc differ diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/refs/heads/master b/test/integration_new/commit/commit/expected/repo/.git_keep/refs/heads/master index a21004dfc..7b10e3bcb 100644 --- a/test/integration_new/commit/commit/expected/repo/.git_keep/refs/heads/master +++ b/test/integration_new/commit/commit/expected/repo/.git_keep/refs/heads/master @@ -1 +1 @@ -460150760ff1f381c3f5769b919cb73107c5871a +944b9ea58bef8f6352c3a081a1d0037125bcaabc diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/HEAD b/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/HEAD index b641844cb..189a2b0bc 100644 --- a/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/HEAD +++ b/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/HEAD @@ -1,4 +1,4 @@ -0000000000000000000000000000000000000000 470038e1336649b2965305f9f6a82501a836810e CI 1659865912 +1000 commit (initial): commit 1 -470038e1336649b2965305f9f6a82501a836810e c8bec8f2b323cbb476e708bd10c145ea7cc9f726 CI 1659865912 +1000 commit: commit 2 -c8bec8f2b323cbb476e708bd10c145ea7cc9f726 62a60693a2e154e745ee353f67a05156d0532c23 CI 1659865912 +1000 commit: commit 3 -62a60693a2e154e745ee353f67a05156d0532c23 c8bec8f2b323cbb476e708bd10c145ea7cc9f726 CI 1659865912 +1000 checkout: moving from master to my-branch-name +0000000000000000000000000000000000000000 4e72cd440eec154569568bff8d4c955052ae246c CI 1660125381 +1000 commit (initial): commit 1 +4e72cd440eec154569568bff8d4c955052ae246c 563414ba32c967cfbe21a17fe892d6118c1c58e8 CI 1660125381 +1000 commit: commit 2 +563414ba32c967cfbe21a17fe892d6118c1c58e8 0af36e404e6fec1c3a4d887e30622238e5ea0b2b CI 1660125381 +1000 commit: commit 3 +0af36e404e6fec1c3a4d887e30622238e5ea0b2b 563414ba32c967cfbe21a17fe892d6118c1c58e8 CI 1660125382 +1000 checkout: moving from master to my-branch-name diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/master b/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/master index 57ad94e9f..0e17a4008 100644 --- a/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/master +++ b/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/master @@ -1,3 +1,3 @@ -0000000000000000000000000000000000000000 470038e1336649b2965305f9f6a82501a836810e CI 1659865912 +1000 commit (initial): commit 1 -470038e1336649b2965305f9f6a82501a836810e c8bec8f2b323cbb476e708bd10c145ea7cc9f726 CI 1659865912 +1000 commit: commit 2 -c8bec8f2b323cbb476e708bd10c145ea7cc9f726 62a60693a2e154e745ee353f67a05156d0532c23 CI 1659865912 +1000 commit: commit 3 +0000000000000000000000000000000000000000 4e72cd440eec154569568bff8d4c955052ae246c CI 1660125381 +1000 commit (initial): commit 1 +4e72cd440eec154569568bff8d4c955052ae246c 563414ba32c967cfbe21a17fe892d6118c1c58e8 CI 1660125381 +1000 commit: commit 2 +563414ba32c967cfbe21a17fe892d6118c1c58e8 0af36e404e6fec1c3a4d887e30622238e5ea0b2b CI 1660125381 +1000 commit: commit 3 diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/my-branch-name b/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/my-branch-name index 7e8364dfe..6f401d926 100644 --- a/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/my-branch-name +++ b/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/my-branch-name @@ -1 +1 @@ -0000000000000000000000000000000000000000 c8bec8f2b323cbb476e708bd10c145ea7cc9f726 CI 1659865912 +1000 branch: Created from c8bec8f2b323cbb476e708bd10c145ea7cc9f726 +0000000000000000000000000000000000000000 563414ba32c967cfbe21a17fe892d6118c1c58e8 CI 1660125382 +1000 branch: Created from 563414ba32c967cfbe21a17fe892d6118c1c58e8 diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/0a/f36e404e6fec1c3a4d887e30622238e5ea0b2b b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/0a/f36e404e6fec1c3a4d887e30622238e5ea0b2b new file mode 100644 index 000000000..eb9800f0a Binary files /dev/null and b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/0a/f36e404e6fec1c3a4d887e30622238e5ea0b2b differ diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/47/0038e1336649b2965305f9f6a82501a836810e b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/47/0038e1336649b2965305f9f6a82501a836810e deleted file mode 100644 index 5099d76b8..000000000 Binary files a/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/47/0038e1336649b2965305f9f6a82501a836810e and /dev/null differ diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/4e/72cd440eec154569568bff8d4c955052ae246c b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/4e/72cd440eec154569568bff8d4c955052ae246c new file mode 100644 index 000000000..40f7e1d72 Binary files /dev/null and b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/4e/72cd440eec154569568bff8d4c955052ae246c differ diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/56/3414ba32c967cfbe21a17fe892d6118c1c58e8 b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/56/3414ba32c967cfbe21a17fe892d6118c1c58e8 new file mode 100644 index 000000000..1a610226f --- /dev/null +++ b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/56/3414ba32c967cfbe21a17fe892d6118c1c58e8 @@ -0,0 +1,2 @@ +xA +1 @Q=E1-m&uw-ͭ=:,D22YU ))sFKYՁD62Ea,q,BʼnBymm*FcMu \ No newline at end of file diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/refs/heads/master b/test/integration_new/commit/new_branch/expected/repo/.git_keep/refs/heads/master index 5c73ff150..003b5b1fd 100644 --- a/test/integration_new/commit/new_branch/expected/repo/.git_keep/refs/heads/master +++ b/test/integration_new/commit/new_branch/expected/repo/.git_keep/refs/heads/master @@ -1 +1 @@ -62a60693a2e154e745ee353f67a05156d0532c23 +0af36e404e6fec1c3a4d887e30622238e5ea0b2b diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/refs/heads/my-branch-name b/test/integration_new/commit/new_branch/expected/repo/.git_keep/refs/heads/my-branch-name index 75da99d74..7d25fb5a8 100644 --- a/test/integration_new/commit/new_branch/expected/repo/.git_keep/refs/heads/my-branch-name +++ b/test/integration_new/commit/new_branch/expected/repo/.git_keep/refs/heads/my-branch-name @@ -1 +1 @@ -c8bec8f2b323cbb476e708bd10c145ea7cc9f726 +563414ba32c967cfbe21a17fe892d6118c1c58e8 diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/COMMIT_EDITMSG b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/COMMIT_EDITMSG index 9e0c89102..b572ccaf3 100644 --- a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/COMMIT_EDITMSG +++ b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/COMMIT_EDITMSG @@ -14,14 +14,14 @@ commit 05 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # -# Date: Mon Aug 8 21:32:34 2022 +1000 +# Date: Wed Aug 10 19:26:28 2022 +1000 # -# interactive rebase in progress; onto a1a6f7b +# interactive rebase in progress; onto cc9defb # Last commands done (4 commands done): -# drop 84b1ae9 commit 04 -# squash aa2585a commit 05 +# drop da71be1 commit 04 +# squash 8a38398 commit 05 # No commands remaining. -# You are currently rebasing branch 'master' on 'a1a6f7b'. +# You are currently rebasing branch 'master' on 'cc9defb'. # # Changes to be committed: # new file: file02.txt diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/ORIG_HEAD b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/ORIG_HEAD index 42745976b..319624f9d 100644 --- a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/ORIG_HEAD +++ b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/ORIG_HEAD @@ -1 +1 @@ -aa2585aff7d2278341ca816f187e623503d7c4fb +8a3839811a7a9f4c678090c9def892d1e7ad7e54 diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/REBASE_HEAD b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/REBASE_HEAD index 42745976b..319624f9d 100644 --- a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/REBASE_HEAD +++ b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/REBASE_HEAD @@ -1 +1 @@ -aa2585aff7d2278341ca816f187e623503d7c4fb +8a3839811a7a9f4c678090c9def892d1e7ad7e54 diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/index b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/index index ce7858b10..2a0f3f08a 100644 Binary files a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/index and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/index differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/logs/HEAD b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/logs/HEAD index 0dda59a30..1d8d45a03 100644 --- a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/logs/HEAD +++ b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/logs/HEAD @@ -1,10 +1,10 @@ -0000000000000000000000000000000000000000 a1a6f7bda6aeaa08ec75f590845780fde90d901c CI 1659958354 +1000 commit (initial): commit 01 -a1a6f7bda6aeaa08ec75f590845780fde90d901c cb7e56856ecee89fa44c613e094fcf962fe18cf1 CI 1659958354 +1000 commit: commit 02 -cb7e56856ecee89fa44c613e094fcf962fe18cf1 6741ab4fe22a3d36b6c64397fc4295dbae1ba71d CI 1659958354 +1000 commit: commit 03 -6741ab4fe22a3d36b6c64397fc4295dbae1ba71d 84b1ae9d83049341897c9388afffdc9049c3317f CI 1659958354 +1000 commit: commit 04 -84b1ae9d83049341897c9388afffdc9049c3317f aa2585aff7d2278341ca816f187e623503d7c4fb CI 1659958354 +1000 commit: commit 05 -aa2585aff7d2278341ca816f187e623503d7c4fb a1a6f7bda6aeaa08ec75f590845780fde90d901c CI 1659958355 +1000 rebase (start): checkout a1a6f7bda6aeaa08ec75f590845780fde90d901c -a1a6f7bda6aeaa08ec75f590845780fde90d901c cb7e56856ecee89fa44c613e094fcf962fe18cf1 CI 1659958355 +1000 rebase: fast-forward -cb7e56856ecee89fa44c613e094fcf962fe18cf1 9c68b57ac7b652fbebc5e93a9a1b72014400c269 CI 1659958355 +1000 rebase (continue) (fixup): # This is a combination of 2 commits. -9c68b57ac7b652fbebc5e93a9a1b72014400c269 f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 CI 1659958355 +1000 rebase (continue) (squash): commit 02 -f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 CI 1659958355 +1000 rebase (continue) (finish): returning to refs/heads/master +0000000000000000000000000000000000000000 cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 CI 1660123588 +1000 commit (initial): commit 01 +cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 2e2cd25ffdec58d32b5d549f8402bd054e22cc2a CI 1660123588 +1000 commit: commit 02 +2e2cd25ffdec58d32b5d549f8402bd054e22cc2a 90fda12ce101e7d0d4594a879e5bbd1be3c857a8 CI 1660123588 +1000 commit: commit 03 +90fda12ce101e7d0d4594a879e5bbd1be3c857a8 da71be1afbb03f46e91ab5de17d69f148bb009f3 CI 1660123588 +1000 commit: commit 04 +da71be1afbb03f46e91ab5de17d69f148bb009f3 8a3839811a7a9f4c678090c9def892d1e7ad7e54 CI 1660123589 +1000 commit: commit 05 +8a3839811a7a9f4c678090c9def892d1e7ad7e54 cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 CI 1660123589 +1000 rebase (start): checkout cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 +cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 2e2cd25ffdec58d32b5d549f8402bd054e22cc2a CI 1660123589 +1000 rebase: fast-forward +2e2cd25ffdec58d32b5d549f8402bd054e22cc2a b85535ebf12659044c33386376121d76756ceb59 CI 1660123590 +1000 rebase (continue) (fixup): # This is a combination of 2 commits. +b85535ebf12659044c33386376121d76756ceb59 aba3469fd6fc584a6af9c0073873005ffaaea56c CI 1660123590 +1000 rebase (continue) (squash): commit 02 +aba3469fd6fc584a6af9c0073873005ffaaea56c aba3469fd6fc584a6af9c0073873005ffaaea56c CI 1660123590 +1000 rebase (continue) (finish): returning to refs/heads/master diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/logs/refs/heads/master b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/logs/refs/heads/master index 89309e282..c6c18ee5a 100644 --- a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/logs/refs/heads/master +++ b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/logs/refs/heads/master @@ -1,6 +1,6 @@ -0000000000000000000000000000000000000000 a1a6f7bda6aeaa08ec75f590845780fde90d901c CI 1659958354 +1000 commit (initial): commit 01 -a1a6f7bda6aeaa08ec75f590845780fde90d901c cb7e56856ecee89fa44c613e094fcf962fe18cf1 CI 1659958354 +1000 commit: commit 02 -cb7e56856ecee89fa44c613e094fcf962fe18cf1 6741ab4fe22a3d36b6c64397fc4295dbae1ba71d CI 1659958354 +1000 commit: commit 03 -6741ab4fe22a3d36b6c64397fc4295dbae1ba71d 84b1ae9d83049341897c9388afffdc9049c3317f CI 1659958354 +1000 commit: commit 04 -84b1ae9d83049341897c9388afffdc9049c3317f aa2585aff7d2278341ca816f187e623503d7c4fb CI 1659958354 +1000 commit: commit 05 -aa2585aff7d2278341ca816f187e623503d7c4fb f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 CI 1659958355 +1000 rebase (continue) (finish): refs/heads/master onto a1a6f7bda6aeaa08ec75f590845780fde90d901c +0000000000000000000000000000000000000000 cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 CI 1660123588 +1000 commit (initial): commit 01 +cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 2e2cd25ffdec58d32b5d549f8402bd054e22cc2a CI 1660123588 +1000 commit: commit 02 +2e2cd25ffdec58d32b5d549f8402bd054e22cc2a 90fda12ce101e7d0d4594a879e5bbd1be3c857a8 CI 1660123588 +1000 commit: commit 03 +90fda12ce101e7d0d4594a879e5bbd1be3c857a8 da71be1afbb03f46e91ab5de17d69f148bb009f3 CI 1660123588 +1000 commit: commit 04 +da71be1afbb03f46e91ab5de17d69f148bb009f3 8a3839811a7a9f4c678090c9def892d1e7ad7e54 CI 1660123589 +1000 commit: commit 05 +8a3839811a7a9f4c678090c9def892d1e7ad7e54 aba3469fd6fc584a6af9c0073873005ffaaea56c CI 1660123590 +1000 rebase (continue) (finish): refs/heads/master onto cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/2e/2cd25ffdec58d32b5d549f8402bd054e22cc2a b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/2e/2cd25ffdec58d32b5d549f8402bd054e22cc2a new file mode 100644 index 000000000..20504e122 --- /dev/null +++ b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/2e/2cd25ffdec58d32b5d549f8402bd054e22cc2a @@ -0,0 +1,3 @@ +xA +0P9$1U1LQ0,<}9 +lcTd 9YT\ИxW\u)Ěч2GqXFj"w;L3\nSO+`?Ќv'y|̱;u \ No newline at end of file diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/67/41ab4fe22a3d36b6c64397fc4295dbae1ba71d b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/67/41ab4fe22a3d36b6c64397fc4295dbae1ba71d deleted file mode 100644 index 7aeda0bfd..000000000 --- a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/67/41ab4fe22a3d36b6c64397fc4295dbae1ba71d +++ /dev/null @@ -1,3 +0,0 @@ -xM -0a9ɟ1 -֖ۅp,޺h< ĩH R䅽TEެ5oxuS@ҖQX}MmTea0ĩ.$Ր\tf6{ݧ:?O; \ No newline at end of file diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/84/b1ae9d83049341897c9388afffdc9049c3317f b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/84/b1ae9d83049341897c9388afffdc9049c3317f deleted file mode 100644 index cd394caaf..000000000 --- a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/84/b1ae9d83049341897c9388afffdc9049c3317f +++ /dev/null @@ -1,3 +0,0 @@ -xM -0@a9I2?m@DcL -֖ۅp,fU[LLsE&K#dnݪ:@A35Qd)BV(&Y-dBuevNe/SnT?σ'u; \ No newline at end of file diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/8a/3839811a7a9f4c678090c9def892d1e7ad7e54 b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/8a/3839811a7a9f4c678090c9def892d1e7ad7e54 new file mode 100644 index 000000000..f518dcc89 Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/8a/3839811a7a9f4c678090c9def892d1e7ad7e54 differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/90/fda12ce101e7d0d4594a879e5bbd1be3c857a8 b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/90/fda12ce101e7d0d4594a879e5bbd1be3c857a8 new file mode 100644 index 000000000..71b49be64 Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/90/fda12ce101e7d0d4594a879e5bbd1be3c857a8 differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/9c/68b57ac7b652fbebc5e93a9a1b72014400c269 b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/9c/68b57ac7b652fbebc5e93a9a1b72014400c269 deleted file mode 100644 index c97722a50..000000000 --- a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/9c/68b57ac7b652fbebc5e93a9a1b72014400c269 +++ /dev/null @@ -1,2 +0,0 @@ -xJ0=)zIڴȲfb6mi"F ^9}3 {(;3p ڌ9;%á2%Yv^ -> SOLAmtz0<[(L<_o|tkkiuETt}袁)fCP..T@}z,~Yeb+H38*mq1{65X؊Oz d \ No newline at end of file diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/a1/a6f7bda6aeaa08ec75f590845780fde90d901c b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/a1/a6f7bda6aeaa08ec75f590845780fde90d901c deleted file mode 100644 index 540590758..000000000 Binary files a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/a1/a6f7bda6aeaa08ec75f590845780fde90d901c and /dev/null differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/aa/2585aff7d2278341ca816f187e623503d7c4fb b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/aa/2585aff7d2278341ca816f187e623503d7c4fb deleted file mode 100644 index 9a3b44f36..000000000 --- a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/aa/2585aff7d2278341ca816f187e623503d7c4fb +++ /dev/null @@ -1,4 +0,0 @@ -xA -1 a=E4m@Dh3) - -.<ۏGwUpLJ9׊$BT!*lyWKB=$˜ҘEx B&}|vOnSO@̔=s z|);= \ No newline at end of file diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/ab/a3469fd6fc584a6af9c0073873005ffaaea56c b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/ab/a3469fd6fc584a6af9c0073873005ffaaea56c new file mode 100644 index 000000000..6f0bc9bd3 --- /dev/null +++ b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/ab/a3469fd6fc584a6af9c0073873005ffaaea56c @@ -0,0 +1,3 @@ +x} +0=).wzc- +Ɩ7Qf ,>8CTA^(: 7RaN4:Jn"eYa\8ˌi5Q }`T~ +:f?zϣ;v \ No newline at end of file diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/f4/316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/f4/316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 deleted file mode 100644 index b48b3e823..000000000 Binary files a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/f4/316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 and /dev/null differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/refs/heads/master b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/refs/heads/master index 4190292c7..93319ec4d 100644 --- a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/refs/heads/master +++ b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/refs/heads/master @@ -1 +1 @@ -f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 +aba3469fd6fc584a6af9c0073873005ffaaea56c