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
|
conf *config.Config
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
debugname = "debug"
|
|
||||||
testdebugname = "debug.test"
|
|
||||||
)
|
|
||||||
|
|
||||||
const dlvCommandLongDesc = `Delve is a source level debugger for Go programs.
|
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,
|
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.`,
|
session.`,
|
||||||
Run: debugCmd,
|
Run: debugCmd,
|
||||||
}
|
}
|
||||||
|
debugCommand.Flags().String("output", "debug", "Output path for the binary.")
|
||||||
RootCommand.AddCommand(debugCommand)
|
RootCommand.AddCommand(debugCommand)
|
||||||
|
|
||||||
// 'exec' subcommand.
|
// 'exec' subcommand.
|
||||||
@ -198,6 +194,7 @@ Alternatively you can specify a package name, and Delve will debug the tests in
|
|||||||
that package instead.`,
|
that package instead.`,
|
||||||
Run: testCmd,
|
Run: testCmd,
|
||||||
}
|
}
|
||||||
|
testCommand.Flags().String("output", "debug.test", "Output path for the binary.")
|
||||||
RootCommand.AddCommand(testCommand)
|
RootCommand.AddCommand(testCommand)
|
||||||
|
|
||||||
// 'trace' subcommand.
|
// '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(&traceAttachPid, "pid", "p", 0, "Pid to attach to.")
|
||||||
traceCommand.Flags().IntVarP(&traceStackDepth, "stack", "s", 0, "Show stack trace with given depth.")
|
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)
|
RootCommand.AddCommand(traceCommand)
|
||||||
|
|
||||||
coreCommand := &cobra.Command{
|
coreCommand := &cobra.Command{
|
||||||
Use: "core <executable> <core>",
|
Use: "core <executable> <core>",
|
||||||
Short: "Examine a core dump.",
|
Short: "Examine a core dump.",
|
||||||
Long: `Examine a core dump.
|
Long: `Examine a core dump.
|
||||||
|
|
||||||
The core command will open the specified core file and the associated
|
The core command will open the specified core file and the associated
|
||||||
executable and let you examine the state of the process when the
|
executable and let you examine the state of the process when the
|
||||||
core dump was taken.`,
|
core dump was taken.`,
|
||||||
@ -249,7 +247,7 @@ core dump was taken.`,
|
|||||||
Use: "replay [trace directory]",
|
Use: "replay [trace directory]",
|
||||||
Short: "Replays a rr trace.",
|
Short: "Replays a rr trace.",
|
||||||
Long: `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:
|
The replay command will open a trace generated by mozilla rr. Mozilla rr must be installed:
|
||||||
https://github.com/mozilla/rr
|
https://github.com/mozilla/rr
|
||||||
`,
|
`,
|
||||||
@ -270,31 +268,35 @@ https://github.com/mozilla/rr
|
|||||||
return RootCommand
|
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) {
|
func debugCmd(cmd *cobra.Command, args []string) {
|
||||||
status := func() int {
|
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
|
var pkg string
|
||||||
dlvArgs, targetArgs := splitArgs(cmd, args)
|
dlvArgs, targetArgs := splitArgs(cmd, args)
|
||||||
|
|
||||||
if len(dlvArgs) > 0 {
|
if len(dlvArgs) > 0 {
|
||||||
pkg = args[0]
|
pkg = args[0]
|
||||||
}
|
}
|
||||||
err := gobuild(debugname, pkg)
|
err = gobuild(debugname, pkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
fp, err := filepath.Abs("./" + debugname)
|
defer remove(debugname)
|
||||||
if err != nil {
|
processArgs := append([]string{debugname}, targetArgs...)
|
||||||
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...)
|
|
||||||
return execute(0, processArgs, conf, "", executingGeneratedFile)
|
return execute(0, processArgs, conf, "", executingGeneratedFile)
|
||||||
}()
|
}()
|
||||||
os.Exit(status)
|
os.Exit(status)
|
||||||
@ -302,6 +304,13 @@ func debugCmd(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
func traceCmd(cmd *cobra.Command, args []string) {
|
func traceCmd(cmd *cobra.Command, args []string) {
|
||||||
status := func() int {
|
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 regexp string
|
||||||
var processArgs []string
|
var processArgs []string
|
||||||
|
|
||||||
@ -319,9 +328,9 @@ func traceCmd(cmd *cobra.Command, args []string) {
|
|||||||
if err := gobuild(debugname, pkg); err != nil {
|
if err := gobuild(debugname, pkg); err != nil {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
defer os.Remove("./" + debugname)
|
defer remove(debugname)
|
||||||
|
|
||||||
processArgs = append([]string{"./" + debugname}, targetArgs...)
|
processArgs = append([]string{debugname}, targetArgs...)
|
||||||
}
|
}
|
||||||
// Make a TCP listener
|
// Make a TCP listener
|
||||||
listener, err := net.Listen("tcp", Addr)
|
listener, err := net.Listen("tcp", Addr)
|
||||||
@ -372,18 +381,24 @@ func traceCmd(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
func testCmd(cmd *cobra.Command, args []string) {
|
func testCmd(cmd *cobra.Command, args []string) {
|
||||||
status := func() int {
|
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
|
var pkg string
|
||||||
dlvArgs, targetArgs := splitArgs(cmd, args)
|
dlvArgs, targetArgs := splitArgs(cmd, args)
|
||||||
|
|
||||||
if len(dlvArgs) > 0 {
|
if len(dlvArgs) > 0 {
|
||||||
pkg = args[0]
|
pkg = args[0]
|
||||||
}
|
}
|
||||||
err := gotestbuild(pkg)
|
err = gotestbuild(debugname, pkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
defer os.Remove("./" + testdebugname)
|
defer remove(debugname)
|
||||||
processArgs := append([]string{"./" + testdebugname}, targetArgs...)
|
processArgs := append([]string{debugname}, targetArgs...)
|
||||||
|
|
||||||
return execute(0, processArgs, conf, "", executingGeneratedTest)
|
return execute(0, processArgs, conf, "", executingGeneratedTest)
|
||||||
}()
|
}()
|
||||||
@ -542,8 +557,8 @@ func gobuild(debugname, pkg string) error {
|
|||||||
return gocommand("build", args...)
|
return gocommand("build", args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func gotestbuild(pkg string) error {
|
func gotestbuild(debugname, pkg string) error {
|
||||||
args := []string{"-gcflags", "-N -l", "-c", "-o", testdebugname}
|
args := []string{"-gcflags", "-N -l", "-c", "-o", debugname}
|
||||||
if BuildFlags != "" {
|
if BuildFlags != "" {
|
||||||
args = append(args, config.SplitQuotedFields(BuildFlags, '\'')...)
|
args = append(args, config.SplitQuotedFields(BuildFlags, '\'')...)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
protest "github.com/derekparker/delve/pkg/proc/test"
|
protest "github.com/derekparker/delve/pkg/proc/test"
|
||||||
"github.com/derekparker/delve/service/rpc2"
|
"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 != "" {
|
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()
|
val, err := exec.Command("go", "env", name).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // the Go tool was tested to work earlier
|
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) {
|
func TestBuild(t *testing.T) {
|
||||||
const listenAddr = "localhost:40573"
|
const listenAddr = "localhost:40573"
|
||||||
var err error
|
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"} {
|
for _, makeProgram := range []string{"make", "mingw32-make"} {
|
||||||
var out []byte
|
var out []byte
|
||||||
cmd := exec.Command(makeProgram, "build")
|
cmd := exec.Command(makeProgram, "build")
|
||||||
@ -100,10 +103,21 @@ func TestBuild(t *testing.T) {
|
|||||||
cmd.Wait()
|
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
|
var stdoutBuf, stderrBuf bytes.Buffer
|
||||||
buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest")
|
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
|
cmd.Dir = buildtestdir
|
||||||
stdin, err := cmd.StdinPipe()
|
stdin, err := cmd.StdinPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -111,37 +125,52 @@ func testIssue398(t *testing.T, dlvbin string, cmds []string) (stdout, stderr []
|
|||||||
}
|
}
|
||||||
cmd.Stdout = &stdoutBuf
|
cmd.Stdout = &stdoutBuf
|
||||||
cmd.Stderr = &stderrBuf
|
cmd.Stderr = &stderrBuf
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
t.Fatal(err)
|
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)
|
fmt.Fprintf(stdin, "%s\n", c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore "dlv debug" command error, it returns
|
// ignore "dlv debug" command error, it returns
|
||||||
// errors even after successful debug session.
|
// errors even after successful debug session.
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
stdout, stderr = stdoutBuf.Bytes(), stderrBuf.Bytes()
|
stdout, stderr = stdoutBuf.Bytes(), stderrBuf.Bytes()
|
||||||
|
|
||||||
debugbin := filepath.Join(buildtestdir, "debug")
|
|
||||||
_, err = os.Stat(debugbin)
|
_, err = os.Stat(debugbin)
|
||||||
if err == nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
if !os.IsNotExist(err) {
|
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
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestIssue398 verifies that the debug executable is removed after exit.
|
func getDlvBin(t *testing.T) (string, string) {
|
||||||
func TestIssue398(t *testing.T) {
|
tmpdir, err := ioutil.TempDir("", "TestDlv")
|
||||||
tmpdir, err := ioutil.TempDir("", "TestIssue398")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpdir)
|
|
||||||
|
|
||||||
dlvbin := filepath.Join(tmpdir, "dlv.exe")
|
dlvbin := filepath.Join(tmpdir, "dlv.exe")
|
||||||
out, err := exec.Command("go", "build", "-o", dlvbin, "github.com/derekparker/delve/cmd/dlv").CombinedOutput()
|
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))
|
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!"
|
// TestOutput verifies that the debug executable is created in the correct path
|
||||||
stdout, _ := testIssue398(t, dlvbin, []string{"continue", "exit"})
|
// and removed after exit.
|
||||||
if !strings.Contains(string(stdout), hello) {
|
func TestOutput(t *testing.T) {
|
||||||
t.Errorf("stdout %q should contain %q", stdout, hello)
|
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