mirror of
https://github.com/go-delve/delve.git
synced 2025-11-03 22:08:33 +08:00
Support --output for debug, trace, and test commands (#1028)
* Support --output for debug, trace, and test commands
With the `--output` parameter you can configure the output binary. For
example:
dlv debug --output /tmp/xxx
Will build the binary to `/tmp/xxx`, instead of always putting it as
`debug` in the current directory.
This ensures that the command always works (even if there is already a
file or directory named `debug`) and doesn't write to the source
directory. Especially for things like Delve/Vim integration this is a
good thing to have, I think.
* Address PR feedback and add a test
- We don't need to use `filepath.IsAbs()` on startup; I added that
because it previously did `"./" + debugname` everywhere, but I don't
think that's needed at all, since `pathname` without a leading `./`
implies the current directory.
- Repurpose the existing `TestIssue398` to also test the `--output`
flag. Also fix an issue where tests wouldn't work if `GOPATH` has
multiple entries (e..g `GOPATH=$HOME/go:$HOME/mygocode`).
- Print an error if we can't remove the debug binary on exit instead of
failing silently. Not strictly related to this PR, but a good change
to add I think.
* Also warn when delve can't remove the binary in test/trace
I only added that to debug, but good to issue this warning consistently.
This commit is contained in:
committed by
Derek Parker
parent
99cad1044b
commit
6fe97fa75b
@ -53,11 +53,6 @@ var (
|
||||
conf *config.Config
|
||||
)
|
||||
|
||||
const (
|
||||
debugname = "debug"
|
||||
testdebugname = "debug.test"
|
||||
)
|
||||
|
||||
const dlvCommandLongDesc = `Delve is a source level debugger for Go programs.
|
||||
|
||||
Delve enables you to interact with your program by controlling the execution of the process,
|
||||
@ -151,6 +146,7 @@ package name and Delve will compile that package instead, and begin a new debug
|
||||
session.`,
|
||||
Run: debugCmd,
|
||||
}
|
||||
debugCommand.Flags().String("output", "debug", "Output path for the binary.")
|
||||
RootCommand.AddCommand(debugCommand)
|
||||
|
||||
// 'exec' subcommand.
|
||||
@ -198,6 +194,7 @@ Alternatively you can specify a package name, and Delve will debug the tests in
|
||||
that package instead.`,
|
||||
Run: testCmd,
|
||||
}
|
||||
testCommand.Flags().String("output", "debug.test", "Output path for the binary.")
|
||||
RootCommand.AddCommand(testCommand)
|
||||
|
||||
// 'trace' subcommand.
|
||||
@ -214,13 +211,14 @@ to know what functions your process is executing.`,
|
||||
}
|
||||
traceCommand.Flags().IntVarP(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
|
||||
traceCommand.Flags().IntVarP(&traceStackDepth, "stack", "s", 0, "Show stack trace with given depth.")
|
||||
traceCommand.Flags().String("output", "debug", "Output path for the binary.")
|
||||
RootCommand.AddCommand(traceCommand)
|
||||
|
||||
coreCommand := &cobra.Command{
|
||||
Use: "core <executable> <core>",
|
||||
Short: "Examine a core dump.",
|
||||
Long: `Examine a core dump.
|
||||
|
||||
|
||||
The core command will open the specified core file and the associated
|
||||
executable and let you examine the state of the process when the
|
||||
core dump was taken.`,
|
||||
@ -249,7 +247,7 @@ core dump was taken.`,
|
||||
Use: "replay [trace directory]",
|
||||
Short: "Replays a rr trace.",
|
||||
Long: `Replays a rr trace.
|
||||
|
||||
|
||||
The replay command will open a trace generated by mozilla rr. Mozilla rr must be installed:
|
||||
https://github.com/mozilla/rr
|
||||
`,
|
||||
@ -270,31 +268,35 @@ https://github.com/mozilla/rr
|
||||
return RootCommand
|
||||
}
|
||||
|
||||
// Remove the file at path and issue a warning to stderr if this fails.
|
||||
func remove(path string) {
|
||||
err := os.Remove(path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not remove %v: %v\n", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
func debugCmd(cmd *cobra.Command, args []string) {
|
||||
status := func() int {
|
||||
debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
var pkg string
|
||||
dlvArgs, targetArgs := splitArgs(cmd, args)
|
||||
|
||||
if len(dlvArgs) > 0 {
|
||||
pkg = args[0]
|
||||
}
|
||||
err := gobuild(debugname, pkg)
|
||||
err = gobuild(debugname, pkg)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return 1
|
||||
}
|
||||
fp, err := filepath.Abs("./" + debugname)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return 1
|
||||
}
|
||||
defer os.Remove(fp)
|
||||
abs, err := filepath.Abs(debugname)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return 1
|
||||
}
|
||||
processArgs := append([]string{abs}, targetArgs...)
|
||||
defer remove(debugname)
|
||||
processArgs := append([]string{debugname}, targetArgs...)
|
||||
return execute(0, processArgs, conf, "", executingGeneratedFile)
|
||||
}()
|
||||
os.Exit(status)
|
||||
@ -302,6 +304,13 @@ func debugCmd(cmd *cobra.Command, args []string) {
|
||||
|
||||
func traceCmd(cmd *cobra.Command, args []string) {
|
||||
status := func() int {
|
||||
|
||||
debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
var regexp string
|
||||
var processArgs []string
|
||||
|
||||
@ -319,9 +328,9 @@ func traceCmd(cmd *cobra.Command, args []string) {
|
||||
if err := gobuild(debugname, pkg); err != nil {
|
||||
return 1
|
||||
}
|
||||
defer os.Remove("./" + debugname)
|
||||
defer remove(debugname)
|
||||
|
||||
processArgs = append([]string{"./" + debugname}, targetArgs...)
|
||||
processArgs = append([]string{debugname}, targetArgs...)
|
||||
}
|
||||
// Make a TCP listener
|
||||
listener, err := net.Listen("tcp", Addr)
|
||||
@ -372,18 +381,24 @@ func traceCmd(cmd *cobra.Command, args []string) {
|
||||
|
||||
func testCmd(cmd *cobra.Command, args []string) {
|
||||
status := func() int {
|
||||
debugname, err := filepath.Abs(cmd.Flag("output").Value.String())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
var pkg string
|
||||
dlvArgs, targetArgs := splitArgs(cmd, args)
|
||||
|
||||
if len(dlvArgs) > 0 {
|
||||
pkg = args[0]
|
||||
}
|
||||
err := gotestbuild(pkg)
|
||||
err = gotestbuild(debugname, pkg)
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
defer os.Remove("./" + testdebugname)
|
||||
processArgs := append([]string{"./" + testdebugname}, targetArgs...)
|
||||
defer remove(debugname)
|
||||
processArgs := append([]string{debugname}, targetArgs...)
|
||||
|
||||
return execute(0, processArgs, conf, "", executingGeneratedTest)
|
||||
}()
|
||||
@ -542,8 +557,8 @@ func gobuild(debugname, pkg string) error {
|
||||
return gocommand("build", args...)
|
||||
}
|
||||
|
||||
func gotestbuild(pkg string) error {
|
||||
args := []string{"-gcflags", "-N -l", "-c", "-o", testdebugname}
|
||||
func gotestbuild(debugname, pkg string) error {
|
||||
args := []string{"-gcflags", "-N -l", "-c", "-o", debugname}
|
||||
if BuildFlags != "" {
|
||||
args = append(args, config.SplitQuotedFields(BuildFlags, '\'')...)
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
protest "github.com/derekparker/delve/pkg/proc/test"
|
||||
"github.com/derekparker/delve/service/rpc2"
|
||||
@ -39,21 +40,23 @@ func assertNoError(err error, t testing.TB, s string) {
|
||||
}
|
||||
}
|
||||
|
||||
func goEnv(name string) string {
|
||||
func goPath(name string) string {
|
||||
if val := os.Getenv(name); val != "" {
|
||||
return val
|
||||
// Use first GOPATH entry if there are multiple.
|
||||
return filepath.SplitList(val)[0]
|
||||
}
|
||||
|
||||
val, err := exec.Command("go", "env", name).Output()
|
||||
if err != nil {
|
||||
panic(err) // the Go tool was tested to work earlier
|
||||
}
|
||||
return strings.TrimSpace(string(val))
|
||||
return filepath.SplitList(strings.TrimSpace(string(val)))[0]
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
const listenAddr = "localhost:40573"
|
||||
var err error
|
||||
makedir := filepath.Join(goEnv("GOPATH"), "src", "github.com", "derekparker", "delve")
|
||||
makedir := filepath.Join(goPath("GOPATH"), "src", "github.com", "derekparker", "delve")
|
||||
for _, makeProgram := range []string{"make", "mingw32-make"} {
|
||||
var out []byte
|
||||
cmd := exec.Command(makeProgram, "build")
|
||||
@ -100,10 +103,21 @@ func TestBuild(t *testing.T) {
|
||||
cmd.Wait()
|
||||
}
|
||||
|
||||
func testIssue398(t *testing.T, dlvbin string, cmds []string) (stdout, stderr []byte) {
|
||||
func testOutput(t *testing.T, dlvbin, output string, delveCmds []string) (stdout, stderr []byte) {
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest")
|
||||
cmd := exec.Command(dlvbin, "debug")
|
||||
|
||||
c := []string{dlvbin, "debug"}
|
||||
debugbin := filepath.Join(buildtestdir, "debug")
|
||||
if output != "" {
|
||||
c = append(c, "--output", output)
|
||||
if filepath.IsAbs(output) {
|
||||
debugbin = output
|
||||
} else {
|
||||
debugbin = filepath.Join(buildtestdir, output)
|
||||
}
|
||||
}
|
||||
cmd := exec.Command(c[0], c[1:]...)
|
||||
cmd.Dir = buildtestdir
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
@ -111,37 +125,52 @@ func testIssue398(t *testing.T, dlvbin string, cmds []string) (stdout, stderr []
|
||||
}
|
||||
cmd.Stdout = &stdoutBuf
|
||||
cmd.Stderr = &stderrBuf
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, c := range cmds {
|
||||
|
||||
// Give delve some time to compile and write the binary.
|
||||
foundIt := false
|
||||
for wait := 0; wait < 30; wait++ {
|
||||
_, err = os.Stat(debugbin)
|
||||
if err == nil {
|
||||
foundIt = true
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
if !foundIt {
|
||||
t.Errorf("running %q: file not created: %v", delveCmds, err)
|
||||
}
|
||||
|
||||
for _, c := range delveCmds {
|
||||
fmt.Fprintf(stdin, "%s\n", c)
|
||||
}
|
||||
|
||||
// ignore "dlv debug" command error, it returns
|
||||
// errors even after successful debug session.
|
||||
cmd.Wait()
|
||||
stdout, stderr = stdoutBuf.Bytes(), stderrBuf.Bytes()
|
||||
|
||||
debugbin := filepath.Join(buildtestdir, "debug")
|
||||
_, err = os.Stat(debugbin)
|
||||
if err == nil {
|
||||
t.Errorf("running %q: file %v was not deleted\nstdout is %q, stderr is %q", cmds, debugbin, stdout, stderr)
|
||||
t.Errorf("running %q: file %v was not deleted\nstdout is %q, stderr is %q", delveCmds, debugbin, stdout, stderr)
|
||||
return
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
t.Errorf("running %q: %v\nstdout is %q, stderr is %q", cmds, err, stdout, stderr)
|
||||
t.Errorf("running %q: %v\nstdout is %q, stderr is %q", delveCmds, err, stdout, stderr)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TestIssue398 verifies that the debug executable is removed after exit.
|
||||
func TestIssue398(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "TestIssue398")
|
||||
func getDlvBin(t *testing.T) (string, string) {
|
||||
tmpdir, err := ioutil.TempDir("", "TestDlv")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
dlvbin := filepath.Join(tmpdir, "dlv.exe")
|
||||
out, err := exec.Command("go", "build", "-o", dlvbin, "github.com/derekparker/delve/cmd/dlv").CombinedOutput()
|
||||
@ -149,11 +178,22 @@ func TestIssue398(t *testing.T) {
|
||||
t.Fatalf("go build -o %v github.com/derekparker/delve/cmd/dlv: %v\n%s", dlvbin, err, string(out))
|
||||
}
|
||||
|
||||
testIssue398(t, dlvbin, []string{"exit"})
|
||||
return dlvbin, tmpdir
|
||||
}
|
||||
|
||||
const hello = "hello world!"
|
||||
stdout, _ := testIssue398(t, dlvbin, []string{"continue", "exit"})
|
||||
if !strings.Contains(string(stdout), hello) {
|
||||
t.Errorf("stdout %q should contain %q", stdout, hello)
|
||||
// TestOutput verifies that the debug executable is created in the correct path
|
||||
// and removed after exit.
|
||||
func TestOutput(t *testing.T) {
|
||||
dlvbin, tmpdir := getDlvBin(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
for _, output := range []string{"", "myownname", filepath.Join(tmpdir, "absolute.path")} {
|
||||
testOutput(t, dlvbin, output, []string{"exit"})
|
||||
|
||||
const hello = "hello world!"
|
||||
stdout, _ := testOutput(t, dlvbin, output, []string{"continue", "exit"})
|
||||
if !strings.Contains(string(stdout), hello) {
|
||||
t.Errorf("stdout %q should contain %q", stdout, hello)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user