mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-02-03 13:21:56 +02:00
move code from main into app package to allow test to be injected
This commit is contained in:
parent
d890238c7b
commit
ba96baee32
22
main.go
22
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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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'.",
|
@ -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,
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
11
pkg/integration/README.md
Normal file
11
pkg/integration/README.md
Normal file
@ -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.
|
@ -1,4 +1,4 @@
|
||||
package integration
|
||||
package deprecated
|
||||
|
||||
import (
|
||||
"encoding/json"
|
@ -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()
|
||||
}
|
@ -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
|
||||
}
|
@ -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}
|
@ -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()
|
||||
})
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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 '<space>'. 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)
|
||||
|
@ -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),
|
||||
|
@ -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")]
|
||||
}
|
@ -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...)
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
}
|
@ -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
|
@ -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)
|
@ -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()
|
@ -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()
|
@ -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")
|
||||
|
18
pkg/integration/tests/tests.go
Normal file
18
pkg/integration/tests/tests.go
Normal file
@ -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,
|
||||
}
|
323
pkg/integration/tui/main.go
Normal file
323
pkg/integration/tui/main.go
Normal file
@ -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
|
||||
}
|
@ -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 '<space>'. 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
|
||||
}
|
||||
|
Binary file not shown.
@ -1 +1 @@
|
||||
0000000000000000000000000000000000000000 460150760ff1f381c3f5769b919cb73107c5871a CI <CI@example.com> 1659863059 +1000 commit (initial): my commit message
|
||||
0000000000000000000000000000000000000000 944b9ea58bef8f6352c3a081a1d0037125bcaabc CI <CI@example.com> 1660133266 +1000 commit (initial): my commit message
|
||||
|
@ -1 +1 @@
|
||||
0000000000000000000000000000000000000000 460150760ff1f381c3f5769b919cb73107c5871a CI <CI@example.com> 1659863059 +1000 commit (initial): my commit message
|
||||
0000000000000000000000000000000000000000 944b9ea58bef8f6352c3a081a1d0037125bcaabc CI <CI@example.com> 1660133266 +1000 commit (initial): my commit message
|
||||
|
Binary file not shown.
Binary file not shown.
@ -1 +1 @@
|
||||
460150760ff1f381c3f5769b919cb73107c5871a
|
||||
944b9ea58bef8f6352c3a081a1d0037125bcaabc
|
||||
|
@ -1,4 +1,4 @@
|
||||
0000000000000000000000000000000000000000 470038e1336649b2965305f9f6a82501a836810e CI <CI@example.com> 1659865912 +1000 commit (initial): commit 1
|
||||
470038e1336649b2965305f9f6a82501a836810e c8bec8f2b323cbb476e708bd10c145ea7cc9f726 CI <CI@example.com> 1659865912 +1000 commit: commit 2
|
||||
c8bec8f2b323cbb476e708bd10c145ea7cc9f726 62a60693a2e154e745ee353f67a05156d0532c23 CI <CI@example.com> 1659865912 +1000 commit: commit 3
|
||||
62a60693a2e154e745ee353f67a05156d0532c23 c8bec8f2b323cbb476e708bd10c145ea7cc9f726 CI <CI@example.com> 1659865912 +1000 checkout: moving from master to my-branch-name
|
||||
0000000000000000000000000000000000000000 4e72cd440eec154569568bff8d4c955052ae246c CI <CI@example.com> 1660125381 +1000 commit (initial): commit 1
|
||||
4e72cd440eec154569568bff8d4c955052ae246c 563414ba32c967cfbe21a17fe892d6118c1c58e8 CI <CI@example.com> 1660125381 +1000 commit: commit 2
|
||||
563414ba32c967cfbe21a17fe892d6118c1c58e8 0af36e404e6fec1c3a4d887e30622238e5ea0b2b CI <CI@example.com> 1660125381 +1000 commit: commit 3
|
||||
0af36e404e6fec1c3a4d887e30622238e5ea0b2b 563414ba32c967cfbe21a17fe892d6118c1c58e8 CI <CI@example.com> 1660125382 +1000 checkout: moving from master to my-branch-name
|
||||
|
@ -1,3 +1,3 @@
|
||||
0000000000000000000000000000000000000000 470038e1336649b2965305f9f6a82501a836810e CI <CI@example.com> 1659865912 +1000 commit (initial): commit 1
|
||||
470038e1336649b2965305f9f6a82501a836810e c8bec8f2b323cbb476e708bd10c145ea7cc9f726 CI <CI@example.com> 1659865912 +1000 commit: commit 2
|
||||
c8bec8f2b323cbb476e708bd10c145ea7cc9f726 62a60693a2e154e745ee353f67a05156d0532c23 CI <CI@example.com> 1659865912 +1000 commit: commit 3
|
||||
0000000000000000000000000000000000000000 4e72cd440eec154569568bff8d4c955052ae246c CI <CI@example.com> 1660125381 +1000 commit (initial): commit 1
|
||||
4e72cd440eec154569568bff8d4c955052ae246c 563414ba32c967cfbe21a17fe892d6118c1c58e8 CI <CI@example.com> 1660125381 +1000 commit: commit 2
|
||||
563414ba32c967cfbe21a17fe892d6118c1c58e8 0af36e404e6fec1c3a4d887e30622238e5ea0b2b CI <CI@example.com> 1660125381 +1000 commit: commit 3
|
||||
|
@ -1 +1 @@
|
||||
0000000000000000000000000000000000000000 c8bec8f2b323cbb476e708bd10c145ea7cc9f726 CI <CI@example.com> 1659865912 +1000 branch: Created from c8bec8f2b323cbb476e708bd10c145ea7cc9f726
|
||||
0000000000000000000000000000000000000000 563414ba32c967cfbe21a17fe892d6118c1c58e8 CI <CI@example.com> 1660125382 +1000 branch: Created from 563414ba32c967cfbe21a17fe892d6118c1c58e8
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
xŤÎA
|
||||
1@Q×=Eö‚¤1‰-�®ćm&�‚u†ˇ‚ÇwŔíç-ľÍ=:Ä,»ľş×D2š2YUŻą ˘×)Ťš)Ą©şsFKYýŐ�ýD62Ł»EaŃ,šę´q¶,‚BʼnŐBy÷űĽÂm€óm¸ú§´ĺé›Ű˘*F’cŠ°Ź�¶şMu˙“˙<Př)R:p
|
Binary file not shown.
@ -1,2 +0,0 @@
|
||||
x�ŽK
|
||||
1]罤ók»AD˜Õ#ÉtP0Î0Dðøfá\ÔæQ<ª¬=:X‰‡¾«BÈìâR(¸’I³$$Ôrå…Ä1׬ƒÙÒ®¯áŒèY÷DA²Šc•Ji¡Mì‰-ªIï~_w˜f¸LóM?©mO=•µ]ÁRXG‹ˆf¬#ªëŸúÏg¾…_9>
|
@ -1 +1 @@
|
||||
62a60693a2e154e745ee353f67a05156d0532c23
|
||||
0af36e404e6fec1c3a4d887e30622238e5ea0b2b
|
||||
|
@ -1 +1 @@
|
||||
c8bec8f2b323cbb476e708bd10c145ea7cc9f726
|
||||
563414ba32c967cfbe21a17fe892d6118c1c58e8
|
||||
|
@ -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
|
||||
|
@ -1 +1 @@
|
||||
aa2585aff7d2278341ca816f187e623503d7c4fb
|
||||
8a3839811a7a9f4c678090c9def892d1e7ad7e54
|
||||
|
@ -1 +1 @@
|
||||
aa2585aff7d2278341ca816f187e623503d7c4fb
|
||||
8a3839811a7a9f4c678090c9def892d1e7ad7e54
|
||||
|
Binary file not shown.
@ -1,10 +1,10 @@
|
||||
0000000000000000000000000000000000000000 a1a6f7bda6aeaa08ec75f590845780fde90d901c CI <CI@example.com> 1659958354 +1000 commit (initial): commit 01
|
||||
a1a6f7bda6aeaa08ec75f590845780fde90d901c cb7e56856ecee89fa44c613e094fcf962fe18cf1 CI <CI@example.com> 1659958354 +1000 commit: commit 02
|
||||
cb7e56856ecee89fa44c613e094fcf962fe18cf1 6741ab4fe22a3d36b6c64397fc4295dbae1ba71d CI <CI@example.com> 1659958354 +1000 commit: commit 03
|
||||
6741ab4fe22a3d36b6c64397fc4295dbae1ba71d 84b1ae9d83049341897c9388afffdc9049c3317f CI <CI@example.com> 1659958354 +1000 commit: commit 04
|
||||
84b1ae9d83049341897c9388afffdc9049c3317f aa2585aff7d2278341ca816f187e623503d7c4fb CI <CI@example.com> 1659958354 +1000 commit: commit 05
|
||||
aa2585aff7d2278341ca816f187e623503d7c4fb a1a6f7bda6aeaa08ec75f590845780fde90d901c CI <CI@example.com> 1659958355 +1000 rebase (start): checkout a1a6f7bda6aeaa08ec75f590845780fde90d901c
|
||||
a1a6f7bda6aeaa08ec75f590845780fde90d901c cb7e56856ecee89fa44c613e094fcf962fe18cf1 CI <CI@example.com> 1659958355 +1000 rebase: fast-forward
|
||||
cb7e56856ecee89fa44c613e094fcf962fe18cf1 9c68b57ac7b652fbebc5e93a9a1b72014400c269 CI <CI@example.com> 1659958355 +1000 rebase (continue) (fixup): # This is a combination of 2 commits.
|
||||
9c68b57ac7b652fbebc5e93a9a1b72014400c269 f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 CI <CI@example.com> 1659958355 +1000 rebase (continue) (squash): commit 02
|
||||
f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 CI <CI@example.com> 1659958355 +1000 rebase (continue) (finish): returning to refs/heads/master
|
||||
0000000000000000000000000000000000000000 cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 CI <CI@example.com> 1660123588 +1000 commit (initial): commit 01
|
||||
cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 2e2cd25ffdec58d32b5d549f8402bd054e22cc2a CI <CI@example.com> 1660123588 +1000 commit: commit 02
|
||||
2e2cd25ffdec58d32b5d549f8402bd054e22cc2a 90fda12ce101e7d0d4594a879e5bbd1be3c857a8 CI <CI@example.com> 1660123588 +1000 commit: commit 03
|
||||
90fda12ce101e7d0d4594a879e5bbd1be3c857a8 da71be1afbb03f46e91ab5de17d69f148bb009f3 CI <CI@example.com> 1660123588 +1000 commit: commit 04
|
||||
da71be1afbb03f46e91ab5de17d69f148bb009f3 8a3839811a7a9f4c678090c9def892d1e7ad7e54 CI <CI@example.com> 1660123589 +1000 commit: commit 05
|
||||
8a3839811a7a9f4c678090c9def892d1e7ad7e54 cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 CI <CI@example.com> 1660123589 +1000 rebase (start): checkout cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347
|
||||
cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 2e2cd25ffdec58d32b5d549f8402bd054e22cc2a CI <CI@example.com> 1660123589 +1000 rebase: fast-forward
|
||||
2e2cd25ffdec58d32b5d549f8402bd054e22cc2a b85535ebf12659044c33386376121d76756ceb59 CI <CI@example.com> 1660123590 +1000 rebase (continue) (fixup): # This is a combination of 2 commits.
|
||||
b85535ebf12659044c33386376121d76756ceb59 aba3469fd6fc584a6af9c0073873005ffaaea56c CI <CI@example.com> 1660123590 +1000 rebase (continue) (squash): commit 02
|
||||
aba3469fd6fc584a6af9c0073873005ffaaea56c aba3469fd6fc584a6af9c0073873005ffaaea56c CI <CI@example.com> 1660123590 +1000 rebase (continue) (finish): returning to refs/heads/master
|
||||
|
@ -1,6 +1,6 @@
|
||||
0000000000000000000000000000000000000000 a1a6f7bda6aeaa08ec75f590845780fde90d901c CI <CI@example.com> 1659958354 +1000 commit (initial): commit 01
|
||||
a1a6f7bda6aeaa08ec75f590845780fde90d901c cb7e56856ecee89fa44c613e094fcf962fe18cf1 CI <CI@example.com> 1659958354 +1000 commit: commit 02
|
||||
cb7e56856ecee89fa44c613e094fcf962fe18cf1 6741ab4fe22a3d36b6c64397fc4295dbae1ba71d CI <CI@example.com> 1659958354 +1000 commit: commit 03
|
||||
6741ab4fe22a3d36b6c64397fc4295dbae1ba71d 84b1ae9d83049341897c9388afffdc9049c3317f CI <CI@example.com> 1659958354 +1000 commit: commit 04
|
||||
84b1ae9d83049341897c9388afffdc9049c3317f aa2585aff7d2278341ca816f187e623503d7c4fb CI <CI@example.com> 1659958354 +1000 commit: commit 05
|
||||
aa2585aff7d2278341ca816f187e623503d7c4fb f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05 CI <CI@example.com> 1659958355 +1000 rebase (continue) (finish): refs/heads/master onto a1a6f7bda6aeaa08ec75f590845780fde90d901c
|
||||
0000000000000000000000000000000000000000 cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 CI <CI@example.com> 1660123588 +1000 commit (initial): commit 01
|
||||
cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 2e2cd25ffdec58d32b5d549f8402bd054e22cc2a CI <CI@example.com> 1660123588 +1000 commit: commit 02
|
||||
2e2cd25ffdec58d32b5d549f8402bd054e22cc2a 90fda12ce101e7d0d4594a879e5bbd1be3c857a8 CI <CI@example.com> 1660123588 +1000 commit: commit 03
|
||||
90fda12ce101e7d0d4594a879e5bbd1be3c857a8 da71be1afbb03f46e91ab5de17d69f148bb009f3 CI <CI@example.com> 1660123588 +1000 commit: commit 04
|
||||
da71be1afbb03f46e91ab5de17d69f148bb009f3 8a3839811a7a9f4c678090c9def892d1e7ad7e54 CI <CI@example.com> 1660123589 +1000 commit: commit 05
|
||||
8a3839811a7a9f4c678090c9def892d1e7ad7e54 aba3469fd6fc584a6af9c0073873005ffaaea56c CI <CI@example.com> 1660123590 +1000 rebase (continue) (finish): refs/heads/master onto cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347
|
||||
|
@ -0,0 +1,3 @@
|
||||
x█нA
|
||||
б0Pв9еЛи$1≥─┬пU▐1≥LQ0╤■ъ,<─ЭщГ}Ь╡╤ЖХ─9З╝
|
||||
l²└Бc┴T≈d ЯЛ9Y░╧≈T\п≤▀ыxвW▒\u)д я┤║2GqджзX┘Fj"▓Аw©╞;L3\╕Ы╕nшSO╡╤+`▄²?агп▄v°ЙЗ'Ъy╟н|л╠;u
|
@ -1,3 +0,0 @@
|
||||
x�ÎM
|
||||
Â0†a×9Åì™ÉŸºê1’ô
|
||||
Ö–ÁãÛ…pûò,ÞºÌó£“h<ô
Ä©H©ÚRòä…½æ„æTØEÆÞ¬Àš5oxuªåŒSˆ¨@Ò–½¯QX}«M£m�T›˜üî÷e£a¤Ë0ÞðÉóúÄ©.ó•$Õ�\ðtf6{ݧ:þä?OìÌ�;ß
|
@ -1,3 +0,0 @@
|
||||
xŤÎM
|
||||
Â0@a×9ĹěI2?m@DčĘcL’
|
||||
Ö–ÁăŰ…pűřŻ,óüč’úfČU…[LąřLŁęčsEő±´&K#dnÝŞ›˝:Č@A35‹Q±˘d)B�†V(&®Y-dBuúî÷e�éçévµŹÎëÓNe™/„Sâ™ŕĽ÷nŻűT·?ůĎ�'÷Ěu;Ť
|
Binary file not shown.
Binary file not shown.
@ -1,2 +0,0 @@
|
||||
x…ŽÁJÄ0†=ç)z–IÚ´‰Č˛°§˝ű“fbĂ6mi"úřF© ^„9}óý3˙¸¦´Ş{(;3pďťÚŚĘ9Íä;‰ť%áµŰą2%Y‰Ťv^
|
||||
�¤>ÎSOL„†ÇAmŃtz0<[ôĺ(čLë×<_oţ ´Í|×tŮkkµiuŹEĄµTát}č˘�—)f¨CPł..TâşŔ@}�z,ź~Yeb�ą+Hś3˝ň“8*ńmóďqžÁ1ä{Ü6ö5ÔüXŘŠOz d×
|
Binary file not shown.
@ -1,4 +0,0 @@
|
||||
x�ÎA
|
||||
1…a×=E÷‚4“¦m@D˜•Çh3)
|
||||
Ö†
|
||||
ß.<€Û�ÿÁ“µµG·ÀáÐwU«pLJ9׊‘¦²¨–€$BT!*ÃÀly×W·ÉÈÊKBç=$ŽÂ˜Ò˜×Ex B¬&¿û}Ýí|³çùvÕOnÛSO²¶‹…@Ì”�¼=‚sΧºþ™ÿzëÈ|²);=
|
@ -0,0 +1,3 @@
|
||||
x}ŽÍ
|
||||
Â0„=ç)ö.Èæwzêc¬Û-
|
||||
Æ–ÁÇ7‡âQfÃð
Œ,µ>8¤CÛTA‹ÏÈ^(:
7RŠa–€N4:�ŽJän³ò¦¯"eÒù–Y‹õa¶\8‰ËŒˆi’Ü5Qö�¿Û}Ù`á<ŒWýp]Ÿz’¥^À¦„Öù˜3mšÞöSMÿãw|ç�ùÅh¾ý-=ä
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,4 @@
|
||||
x�ΞA
|
||||
1@QΧ=Eφ‚$mΣiAD�•ΗHΫλC�ο,<€ΫΟ[ό²τώ@)Ζ¦
|
||||
�«n6ε‚ΩG‘�Ή:A[ZΦ•ζs«Ξ¬²ιk@ΒV…lQB�©bυ�ΌΔ))η\)«+‘'‰FήγΎl0ίΰ<ί®ϊ‘Ύ>υT–~
|
||||
Ι:��„�f―ϋΤΠ?ωΟzσΟ£;v
|
Binary file not shown.
@ -1 +1 @@
|
||||
f4316f7a6df3fe5b7e8da1b2c8767ed1e825dc05
|
||||
aba3469fd6fc584a6af9c0073873005ffaaea56c
|
||||
|
Loading…
x
Reference in New Issue
Block a user