Provide a simple way to debug an integration test
This commit is contained in:
@@ -61,6 +61,15 @@ If you've opened an integration test file in your editor you can run that file b
|
||||
The test will run in a VSCode terminal:
|
||||

|
||||
|
||||
### Debugging tests
|
||||
|
||||
Debugging an integration test is possible in two ways:
|
||||
|
||||
1. Use the -debug option of the integration test runner's "cli" command, e.g. `go run cmd/integration_test/main.go cli -debug tag/reset.go`
|
||||
2. Select a test in the "tui" runner and hit "d" to debug it.
|
||||
|
||||
In both cases the test runner will print to the console that it is waiting for a debugger to attach, so now you need to tell your debugger to attach to a running process with the name "test_lazygit". If you are using Visual Studio Code, an easy way to do that is to use the "Attach to integration test runner" debug configuration. The test runner will resume automatically when it detects that a debugger was attached. Don't forget to set a breakpoint in the code that you want to step through, otherwise the test will just finish (i.e. it doesn't stop in the debugger automatically).
|
||||
|
||||
### Sandbox mode
|
||||
|
||||
Say you want to do a manual test of how lazygit handles merge-conflicts, but you can't be bothered actually finding a way to create merge conflicts in a repo. To make your life easier, you can simply run a merge-conflicts test in sandbox mode, meaning the setup step is run for you, and then instead of the test driving the lazygit session, you're allowed to drive it yourself.
|
||||
|
||||
@@ -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) {
|
||||
func RunCLI(testNames []string, slow bool, sandbox bool, waitForDebugger bool) {
|
||||
inputDelay := tryConvert(os.Getenv("INPUT_DELAY"), 0)
|
||||
if slow {
|
||||
inputDelay = SLOW_INPUT_DELAY
|
||||
@@ -35,6 +35,7 @@ func RunCLI(testNames []string, slow bool, sandbox bool) {
|
||||
runCmdInTerminal,
|
||||
runAndPrintFatalError,
|
||||
sandbox,
|
||||
waitForDebugger,
|
||||
inputDelay,
|
||||
1,
|
||||
)
|
||||
|
||||
@@ -52,6 +52,7 @@ func TestIntegration(t *testing.T) {
|
||||
})
|
||||
},
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
// Allow two attempts at each test to get around flakiness
|
||||
2,
|
||||
|
||||
@@ -3,12 +3,14 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/app"
|
||||
"github.com/jesseduffield/lazygit/pkg/app/daemon"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests"
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
"github.com/mitchellh/go-ps"
|
||||
)
|
||||
|
||||
// The purpose of this program is to run lazygit with an integration test passed in.
|
||||
@@ -29,6 +31,15 @@ func main() {
|
||||
|
||||
integrationTest := getIntegrationTest()
|
||||
|
||||
if os.Getenv("WAIT_FOR_DEBUGGER") != "" {
|
||||
println("Waiting for debugger to attach...")
|
||||
for !isDebuggerAttached() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
println("Debugger attached, continuing")
|
||||
}
|
||||
|
||||
app.Start(dummyBuildInfo, integrationTest)
|
||||
}
|
||||
|
||||
@@ -56,3 +67,21 @@ func getIntegrationTest() integrationTypes.IntegrationTest {
|
||||
|
||||
panic("Could not find integration test with name: " + integrationTestName)
|
||||
}
|
||||
|
||||
// Returns whether we are running under a debugger. It uses a heuristic to find
|
||||
// out: when using dlv, it starts a debugserver executable (which is part of
|
||||
// lldb), and the debuggee becomes a child process of that. So if the name of
|
||||
// our parent process is "debugserver", we run under a debugger. This works even
|
||||
// if the parent process used to be the shell and you then attach to the running
|
||||
// executable.
|
||||
//
|
||||
// On Mac this works with VS Code, with the Jetbrains Goland IDE, and when using
|
||||
// dlv attach in a terminal. I have not been able to verify that it works on
|
||||
// other platforms, it may have to be adapted there.
|
||||
func isDebuggerAttached() bool {
|
||||
process, err := ps.FindProcess(os.Getppid())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return process.Executable() == "debugserver"
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func RunTUI() {
|
||||
return nil
|
||||
}
|
||||
|
||||
suspendAndRunTest(currentTest, true, 0)
|
||||
suspendAndRunTest(currentTest, true, false, 0)
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
@@ -98,7 +98,7 @@ func RunTUI() {
|
||||
return nil
|
||||
}
|
||||
|
||||
suspendAndRunTest(currentTest, false, 0)
|
||||
suspendAndRunTest(currentTest, false, false, 0)
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
@@ -111,7 +111,20 @@ func RunTUI() {
|
||||
return nil
|
||||
}
|
||||
|
||||
suspendAndRunTest(currentTest, false, SLOW_INPUT_DELAY)
|
||||
suspendAndRunTest(currentTest, false, false, SLOW_INPUT_DELAY)
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
if err := g.SetKeybinding("list", 'd', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||
currentTest := app.getCurrentTest()
|
||||
if currentTest == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
suspendAndRunTest(currentTest, false, true, 0)
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
@@ -271,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, inputDelay int) {
|
||||
func suspendAndRunTest(test *components.IntegrationTest, sandbox bool, waitForDebugger bool, inputDelay int) {
|
||||
if err := gocui.Screen.Suspend(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
runTuiTest(test, sandbox, inputDelay)
|
||||
runTuiTest(test, sandbox, waitForDebugger, inputDelay)
|
||||
|
||||
fmt.Fprintf(os.Stdout, "\n%s", style.FgGreen.Sprint("press enter to return"))
|
||||
fmt.Scanln() // wait for enter press
|
||||
@@ -337,7 +350,7 @@ func (self *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, t: run test slow, s: sandbox, o: open test file, shift+o: open test snapshot directory, forward-slash: filter")
|
||||
fmt.Fprintln(keybindingsView, "up/down: navigate, enter: run test, t: run test slow, s: sandbox, d: debug test, o: open test file, shift+o: open test snapshot directory, forward-slash: filter")
|
||||
}
|
||||
|
||||
editorView, err := g.SetViewBeneath("editor", "keybindings", editorViewHeight)
|
||||
@@ -371,13 +384,14 @@ func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func runTuiTest(test *components.IntegrationTest, sandbox bool, inputDelay int) {
|
||||
func runTuiTest(test *components.IntegrationTest, sandbox bool, waitForDebugger bool, inputDelay int) {
|
||||
err := components.RunTests(
|
||||
[]*components.IntegrationTest{test},
|
||||
log.Printf,
|
||||
runCmdInTerminal,
|
||||
runAndPrintError,
|
||||
sandbox,
|
||||
waitForDebugger,
|
||||
inputDelay,
|
||||
1,
|
||||
)
|
||||
|
||||
@@ -29,6 +29,7 @@ func RunTests(
|
||||
runCmd func(cmd *exec.Cmd) error,
|
||||
testWrapper func(test *IntegrationTest, f func() error),
|
||||
sandbox bool,
|
||||
waitForDebugger bool,
|
||||
inputDelay int,
|
||||
maxAttempts int,
|
||||
) error {
|
||||
@@ -58,7 +59,7 @@ func RunTests(
|
||||
)
|
||||
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
err := runTest(test, paths, projectRootDir, logf, runCmd, sandbox, inputDelay, gitVersion)
|
||||
err := runTest(test, paths, projectRootDir, logf, runCmd, sandbox, waitForDebugger, inputDelay, gitVersion)
|
||||
if err != nil {
|
||||
if i == maxAttempts-1 {
|
||||
return err
|
||||
@@ -83,6 +84,7 @@ func runTest(
|
||||
logf func(format string, formatArgs ...interface{}),
|
||||
runCmd func(cmd *exec.Cmd) error,
|
||||
sandbox bool,
|
||||
waitForDebugger bool,
|
||||
inputDelay int,
|
||||
gitVersion *git_commands.GitVersion,
|
||||
) error {
|
||||
@@ -100,7 +102,7 @@ func runTest(
|
||||
return err
|
||||
}
|
||||
|
||||
cmd, err := getLazygitCommand(test, paths, projectRootDir, sandbox, inputDelay)
|
||||
cmd, err := getLazygitCommand(test, paths, projectRootDir, sandbox, waitForDebugger, inputDelay)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -165,7 +167,7 @@ func getGitVersion() (*git_commands.GitVersion, error) {
|
||||
return git_commands.ParseGitVersion(versionStr)
|
||||
}
|
||||
|
||||
func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, sandbox bool, inputDelay int) (*exec.Cmd, error) {
|
||||
func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, sandbox bool, waitForDebugger bool, inputDelay int) (*exec.Cmd, error) {
|
||||
osCommand := oscommands.NewDummyOSCommand()
|
||||
|
||||
err := os.RemoveAll(paths.Config())
|
||||
@@ -197,6 +199,9 @@ func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, sandb
|
||||
if sandbox {
|
||||
cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", SANDBOX_ENV_VAR, "true"))
|
||||
}
|
||||
if waitForDebugger {
|
||||
cmdObj.AddEnvVars("WAIT_FOR_DEBUGGER=true")
|
||||
}
|
||||
if test.ExtraEnvVars() != nil {
|
||||
for key, value := range test.ExtraEnvVars() {
|
||||
cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", key, value))
|
||||
|
||||
Reference in New Issue
Block a user