mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-10 20:05:50 +02:00
allow sandbox mode with integration tests
This commit is contained in:
parent
8ca71eeb36
commit
2691477aff
5 changed files with 137 additions and 66 deletions
|
@ -33,17 +33,32 @@ git commit -am "myfile1"
|
|||
|
||||
## Running tests
|
||||
|
||||
### From a TUI
|
||||
|
||||
You can run/record/sandbox tests via a TUI with the following command:
|
||||
|
||||
```
|
||||
go run test/lazyintegration/main.go
|
||||
```
|
||||
|
||||
This TUI makes much of the following documentation redundant, but feel free to read through anyway!
|
||||
|
||||
### From command line
|
||||
|
||||
To run all tests - assuming you're at the project root:
|
||||
|
||||
```
|
||||
go test ./pkg/gui/
|
||||
```
|
||||
|
||||
To run them in parallel
|
||||
|
||||
```
|
||||
PARALLEL=true go test ./pkg/gui
|
||||
```
|
||||
|
||||
To run a single test
|
||||
|
||||
```
|
||||
go test ./pkg/gui -run /<test name>
|
||||
# For example, to run the `tags` test:
|
||||
|
@ -51,29 +66,35 @@ go test ./pkg/gui -run /tags
|
|||
```
|
||||
|
||||
To run a test at a certain speed
|
||||
|
||||
```
|
||||
SPEED=2 go test ./pkg/gui -run /<test name>
|
||||
```
|
||||
|
||||
To update a snapshot
|
||||
|
||||
```
|
||||
UPDATE_SNAPSHOTS=true go test ./pkg/gui -run /<test name>
|
||||
MODE=updateSnapshot go test ./pkg/gui -run /<test name>
|
||||
```
|
||||
|
||||
## Creating a new test
|
||||
|
||||
To create a new test:
|
||||
1) Copy and paste an existing test directory and rename the new directory to whatever you want the test name to be. Update the test.json file's description to describe your test.
|
||||
2) Update the `setup.sh` any way you like
|
||||
3) If you want to have a config folder for just that test, create a `config` directory to contain a `config.yml` and optionally a `state.yml` file. Otherwise, the `test/default_test_config` directory will be used.
|
||||
4) From the lazygit root directory, run:
|
||||
|
||||
1. Copy and paste an existing test directory and rename the new directory to whatever you want the test name to be. Update the test.json file's description to describe your test.
|
||||
2. Update the `setup.sh` any way you like
|
||||
3. If you want to have a config folder for just that test, create a `config` directory to contain a `config.yml` and optionally a `state.yml` file. Otherwise, the `test/default_test_config` directory will be used.
|
||||
4. From the lazygit root directory, run:
|
||||
|
||||
```
|
||||
RECORD_EVENTS=true go test ./pkg/gui -run /<test name>
|
||||
MODE=record go test ./pkg/gui -run /<test name>
|
||||
```
|
||||
5) Feel free to re-attempt recording as many times as you like. In the absence of a proper testing framework, the more deliberate your keypresses, the better!
|
||||
6) Once satisfied with the recording, stage all the newly created files: `test.json`, `setup.sh`, `recording.json` and the `expected` directory that contains a copy of the repo you created.
|
||||
|
||||
5. Feel free to re-attempt recording as many times as you like. In the absence of a proper testing framework, the more deliberate your keypresses, the better!
|
||||
6. Once satisfied with the recording, stage all the newly created files: `test.json`, `setup.sh`, `recording.json` and the `expected` directory that contains a copy of the repo you created.
|
||||
|
||||
The resulting directory will look like:
|
||||
|
||||
```
|
||||
actual/ (the resulting repo after running the test, ignored by git)
|
||||
expected/ (the 'snapshot' repo)
|
||||
|
@ -85,6 +106,14 @@ recording.json
|
|||
|
||||
Feel free to create a hierarchy of directories in the `test/integration` directory to group tests by feature.
|
||||
|
||||
## Sandboxing
|
||||
|
||||
The integration tests serve a secondary purpose of providing a setup for easy sandboxing. If you want to run a test in sandbox mode (meaning the session won't be recorded and we won't create/update snapshots), go:
|
||||
|
||||
```
|
||||
MODE=sandbox go test ./pkg/gui -run /<test name>
|
||||
```
|
||||
|
||||
## Feedback
|
||||
|
||||
If you think this process can be improved, let me know! It shouldn't be too hard to change things.
|
||||
|
|
|
@ -39,8 +39,7 @@ import (
|
|||
// original playback speed. Speed may be a decimal.
|
||||
|
||||
func Test(t *testing.T) {
|
||||
record := false
|
||||
updateSnapshots := os.Getenv("UPDATE_SNAPSHOTS") != ""
|
||||
mode := integration.GetModeFromEnv()
|
||||
speedEnv := os.Getenv("SPEED")
|
||||
includeSkipped := os.Getenv("INCLUDE_SKIPPED") != ""
|
||||
|
||||
|
@ -53,8 +52,7 @@ func Test(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
})
|
||||
},
|
||||
updateSnapshots,
|
||||
record,
|
||||
mode,
|
||||
speedEnv,
|
||||
func(t *testing.T, expected string, actual string, prefix string) {
|
||||
assert.Equal(t, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual))
|
||||
|
|
|
@ -24,6 +24,36 @@ type Test struct {
|
|||
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, 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.
|
||||
|
@ -31,8 +61,7 @@ func RunTests(
|
|||
logf func(format string, formatArgs ...interface{}),
|
||||
runCmd func(cmd *exec.Cmd) error,
|
||||
fnWrapper func(test *Test, f func(*testing.T) error),
|
||||
updateSnapshots bool,
|
||||
record bool,
|
||||
mode Mode,
|
||||
speedEnv string,
|
||||
onFail func(t *testing.T, expected string, actual string, prefix string),
|
||||
includeSkipped bool,
|
||||
|
@ -65,7 +94,7 @@ func RunTests(
|
|||
}
|
||||
|
||||
fnWrapper(test, func(t *testing.T) error {
|
||||
speeds := getTestSpeeds(test.Speed, updateSnapshots, speedEnv)
|
||||
speeds := getTestSpeeds(test.Speed, mode, speedEnv)
|
||||
testPath := filepath.Join(testDir, test.Name)
|
||||
actualRepoDir := filepath.Join(testPath, "actual")
|
||||
expectedRepoDir := filepath.Join(testPath, "expected")
|
||||
|
@ -73,10 +102,10 @@ func RunTests(
|
|||
expectedRemoteDir := filepath.Join(testPath, "expected_remote")
|
||||
logf("path: %s", testPath)
|
||||
|
||||
// three retries at normal speed for the sake of flakey tests
|
||||
speeds = append(speeds, 1)
|
||||
for i, speed := range speeds {
|
||||
logf("%s: attempting test at speed %f\n", test.Name, speed)
|
||||
if mode != SANDBOX && mode != RECORD {
|
||||
logf("%s: attempting test at speed %f\n", test.Name, speed)
|
||||
}
|
||||
|
||||
findOrCreateDir(testPath)
|
||||
prepareIntegrationTestDir(actualRepoDir)
|
||||
|
@ -88,7 +117,7 @@ func RunTests(
|
|||
|
||||
configDir := filepath.Join(testPath, "used_config")
|
||||
|
||||
cmd, err := getLazygitCommand(testPath, rootDir, record, speed, test.ExtraCmdArgs)
|
||||
cmd, err := getLazygitCommand(testPath, rootDir, mode, speed, test.ExtraCmdArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -98,7 +127,7 @@ func RunTests(
|
|||
return err
|
||||
}
|
||||
|
||||
if updateSnapshots {
|
||||
if mode == UPDATE_SNAPSHOT || mode == RECORD {
|
||||
err = oscommands.CopyDir(actualRepoDir, expectedRepoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -122,39 +151,41 @@ func RunTests(
|
|||
}
|
||||
}
|
||||
|
||||
actualRepo, expectedRepo, err := generateSnapshots(actualRepoDir, expectedRepoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actualRemote := "remote folder does not exist"
|
||||
expectedRemote := "remote folder does not exist"
|
||||
if folderExists(expectedRemoteDir) {
|
||||
actualRemote, expectedRemote, err = generateSnapshotsForRemote(actualRemoteDir, expectedRemoteDir)
|
||||
if mode != SANDBOX {
|
||||
actualRepo, expectedRepo, err := generateSnapshots(actualRepoDir, expectedRepoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if folderExists(actualRemoteDir) {
|
||||
actualRemote = "remote folder exists"
|
||||
}
|
||||
|
||||
if expectedRepo == actualRepo && expectedRemote == actualRemote {
|
||||
logf("%s: success at speed %f\n", test.Name, speed)
|
||||
break
|
||||
}
|
||||
|
||||
// if the snapshots and we haven't tried all playback speeds different we'll retry at a slower speed
|
||||
if i == len(speeds)-1 {
|
||||
// get the log file and print that
|
||||
bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log"))
|
||||
if err != nil {
|
||||
return err
|
||||
actualRemote := "remote folder does not exist"
|
||||
expectedRemote := "remote folder does not exist"
|
||||
if folderExists(expectedRemoteDir) {
|
||||
actualRemote, expectedRemote, err = generateSnapshotsForRemote(actualRemoteDir, expectedRemoteDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if folderExists(actualRemoteDir) {
|
||||
actualRemote = "remote folder exists"
|
||||
}
|
||||
logf("%s", string(bytes))
|
||||
if expectedRepo != actualRepo {
|
||||
onFail(t, expectedRepo, actualRepo, "repo")
|
||||
} else {
|
||||
onFail(t, expectedRemote, actualRemote, "remote")
|
||||
|
||||
if expectedRepo == actualRepo && expectedRemote == actualRemote {
|
||||
logf("%s: success at speed %f\n", test.Name, speed)
|
||||
break
|
||||
}
|
||||
|
||||
// if the snapshots and we haven't tried all playback speeds different we'll retry at a slower speed
|
||||
if i == len(speeds)-1 {
|
||||
// get the log file and print that
|
||||
bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logf("%s", string(bytes))
|
||||
if expectedRepo != actualRepo {
|
||||
onFail(t, expectedRepo, actualRepo, "repo")
|
||||
} else {
|
||||
onFail(t, expectedRemote, actualRemote, "remote")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -231,8 +262,8 @@ func tempLazygitPath() string {
|
|||
return filepath.Join("/tmp", "lazygit", "test_lazygit")
|
||||
}
|
||||
|
||||
func getTestSpeeds(testStartSpeed float64, updateSnapshots bool, speedStr string) []float64 {
|
||||
if updateSnapshots {
|
||||
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}
|
||||
}
|
||||
|
@ -254,7 +285,7 @@ func getTestSpeeds(testStartSpeed float64, updateSnapshots bool, speedStr string
|
|||
if startSpeed > 5 {
|
||||
speeds = append(speeds, 5)
|
||||
}
|
||||
speeds = append(speeds, 1)
|
||||
speeds = append(speeds, 1, 1)
|
||||
|
||||
return speeds
|
||||
}
|
||||
|
@ -400,7 +431,7 @@ func generateSnapshotsForRemote(actualDir string, expectedDir string) (string, s
|
|||
return actual, expected, nil
|
||||
}
|
||||
|
||||
func getLazygitCommand(testPath string, rootDir string, record bool, speed float64, extraCmdArgs string) (*exec.Cmd, error) {
|
||||
func getLazygitCommand(testPath string, rootDir string, mode Mode, speed float64, extraCmdArgs string) (*exec.Cmd, error) {
|
||||
osCommand := oscommands.NewDummyOSCommand()
|
||||
|
||||
replayPath := filepath.Join(testPath, "recording.json")
|
||||
|
@ -432,9 +463,10 @@ func getLazygitCommand(testPath string, rootDir string, record bool, speed float
|
|||
cmdObj := osCommand.Cmd.New(cmdStr)
|
||||
cmdObj.AddEnvVars(fmt.Sprintf("SPEED=%f", speed))
|
||||
|
||||
if record {
|
||||
switch mode {
|
||||
case RECORD:
|
||||
cmdObj.AddEnvVars(fmt.Sprintf("RECORD_EVENTS_TO=%s", replayPath))
|
||||
} else {
|
||||
case TEST, UPDATE_SNAPSHOT:
|
||||
cmdObj.AddEnvVars(fmt.Sprintf("REPLAY_EVENTS_FROM=%s", replayPath))
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,21 @@ func main() {
|
|||
return nil
|
||||
}
|
||||
|
||||
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true RECORD_EVENTS=true go run test/runner/main.go %s", currentTest.Name))
|
||||
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=record go run test/runner/main.go %s", currentTest.Name))
|
||||
app.runSubprocess(cmd)
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.SetKeybinding("list", nil, 's', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||
currentTest := app.getCurrentTest()
|
||||
if currentTest == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=sandbox go run test/runner/main.go %s", currentTest.Name))
|
||||
app.runSubprocess(cmd)
|
||||
|
||||
return nil
|
||||
|
@ -128,13 +142,13 @@ func main() {
|
|||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.SetKeybinding("list", nil, 's', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||
if err := g.SetKeybinding("list", nil, 'u', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||
currentTest := app.getCurrentTest()
|
||||
if currentTest == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true UPDATE_SNAPSHOTS=true go run test/runner/main.go %s", currentTest.Name))
|
||||
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("INCLUDE_SKIPPED=true MODE=updateSnapshot go run test/runner/main.go %s", currentTest.Name))
|
||||
app.runSubprocess(cmd)
|
||||
|
||||
return nil
|
||||
|
@ -347,7 +361,7 @@ func (app *App) layout(g *gocui.Gui) error {
|
|||
keybindingsView.Title = "Keybindings"
|
||||
keybindingsView.Wrap = true
|
||||
keybindingsView.FgColor = gocui.ColorDefault
|
||||
fmt.Fprintln(keybindingsView, "up/down: navigate, enter: run test, s: run test and update snapshots, r: record test, o: open test config, n: duplicate test, m: rename test, d: delete test")
|
||||
fmt.Fprintln(keybindingsView, "up/down: navigate, enter: run test, u: run test and update snapshots, r: record test, s: sandbox, o: open test config, n: duplicate test, m: rename test, d: delete test")
|
||||
}
|
||||
|
||||
editorView, err := g.SetViewBeneath("editor", "keybindings", editorViewHeight)
|
||||
|
|
|
@ -11,19 +11,18 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// see https://github.com/jesseduffield/lazygit/blob/master/docs/Integration_Tests.md
|
||||
// This file can be invoked directly, but you might find it easier to go through
|
||||
// test/lazyintegration/main.go, which provides a convenient gui wrapper to integration
|
||||
// tests.
|
||||
// test/lazyintegration/main.go, which provides a convenient gui wrapper to integration tests.
|
||||
//
|
||||
// If invoked directly, you can specify a test by passing it as the first argument.
|
||||
// You can also specify that you want to record a test by passing RECORD_EVENTS=true
|
||||
// You can also specify that you want to record a test by passing MODE=record
|
||||
// as an env var.
|
||||
|
||||
func main() {
|
||||
record := os.Getenv("RECORD_EVENTS") != ""
|
||||
updateSnapshots := record || os.Getenv("UPDATE_SNAPSHOTS") != ""
|
||||
mode := integration.GetModeFromEnv()
|
||||
speedEnv := os.Getenv("SPEED")
|
||||
includeSkipped := os.Getenv("INCLUDE_SKIPPED") != ""
|
||||
includeSkipped := os.Getenv("INCLUDE_SKIPPED") == "true"
|
||||
selectedTestName := os.Args[1]
|
||||
|
||||
err := integration.RunTests(
|
||||
|
@ -37,8 +36,7 @@ func main() {
|
|||
log.Print(err.Error())
|
||||
}
|
||||
},
|
||||
updateSnapshots,
|
||||
record,
|
||||
mode,
|
||||
speedEnv,
|
||||
func(_t *testing.T, expected string, actual string, prefix string) {
|
||||
assert.Equal(MockTestingT{}, expected, actual, fmt.Sprintf("Unexpected %s. Expected:\n%s\nActual:\n%s\n", prefix, expected, actual))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue