diff --git a/_fixtures/buildtest/main.go b/_fixtures/buildtest/main.go new file mode 100644 index 00000000..7776ec8d --- /dev/null +++ b/_fixtures/buildtest/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("hello world!") +} diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index 530345c2..f6fe2d91 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -1,6 +1,7 @@ package cmds import ( + "bytes" "errors" "fmt" "net" @@ -10,8 +11,8 @@ import ( "path/filepath" "runtime" "strconv" - "strings" "syscall" + "unicode" "github.com/derekparker/delve/config" "github.com/derekparker/delve/service" @@ -68,7 +69,7 @@ func New() *cobra.Command { buildFlagsDefault := "" if runtime.GOOS == "windows" { // Work-around for https://github.com/golang/go/issues/13154 - buildFlagsDefault = "-ldflags=-linkmode internal" + buildFlagsDefault = "-ldflags='-linkmode internal'" } // Main dlv root command. @@ -447,7 +448,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, kind exec func gobuild(debugname, pkg string) error { args := []string{"-gcflags", "-N -l", "-o", debugname} if BuildFlags != "" { - args = append(args, strings.Fields(BuildFlags)...) + args = append(args, splitQuotedFields(BuildFlags)...) } args = append(args, pkg) return gocommand("build", args...) @@ -456,7 +457,7 @@ func gobuild(debugname, pkg string) error { func gotestbuild(pkg string) error { args := []string{"-gcflags", "-N -l", "-c", "-o", testdebugname} if BuildFlags != "" { - args = append(args, strings.Fields(BuildFlags)...) + args = append(args, splitQuotedFields(BuildFlags)...) } args = append(args, pkg) return gocommand("test", args...) @@ -469,3 +470,60 @@ func gocommand(command string, args ...string) error { goBuild.Stderr = os.Stderr return goBuild.Run() } + +// Like strings.Fields but ignores spaces inside areas surrounded +// by single quotes. +// To specify a single quote use backslash to escape it: '\'' +func splitQuotedFields(in string) []string { + type stateEnum int + const ( + inSpace stateEnum = iota + inField + inQuote + inQuoteEscaped + ) + state := inSpace + r := []string{} + var buf bytes.Buffer + + for _, ch := range in { + switch state { + case inSpace: + if ch == '\'' { + state = inQuote + } else if !unicode.IsSpace(ch) { + buf.WriteRune(ch) + state = inField + } + + case inField: + if ch == '\'' { + state = inQuote + } else if unicode.IsSpace(ch) { + r = append(r, buf.String()) + buf.Reset() + } else { + buf.WriteRune(ch) + } + + case inQuote: + if ch == '\'' { + state = inField + } else if ch == '\\' { + state = inQuoteEscaped + } else { + buf.WriteRune(ch) + } + + case inQuoteEscaped: + buf.WriteRune(ch) + state = inQuote + } + } + + if buf.Len() != 0 { + r = append(r, buf.String()) + } + + return r +} diff --git a/cmd/dlv/cmds/commands_test.go b/cmd/dlv/cmds/commands_test.go new file mode 100644 index 00000000..6205b8b7 --- /dev/null +++ b/cmd/dlv/cmds/commands_test.go @@ -0,0 +1,21 @@ +package cmds + +import ( + "testing" +) + +func TestSplitQuotedFields(t *testing.T) { + in := `field'A' 'fieldB' fie'l\'d'C fieldD 'another field' fieldE` + tgt := []string{"fieldA", "fieldB", "fiel'dC", "fieldD", "another field", "fieldE"} + out := splitQuotedFields(in) + + if len(tgt) != len(out) { + t.Fatalf("expected %#v, got %#v (len mismatch)", tgt, out) + } + + for i := range tgt { + if tgt[i] != out[i] { + t.Fatalf(" expected %#v, got %#v (mismatch at %d)", tgt, out, i) + } + } +} diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go new file mode 100644 index 00000000..0933355a --- /dev/null +++ b/cmd/dlv/dlv_test.go @@ -0,0 +1,61 @@ +package main + +import ( + "bufio" + "os" + "os/exec" + "path/filepath" + "runtime" + "testing" + + protest "github.com/derekparker/delve/proc/test" + "github.com/derekparker/delve/service/rpc2" +) + +func assertNoError(err error, t testing.TB, s string) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + fname := filepath.Base(file) + t.Fatalf("failed assertion at %s:%d: %s - %s\n", fname, line, s, err) + } +} + +func TestBuild(t *testing.T) { + const listenAddr = "localhost:40573" + cmd := exec.Command("go", "build", "github.com/derekparker/delve/cmd/dlv") + assertNoError(cmd.Run(), t, "go build") + wd, _ := os.Getwd() + dlvbin := filepath.Join(wd, "dlv") + + defer os.Remove(dlvbin) + + fixtures := protest.FindFixturesDir() + + buildtestdir := filepath.Join(fixtures, "buildtest") + + cmd = exec.Command(dlvbin, "debug", "--headless=true", "--listen="+listenAddr, "--api-version=2") + cmd.Dir = buildtestdir + stdout, err := cmd.StdoutPipe() + assertNoError(err, t, "stdout pipe") + cmd.Start() + defer func() { + cmd.Process.Signal(os.Interrupt) + cmd.Wait() + }() + + scan := bufio.NewScanner(stdout) + // wait for the debugger to start + scan.Scan() + go func() { + for scan.Scan() { + // keep pipe empty + } + }() + + client := rpc2.NewClient(listenAddr) + state := <-client.Continue() + + if !state.Exited { + t.Fatal("Program did not exit") + } +} diff --git a/terminal/command_test.go b/terminal/command_test.go index e012d5e7..5b2111d7 100644 --- a/terminal/command_test.go +++ b/terminal/command_test.go @@ -13,8 +13,8 @@ import ( "github.com/derekparker/delve/proc/test" "github.com/derekparker/delve/service" "github.com/derekparker/delve/service/api" - "github.com/derekparker/delve/service/rpccommon" "github.com/derekparker/delve/service/rpc2" + "github.com/derekparker/delve/service/rpccommon" ) type FakeTerminal struct {