Skip to content

Commit

Permalink
Provide a simple way to debug an integration test
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanhaller committed Sep 11, 2023
1 parent 28d12e4 commit b6c892a
Show file tree
Hide file tree
Showing 22 changed files with 975 additions and 12 deletions.
12 changes: 12 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@
"hideSystemGoroutines": true,
"console": "integratedTerminal",
},
{
// To use this, first start an integration test with the "cli" runner and
// use the -debug option; e.g.
// $ make integration-test-cli -- -debug tag/reset.go
"name": "Attach to integration test runner",
"type": "go",
"request": "attach",
"mode": "local",
"processId": "test_lazygit",
"hideSystemGoroutines": true,
"console": "integratedTerminal",
},
],
"compounds": [
{
Expand Down
6 changes: 5 additions & 1 deletion cmd/integration_test/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func main() {
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" {
Expand All @@ -46,10 +47,13 @@ func main() {
} 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)
clients.RunCLI(testNames, slow, sandbox, waitForDebugger)
case "tui":
clients.RunTUI()
default:
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-runewidth v0.0.15
github.com/mgutz/str v1.2.0
github.com/mitchellh/go-ps v1.0.0
github.com/pmezard/go-difflib v1.0.0
github.com/sahilm/fuzzy v0.1.0
github.com/samber/lo v1.31.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand Down
9 changes: 9 additions & 0 deletions pkg/integration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
![image](https://user-images.githubusercontent.com/8456633/201500446-b87abf11-9653-438f-8a9a-e0bf8abdb7ee.png)

### 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.
Expand Down
3 changes: 2 additions & 1 deletion pkg/integration/clients/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -35,6 +35,7 @@ func RunCLI(testNames []string, slow bool, sandbox bool) {
runCmdInTerminal,
runAndPrintFatalError,
sandbox,
waitForDebugger,
inputDelay,
1,
)
Expand Down
1 change: 1 addition & 0 deletions pkg/integration/clients/go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func TestIntegration(t *testing.T) {
})
},
false,
false,
0,
// Allow two attempts at each test to get around flakiness
2,
Expand Down
29 changes: 29 additions & 0 deletions pkg/integration/clients/injector/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
}

Expand Down Expand Up @@ -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"
}
28 changes: 21 additions & 7 deletions pkg/integration/clients/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func RunTUI() {
return nil
}

suspendAndRunTest(currentTest, true, 0)
suspendAndRunTest(currentTest, true, false, 0)

return nil
}); err != nil {
Expand All @@ -98,7 +98,7 @@ func RunTUI() {
return nil
}

suspendAndRunTest(currentTest, false, 0)
suspendAndRunTest(currentTest, false, false, 0)

return nil
}); err != nil {
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
)
Expand Down
11 changes: 8 additions & 3 deletions pkg/integration/components/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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))
Expand Down
1 change: 1 addition & 0 deletions vendor/github.com/mitchellh/go-ps/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions vendor/github.com/mitchellh/go-ps/LICENSE.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions vendor/github.com/mitchellh/go-ps/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions vendor/github.com/mitchellh/go-ps/Vagrantfile

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b6c892a

Please sign in to comment.