diff --git a/integration/main.go b/integration/main.go deleted file mode 100644 index 9e9fc8751..000000000 --- a/integration/main.go +++ /dev/null @@ -1,132 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "log" - "os" - "path/filepath" - - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" - "github.com/jesseduffield/lazygit/pkg/integration" - "github.com/stretchr/testify/assert" -) - -// 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. -// -// 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 -// as an env var. - -func main() { - err := testWithEnvVars() - if err != nil { - panic(err) - } -} - -func testWithEnvVars() error { - record := os.Getenv("RECORD_EVENTS") != "" - updateSnapshots := record || os.Getenv("UPDATE_SNAPSHOTS") != "" - - return test(record, updateSnapshots) -} - -func test(record bool, updateSnapshots bool) error { - rootDir := integration.GetRootDirectory() - err := os.Chdir(rootDir) - if err != nil { - return err - } - - testDir := filepath.Join(rootDir, "test", "integration") - - osCommand := oscommands.NewDummyOSCommand() - err = osCommand.RunCommand("go build -o %s", integration.TempLazygitPath()) - if err != nil { - return err - } - - tests, err := integration.LoadTests(testDir) - if err != nil { - panic(err) - } - - selectedTestName := os.Args[1] - - for _, test := range tests { - if selectedTestName != "" && test.Name != selectedTestName { - continue - } - - speedEnv := os.Getenv("SPEED") - speeds := integration.GetTestSpeeds(test.Speed, updateSnapshots, speedEnv) - testPath := filepath.Join(testDir, test.Name) - actualDir := filepath.Join(testPath, "actual") - expectedDir := filepath.Join(testPath, "expected") - configDir := filepath.Join(testPath, "used_config") - log.Printf("testPath: %s, actualDir: %s, expectedDir: %s", testPath, actualDir, expectedDir) - - for i, speed := range speeds { - log.Printf("%s: attempting test at speed %f\n", test.Name, speed) - - integration.FindOrCreateDir(testPath) - integration.PrepareIntegrationTestDir(actualDir) - err := integration.CreateFixture(testPath, actualDir) - if err != nil { - return err - } - - cmd, err := integration.GetLazygitCommand(testPath, rootDir, record, speed) - if err != nil { - return err - } - - cmd.Stdout = os.Stdout - cmd.Stdin = os.Stdin - cmd.Stderr = os.Stderr - - if err := cmd.Run(); err != nil { - return err - } - - if updateSnapshots { - err = oscommands.CopyDir(actualDir, expectedDir) - if err != nil { - return err - } - } - - actual, expected, err := integration.GenerateSnapshots(actualDir, expectedDir) - if err != nil { - return err - } - - if expected == actual { - fmt.Printf("%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 { - bytes, err := ioutil.ReadFile(filepath.Join(configDir, "development.log")) - if err != nil { - return err - } - fmt.Println(string(bytes)) - assert.Equal(MockTestingT{}, expected, actual, fmt.Sprintf("expected:\n%s\nactual:\n%s\n", expected, actual)) - os.Exit(1) - } - } - } - - return nil -} - -type MockTestingT struct{} - -func (t MockTestingT) Errorf(format string, args ...interface{}) { - fmt.Printf(format, args...) -} diff --git a/pkg/gui/gui_test.go b/pkg/gui/gui_test.go index 677b77f7c..43c426e53 100644 --- a/pkg/gui/gui_test.go +++ b/pkg/gui/gui_test.go @@ -5,11 +5,10 @@ import ( "io" "io/ioutil" "os" - "path/filepath" + "os/exec" "testing" "github.com/creack/pty" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/integration" "github.com/stretchr/testify/assert" ) @@ -37,85 +36,43 @@ import ( // original playback speed. Speed may be a decimal. func Test(t *testing.T) { - rootDir := integration.GetRootDirectory() - err := os.Chdir(rootDir) - assert.NoError(t, err) - - testDir := filepath.Join(rootDir, "test", "integration") - - osCommand := oscommands.NewDummyOSCommand() - err = osCommand.RunCommand("go build -o %s", integration.TempLazygitPath()) - assert.NoError(t, err) - - tests, err := integration.LoadTests(testDir) - assert.NoError(t, err) - record := false - updateSnapshots := record || os.Getenv("UPDATE_SNAPSHOTS") != "" + updateSnapshots := os.Getenv("UPDATE_SNAPSHOTS") != "" + speedEnv := os.Getenv("SPEED") - for _, test := range tests { - test := test - - t.Run(test.Name, func(t *testing.T) { - speedEnv := os.Getenv("SPEED") - speeds := integration.GetTestSpeeds(test.Speed, updateSnapshots, speedEnv) - testPath := filepath.Join(testDir, test.Name) - actualDir := filepath.Join(testPath, "actual") - expectedDir := filepath.Join(testPath, "expected") - t.Logf("testPath: %s, actualDir: %s, expectedDir: %s", testPath, actualDir, expectedDir) - - // three retries at normal speed for the sake of flakey tests - speeds = append(speeds, 1, 1, 1) - for i, speed := range speeds { - t.Logf("%s: attempting test at speed %f\n", test.Name, speed) - - integration.FindOrCreateDir(testPath) - integration.PrepareIntegrationTestDir(actualDir) - err := integration.CreateFixture(testPath, actualDir) + err := integration.RunTests( + t.Logf, + runCmdHeadless, + func(test *integration.Test, f func() error) { + t.Run(test.Name, func(t *testing.T) { + err := f() assert.NoError(t, err) + }) + }, + updateSnapshots, + record, + speedEnv, + func(expected string, actual string) { + assert.Equal(t, expected, actual, fmt.Sprintf("expected:\n%s\nactual:\n%s\n", expected, actual)) + }, + ) - configDir := filepath.Join(testPath, "used_config") - - cmd, err := integration.GetLazygitCommand(testPath, rootDir, record, speed) - assert.NoError(t, err) - - cmd.Env = append( - cmd.Env, - "HEADLESS=true", - "TERM=xterm", - ) - - f, err := pty.StartWithSize(cmd, &pty.Winsize{Rows: 100, Cols: 100}) - assert.NoError(t, err) - - _, _ = io.Copy(ioutil.Discard, f) - - assert.NoError(t, err) - - _ = f.Close() - - if updateSnapshots { - err = oscommands.CopyDir(actualDir, expectedDir) - assert.NoError(t, err) - } - - actual, expected, err := integration.GenerateSnapshots(actualDir, expectedDir) - assert.NoError(t, err) - - if expected == actual { - t.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")) - assert.NoError(t, err) - t.Log(string(bytes)) - assert.Equal(t, expected, actual, fmt.Sprintf("expected:\n%s\nactual:\n%s\n", expected, actual)) - } - } - }) - } + assert.NoError(t, err) +} + +func runCmdHeadless(cmd *exec.Cmd) error { + cmd.Env = append( + cmd.Env, + "HEADLESS=true", + "TERM=xterm", + ) + + f, err := pty.StartWithSize(cmd, &pty.Winsize{Rows: 100, Cols: 100}) + if err != nil { + return err + } + + _, _ = io.Copy(ioutil.Discard, f) + + return f.Close() } diff --git a/pkg/integration/integration.go b/pkg/integration/integration.go index e433f1954..32c0e9ff0 100644 --- a/pkg/integration/integration.go +++ b/pkg/integration/integration.go @@ -20,7 +20,108 @@ type Test struct { Description string `json:"description"` } -func PrepareIntegrationTestDir(actualDir string) { +// 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() error), + updateSnapshots bool, + record bool, + speedEnv string, + onFail func(expected string, actual string), +) error { + rootDir := GetRootDirectory() + err := os.Chdir(rootDir) + if err != nil { + return err + } + + testDir := filepath.Join(rootDir, "test", "integration") + + osCommand := oscommands.NewDummyOSCommand() + err = osCommand.RunCommand("go build -o %s", tempLazygitPath()) + if err != nil { + return err + } + + tests, err := LoadTests(testDir) + if err != nil { + return err + } + + for _, test := range tests { + test := test + + fnWrapper(test, func() error { + speeds := getTestSpeeds(test.Speed, updateSnapshots, speedEnv) + testPath := filepath.Join(testDir, test.Name) + actualDir := filepath.Join(testPath, "actual") + expectedDir := filepath.Join(testPath, "expected") + logf("testPath: %s, actualDir: %s, expectedDir: %s", testPath, actualDir, expectedDir) + + // three retries at normal speed for the sake of flakey tests + speeds = append(speeds, 1, 1, 1) + for i, speed := range speeds { + logf("%s: attempting test at speed %f\n", test.Name, speed) + + findOrCreateDir(testPath) + prepareIntegrationTestDir(actualDir) + err := createFixture(testPath, actualDir) + if err != nil { + return err + } + + configDir := filepath.Join(testPath, "used_config") + + cmd, err := getLazygitCommand(testPath, rootDir, record, speed) + if err != nil { + return err + } + + err = runCmd(cmd) + if err != nil { + return err + } + + if updateSnapshots { + err = oscommands.CopyDir(actualDir, expectedDir) + if err != nil { + return err + } + } + + actual, expected, err := generateSnapshots(actualDir, expectedDir) + if err != nil { + return err + } + + if expected == actual { + 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)) + onFail(expected, actual) + } + } + + return nil + }) + } + + return nil +} + +func prepareIntegrationTestDir(actualDir string) { // remove contents of integration test directory dir, err := ioutil.ReadDir(actualDir) if err != nil { @@ -63,7 +164,7 @@ func GetRootDirectory() string { } } -func CreateFixture(testPath, actualDir string) error { +func createFixture(testPath, actualDir string) error { osCommand := oscommands.NewDummyOSCommand() bashScriptPath := filepath.Join(testPath, "setup.sh") cmd := secureexec.Command("bash", bashScriptPath, actualDir) @@ -75,11 +176,11 @@ func CreateFixture(testPath, actualDir string) error { return nil } -func TempLazygitPath() string { +func tempLazygitPath() string { return filepath.Join("/tmp", "lazygit", "test_lazygit") } -func GetTestSpeeds(testStartSpeed float64, updateSnapshots bool, speedStr string) []float64 { +func getTestSpeeds(testStartSpeed float64, updateSnapshots bool, speedStr string) []float64 { if updateSnapshots { // have to go at original speed if updating snapshots in case we go to fast and create a junk snapshot return []float64{1.0} @@ -136,7 +237,7 @@ func LoadTests(testDir string) ([]*Test, error) { return tests, nil } -func FindOrCreateDir(path string) { +func findOrCreateDir(path string) { _, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { @@ -150,7 +251,7 @@ func FindOrCreateDir(path string) { } } -func GenerateSnapshot(dir string) (string, error) { +func generateSnapshot(dir string) (string, error) { osCommand := oscommands.NewDummyOSCommand() _, err := os.Stat(filepath.Join(dir, ".git")) @@ -204,8 +305,8 @@ func GenerateSnapshot(dir string) (string, error) { return snapshot, nil } -func GenerateSnapshots(actualDir string, expectedDir string) (string, string, error) { - actual, err := GenerateSnapshot(actualDir) +func generateSnapshots(actualDir string, expectedDir string) (string, string, error) { + actual, err := generateSnapshot(actualDir) if err != nil { return "", "", err } @@ -229,7 +330,7 @@ func GenerateSnapshots(actualDir string, expectedDir string) (string, string, er filepath.Join(expectedDir, ".git"), ) - expected, err := GenerateSnapshot(expectedDir) + expected, err := generateSnapshot(expectedDir) if err != nil { return "", "", err } @@ -237,7 +338,7 @@ func GenerateSnapshots(actualDir string, expectedDir string) (string, string, er return actual, expected, nil } -func GetLazygitCommand(testPath string, rootDir string, record bool, speed float64) (*exec.Cmd, error) { +func getLazygitCommand(testPath string, rootDir string, record bool, speed float64) (*exec.Cmd, error) { osCommand := oscommands.NewDummyOSCommand() replayPath := filepath.Join(testPath, "recording.json") @@ -264,7 +365,7 @@ func GetLazygitCommand(testPath string, rootDir string, record bool, speed float return nil, err } - cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s", TempLazygitPath(), configDir, actualDir) + cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s", tempLazygitPath(), configDir, actualDir) cmd := osCommand.ExecutableFromString(cmdStr) cmd.Env = append(cmd.Env, fmt.Sprintf("SPEED=%f", speed)) diff --git a/test/lazyintegration/main.go b/test/lazyintegration/main.go index 5fdc6ca82..4b3eba77d 100644 --- a/test/lazyintegration/main.go +++ b/test/lazyintegration/main.go @@ -13,6 +13,8 @@ import ( "github.com/jesseduffield/lazygit/pkg/secureexec" ) +// this program lets you manage integration tests in a TUI. + type App struct { tests []*integration.Test itemIdx int @@ -98,7 +100,7 @@ func main() { return nil } - cmd := secureexec.Command("sh", "-c", fmt.Sprintf("RECORD_EVENTS=true go run integration/main.go %s", currentTest.Name)) + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("RECORD_EVENTS=true go run test/runner/main.go %s", currentTest.Name)) app.runSubprocess(cmd) return nil @@ -112,7 +114,7 @@ func main() { return nil } - cmd := secureexec.Command("sh", "-c", fmt.Sprintf("go run integration/main.go %s", currentTest.Name)) + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("go run test/runner/main.go %s", currentTest.Name)) app.runSubprocess(cmd) return nil @@ -126,7 +128,7 @@ func main() { return nil } - cmd := secureexec.Command("sh", "-c", fmt.Sprintf("UPDATE_SNAPSHOTS=true go run integration/main.go %s", currentTest.Name)) + cmd := secureexec.Command("sh", "-c", fmt.Sprintf("UPDATE_SNAPSHOTS=true go run test/runner/main.go %s", currentTest.Name)) app.runSubprocess(cmd) return nil diff --git a/test/runner/main.go b/test/runner/main.go new file mode 100644 index 000000000..54e163c48 --- /dev/null +++ b/test/runner/main.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + + "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/stretchr/testify/assert" +) + +// 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. +// +// 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 +// as an env var. + +func main() { + record := os.Getenv("RECORD_EVENTS") != "" + updateSnapshots := record || os.Getenv("UPDATE_SNAPSHOTS") != "" + speedEnv := os.Getenv("SPEED") + selectedTestName := os.Args[1] + + err := integration.RunTests( + log.Printf, + runCmdInTerminal, + func(test *integration.Test, f func() error) { + if selectedTestName != "" && test.Name != selectedTestName { + return + } + if err := f(); err != nil { + log.Print(err.Error()) + } + }, + updateSnapshots, + record, + speedEnv, + func(expected string, actual string) { + assert.Equal(MockTestingT{}, expected, actual, fmt.Sprintf("expected:\n%s\nactual:\n%s\n", expected, actual)) + }, + ) + if err != nil { + log.Print(err.Error()) + } +} + +type MockTestingT struct{} + +func (t MockTestingT) Errorf(format string, args ...interface{}) { + fmt.Printf(format, args...) +} + +func runCmdInTerminal(cmd *exec.Cmd) error { + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + + return cmd.Run() +}