Repository: go-delve/delve
Pull request #2475 (2021-05-10 20:34:19)

service/dap: warn users of debugging optimized functions
We want to be able to warn users about debugging optimized functions since variables
may not be what they expect. There is currently no way to show the users this warning
without returning an error to the entire request, so we can show this by annotating the
variable scopes instead.

Updates golang/vscode-go#1471

suzmue: Screen shot of the variable pane:

![Screen Shot 2021-05-10 at 2 17 05 PM](https://user-images.githubusercontent.com/6634754/117721181-8a23e980-b1ad-11eb-9a97-00b2b77982e4.png)

cc @polinasok @hyangah 

Patch:
diff --git a/pkg/gobuild/gobuild.go b/pkg/gobuild/gobuild.go
index 0f3cc051..bf571180 100644
--- a/pkg/gobuild/gobuild.go
+++ b/pkg/gobuild/gobuild.go
@@ -7,6 +7,7 @@ import (
 	"os"
 	"os/exec"
 	"runtime"
+	"strings"
 	"time"
 
 	"github.com/go-delve/delve/pkg/config"
@@ -60,7 +61,7 @@ func GoBuild(debugname string, pkgs []string, buildflags string) error {
 
 // GoBuildCombinedOutput builds non-test files in 'pkgs' with the specified 'buildflags'
 // and writes the output at 'debugname'.
-func GoBuildCombinedOutput(debugname string, pkgs []string, buildflags string) ([]byte, error) {
+func GoBuildCombinedOutput(debugname string, pkgs []string, buildflags string) (string, []byte, error) {
 	args := goBuildArgs(debugname, pkgs, buildflags, false)
 	return gocommandCombinedOutput("build", args...)
 }
@@ -74,7 +75,7 @@ func GoTestBuild(debugname string, pkgs []string, buildflags string) error {
 
 // GoTestBuildCombinedOutput builds test files 'pkgs' with the specified 'buildflags'
 // and writes the output at 'debugname'.
-func GoTestBuildCombinedOutput(debugname string, pkgs []string, buildflags string) ([]byte, error) {
+func GoTestBuildCombinedOutput(debugname string, pkgs []string, buildflags string) (string, []byte, error) {
 	args := goBuildArgs(debugname, pkgs, buildflags, true)
 	return gocommandCombinedOutput("test", args...)
 }
@@ -93,19 +94,21 @@ func goBuildArgs(debugname string, pkgs []string, buildflags string, isTest bool
 }
 
 func gocommandRun(command string, args ...string) error {
-	goBuild := gocommandExecCmd(command, args...)
+	_, goBuild := gocommandExecCmd(command, args...)
 	goBuild.Stderr = os.Stdout
 	goBuild.Stdout = os.Stderr
 	return goBuild.Run()
 }
 
-func gocommandCombinedOutput(command string, args ...string) ([]byte, error) {
-	return gocommandExecCmd(command, args...).CombinedOutput()
+func gocommandCombinedOutput(command string, args ...string) (string, []byte, error) {
+	buildCmd, goBuild := gocommandExecCmd(command, args...)
+	out, err := goBuild.CombinedOutput()
+	return buildCmd, out, err
 }
 
-func gocommandExecCmd(command string, args ...string) *exec.Cmd {
+func gocommandExecCmd(command string, args ...string) (string, *exec.Cmd) {
 	allargs := []string{command}
 	allargs = append(allargs, args...)
 	goBuild := exec.Command("go", allargs...)
-	return goBuild
+	return strings.Join(append([]string{"go"}, allargs...), " "), goBuild
 }
diff --git a/service/dap/server.go b/service/dap/server.go
index bf293d5d..fa007fa3 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -399,8 +399,7 @@ func (s *Server) handleRequest(request dap.Message) {
 	// We have a couple of options for handling these without blocking
 	// the request loop indefinitely when we are in running state.
 	// --1-- Return a dummy response or an error right away.
-	// --2-- Halt execution, process the request, resume execution.
-	// TODO(polina): do this for setting breakpoints
+	// --2-- Halt execution, process the request, maybe resume execution.
 	// --3-- Handle such requests asynchronously and let them block until
 	// the process stops or terminates (e.g. using a channel and a single
 	// goroutine to preserve the order). This might not be appropriate
@@ -427,6 +426,28 @@ func (s *Server) handleRequest(request dap.Message) {
 				Body:     dap.ThreadsResponseBody{Threads: []dap.Thread{{Id: -1, Name: "Current"}}},
 			}
 			s.send(response)
+		case *dap.SetBreakpointsRequest:
+			s.log.Debug("halting execution to set breakpoints")
+			_, err := s.debugger.Command(&api.DebuggerCommand{Name: api.Halt}, nil)
+			if err != nil {
+				s.sendErrorResponse(request.Request, UnableToSetBreakpoints, "Unable to set or clear breakpoints", err.Error())
+				return
+			}
+			s.onSetBreakpointsRequest(request)
+			// TODO(polina): consider resuming execution here automatically after suppressing
+			// a stop event when an operation in doRunCommand returns. In case that operation
+			// was already stopping for a different reason, we would need to examine the state
+			// that is returned to determine if this halt was the cause of the stop or not.
+			// We should stop with an event and not resume if one of the following is true:
+			// - StopReason is anything but manual
+			// - Any thread has a breakpoint or CallReturn set
+			// - NextInProgress is false and the last command sent by the user was: next,
+			//   step, stepOut, reverseNext, reverseStep or reverseStepOut
+			// Otherwise, we can skip the stop event and resume the temporarily
+			// interrupted process execution with api.DirectionCongruentContinue.
+			// For this to apply in cases other than api.Continue, we would also need to
+			// introduce a new version of halt that skips ClearInternalBreakpoints
+			// in proc.(*Target).Continue, leaving NextInProgress as true.
 		default:
 			r := request.(dap.RequestMessage).GetRequest()
 			s.sendErrorResponse(*r, DebuggeeIsRunning, fmt.Sprintf("Unable to process `%s`", r.Command), "debuggee is running")
@@ -723,17 +744,24 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		}
 
 		s.log.Debugf("building binary at %s", debugbinary)
+		var cmd string
 		var out []byte
 		switch mode {
 		case "debug":
-			out, err = gobuild.GoBuildCombinedOutput(debugbinary, []string{program}, buildFlags)
+			cmd, out, err = gobuild.GoBuildCombinedOutput(debugbinary, []string{program}, buildFlags)
 		case "test":
-			out, err = gobuild.GoTestBuildCombinedOutput(debugbinary, []string{program}, buildFlags)
+			cmd, out, err = gobuild.GoTestBuildCombinedOutput(debugbinary, []string{program}, buildFlags)
 		}
 		if err != nil {
+			s.send(&dap.OutputEvent{
+				Event: *newEvent("output"),
+				Body: dap.OutputEventBody{
+					Output:   fmt.Sprintf("Build Error: %s\n%s (%s)\n", cmd, strings.TrimSpace(string(out)), err.Error()),
+					Category: "stderr",
+				}})
 			s.sendErrorResponse(request.Request,
 				FailedToLaunch, "Failed to launch",
-				fmt.Sprintf("Build error: %s (%s)", strings.TrimSpace(string(out)), err.Error()))
+				"Build error: Check the debug console for details.")
 			return
 		}
 		program = debugbinary
@@ -1058,7 +1086,7 @@ func (s *Server) onConfigurationDoneRequest(request *dap.ConfigurationDoneReques
 	}
 	s.send(&dap.ConfigurationDoneResponse{Response: *newResponse(request.Request)})
 	if !s.args.stopOnEntry {
-		s.doCommand(api.Continue, asyncSetupDone)
+		s.doRunCommand(api.Continue, asyncSetupDone)
 	}
 }
 
@@ -1068,7 +1096,7 @@ func (s *Server) onContinueRequest(request *dap.ContinueRequest, asyncSetupDone
 	s.send(&dap.ContinueResponse{
 		Response: *newResponse(request.Request),
 		Body:     dap.ContinueResponseBody{AllThreadsContinued: true}})
-	s.doCommand(api.Continue, asyncSetupDone)
+	s.doRunCommand(api.Continue, asyncSetupDone)
 }
 
 func fnName(loc *proc.Location) string {
@@ -1214,7 +1242,7 @@ func stoppedGoroutineID(state *api.DebuggerState) (id int) {
 	return id
 }
 
-// doStepCommand is a wrapper around doCommand that
+// doStepCommand is a wrapper around doRunCommand that
 // first switches selected goroutine. asyncSetupDone is
 // a channel that will be closed to signal that an
 // asynchornous command has completed setup or was interrupted
@@ -1238,7 +1266,7 @@ func (s *Server) doStepCommand(command string, threadId int, asyncSetupDone chan
 		s.send(stopped)
 		return
 	}
-	s.doCommand(command, asyncSetupDone)
+	s.doRunCommand(command, asyncSetupDone)
 }
 
 // onPauseRequest sends a not-yet-implemented error response.
@@ -1310,13 +1338,23 @@ func (s *Server) onScopesRequest(request *dap.ScopesRequest) {
 	goid := sf.(stackFrame).goroutineID
 	frame := sf.(stackFrame).frameIndex
 
+	// Check if the function is optimized.
+	fn, err := s.debugger.Function(goid, frame, 0, DefaultLoadConfig)
+	if fn == nil || err != nil {
+		s.sendErrorResponse(request.Request, UnableToListArgs, "Unable to find enclosing function", err.Error())
+		return
+	}
+	suffix := ""
+	if fn.Optimized() {
+		suffix = " (warning: optimized function)"
+	}
 	// Retrieve arguments
 	args, err := s.debugger.FunctionArguments(goid, frame, 0, DefaultLoadConfig)
 	if err != nil {
 		s.sendErrorResponse(request.Request, UnableToListArgs, "Unable to list args", err.Error())
 		return
 	}
-	argScope := &fullyQualifiedVariable{&proc.Variable{Name: "Arguments", Children: slicePtrVarToSliceVar(args)}, "", true}
+	argScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Arguments%s", suffix), Children: slicePtrVarToSliceVar(args)}, "", true}
 
 	// Retrieve local variables
 	locals, err := s.debugger.LocalVariables(goid, frame, 0, DefaultLoadConfig)
@@ -1324,7 +1362,7 @@ func (s *Server) onScopesRequest(request *dap.ScopesRequest) {
 		s.sendErrorResponse(request.Request, UnableToListLocals, "Unable to list locals", err.Error())
 		return
 	}
-	locScope := &fullyQualifiedVariable{&proc.Variable{Name: "Locals", Children: slicePtrVarToSliceVar(locals)}, "", true}
+	locScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Locals%s", suffix), Children: slicePtrVarToSliceVar(locals)}, "", true}
 
 	scopeArgs := dap.Scope{Name: argScope.Name, VariablesReference: s.variableHandles.create(argScope)}
 	scopeLocals := dap.Scope{Name: locScope.Name, VariablesReference: s.variableHandles.create(locScope)}
@@ -1951,14 +1989,14 @@ func (s *Server) resetHandlesForStoppedEvent() {
 	s.variableHandles.reset()
 }
 
-// doCommand runs a debugger command until it stops on
+// doRunCommand runs a debugger command until it stops on
 // termination, error, breakpoint, etc, when an appropriate
 // event needs to be sent to the client. asyncSetupDone is
 // a channel that will be closed to signal that an
 // asynchornous command has completed setup or was interrupted
 // due to an error, so the server is ready to receive new requests.
-func (s *Server) doCommand(command string, asyncSetupDone chan struct{}) {
-	// TODO(polina): it appears that debugger.Command doesn't close
+func (s *Server) doRunCommand(command string, asyncSetupDone chan struct{}) {
+	// TODO(polina): it appears that debugger.Command doesn't always close
 	// asyncSetupDone (e.g. when having an error next while nexting).
 	// So we should always close it ourselves just in case.
 	defer s.asyncCommandDone(asyncSetupDone)
@@ -1968,6 +2006,9 @@ func (s *Server) doCommand(command string, asyncSetupDone chan struct{}) {
 		return
 	}
 
+	stopReason := s.debugger.StopReason()
+	s.log.Debugf("%q command stopped - reason %q", command, stopReason)
+
 	s.resetHandlesForStoppedEvent()
 	stopped := &dap.StoppedEvent{Event: *newEvent("stopped")}
 	stopped.Body.AllThreadsStopped = true
@@ -1975,9 +2016,7 @@ func (s *Server) doCommand(command string, asyncSetupDone chan struct{}) {
 	if err == nil {
 		stopped.Body.ThreadId = stoppedGoroutineID(state)
 
-		sr := s.debugger.StopReason()
-		s.log.Debugf("%q command stopped - reason %q", command, sr)
-		switch sr {
+		switch stopReason {
 		case proc.StopNextFinished:
 			stopped.Body.Reason = "step"
 		case proc.StopManual: // triggered by halt
diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go
index 14ecd46d..7b9bf756 100644
--- a/service/debugger/debugger.go
+++ b/service/debugger/debugger.go
@@ -1357,6 +1357,18 @@ func (d *Debugger) FunctionArguments(goid, frame, deferredCall int, cfg proc.Loa
 	return s.FunctionArguments(cfg)
 }
 
+// Function returns the current function.
+func (d *Debugger) Function(goid, frame, deferredCall int, cfg proc.LoadConfig) (*proc.Function, error) {
+	d.targetMutex.Lock()
+	defer d.targetMutex.Unlock()
+
+	s, err := proc.ConvertEvalScope(d.target, goid, frame, deferredCall)
+	if err != nil {
+		return nil, err
+	}
+	return s.Fn, nil
+}
+
 // EvalVariableInScope will attempt to evaluate the variable represented by 'symbol'
 // in the scope provided.
 func (d *Debugger) EvalVariableInScope(goid, frame, deferredCall int, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) {


Test patch:
diff --git a/service/dap/daptest/gen/main.go b/service/dap/daptest/gen/main.go
index 41aa002a..14781af4 100644
--- a/service/dap/daptest/gen/main.go
+++ b/service/dap/daptest/gen/main.go
@@ -36,7 +36,7 @@ func (c *Client) Expect{{.}}(t *testing.T) *dap.{{.}} {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.{{.}})
 	if !ok {
-		t.Fatalf("got %q, want *dap.{{.}}", m)
+		t.Fatalf("got %#v, want *dap.{{.}}", m)
 	}
 	return r
 }{{end}}
diff --git a/service/dap/daptest/resp.go b/service/dap/daptest/resp.go
index 086455ba..7002afdd 100644
--- a/service/dap/daptest/resp.go
+++ b/service/dap/daptest/resp.go
@@ -16,7 +16,7 @@ func (c *Client) ExpectAttachResponse(t *testing.T) *dap.AttachResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.AttachResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.AttachResponse", m)
+		t.Fatalf("got %#v, want *dap.AttachResponse", m)
 	}
 	return r
 }
@@ -28,7 +28,7 @@ func (c *Client) ExpectBreakpointEvent(t *testing.T) *dap.BreakpointEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.BreakpointEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.BreakpointEvent", m)
+		t.Fatalf("got %#v, want *dap.BreakpointEvent", m)
 	}
 	return r
 }
@@ -40,7 +40,7 @@ func (c *Client) ExpectBreakpointLocationsResponse(t *testing.T) *dap.Breakpoint
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.BreakpointLocationsResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.BreakpointLocationsResponse", m)
+		t.Fatalf("got %#v, want *dap.BreakpointLocationsResponse", m)
 	}
 	return r
 }
@@ -52,7 +52,7 @@ func (c *Client) ExpectCancelResponse(t *testing.T) *dap.CancelResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.CancelResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.CancelResponse", m)
+		t.Fatalf("got %#v, want *dap.CancelResponse", m)
 	}
 	return r
 }
@@ -64,7 +64,7 @@ func (c *Client) ExpectCapabilitiesEvent(t *testing.T) *dap.CapabilitiesEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.CapabilitiesEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.CapabilitiesEvent", m)
+		t.Fatalf("got %#v, want *dap.CapabilitiesEvent", m)
 	}
 	return r
 }
@@ -76,7 +76,7 @@ func (c *Client) ExpectCompletionsResponse(t *testing.T) *dap.CompletionsRespons
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.CompletionsResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.CompletionsResponse", m)
+		t.Fatalf("got %#v, want *dap.CompletionsResponse", m)
 	}
 	return r
 }
@@ -88,7 +88,7 @@ func (c *Client) ExpectConfigurationDoneResponse(t *testing.T) *dap.Configuratio
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ConfigurationDoneResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ConfigurationDoneResponse", m)
+		t.Fatalf("got %#v, want *dap.ConfigurationDoneResponse", m)
 	}
 	return r
 }
@@ -100,7 +100,7 @@ func (c *Client) ExpectContinueResponse(t *testing.T) *dap.ContinueResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ContinueResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ContinueResponse", m)
+		t.Fatalf("got %#v, want *dap.ContinueResponse", m)
 	}
 	return r
 }
@@ -112,7 +112,7 @@ func (c *Client) ExpectContinuedEvent(t *testing.T) *dap.ContinuedEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ContinuedEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ContinuedEvent", m)
+		t.Fatalf("got %#v, want *dap.ContinuedEvent", m)
 	}
 	return r
 }
@@ -124,7 +124,7 @@ func (c *Client) ExpectDataBreakpointInfoResponse(t *testing.T) *dap.DataBreakpo
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.DataBreakpointInfoResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.DataBreakpointInfoResponse", m)
+		t.Fatalf("got %#v, want *dap.DataBreakpointInfoResponse", m)
 	}
 	return r
 }
@@ -136,7 +136,7 @@ func (c *Client) ExpectDisassembleResponse(t *testing.T) *dap.DisassembleRespons
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.DisassembleResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.DisassembleResponse", m)
+		t.Fatalf("got %#v, want *dap.DisassembleResponse", m)
 	}
 	return r
 }
@@ -148,7 +148,7 @@ func (c *Client) ExpectDisconnectResponse(t *testing.T) *dap.DisconnectResponse
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.DisconnectResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.DisconnectResponse", m)
+		t.Fatalf("got %#v, want *dap.DisconnectResponse", m)
 	}
 	return r
 }
@@ -160,7 +160,7 @@ func (c *Client) ExpectErrorResponse(t *testing.T) *dap.ErrorResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ErrorResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ErrorResponse", m)
+		t.Fatalf("got %#v, want *dap.ErrorResponse", m)
 	}
 	return r
 }
@@ -172,7 +172,7 @@ func (c *Client) ExpectEvaluateResponse(t *testing.T) *dap.EvaluateResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.EvaluateResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.EvaluateResponse", m)
+		t.Fatalf("got %#v, want *dap.EvaluateResponse", m)
 	}
 	return r
 }
@@ -184,7 +184,7 @@ func (c *Client) ExpectExceptionInfoResponse(t *testing.T) *dap.ExceptionInfoRes
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ExceptionInfoResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ExceptionInfoResponse", m)
+		t.Fatalf("got %#v, want *dap.ExceptionInfoResponse", m)
 	}
 	return r
 }
@@ -196,7 +196,7 @@ func (c *Client) ExpectExitedEvent(t *testing.T) *dap.ExitedEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ExitedEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ExitedEvent", m)
+		t.Fatalf("got %#v, want *dap.ExitedEvent", m)
 	}
 	return r
 }
@@ -208,7 +208,7 @@ func (c *Client) ExpectGotoResponse(t *testing.T) *dap.GotoResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.GotoResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.GotoResponse", m)
+		t.Fatalf("got %#v, want *dap.GotoResponse", m)
 	}
 	return r
 }
@@ -220,7 +220,7 @@ func (c *Client) ExpectGotoTargetsResponse(t *testing.T) *dap.GotoTargetsRespons
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.GotoTargetsResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.GotoTargetsResponse", m)
+		t.Fatalf("got %#v, want *dap.GotoTargetsResponse", m)
 	}
 	return r
 }
@@ -232,7 +232,7 @@ func (c *Client) ExpectInitializeResponse(t *testing.T) *dap.InitializeResponse
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.InitializeResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.InitializeResponse", m)
+		t.Fatalf("got %#v, want *dap.InitializeResponse", m)
 	}
 	return r
 }
@@ -244,7 +244,7 @@ func (c *Client) ExpectInitializedEvent(t *testing.T) *dap.InitializedEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.InitializedEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.InitializedEvent", m)
+		t.Fatalf("got %#v, want *dap.InitializedEvent", m)
 	}
 	return r
 }
@@ -256,7 +256,7 @@ func (c *Client) ExpectInvalidatedEvent(t *testing.T) *dap.InvalidatedEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.InvalidatedEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.InvalidatedEvent", m)
+		t.Fatalf("got %#v, want *dap.InvalidatedEvent", m)
 	}
 	return r
 }
@@ -268,7 +268,7 @@ func (c *Client) ExpectLaunchResponse(t *testing.T) *dap.LaunchResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.LaunchResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.LaunchResponse", m)
+		t.Fatalf("got %#v, want *dap.LaunchResponse", m)
 	}
 	return r
 }
@@ -280,7 +280,7 @@ func (c *Client) ExpectLoadedSourceEvent(t *testing.T) *dap.LoadedSourceEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.LoadedSourceEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.LoadedSourceEvent", m)
+		t.Fatalf("got %#v, want *dap.LoadedSourceEvent", m)
 	}
 	return r
 }
@@ -292,7 +292,7 @@ func (c *Client) ExpectLoadedSourcesResponse(t *testing.T) *dap.LoadedSourcesRes
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.LoadedSourcesResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.LoadedSourcesResponse", m)
+		t.Fatalf("got %#v, want *dap.LoadedSourcesResponse", m)
 	}
 	return r
 }
@@ -304,7 +304,7 @@ func (c *Client) ExpectModuleEvent(t *testing.T) *dap.ModuleEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ModuleEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ModuleEvent", m)
+		t.Fatalf("got %#v, want *dap.ModuleEvent", m)
 	}
 	return r
 }
@@ -316,7 +316,7 @@ func (c *Client) ExpectModulesResponse(t *testing.T) *dap.ModulesResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ModulesResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ModulesResponse", m)
+		t.Fatalf("got %#v, want *dap.ModulesResponse", m)
 	}
 	return r
 }
@@ -328,7 +328,7 @@ func (c *Client) ExpectNextResponse(t *testing.T) *dap.NextResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.NextResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.NextResponse", m)
+		t.Fatalf("got %#v, want *dap.NextResponse", m)
 	}
 	return r
 }
@@ -340,7 +340,7 @@ func (c *Client) ExpectOutputEvent(t *testing.T) *dap.OutputEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.OutputEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.OutputEvent", m)
+		t.Fatalf("got %#v, want *dap.OutputEvent", m)
 	}
 	return r
 }
@@ -352,7 +352,7 @@ func (c *Client) ExpectPauseResponse(t *testing.T) *dap.PauseResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.PauseResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.PauseResponse", m)
+		t.Fatalf("got %#v, want *dap.PauseResponse", m)
 	}
 	return r
 }
@@ -364,7 +364,7 @@ func (c *Client) ExpectProcessEvent(t *testing.T) *dap.ProcessEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ProcessEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ProcessEvent", m)
+		t.Fatalf("got %#v, want *dap.ProcessEvent", m)
 	}
 	return r
 }
@@ -376,7 +376,7 @@ func (c *Client) ExpectProgressEndEvent(t *testing.T) *dap.ProgressEndEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ProgressEndEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ProgressEndEvent", m)
+		t.Fatalf("got %#v, want *dap.ProgressEndEvent", m)
 	}
 	return r
 }
@@ -388,7 +388,7 @@ func (c *Client) ExpectProgressStartEvent(t *testing.T) *dap.ProgressStartEvent
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ProgressStartEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ProgressStartEvent", m)
+		t.Fatalf("got %#v, want *dap.ProgressStartEvent", m)
 	}
 	return r
 }
@@ -400,7 +400,7 @@ func (c *Client) ExpectProgressUpdateEvent(t *testing.T) *dap.ProgressUpdateEven
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ProgressUpdateEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ProgressUpdateEvent", m)
+		t.Fatalf("got %#v, want *dap.ProgressUpdateEvent", m)
 	}
 	return r
 }
@@ -412,7 +412,7 @@ func (c *Client) ExpectReadMemoryResponse(t *testing.T) *dap.ReadMemoryResponse
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ReadMemoryResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ReadMemoryResponse", m)
+		t.Fatalf("got %#v, want *dap.ReadMemoryResponse", m)
 	}
 	return r
 }
@@ -424,7 +424,7 @@ func (c *Client) ExpectRestartFrameResponse(t *testing.T) *dap.RestartFrameRespo
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.RestartFrameResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.RestartFrameResponse", m)
+		t.Fatalf("got %#v, want *dap.RestartFrameResponse", m)
 	}
 	return r
 }
@@ -436,7 +436,7 @@ func (c *Client) ExpectRestartResponse(t *testing.T) *dap.RestartResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.RestartResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.RestartResponse", m)
+		t.Fatalf("got %#v, want *dap.RestartResponse", m)
 	}
 	return r
 }
@@ -448,7 +448,7 @@ func (c *Client) ExpectReverseContinueResponse(t *testing.T) *dap.ReverseContinu
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ReverseContinueResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ReverseContinueResponse", m)
+		t.Fatalf("got %#v, want *dap.ReverseContinueResponse", m)
 	}
 	return r
 }
@@ -460,7 +460,7 @@ func (c *Client) ExpectRunInTerminalResponse(t *testing.T) *dap.RunInTerminalRes
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.RunInTerminalResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.RunInTerminalResponse", m)
+		t.Fatalf("got %#v, want *dap.RunInTerminalResponse", m)
 	}
 	return r
 }
@@ -472,7 +472,7 @@ func (c *Client) ExpectScopesResponse(t *testing.T) *dap.ScopesResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ScopesResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ScopesResponse", m)
+		t.Fatalf("got %#v, want *dap.ScopesResponse", m)
 	}
 	return r
 }
@@ -484,7 +484,7 @@ func (c *Client) ExpectSetBreakpointsResponse(t *testing.T) *dap.SetBreakpointsR
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.SetBreakpointsResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.SetBreakpointsResponse", m)
+		t.Fatalf("got %#v, want *dap.SetBreakpointsResponse", m)
 	}
 	return r
 }
@@ -496,7 +496,7 @@ func (c *Client) ExpectSetDataBreakpointsResponse(t *testing.T) *dap.SetDataBrea
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.SetDataBreakpointsResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.SetDataBreakpointsResponse", m)
+		t.Fatalf("got %#v, want *dap.SetDataBreakpointsResponse", m)
 	}
 	return r
 }
@@ -508,7 +508,7 @@ func (c *Client) ExpectSetExceptionBreakpointsResponse(t *testing.T) *dap.SetExc
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.SetExceptionBreakpointsResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.SetExceptionBreakpointsResponse", m)
+		t.Fatalf("got %#v, want *dap.SetExceptionBreakpointsResponse", m)
 	}
 	return r
 }
@@ -520,7 +520,7 @@ func (c *Client) ExpectSetExpressionResponse(t *testing.T) *dap.SetExpressionRes
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.SetExpressionResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.SetExpressionResponse", m)
+		t.Fatalf("got %#v, want *dap.SetExpressionResponse", m)
 	}
 	return r
 }
@@ -532,7 +532,7 @@ func (c *Client) ExpectSetFunctionBreakpointsResponse(t *testing.T) *dap.SetFunc
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.SetFunctionBreakpointsResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.SetFunctionBreakpointsResponse", m)
+		t.Fatalf("got %#v, want *dap.SetFunctionBreakpointsResponse", m)
 	}
 	return r
 }
@@ -544,7 +544,7 @@ func (c *Client) ExpectSetInstructionBreakpointsResponse(t *testing.T) *dap.SetI
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.SetInstructionBreakpointsResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.SetInstructionBreakpointsResponse", m)
+		t.Fatalf("got %#v, want *dap.SetInstructionBreakpointsResponse", m)
 	}
 	return r
 }
@@ -556,7 +556,7 @@ func (c *Client) ExpectSetVariableResponse(t *testing.T) *dap.SetVariableRespons
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.SetVariableResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.SetVariableResponse", m)
+		t.Fatalf("got %#v, want *dap.SetVariableResponse", m)
 	}
 	return r
 }
@@ -568,7 +568,7 @@ func (c *Client) ExpectSourceResponse(t *testing.T) *dap.SourceResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.SourceResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.SourceResponse", m)
+		t.Fatalf("got %#v, want *dap.SourceResponse", m)
 	}
 	return r
 }
@@ -580,7 +580,7 @@ func (c *Client) ExpectStackTraceResponse(t *testing.T) *dap.StackTraceResponse
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.StackTraceResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.StackTraceResponse", m)
+		t.Fatalf("got %#v, want *dap.StackTraceResponse", m)
 	}
 	return r
 }
@@ -592,7 +592,7 @@ func (c *Client) ExpectStepBackResponse(t *testing.T) *dap.StepBackResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.StepBackResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.StepBackResponse", m)
+		t.Fatalf("got %#v, want *dap.StepBackResponse", m)
 	}
 	return r
 }
@@ -604,7 +604,7 @@ func (c *Client) ExpectStepInResponse(t *testing.T) *dap.StepInResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.StepInResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.StepInResponse", m)
+		t.Fatalf("got %#v, want *dap.StepInResponse", m)
 	}
 	return r
 }
@@ -616,7 +616,7 @@ func (c *Client) ExpectStepInTargetsResponse(t *testing.T) *dap.StepInTargetsRes
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.StepInTargetsResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.StepInTargetsResponse", m)
+		t.Fatalf("got %#v, want *dap.StepInTargetsResponse", m)
 	}
 	return r
 }
@@ -628,7 +628,7 @@ func (c *Client) ExpectStepOutResponse(t *testing.T) *dap.StepOutResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.StepOutResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.StepOutResponse", m)
+		t.Fatalf("got %#v, want *dap.StepOutResponse", m)
 	}
 	return r
 }
@@ -640,7 +640,7 @@ func (c *Client) ExpectStoppedEvent(t *testing.T) *dap.StoppedEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.StoppedEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.StoppedEvent", m)
+		t.Fatalf("got %#v, want *dap.StoppedEvent", m)
 	}
 	return r
 }
@@ -652,7 +652,7 @@ func (c *Client) ExpectTerminateResponse(t *testing.T) *dap.TerminateResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.TerminateResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.TerminateResponse", m)
+		t.Fatalf("got %#v, want *dap.TerminateResponse", m)
 	}
 	return r
 }
@@ -664,7 +664,7 @@ func (c *Client) ExpectTerminateThreadsResponse(t *testing.T) *dap.TerminateThre
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.TerminateThreadsResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.TerminateThreadsResponse", m)
+		t.Fatalf("got %#v, want *dap.TerminateThreadsResponse", m)
 	}
 	return r
 }
@@ -676,7 +676,7 @@ func (c *Client) ExpectTerminatedEvent(t *testing.T) *dap.TerminatedEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.TerminatedEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.TerminatedEvent", m)
+		t.Fatalf("got %#v, want *dap.TerminatedEvent", m)
 	}
 	return r
 }
@@ -688,7 +688,7 @@ func (c *Client) ExpectThreadEvent(t *testing.T) *dap.ThreadEvent {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ThreadEvent)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ThreadEvent", m)
+		t.Fatalf("got %#v, want *dap.ThreadEvent", m)
 	}
 	return r
 }
@@ -700,7 +700,7 @@ func (c *Client) ExpectThreadsResponse(t *testing.T) *dap.ThreadsResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.ThreadsResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.ThreadsResponse", m)
+		t.Fatalf("got %#v, want *dap.ThreadsResponse", m)
 	}
 	return r
 }
@@ -712,7 +712,7 @@ func (c *Client) ExpectVariablesResponse(t *testing.T) *dap.VariablesResponse {
 	m := c.ExpectMessage(t)
 	r, ok := m.(*dap.VariablesResponse)
 	if !ok {
-		t.Fatalf("got %q, want *dap.VariablesResponse", m)
+		t.Fatalf("got %#v, want *dap.VariablesResponse", m)
 	}
 	return r
 }
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 063673e3..2ec01c35 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -47,7 +47,11 @@ func TestMain(m *testing.M) {
 
 // name is for _fixtures/<name>.go
 func runTest(t *testing.T, name string, test func(c *daptest.Client, f protest.Fixture)) {
-	var buildFlags protest.BuildFlags = protest.AllNonOptimized
+	runTestBuildFlags(t, name, test, protest.AllNonOptimized)
+}
+
+// name is for _fixtures/<name>.go
+func runTestBuildFlags(t *testing.T, name string, test func(c *daptest.Client, f protest.Fixture), buildFlags protest.BuildFlags) {
 	fixture := protest.BuildFixture(name, buildFlags)
 
 	// Start the DAP server.
@@ -437,9 +441,9 @@ func TestContinueOnEntry(t *testing.T) {
 				}
 				// Single current thread is sent when the program is running
 				// because DAP spec expects at least one thread.
-				// TODO(polina): accept empty already-terminated response here as well?
-				if len(m.Body.Threads) != 1 || m.Body.Threads[0].Id != -1 || m.Body.Threads[0].Name != "Current" {
-					t.Errorf("\ngot  %#v\nwant Id=-1, Name=\"Current\"", m.Body.Threads)
+				// Also accept empty already-terminated response.
+				if len(m.Body.Threads) != 0 && (len(m.Body.Threads) != 1 || m.Body.Threads[0].Id != -1 || m.Body.Threads[0].Name != "Current") {
+					t.Errorf("\ngot  %#v\nwant Id=-1, Name=\"Current\" or empty", m.Body.Threads)
 				}
 			case *dap.TerminatedEvent:
 			default:
@@ -502,8 +506,12 @@ func TestPreSetBreakpoint(t *testing.T) {
 			msg := client.ExpectMessage(t)
 			switch m := msg.(type) {
 			case *dap.ThreadsResponse:
-				if len(m.Body.Threads) != 1 || m.Body.Threads[0].Id != -1 || m.Body.Threads[0].Name != "Current" {
-					t.Errorf("\ngot  %#v\nwant Id=-1, Name=\"Current\"", m.Body.Threads)
+				// If the thread request arrived while the program was running, we expect to get the dummy response
+				// with a single goroutine "Current".
+				// If the thread request arrived after the stop, we should get the goroutine stopped at main.Increment.
+				if (len(m.Body.Threads) != 1 || m.Body.Threads[0].Id != -1 || m.Body.Threads[0].Name != "Current") &&
+					(len(m.Body.Threads) < 1 || m.Body.Threads[0].Id != 1 || !strings.HasPrefix(m.Body.Threads[0].Name, "* [Go 1] main.Increment")) {
+					t.Errorf("\ngot  %#v\nwant Id=-1, Name=\"Current\" or Id=1, Name=\"* [Go 1] main.Increment ...\"", m.Body.Threads)
 				}
 			case *dap.StoppedEvent:
 				if m.Body.Reason != "breakpoint" || m.Body.ThreadId != 1 || !m.Body.AllThreadsStopped {
@@ -1379,6 +1387,63 @@ func TestScopesAndVariablesRequests2(t *testing.T) {
 	})
 }
 
+// TestScopesRequestsOptimized executes to a breakpoint and tests different
+// that the names of the "Locals" and "Arguments" scopes are correctly annotated with
+// a warning about debugging an optimized function.
+func TestScopesRequestsOptimized(t *testing.T) {
+	runTestBuildFlags(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequestWithArgs(map[string]interface{}{
+					"mode": "exec", "program": fixture.Path, "showGlobalVariables": true,
+				})
+			},
+			// Breakpoints are set within the program
+			fixture.Source, []int{},
+			[]onBreakpoint{{
+				// Stop at first breakpoint
+				execute: func() {
+					client.StackTraceRequest(1, 0, 20)
+					stack := client.ExpectStackTraceResponse(t)
+
+					startLineno := 66
+					if runtime.GOOS == "windows" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
+						// Go1.15 on windows inserts a NOP after the call to
+						// runtime.Breakpoint and marks it same line as the
+						// runtime.Breakpoint call, making this flaky, so skip the line check.
+						startLineno = -1
+					}
+
+					expectStackFrames(t, stack, "main.foobar", startLineno, 1000, 4, 4)
+
+					client.ScopesRequest(1000)
+					scopes := client.ExpectScopesResponse(t)
+					expectScope(t, scopes, 0, "Arguments (warning: optimized function)", 1000)
+					expectScope(t, scopes, 1, "Locals (warning: optimized function)", 1001)
+					expectScope(t, scopes, 2, "Globals (package main)", 1002)
+				},
+				disconnect: false,
+			}, {
+				// Stop at second breakpoint
+				execute: func() {
+					// Frame ids get reset at each breakpoint.
+					client.StackTraceRequest(1, 0, 20)
+					stack := client.ExpectStackTraceResponse(t)
+					expectStackFrames(t, stack, "main.barfoo", 27, 1000, 5, 5)
+
+					client.ScopesRequest(1000)
+					scopes := client.ExpectScopesResponse(t)
+					expectScope(t, scopes, 0, "Arguments (warning: optimized function)", 1000)
+					expectScope(t, scopes, 1, "Locals (warning: optimized function)", 1001)
+					expectScope(t, scopes, 2, "Globals (package main)", 1002)
+				},
+				disconnect: false,
+			}})
+	},
+		protest.EnableOptimization)
+}
+
 // TestVariablesLoading exposes test cases where variables might be partiall or
 // fully unloaded.
 func TestVariablesLoading(t *testing.T) {
@@ -1774,6 +1839,32 @@ func TestLaunchRequestWithStackTraceDepth(t *testing.T) {
 	})
 }
 
+type Breakpoint struct {
+	line      int
+	path      string
+	verified  bool
+	msgPrefix string
+}
+
+func expectSetBreakpointsResponse(t *testing.T, client *daptest.Client, bps []Breakpoint) {
+	t.Helper()
+	checkSetBreakpointsResponse(t, client, bps, client.ExpectSetBreakpointsResponse(t))
+}
+
+func checkSetBreakpointsResponse(t *testing.T, client *daptest.Client, bps []Breakpoint, got *dap.SetBreakpointsResponse) {
+	t.Helper()
+	if len(got.Body.Breakpoints) != len(bps) {
+		t.Errorf("got %#v,\nwant len(Breakpoints)=%d", got, len(bps))
+		return
+	}
+	for i, bp := range got.Body.Breakpoints {
+		if bp.Line != bps[i].line || bp.Verified != bps[i].verified || bp.Source.Path != bps[i].path ||
+			!strings.HasPrefix(bp.Message, bps[i].msgPrefix) {
+			t.Errorf("got breakpoints[%d] = %#v, \nwant %#v", i, bp, bps[i])
+		}
+	}
+}
+
 // TestSetBreakpoint executes to a breakpoint and tests different
 // configurations of setBreakpoint requests.
 func TestSetBreakpoint(t *testing.T) {
@@ -1789,34 +1880,13 @@ func TestSetBreakpoint(t *testing.T) {
 				execute: func() {
 					handleStop(t, client, 1, "main.main", 16)
 
-					type Breakpoint struct {
-						line      int
-						path      string
-						verified  bool
-						msgPrefix string
-					}
-					expectSetBreakpointsResponse := func(bps []Breakpoint) {
-						t.Helper()
-						got := client.ExpectSetBreakpointsResponse(t)
-						if len(got.Body.Breakpoints) != len(bps) {
-							t.Errorf("got %#v,\nwant len(Breakpoints)=%d", got, len(bps))
-							return
-						}
-						for i, bp := range got.Body.Breakpoints {
-							if bp.Line != bps[i].line || bp.Verified != bps[i].verified || bp.Source.Path != bps[i].path ||
-								!strings.HasPrefix(bp.Message, bps[i].msgPrefix) {
-								t.Errorf("got breakpoints[%d] = %#v, \nwant %#v", i, bp, bps[i])
-							}
-						}
-					}
-
 					// Set two breakpoints at the next two lines in main
 					client.SetBreakpointsRequest(fixture.Source, []int{17, 18})
-					expectSetBreakpointsResponse([]Breakpoint{{17, fixture.Source, true, ""}, {18, fixture.Source, true, ""}})
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{17, fixture.Source, true, ""}, {18, fixture.Source, true, ""}})
 
 					// Clear 17, reset 18
 					client.SetBreakpointsRequest(fixture.Source, []int{18})
-					expectSetBreakpointsResponse([]Breakpoint{{18, fixture.Source, true, ""}})
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{18, fixture.Source, true, ""}})
 
 					// Skip 17, continue to 18
 					client.ContinueRequest(1)
@@ -1826,7 +1896,7 @@ func TestSetBreakpoint(t *testing.T) {
 
 					// Set another breakpoint inside the loop in loop(), twice to trigger error
 					client.SetBreakpointsRequest(fixture.Source, []int{8, 8})
-					expectSetBreakpointsResponse([]Breakpoint{{8, fixture.Source, true, ""}, {8, "", false, "Breakpoint exists"}})
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}, {8, "", false, "Breakpoint exists"}})
 
 					// Continue into the loop
 					client.ContinueRequest(1)
@@ -1839,7 +1909,7 @@ func TestSetBreakpoint(t *testing.T) {
 
 					// Edit the breakpoint to add a condition
 					client.SetConditionalBreakpointsRequest(fixture.Source, []int{8}, map[int]string{8: "i == 3"})
-					expectSetBreakpointsResponse([]Breakpoint{{8, fixture.Source, true, ""}})
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}})
 
 					// Continue until condition is hit
 					client.ContinueRequest(1)
@@ -1852,7 +1922,7 @@ func TestSetBreakpoint(t *testing.T) {
 
 					// Edit the breakpoint to remove a condition
 					client.SetConditionalBreakpointsRequest(fixture.Source, []int{8}, map[int]string{8: ""})
-					expectSetBreakpointsResponse([]Breakpoint{{8, fixture.Source, true, ""}})
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{8, fixture.Source, true, ""}})
 
 					// Continue for one more loop iteration
 					client.ContinueRequest(1)
@@ -1865,7 +1935,7 @@ func TestSetBreakpoint(t *testing.T) {
 
 					// Set at a line without a statement
 					client.SetBreakpointsRequest(fixture.Source, []int{1000})
-					expectSetBreakpointsResponse([]Breakpoint{{1000, "", false, "could not find statement"}}) // all cleared, none set
+					expectSetBreakpointsResponse(t, client, []Breakpoint{{1000, "", false, "could not find statement"}}) // all cleared, none set
 				},
 				// The program has an infinite loop, so we must kill it by disconnecting.
 				disconnect: true,
@@ -1873,6 +1943,78 @@ func TestSetBreakpoint(t *testing.T) {
 	})
 }
 
+func expectSetBreakpointsResponseAndStoppedEvent(t *testing.T, client *daptest.Client) (se *dap.StoppedEvent, br *dap.SetBreakpointsResponse) {
+	for i := 0; i < 2; i++ {
+		switch m := client.ExpectMessage(t).(type) {
+		case *dap.StoppedEvent:
+			se = m
+		case *dap.SetBreakpointsResponse:
+			br = m
+		default:
+			t.Fatalf("Unexpected message type: expect StoppedEvent or SetBreakpointsResponse, got %#v", m)
+		}
+	}
+	if se == nil || br == nil {
+		t.Fatal("Expected StoppedEvent and SetBreakpointsResponse")
+	}
+	return se, br
+}
+
+func TestSetBreakpointWhileRunning(t *testing.T) {
+	runTest(t, "integrationprog", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
+			},
+			// Set breakpoints
+			fixture.Source, []int{16},
+			[]onBreakpoint{{
+				execute: func() {
+					// The program loops 3 times over lines 14-15-8-9-10-16
+					handleStop(t, client, 1, "main.main", 16) // Line that sleeps for 1 second
+
+					// We can set breakpoints while nexting
+					client.NextRequest(1)
+					client.ExpectNextResponse(t)
+					client.SetBreakpointsRequest(fixture.Source, []int{15}) // [16,] => [15,]
+					se, br := expectSetBreakpointsResponseAndStoppedEvent(t, client)
+					if se.Body.Reason != "pause" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 0 && se.Body.ThreadId != 1 {
+						t.Errorf("\ngot  %#v\nwant Reason='pause' AllThreadsStopped=true ThreadId=0/1", se)
+					}
+					checkSetBreakpointsResponse(t, client, []Breakpoint{{15, fixture.Source, true, ""}}, br)
+					// Halt cancelled next, so if we continue we will not stop at line 14.
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					se = client.ExpectStoppedEvent(t)
+					if se.Body.Reason != "breakpoint" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 1 {
+						t.Errorf("\ngot  %#v\nwant Reason='breakpoint' AllThreadsStopped=true ThreadId=1", se)
+					}
+					handleStop(t, client, 1, "main.main", 15)
+
+					// We can set breakpoints while continuing
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					client.SetBreakpointsRequest(fixture.Source, []int{9}) // [15,] => [9,]
+					se, br = expectSetBreakpointsResponseAndStoppedEvent(t, client)
+					if se.Body.Reason != "pause" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 0 && se.Body.ThreadId != 1 {
+						t.Errorf("\ngot  %#v\nwant Reason='pause' AllThreadsStopped=true ThreadId=0/1", se)
+					}
+					checkSetBreakpointsResponse(t, client, []Breakpoint{{9, fixture.Source, true, ""}}, br)
+					client.ContinueRequest(1)
+					client.ExpectContinueResponse(t)
+					se = client.ExpectStoppedEvent(t)
+					if se.Body.Reason != "breakpoint" || !se.Body.AllThreadsStopped || se.Body.ThreadId != 1 {
+						t.Errorf("\ngot  %#v\nwant Reason='breakpoint' AllThreadsStopped=true ThreadId=1", se)
+					}
+					handleStop(t, client, 1, "main.sayhi", 9)
+
+				},
+				disconnect: true,
+			}})
+	})
+}
+
 // TestLaunchSubstitutePath sets a breakpoint using a path
 // that does not exist and expects the substitutePath attribute
 // in the launch configuration to take care of the mapping.
@@ -3140,14 +3282,6 @@ func TestBadLaunchRequests(t *testing.T) {
 			}
 		}
 
-		expectFailedToLaunchWithMessageRegex := func(response *dap.ErrorResponse, errmsg string) {
-			t.Helper()
-			expectFailedToLaunch(response)
-			if matched, _ := regexp.MatchString(errmsg, response.Body.Error.Format); !matched {
-				t.Errorf("\ngot  %q\nwant %q", response.Body.Error.Format, errmsg)
-			}
-		}
-
 		// Test for the DAP-specific detailed error message.
 		client.LaunchRequest("exec", "", stopOnEntry)
 		expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
@@ -3229,18 +3363,34 @@ func TestBadLaunchRequests(t *testing.T) {
 		expectFailedToLaunch(client.ExpectInvisibleErrorResponse(t)) // No such file or directory
 
 		client.LaunchRequest("debug", fixture.Path+"_does_not_exist", stopOnEntry)
+		oe := client.ExpectOutputEvent(t)
+		if !strings.HasPrefix(oe.Body.Output, "Build Error: ") || oe.Body.Category != "stderr" {
+			t.Errorf("got %#v, want Category=\"stderr\" Output=\"Build Error: ...\"", oe)
+		}
 		expectFailedToLaunch(client.ExpectInvisibleErrorResponse(t))
 
 		client.LaunchRequest("" /*debug by default*/, fixture.Path+"_does_not_exist", stopOnEntry)
+		oe = client.ExpectOutputEvent(t)
+		if !strings.HasPrefix(oe.Body.Output, "Build Error: ") || oe.Body.Category != "stderr" {
+			t.Errorf("got %#v, want Category=\"stderr\" Output=\"Build Error: ...\"", oe)
+		}
 		expectFailedToLaunch(client.ExpectInvisibleErrorResponse(t))
 
 		client.LaunchRequest("exec", fixture.Source, stopOnEntry)
 		expectFailedToLaunch(client.ExpectInvisibleErrorResponse(t)) // Not an executable
 
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": "-bad -flags"})
-		expectFailedToLaunchWithMessageRegex(client.ExpectInvisibleErrorResponse(t), `Failed to launch: Build error: .*flag provided but not defined.*`)
+		oe = client.ExpectOutputEvent(t)
+		if !strings.HasPrefix(oe.Body.Output, "Build Error: ") || oe.Body.Category != "stderr" {
+			t.Errorf("got %#v, want Category=\"stderr\" Output=\"Build Error: ...\"", oe)
+		}
+		expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), "Failed to launch: Build error: Check the debug console for details.")
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": true, "buildFlags": "-bad -flags"})
-		expectFailedToLaunchWithMessageRegex(client.ExpectInvisibleErrorResponse(t), `Failed to launch: Build error: .*flag provided but not defined.*`)
+		oe = client.ExpectOutputEvent(t)
+		if !strings.HasPrefix(oe.Body.Output, "Build Error: ") || oe.Body.Category != "stderr" {
+			t.Errorf("got %#v, want Category=\"stderr\" Output=\"Build Error: ...\"", oe)
+		}
+		expectFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t), "Failed to launch: Build error: Check the debug console for details.")
 
 		// Bad "wd".
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": false, "cwd": "dir/invalid"})