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