mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-02-03 13:21:56 +02:00
add new integration test pattern
This commit is contained in:
parent
c7f9d5801b
commit
77881a9c7d
10
.gitignore
vendored
10
.gitignore
vendored
@ -33,11 +33,19 @@ lazygit.exe
|
||||
!.gitmodules_keep
|
||||
|
||||
test/git_server/data
|
||||
|
||||
# we'll scrap these lines once we've fully moved over to the new integration test approach
|
||||
test/integration/*/actual/
|
||||
test/integration/*/used_config/
|
||||
# these sample hooks waste too much space
|
||||
test/integration/*/expected/**/hooks/
|
||||
test/integration/*/expected_remote/**/hooks/
|
||||
|
||||
test/integration_new/**/actual/
|
||||
test/integration_new/**/used_config/
|
||||
# these sample hooks waste too much space
|
||||
test/integration_new/**/expected/**/hooks/
|
||||
test/integration_new/**/expected_remote/**/hooks/
|
||||
|
||||
oryxBuildBinary
|
||||
__debug_bin
|
||||
__debug_bin
|
||||
|
5
main.go
5
main.go
@ -16,6 +16,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration"
|
||||
"github.com/jesseduffield/lazygit/pkg/logs"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
yaml "github.com/jesseduffield/yaml"
|
||||
@ -150,6 +151,10 @@ func main() {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if test, ok := integration.CurrentIntegrationTest(); ok {
|
||||
test.SetupConfig(appConfig)
|
||||
}
|
||||
|
||||
common, err := app.NewCommon(appConfig)
|
||||
if err != nil {
|
||||
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/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration"
|
||||
"github.com/jesseduffield/lazygit/pkg/tasks"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||
@ -418,12 +419,14 @@ var RuneReplacements = map[rune]string{
|
||||
}
|
||||
|
||||
func (gui *Gui) initGocui(headless bool) (*gocui.Gui, error) {
|
||||
recordEvents := recordingEvents()
|
||||
recordEvents := integration.RecordingEvents()
|
||||
playMode := gocui.NORMAL
|
||||
if recordEvents {
|
||||
playMode = gocui.RECORDING
|
||||
} else if replaying() {
|
||||
} else if integration.Replaying() {
|
||||
playMode = gocui.REPLAYING
|
||||
} else if integration.IntegrationTestName() != "" {
|
||||
playMode = gocui.REPLAYING_NEW
|
||||
}
|
||||
|
||||
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
|
||||
func (gui *Gui) Run(startArgs types.StartArgs) error {
|
||||
g, err := gui.initGocui(headless())
|
||||
g, err := gui.initGocui(integration.Headless())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -490,23 +493,7 @@ func (gui *Gui) Run(startArgs types.StartArgs) error {
|
||||
})
|
||||
deadlock.Opts.Disable = !gui.Debug
|
||||
|
||||
if replaying() {
|
||||
gui.g.RecordingConfig = gocui.RecordingConfig{
|
||||
Speed: getRecordingSpeed(),
|
||||
Leeway: 100,
|
||||
}
|
||||
|
||||
var err error
|
||||
gui.g.Recording, err = gui.loadRecording()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go utils.Safe(func() {
|
||||
time.Sleep(time.Second * 40)
|
||||
log.Fatal("40 seconds is up, lazygit recording took too long to complete")
|
||||
})
|
||||
}
|
||||
gui.handleTestMode()
|
||||
|
||||
gui.g.OnSearchEscape = gui.onSearchEscape
|
||||
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
|
||||
}
|
||||
|
||||
@ -627,7 +614,7 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
|
||||
gui.Mutexes.SubprocessMutex.Lock()
|
||||
defer gui.Mutexes.SubprocessMutex.Unlock()
|
||||
|
||||
if replaying() {
|
||||
if integration.Replaying() {
|
||||
// we do not yet support running subprocesses within integration tests. So if
|
||||
// we're replaying an integration test and we're inside this method, something
|
||||
// has gone wrong, so we should fail
|
||||
|
@ -3,6 +3,9 @@
|
||||
|
||||
package gui
|
||||
|
||||
// this is the new way of running tests. See pkg/integration/integration_tests/commit.go
|
||||
// for an example
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
@ -14,60 +17,37 @@ import (
|
||||
|
||||
"github.com/creack/pty"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
"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) {
|
||||
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(
|
||||
err := integration.RunTestsNew(
|
||||
t.Logf,
|
||||
runCmdHeadless,
|
||||
func(test *integration.Test, f func(*testing.T) error) {
|
||||
func(test types.Test, f func(*testing.T) error) {
|
||||
defer func() { testNumber += 1 }()
|
||||
if testNumber%parallelTotal != parallelIndex {
|
||||
return
|
||||
}
|
||||
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
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))
|
||||
|
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
|
||||
|
||||
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"
|
||||
"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 {
|
||||
Name string `json:"name"`
|
||||
Speed float64 `json:"speed"`
|
||||
Description string `json:"description"`
|
||||
ExtraCmdArgs string `json:"extraCmdArgs"`
|
||||
Skip bool `json:"skip"`
|
||||
}
|
||||
// re-exporting this so that clients only need to import one package
|
||||
var Tests = integration_tests.Tests
|
||||
|
||||
type Mode int
|
||||
|
||||
const (
|
||||
// default: for when we're just running a test and comparing to the snapshot
|
||||
TEST = iota
|
||||
// for when we want to record a test and set the snapshot based on the result
|
||||
RECORD
|
||||
// when we just want to use the setup of the test for our own sandboxing purposes.
|
||||
// This does not record the session and does not create/update snapshots
|
||||
SANDBOX
|
||||
// running a test but updating the snapshot
|
||||
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(
|
||||
func RunTestsNew(
|
||||
logf func(format string, formatArgs ...interface{}),
|
||||
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,
|
||||
speedEnv string,
|
||||
onFail func(t *testing.T, expected string, actual string, prefix string),
|
||||
includeSkipped bool,
|
||||
) error {
|
||||
@ -76,7 +33,7 @@ func RunTests(
|
||||
return err
|
||||
}
|
||||
|
||||
testDir := filepath.Join(rootDir, "test", "integration")
|
||||
testDir := filepath.Join(rootDir, "test", "integration_new")
|
||||
|
||||
osCommand := oscommands.NewDummyOSCommand()
|
||||
err = osCommand.Cmd.New("go build -o " + tempLazygitPath()).Run()
|
||||
@ -84,115 +41,94 @@ func RunTests(
|
||||
return err
|
||||
}
|
||||
|
||||
tests, err := LoadTests(testDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
for _, test := range Tests {
|
||||
test := test
|
||||
|
||||
fnWrapper(test, func(t *testing.T) error { //nolint: thelper
|
||||
if test.Skip && !includeSkipped {
|
||||
logf("skipping test: %s", test.Name)
|
||||
if test.Skip() && !includeSkipped {
|
||||
logf("skipping test: %s", test.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
speeds := getTestSpeeds(test.Speed, mode, speedEnv)
|
||||
testPath := filepath.Join(testDir, test.Name)
|
||||
testPath := filepath.Join(testDir, test.Name())
|
||||
|
||||
actualDir := filepath.Join(testPath, "actual")
|
||||
expectedDir := filepath.Join(testPath, "expected")
|
||||
actualRepoDir := filepath.Join(actualDir, "repo")
|
||||
logf("path: %s", testPath)
|
||||
|
||||
for i, speed := range speeds {
|
||||
if mode != SANDBOX && mode != RECORD {
|
||||
logf("%s: attempting test at speed %f\n", test.Name, speed)
|
||||
}
|
||||
findOrCreateDir(testPath)
|
||||
prepareIntegrationTestDir(actualDir)
|
||||
findOrCreateDir(actualRepoDir)
|
||||
err := createFixtureNew(test, actualRepoDir, rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
findOrCreateDir(testPath)
|
||||
prepareIntegrationTestDir(actualDir)
|
||||
findOrCreateDir(actualRepoDir)
|
||||
err := createFixture(testPath, actualRepoDir)
|
||||
configDir := filepath.Join(testPath, "used_config")
|
||||
|
||||
cmd, err := getLazygitCommandNew(test, testPath, rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = runCmd(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if mode == UPDATE_SNAPSHOT {
|
||||
// create/update snapshot
|
||||
err = oscommands.CopyDir(actualDir, expectedDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configDir := filepath.Join(testPath, "used_config")
|
||||
if err := renameSpecialPaths(expectedDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd, err := getLazygitCommand(testPath, rootDir, mode, speed, test.ExtraCmdArgs)
|
||||
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
|
||||
}
|
||||
|
||||
err = runCmd(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range expectedFiles {
|
||||
if !f.IsDir() {
|
||||
return errors.New("unexpected file (as opposed to directory) in integration test 'expected' directory")
|
||||
}
|
||||
|
||||
if mode == UPDATE_SNAPSHOT || mode == RECORD {
|
||||
// create/update snapshot
|
||||
err = oscommands.CopyDir(actualDir, expectedDir)
|
||||
// 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 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 expectedRepo != actualRepo {
|
||||
// get the log file and print it
|
||||
bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logf("%s", string(bytes))
|
||||
|
||||
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
|
||||
onFail(t, expectedRepo, actualRepo, f.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logf("test passed: %s", test.Name())
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@ -200,344 +136,35 @@ func RunTests(
|
||||
return nil
|
||||
}
|
||||
|
||||
// validates that the actual and expected dirs have the same repo names (doesn't actually check the contents of the repos)
|
||||
func validateSameRepos(expectedDir string, actualDir string) error {
|
||||
// iterate through each repo in the expected dir and compare to the corresponding repo in the actual dir
|
||||
expectedFiles, err := ioutil.ReadDir(expectedDir)
|
||||
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 {
|
||||
func createFixtureNew(test types.Test, actualDir string, rootDir string) error {
|
||||
if err := os.Chdir(actualDir); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for {
|
||||
_, err := os.Stat(filepath.Join(path, ".git"))
|
||||
shell := &ShellImpl{}
|
||||
shell.RunCommand("git init")
|
||||
shell.RunCommand(`git config user.email "CI@example.com"`)
|
||||
shell.RunCommand(`git config user.name "CI"`)
|
||||
|
||||
if err == nil {
|
||||
return path
|
||||
}
|
||||
test.SetupRepo(shell)
|
||||
|
||||
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 {
|
||||
// changing directory back to rootDir after the setup is done
|
||||
if err := os.Chdir(rootDir); 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) {
|
||||
func getLazygitCommandNew(test types.Test, testPath string, rootDir 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)
|
||||
err := os.RemoveAll(configDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -546,17 +173,11 @@ func getLazygitCommand(testPath string, rootDir string, mode Mode, speed float64
|
||||
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.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))
|
||||
}
|
||||
cmdObj.AddEnvVars(fmt.Sprintf("LAZYGIT_TEST_NAME=%s", test.Name()))
|
||||
|
||||
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 (
|
||||
"encoding/json"
|
||||
@ -10,23 +10,10 @@ import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func recordingEvents() bool {
|
||||
return recordEventsTo() != ""
|
||||
}
|
||||
// this all relates to the old way of doing integration tests where you record yourself
|
||||
// and then replay the events.
|
||||
|
||||
func recordEventsTo() string {
|
||||
return os.Getenv("RECORD_EVENTS_TO")
|
||||
}
|
||||
|
||||
func replaying() bool {
|
||||
return os.Getenv("REPLAY_EVENTS_FROM") != ""
|
||||
}
|
||||
|
||||
func headless() bool {
|
||||
return os.Getenv("HEADLESS") != ""
|
||||
}
|
||||
|
||||
func getRecordingSpeed() float64 {
|
||||
func GetRecordingSpeed() float64 {
|
||||
// humans are slow so this speeds things up.
|
||||
speed := 1.0
|
||||
envReplaySpeed := os.Getenv("SPEED")
|
||||
@ -40,7 +27,7 @@ func getRecordingSpeed() float64 {
|
||||
return speed
|
||||
}
|
||||
|
||||
func (gui *Gui) loadRecording() (*gocui.Recording, error) {
|
||||
func LoadRecording() (*gocui.Recording, error) {
|
||||
path := os.Getenv("REPLAY_EVENTS_FROM")
|
||||
|
||||
data, err := ioutil.ReadFile(path)
|
||||
@ -58,8 +45,8 @@ func (gui *Gui) loadRecording() (*gocui.Recording, error) {
|
||||
return recording, nil
|
||||
}
|
||||
|
||||
func (gui *Gui) saveRecording(recording *gocui.Recording) error {
|
||||
if !recordingEvents() {
|
||||
func SaveRecording(recording *gocui.Recording) error {
|
||||
if !RecordingEvents() {
|
||||
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)
|
||||
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
|
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,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,Ï
|
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.
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
|
||||
矢y�5�来ミ(桍ァ^-ン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
Loading…
x
Reference in New Issue
Block a user