diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml deleted file mode 100644 index 36d301e59..000000000 --- a/.github/workflows/automerge.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: automerge -on: - pull_request: - types: - - labeled - - unlabeled - - synchronize - - opened - - edited - - ready_for_review - - reopened - - unlocked - pull_request_review: - types: - - submitted - check_suite: - types: - - completed - status: {} -jobs: - automerge: - runs-on: ubuntu-latest - steps: - - name: automerge - uses: "pascalgn/automerge-action@135f0bdb927d9807b5446f7ca9ecc2c51de03c4a" - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - MERGE_METHOD: rebase diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bf5ecfdf..c0b695e5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,14 +46,14 @@ jobs: # we're passing -short so that we skip the integration tests, which will be run in parallel below run: | go test ./... -short - integration-tests: + integration-tests-old: runs-on: ubuntu-latest strategy: fail-fast: false matrix: parallelism: [5] index: [0,1,2,3,4] - name: "Integration Tests (${{ matrix.index }}/${{ matrix.parallelism }})" + name: "Integration Tests (Old pattern) (${{ matrix.index }}/${{ matrix.parallelism }})" env: GOFLAGS: -mod=vendor steps: @@ -74,7 +74,31 @@ jobs: ${{runner.os}}-go- - name: Test code run: | - PARALLEL_TOTAL=${{ matrix.parallelism }} PARALLEL_INDEX=${{ matrix.index }} go test pkg/gui/gui_test.go + PARALLEL_TOTAL=${{ matrix.parallelism }} PARALLEL_INDEX=${{ matrix.index }} go test pkg/integration/deprecated/*.go + integration-tests: + runs-on: ubuntu-latest + name: "Integration Tests" + env: + GOFLAGS: -mod=vendor + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Setup Go + uses: actions/setup-go@v1 + with: + go-version: 1.18.x + - name: Cache build + uses: actions/cache@v1 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test + restore-keys: | + ${{runner.os}}-go- + - name: Test code + run: | + go test pkg/integration/*.go build: runs-on: ubuntu-latest env: diff --git a/.gitignore b/.gitignore index f5ba66f88..e9ed453a2 100644 --- a/.gitignore +++ b/.gitignore @@ -33,11 +33,19 @@ lazygit.exe !.gitmodules_keep test/git_server/data + +# we'll scrap these lines once we've fully moved over to the new integration test approach test/integration/*/actual/ test/integration/*/used_config/ # these sample hooks waste too much space test/integration/*/expected/**/hooks/ test/integration/*/expected_remote/**/hooks/ +test/integration_new/**/actual/ +test/integration_new/**/used_config/ +# these sample hooks waste too much space +test/integration_new/**/expected/**/hooks/ +test/integration_new/**/expected_remote/**/hooks/ + oryxBuildBinary -__debug_bin \ No newline at end of file +__debug_bin diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 11fb75202..c6a68feae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,9 +63,9 @@ by setting [`formatting.gofumpt`](https://github.com/golang/tools/blob/master/go ```jsonc // .vscode/settings.json { - "gopls": { - "formatting.gofumpt": true - } + "gopls": { + "formatting.gofumpt": true + } } ``` @@ -82,6 +82,7 @@ From most places in the codebase you have access to a logger e.g. `gui.Log.Warn( If you find that the existing logs are too noisy, you can set the log level with e.g. `LOG_LEVEL=warn go run main.go -debug` and then only use `Warn` logs yourself. If you need to log from code in the vendor directory (e.g. the `gocui` package), you won't have access to the logger, but you can easily add logging support by adding the following: + ```go func newLogger() *logrus.Entry { // REPLACE THE BELOW PATH WITH YOUR ACTUAL LOG PATH (YOU'LL SEE THIS PRINTED WHEN YOU RUN `lazygit --logs` @@ -118,9 +119,7 @@ If you want to trigger a debug session from VSCode, you can use the following sn "request": "launch", "mode": "auto", "program": "main.go", - "args": [ - "--debug" - ], + "args": ["--debug"], "console": "externalTerminal" // <-- you need this to actually see the lazygit UI in a window while debugging } ] @@ -129,7 +128,7 @@ If you want to trigger a debug session from VSCode, you can use the following sn ## Testing -Lazygit has two kinds of tests: unit tests and integration tests. Unit tests go in files that end in `_test.go`, and are written in Go. Lazygit has its own integration test system where you can build a sandbox repo with a shell script, record yourself doing something, and commit the resulting repo snapshot. It's pretty damn cool! To learn more see [here](https://github.com/jesseduffield/lazygit/blob/master/docs/Integration_Tests.md) +Lazygit has two kinds of tests: unit tests and integration tests. Unit tests go in files that end in `_test.go`, and are written in Go. For integration tests, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) ## Updating Gocui diff --git a/docs/Integration_Tests.md b/docs/Integration_Tests.md index 2cbf8064a..fab7bb984 100644 --- a/docs/Integration_Tests.md +++ b/docs/Integration_Tests.md @@ -1,122 +1 @@ -# How To Make And Run Integration Tests For lazygit - -Integration tests are located in `test/integration`. Each test will run a bash script to prepare a test repo, then replay a recorded lazygit session from within that repo, and then the resultant repo will be compared to an expected repo that was created upon the initial recording. Each integration test lives in its own directory, and the name of the directory becomes the name of the test. Within the directory must be the following files: - -### `test.json` - -An example of a `test.json` is: - -``` -{ "description": "Open a confirmation, then open a menu over that, then close the menu. Verify that the confirmation popup also closes automatically", "speed": 20 } -``` - -The `speed` key refers to the playback speed as a multiple of the original recording speed. So 20 means the test will run 20 times faster than the original recording speed. If a test fails for a given speed, it will drop the speed and re-test, until finally attempting the test at the original speed. If you omit the speed, it will default to 10. - -### `setup.sh` - -This is a bash script containing the instructions for creating the test repo from scratch. For example: - -``` -#!/bin/sh - -cd $1 - -git init - -git config user.email "CI@example.com" -git config user.name "CI" - -echo test1 > myfile1 -git add . -git commit -am "myfile1" -``` - -Be sure to: - -- ensure that by the end of the test you've got at least one commit in the repo, as we've had issues in the past when that wasn't the case. -- set the git user email and name as above so that your own user details aren't included in the snapshot. - -## Running tests - -### From a TUI - -You can run/record/sandbox tests via a TUI with the following command: - -``` -go run test/lazyintegration/main.go -``` - -This TUI makes much of the following documentation redundant, but feel free to read through anyway! - -### From command line - -To run all tests - assuming you're at the project root: - -``` -go test ./pkg/gui/ -``` - -To run them in parallel - -``` -PARALLEL=true go test ./pkg/gui -``` - -To run a single test - -``` -go test ./pkg/gui -run / -# For example, to run the `tags` test: -go test ./pkg/gui -run /tags -``` - -To run a test at a certain speed - -``` -SPEED=2 go test ./pkg/gui -run / -``` - -To update a snapshot - -``` -MODE=updateSnapshot go test ./pkg/gui -run / -``` - -## Creating a new test - -To create a new test: - -1. Copy and paste an existing test directory and rename the new directory to whatever you want the test name to be. Update the test.json file's description to describe your test. -2. Update the `setup.sh` any way you like -3. If you want to have a config folder for just that test, create a `config` directory to contain a `config.yml` and optionally a `state.yml` file. Otherwise, the `test/default_test_config` directory will be used. -4. From the lazygit root directory, run: - -``` -MODE=record go test ./pkg/gui -run / -``` - -5. Feel free to re-attempt recording as many times as you like. In the absence of a proper testing framework, the more deliberate your keypresses, the better! -6. Once satisfied with the recording, stage all the newly created files: `test.json`, `setup.sh`, `recording.json` and the `expected` directory that contains a copy of the repo you created. - -The resulting directory will look like: - -``` -actual/ (the resulting repo(s) after running the test, ignored by git) -expected/ (the 'snapshot' repo(s)) -config/ (need not be present) -test.json -setup.sh -recording.json -``` - -## Sandboxing - -The integration tests serve a secondary purpose of providing a setup for easy sandboxing. If you want to run a test in sandbox mode (meaning the session won't be recorded and we won't create/update snapshots), go: - -``` -MODE=sandbox go test ./pkg/gui -run / -``` - -## Feedback - -If you think this process can be improved, let me know! It shouldn't be too hard to change things. +see new docs [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) diff --git a/main.go b/main.go index e21751c27..d7ce1db14 100644 --- a/main.go +++ b/main.go @@ -1,222 +1,24 @@ package main import ( - "bytes" - "fmt" - "log" - "os" - "path/filepath" - "runtime" - "runtime/debug" - "strings" - - "github.com/integrii/flaggy" "github.com/jesseduffield/lazygit/pkg/app" - "github.com/jesseduffield/lazygit/pkg/app/daemon" - "github.com/jesseduffield/lazygit/pkg/config" - "github.com/jesseduffield/lazygit/pkg/env" - "github.com/jesseduffield/lazygit/pkg/gui/types" - "github.com/jesseduffield/lazygit/pkg/logs" - "github.com/jesseduffield/lazygit/pkg/utils" - yaml "github.com/jesseduffield/yaml" - "github.com/samber/lo" ) -const DEFAULT_VERSION = "unversioned" - +// These values may be set by the build script via the LDFLAGS argument var ( commit string - version = DEFAULT_VERSION date string + version string buildSource = "unknown" ) func main() { - updateBuildInfo() - - flaggy.DefaultParser.ShowVersionWithVersionFlag = false - - repoPath := "" - flaggy.String(&repoPath, "p", "path", "Path of git repo. (equivalent to --work-tree= --git-dir=/.git/)") - - filterPath := "" - flaggy.String(&filterPath, "f", "filter", "Path to filter on in `git log -- `. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted") - - gitArg := "" - flaggy.AddPositionalValue(&gitArg, "git-arg", 1, false, "Panel to focus upon opening lazygit. Accepted values (based on git terminology): status, branch, log, stash. Ignored if --filter arg is passed.") - - versionFlag := false - flaggy.Bool(&versionFlag, "v", "version", "Print the current version") - - debuggingFlag := false - flaggy.Bool(&debuggingFlag, "d", "debug", "Run in debug mode with logging (see --logs flag below). Use the LOG_LEVEL env var to set the log level (debug/info/warn/error)") - - logFlag := false - flaggy.Bool(&logFlag, "l", "logs", "Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)") - - configFlag := false - flaggy.Bool(&configFlag, "c", "config", "Print the default config") - - configDirFlag := false - flaggy.Bool(&configDirFlag, "cd", "print-config-dir", "Print the config directory") - - useConfigDir := "" - flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory") - - workTree := "" - flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument") - - gitDir := "" - flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument") - - customConfig := "" - flaggy.String(&customConfig, "ucf", "use-config-file", "Comma separated list to custom config file(s)") - - flaggy.Parse() - - if os.Getenv("DEBUG") == "TRUE" { - debuggingFlag = true + ldFlagsBuildInfo := &app.BuildInfo{ + Commit: commit, + Date: date, + Version: version, + BuildSource: buildSource, } - if repoPath != "" { - if workTree != "" || gitDir != "" { - log.Fatal("--path option is incompatible with the --work-tree and --git-dir options") - } - - absRepoPath, err := filepath.Abs(repoPath) - if err != nil { - log.Fatal(err) - } - workTree = absRepoPath - gitDir = filepath.Join(absRepoPath, ".git") - } - - if customConfig != "" { - os.Setenv("LG_CONFIG_FILE", customConfig) - } - - if useConfigDir != "" { - os.Setenv("CONFIG_DIR", useConfigDir) - } - - if workTree != "" { - env.SetGitWorkTreeEnv(workTree) - } - - if gitDir != "" { - env.SetGitDirEnv(gitDir) - } - - if versionFlag { - fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s\n", commit, date, buildSource, version, runtime.GOOS, runtime.GOARCH) - os.Exit(0) - } - - if configFlag { - var buf bytes.Buffer - encoder := yaml.NewEncoder(&buf) - err := encoder.Encode(config.GetDefaultConfig()) - if err != nil { - log.Fatal(err.Error()) - } - fmt.Printf("%s\n", buf.String()) - os.Exit(0) - } - - if configDirFlag { - fmt.Printf("%s\n", config.ConfigDir()) - os.Exit(0) - } - - if logFlag { - logs.TailLogs() - os.Exit(0) - } - - if workTree != "" { - if err := os.Chdir(workTree); err != nil { - log.Fatal(err.Error()) - } - } - - tempDir, err := os.MkdirTemp("", "lazygit-*") - if err != nil { - log.Fatal(err.Error()) - } - defer os.RemoveAll(tempDir) - - appConfig, err := config.NewAppConfig("lazygit", version, commit, date, buildSource, debuggingFlag, tempDir) - if err != nil { - log.Fatal(err.Error()) - } - - common, err := app.NewCommon(appConfig) - if err != nil { - log.Fatal(err) - } - - if daemon.InDaemonMode() { - daemon.Handle(common) - return - } - - parsedGitArg := parseGitArg(gitArg) - - app.Run(appConfig, common, types.NewStartArgs(filterPath, parsedGitArg)) -} - -func parseGitArg(gitArg string) types.GitArg { - typedArg := types.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: - return typedArg - } - - permittedValues := []string{ - string(types.GitArgStatus), - string(types.GitArgBranch), - string(types.GitArgLog), - string(types.GitArgStash), - } - - log.Fatalf("Invalid git arg value: '%s'. Must be one of the following values: %s. e.g. 'lazygit status'. See 'lazygit --help'.", - gitArg, - strings.Join(permittedValues, ", "), - ) - - panic("unreachable") -} - -func updateBuildInfo() { - // if the version has already been set by build flags then we'll honour that. - // chances are it's something like v0.31.0 which is more informative than a - // commit hash. - if version != DEFAULT_VERSION { - return - } - - buildInfo, ok := debug.ReadBuildInfo() - if !ok { - return - } - - revision, ok := lo.Find(buildInfo.Settings, func(setting debug.BuildSetting) bool { - return setting.Key == "vcs.revision" - }) - if ok { - commit = revision.Value - // if lazygit was built from source we'll show the version as the - // abbreviated commit hash - version = utils.ShortSha(revision.Value) - } - - // if version hasn't been set we assume that neither has the date - time, ok := lo.Find(buildInfo.Settings, func(setting debug.BuildSetting) bool { - return setting.Key == "vcs.time" - }) - if ok { - date = time.Value - } + app.Start(ldFlagsBuildInfo, nil) } diff --git a/pkg/app/app.go b/pkg/app/app.go index 418c1406e..3a1c127de 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -14,6 +14,7 @@ import ( "github.com/go-errors/errors" "github.com/jesseduffield/generics/slices" + appTypes "github.com/jesseduffield/lazygit/pkg/app/types" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/git_config" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" @@ -22,7 +23,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/constants" "github.com/jesseduffield/lazygit/pkg/env" "github.com/jesseduffield/lazygit/pkg/gui" - "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/updates" ) @@ -38,7 +38,11 @@ type App struct { Updater *updates.Updater // may only need this on the Gui } -func Run(config config.AppConfigurer, common *common.Common, startArgs types.StartArgs) { +func Run( + config config.AppConfigurer, + common *common.Common, + startArgs appTypes.StartArgs, +) { app, err := NewApp(config, common) if err == nil { @@ -213,7 +217,7 @@ func (app *App) setupRepo() (bool, error) { return false, nil } -func (app *App) Run(startArgs types.StartArgs) error { +func (app *App) Run(startArgs appTypes.StartArgs) error { err := app.Gui.RunAndHandleError(startArgs) return err } diff --git a/pkg/app/entry_point.go b/pkg/app/entry_point.go new file mode 100644 index 000000000..5a767bb94 --- /dev/null +++ b/pkg/app/entry_point.go @@ -0,0 +1,265 @@ +package app + +import ( + "bytes" + "fmt" + "log" + "os" + "path/filepath" + "runtime" + "runtime/debug" + "strings" + + "github.com/integrii/flaggy" + "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" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" + "github.com/jesseduffield/lazygit/pkg/logs" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/samber/lo" + "gopkg.in/yaml.v3" +) + +type cliArgs struct { + RepoPath string + FilterPath string + GitArg string + PrintVersionInfo bool + Debug bool + TailLogs bool + PrintDefaultConfig bool + PrintConfigDir bool + UseConfigDir string + WorkTree string + GitDir string + CustomConfigFile string +} + +type BuildInfo struct { + Commit string + Date string + Version string + BuildSource string +} + +func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTest) { + cliArgs := parseCliArgsAndEnvVars() + mergeBuildInfo(buildInfo) + + if cliArgs.RepoPath != "" { + if cliArgs.WorkTree != "" || cliArgs.GitDir != "" { + log.Fatal("--path option is incompatible with the --work-tree and --git-dir options") + } + + absRepoPath, err := filepath.Abs(cliArgs.RepoPath) + if err != nil { + log.Fatal(err) + } + cliArgs.WorkTree = absRepoPath + cliArgs.GitDir = filepath.Join(absRepoPath, ".git") + } + + if cliArgs.CustomConfigFile != "" { + os.Setenv("LG_CONFIG_FILE", cliArgs.CustomConfigFile) + } + + if cliArgs.UseConfigDir != "" { + os.Setenv("CONFIG_DIR", cliArgs.UseConfigDir) + } + + if cliArgs.WorkTree != "" { + env.SetGitWorkTreeEnv(cliArgs.WorkTree) + } + + if cliArgs.GitDir != "" { + env.SetGitDirEnv(cliArgs.GitDir) + } + + if cliArgs.PrintVersionInfo { + fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s\n", buildInfo.Commit, buildInfo.Date, buildInfo.BuildSource, buildInfo.Version, runtime.GOOS, runtime.GOARCH) + os.Exit(0) + } + + if cliArgs.PrintDefaultConfig { + var buf bytes.Buffer + encoder := yaml.NewEncoder(&buf) + err := encoder.Encode(config.GetDefaultConfig()) + if err != nil { + log.Fatal(err.Error()) + } + fmt.Printf("%s\n", buf.String()) + os.Exit(0) + } + + if cliArgs.PrintConfigDir { + fmt.Printf("%s\n", config.ConfigDir()) + os.Exit(0) + } + + if cliArgs.TailLogs { + logs.TailLogs() + os.Exit(0) + } + + if cliArgs.WorkTree != "" { + if err := os.Chdir(cliArgs.WorkTree); err != nil { + log.Fatal(err.Error()) + } + } + + tempDir, err := os.MkdirTemp("", "lazygit-*") + if err != nil { + log.Fatal(err.Error()) + } + defer os.RemoveAll(tempDir) + + appConfig, err := config.NewAppConfig("lazygit", buildInfo.Version, buildInfo.Commit, buildInfo.Date, buildInfo.BuildSource, cliArgs.Debug, tempDir) + if err != nil { + log.Fatal(err.Error()) + } + + if integrationTest != nil { + integrationTest.SetupConfig(appConfig) + } + + common, err := NewCommon(appConfig) + if err != nil { + log.Fatal(err) + } + + if daemon.InDaemonMode() { + daemon.Handle(common) + return + } + + parsedGitArg := parseGitArg(cliArgs.GitArg) + + Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, integrationTest)) +} + +func parseCliArgsAndEnvVars() *cliArgs { + flaggy.DefaultParser.ShowVersionWithVersionFlag = false + + repoPath := "" + flaggy.String(&repoPath, "p", "path", "Path of git repo. (equivalent to --work-tree= --git-dir=/.git/)") + + filterPath := "" + flaggy.String(&filterPath, "f", "filter", "Path to filter on in `git log -- `. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted") + + gitArg := "" + flaggy.AddPositionalValue(&gitArg, "git-arg", 1, false, "Panel to focus upon opening lazygit. Accepted values (based on git terminology): status, branch, log, stash. Ignored if --filter arg is passed.") + + printVersionInfo := false + flaggy.Bool(&printVersionInfo, "v", "version", "Print the current version") + + debug := false + flaggy.Bool(&debug, "d", "debug", "Run in debug mode with logging (see --logs flag below). Use the LOG_LEVEL env var to set the log level (debug/info/warn/error)") + + tailLogs := false + flaggy.Bool(&tailLogs, "l", "logs", "Tail lazygit logs (intended to be used when `lazygit --debug` is called in a separate terminal tab)") + + printDefaultConfig := false + flaggy.Bool(&printDefaultConfig, "c", "config", "Print the default config") + + printConfigDir := false + flaggy.Bool(&printConfigDir, "cd", "print-config-dir", "Print the config directory") + + useConfigDir := "" + flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory") + + workTree := "" + flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument") + + gitDir := "" + flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument") + + customConfigFile := "" + flaggy.String(&customConfigFile, "ucf", "use-config-file", "Comma separated list to custom config file(s)") + + flaggy.Parse() + + if os.Getenv("DEBUG") == "TRUE" { + debug = true + } + + return &cliArgs{ + RepoPath: repoPath, + FilterPath: filterPath, + GitArg: gitArg, + PrintVersionInfo: printVersionInfo, + Debug: debug, + TailLogs: tailLogs, + PrintDefaultConfig: printDefaultConfig, + PrintConfigDir: printConfigDir, + UseConfigDir: useConfigDir, + WorkTree: workTree, + GitDir: gitDir, + CustomConfigFile: customConfigFile, + } +} + +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 appTypes.GitArgNone, appTypes.GitArgStatus, appTypes.GitArgBranch, appTypes.GitArgLog, appTypes.GitArgStash: + return typedArg + } + + permittedValues := []string{ + 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'.", + gitArg, + strings.Join(permittedValues, ", "), + ) + + panic("unreachable") +} + +// the buildInfo struct we get passed in is based on what's baked into the lazygit +// binary via the LDFLAGS argument. Some lazygit distributions will make use of these +// arguments and some will not. Go recently started baking in build info +// into the binary by default e.g. the git commit hash. So in this function +// we merge the two together, giving priority to the stuff set by LDFLAGS. +// Note: this mutates the argument passed in +func mergeBuildInfo(buildInfo *BuildInfo) { + // if the version has already been set by build flags then we'll honour that. + // chances are it's something like v0.31.0 which is more informative than a + // commit hash. + if buildInfo.Version != "" { + return + } + + buildInfo.Version = "unversioned" + + goBuildInfo, ok := debug.ReadBuildInfo() + if !ok { + return + } + + revision, ok := lo.Find(goBuildInfo.Settings, func(setting debug.BuildSetting) bool { + return setting.Key == "vcs.revision" + }) + if ok { + buildInfo.Commit = revision.Value + // if lazygit was built from source we'll show the version as the + // abbreviated commit hash + buildInfo.Version = utils.ShortSha(revision.Value) + } + + // if version hasn't been set we assume that neither has the date + time, ok := lo.Find(goBuildInfo.Settings, func(setting debug.BuildSetting) bool { + return setting.Key == "vcs.time" + }) + if ok { + buildInfo.Date = time.Value + } +} diff --git a/pkg/gui/types/main_args.go b/pkg/app/types/types.go similarity index 53% rename from pkg/gui/types/main_args.go rename to pkg/app/types/types.go index b055b3736..002111087 100644 --- a/pkg/gui/types/main_args.go +++ b/pkg/app/types/types.go @@ -1,4 +1,8 @@ -package types +package app + +import ( + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" +) // StartArgs is the struct that represents some things we want to do on program start type StartArgs struct { @@ -6,6 +10,8 @@ type StartArgs struct { FilterPath string // GitArg determines what context we open in GitArg GitArg + // integration test (only relevant when invoking lazygit in the context of an integration test) + IntegrationTest integrationTypes.IntegrationTest } type GitArg string @@ -18,9 +24,10 @@ const ( GitArgStash GitArg = "stash" ) -func NewStartArgs(filterPath string, gitArg GitArg) StartArgs { +func NewStartArgs(filterPath string, gitArg GitArg, test integrationTypes.IntegrationTest) StartArgs { return StartArgs{ - FilterPath: filterPath, - GitArg: gitArg, + FilterPath: filterPath, + GitArg: gitArg, + IntegrationTest: test, } } diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index 3591166c7..9806bcf58 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -27,8 +27,6 @@ type AppConfig struct { IsNewRepo bool } -// AppConfigurer interface allows individual app config structs to inherit Fields -// from AppConfig and still be used by lazygit. type AppConfigurer interface { GetDebug() bool diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index bb5821a57..8be5a4a4d 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -10,6 +10,7 @@ import ( "time" "github.com/jesseduffield/gocui" + appTypes "github.com/jesseduffield/lazygit/pkg/app/types" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/git_config" @@ -31,6 +32,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands" "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/types" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" "github.com/jesseduffield/lazygit/pkg/tasks" "github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/updates" @@ -213,7 +215,7 @@ const ( COMPLETE ) -func (gui *Gui) onNewRepo(startArgs types.StartArgs, reuseState bool) error { +func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, reuseState bool) error { var err error gui.git, err = commands.NewGitCommand( gui.Common, @@ -245,7 +247,7 @@ func (gui *Gui) onNewRepo(startArgs types.StartArgs, reuseState bool) error { // it gets a bit confusing to land back in the status panel when visiting a repo // you've already switched from. There's no doubt some easy way to make the UX // optimal for all cases but I'm too lazy to think about what that is right now -func (gui *Gui) resetState(startArgs types.StartArgs, reuseState bool) { +func (gui *Gui) resetState(startArgs appTypes.StartArgs, reuseState bool) { currentDir, err := os.Getwd() if reuseState { @@ -300,28 +302,28 @@ func (gui *Gui) resetState(startArgs types.StartArgs, reuseState bool) { gui.RepoStateMap[Repo(currentDir)] = gui.State } -func initialScreenMode(startArgs types.StartArgs) WindowMaximisation { - if startArgs.FilterPath != "" || startArgs.GitArg != types.GitArgNone { +func initialScreenMode(startArgs appTypes.StartArgs) WindowMaximisation { + if startArgs.FilterPath != "" || startArgs.GitArg != appTypes.GitArgNone { return SCREEN_HALF } else { return SCREEN_NORMAL } } -func initialContext(contextTree *context.ContextTree, startArgs types.StartArgs) types.IListContext { +func initialContext(contextTree *context.ContextTree, startArgs appTypes.StartArgs) types.IListContext { var initialContext types.IListContext = contextTree.Files if startArgs.FilterPath != "" { initialContext = contextTree.LocalCommits - } else if startArgs.GitArg != types.GitArgNone { + } else if startArgs.GitArg != appTypes.GitArgNone { switch startArgs.GitArg { - case types.GitArgStatus: + case appTypes.GitArgStatus: initialContext = contextTree.Files - case types.GitArgBranch: + case appTypes.GitArgBranch: initialContext = contextTree.Branches - case types.GitArgLog: + case appTypes.GitArgLog: initialContext = contextTree.LocalCommits - case types.GitArgStash: + case appTypes.GitArgStash: initialContext = contextTree.Stash default: panic("unhandled git arg") @@ -417,13 +419,15 @@ var RuneReplacements = map[rune]string{ graph.CommitSymbol: "o", } -func (gui *Gui) initGocui(headless bool) (*gocui.Gui, error) { - recordEvents := recordingEvents() +func (gui *Gui) initGocui(headless bool, test integrationTypes.IntegrationTest) (*gocui.Gui, error) { + recordEvents := RecordingEvents() playMode := gocui.NORMAL if recordEvents { playMode = gocui.RECORDING - } else if replaying() { + } else if Replaying() { playMode = gocui.REPLAYING + } else if test != nil { + playMode = gocui.REPLAYING_NEW } g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, playMode, headless, RuneReplacements) @@ -474,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()) +func (gui *Gui) Run(startArgs appTypes.StartArgs) error { + g, err := gui.initGocui(Headless(), startArgs.IntegrationTest) if err != nil { return err } @@ -490,23 +494,7 @@ func (gui *Gui) Run(startArgs types.StartArgs) error { }) deadlock.Opts.Disable = !gui.Debug - if replaying() { - gui.g.RecordingConfig = gocui.RecordingConfig{ - Speed: getRecordingSpeed(), - Leeway: 100, - } - - var err error - gui.g.Recording, err = gui.loadRecording() - if err != nil { - return err - } - - go utils.Safe(func() { - time.Sleep(time.Second * 40) - log.Fatal("40 seconds is up, lazygit recording took too long to complete") - }) - } + gui.handleTestMode(startArgs.IntegrationTest) gui.g.OnSearchEscape = gui.onSearchEscape if err := gui.Config.ReloadUserConfig(); err != nil { @@ -567,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 { @@ -593,7 +581,7 @@ func (gui *Gui) RunAndHandleError(startArgs types.StartArgs) error { } } - if err := gui.saveRecording(gui.g.Recording); err != nil { + if err := SaveRecording(gui.g.Recording); err != nil { return err } @@ -627,7 +615,7 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool, gui.Mutexes.SubprocessMutex.Lock() defer gui.Mutexes.SubprocessMutex.Unlock() - if replaying() { + if Replaying() { // we do not yet support running subprocesses within integration tests. So if // we're replaying an integration test and we're inside this method, something // has gone wrong, so we should fail diff --git a/pkg/gui/gui_driver.go b/pkg/gui/gui_driver.go new file mode 100644 index 000000000..860c6c9b8 --- /dev/null +++ b/pkg/gui/gui_driver.go @@ -0,0 +1,73 @@ +package gui + +import ( + "time" + + "github.com/gdamore/tcell/v2" + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "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 GuiDriver struct { + gui *Gui +} + +var _ integrationTypes.GuiDriver = &GuiDriver{} + +func (self *GuiDriver) PressKey(keyStr string) { + key := keybindings.GetKey(keyStr) + + var r rune + var tcellKey tcell.Key + switch v := key.(type) { + case rune: + r = v + tcellKey = tcell.KeyRune + case gocui.Key: + tcellKey = tcell.Key(v) + } + + self.gui.g.ReplayedEvents.Keys <- gocui.NewTcellKeyEventWrapper( + tcell.NewEventKey(tcellKey, r, tcell.ModNone), + 0, + ) +} + +func (self *GuiDriver) Keys() config.KeybindingConfig { + return self.gui.Config.GetUserConfig().Keybinding +} + +func (self *GuiDriver) CurrentContext() types.Context { + return self.gui.c.CurrentContext() +} + +func (self *GuiDriver) Model() *types.Model { + return self.gui.State.Model +} + +func (self *GuiDriver) Fail(message string) { + self.gui.g.Close() + // need to give the gui time to close + time.Sleep(time.Millisecond * 100) + panic(message) +} + +// logs to the normal place that you log to i.e. viewable with `lazygit --logs` +func (self *GuiDriver) Log(message string) { + self.gui.c.Log.Warn(message) +} + +// logs in the actual UI (in the commands panel) +func (self *GuiDriver) LogUI(message string) { + self.gui.c.LogAction(message) +} + +func (self *GuiDriver) CheckedOutRef() *models.Branch { + return self.gui.helpers.Refs.GetCheckedOutRef() +} diff --git a/pkg/gui/recent_repos_panel.go b/pkg/gui/recent_repos_panel.go index 73d6e54c5..705461726 100644 --- a/pkg/gui/recent_repos_panel.go +++ b/pkg/gui/recent_repos_panel.go @@ -9,6 +9,7 @@ import ( "sync" "github.com/jesseduffield/generics/slices" + appTypes "github.com/jesseduffield/lazygit/pkg/app/types" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/env" "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" @@ -152,7 +153,7 @@ func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error { gui.Mutexes.RefreshingFilesMutex.Lock() defer gui.Mutexes.RefreshingFilesMutex.Unlock() - return gui.onNewRepo(types.StartArgs{}, reuse) + return gui.onNewRepo(appTypes.StartArgs{}, reuse) } // updateRecentRepoList registers the fact that we opened lazygit in this repo, diff --git a/pkg/gui/recording.go b/pkg/gui/recording.go deleted file mode 100644 index 9edd50f08..000000000 --- a/pkg/gui/recording.go +++ /dev/null @@ -1,74 +0,0 @@ -package gui - -import ( - "encoding/json" - "io/ioutil" - "log" - "os" - "strconv" - - "github.com/jesseduffield/gocui" -) - -func recordingEvents() bool { - return recordEventsTo() != "" -} - -func recordEventsTo() string { - return os.Getenv("RECORD_EVENTS_TO") -} - -func replaying() bool { - return os.Getenv("REPLAY_EVENTS_FROM") != "" -} - -func headless() bool { - return os.Getenv("HEADLESS") != "" -} - -func getRecordingSpeed() float64 { - // humans are slow so this speeds things up. - speed := 1.0 - envReplaySpeed := os.Getenv("SPEED") - if envReplaySpeed != "" { - var err error - speed, err = strconv.ParseFloat(envReplaySpeed, 64) - if err != nil { - log.Fatal(err) - } - } - return speed -} - -func (gui *Gui) loadRecording() (*gocui.Recording, error) { - path := os.Getenv("REPLAY_EVENTS_FROM") - - data, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - recording := &gocui.Recording{} - - err = json.Unmarshal(data, &recording) - if err != nil { - return nil, err - } - - return recording, nil -} - -func (gui *Gui) saveRecording(recording *gocui.Recording) error { - if !recordingEvents() { - return nil - } - - jsonEvents, err := json.Marshal(recording) - if err != nil { - return err - } - - path := recordEventsTo() - - return ioutil.WriteFile(path, jsonEvents, 0o600) -} diff --git a/pkg/gui/services/custom_commands/resolver.go b/pkg/gui/services/custom_commands/resolver.go index 35ebbd9d1..4702d36c4 100644 --- a/pkg/gui/services/custom_commands/resolver.go +++ b/pkg/gui/services/custom_commands/resolver.go @@ -1,6 +1,9 @@ package custom_commands import ( + "bytes" + "text/template" + "github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/config" ) @@ -101,3 +104,23 @@ func (self *Resolver) resolveMenuOption(option *config.CustomCommandMenuOption, Value: value, }, nil } + +type CustomCommandObject struct { + // deprecated. Use Responses instead + PromptResponses []string + Form map[string]string +} + +func ResolveTemplate(templateStr string, object interface{}) (string, error) { + tmpl, err := template.New("template").Parse(templateStr) + if err != nil { + return "", err + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, object); err != nil { + return "", err + } + + return buf.String(), nil +} diff --git a/pkg/gui/test_mode.go b/pkg/gui/test_mode.go new file mode 100644 index 000000000..cd3bd83ba --- /dev/null +++ b/pkg/gui/test_mode.go @@ -0,0 +1,124 @@ +package gui + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" + "strconv" + "time" + + "github.com/jesseduffield/gocui" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +type IntegrationTest interface { + Run(guiAdapter *GuiDriver) +} + +func (gui *Gui) handleTestMode(test integrationTypes.IntegrationTest) { + if test != nil { + go func() { + time.Sleep(time.Millisecond * 100) + + test.Run(&GuiDriver{gui: gui}) + + gui.g.Update(func(*gocui.Gui) error { + return gocui.ErrQuit + }) + + time.Sleep(time.Second * 1) + + log.Fatal("gocui should have already exited") + }() + + go utils.Safe(func() { + time.Sleep(time.Second * 40) + log.Fatal("40 seconds is up, lazygit recording took too long to complete") + }) + } + + if Replaying() { + gui.g.RecordingConfig = gocui.RecordingConfig{ + Speed: GetRecordingSpeed(), + Leeway: 100, + } + + var err error + gui.g.Recording, err = LoadRecording() + if err != nil { + panic(err) + } + + go utils.Safe(func() { + time.Sleep(time.Second * 40) + log.Fatal("40 seconds is up, lazygit recording took too long to complete") + }) + } +} + +func Headless() bool { + return os.Getenv("HEADLESS") != "" +} + +// OLD integration test format stuff + +func Replaying() bool { + return os.Getenv("REPLAY_EVENTS_FROM") != "" +} + +func RecordingEvents() bool { + return recordEventsTo() != "" +} + +func recordEventsTo() string { + return os.Getenv("RECORD_EVENTS_TO") +} + +func GetRecordingSpeed() float64 { + // humans are slow so this speeds things up. + speed := 1.0 + envReplaySpeed := os.Getenv("SPEED") + if envReplaySpeed != "" { + var err error + speed, err = strconv.ParseFloat(envReplaySpeed, 64) + if err != nil { + log.Fatal(err) + } + } + return speed +} + +func LoadRecording() (*gocui.Recording, error) { + path := os.Getenv("REPLAY_EVENTS_FROM") + + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + recording := &gocui.Recording{} + + err = json.Unmarshal(data, &recording) + if err != nil { + return nil, err + } + + return recording, nil +} + +func SaveRecording(recording *gocui.Recording) error { + if !RecordingEvents() { + return nil + } + + jsonEvents, err := json.Marshal(recording) + if err != nil { + return err + } + + path := recordEventsTo() + + return ioutil.WriteFile(path, jsonEvents, 0o600) +} diff --git a/pkg/integration/README.md b/pkg/integration/README.md new file mode 100644 index 000000000..01d5786b1 --- /dev/null +++ b/pkg/integration/README.md @@ -0,0 +1,74 @@ +# Integration Tests + +The pkg/integration pacakge 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. + +## Writing tests + +The tests live in pkg/integration/tests. Each test has two important steps: the setup step and the run step. + +### Setup step + +In the setup step, we prepare a repo with shell commands, for example, creating a merge conflict that will need to be resolved upon opening lazygit. This is all done via the `shell` argument. + +### Run step + +The run step has four arguments passed in: + +1. `shell` +2. `input` +3. `assert` +4. `keys` + +`shell` we've already seen in the setup step. The reason it's passed into the run step is that we may want to emulate background events. For example, the user modifying a file outside of lazygit. + +`input` is for driving the gui by pressing certain keys, selecting list items, etc. + +`assert` is for asserting on the state of the lazygit session. When you call a method on `assert`, the assert struct will wait for the assertion to hold true and then continue (failing the test after a timeout). For this reason, assertions have two purposes: one is to ensure the test fails as soon as something unexpected happens, but another is to allow lazygit to process a keypress before you follow up with more keypresses. If you input a bunch of keypresses too quickly lazygit might get confused. + +### Tips + +Try to do as much setup work as possible in your setup step. For example, if all you're testing is that the user is able to resolve merge conflicts, create the merge conflicts in the setup step. On the other hand, if you're testing to see that lazygit can warn the user about merge conflicts after an attempted merge, it's fine to wait until the run step to actually create the conflicts. If the run step is focused on the thing you're trying to test, the test will run faster and its intent will be clearer. + +Use assertions to ensure that lazygit has processed all your keybindings so far. For example, if you press 'n' on a branch to create a new branch, assert that the confirmation view is now focused. + +If you find yourself doing something frequently in a test, consider making it a method in one of the helper arguments. For example, instead of calling `input.PressKey(keys.Universal.Confirm)` in 100 places, it's better to have a method `input.Confirm()`. This is not to say that everything should be made into a method on the input struct: just things that are particularly common in tests. + +## Running tests + +There are three ways to invoke a test: + +1. go run pkg/integration/cmd/runner/main.go [...] +2. go run pkg/integration/cmd/tui/main.go +3. go test pkg/integration/go_test.go + +The first, the test runner, is for directly running a test from the command line. If you pass no arguments, it runs all tests. +The second, the TUI, is for running tests from a terminal UI where it's easier to find a test and run it without having to copy it's name and paste it into the terminal. This is the easiest approach by far. +The third, the go-test command, intended only for use in CI, to be run along with the other `go test` tests. This runs the tests in headless mode so there's no visual output. + +The name of a test is based on its path, so the name of the test at `pkg/integration/tests/commit/new_branch.go` is commit/new_branch. So to run it with our test runner you would run `go run pkg/integration/cmd/runner/main.go commit/new_branch`. + +You can pass the KEY_PRESS_DELAY env var to the test runner in order to set a delay in milliseconds between keypresses, which helps for watching a test at a realistic speed to understand what it's doing. Or in the tui you can press 't' to run the test with a pre-set delay. + +### Snapshots + +At the moment (this is subject to change) each test has a snapshot repo created after running for the first time. These snapshots live in `test/integration_new`, in folders named 'expected' (alongside the 'actual' folders which contain the resulting repo from the last test run). Whenever you run a test, the resultant repo will be compared against the snapshot repo and if they're different, you'll be asked whether you want to update the snapshot. If you want to update a snapshot without being prompted you can pass MODE=updateSnapshot to the test runner or the go test command. This is useful when you've made a change to + +### Sandbox mode + +Say you want to do a manual test of how lazygit handles merge-conflicts, but you can't be bothered actually finding a way to create merge conflicts in a repo. To make your life easier, you can simply run a merge-conflicts test in sandbox mode, meaning the setup step is run for you, and then instead of the test driving the lazygit session, you're allowed to drive it yourself. + +To run a test in sandbox mode you can press 's' on a test in the test TUI or pass the env var MODE=sandbox to the test runner. + +## Migration process + +At the time of writing, most tests are created under an old approach, where you would record yourself in a lazygit session and then the test would replay the keybindings with the same timestamps. This old approach is great for writing tests quickly, but is much harder to maintain. It has to rely entirely on snapshots to determining if a test passes or fails, and can't do assertions along the way. It's also harder to grok what's the intention behind certain actions that take place within the test (e.g. was the recorder intentionally switching to another panel or was that just a misclick?). + +At the moment, all the deprecated test code lives in pkg/integration/deprecated. Hopefully in the very near future we migrate everything across so that we don't need to maintain two systems. + +We should never write any new tests under the old method, and if a given test breaks because of new functionality, it's best to simply rewrite it under the new approach. If you want to run a test for the sake of watching what it does so that you can transcribe it into the new approach, you can run: + +``` +go run pkg/integration/deprecated/cmd/tui/main.go +``` + +The tests in the old format live in test/integration. In the old format, test definitions are co-located with the snapshots. The setup step is done in a `setup.sh` shell script and the `recording.json` file contains the recorded keypresses to be replayed during the test. diff --git a/pkg/integration/cmd/injector/main.go b/pkg/integration/cmd/injector/main.go new file mode 100644 index 000000000..2f47d04a6 --- /dev/null +++ b/pkg/integration/cmd/injector/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + "os" + + "github.com/jesseduffield/lazygit/pkg/app" + "github.com/jesseduffield/lazygit/pkg/app/daemon" + "github.com/jesseduffield/lazygit/pkg/integration" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" +) + +// The purpose of this program is to run lazygit with an integration test passed in. +// We could have done the check on LAZYGIT_TEST_NAME in the root main.go but +// that would mean lazygit would be depending on integration test code which +// would bloat the binary. + +// You should not invoke this program directly. Instead you should go through +// pkg/integration/cmd/runner/main.go or pkg/integration/cmd/tui/main.go + +func main() { + dummyBuildInfo := &app.BuildInfo{ + Commit: "", + Date: "", + Version: "", + BuildSource: "integration test", + } + + integrationTest := getIntegrationTest() + + app.Start(dummyBuildInfo, integrationTest) +} + +func getIntegrationTest() integrationTypes.IntegrationTest { + if daemon.InDaemonMode() { + // if we've invoked lazygit as a daemon from within lazygit, + // we don't want to pass a test to the rest of the code. + return nil + } + + integrationTestName := os.Getenv(integration.LAZYGIT_TEST_NAME_ENV_VAR) + if integrationTestName == "" { + panic(fmt.Sprintf( + "expected %s environment variable to be set, given that we're running an integration test", + integration.LAZYGIT_TEST_NAME_ENV_VAR, + )) + } + + for _, candidateTest := range integration.Tests { + if candidateTest.Name() == integrationTestName { + return candidateTest + } + } + + panic("Could not find integration test with name: " + integrationTestName) +} diff --git a/pkg/integration/cmd/runner/main.go b/pkg/integration/cmd/runner/main.go new file mode 100644 index 000000000..1fbde96c5 --- /dev/null +++ b/pkg/integration/cmd/runner/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "log" + "os" + "os/exec" + + "github.com/jesseduffield/generics/slices" + "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +// see pkg/integration/README.md + +// The purpose of this program is to run integration tests. It does this by +// building our injector program (in the sibling injector directory) and then for +// each test we're running, invoke the injector program with the test's name as +// an environment variable. Then the injector finds the test and passes it to +// the lazygit startup code. + +// If invoked directly, you can specify tests to run by passing their names as positional arguments + +func main() { + mode := integration.GetModeFromEnv() + includeSkipped := os.Getenv("INCLUDE_SKIPPED") == "true" + var testsToRun []*components.IntegrationTest + + if len(os.Args) > 1 { + outer: + for _, testName := range os.Args[1:] { + // check if our given test name actually exists + for _, test := range integration.Tests { + if test.Name() == testName { + testsToRun = append(testsToRun, test) + continue outer + } + } + log.Fatalf("test %s not found. Perhaps you forgot to add it to `pkg/integration/integration_tests/tests.go`?", testName) + } + } else { + testsToRun = integration.Tests + } + + testNames := slices.Map(testsToRun, func(test *components.IntegrationTest) string { + return test.Name() + }) + + err := integration.RunTests( + log.Printf, + runCmdInTerminal, + func(test *components.IntegrationTest, f func() error) { + if !slices.Contains(testNames, test.Name()) { + return + } + if err := f(); err != nil { + log.Print(err.Error()) + } + }, + mode, + includeSkipped, + ) + if err != nil { + log.Print(err.Error()) + } +} + +func runCmdInTerminal(cmd *exec.Cmd) error { + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + + return cmd.Run() +} diff --git a/pkg/integration/cmd/tui/main.go b/pkg/integration/cmd/tui/main.go new file mode 100644 index 000000000..c9e533f61 --- /dev/null +++ b/pkg/integration/cmd/tui/main.go @@ -0,0 +1,306 @@ +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/components" + "github.com/jesseduffield/lazygit/pkg/secureexec" +) + +// This program lets you run integration tests from a TUI. See pkg/integration/README.md for more info. + +type App struct { + tests []*components.IntegrationTest + itemIdx int + testDir string + filtering bool + g *gocui.Gui +} + +func (app *App) getCurrentTest() *components.IntegrationTest { + if len(app.tests) > 0 { + return app.tests[app.itemIdx] + } + 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/cmd/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/cmd/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/cmd/runner/main.go %s", currentTest.Name())) + app.runSubprocess(cmd) + + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", 'o', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + currentTest := app.getCurrentTest() + if currentTest == nil { + return nil + } + + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("code -r pkg/integration/tests/%s", currentTest.Name())) + if err := cmd.Run(); err != nil { + return err + } + + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", 'O', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + currentTest := app.getCurrentTest() + if currentTest == nil { + return nil + } + + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("code test/integration_new/%s", currentTest.Name())) + if err := cmd.Run(); err != nil { + return err + } + + return nil + }); err != nil { + log.Panicln(err) + } + + if err := g.SetKeybinding("list", '/', gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + app.filtering = true + if _, err := g.SetCurrentView("editor"); err != nil { + return err + } + editorView, err := g.View("editor") + if err != nil { + return err + } + editorView.Clear() + + return nil + }); err != nil { + log.Panicln(err) + } + + // not using the editor yet, but will use it to help filter the list + if err := g.SetKeybinding("editor", gocui.KeyEsc, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + app.filtering = false + if _, err := g.SetCurrentView("list"); err != nil { + return err + } + + return nil + }); err != nil { + log.Panicln(err) + } + + err = g.MainLoop() + g.Close() + switch err { + case gocui.ErrQuit: + return + default: + log.Panicln(err) + } +} + +func (app *App) runSubprocess(cmd *exec.Cmd) { + if err := gocui.Screen.Suspend(); err != nil { + panic(err) + } + + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err := cmd.Run(); err != nil { + log.Println(err.Error()) + } + cmd.Stdin = nil + cmd.Stderr = nil + cmd.Stdout = nil + + fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint("press enter to return")) + fmt.Scanln() // wait for enter press + + if err := gocui.Screen.Resume(); err != nil { + panic(err) + } +} + +func (app *App) layout(g *gocui.Gui) error { + maxX, maxY := g.Size() + descriptionViewHeight := 7 + keybindingsViewHeight := 3 + editorViewHeight := 3 + if !app.filtering { + editorViewHeight = 0 + } else { + descriptionViewHeight = 0 + keybindingsViewHeight = 0 + } + g.Cursor = app.filtering + g.FgColor = gocui.ColorGreen + listView, err := g.SetView("list", 0, 0, maxX-1, maxY-descriptionViewHeight-keybindingsViewHeight-editorViewHeight-1, 0) + if err != nil { + if err.Error() != "unknown view" { + return err + } + listView.Highlight = true + listView.Clear() + for _, test := range app.tests { + fmt.Fprintln(listView, test.Name()) + } + listView.Title = "Tests" + listView.FgColor = gocui.ColorDefault + if _, err := g.SetCurrentView("list"); err != nil { + return err + } + } + + descriptionView, err := g.SetViewBeneath("description", "list", descriptionViewHeight) + if err != nil { + if err.Error() != "unknown view" { + return err + } + descriptionView.Title = "Test description" + descriptionView.Wrap = true + descriptionView.FgColor = gocui.ColorDefault + } + + keybindingsView, err := g.SetViewBeneath("keybindings", "description", keybindingsViewHeight) + if err != nil { + if err.Error() != "unknown view" { + return err + } + keybindingsView.Title = "Keybindings" + keybindingsView.Wrap = true + keybindingsView.FgColor = gocui.ColorDefault + fmt.Fprintln(keybindingsView, "up/down: navigate, enter: run test, t: run test slow, s: sandbox, o: open test file, shift+o: open test snapshot directory, forward-slash: filter") + } + + editorView, err := g.SetViewBeneath("editor", "keybindings", editorViewHeight) + if err != nil { + if err.Error() != "unknown view" { + return err + } + editorView.Title = "Filter" + editorView.FgColor = gocui.ColorDefault + editorView.Editable = true + } + + currentTest := app.getCurrentTest() + if currentTest == nil { + return nil + } + + descriptionView.Clear() + fmt.Fprint(descriptionView, currentTest.Description()) + + if err := g.SetKeybinding("list", gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { + if app.itemIdx < len(app.tests)-1 { + app.itemIdx++ + } + + listView, err := g.View("list") + if err != nil { + return err + } + listView.FocusPoint(0, app.itemIdx) + return nil + }); err != nil { + log.Panicln(err) + } + + return nil +} + +func quit(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit +} diff --git a/pkg/integration/components/assert.go b/pkg/integration/components/assert.go new file mode 100644 index 000000000..584ad438b --- /dev/null +++ b/pkg/integration/components/assert.go @@ -0,0 +1,111 @@ +package components + +import ( + "fmt" + "strings" + "time" + + "github.com/jesseduffield/lazygit/pkg/gui/types" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" +) + +// through this struct we assert on the state of the lazygit gui + +type Assert struct { + gui integrationTypes.GuiDriver +} + +func NewAssert(gui integrationTypes.GuiDriver) *Assert { + return &Assert{gui: gui} +} + +func (self *Assert) WorkingTreeFileCount(expectedCount int) { + self.assertWithRetries(func() (bool, string) { + actualCount := len(self.gui.Model().Files) + + return actualCount == expectedCount, fmt.Sprintf( + "Expected %d changed working tree files, but got %d", + expectedCount, actualCount, + ) + }) +} + +func (self *Assert) CommitCount(expectedCount int) { + self.assertWithRetries(func() (bool, string) { + actualCount := len(self.gui.Model().Commits) + + return actualCount == expectedCount, fmt.Sprintf( + "Expected %d commits present, but got %d", + expectedCount, actualCount, + ) + }) +} + +func (self *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" + } + + headCommit := self.gui.Model().Commits[0] + if headCommit.Name != expectedMessage { + return false, fmt.Sprintf( + "Expected commit message to be '%s', but got '%s'", + expectedMessage, headCommit.Name, + ) + } + + return true, "" + }) +} + +func (self *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 *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 *Assert) InListContext() { + self.assertWithRetries(func() (bool, string) { + currentContext := self.gui.CurrentContext() + _, ok := currentContext.(types.IListContext) + return ok, fmt.Sprintf("Expected current context to be a list context, but got %s", currentContext.GetKey()) + }) +} + +func (self *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 *Assert) assertWithRetries(test func() (bool, string)) { + waitTimes := []int{0, 1, 5, 10, 200, 500, 1000} + + var message string + for _, waitTime := range waitTimes { + time.Sleep(time.Duration(waitTime) * time.Millisecond) + + var ok bool + ok, message = test() + if ok { + return + } + } + + self.Fail(message) +} + +// for when you just want to fail the test yourself +func (self *Assert) Fail(message string) { + self.gui.Fail(message) +} diff --git a/pkg/integration/components/input.go b/pkg/integration/components/input.go new file mode 100644 index 000000000..d44b11830 --- /dev/null +++ b/pkg/integration/components/input.go @@ -0,0 +1,166 @@ +package components + +import ( + "fmt" + "strings" + "time" + + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gui/types" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" +) + +type Input struct { + gui integrationTypes.GuiDriver + keys config.KeybindingConfig + assert *Assert + pushKeyDelay int +} + +func NewInput(gui integrationTypes.GuiDriver, keys config.KeybindingConfig, assert *Assert, pushKeyDelay int) *Input { + return &Input{ + gui: gui, + keys: keys, + assert: assert, + pushKeyDelay: pushKeyDelay, + } +} + +// key is something like 'w' or ''. It's best not to pass a direct value, +// but instead to go through the default user config to get a more meaningful key name +func (self *Input) PressKeys(keyStrs ...string) { + for _, keyStr := range keyStrs { + self.pressKey(keyStr) + } +} + +func (self *Input) pressKey(keyStr string) { + self.Wait(self.pushKeyDelay) + + self.gui.PressKey(keyStr) +} + +func (self *Input) SwitchToStatusWindow() { + self.pressKey(self.keys.Universal.JumpToBlock[0]) +} + +func (self *Input) SwitchToFilesWindow() { + self.pressKey(self.keys.Universal.JumpToBlock[1]) +} + +func (self *Input) SwitchToBranchesWindow() { + self.pressKey(self.keys.Universal.JumpToBlock[2]) +} + +func (self *Input) SwitchToCommitsWindow() { + self.pressKey(self.keys.Universal.JumpToBlock[3]) +} + +func (self *Input) SwitchToStashWindow() { + self.pressKey(self.keys.Universal.JumpToBlock[4]) +} + +func (self *Input) Type(content string) { + for _, char := range content { + self.pressKey(string(char)) + } +} + +// i.e. pressing enter +func (self *Input) Confirm() { + self.pressKey(self.keys.Universal.Confirm) +} + +// i.e. pressing escape +func (self *Input) Cancel() { + self.pressKey(self.keys.Universal.Return) +} + +// i.e. pressing space +func (self *Input) Select() { + self.pressKey(self.keys.Universal.Select) +} + +// i.e. pressing down arrow +func (self *Input) NextItem() { + self.pressKey(self.keys.Universal.NextItem) +} + +// i.e. pressing up arrow +func (self *Input) PreviousItem() { + self.pressKey(self.keys.Universal.PrevItem) +} + +func (self *Input) ContinueMerge() { + self.PressKeys(self.keys.Universal.CreateRebaseOptionsMenu) + self.assert.SelectedLineContains("continue") + self.Confirm() +} + +func (self *Input) ContinueRebase() { + self.ContinueMerge() +} + +// for when you want to allow lazygit to process something before continuing +func (self *Input) Wait(milliseconds int) { + time.Sleep(time.Duration(milliseconds) * time.Millisecond) +} + +func (self *Input) LogUI(message string) { + self.gui.LogUI(message) +} + +func (self *Input) 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 *Input) NavigateToListItemContainingText(text string) { + self.assert.InListContext() + + currentContext := self.gui.CurrentContext().(types.IListContext) + + view := currentContext.GetView() + + // first we look for a duplicate on the current screen. We won't bother looking beyond that though. + matchCount := 0 + matchIndex := -1 + for i, line := range view.ViewBufferLines() { + if strings.Contains(line, text) { + matchCount++ + matchIndex = i + } + } + if matchCount > 1 { + self.assert.Fail(fmt.Sprintf("Found %d matches for %s, expected only a single match", matchCount, text)) + } + if matchCount == 1 { + selectedLineIdx := view.SelectedLineIdx() + if selectedLineIdx == matchIndex { + return + } + if selectedLineIdx < matchIndex { + for i := selectedLineIdx; i < matchIndex; i++ { + self.NextItem() + } + return + } else { + for i := selectedLineIdx; i > matchIndex; i-- { + self.PreviousItem() + } + return + } + } + + self.assert.Fail(fmt.Sprintf("Could not find item containing text: %s", text)) +} diff --git a/pkg/integration/components/shell.go b/pkg/integration/components/shell.go new file mode 100644 index 000000000..ee57cf401 --- /dev/null +++ b/pkg/integration/components/shell.go @@ -0,0 +1,83 @@ +package components + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/jesseduffield/lazygit/pkg/secureexec" + "github.com/mgutz/str" +) + +// 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{} + +func NewShell() *Shell { + return &Shell{} +} + +func (s *Shell) RunCommand(cmdStr string) *Shell { + args := str.ToArgv(cmdStr) + cmd := secureexec.Command(args[0], args[1:]...) + cmd.Env = os.Environ() + + output, err := cmd.CombinedOutput() + if err != nil { + panic(fmt.Sprintf("error running command: %s\n%s", cmdStr, string(output))) + } + + return s +} + +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)) + } + + return s +} + +func (s *Shell) NewBranch(name string) *Shell { + return s.RunCommand("git checkout -b " + name) +} + +func (s *Shell) GitAdd(path string) *Shell { + return s.RunCommand(fmt.Sprintf("git add \"%s\"", path)) +} + +func (s *Shell) GitAddAll() *Shell { + return s.RunCommand("git add -A") +} + +func (s *Shell) Commit(message string) *Shell { + return s.RunCommand(fmt.Sprintf("git commit -m \"%s\"", message)) +} + +func (s *Shell) EmptyCommit(message string) *Shell { + return s.RunCommand(fmt.Sprintf("git commit --allow-empty -m \"%s\"", message)) +} + +// 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) +} + +// 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), + fmt.Sprintf("file%02d content", i), + ). + Commit(fmt.Sprintf("commit %02d", i)) + } + + return s +} diff --git a/pkg/integration/components/test.go b/pkg/integration/components/test.go new file mode 100644 index 000000000..a5973c07a --- /dev/null +++ b/pkg/integration/components/test.go @@ -0,0 +1,129 @@ +package components + +import ( + "os" + "strconv" + "strings" + + "github.com/jesseduffield/lazygit/pkg/config" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +// Test describes an integration tests that will be run against the lazygit gui. + +// our unit tests will use this description to avoid a panic caused by attempting +// to get the test's name via it's file's path. +const unitTestDescription = "test test" + +type IntegrationTest struct { + name string + description string + extraCmdArgs string + skip bool + setupRepo func(shell *Shell) + setupConfig func(config *config.AppConfig) + run func( + shell *Shell, + input *Input, + assert *Assert, + keys config.KeybindingConfig, + ) +} + +var _ integrationTypes.IntegrationTest = &IntegrationTest{} + +type NewIntegrationTestArgs struct { + // Briefly describes what happens in the test and what it's testing for + Description string + // prepares a repo for testing + SetupRepo func(shell *Shell) + // takes a config and mutates. The mutated context will end up being passed to the gui + SetupConfig func(config *config.AppConfig) + // runs the test + Run func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) + // additional args passed to lazygit + ExtraCmdArgs string + // for when a test is flakey + Skip bool +} + +func NewIntegrationTest(args NewIntegrationTestArgs) *IntegrationTest { + name := "" + if args.Description != unitTestDescription { + // this panics if we're in a unit test for our integration tests, + // so we're using "test test" as a sentinel value + name = testNameFromFilePath() + } + + return &IntegrationTest{ + name: name, + description: args.Description, + extraCmdArgs: args.ExtraCmdArgs, + skip: args.Skip, + setupRepo: args.SetupRepo, + setupConfig: args.SetupConfig, + run: args.Run, + } +} + +func (self *IntegrationTest) Name() string { + return self.name +} + +func (self *IntegrationTest) Description() string { + return self.description +} + +func (self *IntegrationTest) ExtraCmdArgs() string { + return self.extraCmdArgs +} + +func (self *IntegrationTest) Skip() bool { + return self.skip +} + +func (self *IntegrationTest) SetupConfig(config *config.AppConfig) { + self.setupConfig(config) +} + +func (self *IntegrationTest) 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 *IntegrationTest) Run(gui integrationTypes.GuiDriver) { + shell := NewShell() + assert := NewAssert(gui) + keys := gui.Keys() + 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/tests/")[1] + + return name[:len(name)-len(".go")] +} + +// this is the delay in milliseconds between keypresses +// defaults to zero +func KeyPressDelay() int { + delayStr := os.Getenv("KEY_PRESS_DELAY") + if delayStr == "" { + return 0 + } + + delay, err := strconv.Atoi(delayStr) + if err != nil { + panic(err) + } + return delay +} diff --git a/pkg/integration/components/test_test.go b/pkg/integration/components/test_test.go new file mode 100644 index 000000000..3be317424 --- /dev/null +++ b/pkg/integration/components/test_test.go @@ -0,0 +1,105 @@ +package components + +import ( + "testing" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/gui/types" + integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types" + "github.com/stretchr/testify/assert" +) + +type fakeGuiDriver struct { + failureMessage string + pressedKeys []string +} + +var _ integrationTypes.GuiDriver = &fakeGuiDriver{} + +type GuiDriver 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 +} + +func (self *fakeGuiDriver) PressKey(key string) { + self.pressedKeys = append(self.pressedKeys, key) +} + +func (self *fakeGuiDriver) Keys() config.KeybindingConfig { + return config.KeybindingConfig{} +} + +func (self *fakeGuiDriver) CurrentContext() types.Context { + return nil +} + +func (self *fakeGuiDriver) Model() *types.Model { + return &types.Model{Commits: []*models.Commit{}} +} + +func (self *fakeGuiDriver) Fail(message string) { + self.failureMessage = message +} + +func (self *fakeGuiDriver) Log(message string) { +} + +func (self *fakeGuiDriver) LogUI(message string) { +} + +func (self *fakeGuiDriver) CheckedOutRef() *models.Branch { + return nil +} + +func TestAssertionFailure(t *testing.T) { + test := NewIntegrationTest(NewIntegrationTestArgs{ + Description: unitTestDescription, + Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) { + input.PressKeys("a") + input.PressKeys("b") + assert.CommitCount(2) + }, + }) + driver := &fakeGuiDriver{} + test.Run(driver) + assert.EqualValues(t, []string{"a", "b"}, driver.pressedKeys) + assert.Equal(t, "Expected 2 commits present, but got 0", driver.failureMessage) +} + +func TestManualFailure(t *testing.T) { + test := NewIntegrationTest(NewIntegrationTestArgs{ + Description: unitTestDescription, + Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) { + assert.Fail("blah") + }, + }) + driver := &fakeGuiDriver{} + test.Run(driver) + assert.Equal(t, "blah", driver.failureMessage) +} + +func TestSuccess(t *testing.T) { + test := NewIntegrationTest(NewIntegrationTestArgs{ + Description: unitTestDescription, + Run: func(shell *Shell, input *Input, assert *Assert, keys config.KeybindingConfig) { + input.PressKeys("a") + input.PressKeys("b") + assert.CommitCount(0) + }, + }) + driver := &fakeGuiDriver{} + test.Run(driver) + assert.EqualValues(t, []string{"a", "b"}, driver.pressedKeys) + assert.Equal(t, "", driver.failureMessage) +} diff --git a/test/runner/main.go b/pkg/integration/deprecated/cmd/runner/main.go similarity index 77% rename from test/runner/main.go rename to pkg/integration/deprecated/cmd/runner/main.go index 58ccd591f..e225b3bd3 100644 --- a/test/runner/main.go +++ b/pkg/integration/deprecated/cmd/runner/main.go @@ -7,11 +7,13 @@ import ( "os/exec" "testing" - "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/jesseduffield/lazygit/pkg/integration/deprecated" "github.com/stretchr/testify/assert" ) -// see docs/Integration_Tests.md +// Deprecated: This file is part of the old way of doing things. See pkg/integration/cmd/runner/main.go for the new way + +// see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md // This file can be invoked directly, but you might find it easier to go through // test/lazyintegration/main.go, which provides a convenient gui wrapper to integration tests. // @@ -20,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.IntegrationTest, f func(*testing.T) error) { if selectedTestName != "" && test.Name != selectedTestName { return } diff --git a/test/lazyintegration/main.go b/pkg/integration/deprecated/cmd/tui/main.go similarity index 91% rename from test/lazyintegration/main.go rename to pkg/integration/deprecated/cmd/tui/main.go index f62d92fbe..136ed29fe 100644 --- a/test/lazyintegration/main.go +++ b/pkg/integration/deprecated/cmd/tui/main.go @@ -10,21 +10,23 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/gui" "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/jesseduffield/lazygit/pkg/integration/deprecated" "github.com/jesseduffield/lazygit/pkg/secureexec" ) -// this program lets you manage integration tests in a TUI. See docs/Integration_Tests.md for more info. +// Deprecated. See lazy_integration for the new approach. + +// this program lets you manage integration tests in a TUI. See https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md for more info. type App struct { - tests []*integration.Test + tests []*deprecated.IntegrationTest itemIdx int testDir string editing bool g *gocui.Gui } -func (app *App) getCurrentTest() *integration.Test { +func (app *App) getCurrentTest() *deprecated.IntegrationTest { 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} @@ -106,7 +108,7 @@ func main() { return nil } - cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=record go run test/runner/main.go %s", currentTest.Name)) + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=record go run pkg/integration/deprecated/cmd/runner/main.go %s", currentTest.Name)) app.runSubprocess(cmd) return nil @@ -120,7 +122,7 @@ func main() { return nil } - cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=sandbox go run test/runner/main.go %s", currentTest.Name)) + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=sandbox go run pkg/integration/deprecated/cmd/runner/main.go %s", currentTest.Name)) app.runSubprocess(cmd) return nil @@ -134,7 +136,7 @@ func main() { return nil } - cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true go run test/runner/main.go %s", currentTest.Name)) + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true go run pkg/integration/deprecated/cmd/runner/main.go %s", currentTest.Name)) app.runSubprocess(cmd) return nil @@ -148,7 +150,7 @@ func main() { return nil } - cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=updateSnapshot go run test/runner/main.go %s", currentTest.Name)) + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=updateSnapshot go run pkg/integration/deprecated/cmd/runner/main.go %s", currentTest.Name)) app.runSubprocess(cmd) return nil @@ -162,7 +164,7 @@ func main() { return nil } - cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true SPEED=1 go run test/runner/main.go %s", currentTest.Name)) + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true SPEED=1 go run pkg/integration/deprecated/cmd/runner/main.go %s", currentTest.Name)) app.runSubprocess(cmd) return nil diff --git a/pkg/gui/gui_test.go b/pkg/integration/deprecated/go_test.go similarity index 89% rename from pkg/gui/gui_test.go rename to pkg/integration/deprecated/go_test.go index d2345d5d0..fbec34bd9 100644 --- a/pkg/gui/gui_test.go +++ b/pkg/integration/deprecated/go_test.go @@ -1,7 +1,7 @@ //go:build !windows // +build !windows -package gui +package deprecated import ( "fmt" @@ -13,10 +13,11 @@ import ( "testing" "github.com/creack/pty" - "github.com/jesseduffield/lazygit/pkg/integration" "github.com/stretchr/testify/assert" ) +// Deprecated. + // This file is quite similar to integration/main.go. The main difference is that this file is // run via `go test` whereas the other is run via `test/lazyintegration/main.go` which provides // a convenient gui wrapper around our integration tests. The `go test` approach is better @@ -25,10 +26,10 @@ import ( // you'll need to take the other approach // // As for this file, to run an integration test, e.g. for test 'commit', go: -// go test pkg/gui/gui_test.go -run /commit +// go test pkg/gui/old_gui_test.go -run /commit // // To update a snapshot for an integration test, pass UPDATE_SNAPSHOTS=true -// UPDATE_SNAPSHOTS=true go test pkg/gui/gui_test.go -run /commit +// UPDATE_SNAPSHOTS=true go test pkg/gui/old_gui_test.go -run /commit // // integration tests are run in test/integration//actual and the final test does // not clean up that directory so you can cd into it to see for yourself what @@ -44,7 +45,7 @@ func Test(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") != "" @@ -52,10 +53,10 @@ func Test(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 *IntegrationTest, f func(*testing.T) error) { defer func() { testNumber += 1 }() if testNumber%parallelTotal != parallelIndex { return @@ -78,6 +79,15 @@ func Test(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, @@ -94,12 +104,3 @@ func runCmdHeadless(cmd *exec.Cmd) error { return f.Close() } - -func tryConvert(numStr string, defaultVal int) int { - num, err := strconv.Atoi(numStr) - if err != nil { - return defaultVal - } - - return num -} diff --git a/pkg/integration/deprecated/integration.go b/pkg/integration/deprecated/integration.go new file mode 100644 index 000000000..b44fdb1b2 --- /dev/null +++ b/pkg/integration/deprecated/integration.go @@ -0,0 +1,564 @@ +package deprecated + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/jesseduffield/generics/slices" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/secureexec" +) + +// Deprecated: This file is part of the old way of doing things. See pkg/integration/integration.go for the new way + +// This package is for running our integration test suite. See https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md for more info. + +type IntegrationTest struct { + Name string `json:"name"` + Speed float64 `json:"speed"` + Description string `json:"description"` + ExtraCmdArgs string `json:"extraCmdArgs"` + Skip bool `json:"skip"` +} + +type Mode int + +const ( + // default: for when we're just running a test and comparing to the snapshot + TEST = iota + // for when we want to record a test and set the snapshot based on the result + RECORD + // when we just want to use the setup of the test for our own sandboxing purposes. + // This does not record the session and does not create/update snapshots + SANDBOX + // running a test but updating the snapshot + UPDATE_SNAPSHOT +) + +func GetModeFromEnv() Mode { + switch os.Getenv("MODE") { + case "record": + return RECORD + case "", "test": + return TEST + 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") + } +} + +// this function is used by both `go test` and from our lazyintegration gui, but +// errors need to be handled differently in each (for example go test is always +// working with *testing.T) so we pass in any differences as args here. +func RunTests( + logf func(format string, formatArgs ...interface{}), + runCmd func(cmd *exec.Cmd) error, + fnWrapper func(test *IntegrationTest, f func(*testing.T) error), + mode Mode, + speedEnv string, + onFail func(t *testing.T, expected string, actual string, prefix string), + includeSkipped bool, +) error { + rootDir := GetRootDirectory() + err := os.Chdir(rootDir) + if err != nil { + return err + } + + testDir := filepath.Join(rootDir, "test", "integration") + + osCommand := oscommands.NewDummyOSCommand() + err = osCommand.Cmd.New("go build -o " + tempLazygitPath()).Run() + if err != nil { + return err + } + + tests, err := LoadTests(testDir) + if err != nil { + return err + } + + for _, test := range tests { + test := test + + fnWrapper(test, func(t *testing.T) error { //nolint: thelper + if test.Skip && !includeSkipped { + logf("skipping test: %s", test.Name) + return nil + } + + speeds := getTestSpeeds(test.Speed, mode, speedEnv) + testPath := filepath.Join(testDir, test.Name) + actualDir := filepath.Join(testPath, "actual") + expectedDir := filepath.Join(testPath, "expected") + actualRepoDir := filepath.Join(actualDir, "repo") + logf("path: %s", testPath) + + for i, speed := range speeds { + if mode != SANDBOX && mode != RECORD { + logf("%s: attempting test at speed %f\n", test.Name, speed) + } + + findOrCreateDir(testPath) + prepareIntegrationTestDir(actualDir) + findOrCreateDir(actualRepoDir) + err := createFixture(testPath, actualRepoDir) + if err != nil { + return err + } + + configDir := filepath.Join(testPath, "used_config") + + cmd, err := getLazygitCommand(testPath, rootDir, mode, speed, test.ExtraCmdArgs) + if err != nil { + return err + } + + err = runCmd(cmd) + if err != nil { + return err + } + + if mode == UPDATE_SNAPSHOT || mode == RECORD { + // 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 + } + + success := true + 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 { + success = false + // if the snapshot doesn't match and we haven't tried all playback speeds different we'll retry at a slower speed + if i < len(speeds)-1 { + break + } + + // 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(t, expectedRepo, actualRepo, f.Name()) + } + } + + if success { + logf("%s: success at speed %f\n", test.Name, speed) + break + } + } + } + + return nil + }) + } + + 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 + } + + var actualFiles []os.FileInfo + actualFiles, err = ioutil.ReadDir(actualDir) + if err != nil { + return err + } + + 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 nil +} + +func getFileName(f os.FileInfo) string { + return f.Name() +} + +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 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 createFixture(testPath, actualDir string) error { + bashScriptPath := filepath.Join(testPath, "setup.sh") + cmd := secureexec.Command("bash", bashScriptPath, actualDir) + + if output, err := cmd.CombinedOutput(); err != nil { + return errors.New(string(output)) + } + + return nil +} + +func tempLazygitPath() string { + return filepath.Join("/tmp", "lazygit", "test_lazygit") +} + +func getTestSpeeds(testStartSpeed float64, mode Mode, speedStr string) []float64 { + if mode != TEST { + // have to go at original speed if updating snapshots in case we go to fast and create a junk snapshot + return []float64{1.0} + } + + if speedStr != "" { + speed, err := strconv.ParseFloat(speedStr, 64) + if err != nil { + panic(err) + } + return []float64{speed} + } + + // default is 10, 5, 1 + startSpeed := 10.0 + if testStartSpeed != 0 { + startSpeed = testStartSpeed + } + speeds := []float64{startSpeed} + if startSpeed > 5 { + speeds = append(speeds, 5) + } + speeds = append(speeds, 1, 1) + + return speeds +} + +func LoadTests(testDir string) ([]*IntegrationTest, error) { + paths, err := filepath.Glob(filepath.Join(testDir, "/*/test.json")) + if err != nil { + return nil, err + } + + tests := make([]*IntegrationTest, len(paths)) + + for i, path := range paths { + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + test := &IntegrationTest{} + + err = json.Unmarshal(data, test) + if err != nil { + return nil, err + } + + test.Name = strings.TrimPrefix(filepath.Dir(path), testDir+"/") + + tests[i] = test + } + + return tests, nil +} + +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) + } + } +} + +// 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 generateSnapshots(actualDir string, expectedDir string) (string, string, error) { + actual, err := generateSnapshot(actualDir) + if err != nil { + return "", "", err + } + + // 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. + expectedDirCopyDir := filepath.Join(filepath.Dir(expectedDir), "expected_dir_test") + err = oscommands.CopyDir(expectedDir, expectedDirCopyDir) + if err != nil { + return "", "", err + } + + defer func() { + err := os.RemoveAll(expectedDirCopyDir) + if err != nil { + panic(err) + } + }() + + if err := restoreSpecialPaths(expectedDirCopyDir); err != nil { + return "", "", err + } + + expected, err := generateSnapshot(expectedDirCopyDir) + if err != nil { + return "", "", err + } + + return actual, expected, 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 +} + +func getLazygitCommand(testPath string, rootDir string, mode Mode, speed float64, extraCmdArgs string) (*exec.Cmd, error) { + osCommand := oscommands.NewDummyOSCommand() + + replayPath := filepath.Join(testPath, "recording.json") + templateConfigDir := filepath.Join(rootDir, "test", "default_test_config") + actualRepoDir := filepath.Join(testPath, "actual", "repo") + + exists, err := osCommand.FileExists(filepath.Join(testPath, "config")) + if err != nil { + return nil, err + } + + if exists { + templateConfigDir = filepath.Join(testPath, "config") + } + + configDir := filepath.Join(testPath, "used_config") + + err = os.RemoveAll(configDir) + if err != nil { + 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, extraCmdArgs) + + cmdObj := osCommand.Cmd.New(cmdStr) + cmdObj.AddEnvVars(fmt.Sprintf("SPEED=%f", speed)) + + switch mode { + case RECORD: + cmdObj.AddEnvVars(fmt.Sprintf("RECORD_EVENTS_TO=%s", replayPath)) + case TEST, UPDATE_SNAPSHOT: + cmdObj.AddEnvVars(fmt.Sprintf("REPLAY_EVENTS_FROM=%s", replayPath)) + } + + return cmdObj.GetCmd(), nil +} diff --git a/pkg/integration/go_test.go b/pkg/integration/go_test.go new file mode 100644 index 000000000..0e55f9886 --- /dev/null +++ b/pkg/integration/go_test.go @@ -0,0 +1,79 @@ +//go:build !windows +// +build !windows + +package integration + +// this is the new way of running tests. See pkg/integration/integration_tests/commit.go +// for an example + +import ( + "io" + "io/ioutil" + "os" + "os/exec" + "strconv" + "testing" + + "github.com/creack/pty" + "github.com/jesseduffield/lazygit/pkg/integration/components" + "github.com/stretchr/testify/assert" +) + +func TestIntegration(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration tests in short mode") + } + + mode := GetModeFromEnv() + includeSkipped := os.Getenv("INCLUDE_SKIPPED") != "" + + parallelTotal := tryConvert(os.Getenv("PARALLEL_TOTAL"), 1) + parallelIndex := tryConvert(os.Getenv("PARALLEL_INDEX"), 0) + testNumber := 0 + + err := RunTests( + t.Logf, + runCmdHeadless, + func(test *components.IntegrationTest, f func() error) { + defer func() { testNumber += 1 }() + if testNumber%parallelTotal != parallelIndex { + return + } + + t.Run(test.Name(), func(t *testing.T) { + err := f() + assert.NoError(t, err) + }) + }, + mode, + includeSkipped, + ) + + assert.NoError(t, err) +} + +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() +} + +func tryConvert(numStr string, defaultVal int) int { + num, err := strconv.Atoi(numStr) + if err != nil { + return defaultVal + } + + return num +} diff --git a/pkg/integration/integration.go b/pkg/integration/integration.go index 4172f3e49..ca71241e2 100644 --- a/pkg/integration/integration.go +++ b/pkg/integration/integration.go @@ -1,7 +1,6 @@ package integration import ( - "encoding/json" "errors" "fmt" "io/ioutil" @@ -9,65 +8,47 @@ import ( "os" "os/exec" "path/filepath" - "strconv" "strings" - "testing" "github.com/jesseduffield/generics/slices" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/secureexec" + "github.com/jesseduffield/lazygit/pkg/integration/components" + "github.com/jesseduffield/lazygit/pkg/integration/tests" + "github.com/stretchr/testify/assert" ) -// This package is for running our integration test suite. See docs/Integration_Tests.md for more info +// this is the integration runner for the new and improved integration interface -type Test struct { - Name string `json:"name"` - Speed float64 `json:"speed"` - Description string `json:"description"` - ExtraCmdArgs string `json:"extraCmdArgs"` - Skip bool `json:"skip"` -} +var Tests = tests.Tests type Mode int const ( - // default: for when we're just running a test and comparing to the snapshot - TEST = iota - // for when we want to record a test and set the snapshot based on the result - RECORD - // when we just want to use the setup of the test for our own sandboxing purposes. - // This does not record the session and does not create/update snapshots - SANDBOX - // running a test but updating the snapshot + // 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 ) -func GetModeFromEnv() Mode { - switch os.Getenv("MODE") { - case "record": - return RECORD - case "", "test": - return TEST - case "updateSnapshot": - return UPDATE_SNAPSHOT - case "sandbox": - return SANDBOX - default: - log.Fatalf("unknown test mode: %s, must be one of [test, record, update, sandbox]", os.Getenv("MODE")) - panic("unreachable") - } -} +const LAZYGIT_TEST_NAME_ENV_VAR = "LAZYGIT_TEST_NAME" + +type ( + logf func(format string, formatArgs ...interface{}) +) -// this function is used by both `go test` and from our lazyintegration gui, but -// errors need to be handled differently in each (for example go test is always -// working with *testing.T) so we pass in any differences as args here. func RunTests( - logf func(format string, formatArgs ...interface{}), + logf logf, runCmd func(cmd *exec.Cmd) error, - fnWrapper func(test *Test, f func(*testing.T) error), + fnWrapper func(test *components.IntegrationTest, f func() error), mode Mode, - speedEnv string, - onFail func(t *testing.T, expected string, actual string, prefix string), includeSkipped bool, ) error { rootDir := GetRootDirectory() @@ -76,121 +57,88 @@ func RunTests( return err } - testDir := filepath.Join(rootDir, "test", "integration") + testDir := filepath.Join(rootDir, "test", "integration_new") osCommand := oscommands.NewDummyOSCommand() - err = osCommand.Cmd.New("go build -o " + tempLazygitPath()).Run() + err = osCommand.Cmd.New(fmt.Sprintf("go build -o %s pkg/integration/cmd/injector/main.go", tempLazygitPath())).Run() if err != nil { return err } - tests, err := LoadTests(testDir) - if err != nil { - return err - } - - for _, test := range tests { + for _, test := range Tests { test := test - fnWrapper(test, func(t *testing.T) error { //nolint: thelper - if test.Skip && !includeSkipped { - logf("skipping test: %s", test.Name) + fnWrapper(test, func() error { //nolint: thelper + if test.Skip() && !includeSkipped { + logf("skipping test: %s", test.Name()) return nil } - speeds := getTestSpeeds(test.Speed, mode, speedEnv) - testPath := filepath.Join(testDir, test.Name) + testPath := filepath.Join(testDir, test.Name()) + actualDir := filepath.Join(testPath, "actual") expectedDir := filepath.Join(testPath, "expected") actualRepoDir := filepath.Join(actualDir, "repo") logf("path: %s", testPath) - for i, speed := range speeds { - if mode != SANDBOX && mode != RECORD { - logf("%s: attempting test at speed %f\n", test.Name, speed) - } + findOrCreateDir(testPath) + prepareIntegrationTestDir(actualDir) + findOrCreateDir(actualRepoDir) + err := createFixture(test, actualRepoDir, rootDir) + if err != nil { + return err + } - findOrCreateDir(testPath) - prepareIntegrationTestDir(actualDir) - findOrCreateDir(actualRepoDir) - err := createFixture(testPath, actualRepoDir) - if err != nil { + configDir := filepath.Join(testPath, "used_config") + + cmd, err := getLazygitCommand(test, testPath, rootDir) + if err != nil { + return err + } + + err = runCmd(cmd) + if err != nil { + return err + } + + switch mode { + case UPDATE_SNAPSHOT: + if err := updateSnapshot(actualDir, expectedDir); err != nil { return err } - - configDir := filepath.Join(testPath, "used_config") - - cmd, err := getLazygitCommand(testPath, rootDir, mode, speed, test.ExtraCmdArgs) - if err != nil { + 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(actualDir, expectedDir); err != nil { + return err + } + logf("No existing snapshot found for %s. Created snapshot.", test.Name()) - err = runCmd(cmd) - if err != nil { - return err + return nil } - if mode == UPDATE_SNAPSHOT || mode == RECORD { - // create/update snapshot - err = oscommands.CopyDir(actualDir, expectedDir) - if err != nil { - return err - } + if err := compareSnapshots(logf, configDir, actualDir, expectedDir, test.Name()); err != nil { + logf("%s", 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 - } - - success := true - 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 { + // prompt user whether to update the snapshot (Y/N) + if promptUserToUpdateSnapshot() { + if err := updateSnapshot(actualDir, expectedDir); err != nil { return err } - - if expectedRepo != actualRepo { - success = false - // if the snapshot doesn't match and we haven't tried all playback speeds different we'll retry at a slower speed - if i < len(speeds)-1 { - break - } - - // 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(t, expectedRepo, actualRepo, f.Name()) - } - } - - if success { - logf("%s: success at speed %f\n", test.Name, speed) - break + logf("Snapshot updated: %s", test.Name()) + } else { + return err } } + + logf("Test passed: %s", test.Name()) + case SANDBOX: + logf("Session exited") } return nil @@ -200,48 +148,148 @@ func RunTests( 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) +func promptUserToUpdateSnapshot() bool { + fmt.Println("Test failed. Update snapshot? (y/n)") + var input string + fmt.Scanln(&input) + return input == "y" +} + +func updateSnapshot(actualDir string, expectedDir string) error { + // create/update snapshot + err := oscommands.CopyDir(actualDir, expectedDir) if err != nil { return err } - var actualFiles []os.FileInfo - actualFiles, err = ioutil.ReadDir(actualDir) + 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 } - 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) + 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 getFileName(f os.FileInfo) string { - return f.Name() +func createFixture(test *components.IntegrationTest, actualDir string, rootDir string) error { + if err := os.Chdir(actualDir); err != nil { + panic(err) + } + + shell := components.NewShell() + shell.RunCommand("git init") + shell.RunCommand(`git config user.email "CI@example.com"`) + shell.RunCommand(`git config user.name "CI"`) + + test.SetupRepo(shell) + + // changing directory back to rootDir after the setup is done + if err := os.Chdir(rootDir); err != nil { + panic(err) + } + + return nil } -func prepareIntegrationTestDir(actualDir string) { - // remove contents of integration test directory - dir, err := ioutil.ReadDir(actualDir) +func getLazygitCommand(test *components.IntegrationTest, testPath string, rootDir string) (*exec.Cmd, error) { + osCommand := oscommands.NewDummyOSCommand() + + templateConfigDir := filepath.Join(rootDir, "test", "default_test_config") + actualRepoDir := filepath.Join(testPath, "actual", "repo") + + configDir := filepath.Join(testPath, "used_config") + + err := os.RemoveAll(configDir) if err != nil { - if os.IsNotExist(err) { - err = os.Mkdir(actualDir, 0o777) - if err != nil { - panic(err) - } - } else { - panic(err) - } + return nil, err } - for _, d := range dir { - os.RemoveAll(filepath.Join(actualDir, d.Name())) + 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("%s=%s", LAZYGIT_TEST_NAME_ENV_VAR, 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") } } @@ -270,90 +318,22 @@ func GetRootDirectory() string { } } -func createFixture(testPath, actualDir string) error { - bashScriptPath := filepath.Join(testPath, "setup.sh") - cmd := secureexec.Command("bash", bashScriptPath, actualDir) - - if output, err := cmd.CombinedOutput(); err != nil { - return errors.New(string(output)) - } - - return nil -} - func tempLazygitPath() string { return filepath.Join("/tmp", "lazygit", "test_lazygit") } -func getTestSpeeds(testStartSpeed float64, mode Mode, speedStr string) []float64 { - if mode != TEST { - // have to go at original speed if updating snapshots in case we go to fast and create a junk snapshot - return []float64{1.0} - } - - if speedStr != "" { - speed, err := strconv.ParseFloat(speedStr, 64) - if err != nil { - panic(err) - } - return []float64{speed} - } - - // default is 10, 5, 1 - startSpeed := 10.0 - if testStartSpeed != 0 { - startSpeed = testStartSpeed - } - speeds := []float64{startSpeed} - if startSpeed > 5 { - speeds = append(speeds, 5) - } - speeds = append(speeds, 1, 1) - - return speeds -} - -func LoadTests(testDir string) ([]*Test, error) { - paths, err := filepath.Glob(filepath.Join(testDir, "/*/test.json")) +func generateSnapshots(actualDir string, expectedDir string) (string, string, error) { + actual, err := generateSnapshot(actualDir) if err != nil { - return nil, err + return "", "", err } - tests := make([]*Test, len(paths)) - - for i, path := range paths { - data, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - test := &Test{} - - err = json.Unmarshal(data, test) - if err != nil { - return nil, err - } - - test.Name = strings.TrimPrefix(filepath.Dir(path), testDir+"/") - - tests[i] = test - } - - return tests, nil -} - -func findOrCreateDir(path string) { - _, err := os.Stat(path) + expected, err := generateSnapshot(expectedDir) if err != nil { - if os.IsNotExist(err) { - err = os.MkdirAll(path, 0o777) - if err != nil { - panic(err) - } - } else { - panic(err) - } + return "", "", err } + + return actual, expected, nil } // note that we don't actually store this snapshot in the lazygit repo. @@ -425,41 +405,6 @@ func generateSnapshot(dir string) (string, error) { return snapshot, nil } -func generateSnapshots(actualDir string, expectedDir string) (string, string, error) { - actual, err := generateSnapshot(actualDir) - if err != nil { - return "", "", err - } - - // 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. - expectedDirCopyDir := filepath.Join(filepath.Dir(expectedDir), "expected_dir_test") - err = oscommands.CopyDir(expectedDir, expectedDirCopyDir) - if err != nil { - return "", "", err - } - - defer func() { - err := os.RemoveAll(expectedDirCopyDir) - if err != nil { - panic(err) - } - }() - - if err := restoreSpecialPaths(expectedDirCopyDir); err != nil { - return "", "", err - } - - expected, err := generateSnapshot(expectedDirCopyDir) - if err != nil { - return "", "", err - } - - return actual, expected, nil -} - func getPathsToRename(dir string, needle string, contains string) []string { pathsToRename := []string{} @@ -519,44 +464,75 @@ func restoreSpecialPaths(dir string) error { return nil } -func getLazygitCommand(testPath string, rootDir string, mode Mode, speed float64, extraCmdArgs string) (*exec.Cmd, error) { - osCommand := oscommands.NewDummyOSCommand() - - replayPath := filepath.Join(testPath, "recording.json") - templateConfigDir := filepath.Join(rootDir, "test", "default_test_config") - actualRepoDir := filepath.Join(testPath, "actual", "repo") - - exists, err := osCommand.FileExists(filepath.Join(testPath, "config")) +// 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 nil, err + return err } - if exists { - templateConfigDir = filepath.Join(testPath, "config") - } - - configDir := filepath.Join(testPath, "used_config") - - err = os.RemoveAll(configDir) + var actualFiles []os.FileInfo + actualFiles, err = ioutil.ReadDir(actualDir) if err != nil { - return nil, err - } - err = oscommands.CopyDir(templateConfigDir, configDir) - if err != nil { - return nil, err + return err } - cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s %s", tempLazygitPath(), configDir, actualRepoDir, extraCmdArgs) - - cmdObj := osCommand.Cmd.New(cmdStr) - cmdObj.AddEnvVars(fmt.Sprintf("SPEED=%f", speed)) - - switch mode { - case RECORD: - cmdObj.AddEnvVars(fmt.Sprintf("RECORD_EVENTS_TO=%s", replayPath)) - case TEST, UPDATE_SNAPSHOT: - cmdObj.AddEnvVars(fmt.Sprintf("REPLAY_EVENTS_FROM=%s", replayPath)) + 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 cmdObj.GetCmd(), nil + return nil +} + +func getFileName(f os.FileInfo) string { + return f.Name() +} + +func findOrCreateDir(path string) { + _, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + err = os.MkdirAll(path, 0o777) + if err != nil { + panic(err) + } + } else { + panic(err) + } + } +} + +func prepareIntegrationTestDir(actualDir string) { + // remove contents of integration test directory + dir, err := ioutil.ReadDir(actualDir) + if err != nil { + if os.IsNotExist(err) { + err = os.Mkdir(actualDir, 0o777) + if err != nil { + panic(err) + } + } else { + panic(err) + } + } + for _, d := range dir { + os.RemoveAll(filepath.Join(actualDir, d.Name())) + } +} + +func getDiff(prefix string, expected string, actual string) string { + mockT := &MockTestingT{} + assert.Equal(mockT, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual)) + return mockT.err +} + +type MockTestingT struct { + err string +} + +func (self *MockTestingT) Errorf(format string, args ...interface{}) { + self.err += fmt.Sprintf(format, args...) } diff --git a/pkg/integration/tests/branch/suggestions.go b/pkg/integration/tests/branch/suggestions.go new file mode 100644 index 000000000..0d8269f1d --- /dev/null +++ b/pkg/integration/tests/branch/suggestions.go @@ -0,0 +1,41 @@ +package branch + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var Suggestions = components.NewIntegrationTest(components.NewIntegrationTestArgs{ + Description: "Checking out a branch with name suggestions", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *components.Shell) { + shell. + EmptyCommit("my commit message"). + NewBranch("new-branch"). + NewBranch("new-branch-2"). + NewBranch("new-branch-3"). + NewBranch("branch-to-checkout"). + NewBranch("other-new-branch-2"). + NewBranch("other-new-branch-3") + }, + Run: func(shell *components.Shell, input *components.Input, assert *components.Assert, keys config.KeybindingConfig) { + input.SwitchToBranchesWindow() + assert.CurrentViewName("localBranches") + + input.PressKeys(keys.Branches.CheckoutBranchByName) + assert.CurrentViewName("confirmation") + + input.Type("branch-to") + + input.PressKeys(keys.Universal.TogglePanel) + assert.CurrentViewName("suggestions") + + // we expect the first suggestion to be the branch we want because it most + // closely matches what we typed in + input.Confirm() + + assert.CurrentBranchName("branch-to-checkout") + }, +}) diff --git a/pkg/integration/tests/commit/commit.go b/pkg/integration/tests/commit/commit.go new file mode 100644 index 000000000..12a68925d --- /dev/null +++ b/pkg/integration/tests/commit/commit.go @@ -0,0 +1,32 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var Commit = components.NewIntegrationTest(components.NewIntegrationTestArgs{ + Description: "Staging a couple files and committing", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *components.Shell) { + shell.CreateFile("myfile", "myfile content") + shell.CreateFile("myfile2", "myfile2 content") + }, + Run: func(shell *components.Shell, input *components.Input, assert *components.Assert, keys config.KeybindingConfig) { + assert.CommitCount(0) + + input.Select() + input.NextItem() + input.Select() + input.PressKeys(keys.Files.CommitChanges) + + commitMessage := "my commit message" + input.Type(commitMessage) + input.Confirm() + + assert.CommitCount(1) + assert.HeadCommitMessage(commitMessage) + }, +}) diff --git a/pkg/integration/tests/commit/new_branch.go b/pkg/integration/tests/commit/new_branch.go new file mode 100644 index 000000000..ad96938f5 --- /dev/null +++ b/pkg/integration/tests/commit/new_branch.go @@ -0,0 +1,38 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var NewBranch = components.NewIntegrationTest(components.NewIntegrationTestArgs{ + Description: "Creating a new branch from a commit", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *components.Shell) { + shell. + EmptyCommit("commit 1"). + EmptyCommit("commit 2"). + EmptyCommit("commit 3") + }, + Run: func(shell *components.Shell, input *components.Input, assert *components.Assert, keys config.KeybindingConfig) { + assert.CommitCount(3) + + input.SwitchToCommitsWindow() + assert.CurrentViewName("commits") + input.NextItem() + + input.PressKeys(keys.Universal.New) + + assert.CurrentViewName("confirmation") + + branchName := "my-branch-name" + input.Type(branchName) + input.Confirm() + + assert.CommitCount(2) + assert.HeadCommitMessage("commit 2") + assert.CurrentBranchName(branchName) + }, +}) diff --git a/pkg/integration/tests/interactive_rebase/one.go b/pkg/integration/tests/interactive_rebase/one.go new file mode 100644 index 000000000..3c785a727 --- /dev/null +++ b/pkg/integration/tests/interactive_rebase/one.go @@ -0,0 +1,41 @@ +package interactive_rebase + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var One = components.NewIntegrationTest(components.NewIntegrationTestArgs{ + Description: "Begins an interactive rebase, then fixups, drops, and squashes some commits", + ExtraCmdArgs: "", + Skip: false, + SetupConfig: func(config *config.AppConfig) {}, + SetupRepo: func(shell *components.Shell) { + shell. + CreateNCommits(5) // these will appears at commit 05, 04, 04, down to 01 + }, + Run: func(shell *components.Shell, input *components.Input, assert *components.Assert, keys config.KeybindingConfig) { + input.SwitchToCommitsWindow() + assert.CurrentViewName("commits") + + input.NavigateToListItemContainingText("commit 02") + input.PressKeys(keys.Universal.Edit) + assert.SelectedLineContains("YOU ARE HERE") + + input.PreviousItem() + input.PressKeys(keys.Commits.MarkCommitAsFixup) + assert.SelectedLineContains("fixup") + + input.PreviousItem() + input.PressKeys(keys.Universal.Remove) + assert.SelectedLineContains("drop") + + input.PreviousItem() + input.PressKeys(keys.Commits.SquashDown) + assert.SelectedLineContains("squash") + + input.ContinueRebase() + + assert.CommitCount(2) + }, +}) diff --git a/pkg/integration/tests/tests.go b/pkg/integration/tests/tests.go new file mode 100644 index 000000000..e9794169a --- /dev/null +++ b/pkg/integration/tests/tests.go @@ -0,0 +1,18 @@ +package tests + +import ( + "github.com/jesseduffield/lazygit/pkg/integration/components" + "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 = []*components.IntegrationTest{ + commit.Commit, + commit.NewBranch, + branch.Suggestions, + interactive_rebase.One, +} diff --git a/pkg/integration/types/types.go b/pkg/integration/types/types.go new file mode 100644 index 000000000..543212d59 --- /dev/null +++ b/pkg/integration/types/types.go @@ -0,0 +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" +) + +// 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 IntegrationTest interface { + Run(GuiDriver) + SetupConfig(config *config.AppConfig) +} + +// this is the interface through which our integration tests interact with the lazygit gui +type GuiDriver interface { + PressKey(string) + Keys() config.KeybindingConfig + CurrentContext() types.Context + Model() *types.Model + Fail(message string) + // These two log methods are for the sake of debugging while testing. There's no need to actually + // commit any logging. + // logs to the normal place that you log to i.e. viewable with `lazygit --logs` + Log(message string) + // logs in the actual UI (in the commands panel) + LogUI(message string) + CheckedOutRef() *models.Branch +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 3f882c2b5..2f33862e8 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -128,3 +128,10 @@ func StackTrace() string { n := runtime.Stack(buf, false) return fmt.Sprintf("%s\n", buf[:n]) } + +// returns the path of the file that calls the function. +// 'skip' is the number of stack frames to skip. +func FilePath(skip int) string { + _, path, _, _ := runtime.Caller(skip) + return path +} diff --git a/scripts/bisect.sh b/scripts/bisect.sh index 0e5f404cb..a3bc5f19e 100755 --- a/scripts/bisect.sh +++ b/scripts/bisect.sh @@ -2,7 +2,7 @@ # How to use: # 1) find a commit that is working fine. -# 2) Create an integration test capturing the fact that it works (Don't commit it). See https://github.com/jesseduffield/lazygit/blob/master/docs/Integration_Tests.md +# 2) Create an integration test capturing the fact that it works (Don't commit it). See https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md # 3) checkout the commit that's known to be failing # 4) run this script supplying the commit sha / tag name that works and the name of the newly created test diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/COMMIT_EDITMSG b/test/integration/branchSuggestions/expected/repo/.git_keep/COMMIT_EDITMSG deleted file mode 100644 index dc3ab4abe..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/COMMIT_EDITMSG +++ /dev/null @@ -1 +0,0 @@ -file0 diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/HEAD b/test/integration/branchSuggestions/expected/repo/.git_keep/HEAD deleted file mode 100644 index e2b7d4d2e..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/new-branch-3 diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/index b/test/integration/branchSuggestions/expected/repo/.git_keep/index deleted file mode 100644 index fbde0e92a..000000000 Binary files a/test/integration/branchSuggestions/expected/repo/.git_keep/index and /dev/null differ diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/HEAD b/test/integration/branchSuggestions/expected/repo/.git_keep/logs/HEAD deleted file mode 100644 index 4deacbbf3..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/HEAD +++ /dev/null @@ -1,8 +0,0 @@ -0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 commit (initial): file0 -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 checkout: moving from master to new-branch -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 checkout: moving from new-branch to new-branch-2 -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 checkout: moving from new-branch-2 to new-branch-3 -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 checkout: moving from new-branch-3 to old-branch -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 checkout: moving from old-branch to old-branch-2 -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 checkout: moving from old-branch-2 to old-branch-3 -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675450 +1000 checkout: moving from old-branch-3 to new-branch-3 diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/master b/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/master deleted file mode 100644 index 8c6dabf38..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 commit (initial): file0 diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/new-branch b/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/new-branch deleted file mode 100644 index 530a272f0..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/new-branch +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 branch: Created from HEAD diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-2 b/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-2 deleted file mode 100644 index 530a272f0..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-2 +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 branch: Created from HEAD diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-3 b/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-3 deleted file mode 100644 index 530a272f0..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-3 +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 branch: Created from HEAD diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/old-branch b/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/old-branch deleted file mode 100644 index 530a272f0..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/old-branch +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 branch: Created from HEAD diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/old-branch-2 b/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/old-branch-2 deleted file mode 100644 index 530a272f0..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/old-branch-2 +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 branch: Created from HEAD diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/old-branch-3 b/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/old-branch-3 deleted file mode 100644 index 530a272f0..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/logs/refs/heads/old-branch-3 +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI 1617675445 +1000 branch: Created from HEAD diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 b/test/integration/branchSuggestions/expected/repo/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 deleted file mode 100644 index 79fcadf67..000000000 Binary files a/test/integration/branchSuggestions/expected/repo/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 and /dev/null differ diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da b/test/integration/branchSuggestions/expected/repo/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da deleted file mode 100644 index 06c9cb73d..000000000 Binary files a/test/integration/branchSuggestions/expected/repo/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da and /dev/null differ diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/objects/75/e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 b/test/integration/branchSuggestions/expected/repo/.git_keep/objects/75/e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 deleted file mode 100644 index 16270b9a6..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/objects/75/e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 +++ /dev/null @@ -1,2 +0,0 @@ -xA -0Fa9ĀU4)<=o魭D4vWc% >VTQ՚4}pYz{x >8UGgafsc2'7u+ \ No newline at end of file diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/master b/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/master deleted file mode 100644 index ae478b1c7..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/new-branch b/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/new-branch deleted file mode 100644 index ae478b1c7..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/new-branch +++ /dev/null @@ -1 +0,0 @@ -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/new-branch-2 b/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/new-branch-2 deleted file mode 100644 index ae478b1c7..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/new-branch-2 +++ /dev/null @@ -1 +0,0 @@ -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/new-branch-3 b/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/new-branch-3 deleted file mode 100644 index ae478b1c7..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/new-branch-3 +++ /dev/null @@ -1 +0,0 @@ -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/old-branch b/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/old-branch deleted file mode 100644 index ae478b1c7..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/old-branch +++ /dev/null @@ -1 +0,0 @@ -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/old-branch-2 b/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/old-branch-2 deleted file mode 100644 index ae478b1c7..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/old-branch-2 +++ /dev/null @@ -1 +0,0 @@ -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/old-branch-3 b/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/old-branch-3 deleted file mode 100644 index ae478b1c7..000000000 --- a/test/integration/branchSuggestions/expected/repo/.git_keep/refs/heads/old-branch-3 +++ /dev/null @@ -1 +0,0 @@ -75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 diff --git a/test/integration/branchSuggestions/expected/repo/file0 b/test/integration/branchSuggestions/expected/repo/file0 deleted file mode 100644 index 38143ad4a..000000000 --- a/test/integration/branchSuggestions/expected/repo/file0 +++ /dev/null @@ -1 +0,0 @@ -test0 diff --git a/test/integration/branchSuggestions/recording.json b/test/integration/branchSuggestions/recording.json deleted file mode 100644 index 207dbcb3f..000000000 --- a/test/integration/branchSuggestions/recording.json +++ /dev/null @@ -1 +0,0 @@ -{"KeyEvents":[{"Timestamp":639,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1752,"Mod":0,"Key":256,"Ch":99},{"Timestamp":2183,"Mod":0,"Key":256,"Ch":110},{"Timestamp":2271,"Mod":0,"Key":256,"Ch":101},{"Timestamp":2327,"Mod":0,"Key":256,"Ch":119},{"Timestamp":2599,"Mod":0,"Key":256,"Ch":45},{"Timestamp":3583,"Mod":0,"Key":9,"Ch":9},{"Timestamp":3880,"Mod":0,"Key":258,"Ch":0},{"Timestamp":4175,"Mod":0,"Key":13,"Ch":13},{"Timestamp":4815,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/branchSuggestions/setup.sh b/test/integration/branchSuggestions/setup.sh deleted file mode 100644 index d67fa9291..000000000 --- a/test/integration/branchSuggestions/setup.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -set -e - -cd $1 - -git init - -git config user.email "CI@example.com" -git config user.name "CI" - -echo test0 > file0 -git add . -git commit -am file0 - -git checkout -b new-branch -git checkout -b new-branch-2 -git checkout -b new-branch-3 -git checkout -b old-branch -git checkout -b old-branch-2 -git checkout -b old-branch-3 diff --git a/test/integration/branchSuggestions/test.json b/test/integration/branchSuggestions/test.json deleted file mode 100644 index fafad1962..000000000 --- a/test/integration/branchSuggestions/test.json +++ /dev/null @@ -1 +0,0 @@ -{ "description": "Checking out a branch with name suggestions", "speed": 100 } diff --git a/test/integration/commit/expected/repo/.git_keep/COMMIT_EDITMSG b/test/integration/commit/expected/repo/.git_keep/COMMIT_EDITMSG deleted file mode 100644 index 01f9a2aac..000000000 --- a/test/integration/commit/expected/repo/.git_keep/COMMIT_EDITMSG +++ /dev/null @@ -1 +0,0 @@ -commit diff --git a/test/integration/commit/expected/repo/.git_keep/index b/test/integration/commit/expected/repo/.git_keep/index deleted file mode 100644 index e0cf62690..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/index and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/logs/HEAD b/test/integration/commit/expected/repo/.git_keep/logs/HEAD deleted file mode 100644 index 66475b1c1..000000000 --- a/test/integration/commit/expected/repo/.git_keep/logs/HEAD +++ /dev/null @@ -1,5 +0,0 @@ -0000000000000000000000000000000000000000 3df3d8761bc0f0828596b11845aeac175b7b7393 CI 1617671339 +1000 commit (initial): myfile1 -3df3d8761bc0f0828596b11845aeac175b7b7393 a7d53cc21fd53100f955377be379423b0e386274 CI 1617671339 +1000 commit: myfile2 -a7d53cc21fd53100f955377be379423b0e386274 4ba4f1ed711a9081fab21bc222469aa5176a01f8 CI 1617671339 +1000 commit: myfile3 -4ba4f1ed711a9081fab21bc222469aa5176a01f8 1440bc6cc888a09dca2329d1060eec6de78d9d21 CI 1617671339 +1000 commit: myfile4 -1440bc6cc888a09dca2329d1060eec6de78d9d21 e7560e2cd4783a261ad32496cefed2d9f69a46e7 CI 1617671342 +1000 commit: commit diff --git a/test/integration/commit/expected/repo/.git_keep/logs/refs/heads/master b/test/integration/commit/expected/repo/.git_keep/logs/refs/heads/master deleted file mode 100644 index 66475b1c1..000000000 --- a/test/integration/commit/expected/repo/.git_keep/logs/refs/heads/master +++ /dev/null @@ -1,5 +0,0 @@ -0000000000000000000000000000000000000000 3df3d8761bc0f0828596b11845aeac175b7b7393 CI 1617671339 +1000 commit (initial): myfile1 -3df3d8761bc0f0828596b11845aeac175b7b7393 a7d53cc21fd53100f955377be379423b0e386274 CI 1617671339 +1000 commit: myfile2 -a7d53cc21fd53100f955377be379423b0e386274 4ba4f1ed711a9081fab21bc222469aa5176a01f8 CI 1617671339 +1000 commit: myfile3 -4ba4f1ed711a9081fab21bc222469aa5176a01f8 1440bc6cc888a09dca2329d1060eec6de78d9d21 CI 1617671339 +1000 commit: myfile4 -1440bc6cc888a09dca2329d1060eec6de78d9d21 e7560e2cd4783a261ad32496cefed2d9f69a46e7 CI 1617671342 +1000 commit: commit diff --git a/test/integration/commit/expected/repo/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 b/test/integration/commit/expected/repo/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 deleted file mode 100644 index 7f2ebf4ee..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52 and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/14/40bc6cc888a09dca2329d1060eec6de78d9d21 b/test/integration/commit/expected/repo/.git_keep/objects/14/40bc6cc888a09dca2329d1060eec6de78d9d21 deleted file mode 100644 index 0a95d28ba..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/14/40bc6cc888a09dca2329d1060eec6de78d9d21 and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/commit/expected/repo/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 deleted file mode 100644 index f74bf2335..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce b/test/integration/commit/expected/repo/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce deleted file mode 100644 index 0a734f981..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 b/test/integration/commit/expected/repo/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 deleted file mode 100644 index 31ae3f5ba..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591 and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/30/a1ca3481fdec3245b02aeacfb72ddfe2a433be b/test/integration/commit/expected/repo/.git_keep/objects/30/a1ca3481fdec3245b02aeacfb72ddfe2a433be deleted file mode 100644 index aca754d63..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/30/a1ca3481fdec3245b02aeacfb72ddfe2a433be and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/3d/f3d8761bc0f0828596b11845aeac175b7b7393 b/test/integration/commit/expected/repo/.git_keep/objects/3d/f3d8761bc0f0828596b11845aeac175b7b7393 deleted file mode 100644 index 9abdddbd3..000000000 --- a/test/integration/commit/expected/repo/.git_keep/objects/3d/f3d8761bc0f0828596b11845aeac175b7b7393 +++ /dev/null @@ -1,3 +0,0 @@ -xA -0@Q9iI -"BW=FL!R"~r*Jd ¬D "\S.0p~6fw LzLɝ})2r, \ No newline at end of file diff --git a/test/integration/commit/expected/repo/.git_keep/objects/4b/a4f1ed711a9081fab21bc222469aa5176a01f8 b/test/integration/commit/expected/repo/.git_keep/objects/4b/a4f1ed711a9081fab21bc222469aa5176a01f8 deleted file mode 100644 index b67f58f76..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/4b/a4f1ed711a9081fab21bc222469aa5176a01f8 and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/4f/346f1ad5ba2917da2109e2eaa2f2dfbb86f10f b/test/integration/commit/expected/repo/.git_keep/objects/4f/346f1ad5ba2917da2109e2eaa2f2dfbb86f10f deleted file mode 100644 index 953241815..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/4f/346f1ad5ba2917da2109e2eaa2f2dfbb86f10f and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/commit/expected/repo/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 deleted file mode 100644 index 285df3e5f..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 b/test/integration/commit/expected/repo/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 deleted file mode 100644 index 96d2e71a6..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416 and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/a7/d53cc21fd53100f955377be379423b0e386274 b/test/integration/commit/expected/repo/.git_keep/objects/a7/d53cc21fd53100f955377be379423b0e386274 deleted file mode 100644 index 77d08ca03..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/a7/d53cc21fd53100f955377be379423b0e386274 and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 b/test/integration/commit/expected/repo/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 deleted file mode 100644 index d39fa7d2f..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54 and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b b/test/integration/commit/expected/repo/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b deleted file mode 100644 index 9b771fc2f..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/objects/e7/560e2cd4783a261ad32496cefed2d9f69a46e7 b/test/integration/commit/expected/repo/.git_keep/objects/e7/560e2cd4783a261ad32496cefed2d9f69a46e7 deleted file mode 100644 index 9eb3492d1..000000000 Binary files a/test/integration/commit/expected/repo/.git_keep/objects/e7/560e2cd4783a261ad32496cefed2d9f69a46e7 and /dev/null differ diff --git a/test/integration/commit/expected/repo/.git_keep/refs/heads/master b/test/integration/commit/expected/repo/.git_keep/refs/heads/master deleted file mode 100644 index c641d5ee6..000000000 --- a/test/integration/commit/expected/repo/.git_keep/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -e7560e2cd4783a261ad32496cefed2d9f69a46e7 diff --git a/test/integration/commit/expected/repo/myfile1 b/test/integration/commit/expected/repo/myfile1 deleted file mode 100644 index a5bce3fd2..000000000 --- a/test/integration/commit/expected/repo/myfile1 +++ /dev/null @@ -1 +0,0 @@ -test1 diff --git a/test/integration/commit/expected/repo/myfile2 b/test/integration/commit/expected/repo/myfile2 deleted file mode 100644 index 180cf8328..000000000 --- a/test/integration/commit/expected/repo/myfile2 +++ /dev/null @@ -1 +0,0 @@ -test2 diff --git a/test/integration/commit/expected/repo/myfile3 b/test/integration/commit/expected/repo/myfile3 deleted file mode 100644 index df6b0d2bc..000000000 --- a/test/integration/commit/expected/repo/myfile3 +++ /dev/null @@ -1 +0,0 @@ -test3 diff --git a/test/integration/commit/expected/repo/myfile4 b/test/integration/commit/expected/repo/myfile4 deleted file mode 100644 index d234c5e05..000000000 --- a/test/integration/commit/expected/repo/myfile4 +++ /dev/null @@ -1 +0,0 @@ -test4 diff --git a/test/integration/commit/expected/repo/myfile5 b/test/integration/commit/expected/repo/myfile5 deleted file mode 100644 index 4f346f1ad..000000000 --- a/test/integration/commit/expected/repo/myfile5 +++ /dev/null @@ -1 +0,0 @@ -test5 diff --git a/test/integration/commit/recording.json b/test/integration/commit/recording.json deleted file mode 100644 index eb45c9fde..000000000 --- a/test/integration/commit/recording.json +++ /dev/null @@ -1 +0,0 @@ -{"KeyEvents":[{"Timestamp":527,"Mod":0,"Key":256,"Ch":32},{"Timestamp":830,"Mod":0,"Key":256,"Ch":99},{"Timestamp":1127,"Mod":0,"Key":256,"Ch":99},{"Timestamp":1190,"Mod":0,"Key":256,"Ch":111},{"Timestamp":1335,"Mod":0,"Key":256,"Ch":109},{"Timestamp":1447,"Mod":0,"Key":256,"Ch":109},{"Timestamp":1583,"Mod":0,"Key":256,"Ch":105},{"Timestamp":1606,"Mod":0,"Key":256,"Ch":116},{"Timestamp":1935,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2353,"Mod":0,"Key":27,"Ch":0}],"ResizeEvents":[{"Timestamp":0,"Width":127,"Height":35}]} diff --git a/test/integration/commit/setup.sh b/test/integration/commit/setup.sh deleted file mode 100644 index c6c6a9271..000000000 --- a/test/integration/commit/setup.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh - -set -e - -cd $1 - -git init - -git config user.email "CI@example.com" -git config user.name "CI" - -echo test1 > myfile1 -git add . -git commit -am "myfile1" -echo test2 > myfile2 -git add . -git commit -am "myfile2" -echo test3 > myfile3 -git add . -git commit -am "myfile3" -echo test4 > myfile4 -git add . -git commit -am "myfile4" -echo test5 > myfile5 diff --git a/test/integration/commit/test.json b/test/integration/commit/test.json deleted file mode 100644 index 60bfe9319..000000000 --- a/test/integration/commit/test.json +++ /dev/null @@ -1 +0,0 @@ -{ "description": "stage a file and commit the change", "speed": 15 } diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/COMMIT_EDITMSG b/test/integration/commitsNewBranch/expected/repo/.git_keep/COMMIT_EDITMSG deleted file mode 100644 index 6c493ff74..000000000 --- a/test/integration/commitsNewBranch/expected/repo/.git_keep/COMMIT_EDITMSG +++ /dev/null @@ -1 +0,0 @@ -file2 diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/HEAD b/test/integration/commitsNewBranch/expected/repo/.git_keep/HEAD deleted file mode 100644 index 78bc9f37b..000000000 --- a/test/integration/commitsNewBranch/expected/repo/.git_keep/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/lol diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/index b/test/integration/commitsNewBranch/expected/repo/.git_keep/index deleted file mode 100644 index 577a68b72..000000000 Binary files a/test/integration/commitsNewBranch/expected/repo/.git_keep/index and /dev/null differ diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/logs/HEAD b/test/integration/commitsNewBranch/expected/repo/.git_keep/logs/HEAD deleted file mode 100644 index 67065fbb9..000000000 --- a/test/integration/commitsNewBranch/expected/repo/.git_keep/logs/HEAD +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 9901fd9b7766be600bed07f55f1794a759527a98 CI 1617674232 +1000 commit (initial): file0 -9901fd9b7766be600bed07f55f1794a759527a98 0029f9bf66e346d47ede6a501abb5b82bee60096 CI 1617674232 +1000 commit: file1 -0029f9bf66e346d47ede6a501abb5b82bee60096 e1cb250774fb8606d33062518d0ae03831130249 CI 1617674232 +1000 commit: file2 -e1cb250774fb8606d33062518d0ae03831130249 0029f9bf66e346d47ede6a501abb5b82bee60096 CI 1617674249 +1000 checkout: moving from master to lol diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/logs/refs/heads/lol b/test/integration/commitsNewBranch/expected/repo/.git_keep/logs/refs/heads/lol deleted file mode 100644 index 1202f15d1..000000000 --- a/test/integration/commitsNewBranch/expected/repo/.git_keep/logs/refs/heads/lol +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 0029f9bf66e346d47ede6a501abb5b82bee60096 CI 1617674249 +1000 branch: Created from 0029f9bf66e346d47ede6a501abb5b82bee60096 diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/logs/refs/heads/master b/test/integration/commitsNewBranch/expected/repo/.git_keep/logs/refs/heads/master deleted file mode 100644 index 5c02b3b2c..000000000 --- a/test/integration/commitsNewBranch/expected/repo/.git_keep/logs/refs/heads/master +++ /dev/null @@ -1,3 +0,0 @@ -0000000000000000000000000000000000000000 9901fd9b7766be600bed07f55f1794a759527a98 CI 1617674232 +1000 commit (initial): file0 -9901fd9b7766be600bed07f55f1794a759527a98 0029f9bf66e346d47ede6a501abb5b82bee60096 CI 1617674232 +1000 commit: file1 -0029f9bf66e346d47ede6a501abb5b82bee60096 e1cb250774fb8606d33062518d0ae03831130249 CI 1617674232 +1000 commit: file2 diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/00/29f9bf66e346d47ede6a501abb5b82bee60096 b/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/00/29f9bf66e346d47ede6a501abb5b82bee60096 deleted file mode 100644 index e5731eb1f..000000000 Binary files a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/00/29f9bf66e346d47ede6a501abb5b82bee60096 and /dev/null differ diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 b/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 deleted file mode 100644 index f74bf2335..000000000 Binary files a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827 and /dev/null differ diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 b/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 deleted file mode 100644 index 79fcadf67..000000000 Binary files a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/1e/3e67b999db1576ad1ee08bf4f02bdf29e49442 and /dev/null differ diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da b/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da deleted file mode 100644 index 06c9cb73d..000000000 Binary files a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/38/143ad4a0fe2ab6ee53c2ef89a5d9e2bd9535da and /dev/null differ diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/99/01fd9b7766be600bed07f55f1794a759527a98 b/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/99/01fd9b7766be600bed07f55f1794a759527a98 deleted file mode 100644 index cd2e8264c..000000000 Binary files a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/99/01fd9b7766be600bed07f55f1794a759527a98 and /dev/null differ diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c b/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c deleted file mode 100644 index 0e95eb06d..000000000 Binary files a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/9e/88a70dc8d82dd2afbfd50176ef78e18823bc2c and /dev/null differ diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 b/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 deleted file mode 100644 index 285df3e5f..000000000 Binary files a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5 and /dev/null differ diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 b/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 deleted file mode 100644 index 2e9066287..000000000 --- a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/d0/76cc9cc09acaa2d36fbc7a95fd3e2306494641 +++ /dev/null @@ -1,2 +0,0 @@ -x+)JMU03c040031QHI5`ֶww.hT[H - yW5Ɨ(| ^-W(x9 \ No newline at end of file diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/e1/cb250774fb8606d33062518d0ae03831130249 b/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/e1/cb250774fb8606d33062518d0ae03831130249 deleted file mode 100644 index fc22897cc..000000000 Binary files a/test/integration/commitsNewBranch/expected/repo/.git_keep/objects/e1/cb250774fb8606d33062518d0ae03831130249 and /dev/null differ diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/refs/heads/lol b/test/integration/commitsNewBranch/expected/repo/.git_keep/refs/heads/lol deleted file mode 100644 index e92394760..000000000 --- a/test/integration/commitsNewBranch/expected/repo/.git_keep/refs/heads/lol +++ /dev/null @@ -1 +0,0 @@ -0029f9bf66e346d47ede6a501abb5b82bee60096 diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/refs/heads/master b/test/integration/commitsNewBranch/expected/repo/.git_keep/refs/heads/master deleted file mode 100644 index d5689ed85..000000000 --- a/test/integration/commitsNewBranch/expected/repo/.git_keep/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -e1cb250774fb8606d33062518d0ae03831130249 diff --git a/test/integration/commitsNewBranch/expected/repo/file0 b/test/integration/commitsNewBranch/expected/repo/file0 deleted file mode 100644 index 38143ad4a..000000000 --- a/test/integration/commitsNewBranch/expected/repo/file0 +++ /dev/null @@ -1 +0,0 @@ -test0 diff --git a/test/integration/commitsNewBranch/expected/repo/file1 b/test/integration/commitsNewBranch/expected/repo/file1 deleted file mode 100644 index a5bce3fd2..000000000 --- a/test/integration/commitsNewBranch/expected/repo/file1 +++ /dev/null @@ -1 +0,0 @@ -test1 diff --git a/test/integration/commitsNewBranch/recording.json b/test/integration/commitsNewBranch/recording.json deleted file mode 100644 index ca4d07a4a..000000000 --- a/test/integration/commitsNewBranch/recording.json +++ /dev/null @@ -1 +0,0 @@ -{"KeyEvents":[{"Timestamp":972,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1243,"Mod":0,"Key":259,"Ch":0},{"Timestamp":1812,"Mod":0,"Key":256,"Ch":120},{"Timestamp":2683,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3018,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3033,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3050,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3067,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3084,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3100,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3363,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3499,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3628,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3771,"Mod":0,"Key":258,"Ch":0},{"Timestamp":3908,"Mod":0,"Key":258,"Ch":0},{"Timestamp":4051,"Mod":0,"Key":258,"Ch":0},{"Timestamp":4259,"Mod":0,"Key":258,"Ch":0},{"Timestamp":4883,"Mod":0,"Key":258,"Ch":0},{"Timestamp":5124,"Mod":0,"Key":258,"Ch":0},{"Timestamp":5355,"Mod":0,"Key":258,"Ch":0},{"Timestamp":6083,"Mod":0,"Key":258,"Ch":0},{"Timestamp":6563,"Mod":0,"Key":258,"Ch":0},{"Timestamp":7210,"Mod":0,"Key":258,"Ch":0},{"Timestamp":9475,"Mod":0,"Key":258,"Ch":0},{"Timestamp":10395,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11019,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11346,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11587,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11771,"Mod":0,"Key":258,"Ch":0},{"Timestamp":11883,"Mod":0,"Key":258,"Ch":0},{"Timestamp":12003,"Mod":0,"Key":258,"Ch":0},{"Timestamp":12132,"Mod":0,"Key":258,"Ch":0},{"Timestamp":12268,"Mod":0,"Key":258,"Ch":0},{"Timestamp":12395,"Mod":0,"Key":258,"Ch":0},{"Timestamp":12539,"Mod":0,"Key":258,"Ch":0},{"Timestamp":12667,"Mod":0,"Key":258,"Ch":0},{"Timestamp":12804,"Mod":0,"Key":258,"Ch":0},{"Timestamp":12947,"Mod":0,"Key":258,"Ch":0},{"Timestamp":13075,"Mod":0,"Key":258,"Ch":0},{"Timestamp":13211,"Mod":0,"Key":258,"Ch":0},{"Timestamp":13347,"Mod":0,"Key":258,"Ch":0},{"Timestamp":13475,"Mod":0,"Key":258,"Ch":0},{"Timestamp":13620,"Mod":0,"Key":258,"Ch":0},{"Timestamp":13771,"Mod":0,"Key":258,"Ch":0},{"Timestamp":13883,"Mod":0,"Key":258,"Ch":0},{"Timestamp":14027,"Mod":0,"Key":258,"Ch":0},{"Timestamp":14405,"Mod":0,"Key":27,"Ch":0},{"Timestamp":15540,"Mod":0,"Key":258,"Ch":0},{"Timestamp":15995,"Mod":0,"Key":256,"Ch":110},{"Timestamp":17267,"Mod":0,"Key":256,"Ch":108},{"Timestamp":17396,"Mod":0,"Key":256,"Ch":111},{"Timestamp":17547,"Mod":0,"Key":256,"Ch":108},{"Timestamp":17675,"Mod":0,"Key":13,"Ch":13},{"Timestamp":20195,"Mod":0,"Key":256,"Ch":113}],"ResizeEvents":[{"Timestamp":0,"Width":272,"Height":74}]} \ No newline at end of file diff --git a/test/integration/commitsNewBranch/setup.sh b/test/integration/commitsNewBranch/setup.sh deleted file mode 100644 index 4e35cf543..000000000 --- a/test/integration/commitsNewBranch/setup.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -set -e - -cd $1 - -git init - -git config user.email "CI@example.com" -git config user.name "CI" - -echo test0 > file0 -git add . -git commit -am file0 - -echo test1 > file1 -git add . -git commit -am file1 - -echo test2 > file2 -git add . -git commit -am file2 diff --git a/test/integration/commitsNewBranch/test.json b/test/integration/commitsNewBranch/test.json deleted file mode 100644 index d760dcc6c..000000000 --- a/test/integration/commitsNewBranch/test.json +++ /dev/null @@ -1 +0,0 @@ -{ "description": "Reverting a commit. Note here that our snapshot test fails if the commit SHA is included in the message hence the renaming of the revert commit after creating it", "speed": 20 } diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/COMMIT_EDITMSG b/test/integration_new/branch/suggestions/expected/repo/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..8a744b4fe --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +my commit message diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/FETCH_HEAD b/test/integration_new/branch/suggestions/expected/repo/.git_keep/FETCH_HEAD similarity index 100% rename from test/integration/branchSuggestions/expected/repo/.git_keep/FETCH_HEAD rename to test/integration_new/branch/suggestions/expected/repo/.git_keep/FETCH_HEAD diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/HEAD b/test/integration_new/branch/suggestions/expected/repo/.git_keep/HEAD new file mode 100644 index 000000000..3b627c921 --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/branch-to-checkout diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/config b/test/integration_new/branch/suggestions/expected/repo/.git_keep/config similarity index 100% rename from test/integration/branchSuggestions/expected/repo/.git_keep/config rename to test/integration_new/branch/suggestions/expected/repo/.git_keep/config diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/description b/test/integration_new/branch/suggestions/expected/repo/.git_keep/description similarity index 100% rename from test/integration/branchSuggestions/expected/repo/.git_keep/description rename to test/integration_new/branch/suggestions/expected/repo/.git_keep/description diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/index b/test/integration_new/branch/suggestions/expected/repo/.git_keep/index new file mode 100644 index 000000000..65d675154 Binary files /dev/null and b/test/integration_new/branch/suggestions/expected/repo/.git_keep/index differ diff --git a/test/integration/branchSuggestions/expected/repo/.git_keep/info/exclude b/test/integration_new/branch/suggestions/expected/repo/.git_keep/info/exclude similarity index 100% rename from test/integration/branchSuggestions/expected/repo/.git_keep/info/exclude rename to test/integration_new/branch/suggestions/expected/repo/.git_keep/info/exclude diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/HEAD b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/HEAD new file mode 100644 index 000000000..e97c3aa0e --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/HEAD @@ -0,0 +1,8 @@ +0000000000000000000000000000000000000000 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 commit (initial): my commit message +1682dc1949e1937af44b5270fec5c1ac9256c6a1 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 checkout: moving from master to new-branch +1682dc1949e1937af44b5270fec5c1ac9256c6a1 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 checkout: moving from new-branch to new-branch-2 +1682dc1949e1937af44b5270fec5c1ac9256c6a1 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 checkout: moving from new-branch-2 to new-branch-3 +1682dc1949e1937af44b5270fec5c1ac9256c6a1 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 checkout: moving from new-branch-3 to branch-to-checkout +1682dc1949e1937af44b5270fec5c1ac9256c6a1 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 checkout: moving from branch-to-checkout to other-new-branch-2 +1682dc1949e1937af44b5270fec5c1ac9256c6a1 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 checkout: moving from other-new-branch-2 to other-new-branch-3 +1682dc1949e1937af44b5270fec5c1ac9256c6a1 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 checkout: moving from other-new-branch-3 to branch-to-checkout diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/branch-to-checkout b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/branch-to-checkout new file mode 100644 index 000000000..6b7fc9713 --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/branch-to-checkout @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 branch: Created from HEAD diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/master b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..7475970dc --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 commit (initial): my commit message diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/new-branch b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/new-branch new file mode 100644 index 000000000..6b7fc9713 --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/new-branch @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 branch: Created from HEAD diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-2 b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-2 new file mode 100644 index 000000000..6b7fc9713 --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-2 @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 branch: Created from HEAD diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-3 b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-3 new file mode 100644 index 000000000..6b7fc9713 --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/new-branch-3 @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 branch: Created from HEAD diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/other-new-branch-2 b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/other-new-branch-2 new file mode 100644 index 000000000..6b7fc9713 --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/other-new-branch-2 @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 branch: Created from HEAD diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/other-new-branch-3 b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/other-new-branch-3 new file mode 100644 index 000000000..6b7fc9713 --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/logs/refs/heads/other-new-branch-3 @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 1682dc1949e1937af44b5270fec5c1ac9256c6a1 CI 1659873850 +1000 branch: Created from HEAD diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/objects/16/82dc1949e1937af44b5270fec5c1ac9256c6a1 b/test/integration_new/branch/suggestions/expected/repo/.git_keep/objects/16/82dc1949e1937af44b5270fec5c1ac9256c6a1 new file mode 100644 index 000000000..45c339c12 Binary files /dev/null and b/test/integration_new/branch/suggestions/expected/repo/.git_keep/objects/16/82dc1949e1937af44b5270fec5c1ac9256c6a1 differ diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/test/integration_new/branch/suggestions/expected/repo/.git_keep/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 new file mode 100644 index 000000000..adf64119a Binary files /dev/null and b/test/integration_new/branch/suggestions/expected/repo/.git_keep/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/branch-to-checkout b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/branch-to-checkout new file mode 100644 index 000000000..23eeb4fdc --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/branch-to-checkout @@ -0,0 +1 @@ +1682dc1949e1937af44b5270fec5c1ac9256c6a1 diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/master b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/master new file mode 100644 index 000000000..23eeb4fdc --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/master @@ -0,0 +1 @@ +1682dc1949e1937af44b5270fec5c1ac9256c6a1 diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/new-branch b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/new-branch new file mode 100644 index 000000000..23eeb4fdc --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/new-branch @@ -0,0 +1 @@ +1682dc1949e1937af44b5270fec5c1ac9256c6a1 diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/new-branch-2 b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/new-branch-2 new file mode 100644 index 000000000..23eeb4fdc --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/new-branch-2 @@ -0,0 +1 @@ +1682dc1949e1937af44b5270fec5c1ac9256c6a1 diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/new-branch-3 b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/new-branch-3 new file mode 100644 index 000000000..23eeb4fdc --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/new-branch-3 @@ -0,0 +1 @@ +1682dc1949e1937af44b5270fec5c1ac9256c6a1 diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/other-new-branch-2 b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/other-new-branch-2 new file mode 100644 index 000000000..23eeb4fdc --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/other-new-branch-2 @@ -0,0 +1 @@ +1682dc1949e1937af44b5270fec5c1ac9256c6a1 diff --git a/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/other-new-branch-3 b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/other-new-branch-3 new file mode 100644 index 000000000..23eeb4fdc --- /dev/null +++ b/test/integration_new/branch/suggestions/expected/repo/.git_keep/refs/heads/other-new-branch-3 @@ -0,0 +1 @@ +1682dc1949e1937af44b5270fec5c1ac9256c6a1 diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/COMMIT_EDITMSG b/test/integration_new/commit/commit/expected/repo/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..8a744b4fe --- /dev/null +++ b/test/integration_new/commit/commit/expected/repo/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +my commit message diff --git a/test/integration/commit/expected/repo/.git_keep/FETCH_HEAD b/test/integration_new/commit/commit/expected/repo/.git_keep/FETCH_HEAD similarity index 100% rename from test/integration/commit/expected/repo/.git_keep/FETCH_HEAD rename to test/integration_new/commit/commit/expected/repo/.git_keep/FETCH_HEAD diff --git a/test/integration/commit/expected/repo/.git_keep/HEAD b/test/integration_new/commit/commit/expected/repo/.git_keep/HEAD similarity index 100% rename from test/integration/commit/expected/repo/.git_keep/HEAD rename to test/integration_new/commit/commit/expected/repo/.git_keep/HEAD diff --git a/test/integration/commit/expected/repo/.git_keep/config b/test/integration_new/commit/commit/expected/repo/.git_keep/config similarity index 100% rename from test/integration/commit/expected/repo/.git_keep/config rename to test/integration_new/commit/commit/expected/repo/.git_keep/config diff --git a/test/integration/commit/expected/repo/.git_keep/description b/test/integration_new/commit/commit/expected/repo/.git_keep/description similarity index 100% rename from test/integration/commit/expected/repo/.git_keep/description rename to test/integration_new/commit/commit/expected/repo/.git_keep/description diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/index b/test/integration_new/commit/commit/expected/repo/.git_keep/index new file mode 100644 index 000000000..31a81c209 Binary files /dev/null and b/test/integration_new/commit/commit/expected/repo/.git_keep/index differ diff --git a/test/integration/commit/expected/repo/.git_keep/info/exclude b/test/integration_new/commit/commit/expected/repo/.git_keep/info/exclude similarity index 100% rename from test/integration/commit/expected/repo/.git_keep/info/exclude rename to test/integration_new/commit/commit/expected/repo/.git_keep/info/exclude diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/logs/HEAD b/test/integration_new/commit/commit/expected/repo/.git_keep/logs/HEAD new file mode 100644 index 000000000..925bffbb2 --- /dev/null +++ b/test/integration_new/commit/commit/expected/repo/.git_keep/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 944b9ea58bef8f6352c3a081a1d0037125bcaabc CI 1660133266 +1000 commit (initial): my commit message diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/logs/refs/heads/master b/test/integration_new/commit/commit/expected/repo/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..925bffbb2 --- /dev/null +++ b/test/integration_new/commit/commit/expected/repo/.git_keep/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 944b9ea58bef8f6352c3a081a1d0037125bcaabc CI 1660133266 +1000 commit (initial): my commit message diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/objects/3a/e2df795236e3c84cb1faa242d3268838603515 b/test/integration_new/commit/commit/expected/repo/.git_keep/objects/3a/e2df795236e3c84cb1faa242d3268838603515 new file mode 100644 index 000000000..57198442f Binary files /dev/null and b/test/integration_new/commit/commit/expected/repo/.git_keep/objects/3a/e2df795236e3c84cb1faa242d3268838603515 differ diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/objects/94/4b9ea58bef8f6352c3a081a1d0037125bcaabc b/test/integration_new/commit/commit/expected/repo/.git_keep/objects/94/4b9ea58bef8f6352c3a081a1d0037125bcaabc new file mode 100644 index 000000000..edba03fb8 Binary files /dev/null and b/test/integration_new/commit/commit/expected/repo/.git_keep/objects/94/4b9ea58bef8f6352c3a081a1d0037125bcaabc differ diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/objects/97/04090f88911a4083ef7d5907e38b9f45e43b16 b/test/integration_new/commit/commit/expected/repo/.git_keep/objects/97/04090f88911a4083ef7d5907e38b9f45e43b16 new file mode 100644 index 000000000..c4b48a2f0 Binary files /dev/null and b/test/integration_new/commit/commit/expected/repo/.git_keep/objects/97/04090f88911a4083ef7d5907e38b9f45e43b16 differ diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/objects/ad/a5661567ddf0a64f589cad3cd0cffd7e79af99 b/test/integration_new/commit/commit/expected/repo/.git_keep/objects/ad/a5661567ddf0a64f589cad3cd0cffd7e79af99 new file mode 100644 index 000000000..98345f609 Binary files /dev/null and b/test/integration_new/commit/commit/expected/repo/.git_keep/objects/ad/a5661567ddf0a64f589cad3cd0cffd7e79af99 differ diff --git a/test/integration_new/commit/commit/expected/repo/.git_keep/refs/heads/master b/test/integration_new/commit/commit/expected/repo/.git_keep/refs/heads/master new file mode 100644 index 000000000..7b10e3bcb --- /dev/null +++ b/test/integration_new/commit/commit/expected/repo/.git_keep/refs/heads/master @@ -0,0 +1 @@ +944b9ea58bef8f6352c3a081a1d0037125bcaabc diff --git a/test/integration_new/commit/commit/expected/repo/myfile b/test/integration_new/commit/commit/expected/repo/myfile new file mode 100644 index 000000000..ada566156 --- /dev/null +++ b/test/integration_new/commit/commit/expected/repo/myfile @@ -0,0 +1 @@ +myfile content \ No newline at end of file diff --git a/test/integration_new/commit/commit/expected/repo/myfile2 b/test/integration_new/commit/commit/expected/repo/myfile2 new file mode 100644 index 000000000..9704090f8 --- /dev/null +++ b/test/integration_new/commit/commit/expected/repo/myfile2 @@ -0,0 +1 @@ +myfile2 content \ No newline at end of file diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/COMMIT_EDITMSG b/test/integration_new/commit/new_branch/expected/repo/.git_keep/COMMIT_EDITMSG new file mode 100644 index 000000000..68d1ef3ef --- /dev/null +++ b/test/integration_new/commit/new_branch/expected/repo/.git_keep/COMMIT_EDITMSG @@ -0,0 +1 @@ +commit 3 diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/FETCH_HEAD b/test/integration_new/commit/new_branch/expected/repo/.git_keep/FETCH_HEAD similarity index 100% rename from test/integration/commitsNewBranch/expected/repo/.git_keep/FETCH_HEAD rename to test/integration_new/commit/new_branch/expected/repo/.git_keep/FETCH_HEAD diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/HEAD b/test/integration_new/commit/new_branch/expected/repo/.git_keep/HEAD new file mode 100644 index 000000000..634a851e9 --- /dev/null +++ b/test/integration_new/commit/new_branch/expected/repo/.git_keep/HEAD @@ -0,0 +1 @@ +ref: refs/heads/my-branch-name diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/config b/test/integration_new/commit/new_branch/expected/repo/.git_keep/config similarity index 100% rename from test/integration/commitsNewBranch/expected/repo/.git_keep/config rename to test/integration_new/commit/new_branch/expected/repo/.git_keep/config diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/description b/test/integration_new/commit/new_branch/expected/repo/.git_keep/description similarity index 100% rename from test/integration/commitsNewBranch/expected/repo/.git_keep/description rename to test/integration_new/commit/new_branch/expected/repo/.git_keep/description diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/index b/test/integration_new/commit/new_branch/expected/repo/.git_keep/index new file mode 100644 index 000000000..65d675154 Binary files /dev/null and b/test/integration_new/commit/new_branch/expected/repo/.git_keep/index differ diff --git a/test/integration/commitsNewBranch/expected/repo/.git_keep/info/exclude b/test/integration_new/commit/new_branch/expected/repo/.git_keep/info/exclude similarity index 100% rename from test/integration/commitsNewBranch/expected/repo/.git_keep/info/exclude rename to test/integration_new/commit/new_branch/expected/repo/.git_keep/info/exclude diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/HEAD b/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/HEAD new file mode 100644 index 000000000..189a2b0bc --- /dev/null +++ b/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/HEAD @@ -0,0 +1,4 @@ +0000000000000000000000000000000000000000 4e72cd440eec154569568bff8d4c955052ae246c CI 1660125381 +1000 commit (initial): commit 1 +4e72cd440eec154569568bff8d4c955052ae246c 563414ba32c967cfbe21a17fe892d6118c1c58e8 CI 1660125381 +1000 commit: commit 2 +563414ba32c967cfbe21a17fe892d6118c1c58e8 0af36e404e6fec1c3a4d887e30622238e5ea0b2b CI 1660125381 +1000 commit: commit 3 +0af36e404e6fec1c3a4d887e30622238e5ea0b2b 563414ba32c967cfbe21a17fe892d6118c1c58e8 CI 1660125382 +1000 checkout: moving from master to my-branch-name diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/master b/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..0e17a4008 --- /dev/null +++ b/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 4e72cd440eec154569568bff8d4c955052ae246c CI 1660125381 +1000 commit (initial): commit 1 +4e72cd440eec154569568bff8d4c955052ae246c 563414ba32c967cfbe21a17fe892d6118c1c58e8 CI 1660125381 +1000 commit: commit 2 +563414ba32c967cfbe21a17fe892d6118c1c58e8 0af36e404e6fec1c3a4d887e30622238e5ea0b2b CI 1660125381 +1000 commit: commit 3 diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/my-branch-name b/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/my-branch-name new file mode 100644 index 000000000..6f401d926 --- /dev/null +++ b/test/integration_new/commit/new_branch/expected/repo/.git_keep/logs/refs/heads/my-branch-name @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 563414ba32c967cfbe21a17fe892d6118c1c58e8 CI 1660125382 +1000 branch: Created from 563414ba32c967cfbe21a17fe892d6118c1c58e8 diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/0a/f36e404e6fec1c3a4d887e30622238e5ea0b2b b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/0a/f36e404e6fec1c3a4d887e30622238e5ea0b2b new file mode 100644 index 000000000..eb9800f0a Binary files /dev/null and b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/0a/f36e404e6fec1c3a4d887e30622238e5ea0b2b differ diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 new file mode 100644 index 000000000..adf64119a Binary files /dev/null and b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/4e/72cd440eec154569568bff8d4c955052ae246c b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/4e/72cd440eec154569568bff8d4c955052ae246c new file mode 100644 index 000000000..40f7e1d72 Binary files /dev/null and b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/4e/72cd440eec154569568bff8d4c955052ae246c differ diff --git a/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/56/3414ba32c967cfbe21a17fe892d6118c1c58e8 b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/56/3414ba32c967cfbe21a17fe892d6118c1c58e8 new file mode 100644 index 000000000..1a610226f --- /dev/null +++ b/test/integration_new/commit/new_branch/expected/repo/.git_keep/objects/56/3414ba32c967cfbe21a17fe892d6118c1c58e8 @@ -0,0 +1,2 @@ +xA +1 @Q=E1-m&uw-ͭ=:,D22YU ))sFKYՁD62Ea,q,BʼnBymm*FcMu 1660123588 +1000 commit (initial): commit 01 +cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 2e2cd25ffdec58d32b5d549f8402bd054e22cc2a CI 1660123588 +1000 commit: commit 02 +2e2cd25ffdec58d32b5d549f8402bd054e22cc2a 90fda12ce101e7d0d4594a879e5bbd1be3c857a8 CI 1660123588 +1000 commit: commit 03 +90fda12ce101e7d0d4594a879e5bbd1be3c857a8 da71be1afbb03f46e91ab5de17d69f148bb009f3 CI 1660123588 +1000 commit: commit 04 +da71be1afbb03f46e91ab5de17d69f148bb009f3 8a3839811a7a9f4c678090c9def892d1e7ad7e54 CI 1660123589 +1000 commit: commit 05 +8a3839811a7a9f4c678090c9def892d1e7ad7e54 cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 CI 1660123589 +1000 rebase (start): checkout cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 +cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 2e2cd25ffdec58d32b5d549f8402bd054e22cc2a CI 1660123589 +1000 rebase: fast-forward +2e2cd25ffdec58d32b5d549f8402bd054e22cc2a b85535ebf12659044c33386376121d76756ceb59 CI 1660123590 +1000 rebase (continue) (fixup): # This is a combination of 2 commits. +b85535ebf12659044c33386376121d76756ceb59 aba3469fd6fc584a6af9c0073873005ffaaea56c CI 1660123590 +1000 rebase (continue) (squash): commit 02 +aba3469fd6fc584a6af9c0073873005ffaaea56c aba3469fd6fc584a6af9c0073873005ffaaea56c CI 1660123590 +1000 rebase (continue) (finish): returning to refs/heads/master diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/logs/refs/heads/master b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/logs/refs/heads/master new file mode 100644 index 000000000..c6c18ee5a --- /dev/null +++ b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/logs/refs/heads/master @@ -0,0 +1,6 @@ +0000000000000000000000000000000000000000 cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 CI 1660123588 +1000 commit (initial): commit 01 +cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 2e2cd25ffdec58d32b5d549f8402bd054e22cc2a CI 1660123588 +1000 commit: commit 02 +2e2cd25ffdec58d32b5d549f8402bd054e22cc2a 90fda12ce101e7d0d4594a879e5bbd1be3c857a8 CI 1660123588 +1000 commit: commit 03 +90fda12ce101e7d0d4594a879e5bbd1be3c857a8 da71be1afbb03f46e91ab5de17d69f148bb009f3 CI 1660123588 +1000 commit: commit 04 +da71be1afbb03f46e91ab5de17d69f148bb009f3 8a3839811a7a9f4c678090c9def892d1e7ad7e54 CI 1660123589 +1000 commit: commit 05 +8a3839811a7a9f4c678090c9def892d1e7ad7e54 aba3469fd6fc584a6af9c0073873005ffaaea56c CI 1660123590 +1000 rebase (continue) (finish): refs/heads/master onto cc9defb8ae9134f1a9a6c28a0006dc8c8cd78347 diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/06/47fe4b7302efbfb235b8f0681b592cc3389d36 b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/06/47fe4b7302efbfb235b8f0681b592cc3389d36 new file mode 100644 index 000000000..a8a2b586d Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/06/47fe4b7302efbfb235b8f0681b592cc3389d36 differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/2e/2cd25ffdec58d32b5d549f8402bd054e22cc2a b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/2e/2cd25ffdec58d32b5d549f8402bd054e22cc2a new file mode 100644 index 000000000..20504e122 --- /dev/null +++ b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/2e/2cd25ffdec58d32b5d549f8402bd054e22cc2a @@ -0,0 +1,3 @@ +xA +0P9$1U1LQ0,<}9 +lcTd 9YT\ИxW\u)Ěч2GqXFj"w;L3\nSO+`?Ќv'y|̱;u \ No newline at end of file diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/35/da65f29bc0b48aa80bd3a02cff623cf4355fd3 b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/35/da65f29bc0b48aa80bd3a02cff623cf4355fd3 new file mode 100644 index 000000000..350af2800 Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/35/da65f29bc0b48aa80bd3a02cff623cf4355fd3 differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/3b/f868a389d0073e715e848f0ee33d71064539ca b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/3b/f868a389d0073e715e848f0ee33d71064539ca new file mode 100644 index 000000000..07b07e91f Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/3b/f868a389d0073e715e848f0ee33d71064539ca differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/47/d78ad7a27fc7fe483389512ebf7ea34c5514bc b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/47/d78ad7a27fc7fe483389512ebf7ea34c5514bc new file mode 100644 index 000000000..c562d38cc Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/47/d78ad7a27fc7fe483389512ebf7ea34c5514bc differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/55/3197193920043fb04f3e39e825916990955204 b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/55/3197193920043fb04f3e39e825916990955204 new file mode 100644 index 000000000..ac90c394a Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/55/3197193920043fb04f3e39e825916990955204 differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/8a/3839811a7a9f4c678090c9def892d1e7ad7e54 b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/8a/3839811a7a9f4c678090c9def892d1e7ad7e54 new file mode 100644 index 000000000..f518dcc89 Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/8a/3839811a7a9f4c678090c9def892d1e7ad7e54 differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/90/fda12ce101e7d0d4594a879e5bbd1be3c857a8 b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/90/fda12ce101e7d0d4594a879e5bbd1be3c857a8 new file mode 100644 index 000000000..71b49be64 Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/90/fda12ce101e7d0d4594a879e5bbd1be3c857a8 differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/a0/2c4b36b68df7081152282cf1aabcab7b24e69b b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/a0/2c4b36b68df7081152282cf1aabcab7b24e69b new file mode 100644 index 000000000..85866acd8 Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/a0/2c4b36b68df7081152282cf1aabcab7b24e69b differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/ab/a3469fd6fc584a6af9c0073873005ffaaea56c b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/ab/a3469fd6fc584a6af9c0073873005ffaaea56c new file mode 100644 index 000000000..6f0bc9bd3 --- /dev/null +++ b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/ab/a3469fd6fc584a6af9c0073873005ffaaea56c @@ -0,0 +1,3 @@ +x} +0=).wzc- +Ɩ7Qf ,>8CTA^(: 7RaN4:Jn"eYa\8ˌi5Q }`T~ +:f?zϣ;v \ No newline at end of file diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/e2/1978e5aaff3752bdeeb635c1667ec59c5bbde1 b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/e2/1978e5aaff3752bdeeb635c1667ec59c5bbde1 new file mode 100644 index 000000000..37f59fe0f Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/e2/1978e5aaff3752bdeeb635c1667ec59c5bbde1 differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/e6/db1f58c2bb5ead41049a8ef3910360eead21e2 b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/e6/db1f58c2bb5ead41049a8ef3910360eead21e2 new file mode 100644 index 000000000..8bcfafeb6 Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/e6/db1f58c2bb5ead41049a8ef3910360eead21e2 differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/e9/380a3c752e4b7c7e754fc402ce52302795a95a b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/e9/380a3c752e4b7c7e754fc402ce52302795a95a new file mode 100644 index 000000000..a75ffaf35 Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/e9/380a3c752e4b7c7e754fc402ce52302795a95a differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/f2/c01a881661486f147e47f5be82914c5d0c0030 b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/f2/c01a881661486f147e47f5be82914c5d0c0030 new file mode 100644 index 000000000..7e30b2e35 Binary files /dev/null and b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/objects/f2/c01a881661486f147e47f5be82914c5d0c0030 differ diff --git a/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/refs/heads/master b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/refs/heads/master new file mode 100644 index 000000000..93319ec4d --- /dev/null +++ b/test/integration_new/interactive_rebase/one/expected/repo/.git_keep/refs/heads/master @@ -0,0 +1 @@ +aba3469fd6fc584a6af9c0073873005ffaaea56c diff --git a/test/integration_new/interactive_rebase/one/expected/repo/file01.txt b/test/integration_new/interactive_rebase/one/expected/repo/file01.txt new file mode 100644 index 000000000..47d78ad7a --- /dev/null +++ b/test/integration_new/interactive_rebase/one/expected/repo/file01.txt @@ -0,0 +1 @@ +file01 content \ No newline at end of file diff --git a/test/integration_new/interactive_rebase/one/expected/repo/file02.txt b/test/integration_new/interactive_rebase/one/expected/repo/file02.txt new file mode 100644 index 000000000..0647fe4b7 --- /dev/null +++ b/test/integration_new/interactive_rebase/one/expected/repo/file02.txt @@ -0,0 +1 @@ +file02 content \ No newline at end of file diff --git a/test/integration_new/interactive_rebase/one/expected/repo/file03.txt b/test/integration_new/interactive_rebase/one/expected/repo/file03.txt new file mode 100644 index 000000000..3bf868a38 --- /dev/null +++ b/test/integration_new/interactive_rebase/one/expected/repo/file03.txt @@ -0,0 +1 @@ +file03 content \ No newline at end of file diff --git a/test/integration_new/interactive_rebase/one/expected/repo/file05.txt b/test/integration_new/interactive_rebase/one/expected/repo/file05.txt new file mode 100644 index 000000000..c255cf4ef --- /dev/null +++ b/test/integration_new/interactive_rebase/one/expected/repo/file05.txt @@ -0,0 +1 @@ +file05 content \ No newline at end of file diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go index 03d3b3adc..0c76bfee7 100644 --- a/vendor/github.com/jesseduffield/gocui/gui.go +++ b/vendor/github.com/jesseduffield/gocui/gui.go @@ -108,6 +108,8 @@ const ( NORMAL PlayMode = iota RECORDING REPLAYING + // for the new form of integration tests + REPLAYING_NEW ) type Recording struct { @@ -116,8 +118,8 @@ type Recording struct { } type replayedEvents struct { - keys chan *TcellKeyEventWrapper - resizes chan *TcellResizeEventWrapper + Keys chan *TcellKeyEventWrapper + Resizes chan *TcellResizeEventWrapper } type RecordingConfig struct { @@ -216,10 +218,10 @@ func NewGui(mode OutputMode, supportOverlaps bool, playMode PlayMode, headless b KeyEvents: []*TcellKeyEventWrapper{}, ResizeEvents: []*TcellResizeEventWrapper{}, } - } else if playMode == REPLAYING { + } else if playMode == REPLAYING || playMode == REPLAYING_NEW { g.ReplayedEvents = replayedEvents{ - keys: make(chan *TcellKeyEventWrapper), - resizes: make(chan *TcellResizeEventWrapper), + Keys: make(chan *TcellKeyEventWrapper), + Resizes: make(chan *TcellResizeEventWrapper), } } @@ -1420,7 +1422,7 @@ func (g *Gui) replayRecording() { case <-ticker.C: timeWaited += 1 if timeWaited >= timeToWait { - g.ReplayedEvents.keys <- event + g.ReplayedEvents.Keys <- event break middle } case <-g.stop: @@ -1453,7 +1455,7 @@ func (g *Gui) replayRecording() { case <-ticker.C: timeWaited += 1 if timeWaited >= timeToWait { - g.ReplayedEvents.resizes <- event + g.ReplayedEvents.Resizes <- event break middle2 } case <-g.stop: diff --git a/vendor/github.com/jesseduffield/gocui/tcell_driver.go b/vendor/github.com/jesseduffield/gocui/tcell_driver.go index c5555e30d..81d30fe91 100644 --- a/vendor/github.com/jesseduffield/gocui/tcell_driver.go +++ b/vendor/github.com/jesseduffield/gocui/tcell_driver.go @@ -232,11 +232,11 @@ func (g *Gui) timeSinceStart() int64 { // pollEvent get tcell.Event and transform it into gocuiEvent func (g *Gui) pollEvent() GocuiEvent { var tev tcell.Event - if g.PlayMode == REPLAYING { + if g.PlayMode == REPLAYING || g.PlayMode == REPLAYING_NEW { select { - case ev := <-g.ReplayedEvents.keys: + case ev := <-g.ReplayedEvents.Keys: tev = (ev).toTcellEvent() - case ev := <-g.ReplayedEvents.resizes: + case ev := <-g.ReplayedEvents.Resizes: tev = (ev).toTcellEvent() } } else { diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index 8ec290176..95c7ef4b1 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -465,6 +465,14 @@ func (v *View) Cursor() (x, y int) { return v.cx, v.cy } +func (v *View) CursorX() int { + return v.cx +} + +func (v *View) CursorY() int { + return v.cy +} + // SetOrigin sets the origin position of the view's internal buffer, // so the buffer starts to be printed from this point, which means that // it is linked with the origin point of view. It can be used to @@ -1235,6 +1243,13 @@ func (v *View) SelectedLineIdx() int { return seletedLineIdx } +// expected to only be used in tests +func (v *View) SelectedLine() string { + line := v.lines[v.SelectedLineIdx()] + str := lineType(line).String() + return strings.Replace(str, "\x00", " ", -1) +} + func (v *View) SelectedPoint() (int, int) { cx, cy := v.Cursor() ox, oy := v.Origin()