github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/cmd/dlv/dlv_test.go (about) 1 package main_test 2 3 import ( 4 "bufio" 5 "bytes" 6 "flag" 7 "fmt" 8 "go/ast" 9 "go/token" 10 "go/types" 11 "io" 12 "io/ioutil" 13 "net" 14 "os" 15 "os/exec" 16 "os/user" 17 "path/filepath" 18 "runtime" 19 "strconv" 20 "strings" 21 "testing" 22 "time" 23 24 "github.com/go-delve/delve/pkg/goversion" 25 protest "github.com/go-delve/delve/pkg/proc/test" 26 "github.com/go-delve/delve/pkg/terminal" 27 "github.com/go-delve/delve/service/dap" 28 "github.com/go-delve/delve/service/dap/daptest" 29 "github.com/go-delve/delve/service/rpc2" 30 godap "github.com/google/go-dap" 31 "golang.org/x/tools/go/packages" 32 ) 33 34 var testBackend string 35 var ldFlags string 36 37 func init() { 38 ldFlags = os.Getenv("CGO_LDFLAGS") 39 } 40 41 func TestMain(m *testing.M) { 42 flag.StringVar(&testBackend, "backend", "", "selects backend") 43 flag.Parse() 44 if testBackend == "" { 45 testBackend = os.Getenv("PROCTEST") 46 if testBackend == "" { 47 testBackend = "native" 48 if runtime.GOOS == "darwin" { 49 testBackend = "lldb" 50 } 51 } 52 } 53 os.Exit(protest.RunTestsWithFixtures(m)) 54 } 55 56 func assertNoError(err error, t testing.TB, s string) { 57 t.Helper() 58 if err != nil { 59 _, file, line, _ := runtime.Caller(1) 60 fname := filepath.Base(file) 61 t.Fatalf("failed assertion at %s:%d: %s - %s\n", fname, line, s, err) 62 } 63 } 64 65 func projectRoot() string { 66 wd, err := os.Getwd() 67 if err != nil { 68 panic(err) 69 } 70 71 gopaths := strings.FieldsFunc(os.Getenv("GOPATH"), func(r rune) bool { return r == os.PathListSeparator }) 72 for _, curpath := range gopaths { 73 // Detects "gopath mode" when GOPATH contains several paths ex. "d:\\dir\\gopath;f:\\dir\\gopath2" 74 if strings.Contains(wd, curpath) { 75 return filepath.Join(curpath, "src", "github.com", "go-delve", "delve") 76 } 77 } 78 val, err := exec.Command("go", "list", "-mod=", "-m", "-f", "{{ .Dir }}").Output() 79 if err != nil { 80 panic(err) // the Go tool was tested to work earlier 81 } 82 return strings.TrimSuffix(string(val), "\n") 83 } 84 85 func TestBuild(t *testing.T) { 86 const listenAddr = "127.0.0.1:40573" 87 var err error 88 89 cmd := exec.Command("go", "run", "_scripts/make.go", "build") 90 cmd.Dir = projectRoot() 91 out, err := cmd.CombinedOutput() 92 if err != nil { 93 t.Fatalf("makefile error: %v\noutput %s\n", err, string(out)) 94 } 95 96 dlvbin := filepath.Join(cmd.Dir, "dlv") 97 defer os.Remove(dlvbin) 98 99 fixtures := protest.FindFixturesDir() 100 101 buildtestdir := filepath.Join(fixtures, "buildtest") 102 103 cmd = exec.Command(dlvbin, "debug", "--headless=true", "--listen="+listenAddr, "--api-version=2", "--backend="+testBackend, "--log", "--log-output=debugger,rpc") 104 cmd.Dir = buildtestdir 105 stderr, err := cmd.StderrPipe() 106 assertNoError(err, t, "stderr pipe") 107 defer stderr.Close() 108 109 assertNoError(cmd.Start(), t, "dlv debug") 110 111 scan := bufio.NewScanner(stderr) 112 // wait for the debugger to start 113 for scan.Scan() { 114 text := scan.Text() 115 t.Log(text) 116 if strings.Contains(text, "API server pid = ") { 117 break 118 } 119 } 120 go func() { 121 for scan.Scan() { 122 t.Log(scan.Text()) 123 // keep pipe empty 124 } 125 }() 126 127 client := rpc2.NewClient(listenAddr) 128 state := <-client.Continue() 129 130 if !state.Exited { 131 t.Fatal("Program did not exit") 132 } 133 134 client.Detach(true) 135 cmd.Wait() 136 } 137 138 func testOutput(t *testing.T, dlvbin, output string, delveCmds []string) (stdout, stderr []byte) { 139 var stdoutBuf, stderrBuf bytes.Buffer 140 buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest") 141 142 c := []string{dlvbin, "debug", "--allow-non-terminal-interactive=true"} 143 debugbin := filepath.Join(buildtestdir, "__debug_bin") 144 if output != "" { 145 c = append(c, "--output", output) 146 if filepath.IsAbs(output) { 147 debugbin = output 148 } else { 149 debugbin = filepath.Join(buildtestdir, output) 150 } 151 } 152 cmd := exec.Command(c[0], c[1:]...) 153 cmd.Dir = buildtestdir 154 stdin, err := cmd.StdinPipe() 155 assertNoError(err, t, "stdin pipe") 156 defer stdin.Close() 157 158 cmd.Stdout = &stdoutBuf 159 cmd.Stderr = &stderrBuf 160 161 assertNoError(cmd.Start(), t, "dlv debug with output") 162 163 // Give delve some time to compile and write the binary. 164 foundIt := false 165 for wait := 0; wait < 30; wait++ { 166 _, err = os.Stat(debugbin) 167 if err == nil { 168 foundIt = true 169 break 170 } 171 172 time.Sleep(1 * time.Second) 173 } 174 if !foundIt { 175 t.Errorf("running %q: file not created: %v", delveCmds, err) 176 } 177 178 for _, c := range delveCmds { 179 fmt.Fprintf(stdin, "%s\n", c) 180 } 181 182 // ignore "dlv debug" command error, it returns 183 // errors even after successful debug session. 184 cmd.Wait() 185 stdout, stderr = stdoutBuf.Bytes(), stderrBuf.Bytes() 186 187 _, err = os.Stat(debugbin) 188 if err == nil { 189 // Sometimes delve on Windows can't remove the built binary before 190 // exiting and gets an "Access is denied" error when trying. 191 // See: https://travis-ci.com/go-delve/delve/jobs/296325131) 192 // We have added a delay to gobuild.Remove, but to avoid any test 193 // flakiness, we guard against this failure here as well. 194 if runtime.GOOS != "windows" || !strings.Contains(err.Error(), "Access is denied") { 195 t.Errorf("running %q: file %v was not deleted\nstdout is %q, stderr is %q", delveCmds, debugbin, stdout, stderr) 196 } 197 return 198 } 199 if !os.IsNotExist(err) { 200 t.Errorf("running %q: %v\nstdout is %q, stderr is %q", delveCmds, err, stdout, stderr) 201 return 202 } 203 return 204 } 205 206 func getDlvBin(t *testing.T) string { 207 // In case this was set in the environment 208 // from getDlvBinEBPF lets clear it here so 209 // we can ensure we don't get build errors 210 // depending on the test ordering. 211 os.Setenv("CGO_LDFLAGS", ldFlags) 212 var tags string 213 if runtime.GOOS == "windows" && runtime.GOARCH == "arm64" { 214 tags = "-tags=exp.winarm64" 215 } 216 if runtime.GOOS == "linux" && runtime.GOARCH == "ppc64le" { 217 tags = "-tags=exp.linuxppc64le" 218 } 219 return getDlvBinInternal(t, tags) 220 } 221 222 func getDlvBinEBPF(t *testing.T) string { 223 return getDlvBinInternal(t, "-tags", "ebpf") 224 } 225 226 func getDlvBinInternal(t *testing.T, goflags ...string) string { 227 dlvbin := filepath.Join(t.TempDir(), "dlv.exe") 228 args := append([]string{"build", "-o", dlvbin}, goflags...) 229 args = append(args, "github.com/go-delve/delve/cmd/dlv") 230 231 out, err := exec.Command("go", args...).CombinedOutput() 232 if err != nil { 233 t.Fatalf("go build -o %v github.com/go-delve/delve/cmd/dlv: %v\n%s", dlvbin, err, string(out)) 234 } 235 236 return dlvbin 237 } 238 239 // TestOutput verifies that the debug executable is created in the correct path 240 // and removed after exit. 241 func TestOutput(t *testing.T) { 242 dlvbin := getDlvBin(t) 243 244 for _, output := range []string{"__debug_bin", "myownname", filepath.Join(t.TempDir(), "absolute.path")} { 245 testOutput(t, dlvbin, output, []string{"exit"}) 246 247 const hello = "hello world!" 248 stdout, _ := testOutput(t, dlvbin, output, []string{"continue", "exit"}) 249 if !strings.Contains(string(stdout), hello) { 250 t.Errorf("stdout %q should contain %q", stdout, hello) 251 } 252 } 253 } 254 255 // TestContinue verifies that the debugged executable starts immediately with --continue 256 func TestContinue(t *testing.T) { 257 const listenAddr = "127.0.0.1:40573" 258 259 dlvbin := getDlvBin(t) 260 261 buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest") 262 cmd := exec.Command(dlvbin, "debug", "--headless", "--continue", "--accept-multiclient", "--listen", listenAddr) 263 cmd.Dir = buildtestdir 264 stdout, err := cmd.StdoutPipe() 265 assertNoError(err, t, "stdout pipe") 266 defer stdout.Close() 267 268 assertNoError(cmd.Start(), t, "start headless instance") 269 270 scan := bufio.NewScanner(stdout) 271 // wait for the debugger to start 272 for scan.Scan() { 273 t.Log(scan.Text()) 274 if scan.Text() == "hello world!" { 275 break 276 } 277 } 278 279 // and detach from and kill the headless instance 280 client := rpc2.NewClient(listenAddr) 281 if err := client.Detach(true); err != nil { 282 t.Fatalf("error detaching from headless instance: %v", err) 283 } 284 cmd.Wait() 285 } 286 287 // TestRedirect verifies that redirecting stdin works 288 func TestRedirect(t *testing.T) { 289 const listenAddr = "127.0.0.1:40573" 290 291 dlvbin := getDlvBin(t) 292 293 catfixture := filepath.Join(protest.FindFixturesDir(), "cat.go") 294 cmd := exec.Command(dlvbin, "debug", "--headless", "--continue", "--accept-multiclient", "--listen", listenAddr, "-r", catfixture, catfixture) 295 stdout, err := cmd.StdoutPipe() 296 assertNoError(err, t, "stdout pipe") 297 defer stdout.Close() 298 299 assertNoError(cmd.Start(), t, "start headless instance") 300 301 scan := bufio.NewScanner(stdout) 302 // wait for the debugger to start 303 for scan.Scan() { 304 t.Log(scan.Text()) 305 if scan.Text() == "read \"}\"" { 306 break 307 } 308 } 309 310 // and detach from and kill the headless instance 311 client := rpc2.NewClient(listenAddr) 312 _ = client.Detach(true) 313 cmd.Wait() 314 } 315 316 const checkAutogenDocLongOutput = false 317 318 func checkAutogenDoc(t *testing.T, filename, gencommand string, generated []byte) { 319 saved := slurpFile(t, filepath.Join(projectRoot(), filename)) 320 321 saved = bytes.ReplaceAll(saved, []byte("\r\n"), []byte{'\n'}) 322 generated = bytes.ReplaceAll(generated, []byte("\r\n"), []byte{'\n'}) 323 324 if len(saved) != len(generated) { 325 if checkAutogenDocLongOutput { 326 t.Logf("generated %q saved %q\n", generated, saved) 327 } 328 diffMaybe(t, filename, generated) 329 t.Fatalf("%s: needs to be regenerated; run %s", filename, gencommand) 330 } 331 332 for i := range saved { 333 if saved[i] != generated[i] { 334 if checkAutogenDocLongOutput { 335 t.Logf("generated %q saved %q\n", generated, saved) 336 } 337 diffMaybe(t, filename, generated) 338 t.Fatalf("%s: needs to be regenerated; run %s", filename, gencommand) 339 } 340 } 341 } 342 343 func slurpFile(t *testing.T, filename string) []byte { 344 saved, err := ioutil.ReadFile(filename) 345 if err != nil { 346 t.Fatalf("Could not read %s: %v", filename, err) 347 } 348 return saved 349 } 350 351 func diffMaybe(t *testing.T, filename string, generated []byte) { 352 _, err := exec.LookPath("diff") 353 if err != nil { 354 return 355 } 356 cmd := exec.Command("diff", filename, "-") 357 cmd.Dir = projectRoot() 358 stdin, _ := cmd.StdinPipe() 359 go func() { 360 stdin.Write(generated) 361 stdin.Close() 362 }() 363 out, _ := cmd.CombinedOutput() 364 t.Logf("diff:\n%s", string(out)) 365 } 366 367 // TestGeneratedDoc tests that the autogenerated documentation has been 368 // updated. 369 func TestGeneratedDoc(t *testing.T) { 370 if strings.ToLower(os.Getenv("TRAVIS")) == "true" && runtime.GOOS == "windows" { 371 t.Skip("skipping test on Windows in CI") 372 } 373 if runtime.GOOS == "windows" && runtime.GOARCH == "arm64" { 374 //TODO(qmuntal): investigate further when the Windows ARM64 backend is more stable. 375 t.Skip("skipping test on Windows in CI") 376 } 377 if runtime.GOOS == "linux" && runtime.GOARCH == "ppc64le" { 378 //TODO(alexsaezm): finish CI integration 379 t.Skip("skipping test on Linux/PPC64LE in CI") 380 } 381 // Checks gen-cli-docs.go 382 var generatedBuf bytes.Buffer 383 commands := terminal.DebugCommands(nil) 384 commands.WriteMarkdown(&generatedBuf) 385 checkAutogenDoc(t, "Documentation/cli/README.md", "_scripts/gen-cli-docs.go", generatedBuf.Bytes()) 386 387 // Checks gen-usage-docs.go 388 tempDir := t.TempDir() 389 cmd := exec.Command("go", "run", "_scripts/gen-usage-docs.go", tempDir) 390 cmd.Dir = projectRoot() 391 err := cmd.Run() 392 assertNoError(err, t, "go run _scripts/gen-usage-docs.go") 393 entries, err := ioutil.ReadDir(tempDir) 394 assertNoError(err, t, "ReadDir") 395 for _, doc := range entries { 396 docFilename := "Documentation/usage/" + doc.Name() 397 checkAutogenDoc(t, docFilename, "_scripts/gen-usage-docs.go", slurpFile(t, tempDir+"/"+doc.Name())) 398 } 399 400 runScript := func(args ...string) []byte { 401 a := []string{"run"} 402 a = append(a, args...) 403 cmd := exec.Command("go", a...) 404 cmd.Dir = projectRoot() 405 out, err := cmd.CombinedOutput() 406 if err != nil { 407 t.Fatalf("could not run script %v: %v (output: %q)", args, err, string(out)) 408 } 409 return out 410 } 411 412 checkAutogenDoc(t, "Documentation/backend_test_health.md", "go run _scripts/gen-backend_test_health.go", runScript("_scripts/gen-backend_test_health.go", "-")) 413 checkAutogenDoc(t, "pkg/terminal/starbind/starlark_mapping.go", "'go generate' inside pkg/terminal/starbind", runScript("_scripts/gen-starlark-bindings.go", "go", "-")) 414 checkAutogenDoc(t, "Documentation/cli/starlark.md", "'go generate' inside pkg/terminal/starbind", runScript("_scripts/gen-starlark-bindings.go", "doc/dummy", "Documentation/cli/starlark.md")) 415 checkAutogenDoc(t, "Documentation/faq.md", "'go run _scripts/gen-faq-toc.go Documentation/faq.md Documentation/faq.md'", runScript("_scripts/gen-faq-toc.go", "Documentation/faq.md", "-")) 416 if goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) { 417 checkAutogenDoc(t, "_scripts/rtype-out.txt", "go run _scripts/rtype.go report _scripts/rtype-out.txt", runScript("_scripts/rtype.go", "report")) 418 runScript("_scripts/rtype.go", "check") 419 } 420 } 421 422 func TestExitInInit(t *testing.T) { 423 dlvbin := getDlvBin(t) 424 425 buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest") 426 exitInit := filepath.Join(protest.FindFixturesDir(), "exit.init") 427 cmd := exec.Command(dlvbin, "--init", exitInit, "debug") 428 cmd.Dir = buildtestdir 429 out, err := cmd.CombinedOutput() 430 t.Logf("%q %v\n", string(out), err) 431 // dlv will exit anyway because stdin is not a tty but it will print the 432 // prompt once if the init file didn't call exit successfully. 433 if strings.Contains(string(out), "(dlv)") { 434 t.Fatal("init did not cause dlv to exit") 435 } 436 } 437 438 func getMethods(pkg *types.Package, typename string) map[string]*types.Func { 439 r := make(map[string]*types.Func) 440 mset := types.NewMethodSet(types.NewPointer(pkg.Scope().Lookup(typename).Type())) 441 for i := 0; i < mset.Len(); i++ { 442 fn := mset.At(i).Obj().(*types.Func) 443 r[fn.Name()] = fn 444 } 445 return r 446 } 447 448 func publicMethodOf(decl ast.Decl, receiver string) *ast.FuncDecl { 449 fndecl, isfunc := decl.(*ast.FuncDecl) 450 if !isfunc { 451 return nil 452 } 453 if fndecl.Name.Name[0] >= 'a' && fndecl.Name.Name[0] <= 'z' { 454 return nil 455 } 456 if fndecl.Recv == nil || len(fndecl.Recv.List) != 1 { 457 return nil 458 } 459 starexpr, isstar := fndecl.Recv.List[0].Type.(*ast.StarExpr) 460 if !isstar { 461 return nil 462 } 463 identexpr, isident := starexpr.X.(*ast.Ident) 464 if !isident || identexpr.Name != receiver { 465 return nil 466 } 467 if fndecl.Body == nil { 468 return nil 469 } 470 return fndecl 471 } 472 473 func findCallCall(fndecl *ast.FuncDecl) *ast.CallExpr { 474 for _, stmt := range fndecl.Body.List { 475 var x ast.Expr = nil 476 477 switch s := stmt.(type) { 478 case *ast.AssignStmt: 479 if len(s.Rhs) == 1 { 480 x = s.Rhs[0] 481 } 482 case *ast.ReturnStmt: 483 if len(s.Results) == 1 { 484 x = s.Results[0] 485 } 486 case *ast.ExprStmt: 487 x = s.X 488 } 489 490 callx, iscall := x.(*ast.CallExpr) 491 if !iscall { 492 continue 493 } 494 fun, issel := callx.Fun.(*ast.SelectorExpr) 495 if !issel || fun.Sel.Name != "call" { 496 continue 497 } 498 return callx 499 } 500 return nil 501 } 502 503 func qf(*types.Package) string { 504 return "" 505 } 506 507 func TestTypecheckRPC(t *testing.T) { 508 fset := &token.FileSet{} 509 cfg := &packages.Config{ 510 Mode: packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedName | packages.NeedCompiledGoFiles | packages.NeedTypes, 511 Fset: fset, 512 } 513 pkgs, err := packages.Load(cfg, "github.com/go-delve/delve/service/rpc2") 514 if err != nil { 515 t.Fatal(err) 516 } 517 var clientAst *ast.File 518 var serverMethods map[string]*types.Func 519 var info *types.Info 520 packages.Visit(pkgs, func(pkg *packages.Package) bool { 521 if pkg.PkgPath != "github.com/go-delve/delve/service/rpc2" { 522 return true 523 } 524 t.Logf("package found: %v", pkg.PkgPath) 525 serverMethods = getMethods(pkg.Types, "RPCServer") 526 info = pkg.TypesInfo 527 for i := range pkg.Syntax { 528 t.Logf("file %q", pkg.CompiledGoFiles[i]) 529 if strings.HasSuffix(pkg.CompiledGoFiles[i], string(os.PathSeparator)+"client.go") { 530 clientAst = pkg.Syntax[i] 531 break 532 } 533 } 534 return true 535 }, nil) 536 537 errcount := 0 538 539 for _, decl := range clientAst.Decls { 540 fndecl := publicMethodOf(decl, "RPCClient") 541 if fndecl == nil { 542 continue 543 } 544 545 switch fndecl.Name.Name { 546 case "Continue", "Rewind": 547 // wrappers over continueDir 548 continue 549 case "SetReturnValuesLoadConfig", "Disconnect": 550 // support functions 551 continue 552 } 553 554 if fndecl.Name.Name == "Continue" || fndecl.Name.Name == "Rewind" || fndecl.Name.Name == "DirectionCongruentContinue" { 555 // using continueDir 556 continue 557 } 558 559 callx := findCallCall(fndecl) 560 561 if callx == nil { 562 t.Errorf("%s: could not find RPC call", fset.Position(fndecl.Pos())) 563 errcount++ 564 continue 565 } 566 567 if len(callx.Args) != 3 { 568 t.Errorf("%s: wrong number of arguments for RPC call", fset.Position(callx.Pos())) 569 errcount++ 570 continue 571 } 572 573 arg0, arg0islit := callx.Args[0].(*ast.BasicLit) 574 arg1 := callx.Args[1] 575 arg2 := callx.Args[2] 576 if !arg0islit || arg0.Kind != token.STRING { 577 continue 578 } 579 name, _ := strconv.Unquote(arg0.Value) 580 serverMethod := serverMethods[name] 581 if serverMethod == nil { 582 t.Errorf("%s: could not find RPC method %q", fset.Position(callx.Pos()), name) 583 errcount++ 584 continue 585 } 586 587 params := serverMethod.Type().(*types.Signature).Params() 588 589 if a, e := info.TypeOf(arg1), params.At(0).Type(); !types.AssignableTo(a, e) { 590 t.Errorf("%s: wrong type of first argument %s, expected %s", fset.Position(callx.Pos()), types.TypeString(a, qf), types.TypeString(e, qf)) 591 errcount++ 592 continue 593 } 594 595 if !strings.HasSuffix(params.At(1).Type().String(), "/service.RPCCallback") { 596 if a, e := info.TypeOf(arg2), params.At(1).Type(); !types.AssignableTo(a, e) { 597 t.Errorf("%s: wrong type of second argument %s, expected %s", fset.Position(callx.Pos()), types.TypeString(a, qf), types.TypeString(e, qf)) 598 errcount++ 599 continue 600 } 601 } 602 603 if clit, ok := arg1.(*ast.CompositeLit); ok { 604 typ := params.At(0).Type() 605 st := typ.Underlying().(*types.Struct) 606 if len(clit.Elts) != st.NumFields() && types.TypeString(typ, qf) != "DebuggerCommand" { 607 t.Errorf("%s: wrong number of fields in first argument's literal %d, expected %d", fset.Position(callx.Pos()), len(clit.Elts), st.NumFields()) 608 errcount++ 609 continue 610 } 611 } 612 } 613 614 if errcount > 0 { 615 t.Errorf("%d errors", errcount) 616 } 617 } 618 619 // TestDAPCmd verifies that a dap server can be started and shut down. 620 func TestDAPCmd(t *testing.T) { 621 const listenAddr = "127.0.0.1:40575" 622 623 dlvbin := getDlvBin(t) 624 625 cmd := exec.Command(dlvbin, "dap", "--log-output=dap", "--log", "--listen", listenAddr) 626 stdout, err := cmd.StdoutPipe() 627 assertNoError(err, t, "stdout pipe") 628 defer stdout.Close() 629 stderr, err := cmd.StderrPipe() 630 assertNoError(err, t, "stderr pipe") 631 defer stderr.Close() 632 633 assertNoError(cmd.Start(), t, "start dap instance") 634 635 scanOut := bufio.NewScanner(stdout) 636 scanErr := bufio.NewScanner(stderr) 637 // Wait for the debug server to start 638 scanOut.Scan() 639 listening := "DAP server listening at: " + listenAddr 640 if scanOut.Text() != listening { 641 cmd.Process.Kill() // release the port 642 t.Fatalf("Unexpected stdout:\ngot %q\nwant %q", scanOut.Text(), listening) 643 } 644 go func() { 645 for scanErr.Scan() { 646 t.Log(scanErr.Text()) 647 } 648 }() 649 650 // Connect a client and request shutdown. 651 client := daptest.NewClient(listenAddr) 652 client.DisconnectRequest() 653 client.ExpectDisconnectResponse(t) 654 client.ExpectTerminatedEvent(t) 655 _, err = client.ReadMessage() 656 if runtime.GOOS == "windows" { 657 if err == nil { 658 t.Errorf("got %q, want non-nil\n", err) 659 } 660 } else { 661 if err != io.EOF { 662 t.Errorf("got %q, want \"EOF\"\n", err) 663 } 664 } 665 client.Close() 666 cmd.Wait() 667 } 668 669 func newDAPRemoteClient(t *testing.T, addr string, isDlvAttach bool, isMulti bool) *daptest.Client { 670 c := daptest.NewClient(addr) 671 c.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true}) 672 if isDlvAttach || isMulti { 673 c.ExpectCapabilitiesEventSupportTerminateDebuggee(t) 674 } 675 c.ExpectInitializedEvent(t) 676 c.ExpectAttachResponse(t) 677 c.ConfigurationDoneRequest() 678 c.ExpectStoppedEvent(t) 679 c.ExpectConfigurationDoneResponse(t) 680 return c 681 } 682 683 func TestRemoteDAPClient(t *testing.T) { 684 const listenAddr = "127.0.0.1:40576" 685 686 dlvbin := getDlvBin(t) 687 688 buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest") 689 cmd := exec.Command(dlvbin, "debug", "--headless", "--log-output=dap", "--log", "--listen", listenAddr) 690 cmd.Dir = buildtestdir 691 stdout, err := cmd.StdoutPipe() 692 assertNoError(err, t, "stdout pipe") 693 defer stdout.Close() 694 stderr, err := cmd.StderrPipe() 695 assertNoError(err, t, "stderr pipe") 696 defer stderr.Close() 697 assertNoError(cmd.Start(), t, "start headless instance") 698 699 scanOut := bufio.NewScanner(stdout) 700 scanErr := bufio.NewScanner(stderr) 701 // Wait for the debug server to start 702 scanOut.Scan() 703 t.Log(scanOut.Text()) 704 go func() { // Capture logging 705 for scanErr.Scan() { 706 t.Log(scanErr.Text()) 707 } 708 }() 709 710 client := newDAPRemoteClient(t, listenAddr, false, false) 711 client.ContinueRequest(1) 712 client.ExpectContinueResponse(t) 713 client.ExpectTerminatedEvent(t) 714 715 client.DisconnectRequest() 716 client.ExpectOutputEventProcessExited(t, 0) 717 client.ExpectOutputEventDetaching(t) 718 client.ExpectDisconnectResponse(t) 719 client.ExpectTerminatedEvent(t) 720 if _, err := client.ReadMessage(); err == nil { 721 t.Error("expected read error upon shutdown") 722 } 723 client.Close() 724 cmd.Wait() 725 } 726 727 func closeDAPRemoteMultiClient(t *testing.T, c *daptest.Client, expectStatus string) { 728 c.DisconnectRequest() 729 c.ExpectOutputEventClosingClient(t, expectStatus) 730 c.ExpectDisconnectResponse(t) 731 c.ExpectTerminatedEvent(t) 732 c.Close() 733 time.Sleep(10 * time.Millisecond) 734 } 735 736 func TestRemoteDAPClientMulti(t *testing.T) { 737 const listenAddr = "127.0.0.1:40577" 738 739 dlvbin := getDlvBin(t) 740 741 buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest") 742 cmd := exec.Command(dlvbin, "debug", "--headless", "--accept-multiclient", "--log-output=debugger", "--log", "--listen", listenAddr) 743 cmd.Dir = buildtestdir 744 stdout, err := cmd.StdoutPipe() 745 assertNoError(err, t, "stdout pipe") 746 defer stdout.Close() 747 stderr, err := cmd.StderrPipe() 748 assertNoError(err, t, "stderr pipe") 749 defer stderr.Close() 750 assertNoError(cmd.Start(), t, "start headless instance") 751 752 scanOut := bufio.NewScanner(stdout) 753 scanErr := bufio.NewScanner(stderr) 754 // Wait for the debug server to start 755 scanOut.Scan() 756 t.Log(scanOut.Text()) 757 go func() { // Capture logging 758 for scanErr.Scan() { 759 t.Log(scanErr.Text()) 760 } 761 }() 762 763 // Client 0 connects but with the wrong attach request 764 dapclient0 := daptest.NewClient(listenAddr) 765 dapclient0.AttachRequest(map[string]interface{}{"mode": "local"}) 766 dapclient0.ExpectErrorResponse(t) 767 768 // Client 1 connects and continues to main.main 769 dapclient := newDAPRemoteClient(t, listenAddr, false, true) 770 dapclient.SetFunctionBreakpointsRequest([]godap.FunctionBreakpoint{{Name: "main.main"}}) 771 dapclient.ExpectSetFunctionBreakpointsResponse(t) 772 dapclient.ContinueRequest(1) 773 dapclient.ExpectContinueResponse(t) 774 dapclient.ExpectStoppedEvent(t) 775 dapclient.CheckStopLocation(t, 1, "main.main", 5) 776 closeDAPRemoteMultiClient(t, dapclient, "halted") 777 778 // Client 2 reconnects at main.main and continues to process exit 779 dapclient2 := newDAPRemoteClient(t, listenAddr, false, true) 780 dapclient2.CheckStopLocation(t, 1, "main.main", 5) 781 dapclient2.ContinueRequest(1) 782 dapclient2.ExpectContinueResponse(t) 783 dapclient2.ExpectTerminatedEvent(t) 784 closeDAPRemoteMultiClient(t, dapclient2, "exited") 785 786 // Attach to exited processes is an error 787 dapclient3 := daptest.NewClient(listenAddr) 788 dapclient3.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true}) 789 dapclient3.ExpectErrorResponseWith(t, dap.FailedToAttach, `Process \d+ has exited with status 0`, true) 790 closeDAPRemoteMultiClient(t, dapclient3, "exited") 791 792 // But rpc clients can still connect and restart 793 rpcclient := rpc2.NewClient(listenAddr) 794 if _, err := rpcclient.Restart(false); err != nil { 795 t.Errorf("error restarting with rpc client: %v", err) 796 } 797 if err := rpcclient.Detach(true); err != nil { 798 t.Fatalf("error detaching from headless instance: %v", err) 799 } 800 cmd.Wait() 801 } 802 803 func TestRemoteDAPClientAfterContinue(t *testing.T) { 804 const listenAddr = "127.0.0.1:40578" 805 806 dlvbin := getDlvBin(t) 807 808 fixture := protest.BuildFixture("loopprog", 0) 809 cmd := exec.Command(dlvbin, "exec", fixture.Path, "--headless", "--continue", "--accept-multiclient", "--log-output=debugger,dap", "--log", "--listen", listenAddr) 810 stdout, err := cmd.StdoutPipe() 811 assertNoError(err, t, "stdout pipe") 812 defer stdout.Close() 813 stderr, err := cmd.StderrPipe() 814 assertNoError(err, t, "stderr pipe") 815 defer stderr.Close() 816 assertNoError(cmd.Start(), t, "start headless instance") 817 818 scanOut := bufio.NewScanner(stdout) 819 scanErr := bufio.NewScanner(stderr) 820 // Wait for the debug server to start 821 scanOut.Scan() // "API server listening..."" 822 t.Log(scanOut.Text()) 823 // Wait for the program to start 824 scanOut.Scan() // "past main" 825 t.Log(scanOut.Text()) 826 827 go func() { // Capture logging 828 for scanErr.Scan() { 829 text := scanErr.Text() 830 if strings.Contains(text, "Internal Error") { 831 t.Error("ERROR", text) 832 } else { 833 t.Log(text) 834 } 835 } 836 }() 837 838 c := newDAPRemoteClient(t, listenAddr, false, true) 839 c.ContinueRequest(1) 840 c.ExpectContinueResponse(t) 841 c.DisconnectRequest() 842 c.ExpectOutputEventClosingClient(t, "running") 843 c.ExpectDisconnectResponse(t) 844 c.ExpectTerminatedEvent(t) 845 c.Close() 846 847 c = newDAPRemoteClient(t, listenAddr, false, true) 848 c.DisconnectRequestWithKillOption(true) 849 c.ExpectOutputEventDetachingKill(t) 850 c.ExpectDisconnectResponse(t) 851 c.ExpectTerminatedEvent(t) 852 if _, err := c.ReadMessage(); err == nil { 853 t.Error("expected read error upon shutdown") 854 } 855 c.Close() 856 cmd.Wait() 857 } 858 859 // TestDAPCmdWithClient tests dlv dap --client-addr can be started and shut down. 860 func TestDAPCmdWithClient(t *testing.T) { 861 listener, err := net.Listen("tcp", ":0") 862 if err != nil { 863 t.Fatalf("cannot setup listener required for testing: %v", err) 864 } 865 defer listener.Close() 866 867 dlvbin := getDlvBin(t) 868 869 cmd := exec.Command(dlvbin, "dap", "--log-output=dap", "--log", "--client-addr", listener.Addr().String()) 870 buf := &bytes.Buffer{} 871 cmd.Stdin = buf 872 cmd.Stdout = buf 873 assertNoError(cmd.Start(), t, "start dlv dap process with --client-addr flag") 874 875 // Wait for the connection. 876 conn, err := listener.Accept() 877 if err != nil { 878 cmd.Process.Kill() // release the port 879 t.Fatalf("Failed to get connection: %v", err) 880 } 881 t.Log("dlv dap process dialed in successfully") 882 883 client := daptest.NewClientFromConn(conn) 884 client.InitializeRequest() 885 client.ExpectInitializeResponse(t) 886 887 // Close the connection. 888 if err := conn.Close(); err != nil { 889 cmd.Process.Kill() 890 t.Fatalf("Failed to get connection: %v", err) 891 } 892 893 // Connection close should trigger dlv-reverse command's normal exit. 894 if err := cmd.Wait(); err != nil { 895 cmd.Process.Kill() 896 t.Fatalf("command failed: %v\n%s\n%v", err, buf.Bytes(), cmd.Process.Pid) 897 } 898 } 899 900 func TestTrace(t *testing.T) { 901 dlvbin := getDlvBin(t) 902 903 expected := []byte("> goroutine(1): main.foo(99, 9801)\n>> goroutine(1): => (9900)\n") 904 905 fixtures := protest.FindFixturesDir() 906 cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "issue573.go"), "foo") 907 rdr, err := cmd.StderrPipe() 908 assertNoError(err, t, "stderr pipe") 909 defer rdr.Close() 910 911 cmd.Dir = filepath.Join(fixtures, "buildtest") 912 913 assertNoError(cmd.Start(), t, "running trace") 914 915 output, err := ioutil.ReadAll(rdr) 916 assertNoError(err, t, "ReadAll") 917 918 if !bytes.Contains(output, expected) { 919 t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output)) 920 } 921 cmd.Wait() 922 } 923 924 func TestTrace2(t *testing.T) { 925 dlvbin := getDlvBin(t) 926 927 expected := []byte("> goroutine(1): main.callme(2)\n>> goroutine(1): => (4)\n") 928 929 fixtures := protest.FindFixturesDir() 930 cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "traceprog.go"), "callme") 931 rdr, err := cmd.StderrPipe() 932 assertNoError(err, t, "stderr pipe") 933 defer rdr.Close() 934 935 cmd.Dir = filepath.Join(fixtures, "buildtest") 936 937 assertNoError(cmd.Start(), t, "running trace") 938 939 output, err := ioutil.ReadAll(rdr) 940 assertNoError(err, t, "ReadAll") 941 942 if !bytes.Contains(output, expected) { 943 t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output)) 944 } 945 assertNoError(cmd.Wait(), t, "cmd.Wait()") 946 } 947 948 func TestTraceMultipleGoroutines(t *testing.T) { 949 dlvbin := getDlvBin(t) 950 951 // TODO(derekparker) this test has to be a bit vague to avoid flakyness. 952 // I think a future improvement could be to use regexp captures to match the 953 // goroutine IDs at function entry and exit. 954 expected := []byte("main.callme(0, \"five\")\n") 955 expected2 := []byte("=> (0)\n") 956 957 fixtures := protest.FindFixturesDir() 958 cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "goroutines-trace.go"), "callme") 959 rdr, err := cmd.StderrPipe() 960 assertNoError(err, t, "stderr pipe") 961 defer rdr.Close() 962 963 cmd.Dir = filepath.Join(fixtures, "buildtest") 964 965 assertNoError(cmd.Start(), t, "running trace") 966 967 output, err := ioutil.ReadAll(rdr) 968 assertNoError(err, t, "ReadAll") 969 970 if !bytes.Contains(output, expected) { 971 t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output)) 972 } 973 if !bytes.Contains(output, expected2) { 974 t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output)) 975 } 976 cmd.Wait() 977 } 978 979 func TestTracePid(t *testing.T) { 980 if runtime.GOOS == "linux" { 981 bs, _ := ioutil.ReadFile("/proc/sys/kernel/yama/ptrace_scope") 982 if bs == nil || strings.TrimSpace(string(bs)) != "0" { 983 t.Logf("can not run TestAttachDetach: %v\n", bs) 984 return 985 } 986 } 987 988 dlvbin := getDlvBin(t) 989 990 expected := []byte("goroutine(1): main.A()\n>> goroutine(1): => ()\n") 991 992 // make process run 993 fix := protest.BuildFixture("issue2023", 0) 994 targetCmd := exec.Command(fix.Path) 995 assertNoError(targetCmd.Start(), t, "execute issue2023") 996 997 if targetCmd.Process == nil || targetCmd.Process.Pid == 0 { 998 t.Fatal("expected target process running") 999 } 1000 defer targetCmd.Process.Kill() 1001 1002 // dlv attach the process by pid 1003 cmd := exec.Command(dlvbin, "trace", "-p", strconv.Itoa(targetCmd.Process.Pid), "main.A") 1004 rdr, err := cmd.StderrPipe() 1005 assertNoError(err, t, "stderr pipe") 1006 defer rdr.Close() 1007 1008 assertNoError(cmd.Start(), t, "running trace") 1009 1010 output, err := ioutil.ReadAll(rdr) 1011 assertNoError(err, t, "ReadAll") 1012 1013 if !bytes.Contains(output, expected) { 1014 t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output)) 1015 } 1016 1017 cmd.Wait() 1018 } 1019 1020 func TestTraceBreakpointExists(t *testing.T) { 1021 dlvbin := getDlvBin(t) 1022 1023 fixtures := protest.FindFixturesDir() 1024 // We always set breakpoints on some runtime functions at startup, so this would return with 1025 // a breakpoints exists error. 1026 // TODO: Perhaps we shouldn't be setting these default breakpoints in trace mode, however. 1027 cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "issue573.go"), "runtime.*") 1028 rdr, err := cmd.StderrPipe() 1029 assertNoError(err, t, "stderr pipe") 1030 defer rdr.Close() 1031 1032 cmd.Dir = filepath.Join(fixtures, "buildtest") 1033 1034 assertNoError(cmd.Start(), t, "running trace") 1035 1036 defer cmd.Wait() 1037 1038 output, err := ioutil.ReadAll(rdr) 1039 assertNoError(err, t, "ReadAll") 1040 1041 if bytes.Contains(output, []byte("Breakpoint exists")) { 1042 t.Fatal("Breakpoint exists errors should be ignored") 1043 } 1044 } 1045 1046 func TestTracePrintStack(t *testing.T) { 1047 dlvbin := getDlvBin(t) 1048 1049 fixtures := protest.FindFixturesDir() 1050 cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), "--stack", "2", filepath.Join(fixtures, "issue573.go"), "foo") 1051 rdr, err := cmd.StderrPipe() 1052 assertNoError(err, t, "stderr pipe") 1053 defer rdr.Close() 1054 1055 cmd.Dir = filepath.Join(fixtures, "buildtest") 1056 assertNoError(cmd.Start(), t, "running trace") 1057 1058 defer cmd.Wait() 1059 1060 output, err := ioutil.ReadAll(rdr) 1061 assertNoError(err, t, "ReadAll") 1062 1063 if !bytes.Contains(output, []byte("Stack:")) && !bytes.Contains(output, []byte("main.main")) { 1064 t.Fatal("stacktrace not printed") 1065 } 1066 } 1067 1068 func TestTraceEBPF(t *testing.T) { 1069 if os.Getenv("CI") == "true" { 1070 t.Skip("cannot run test in CI, requires kernel compiled with btf support") 1071 } 1072 if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { 1073 t.Skip("not implemented on non linux/amd64 systems") 1074 } 1075 if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) { 1076 t.Skip("requires at least Go 1.16 to run test") 1077 } 1078 usr, err := user.Current() 1079 if err != nil { 1080 t.Fatal(err) 1081 } 1082 if usr.Uid != "0" { 1083 t.Skip("test must be run as root") 1084 } 1085 1086 dlvbin := getDlvBinEBPF(t) 1087 1088 expected := []byte("> (1) main.foo(99, 9801)\n=> \"9900\"") 1089 1090 fixtures := protest.FindFixturesDir() 1091 cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "issue573.go"), "foo") 1092 rdr, err := cmd.StderrPipe() 1093 assertNoError(err, t, "stderr pipe") 1094 defer rdr.Close() 1095 1096 assertNoError(cmd.Start(), t, "running trace") 1097 1098 output, err := ioutil.ReadAll(rdr) 1099 assertNoError(err, t, "ReadAll") 1100 1101 cmd.Wait() 1102 if !bytes.Contains(output, expected) { 1103 t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output)) 1104 } 1105 } 1106 1107 func TestTraceEBPF2(t *testing.T) { 1108 if os.Getenv("CI") == "true" { 1109 t.Skip("cannot run test in CI, requires kernel compiled with btf support") 1110 } 1111 if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { 1112 t.Skip("not implemented on non linux/amd64 systems") 1113 } 1114 if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) { 1115 t.Skip("requires at least Go 1.16 to run test") 1116 } 1117 usr, err := user.Current() 1118 if err != nil { 1119 t.Fatal(err) 1120 } 1121 if usr.Uid != "0" { 1122 t.Skip("test must be run as root") 1123 } 1124 1125 dlvbin := getDlvBinEBPF(t) 1126 1127 expected := []byte(`> (1) main.callme(10) 1128 > (1) main.callme(9) 1129 > (1) main.callme(8) 1130 > (1) main.callme(7) 1131 > (1) main.callme(6) 1132 > (1) main.callme(5) 1133 > (1) main.callme(4) 1134 > (1) main.callme(3) 1135 > (1) main.callme(2) 1136 > (1) main.callme(1) 1137 > (1) main.callme(0) 1138 => "100" 1139 => "100" 1140 => "100" 1141 => "100" 1142 => "100" 1143 => "100" 1144 => "100" 1145 => "100" 1146 => "100" 1147 => "100" 1148 => "100"`) 1149 1150 fixtures := protest.FindFixturesDir() 1151 cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "ebpf_trace.go"), "main.callme") 1152 rdr, err := cmd.StderrPipe() 1153 assertNoError(err, t, "stderr pipe") 1154 defer rdr.Close() 1155 1156 assertNoError(cmd.Start(), t, "running trace") 1157 1158 output, err := ioutil.ReadAll(rdr) 1159 assertNoError(err, t, "ReadAll") 1160 1161 cmd.Wait() 1162 if !bytes.Contains(output, expected) { 1163 t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output)) 1164 } 1165 } 1166 1167 func TestTraceEBPF3(t *testing.T) { 1168 if os.Getenv("CI") == "true" { 1169 t.Skip("cannot run test in CI, requires kernel compiled with btf support") 1170 } 1171 if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { 1172 t.Skip("not implemented on non linux/amd64 systems") 1173 } 1174 if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) { 1175 t.Skip("requires at least Go 1.16 to run test") 1176 } 1177 usr, err := user.Current() 1178 if err != nil { 1179 t.Fatal(err) 1180 } 1181 if usr.Uid != "0" { 1182 t.Skip("test must be run as root") 1183 } 1184 1185 dlvbin := getDlvBinEBPF(t) 1186 1187 expected := []byte(`> (1) main.tracedFunction(0) 1188 > (1) main.tracedFunction(1) 1189 > (1) main.tracedFunction(2) 1190 > (1) main.tracedFunction(3) 1191 > (1) main.tracedFunction(4) 1192 > (1) main.tracedFunction(5) 1193 > (1) main.tracedFunction(6) 1194 > (1) main.tracedFunction(7) 1195 > (1) main.tracedFunction(8) 1196 > (1) main.tracedFunction(9)`) 1197 1198 fixtures := protest.FindFixturesDir() 1199 cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "ebpf_trace2.go"), "main.traced") 1200 rdr, err := cmd.StderrPipe() 1201 assertNoError(err, t, "stderr pipe") 1202 defer rdr.Close() 1203 1204 assertNoError(cmd.Start(), t, "running trace") 1205 1206 output, err := ioutil.ReadAll(rdr) 1207 assertNoError(err, t, "ReadAll") 1208 1209 cmd.Wait() 1210 if !bytes.Contains(output, expected) { 1211 t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output)) 1212 } 1213 } 1214 1215 func TestTraceEBPF4(t *testing.T) { 1216 if os.Getenv("CI") == "true" { 1217 t.Skip("cannot run test in CI, requires kernel compiled with btf support") 1218 } 1219 if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { 1220 t.Skip("not implemented on non linux/amd64 systems") 1221 } 1222 if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 16) { 1223 t.Skip("requires at least Go 1.16 to run test") 1224 } 1225 usr, err := user.Current() 1226 if err != nil { 1227 t.Fatal(err) 1228 } 1229 if usr.Uid != "0" { 1230 t.Skip("test must be run as root") 1231 } 1232 1233 dlvbin := getDlvBinEBPF(t) 1234 1235 expected := []byte(`> (1) main.tracedFunction(0, true, 97) 1236 > (1) main.tracedFunction(1, false, 98) 1237 > (1) main.tracedFunction(2, false, 99) 1238 > (1) main.tracedFunction(3, false, 100) 1239 > (1) main.tracedFunction(4, false, 101) 1240 > (1) main.tracedFunction(5, true, 102) 1241 > (1) main.tracedFunction(6, false, 103) 1242 > (1) main.tracedFunction(7, false, 104) 1243 > (1) main.tracedFunction(8, false, 105) 1244 > (1) main.tracedFunction(9, false, 106)`) 1245 1246 fixtures := protest.FindFixturesDir() 1247 cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "ebpf_trace3.go"), "main.traced") 1248 rdr, err := cmd.StderrPipe() 1249 assertNoError(err, t, "stderr pipe") 1250 defer rdr.Close() 1251 1252 assertNoError(cmd.Start(), t, "running trace") 1253 1254 output, err := ioutil.ReadAll(rdr) 1255 assertNoError(err, t, "ReadAll") 1256 1257 cmd.Wait() 1258 if !bytes.Contains(output, expected) { 1259 t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output)) 1260 } 1261 } 1262 1263 func TestDlvTestChdir(t *testing.T) { 1264 dlvbin := getDlvBin(t) 1265 1266 fixtures := protest.FindFixturesDir() 1267 1268 dotest := func(testargs []string) { 1269 t.Helper() 1270 1271 args := []string{"--allow-non-terminal-interactive=true", "test"} 1272 args = append(args, testargs...) 1273 args = append(args, "--", "-test.v") 1274 t.Logf("dlv test %s", args) 1275 cmd := exec.Command(dlvbin, args...) 1276 cmd.Stdin = strings.NewReader("continue\nexit\n") 1277 out, err := cmd.CombinedOutput() 1278 if err != nil { 1279 t.Fatalf("error executing Delve: %v", err) 1280 } 1281 t.Logf("output: %q", out) 1282 1283 p, _ := filepath.Abs(filepath.Join(fixtures, "buildtest")) 1284 tgt := "current directory: " + p 1285 if !strings.Contains(string(out), tgt) { 1286 t.Errorf("output did not contain expected string %q", tgt) 1287 } 1288 } 1289 1290 dotest([]string{filepath.Join(fixtures, "buildtest")}) 1291 files, _ := filepath.Glob(filepath.Join(fixtures, "buildtest", "*.go")) 1292 dotest(files) 1293 } 1294 1295 func TestVersion(t *testing.T) { 1296 dlvbin := getDlvBin(t) 1297 1298 got, err := exec.Command(dlvbin, "version", "-v").CombinedOutput() 1299 if err != nil { 1300 t.Fatalf("error executing `dlv version`: %v\n%s\n", err, got) 1301 } 1302 want1 := []byte("mod\tgithub.com/go-delve/delve") 1303 want2 := []byte("dep\tgithub.com/google/go-dap") 1304 if !bytes.Contains(got, want1) || !bytes.Contains(got, want2) { 1305 t.Errorf("got %s\nwant %v and %v in the output", got, want1, want2) 1306 } 1307 } 1308 1309 func TestStaticcheck(t *testing.T) { 1310 _, err := exec.LookPath("staticcheck") 1311 if err != nil { 1312 t.Skip("staticcheck not installed") 1313 } 1314 // default checks minus SA1019 which complains about deprecated identifiers, which change between versions of Go. 1315 args := []string{"-tests=false", "-checks=all,-SA1019,-ST1000,-ST1003,-ST1016,-S1021,-ST1023", "github.com/go-delve/delve/..."} 1316 // * SA1019 is disabled because new deprecations get added on every version 1317 // of Go making the output of staticcheck inconsistent depending on the 1318 // version of Go used to run it. 1319 // * ST1000,ST1003,ST1016 are disabled in the default 1320 // staticcheck configuration 1321 // * S1021 "Merge variable declaration and assignment" is disabled because 1322 // where we don't do this it is a deliberate style choice. 1323 // * ST1023 "Redundant type in variable declaration" same as S1021. 1324 cmd := exec.Command("staticcheck", args...) 1325 cmd.Dir = projectRoot() 1326 cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=amd64") 1327 out, _ := cmd.CombinedOutput() 1328 checkAutogenDoc(t, "_scripts/staticcheck-out.txt", fmt.Sprintf("staticcheck %s > _scripts/staticcheck-out.txt", strings.Join(args, " ")), out) 1329 } 1330 1331 func TestDefaultBinary(t *testing.T) { 1332 // Check that when delve is run twice in the same directory simultaneously 1333 // it will pick different default output binary paths. 1334 dlvbin := getDlvBin(t) 1335 fixture := filepath.Join(protest.FindFixturesDir(), "testargs.go") 1336 1337 startOne := func() (io.WriteCloser, func() error, *bytes.Buffer) { 1338 cmd := exec.Command(dlvbin, "debug", "--allow-non-terminal-interactive=true", fixture, "--", "test") 1339 stdin, _ := cmd.StdinPipe() 1340 stdoutBuf := new(bytes.Buffer) 1341 cmd.Stdout = stdoutBuf 1342 1343 assertNoError(cmd.Start(), t, "dlv debug") 1344 return stdin, cmd.Wait, stdoutBuf 1345 } 1346 1347 stdin1, wait1, stdoutBuf1 := startOne() 1348 defer stdin1.Close() 1349 1350 stdin2, wait2, stdoutBuf2 := startOne() 1351 defer stdin2.Close() 1352 1353 fmt.Fprintf(stdin1, "continue\nquit\n") 1354 fmt.Fprintf(stdin2, "continue\nquit\n") 1355 1356 wait1() 1357 wait2() 1358 1359 out1, out2 := stdoutBuf1.String(), stdoutBuf2.String() 1360 t.Logf("%q", out1) 1361 t.Logf("%q", out2) 1362 if out1 == out2 { 1363 t.Errorf("outputs match") 1364 } 1365 }