mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-03-19 21:28:28 +02:00
support integration testing
WIP
This commit is contained in:
parent
ece93e5eef
commit
f76196937a
3
.gitignore
vendored
3
.gitignore
vendored
@ -25,4 +25,5 @@ lazygit
|
||||
!.circleci/
|
||||
!.github/
|
||||
|
||||
test/git_server/data
|
||||
test/git_server/data
|
||||
test/integration_test/
|
||||
|
224
pkg/gui/gui_test.go
Normal file
224
pkg/gui/gui_test.go
Normal file
@ -0,0 +1,224 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// To run an integration test, e.g. for test 'commit', go:
|
||||
// go test pkg/gui/gui_test.go -run /commit
|
||||
//
|
||||
// To record keypresses for an integration test, pass RECORD_EVENTS=true like so:
|
||||
// RECORD_EVENTS=true go test pkg/gui/gui_test.go -run /commit
|
||||
//
|
||||
// To update a snapshot for an integration test, pass UPDATE_SNAPSHOT=true
|
||||
// UPDATE_SNAPSHOT=true go test pkg/gui/gui_test.go -run /commit
|
||||
//
|
||||
// When RECORD_EVENTS is true, updates will be updated automatically
|
||||
//
|
||||
// integration tests are run in test/integration_test 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 failed.
|
||||
//
|
||||
// TODO: support passing an env var for playback speed, given it's currently pretty fast
|
||||
|
||||
type integrationTest struct {
|
||||
name string
|
||||
prepare func() error
|
||||
}
|
||||
|
||||
func generateSnapshot(t *testing.T) string {
|
||||
osCommand := oscommands.NewDummyOSCommand()
|
||||
cmd := `sh -c "git status; cat ./*; git log --pretty=%B -p"`
|
||||
|
||||
snapshot, err := osCommand.RunCommandWithOutput(cmd)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return snapshot
|
||||
}
|
||||
|
||||
func findOrCreateDir(path string) {
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = os.MkdirAll(path, 0777)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
tests := []integrationTest{
|
||||
{
|
||||
name: "commit",
|
||||
prepare: createFixture1,
|
||||
},
|
||||
{
|
||||
name: "squash",
|
||||
prepare: createFixture2,
|
||||
},
|
||||
}
|
||||
|
||||
gotoRootDirectory()
|
||||
|
||||
rootDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
testPath := filepath.Join(rootDir, "test", "integration", test.name)
|
||||
findOrCreateDir(testPath)
|
||||
|
||||
replayPath := filepath.Join(testPath, "recording.json")
|
||||
snapshotPath := filepath.Join(testPath, "snapshot.txt")
|
||||
|
||||
err := os.Chdir(rootDir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
prepareIntegrationTestDir()
|
||||
|
||||
err = test.prepare()
|
||||
assert.NoError(t, err)
|
||||
|
||||
record := os.Getenv("RECORD_EVENTS") != ""
|
||||
runLazygit(t, replayPath, record)
|
||||
|
||||
updateSnapshot := os.Getenv("UPDATE_SNAPSHOT") != ""
|
||||
|
||||
actual := generateSnapshot(t)
|
||||
|
||||
if updateSnapshot {
|
||||
err := ioutil.WriteFile(snapshotPath, []byte(actual), 0600)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
expectedBytes, err := ioutil.ReadFile(snapshotPath)
|
||||
assert.NoError(t, err)
|
||||
expected := string(expectedBytes)
|
||||
|
||||
assert.Equal(t, expected, actual, fmt.Sprintf("expected:\n%s\nactual:\n%s\n", expected, actual))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createFixture1() error {
|
||||
cmds := []string{
|
||||
"git init",
|
||||
`sh -c "echo test > myfile"`,
|
||||
}
|
||||
|
||||
return runCommands(cmds)
|
||||
}
|
||||
|
||||
func createFixture2() error {
|
||||
cmds := []string{
|
||||
"git init",
|
||||
`sh -c "echo test1 > myfile1"`,
|
||||
`git add .`,
|
||||
`git commit -am "myfile1"`,
|
||||
`sh -c "echo test2 > myfile2"`,
|
||||
`git add .`,
|
||||
`git commit -am "myfile2"`,
|
||||
`sh -c "echo test3 > myfile3"`,
|
||||
`git add .`,
|
||||
`git commit -am "myfile3"`,
|
||||
`sh -c "echo test4 > myfile4"`,
|
||||
`git add .`,
|
||||
`git commit -am "myfile4"`,
|
||||
`sh -c "echo test5 > myfile5"`,
|
||||
`git add .`,
|
||||
`git commit -am "myfile5"`,
|
||||
}
|
||||
|
||||
return runCommands(cmds)
|
||||
}
|
||||
|
||||
func runCommands(cmds []string) error {
|
||||
osCommand := oscommands.NewDummyOSCommand()
|
||||
|
||||
for _, cmd := range cmds {
|
||||
if err := osCommand.RunCommand(cmd); err != nil {
|
||||
return errors.New(fmt.Sprintf("error running command `%s`: %v", cmd, err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func gotoRootDirectory() {
|
||||
for {
|
||||
_, err := os.Stat(".git")
|
||||
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !os.IsNotExist(err) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err = os.Chdir(".."); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runLazygit(t *testing.T, replayPath string, record bool) {
|
||||
osCommand := oscommands.NewDummyOSCommand()
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if record {
|
||||
cmd = osCommand.ExecutableFromString("lazygit")
|
||||
cmd.Env = append(
|
||||
cmd.Env,
|
||||
fmt.Sprintf("RECORD_EVENTS_TO=%s", replayPath),
|
||||
)
|
||||
} else {
|
||||
cmd = osCommand.ExecutableFromString("lazygit")
|
||||
cmd.Env = append(
|
||||
cmd.Env,
|
||||
fmt.Sprintf("REPLAY_EVENTS_FROM=%s", replayPath),
|
||||
)
|
||||
}
|
||||
err := osCommand.RunExecutable(cmd)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func prepareIntegrationTestDir() {
|
||||
path := filepath.Join("test", "integration_test")
|
||||
|
||||
// remove contents of integration test directory
|
||||
dir, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = os.Mkdir(path, 0777)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
for _, d := range dir {
|
||||
os.RemoveAll(filepath.Join(path, d.Name()))
|
||||
}
|
||||
|
||||
if err := os.Chdir(path); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -9,7 +9,11 @@ import (
|
||||
)
|
||||
|
||||
func recordingEvents() bool {
|
||||
return os.Getenv("RECORD_EVENTS") == "true"
|
||||
return recordEventsTo() != ""
|
||||
}
|
||||
|
||||
func recordEventsTo() string {
|
||||
return os.Getenv("RECORD_EVENTS_TO")
|
||||
}
|
||||
|
||||
func (gui *Gui) timeSinceStart() int64 {
|
||||
@ -29,11 +33,14 @@ func (gui *Gui) replayRecordedEvents() {
|
||||
ticker := time.NewTicker(time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
var leeway int64 = 1000
|
||||
// might need to add leeway if this ends up flakey
|
||||
var leeway int64 = 0
|
||||
// humans are slow so this speeds things up.
|
||||
var speed int64 = 5
|
||||
|
||||
for _, event := range events {
|
||||
for range ticker.C {
|
||||
now := gui.timeSinceStart() - leeway
|
||||
now := gui.timeSinceStart()*speed - leeway
|
||||
if gui.g != nil && now >= event.Timestamp {
|
||||
gui.g.ReplayedEvents <- *event.Event
|
||||
break
|
||||
@ -70,7 +77,9 @@ func (gui *Gui) saveRecordedEvents() error {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile("recorded_events.json", jsonEvents, 0600)
|
||||
path := recordEventsTo()
|
||||
|
||||
return ioutil.WriteFile(path, jsonEvents, 0600)
|
||||
}
|
||||
|
||||
func (gui *Gui) recordEvents() {
|
||||
|
226
test/integration/commit/recording.json
Normal file
226
test/integration/commit/recording.json
Normal file
@ -0,0 +1,226 @@
|
||||
[
|
||||
{
|
||||
"Timestamp": 41,
|
||||
"Event": {
|
||||
"Type": 1,
|
||||
"Mod": 0,
|
||||
"Key": 0,
|
||||
"Ch": 0,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 0,
|
||||
"Bytes": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 1042,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 32,
|
||||
"Ch": 0,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "IA=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 1602,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 0,
|
||||
"Ch": 99,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "Yw=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 2010,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 0,
|
||||
"Ch": 109,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "bQ=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 2170,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 0,
|
||||
"Ch": 121,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "eQ=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 2234,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 32,
|
||||
"Ch": 0,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "IA=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 2354,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 0,
|
||||
"Ch": 99,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "Yw=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 2410,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 0,
|
||||
"Ch": 111,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "bw=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 2578,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 0,
|
||||
"Ch": 109,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "bQ=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 2690,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 0,
|
||||
"Ch": 109,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "bQ=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 2730,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 0,
|
||||
"Ch": 105,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "aQ=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 2850,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 0,
|
||||
"Ch": 116,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "dA=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 2954,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 13,
|
||||
"Ch": 0,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "DQ=="
|
||||
}
|
||||
},
|
||||
{
|
||||
"Timestamp": 3625,
|
||||
"Event": {
|
||||
"Type": 0,
|
||||
"Mod": 0,
|
||||
"Key": 0,
|
||||
"Ch": 113,
|
||||
"Width": 0,
|
||||
"Height": 0,
|
||||
"Err": null,
|
||||
"MouseX": 0,
|
||||
"MouseY": 0,
|
||||
"N": 1,
|
||||
"Bytes": "cQ=="
|
||||
}
|
||||
}
|
||||
]
|
13
test/integration/commit/snapshot.txt
Normal file
13
test/integration/commit/snapshot.txt
Normal file
@ -0,0 +1,13 @@
|
||||
On branch master
|
||||
nothing to commit, working tree clean
|
||||
test
|
||||
my commit
|
||||
|
||||
|
||||
diff --git a/myfile b/myfile
|
||||
new file mode 100644
|
||||
index 0000000..9daeafb
|
||||
--- /dev/null
|
||||
+++ b/myfile
|
||||
@@ -0,0 +1 @@
|
||||
+test
|
1
test/integration/squash/recording.json
Normal file
1
test/integration/squash/recording.json
Normal file
@ -0,0 +1 @@
|
||||
[{"Timestamp":14,"Event":{"Type":1,"Mod":0,"Key":0,"Ch":0,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":0,"Bytes":null}},{"Timestamp":482,"Event":{"Type":0,"Mod":0,"Key":65514,"Ch":0,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":3,"Bytes":"G09D"}},{"Timestamp":626,"Event":{"Type":0,"Mod":0,"Key":65514,"Ch":0,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":3,"Bytes":"G09D"}},{"Timestamp":810,"Event":{"Type":0,"Mod":0,"Key":65516,"Ch":0,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":3,"Bytes":"G09C"}},{"Timestamp":937,"Event":{"Type":0,"Mod":0,"Key":65516,"Ch":0,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":3,"Bytes":"G09C"}},{"Timestamp":1065,"Event":{"Type":0,"Mod":0,"Key":65516,"Ch":0,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":3,"Bytes":"G09C"}},{"Timestamp":1591,"Event":{"Type":0,"Mod":0,"Key":0,"Ch":101,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":1,"Bytes":"ZQ=="}},{"Timestamp":2034,"Event":{"Type":0,"Mod":0,"Key":65517,"Ch":0,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":3,"Bytes":"G09B"}},{"Timestamp":2243,"Event":{"Type":0,"Mod":0,"Key":0,"Ch":115,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":1,"Bytes":"cw=="}},{"Timestamp":2554,"Event":{"Type":0,"Mod":0,"Key":65517,"Ch":0,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":3,"Bytes":"G09B"}},{"Timestamp":2803,"Event":{"Type":0,"Mod":0,"Key":0,"Ch":100,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":1,"Bytes":"ZA=="}},{"Timestamp":3209,"Event":{"Type":0,"Mod":0,"Key":65517,"Ch":0,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":3,"Bytes":"G09B"}},{"Timestamp":3522,"Event":{"Type":0,"Mod":0,"Key":0,"Ch":102,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":1,"Bytes":"Zg=="}},{"Timestamp":4066,"Event":{"Type":0,"Mod":0,"Key":0,"Ch":109,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":1,"Bytes":"bQ=="}},{"Timestamp":5091,"Event":{"Type":0,"Mod":0,"Key":13,"Ch":0,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":1,"Bytes":"DQ=="}},{"Timestamp":5834,"Event":{"Type":0,"Mod":0,"Key":0,"Ch":113,"Width":0,"Height":0,"Err":null,"MouseX":0,"MouseY":0,"N":1,"Bytes":"cQ=="}}]
|
42
test/integration/squash/snapshot.txt
Normal file
42
test/integration/squash/snapshot.txt
Normal file
@ -0,0 +1,42 @@
|
||||
On branch master
|
||||
nothing to commit, working tree clean
|
||||
test1
|
||||
test2
|
||||
test3
|
||||
test5
|
||||
myfile2
|
||||
|
||||
myfile3
|
||||
|
||||
|
||||
diff --git a/myfile2 b/myfile2
|
||||
new file mode 100644
|
||||
index 0000000..180cf83
|
||||
--- /dev/null
|
||||
+++ b/myfile2
|
||||
@@ -0,0 +1 @@
|
||||
+test2
|
||||
diff --git a/myfile3 b/myfile3
|
||||
new file mode 100644
|
||||
index 0000000..df6b0d2
|
||||
--- /dev/null
|
||||
+++ b/myfile3
|
||||
@@ -0,0 +1 @@
|
||||
+test3
|
||||
diff --git a/myfile5 b/myfile5
|
||||
new file mode 100644
|
||||
index 0000000..4f346f1
|
||||
--- /dev/null
|
||||
+++ b/myfile5
|
||||
@@ -0,0 +1 @@
|
||||
+test5
|
||||
myfile1
|
||||
|
||||
|
||||
diff --git a/myfile1 b/myfile1
|
||||
new file mode 100644
|
||||
index 0000000..a5bce3f
|
||||
--- /dev/null
|
||||
+++ b/myfile1
|
||||
@@ -0,0 +1 @@
|
||||
+test1
|
Loading…
x
Reference in New Issue
Block a user