mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-25 00:46:54 +02:00
add new integration test pattern
This commit is contained in:
.gitignoremain.gorecording.jsonsetup.shtest.jsonrecording.jsonsetup.shtest.jsonrecording.jsonsetup.shtest.json
pkg
test
integration
branchSuggestions
expected
repo
commit
expected
repo
commitsNewBranch
expected
repo
integration_new
branch
suggestions
expected
repo
.git_keep
commit
commit
expected
new_branch
expected
repo
.git_keep
runner
runner_new
vendor/github.com/jesseduffield/gocui
8
.gitignore
vendored
8
.gitignore
vendored
@ -33,11 +33,19 @@ lazygit.exe
|
|||||||
!.gitmodules_keep
|
!.gitmodules_keep
|
||||||
|
|
||||||
test/git_server/data
|
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/*/actual/
|
||||||
test/integration/*/used_config/
|
test/integration/*/used_config/
|
||||||
# these sample hooks waste too much space
|
# these sample hooks waste too much space
|
||||||
test/integration/*/expected/**/hooks/
|
test/integration/*/expected/**/hooks/
|
||||||
test/integration/*/expected_remote/**/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
|
oryxBuildBinary
|
||||||
__debug_bin
|
__debug_bin
|
5
main.go
5
main.go
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/jesseduffield/lazygit/pkg/config"
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
"github.com/jesseduffield/lazygit/pkg/env"
|
"github.com/jesseduffield/lazygit/pkg/env"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration"
|
||||||
"github.com/jesseduffield/lazygit/pkg/logs"
|
"github.com/jesseduffield/lazygit/pkg/logs"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
yaml "github.com/jesseduffield/yaml"
|
yaml "github.com/jesseduffield/yaml"
|
||||||
@ -150,6 +151,10 @@ func main() {
|
|||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if test, ok := integration.CurrentIntegrationTest(); ok {
|
||||||
|
test.SetupConfig(appConfig)
|
||||||
|
}
|
||||||
|
|
||||||
common, err := app.NewCommon(appConfig)
|
common, err := app.NewCommon(appConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
88
pkg/gui/assert.go
Normal file
88
pkg/gui/assert.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AssertImpl struct {
|
||||||
|
gui *Gui
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.Assert = &AssertImpl{}
|
||||||
|
|
||||||
|
func (self *AssertImpl) WorkingTreeFileCount(expectedCount int) {
|
||||||
|
self.assertWithRetries(func() (bool, string) {
|
||||||
|
actualCount := len(self.gui.State.Model.Files)
|
||||||
|
|
||||||
|
return actualCount == expectedCount, fmt.Sprintf(
|
||||||
|
"Expected %d changed working tree files, but got %d",
|
||||||
|
expectedCount, actualCount,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AssertImpl) CommitCount(expectedCount int) {
|
||||||
|
self.assertWithRetries(func() (bool, string) {
|
||||||
|
actualCount := len(self.gui.State.Model.Commits)
|
||||||
|
|
||||||
|
return actualCount == expectedCount, fmt.Sprintf(
|
||||||
|
"Expected %d commits present, but got %d",
|
||||||
|
expectedCount, actualCount,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AssertImpl) HeadCommitMessage(expectedMessage string) {
|
||||||
|
self.assertWithRetries(func() (bool, string) {
|
||||||
|
if len(self.gui.State.Model.Commits) == 0 {
|
||||||
|
return false, "Expected at least one commit to be present"
|
||||||
|
}
|
||||||
|
|
||||||
|
headCommit := self.gui.State.Model.Commits[0]
|
||||||
|
if headCommit.Name != expectedMessage {
|
||||||
|
return false, fmt.Sprintf(
|
||||||
|
"Expected commit message to be '%s', but got '%s'",
|
||||||
|
expectedMessage, headCommit.Name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AssertImpl) CurrentViewName(expectedViewName string) {
|
||||||
|
self.assertWithRetries(func() (bool, string) {
|
||||||
|
actual := self.gui.currentViewName()
|
||||||
|
return actual == expectedViewName, fmt.Sprintf("Expected current view name to be '%s', but got '%s'", expectedViewName, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AssertImpl) CurrentBranchName(expectedViewName string) {
|
||||||
|
self.assertWithRetries(func() (bool, string) {
|
||||||
|
actual := self.gui.helpers.Refs.GetCheckedOutRef().Name
|
||||||
|
return actual == expectedViewName, fmt.Sprintf("Expected current branch name to be '%s', but got '%s'", expectedViewName, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *AssertImpl) assertWithRetries(test func() (bool, string)) {
|
||||||
|
waitTimes := []int{0, 100, 200, 400, 800, 1600}
|
||||||
|
|
||||||
|
var message string
|
||||||
|
for _, waitTime := range waitTimes {
|
||||||
|
time.Sleep(time.Duration(waitTime) * time.Millisecond)
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
ok, message = test()
|
||||||
|
if ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.gui.g.Close()
|
||||||
|
// need to give the gui time to close
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
panic(message)
|
||||||
|
}
|
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands"
|
"github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration"
|
||||||
"github.com/jesseduffield/lazygit/pkg/tasks"
|
"github.com/jesseduffield/lazygit/pkg/tasks"
|
||||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||||
@ -418,12 +419,14 @@ var RuneReplacements = map[rune]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) initGocui(headless bool) (*gocui.Gui, error) {
|
func (gui *Gui) initGocui(headless bool) (*gocui.Gui, error) {
|
||||||
recordEvents := recordingEvents()
|
recordEvents := integration.RecordingEvents()
|
||||||
playMode := gocui.NORMAL
|
playMode := gocui.NORMAL
|
||||||
if recordEvents {
|
if recordEvents {
|
||||||
playMode = gocui.RECORDING
|
playMode = gocui.RECORDING
|
||||||
} else if replaying() {
|
} else if integration.Replaying() {
|
||||||
playMode = gocui.REPLAYING
|
playMode = gocui.REPLAYING
|
||||||
|
} else if integration.IntegrationTestName() != "" {
|
||||||
|
playMode = gocui.REPLAYING_NEW
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, playMode, headless, RuneReplacements)
|
g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, playMode, headless, RuneReplacements)
|
||||||
@ -475,7 +478,7 @@ func (gui *Gui) viewTabMap() map[string][]context.TabView {
|
|||||||
|
|
||||||
// Run: setup the gui with keybindings and start the mainloop
|
// Run: setup the gui with keybindings and start the mainloop
|
||||||
func (gui *Gui) Run(startArgs types.StartArgs) error {
|
func (gui *Gui) Run(startArgs types.StartArgs) error {
|
||||||
g, err := gui.initGocui(headless())
|
g, err := gui.initGocui(integration.Headless())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -490,23 +493,7 @@ func (gui *Gui) Run(startArgs types.StartArgs) error {
|
|||||||
})
|
})
|
||||||
deadlock.Opts.Disable = !gui.Debug
|
deadlock.Opts.Disable = !gui.Debug
|
||||||
|
|
||||||
if replaying() {
|
gui.handleTestMode()
|
||||||
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.g.OnSearchEscape = gui.onSearchEscape
|
gui.g.OnSearchEscape = gui.onSearchEscape
|
||||||
if err := gui.Config.ReloadUserConfig(); err != nil {
|
if err := gui.Config.ReloadUserConfig(); err != nil {
|
||||||
@ -593,7 +580,7 @@ func (gui *Gui) RunAndHandleError(startArgs types.StartArgs) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gui.saveRecording(gui.g.Recording); err != nil {
|
if err := integration.SaveRecording(gui.g.Recording); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -627,7 +614,7 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
|
|||||||
gui.Mutexes.SubprocessMutex.Lock()
|
gui.Mutexes.SubprocessMutex.Lock()
|
||||||
defer gui.Mutexes.SubprocessMutex.Unlock()
|
defer gui.Mutexes.SubprocessMutex.Unlock()
|
||||||
|
|
||||||
if replaying() {
|
if integration.Replaying() {
|
||||||
// we do not yet support running subprocesses within integration tests. So if
|
// 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
|
// we're replaying an integration test and we're inside this method, something
|
||||||
// has gone wrong, so we should fail
|
// has gone wrong, so we should fail
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
package gui
|
package gui
|
||||||
|
|
||||||
|
// this is the new way of running tests. See pkg/integration/integration_tests/commit.go
|
||||||
|
// for an example
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -14,60 +17,37 @@ import (
|
|||||||
|
|
||||||
"github.com/creack/pty"
|
"github.com/creack/pty"
|
||||||
"github.com/jesseduffield/lazygit/pkg/integration"
|
"github.com/jesseduffield/lazygit/pkg/integration"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
|
||||||
// for CI and for running locally in the background to ensure you haven't broken
|
|
||||||
// anything while making changes. If you want to visually see what's happening when a test is run,
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
// To update a snapshot for an integration test, pass UPDATE_SNAPSHOTS=true
|
|
||||||
// UPDATE_SNAPSHOTS=true go test pkg/gui/gui_test.go -run /commit
|
|
||||||
//
|
|
||||||
// integration tests are run in test/integration/<test_name>/actual and the final test does
|
|
||||||
// not clean up that directory so you can cd into it to see for yourself what
|
|
||||||
// happened when a test fails.
|
|
||||||
//
|
|
||||||
// To override speed, pass e.g. `SPEED=1` as an env var. Otherwise we start each test
|
|
||||||
// at a high speed and then drop down to lower speeds upon each failure until finally
|
|
||||||
// trying at the original playback speed (speed 1). A speed of 2 represents twice the
|
|
||||||
// original playback speed. Speed may be a decimal.
|
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
func Test(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("Skipping integration tests in short mode")
|
t.Skip("Skipping integration tests in short mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
mode := integration.GetModeFromEnv()
|
mode := integration.GetModeFromEnv()
|
||||||
speedEnv := os.Getenv("SPEED")
|
|
||||||
includeSkipped := os.Getenv("INCLUDE_SKIPPED") != ""
|
includeSkipped := os.Getenv("INCLUDE_SKIPPED") != ""
|
||||||
|
|
||||||
parallelTotal := tryConvert(os.Getenv("PARALLEL_TOTAL"), 1)
|
parallelTotal := tryConvert(os.Getenv("PARALLEL_TOTAL"), 1)
|
||||||
parallelIndex := tryConvert(os.Getenv("PARALLEL_INDEX"), 0)
|
parallelIndex := tryConvert(os.Getenv("PARALLEL_INDEX"), 0)
|
||||||
testNumber := 0
|
testNumber := 0
|
||||||
|
|
||||||
err := integration.RunTests(
|
err := integration.RunTestsNew(
|
||||||
t.Logf,
|
t.Logf,
|
||||||
runCmdHeadless,
|
runCmdHeadless,
|
||||||
func(test *integration.Test, f func(*testing.T) error) {
|
func(test types.Test, f func(*testing.T) error) {
|
||||||
defer func() { testNumber += 1 }()
|
defer func() { testNumber += 1 }()
|
||||||
if testNumber%parallelTotal != parallelIndex {
|
if testNumber%parallelTotal != parallelIndex {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run(test.Name, func(t *testing.T) {
|
t.Run(test.Name(), func(t *testing.T) {
|
||||||
err := f(t)
|
err := f(t)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
mode,
|
mode,
|
||||||
speedEnv,
|
|
||||||
func(t *testing.T, expected string, actual string, prefix string) {
|
func(t *testing.T, expected string, actual string, prefix string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
assert.Equal(t, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual))
|
assert.Equal(t, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual))
|
||||||
|
93
pkg/gui/input.go
Normal file
93
pkg/gui/input.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InputImpl struct {
|
||||||
|
g *gocui.Gui
|
||||||
|
keys config.KeybindingConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.Input = &InputImpl{}
|
||||||
|
|
||||||
|
func (self *InputImpl) PushKeys(keyStrs ...string) {
|
||||||
|
for _, keyStr := range keyStrs {
|
||||||
|
self.pushKey(keyStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) pushKey(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.g.ReplayedEvents.Keys <- gocui.NewTcellKeyEventWrapper(
|
||||||
|
tcell.NewEventKey(tcellKey, r, tcell.ModNone),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) SwitchToStatusWindow() {
|
||||||
|
self.pushKey(self.keys.Universal.JumpToBlock[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) SwitchToFilesWindow() {
|
||||||
|
self.pushKey(self.keys.Universal.JumpToBlock[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) SwitchToBranchesWindow() {
|
||||||
|
self.pushKey(self.keys.Universal.JumpToBlock[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) SwitchToCommitsWindow() {
|
||||||
|
self.pushKey(self.keys.Universal.JumpToBlock[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) SwitchToStashWindow() {
|
||||||
|
self.pushKey(self.keys.Universal.JumpToBlock[4])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) Type(content string) {
|
||||||
|
for _, char := range content {
|
||||||
|
self.pushKey(string(char))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) Confirm() {
|
||||||
|
self.pushKey(self.keys.Universal.Confirm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) Cancel() {
|
||||||
|
self.pushKey(self.keys.Universal.Return)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) Select() {
|
||||||
|
self.pushKey(self.keys.Universal.Select)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) NextItem() {
|
||||||
|
self.pushKey(self.keys.Universal.NextItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) PreviousItem() {
|
||||||
|
self.pushKey(self.keys.Universal.PrevItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InputImpl) Wait(milliseconds int) {
|
||||||
|
time.Sleep(time.Duration(milliseconds) * time.Millisecond)
|
||||||
|
}
|
76
pkg/gui/old_gui_test.go
Normal file
76
pkg/gui/old_gui_test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecated: this is the old way of running tests. See pkg/gui/gui_test.go for the new way.
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// for CI and for running locally in the background to ensure you haven't broken
|
||||||
|
// anything while making changes. If you want to visually see what's happening when a test is run,
|
||||||
|
// 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/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/old_gui_test.go -run /commit
|
||||||
|
//
|
||||||
|
// integration tests are run in test/integration/<test_name>/actual and the final test does
|
||||||
|
// not clean up that directory so you can cd into it to see for yourself what
|
||||||
|
// happened when a test fails.
|
||||||
|
//
|
||||||
|
// To override speed, pass e.g. `SPEED=1` as an env var. Otherwise we start each test
|
||||||
|
// at a high speed and then drop down to lower speeds upon each failure until finally
|
||||||
|
// trying at the original playback speed (speed 1). A speed of 2 represents twice the
|
||||||
|
// original playback speed. Speed may be a decimal.
|
||||||
|
|
||||||
|
func TestOld(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("Skipping integration tests in short mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := integration.GetModeFromEnv()
|
||||||
|
speedEnv := os.Getenv("SPEED")
|
||||||
|
includeSkipped := os.Getenv("INCLUDE_SKIPPED") != ""
|
||||||
|
|
||||||
|
parallelTotal := tryConvert(os.Getenv("PARALLEL_TOTAL"), 1)
|
||||||
|
parallelIndex := tryConvert(os.Getenv("PARALLEL_INDEX"), 0)
|
||||||
|
testNumber := 0
|
||||||
|
|
||||||
|
err := integration.RunTests(
|
||||||
|
t.Logf,
|
||||||
|
runCmdHeadless,
|
||||||
|
func(test *integration.Test, f func(*testing.T) error) {
|
||||||
|
defer func() { testNumber += 1 }()
|
||||||
|
if testNumber%parallelTotal != parallelIndex {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
|
err := f(t)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
mode,
|
||||||
|
speedEnv,
|
||||||
|
func(t *testing.T, expected string, actual string, prefix string) {
|
||||||
|
t.Helper()
|
||||||
|
assert.Equal(t, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual))
|
||||||
|
},
|
||||||
|
includeSkipped,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
63
pkg/gui/test_mode.go
Normal file
63
pkg/gui/test_mode.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (gui *Gui) handleTestMode() {
|
||||||
|
if integration.PlayingIntegrationTest() {
|
||||||
|
test, ok := integration.CurrentIntegrationTest()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("test %s not found", integration.IntegrationTestName()))
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
|
||||||
|
test.Run(
|
||||||
|
&integration.ShellImpl{},
|
||||||
|
&InputImpl{g: gui.g, keys: gui.Config.GetUserConfig().Keybinding},
|
||||||
|
&AssertImpl{gui: gui},
|
||||||
|
gui.c.UserConfig.Keybinding,
|
||||||
|
)
|
||||||
|
|
||||||
|
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 integration.Replaying() {
|
||||||
|
gui.g.RecordingConfig = gocui.RecordingConfig{
|
||||||
|
Speed: integration.GetRecordingSpeed(),
|
||||||
|
Leeway: 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
gui.g.Recording, err = integration.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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
46
pkg/integration/env.go
Normal file
46
pkg/integration/env.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/generics/slices"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Headless() bool {
|
||||||
|
return os.Getenv("HEADLESS") != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// NEW integration test format stuff
|
||||||
|
|
||||||
|
func IntegrationTestName() string {
|
||||||
|
return os.Getenv("LAZYGIT_TEST_NAME")
|
||||||
|
}
|
||||||
|
|
||||||
|
func CurrentIntegrationTest() (types.Test, bool) {
|
||||||
|
if !PlayingIntegrationTest() {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return slices.Find(Tests, func(test types.Test) bool {
|
||||||
|
return test.Name() == IntegrationTestName()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func PlayingIntegrationTest() bool {
|
||||||
|
return IntegrationTestName() != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
@ -1,72 +1,29 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jesseduffield/generics/slices"
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
"github.com/jesseduffield/lazygit/pkg/integration/integration_tests"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 {
|
// re-exporting this so that clients only need to import one package
|
||||||
Name string `json:"name"`
|
var Tests = integration_tests.Tests
|
||||||
Speed float64 `json:"speed"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
ExtraCmdArgs string `json:"extraCmdArgs"`
|
|
||||||
Skip bool `json:"skip"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mode int
|
func RunTestsNew(
|
||||||
|
|
||||||
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, update, 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{}),
|
logf func(format string, formatArgs ...interface{}),
|
||||||
runCmd func(cmd *exec.Cmd) error,
|
runCmd func(cmd *exec.Cmd) error,
|
||||||
fnWrapper func(test *Test, f func(*testing.T) error),
|
fnWrapper func(test types.Test, f func(*testing.T) error),
|
||||||
mode Mode,
|
mode Mode,
|
||||||
speedEnv string,
|
|
||||||
onFail func(t *testing.T, expected string, actual string, prefix string),
|
onFail func(t *testing.T, expected string, actual string, prefix string),
|
||||||
includeSkipped bool,
|
includeSkipped bool,
|
||||||
) error {
|
) error {
|
||||||
@ -76,7 +33,7 @@ func RunTests(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
testDir := filepath.Join(rootDir, "test", "integration")
|
testDir := filepath.Join(rootDir, "test", "integration_new")
|
||||||
|
|
||||||
osCommand := oscommands.NewDummyOSCommand()
|
osCommand := oscommands.NewDummyOSCommand()
|
||||||
err = osCommand.Cmd.New("go build -o " + tempLazygitPath()).Run()
|
err = osCommand.Cmd.New("go build -o " + tempLazygitPath()).Run()
|
||||||
@ -84,43 +41,33 @@ func RunTests(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tests, err := LoadTests(testDir)
|
for _, test := range Tests {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
test := test
|
test := test
|
||||||
|
|
||||||
fnWrapper(test, func(t *testing.T) error { //nolint: thelper
|
fnWrapper(test, func(t *testing.T) error { //nolint: thelper
|
||||||
if test.Skip && !includeSkipped {
|
if test.Skip() && !includeSkipped {
|
||||||
logf("skipping test: %s", test.Name)
|
logf("skipping test: %s", test.Name())
|
||||||
return nil
|
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")
|
actualDir := filepath.Join(testPath, "actual")
|
||||||
expectedDir := filepath.Join(testPath, "expected")
|
expectedDir := filepath.Join(testPath, "expected")
|
||||||
actualRepoDir := filepath.Join(actualDir, "repo")
|
actualRepoDir := filepath.Join(actualDir, "repo")
|
||||||
logf("path: %s", testPath)
|
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)
|
findOrCreateDir(testPath)
|
||||||
prepareIntegrationTestDir(actualDir)
|
prepareIntegrationTestDir(actualDir)
|
||||||
findOrCreateDir(actualRepoDir)
|
findOrCreateDir(actualRepoDir)
|
||||||
err := createFixture(testPath, actualRepoDir)
|
err := createFixtureNew(test, actualRepoDir, rootDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
configDir := filepath.Join(testPath, "used_config")
|
configDir := filepath.Join(testPath, "used_config")
|
||||||
|
|
||||||
cmd, err := getLazygitCommand(testPath, rootDir, mode, speed, test.ExtraCmdArgs)
|
cmd, err := getLazygitCommandNew(test, testPath, rootDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -130,7 +77,7 @@ func RunTests(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode == UPDATE_SNAPSHOT || mode == RECORD {
|
if mode == UPDATE_SNAPSHOT {
|
||||||
// create/update snapshot
|
// create/update snapshot
|
||||||
err = oscommands.CopyDir(actualDir, expectedDir)
|
err = oscommands.CopyDir(actualDir, expectedDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -153,7 +100,6 @@ func RunTests(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
success := true
|
|
||||||
for _, f := range expectedFiles {
|
for _, f := range expectedFiles {
|
||||||
if !f.IsDir() {
|
if !f.IsDir() {
|
||||||
return errors.New("unexpected file (as opposed to directory) in integration test 'expected' directory")
|
return errors.New("unexpected file (as opposed to directory) in integration test 'expected' directory")
|
||||||
@ -169,12 +115,6 @@ func RunTests(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if expectedRepo != actualRepo {
|
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
|
// get the log file and print it
|
||||||
bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log"))
|
bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -185,13 +125,9 @@ func RunTests(
|
|||||||
onFail(t, expectedRepo, actualRepo, f.Name())
|
onFail(t, expectedRepo, actualRepo, f.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if success {
|
logf("test passed: %s", test.Name())
|
||||||
logf("%s: success at speed %f\n", test.Name, speed)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -200,344 +136,35 @@ func RunTests(
|
|||||||
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 createFixtureNew(test types.Test, actualDir string, rootDir string) error {
|
||||||
func validateSameRepos(expectedDir string, actualDir string) error {
|
if err := os.Chdir(actualDir); err != nil {
|
||||||
// iterate through each repo in the expected dir and compare to the corresponding repo in the actual dir
|
panic(err)
|
||||||
expectedFiles, err := ioutil.ReadDir(expectedDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var actualFiles []os.FileInfo
|
shell := &ShellImpl{}
|
||||||
actualFiles, err = ioutil.ReadDir(actualDir)
|
shell.RunCommand("git init")
|
||||||
if err != nil {
|
shell.RunCommand(`git config user.email "CI@example.com"`)
|
||||||
return err
|
shell.RunCommand(`git config user.name "CI"`)
|
||||||
}
|
|
||||||
|
|
||||||
expectedFileNames := slices.Map(expectedFiles, getFileName)
|
test.SetupRepo(shell)
|
||||||
actualFileNames := slices.Map(actualFiles, getFileName)
|
|
||||||
if !slices.Equal(expectedFileNames, actualFileNames) {
|
// changing directory back to rootDir after the setup is done
|
||||||
return fmt.Errorf("expected and actual repo dirs do not match: expected: %s, actual: %s", expectedFileNames, actualFileNames)
|
if err := os.Chdir(rootDir); err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFileName(f os.FileInfo) string {
|
func getLazygitCommandNew(test types.Test, testPath string, rootDir string) (*exec.Cmd, error) {
|
||||||
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) ([]*Test, error) {
|
|
||||||
paths, err := filepath.Glob(filepath.Join(testDir, "/*/test.json"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, 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)
|
|
||||||
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()
|
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")
|
templateConfigDir := filepath.Join(rootDir, "test", "default_test_config")
|
||||||
actualRepoDir := filepath.Join(testPath, "actual", "repo")
|
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")
|
configDir := filepath.Join(testPath, "used_config")
|
||||||
|
|
||||||
err = os.RemoveAll(configDir)
|
err := os.RemoveAll(configDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -546,17 +173,11 @@ func getLazygitCommand(testPath string, rootDir string, mode Mode, speed float64
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s %s", tempLazygitPath(), configDir, actualRepoDir, extraCmdArgs)
|
cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s %s", tempLazygitPath(), configDir, actualRepoDir, test.ExtraCmdArgs())
|
||||||
|
|
||||||
cmdObj := osCommand.Cmd.New(cmdStr)
|
cmdObj := osCommand.Cmd.New(cmdStr)
|
||||||
cmdObj.AddEnvVars(fmt.Sprintf("SPEED=%f", speed))
|
|
||||||
|
|
||||||
switch mode {
|
cmdObj.AddEnvVars(fmt.Sprintf("LAZYGIT_TEST_NAME=%s", test.Name()))
|
||||||
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
|
return cmdObj.GetCmd(), nil
|
||||||
}
|
}
|
||||||
|
564
pkg/integration/integration_old.go
Normal file
564
pkg/integration/integration_old.go
Normal file
@ -0,0 +1,564 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This package is for running our integration test suite. See docs/Integration_Tests.md for more info.
|
||||||
|
|
||||||
|
// Deprecated: This file is part of the old way of doing things. See integration.go for the new way
|
||||||
|
|
||||||
|
type Test 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 *Test, 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) ([]*Test, error) {
|
||||||
|
paths, err := filepath.Glob(filepath.Join(testDir, "/*/test.json"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, 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)
|
||||||
|
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
|
||||||
|
}
|
40
pkg/integration/integration_tests/branch/suggestions.go
Normal file
40
pkg/integration/integration_tests/branch/suggestions.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package branch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Suggestions = types.NewTest(types.NewTestArgs{
|
||||||
|
Description: "Checking out a branch with name suggestions",
|
||||||
|
ExtraCmdArgs: "",
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell types.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 types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) {
|
||||||
|
input.SwitchToBranchesWindow()
|
||||||
|
|
||||||
|
input.PushKeys(keys.Branches.CheckoutBranchByName)
|
||||||
|
assert.CurrentViewName("confirmation")
|
||||||
|
|
||||||
|
input.Type("branch-to")
|
||||||
|
|
||||||
|
input.PushKeys(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")
|
||||||
|
},
|
||||||
|
})
|
32
pkg/integration/integration_tests/commit/commit.go
Normal file
32
pkg/integration/integration_tests/commit/commit.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package commit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Commit = types.NewTest(types.NewTestArgs{
|
||||||
|
Description: "Staging a couple files and committing",
|
||||||
|
ExtraCmdArgs: "",
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell types.Shell) {
|
||||||
|
shell.CreateFile("myfile", "myfile content")
|
||||||
|
shell.CreateFile("myfile2", "myfile2 content")
|
||||||
|
},
|
||||||
|
Run: func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) {
|
||||||
|
assert.CommitCount(0)
|
||||||
|
|
||||||
|
input.Select()
|
||||||
|
input.NextItem()
|
||||||
|
input.Select()
|
||||||
|
input.PushKeys(keys.Files.CommitChanges)
|
||||||
|
|
||||||
|
commitMessage := "my commit message"
|
||||||
|
input.Type(commitMessage)
|
||||||
|
input.Confirm()
|
||||||
|
|
||||||
|
assert.CommitCount(1)
|
||||||
|
assert.HeadCommitMessage(commitMessage)
|
||||||
|
},
|
||||||
|
})
|
38
pkg/integration/integration_tests/commit/new_branch.go
Normal file
38
pkg/integration/integration_tests/commit/new_branch.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package commit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var NewBranch = types.NewTest(types.NewTestArgs{
|
||||||
|
Description: "Creating a new branch from a commit",
|
||||||
|
ExtraCmdArgs: "",
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell types.Shell) {
|
||||||
|
shell.
|
||||||
|
EmptyCommit("commit 1").
|
||||||
|
EmptyCommit("commit 2").
|
||||||
|
EmptyCommit("commit 3")
|
||||||
|
},
|
||||||
|
Run: func(shell types.Shell, input types.Input, assert types.Assert, keys config.KeybindingConfig) {
|
||||||
|
assert.CommitCount(3)
|
||||||
|
|
||||||
|
input.SwitchToCommitsWindow()
|
||||||
|
assert.CurrentViewName("commits")
|
||||||
|
input.NextItem()
|
||||||
|
|
||||||
|
input.PushKeys(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)
|
||||||
|
},
|
||||||
|
})
|
16
pkg/integration/integration_tests/tests.go
Normal file
16
pkg/integration/integration_tests/tests.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package integration_tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration/integration_tests/branch"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration/integration_tests/commit"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Here is where we lists the actual tests that will run. When you create a new test,
|
||||||
|
// be sure to add it to this list.
|
||||||
|
|
||||||
|
var Tests = []types.Test{
|
||||||
|
commit.Commit,
|
||||||
|
commit.NewBranch,
|
||||||
|
branch.Suggestions,
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package gui
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -10,23 +10,10 @@ import (
|
|||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func recordingEvents() bool {
|
// this all relates to the old way of doing integration tests where you record yourself
|
||||||
return recordEventsTo() != ""
|
// and then replay the events.
|
||||||
}
|
|
||||||
|
|
||||||
func recordEventsTo() string {
|
func GetRecordingSpeed() float64 {
|
||||||
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.
|
// humans are slow so this speeds things up.
|
||||||
speed := 1.0
|
speed := 1.0
|
||||||
envReplaySpeed := os.Getenv("SPEED")
|
envReplaySpeed := os.Getenv("SPEED")
|
||||||
@ -40,7 +27,7 @@ func getRecordingSpeed() float64 {
|
|||||||
return speed
|
return speed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) loadRecording() (*gocui.Recording, error) {
|
func LoadRecording() (*gocui.Recording, error) {
|
||||||
path := os.Getenv("REPLAY_EVENTS_FROM")
|
path := os.Getenv("REPLAY_EVENTS_FROM")
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := ioutil.ReadFile(path)
|
||||||
@ -58,8 +45,8 @@ func (gui *Gui) loadRecording() (*gocui.Recording, error) {
|
|||||||
return recording, nil
|
return recording, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) saveRecording(recording *gocui.Recording) error {
|
func SaveRecording(recording *gocui.Recording) error {
|
||||||
if !recordingEvents() {
|
if !RecordingEvents() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
53
pkg/integration/shell.go
Normal file
53
pkg/integration/shell.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
||||||
|
"github.com/mgutz/str"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ShellImpl struct{}
|
||||||
|
|
||||||
|
var _ types.Shell = &ShellImpl{}
|
||||||
|
|
||||||
|
func (s *ShellImpl) RunCommand(cmdStr string) types.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 *ShellImpl) CreateFile(path string, content string) types.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 *ShellImpl) NewBranch(name string) types.Shell {
|
||||||
|
return s.RunCommand("git checkout -b " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ShellImpl) GitAddAll() types.Shell {
|
||||||
|
return s.RunCommand("git add -A")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ShellImpl) Commit(message string) types.Shell {
|
||||||
|
return s.RunCommand(fmt.Sprintf("git commit -m \"%s\"", message))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ShellImpl) EmptyCommit(message string) types.Shell {
|
||||||
|
return s.RunCommand(fmt.Sprintf("git commit --allow-empty -m \"%s\"", message))
|
||||||
|
}
|
152
pkg/integration/types/types.go
Normal file
152
pkg/integration/types/types.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Test interface {
|
||||||
|
Name() string
|
||||||
|
Description() string
|
||||||
|
// this is called before lazygit is run, for the sake of preparing the repo
|
||||||
|
SetupRepo(Shell)
|
||||||
|
// this gives you the default config and lets you set whatever values on it you like,
|
||||||
|
// so that they appear when lazygit runs
|
||||||
|
SetupConfig(config *config.AppConfig)
|
||||||
|
// this is called upon lazygit starting
|
||||||
|
Run(Shell, Input, Assert, config.KeybindingConfig)
|
||||||
|
// e.g. '-debug'
|
||||||
|
ExtraCmdArgs() string
|
||||||
|
// for tests that are flakey and when we don't have time to fix them
|
||||||
|
Skip() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is for running shell commands, mostly for the sake of setting up the repo
|
||||||
|
// but you can also run the commands from within lazygit to emulate things happening
|
||||||
|
// in the background.
|
||||||
|
// Implementation is at pkg/integration/shell.go
|
||||||
|
type Shell interface {
|
||||||
|
RunCommand(command string) Shell
|
||||||
|
CreateFile(name string, content string) Shell
|
||||||
|
NewBranch(branchName string) Shell
|
||||||
|
GitAddAll() Shell
|
||||||
|
Commit(message string) Shell
|
||||||
|
EmptyCommit(message string) Shell
|
||||||
|
}
|
||||||
|
|
||||||
|
// through this interface our test interacts with the lazygit gui
|
||||||
|
// Implementation is at pkg/gui/input.go
|
||||||
|
type Input interface {
|
||||||
|
// key is something like 'w' or '<space>'. It's best not to pass a direct value,
|
||||||
|
// but instead to go through the default user config to get a more meaningful key name
|
||||||
|
PushKeys(keys ...string)
|
||||||
|
// for typing into a popup prompt
|
||||||
|
Type(content string)
|
||||||
|
// for when you want to allow lazygit to process something before continuing
|
||||||
|
Wait(milliseconds int)
|
||||||
|
// going straight to a particular side window
|
||||||
|
SwitchToStatusWindow()
|
||||||
|
SwitchToFilesWindow()
|
||||||
|
SwitchToBranchesWindow()
|
||||||
|
SwitchToCommitsWindow()
|
||||||
|
SwitchToStashWindow()
|
||||||
|
// i.e. pressing enter
|
||||||
|
Confirm()
|
||||||
|
// i.e. pressing escape
|
||||||
|
Cancel()
|
||||||
|
// i.e. pressing space
|
||||||
|
Select()
|
||||||
|
// i.e. pressing down arrow
|
||||||
|
NextItem()
|
||||||
|
// i.e. pressing up arrow
|
||||||
|
PreviousItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// through this interface we assert on the state of the lazygit gui
|
||||||
|
type Assert interface {
|
||||||
|
WorkingTreeFileCount(int)
|
||||||
|
CommitCount(int)
|
||||||
|
HeadCommitMessage(string)
|
||||||
|
CurrentViewName(expectedViewName string)
|
||||||
|
CurrentBranchName(expectedBranchName string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestImpl struct {
|
||||||
|
name string
|
||||||
|
description string
|
||||||
|
extraCmdArgs string
|
||||||
|
skip bool
|
||||||
|
setupRepo func(shell Shell)
|
||||||
|
setupConfig func(config *config.AppConfig)
|
||||||
|
run func(
|
||||||
|
shell Shell,
|
||||||
|
input Input,
|
||||||
|
assert Assert,
|
||||||
|
keys config.KeybindingConfig,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewTestArgs struct {
|
||||||
|
Description string
|
||||||
|
SetupRepo func(shell Shell)
|
||||||
|
SetupConfig func(config *config.AppConfig)
|
||||||
|
Run func(shell Shell, input Input, assert Assert, keys config.KeybindingConfig)
|
||||||
|
ExtraCmdArgs string
|
||||||
|
Skip bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTest(args NewTestArgs) *TestImpl {
|
||||||
|
return &TestImpl{
|
||||||
|
name: testNameFromFilePath(),
|
||||||
|
description: args.Description,
|
||||||
|
extraCmdArgs: args.ExtraCmdArgs,
|
||||||
|
skip: args.Skip,
|
||||||
|
setupRepo: args.SetupRepo,
|
||||||
|
setupConfig: args.SetupConfig,
|
||||||
|
run: args.Run,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Test = (*TestImpl)(nil)
|
||||||
|
|
||||||
|
func (self *TestImpl) Name() string {
|
||||||
|
return self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *TestImpl) Description() string {
|
||||||
|
return self.description
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *TestImpl) ExtraCmdArgs() string {
|
||||||
|
return self.extraCmdArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *TestImpl) Skip() bool {
|
||||||
|
return self.skip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *TestImpl) SetupConfig(config *config.AppConfig) {
|
||||||
|
self.setupConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *TestImpl) SetupRepo(shell Shell) {
|
||||||
|
self.setupRepo(shell)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *TestImpl) Run(
|
||||||
|
shell Shell,
|
||||||
|
input Input,
|
||||||
|
assert Assert,
|
||||||
|
keys config.KeybindingConfig,
|
||||||
|
) {
|
||||||
|
self.run(shell, input, assert, keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNameFromFilePath() string {
|
||||||
|
path := utils.FilePath(3)
|
||||||
|
name := strings.Split(path, "integration/integration_tests/")[1]
|
||||||
|
|
||||||
|
return name[:len(name)-len(".go")]
|
||||||
|
}
|
@ -128,3 +128,10 @@ func StackTrace() string {
|
|||||||
n := runtime.Stack(buf, false)
|
n := runtime.Stack(buf, false)
|
||||||
return fmt.Sprintf("%s\n", buf[:n])
|
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
|
||||||
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
file0
|
|
@ -1 +0,0 @@
|
|||||||
ref: refs/heads/new-branch-3
|
|
Binary file not shown.
@ -1,8 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 commit (initial): file0
|
|
||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 checkout: moving from master to new-branch
|
|
||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 checkout: moving from new-branch to new-branch-2
|
|
||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 checkout: moving from new-branch-2 to new-branch-3
|
|
||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 checkout: moving from new-branch-3 to old-branch
|
|
||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 checkout: moving from old-branch to old-branch-2
|
|
||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 checkout: moving from old-branch-2 to old-branch-3
|
|
||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675450 +1000 checkout: moving from old-branch-3 to new-branch-3
|
|
@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 commit (initial): file0
|
|
@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 branch: Created from HEAD
|
|
@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 branch: Created from HEAD
|
|
@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 branch: Created from HEAD
|
|
@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 branch: Created from HEAD
|
|
@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 branch: Created from HEAD
|
|
@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8 CI <CI@example.com> 1617675445 +1000 branch: Created from HEAD
|
|
Binary file not shown.
Binary file not shown.
@ -1,2 +0,0 @@
|
|||||||
x��A
|
|
||||||
�0Fa�9�����Ā��U��4��)<�=���o魭�D�4v�W��c�%�>���V��T�Q՚����4�}������pYz{�x >8UGgafs�c2�'7u�����+�
|
|
@ -1 +0,0 @@
|
|||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8
|
|
@ -1 +0,0 @@
|
|||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8
|
|
@ -1 +0,0 @@
|
|||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8
|
|
@ -1 +0,0 @@
|
|||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8
|
|
@ -1 +0,0 @@
|
|||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8
|
|
@ -1 +0,0 @@
|
|||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8
|
|
@ -1 +0,0 @@
|
|||||||
75e9e90a1d58c37d97d46a543dfbfd0f33fc52d8
|
|
@ -1 +0,0 @@
|
|||||||
test0
|
|
@ -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}]}
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||||||
{ "description": "Checking out a branch with name suggestions", "speed": 100 }
|
|
@ -1 +0,0 @@
|
|||||||
commit
|
|
Binary file not shown.
@ -1,5 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 3df3d8761bc0f0828596b11845aeac175b7b7393 CI <CI@example.com> 1617671339 +1000 commit (initial): myfile1
|
|
||||||
3df3d8761bc0f0828596b11845aeac175b7b7393 a7d53cc21fd53100f955377be379423b0e386274 CI <CI@example.com> 1617671339 +1000 commit: myfile2
|
|
||||||
a7d53cc21fd53100f955377be379423b0e386274 4ba4f1ed711a9081fab21bc222469aa5176a01f8 CI <CI@example.com> 1617671339 +1000 commit: myfile3
|
|
||||||
4ba4f1ed711a9081fab21bc222469aa5176a01f8 1440bc6cc888a09dca2329d1060eec6de78d9d21 CI <CI@example.com> 1617671339 +1000 commit: myfile4
|
|
||||||
1440bc6cc888a09dca2329d1060eec6de78d9d21 e7560e2cd4783a261ad32496cefed2d9f69a46e7 CI <CI@example.com> 1617671342 +1000 commit: commit
|
|
@ -1,5 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 3df3d8761bc0f0828596b11845aeac175b7b7393 CI <CI@example.com> 1617671339 +1000 commit (initial): myfile1
|
|
||||||
3df3d8761bc0f0828596b11845aeac175b7b7393 a7d53cc21fd53100f955377be379423b0e386274 CI <CI@example.com> 1617671339 +1000 commit: myfile2
|
|
||||||
a7d53cc21fd53100f955377be379423b0e386274 4ba4f1ed711a9081fab21bc222469aa5176a01f8 CI <CI@example.com> 1617671339 +1000 commit: myfile3
|
|
||||||
4ba4f1ed711a9081fab21bc222469aa5176a01f8 1440bc6cc888a09dca2329d1060eec6de78d9d21 CI <CI@example.com> 1617671339 +1000 commit: myfile4
|
|
||||||
1440bc6cc888a09dca2329d1060eec6de78d9d21 e7560e2cd4783a261ad32496cefed2d9f69a46e7 CI <CI@example.com> 1617671342 +1000 commit: commit
|
|
BIN
test/integration/commit/expected/repo/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52
BIN
test/integration/commit/expected/repo/.git_keep/objects/0e/6cf0a6b79e8d44e186d812a1f74b43d64fac52
Binary file not shown.
BIN
test/integration/commit/expected/repo/.git_keep/objects/14/40bc6cc888a09dca2329d1060eec6de78d9d21
BIN
test/integration/commit/expected/repo/.git_keep/objects/14/40bc6cc888a09dca2329d1060eec6de78d9d21
Binary file not shown.
BIN
test/integration/commit/expected/repo/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827
BIN
test/integration/commit/expected/repo/.git_keep/objects/18/0cf8328022becee9aaa2577a8f84ea2b9f3827
Binary file not shown.
BIN
test/integration/commit/expected/repo/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce
BIN
test/integration/commit/expected/repo/.git_keep/objects/2b/173c861df433fa43ffad13f80c8b312c5c8bce
Binary file not shown.
BIN
test/integration/commit/expected/repo/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591
BIN
test/integration/commit/expected/repo/.git_keep/objects/2f/6174050380438f14b16658a356e762435ca591
Binary file not shown.
BIN
test/integration/commit/expected/repo/.git_keep/objects/30/a1ca3481fdec3245b02aeacfb72ddfe2a433be
BIN
test/integration/commit/expected/repo/.git_keep/objects/30/a1ca3481fdec3245b02aeacfb72ddfe2a433be
Binary file not shown.
@ -1,3 +0,0 @@
|
|||||||
x��A
|
|
||||||
�0@Q�9���i�I
|
|
||||||
"BW=F�L��!R"����~�����r��*�J��d ��¬�D���"\S�.����0�p���~��6��fw � ���L���zL��ɝ}��)�2r,�
|
|
BIN
test/integration/commit/expected/repo/.git_keep/objects/4b/a4f1ed711a9081fab21bc222469aa5176a01f8
BIN
test/integration/commit/expected/repo/.git_keep/objects/4b/a4f1ed711a9081fab21bc222469aa5176a01f8
Binary file not shown.
BIN
test/integration/commit/expected/repo/.git_keep/objects/4f/346f1ad5ba2917da2109e2eaa2f2dfbb86f10f
BIN
test/integration/commit/expected/repo/.git_keep/objects/4f/346f1ad5ba2917da2109e2eaa2f2dfbb86f10f
Binary file not shown.
BIN
test/integration/commit/expected/repo/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5
BIN
test/integration/commit/expected/repo/.git_keep/objects/a5/bce3fd2565d8f458555a0c6f42d0504a848bd5
Binary file not shown.
BIN
test/integration/commit/expected/repo/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416
BIN
test/integration/commit/expected/repo/.git_keep/objects/a7/341a59f0ddeef969e69fb6368266d22b0f2416
Binary file not shown.
BIN
test/integration/commit/expected/repo/.git_keep/objects/a7/d53cc21fd53100f955377be379423b0e386274
BIN
test/integration/commit/expected/repo/.git_keep/objects/a7/d53cc21fd53100f955377be379423b0e386274
Binary file not shown.
BIN
test/integration/commit/expected/repo/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54
BIN
test/integration/commit/expected/repo/.git_keep/objects/d2/34c5e057fe32c676ea67e8cb38f4625ddaeb54
Binary file not shown.
BIN
test/integration/commit/expected/repo/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b
BIN
test/integration/commit/expected/repo/.git_keep/objects/df/6b0d2bcc76e6ec0fca20c227104a4f28bac41b
Binary file not shown.
BIN
test/integration/commit/expected/repo/.git_keep/objects/e7/560e2cd4783a261ad32496cefed2d9f69a46e7
BIN
test/integration/commit/expected/repo/.git_keep/objects/e7/560e2cd4783a261ad32496cefed2d9f69a46e7
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
e7560e2cd4783a261ad32496cefed2d9f69a46e7
|
|
@ -1 +0,0 @@
|
|||||||
test1
|
|
@ -1 +0,0 @@
|
|||||||
test2
|
|
@ -1 +0,0 @@
|
|||||||
test3
|
|
@ -1 +0,0 @@
|
|||||||
test4
|
|
@ -1 +0,0 @@
|
|||||||
test5
|
|
@ -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}]}
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||||||
{ "description": "stage a file and commit the change", "speed": 15 }
|
|
@ -1 +0,0 @@
|
|||||||
file2
|
|
@ -1 +0,0 @@
|
|||||||
ref: refs/heads/lol
|
|
Binary file not shown.
@ -1,4 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 9901fd9b7766be600bed07f55f1794a759527a98 CI <CI@example.com> 1617674232 +1000 commit (initial): file0
|
|
||||||
9901fd9b7766be600bed07f55f1794a759527a98 0029f9bf66e346d47ede6a501abb5b82bee60096 CI <CI@example.com> 1617674232 +1000 commit: file1
|
|
||||||
0029f9bf66e346d47ede6a501abb5b82bee60096 e1cb250774fb8606d33062518d0ae03831130249 CI <CI@example.com> 1617674232 +1000 commit: file2
|
|
||||||
e1cb250774fb8606d33062518d0ae03831130249 0029f9bf66e346d47ede6a501abb5b82bee60096 CI <CI@example.com> 1617674249 +1000 checkout: moving from master to lol
|
|
@ -1 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 0029f9bf66e346d47ede6a501abb5b82bee60096 CI <CI@example.com> 1617674249 +1000 branch: Created from 0029f9bf66e346d47ede6a501abb5b82bee60096
|
|
@ -1,3 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000 9901fd9b7766be600bed07f55f1794a759527a98 CI <CI@example.com> 1617674232 +1000 commit (initial): file0
|
|
||||||
9901fd9b7766be600bed07f55f1794a759527a98 0029f9bf66e346d47ede6a501abb5b82bee60096 CI <CI@example.com> 1617674232 +1000 commit: file1
|
|
||||||
0029f9bf66e346d47ede6a501abb5b82bee60096 e1cb250774fb8606d33062518d0ae03831130249 CI <CI@example.com> 1617674232 +1000 commit: file2
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,2 +0,0 @@
|
|||||||
x+)JMU03c040031QH��I5`������ֶw���w.��h�T�[H
|
|
||||||
��y�W5�Ɨ��(�|�^-�W(x9
|
|
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
0029f9bf66e346d47ede6a501abb5b82bee60096
|
|
@ -1 +0,0 @@
|
|||||||
e1cb250774fb8606d33062518d0ae03831130249
|
|
@ -1 +0,0 @@
|
|||||||
test0
|
|
@ -1 +0,0 @@
|
|||||||
test1
|
|
@ -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}]}
|
|
@ -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
|
|
@ -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 }
|
|
@ -0,0 +1 @@
|
|||||||
|
my commit message
|
@ -0,0 +1 @@
|
|||||||
|
ref: refs/heads/branch-to-checkout
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user