diff --git a/Makefile b/Makefile index f10109c19..a220b68b3 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ update-cheatsheet: # For more details about integration test, see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md. .PHONY: integration-test-tui integration-test-tui: - go run cmd/integration_test/main.go tui + go run cmd/integration_test/main.go tui $(filter-out $@,$(MAKECMDGOALS)) .PHONY: integration-test-cli integration-test-cli: diff --git a/cmd/integration_test/main.go b/cmd/integration_test/main.go index 7153691da..711ce8b33 100644 --- a/cmd/integration_test/main.go +++ b/cmd/integration_test/main.go @@ -26,6 +26,29 @@ Usage: > go run cmd/integration_test/main.go help ` +type flagInfo struct { + name string // name of the flag; can be used with "-" or "--" + flag *bool // a pointer to the variable that should be set to true when this flag is passed +} + +// Takes the args that you want to parse (excluding the program name and any +// subcommands), and returns the remaining args with the flags removed +func parseFlags(args []string, flags []flagInfo) []string { +outer: + for len(args) > 0 { + for _, f := range flags { + if args[0] == "-"+f.name || args[0] == "--"+f.name { + *f.flag = true + args = args[1:] + continue outer + } + } + break + } + + return args +} + func main() { if len(os.Args) < 2 { log.Fatal(usage) @@ -35,27 +58,26 @@ func main() { case "help": fmt.Println(usage) case "cli": - testNames := os.Args[2:] slow := false sandbox := false waitForDebugger := false - // get the next arg if it's --slow - if len(os.Args) > 2 { - if os.Args[2] == "--slow" || os.Args[2] == "-slow" { - testNames = os.Args[3:] - slow = true - } else if os.Args[2] == "--sandbox" || os.Args[2] == "-sandbox" { - testNames = os.Args[3:] - sandbox = true - } else if os.Args[2] == "--debug" || os.Args[2] == "-debug" { - testNames = os.Args[3:] - waitForDebugger = true - } - } - - clients.RunCLI(testNames, slow, sandbox, waitForDebugger) + raceDetector := false + testNames := parseFlags(os.Args[2:], []flagInfo{ + {"slow", &slow}, + {"sandbox", &sandbox}, + {"debug", &waitForDebugger}, + {"race", &raceDetector}, + }) + clients.RunCLI(testNames, slow, sandbox, waitForDebugger, raceDetector) case "tui": - clients.RunTUI() + raceDetector := false + remainingArgs := parseFlags(os.Args[2:], []flagInfo{ + {"race", &raceDetector}, + }) + if len(remainingArgs) > 0 { + log.Fatal("tui only supports the -race argument.") + } + clients.RunTUI(raceDetector) default: log.Fatal(usage) } diff --git a/pkg/integration/clients/cli.go b/pkg/integration/clients/cli.go index 571dea491..d6cf5a628 100644 --- a/pkg/integration/clients/cli.go +++ b/pkg/integration/clients/cli.go @@ -23,7 +23,7 @@ import ( // If invoked directly, you can specify tests to run by passing their names as positional arguments -func RunCLI(testNames []string, slow bool, sandbox bool, waitForDebugger bool) { +func RunCLI(testNames []string, slow bool, sandbox bool, waitForDebugger bool, raceDetector bool) { inputDelay := tryConvert(os.Getenv("INPUT_DELAY"), 0) if slow { inputDelay = SLOW_INPUT_DELAY @@ -36,6 +36,7 @@ func RunCLI(testNames []string, slow bool, sandbox bool, waitForDebugger bool) { runAndPrintFatalError, sandbox, waitForDebugger, + raceDetector, inputDelay, 1, ) @@ -87,12 +88,15 @@ outer: return testsToRun } -func runCmdInTerminal(cmd *exec.Cmd) error { +func runCmdInTerminal(cmd *exec.Cmd) (int, error) { cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin cmd.Stderr = os.Stderr - return cmd.Run() + if err := cmd.Start(); err != nil { + return -1, err + } + return cmd.Process.Pid, cmd.Wait() } func tryConvert(numStr string, defaultVal int) int { diff --git a/pkg/integration/clients/go_test.go b/pkg/integration/clients/go_test.go index 01f174211..6503621ba 100644 --- a/pkg/integration/clients/go_test.go +++ b/pkg/integration/clients/go_test.go @@ -27,6 +27,7 @@ func TestIntegration(t *testing.T) { parallelTotal := tryConvert(os.Getenv("PARALLEL_TOTAL"), 1) parallelIndex := tryConvert(os.Getenv("PARALLEL_INDEX"), 0) + raceDetector := os.Getenv("LAZYGIT_RACE_DETECTOR") != "" testNumber := 0 err := components.RunTests( @@ -53,6 +54,7 @@ func TestIntegration(t *testing.T) { }, false, false, + raceDetector, 0, // Allow two attempts at each test to get around flakiness 2, @@ -61,7 +63,7 @@ func TestIntegration(t *testing.T) { assert.NoError(t, err) } -func runCmdHeadless(cmd *exec.Cmd) error { +func runCmdHeadless(cmd *exec.Cmd) (int, error) { cmd.Env = append( cmd.Env, "HEADLESS=true", @@ -79,15 +81,16 @@ func runCmdHeadless(cmd *exec.Cmd) error { // running other commands in a pty. f, err := pty.StartWithSize(cmd, &pty.Winsize{Rows: 300, Cols: 300}) if err != nil { - return err + return -1, err } _, _ = io.Copy(io.Discard, f) if cmd.Wait() != nil { + _ = f.Close() // return an error with the stderr output - return errors.New(stderr.String()) + return cmd.Process.Pid, errors.New(stderr.String()) } - return f.Close() + return cmd.Process.Pid, f.Close() } diff --git a/pkg/integration/clients/tui.go b/pkg/integration/clients/tui.go index 64610ccb3..7ad9cb47f 100644 --- a/pkg/integration/clients/tui.go +++ b/pkg/integration/clients/tui.go @@ -21,7 +21,7 @@ import ( var SLOW_INPUT_DELAY = 600 -func RunTUI() { +func RunTUI(raceDetector bool) { rootDir := utils.GetLazyRootDirectory() testDir := filepath.Join(rootDir, "test", "integration") @@ -85,7 +85,7 @@ func RunTUI() { return nil } - suspendAndRunTest(currentTest, true, false, 0) + suspendAndRunTest(currentTest, true, false, raceDetector, 0) return nil }); err != nil { @@ -98,7 +98,7 @@ func RunTUI() { return nil } - suspendAndRunTest(currentTest, false, false, 0) + suspendAndRunTest(currentTest, false, false, raceDetector, 0) return nil }); err != nil { @@ -111,7 +111,7 @@ func RunTUI() { return nil } - suspendAndRunTest(currentTest, false, false, SLOW_INPUT_DELAY) + suspendAndRunTest(currentTest, false, false, raceDetector, SLOW_INPUT_DELAY) return nil }); err != nil { @@ -124,7 +124,7 @@ func RunTUI() { return nil } - suspendAndRunTest(currentTest, false, true, 0) + suspendAndRunTest(currentTest, false, true, raceDetector, 0) return nil }); err != nil { @@ -284,12 +284,12 @@ func (self *app) wrapEditor(f func(v *gocui.View, key gocui.Key, ch rune, mod go } } -func suspendAndRunTest(test *components.IntegrationTest, sandbox bool, waitForDebugger bool, inputDelay int) { +func suspendAndRunTest(test *components.IntegrationTest, sandbox bool, waitForDebugger bool, raceDetector bool, inputDelay int) { if err := gocui.Screen.Suspend(); err != nil { panic(err) } - runTuiTest(test, sandbox, waitForDebugger, inputDelay) + runTuiTest(test, sandbox, waitForDebugger, raceDetector, inputDelay) fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint("press enter to return")) fmt.Scanln() // wait for enter press @@ -384,7 +384,7 @@ func quit(g *gocui.Gui, v *gocui.View) error { return gocui.ErrQuit } -func runTuiTest(test *components.IntegrationTest, sandbox bool, waitForDebugger bool, inputDelay int) { +func runTuiTest(test *components.IntegrationTest, sandbox bool, waitForDebugger bool, raceDetector bool, inputDelay int) { err := components.RunTests( []*components.IntegrationTest{test}, log.Printf, @@ -392,6 +392,7 @@ func runTuiTest(test *components.IntegrationTest, sandbox bool, waitForDebugger runAndPrintError, sandbox, waitForDebugger, + raceDetector, inputDelay, 1, ) diff --git a/pkg/integration/components/runner.go b/pkg/integration/components/runner.go index 4a9d624ad..821319dc1 100644 --- a/pkg/integration/components/runner.go +++ b/pkg/integration/components/runner.go @@ -26,10 +26,11 @@ const ( func RunTests( tests []*IntegrationTest, logf func(format string, formatArgs ...interface{}), - runCmd func(cmd *exec.Cmd) error, + runCmd func(cmd *exec.Cmd) (int, error), testWrapper func(test *IntegrationTest, f func() error), sandbox bool, waitForDebugger bool, + raceDetector bool, inputDelay int, maxAttempts int, ) error { @@ -41,7 +42,7 @@ func RunTests( testDir := filepath.Join(projectRootDir, "test", "_results") - if err := buildLazygit(); err != nil { + if err := buildLazygit(raceDetector); err != nil { return err } @@ -59,7 +60,7 @@ func RunTests( ) for i := 0; i < maxAttempts; i++ { - err := runTest(test, paths, projectRootDir, logf, runCmd, sandbox, waitForDebugger, inputDelay, gitVersion) + err := runTest(test, paths, projectRootDir, logf, runCmd, sandbox, waitForDebugger, raceDetector, inputDelay, gitVersion) if err != nil { if i == maxAttempts-1 { return err @@ -82,9 +83,10 @@ func runTest( paths Paths, projectRootDir string, logf func(format string, formatArgs ...interface{}), - runCmd func(cmd *exec.Cmd) error, + runCmd func(cmd *exec.Cmd) (int, error), sandbox bool, waitForDebugger bool, + raceDetector bool, inputDelay int, gitVersion *git_commands.GitVersion, ) error { @@ -107,12 +109,17 @@ func runTest( return err } - err = runCmd(cmd) - if err != nil { - return err + pid, err := runCmd(cmd) + + // Print race detector log regardless of the command's exit status + if raceDetector { + logPath := fmt.Sprintf("%s.%d", raceDetectorLogsPath(), pid) + if bytes, err := os.ReadFile(logPath); err == nil { + logf("Race detector log:\n" + string(bytes)) + } } - return nil + return err } func prepareTestDir( @@ -131,15 +138,18 @@ func prepareTestDir( return createFixture(test, paths, rootDir) } -func buildLazygit() error { +func buildLazygit(raceDetector bool) error { // // TODO: remove this line! // // skipping this because I'm not making changes to the app code atm. // return nil + args := []string{"go", "build"} + if raceDetector { + args = append(args, "-race") + } + args = append(args, "-o", tempLazygitPath(), filepath.FromSlash("pkg/integration/clients/injector/main.go")) osCommand := oscommands.NewDummyOSCommand() - return osCommand.Cmd.New([]string{ - "go", "build", "-o", tempLazygitPath(), filepath.FromSlash("pkg/integration/clients/injector/main.go"), - }).Run() + return osCommand.Cmd.New(args).Run() } func createFixture(test *IntegrationTest, paths Paths, rootDir string) error { @@ -202,6 +212,9 @@ func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, sandb if waitForDebugger { cmdObj.AddEnvVars("WAIT_FOR_DEBUGGER=true") } + // Set a race detector log path only to avoid spamming the terminal with the + // logs. We are not showing this anywhere yet. + cmdObj.AddEnvVars(fmt.Sprintf("GORACE=log_path=%s", raceDetectorLogsPath())) if test.ExtraEnvVars() != nil { for key, value := range test.ExtraEnvVars() { cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", key, value)) @@ -221,6 +234,10 @@ func tempLazygitPath() string { return filepath.Join("/tmp", "lazygit", "test_lazygit") } +func raceDetectorLogsPath() string { + return filepath.Join("/tmp", "lazygit", "race_log") +} + func findOrCreateDir(path string) { _, err := os.Stat(path) if err != nil {