From 95e7cafd0c77790d728a6dc6ffc65c2cb29a13c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20S=C3=A1ez?= Date: Fri, 5 Jun 2020 20:03:09 +0200 Subject: [PATCH] terminal/command: Add 'reload' command (#1971) * terminal/command: Add 'reload' command These changes add the 'reload' command, which allows us to rebuild the project and start the debugging session again. Currently, if the project's code is updated while debugging it, Delve shows the new source code, but it's still running the old one. With 'reload', the whole binary is rebuilt, and the process starts again. Fixes #1551 * Remove unnecessary print Changes to be committed: modified: pkg/terminal/command.go * Add tests and refactor the code Changes to be committed: modified: cmd/dlv/cmds/commands.go modified: go.mod modified: pkg/terminal/command.go modified: service/config.go modified: service/debugger/debugger.go modified: service/test/integration2_test.go * Fix typo in the comment Changes to be committed: modified: service/debugger/debugger.go * Fix typo in the name of the variables The variables are local therefore the capitalization is not needed Changes to be committed: modified: cmd/dlv/cmds/commands.go * Call GoTestBuild Also, remove the := to avoid redeclaration * Change the Kind in the tests Change from debugger.ExecutingGeneratedTest to debugger.ExecutingGeneratedFile for consistency. We are generating a real binary instead of a test one so ExecutingGeneratedFile makes more sense here. Changes to be committed: modified: service/test/integration2_test.go * Avoid breakpoints based on addresses Changes to be committed: modified: service/debugger/debugger.go * Update the rebuild behaviour There are a few cases where we can't rebuild the binary because we don't know how it was build. Changes to be committed: modified: service/debugger/debugger.go * Fix typos and update documentation Changes to be committed: modified: Documentation/cli/README.md modified: pkg/terminal/command.go modified: service/config.go modified: service/debugger/debugger.go * Fix typo * Remove variables They were added to the debugger.Config * Rename variable Rename Kind to ExecuteKind to make it more accurate Changes to be committed: modified: cmd/dlv/cmds/commands.go modified: service/debugger/debugger.go modified: service/test/integration2_test.go --- Documentation/cli/README.md | 5 ++ Documentation/cli/starlark.md | 2 +- cmd/dlv/cmds/commands.go | 34 ++++----- go.sum | 4 + pkg/terminal/command.go | 15 +++- pkg/terminal/starbind/starlark_mapping.go | 8 ++ service/client.go | 6 +- service/debugger/debugger.go | 44 ++++++++++- service/rpc1/server.go | 2 +- service/rpc2/client.go | 8 +- service/rpc2/server.go | 5 +- service/test/integration2_test.go | 90 ++++++++++++++++++++--- 12 files changed, 182 insertions(+), 41 deletions(-) diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index 0eac6dd8..ea721fd6 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -13,6 +13,7 @@ Command | Description [call](#call) | Resumes process, injecting a function call (EXPERIMENTAL!!!) [continue](#continue) | Run until breakpoint or program termination. [next](#next) | Step over to next source line. +[rebuild](#rebuild) | Rebuild the target executable and restarts it. It does not work if the executable was not built by delve. [restart](#restart) | Restart process. [rev](#rev) | Reverses the execution of the target program for the command specified. [rewind](#rewind) | Run backwards until breakpoint or program termination. @@ -394,6 +395,10 @@ See [Documentation/cli/expr.md](//github.com/go-delve/delve/tree/master/Document Aliases: p +## rebuild +Rebuild the target executable and restarts it. It does not work if the executable was not built by delve. + + ## regs Print contents of CPU registers. diff --git a/Documentation/cli/starlark.md b/Documentation/cli/starlark.md index 1de8a5ff..ea64a6ce 100644 --- a/Documentation/cli/starlark.md +++ b/Documentation/cli/starlark.md @@ -51,7 +51,7 @@ threads() | Equivalent to API call [ListThreads](https://godoc.org/github.com/go types(Filter) | Equivalent to API call [ListTypes](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListTypes) process_pid() | Equivalent to API call [ProcessPid](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ProcessPid) recorded() | Equivalent to API call [Recorded](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Recorded) -restart(Position, ResetArgs, NewArgs, Rerecord) | Equivalent to API call [Restart](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Restart) +restart(Position, ResetArgs, NewArgs, Rerecord, Rebuild) | Equivalent to API call [Restart](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Restart) set_expr(Scope, Symbol, Value) | Equivalent to API call [Set](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Set) stacktrace(Id, Depth, Full, Defers, Opts, Cfg) | Equivalent to API call [Stacktrace](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Stacktrace) state(NonBlocking) | Equivalent to API call [State](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.State) diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index 2e6f1236..e26bacf9 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -210,7 +210,7 @@ or later, -gcflags="-N -l" on earlier versions of Go.`, return nil }, Run: func(cmd *cobra.Command, args []string) { - os.Exit(execute(0, args, conf, "", executingExistingFile)) + os.Exit(execute(0, args, conf, "", debugger.ExecutingExistingFile, args, buildFlags)) }, } execCommand.Flags().StringVar(&tty, "tty", "", "TTY to use for the target program") @@ -312,7 +312,7 @@ https://github.com/mozilla/rr }, Run: func(cmd *cobra.Command, args []string) { backend = "rr" - os.Exit(execute(0, []string{}, conf, args[0], executingOther)) + os.Exit(execute(0, []string{}, conf, args[0], debugger.ExecutingOther, args, buildFlags)) }, } rootCommand.AddCommand(replayCommand) @@ -441,7 +441,7 @@ func debugCmd(cmd *cobra.Command, args []string) { } defer gobuild.Remove(debugname) processArgs := append([]string{debugname}, targetArgs...) - return execute(0, processArgs, conf, "", executingGeneratedFile) + return execute(0, processArgs, conf, "", debugger.ExecutingGeneratedFile, dlvArgs, buildFlags) }() os.Exit(status) } @@ -591,7 +591,7 @@ func testCmd(cmd *cobra.Command, args []string) { defer gobuild.Remove(debugname) processArgs := append([]string{debugname}, targetArgs...) - return execute(0, processArgs, conf, "", executingGeneratedTest) + return execute(0, processArgs, conf, "", debugger.ExecutingGeneratedTest, dlvArgs, buildFlags) }() os.Exit(status) } @@ -602,11 +602,11 @@ func attachCmd(cmd *cobra.Command, args []string) { fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0]) os.Exit(1) } - os.Exit(execute(pid, args[1:], conf, "", executingOther)) + os.Exit(execute(pid, args[1:], conf, "", debugger.ExecutingOther, args, buildFlags)) } func coreCmd(cmd *cobra.Command, args []string) { - os.Exit(execute(0, []string{args[0]}, conf, args[1], executingOther)) + os.Exit(execute(0, []string{args[0]}, conf, args[1], debugger.ExecutingOther, args, buildFlags)) } func connectCmd(cmd *cobra.Command, args []string) { @@ -615,7 +615,7 @@ func connectCmd(cmd *cobra.Command, args []string) { fmt.Fprint(os.Stderr, "An empty address was provided. You must provide an address as the first argument.\n") os.Exit(1) } - os.Exit(connect(addr, nil, conf, executingOther)) + os.Exit(connect(addr, nil, conf, debugger.ExecutingOther)) } // waitForDisconnectSignal is a blocking function that waits for either @@ -656,7 +656,7 @@ func splitArgs(cmd *cobra.Command, args []string) ([]string, []string) { return args, []string{} } -func connect(addr string, clientConn net.Conn, conf *config.Config, kind executeKind) int { +func connect(addr string, clientConn net.Conn, conf *config.Config, kind debugger.ExecuteKind) int { // Create and start a terminal - attach to running instance var client *rpc2.RPCClient if clientConn != nil { @@ -687,16 +687,7 @@ func connect(addr string, clientConn net.Conn, conf *config.Config, kind execute return status } -type executeKind int - -const ( - executingExistingFile = executeKind(iota) - executingGeneratedFile - executingGeneratedTest - executingOther -) - -func execute(attachPid int, processArgs []string, conf *config.Config, coreFile string, kind executeKind) int { +func execute(attachPid int, processArgs []string, conf *config.Config, coreFile string, kind debugger.ExecuteKind, dlvArgs []string, buildFlags string) int { if err := logflags.Setup(log, logOutput, logDest); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) return 1 @@ -760,6 +751,9 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile Backend: backend, CoreFile: coreFile, Foreground: headless && tty == "", + Packages: dlvArgs, + BuildFlags: buildFlags, + ExecuteKind: kind, DebugInfoDirectories: conf.DebugInfoDirectories, CheckGoVersion: checkGoVersion, TTY: tty, @@ -773,10 +767,10 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile if err := server.Run(); err != nil { if err == api.ErrNotExecutable { switch kind { - case executingGeneratedFile: + case debugger.ExecutingGeneratedFile: fmt.Fprintln(os.Stderr, "Can not debug non-main package") return 1 - case executingExistingFile: + case debugger.ExecutingExistingFile: fmt.Fprintf(os.Stderr, "%s is not executable\n", processArgs[0]) return 1 default: diff --git a/go.sum b/go.sum index a65d5008..48f65028 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,12 @@ golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSF golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f h1:3MlESg/jvTr87F4ttA/q4B+uhe/q6qleC9/DP+IwQmY= golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200415034506-5d8e1897c761 h1:FVw4lelfGRNPqB3C8qX1m+QyeM2vzToIwlFhEZX42y8= +golang.org/x/tools v0.0.0-20200415034506-5d8e1897c761/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 5c780aa1..9ec2bf00 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -143,6 +143,7 @@ For live targets the command takes the following forms: If newargv is omitted the process is restarted (or re-recorded) with the same argument vector. If -noargs is specified instead, the argument vector is cleared. `}, + {aliases: []string{"rebuild"}, group: runCmds, cmdFn: c.rebuild, allowedPrefixes: revPrefix, helpMsg: "Rebuild the target executable and restarts it. It does not work if the executable was not built by delve."}, {aliases: []string{"continue", "c"}, group: runCmds, cmdFn: c.cont, allowedPrefixes: revPrefix, helpMsg: "Run until breakpoint or program termination."}, {aliases: []string{"step", "s"}, group: runCmds, cmdFn: c.step, allowedPrefixes: revPrefix, helpMsg: "Single step through program."}, {aliases: []string{"step-instruction", "si"}, group: runCmds, allowedPrefixes: revPrefix, cmdFn: c.stepInstruction, helpMsg: "Single step a single cpu instruction."}, @@ -1028,7 +1029,7 @@ func restartLive(t *Term, ctx callContext, args string) error { } func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newArgv []string) error { - discarded, err := t.client.RestartFrom(rerecord, restartPos, resetArgs, newArgv) + discarded, err := t.client.RestartFrom(rerecord, restartPos, resetArgs, newArgv, false) if err != nil { return err } @@ -1073,6 +1074,18 @@ func printcontextNoState(t *Term) { printcontext(t, state) } +func (c *Commands) rebuild(t *Term, ctx callContext, args string) error { + if ctx.Prefix == revPrefix { + return c.rewind(t, ctx, args) + } + defer t.onStop() + discarded, err := t.client.Restart(true) + if len(discarded) > 0 { + fmt.Printf("not all breakpoints could be restored.") + } + return err +} + func (c *Commands) cont(t *Term, ctx callContext, args string) error { if ctx.Prefix == revPrefix { return c.rewind(t, ctx, args) diff --git a/pkg/terminal/starbind/starlark_mapping.go b/pkg/terminal/starbind/starlark_mapping.go index 5374d88d..1480507e 100644 --- a/pkg/terminal/starbind/starlark_mapping.go +++ b/pkg/terminal/starbind/starlark_mapping.go @@ -1091,6 +1091,12 @@ func (env *Env) starlarkPredeclare() starlark.StringDict { return starlark.None, decorateError(thread, err) } } + if len(args) > 4 && args[4] != starlark.None { + err := unmarshalStarlarkValue(args[4], &rpcArgs.Rebuild, "Rebuild") + if err != nil { + return starlark.None, decorateError(thread, err) + } + } for _, kv := range kwargs { var err error switch kv[0].(starlark.String) { @@ -1102,6 +1108,8 @@ func (env *Env) starlarkPredeclare() starlark.StringDict { err = unmarshalStarlarkValue(kv[1], &rpcArgs.NewArgs, "NewArgs") case "Rerecord": err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rerecord, "Rerecord") + case "Rebuild": + err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rebuild, "Rebuild") default: err = fmt.Errorf("unknown argument %q", kv[0]) } diff --git a/service/client.go b/service/client.go index ed5b8f98..03efb276 100644 --- a/service/client.go +++ b/service/client.go @@ -18,10 +18,10 @@ type Client interface { // Detach detaches the debugger, optionally killing the process. Detach(killProcess bool) error - // Restarts program. - Restart() ([]api.DiscardedBreakpoint, error) + // Restarts program. Set true if you want to rebuild the process we are debugging. + Restart(rebuild bool) ([]api.DiscardedBreakpoint, error) // Restarts program from the specified position. - RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error) + RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, rebuild bool) ([]api.DiscardedBreakpoint, error) // GetState returns the current debugger state. GetState() (*api.DebuggerState, error) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index c83eedad..7a567f3f 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -16,6 +16,7 @@ import ( "time" "github.com/go-delve/delve/pkg/dwarf/op" + "github.com/go-delve/delve/pkg/gobuild" "github.com/go-delve/delve/pkg/goversion" "github.com/go-delve/delve/pkg/locspec" "github.com/go-delve/delve/pkg/logflags" @@ -63,6 +64,15 @@ type Debugger struct { recordMutex sync.Mutex } +type ExecuteKind int + +const ( + ExecutingExistingFile = ExecuteKind(iota) + ExecutingGeneratedFile + ExecutingGeneratedTest + ExecutingOther +) + // Config provides the configuration to start a Debugger. // // Only one of ProcessArgs or AttachPid should be specified. If ProcessArgs is @@ -98,6 +108,15 @@ type Config struct { // TTY is passed along to the target process on creation. Used to specify a // TTY for that process. TTY string + + // Packages contains the packages that we are debugging. + Packages []string + + // BuildFlags contains the flags passed to the compiler. + BuildFlags string + + // ExecuteKind contains the kind of the executed program. + ExecuteKind ExecuteKind } // New creates a new Debugger. ProcessArgs specify the commandline arguments for the @@ -390,7 +409,7 @@ func (d *Debugger) detach(kill bool) error { // If the target process is a recording it will restart it from the given // position. If pos starts with 'c' it's a checkpoint ID, otherwise it's an // event number. If resetArgs is true, newArgs will replace the process args. -func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error) { +func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []string, rebuild bool) ([]api.DiscardedBreakpoint, error) { d.targetMutex.Lock() defer d.targetMutex.Unlock() @@ -421,6 +440,25 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs [] } var p *proc.Target var err error + + if rebuild { + switch d.config.ExecuteKind { + case ExecutingGeneratedFile: + err = gobuild.GoBuild(d.processArgs[0], d.config.Packages, d.config.BuildFlags) + if err != nil { + return nil, fmt.Errorf("could not rebuild process: %s", err) + } + case ExecutingGeneratedTest: + err = gobuild.GoTestBuild(d.processArgs[0], d.config.Packages, d.config.BuildFlags) + if err != nil { + return nil, fmt.Errorf("could not rebuild process: %s", err) + } + default: + // We cannot build a process that we didn't start, because we don't know how it was built. + return nil, fmt.Errorf("cannot rebuild a binary") + } + } + if recorded { run, stop, err2 := gdbserial.RecordAsync(d.processArgs, d.config.WorkingDir, false) if err2 != nil { @@ -450,6 +488,10 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs [] } createLogicalBreakpoint(p, addrs, oldBp) } else { + // Avoid setting a breakpoint based on address when rebuilding + if rebuild { + continue + } newBp, err := p.SetBreakpoint(oldBp.Addr, proc.UserBreakpoint, nil) if err != nil { return nil, err diff --git a/service/rpc1/server.go b/service/rpc1/server.go index 7d6f4f2f..d798307d 100644 --- a/service/rpc1/server.go +++ b/service/rpc1/server.go @@ -46,7 +46,7 @@ func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error { if s.config.Debugger.AttachPid != 0 { return errors.New("cannot restart process Delve did not create") } - _, err := s.debugger.Restart(false, "", false, nil) + _, err := s.debugger.Restart(false, "", false, nil, false) return err } diff --git a/service/rpc2/client.go b/service/rpc2/client.go index 0366b1c1..c738df26 100644 --- a/service/rpc2/client.go +++ b/service/rpc2/client.go @@ -60,15 +60,15 @@ func (c *RPCClient) Detach(kill bool) error { return c.call("Detach", DetachIn{kill}, out) } -func (c *RPCClient) Restart() ([]api.DiscardedBreakpoint, error) { +func (c *RPCClient) Restart(rebuild bool) ([]api.DiscardedBreakpoint, error) { out := new(RestartOut) - err := c.call("Restart", RestartIn{"", false, nil, false}, out) + err := c.call("Restart", RestartIn{"", false, nil, false, rebuild}, out) return out.DiscardedBreakpoints, err } -func (c *RPCClient) RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string) ([]api.DiscardedBreakpoint, error) { +func (c *RPCClient) RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, rebuild bool) ([]api.DiscardedBreakpoint, error) { out := new(RestartOut) - err := c.call("Restart", RestartIn{pos, resetArgs, newArgs, rerecord}, out) + err := c.call("Restart", RestartIn{pos, resetArgs, newArgs, rerecord, rebuild}, out) return out.DiscardedBreakpoints, err } diff --git a/service/rpc2/server.go b/service/rpc2/server.go index b9a1ad58..4d55fb17 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -76,6 +76,9 @@ type RestartIn struct { // When Rerecord is set the target will be rerecorded Rerecord bool + + // When Rebuild is set the process will be build again + Rebuild bool } type RestartOut struct { @@ -90,7 +93,7 @@ func (s *RPCServer) Restart(arg RestartIn, cb service.RPCCallback) { } var out RestartOut var err error - out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Rerecord, arg.Position, arg.ResetArgs, arg.NewArgs) + out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Rerecord, arg.Position, arg.ResetArgs, arg.NewArgs, arg.Rebuild) cb.Return(out, err) } diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index e62720ed..f532cfe7 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -3,6 +3,7 @@ package service_test import ( "flag" "fmt" + "io/ioutil" "math/rand" "net" "net/rpc" @@ -74,6 +75,9 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clie Debugger: debugger.Config{ Backend: testBackend, CheckGoVersion: true, + Packages: []string{fixture.Source}, + BuildFlags: "", // build flags can be an empty string here because the only test that uses it, does not set special flags. + ExecuteKind: debugger.ExecutingGeneratedFile, }, }) if err := server.Run(); err != nil { @@ -109,7 +113,8 @@ func TestRunWithInvalidPath(t *testing.T) { ProcessArgs: []string{"invalid_path"}, APIVersion: 2, Debugger: debugger.Config{ - Backend: testBackend, + Backend: testBackend, + ExecuteKind: debugger.ExecutingGeneratedFile, }, }) if err := server.Run(); err == nil { @@ -124,7 +129,7 @@ func TestRestart_afterExit(t *testing.T) { if !state.Exited { t.Fatal("expected initial process to have exited") } - if _, err := c.Restart(); err != nil { + if _, err := c.Restart(false); err != nil { t.Fatal(err) } if c.ProcessPid() == origPid { @@ -154,7 +159,7 @@ func TestRestart_breakpointPreservation(t *testing.T) { } t.Log("Restart") - c.Restart() + c.Restart(false) stateCh = c.Continue() state = <-stateCh if state.CurrentThread.Breakpoint.Name != "firstbreakpoint" || !state.CurrentThread.Breakpoint.Tracepoint { @@ -178,7 +183,7 @@ func TestRestart_duringStop(t *testing.T) { if state.CurrentThread.Breakpoint == nil { t.Fatal("did not hit breakpoint") } - if _, err := c.Restart(); err != nil { + if _, err := c.Restart(false); err != nil { t.Fatal(err) } if c.ProcessPid() == origPid { @@ -194,6 +199,72 @@ func TestRestart_duringStop(t *testing.T) { }) } +// This source is a slightly modified version of +// _fixtures/testenv.go. The only difference is that +// the name of the environment variable we are trying to +// read is named differently, so we can assert the code +// was actually changed in the test. +const modifiedSource = `package main + +import ( + "fmt" + "os" + "runtime" +) + +func main() { + x := os.Getenv("SOMEMODIFIEDVAR") + runtime.Breakpoint() + fmt.Printf("SOMEMODIFIEDVAR=%s\n", x) +} +` + +func TestRestart_rebuild(t *testing.T) { + // In the original fixture file the env var tested for is SOMEVAR. + os.Setenv("SOMEVAR", "bah") + + withTestClient2Extended("testenv", t, 0, func(c service.Client, f protest.Fixture) { + <-c.Continue() + + var1, err := c.EvalVariable(api.EvalScope{GoroutineID: -1}, "x", normalLoadConfig) + assertNoError(err, t, "EvalVariable") + + if var1.Value != "bah" { + t.Fatalf("expected 'bah' got %q", var1.Value) + } + + fi, err := os.Stat(f.Source) + assertNoError(err, t, "Stat fixture.Source") + + originalSource, err := ioutil.ReadFile(f.Source) + assertNoError(err, t, "Reading original source") + + // Ensure we write the original source code back after the test exits. + defer ioutil.WriteFile(f.Source, originalSource, fi.Mode()) + + // Write modified source code to the fixture file. + err = ioutil.WriteFile(f.Source, []byte(modifiedSource), fi.Mode()) + assertNoError(err, t, "Writing modified source") + + // First set our new env var and ensure later that the + // modified source code picks it up. + os.Setenv("SOMEMODIFIEDVAR", "foobar") + + // Restart the program, rebuilding from source. + _, err = c.Restart(true) + assertNoError(err, t, "Restart(true)") + + <-c.Continue() + + var1, err = c.EvalVariable(api.EvalScope{GoroutineID: -1}, "x", normalLoadConfig) + assertNoError(err, t, "EvalVariable") + + if var1.Value != "foobar" { + t.Fatalf("expected 'foobar' got %q", var1.Value) + } + }) +} + func TestClientServer_exit(t *testing.T) { protest.AllowRecording(t) withTestClient2("continuetestprog", t, func(c service.Client) { @@ -1350,7 +1421,7 @@ func TestClientServer_RestartBreakpointPosition(t *testing.T) { } _, err = c.Halt() assertNoError(err, t, "Halt") - _, err = c.Restart() + _, err = c.Restart(false) assertNoError(err, t, "Restart") bps, err := c.ListBreakpoints() assertNoError(err, t, "ListBreakpoints") @@ -1574,7 +1645,8 @@ func TestAcceptMulticlient(t *testing.T) { AcceptMulti: true, DisconnectChan: disconnectChan, Debugger: debugger.Config{ - Backend: testBackend, + Backend: testBackend, + ExecuteKind: debugger.ExecutingGeneratedTest, }, }) if err := server.Run(); err != nil { @@ -1826,7 +1898,7 @@ func TestRerecord(t *testing.T) { t0 := gett() - _, err = c.RestartFrom(false, "", false, nil) + _, err = c.RestartFrom(false, "", false, nil, false) assertNoError(err, t, "First restart") t1 := gett() @@ -1836,7 +1908,7 @@ func TestRerecord(t *testing.T) { time.Sleep(2 * time.Second) // make sure that we're not running inside the same second - _, err = c.RestartFrom(true, "", false, nil) + _, err = c.RestartFrom(true, "", false, nil, false) assertNoError(err, t, "Second restart") t2 := gett() @@ -1901,7 +1973,7 @@ func TestStopRecording(t *testing.T) { // try rerecording go func() { - c.RestartFrom(true, "", false, nil) + c.RestartFrom(true, "", false, nil, false) }() time.Sleep(time.Second) // hopefully the re-recording started...