Repository: go-delve/delve
Pull request #2730 (2021-10-01 17:49:28)

service/dap: log goroutine id and include source info
Log the goroutine id that hit the log message and include the source information. This source information is displayed to the right of the log message and VS Code links it to the appropriate source location.

Updates golang/vscode-go#123

<img width="496" alt="Screen Shot 2021-10-01 at 12 46 34 PM" src="https://user-images.githubusercontent.com/6634754/135664822-77d50102-bca8-472a-bea3-0a82a456bc80.png">



Patch:
diff --git a/service/dap/server.go b/service/dap/server.go
index e0ba5382..6ab61fab 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -17,6 +17,7 @@ import (
 	"go/constant"
 	"go/parser"
 	"io"
+	"io/ioutil"
 	"net"
 	"os"
 	"os/exec"
@@ -74,7 +75,11 @@ import (
 // error or responding to a (synchronous) DAP disconnect request.
 // Once stop is triggered, the goroutine exits.
 //
-// TODO(polina): add another layer of per-client goroutines to support multiple clients
+// TODO(polina): add another layer of per-client goroutines to support multiple clients.
+// Note that some requests change the process's environment such
+// as working directory (for example, see DelveCwd of launch configuration).
+// So if we want to reuse this process for multiple debugging sessions
+// we need to address that.
 //
 // (3) Per-request goroutine is started for each asynchronous request
 // that resumes execution. We check if target is running already, so
@@ -807,10 +812,29 @@ func (s *Session) setClientCapabilities(args dap.InitializeRequestArguments) {
 	s.clientCapabilities.supportsVariableType = args.SupportsVariableType
 }
 
-// Default output file pathname for the compiled binary in debug or test modes,
-// relative to the current working directory of the server.
+// Default output file pathname for the compiled binary in debug or test modes
+// when temporary debug binary creation fails.
+// This is relative to the current working directory of the server.
 const defaultDebugBinary string = "./__debug_bin"
 
+func (s *Session) tempDebugBinary() string {
+	binaryPattern := "__debug_bin"
+	if runtime.GOOS == "windows" {
+		binaryPattern = "__debug_bin*.exe"
+	}
+	f, err := ioutil.TempFile("", binaryPattern)
+	if err != nil {
+		s.config.log.Errorf("failed to create a temporary binary (%v), falling back to %q", err, defaultDebugBinary)
+		return cleanExeName(defaultDebugBinary)
+	}
+	name := f.Name()
+	if err := f.Close(); err != nil {
+		s.config.log.Errorf("failed to create a temporary binary (%v), falling back to %q", err, defaultDebugBinary)
+		return cleanExeName(defaultDebugBinary)
+	}
+	return name
+}
+
 func cleanExeName(name string) string {
 	if runtime.GOOS == "windows" && filepath.Ext(name) != ".exe" {
 		return name + ".exe"
@@ -832,6 +856,14 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
 		return
 	}
 
+	if args.DelveCwd != "" {
+		if err := os.Chdir(args.DelveCwd); err != nil {
+			s.sendShowUserErrorResponse(request.Request,
+				FailedToLaunch, "Failed to launch", fmt.Sprintf("failed to chdir using %q - %v", args.DelveCwd, err))
+			return
+		}
+	}
+
 	mode := args.Mode
 	if mode == "" {
 		mode = "debug"
@@ -887,15 +919,18 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
 
 	// Prepare the debug executable filename, build flags and build it
 	if mode == "debug" || mode == "test" {
-		output := args.Output
-		if output == "" {
-			output = defaultDebugBinary
+		debugbinary := args.Output
+		if debugbinary == "" {
+			debugbinary = s.tempDebugBinary()
+		} else {
+			debugbinary = cleanExeName(debugbinary)
 		}
-		output = cleanExeName(output)
-		debugbinary, err := filepath.Abs(output)
-		if err != nil {
-			s.sendShowUserErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
+
+		if o, err := filepath.Abs(debugbinary); err != nil {
+			s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
 			return
+		} else {
+			debugbinary = o
 		}
 		buildFlags := args.BuildFlags
 
@@ -903,6 +938,8 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
 		var out []byte
 		wd, _ := os.Getwd()
 		s.config.log.Debugf("building program '%s' in '%s' with flags '%v'", program, wd, buildFlags)
+
+		var err error
 		switch mode {
 		case "debug":
 			cmd, out, err = gobuild.GoBuildCombinedOutput(debugbinary, []string{program}, buildFlags)
@@ -934,9 +971,14 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
 	}
 
 	s.config.ProcessArgs = append([]string{program}, args.Args...)
-	s.config.Debugger.WorkingDir = filepath.Dir(program)
 	if args.Cwd != "" {
 		s.config.Debugger.WorkingDir = args.Cwd
+	} else if mode == "test" {
+		// In test mode, run the test binary from the package directory
+		// like in `go test` and `dlv test` by default.
+		s.config.Debugger.WorkingDir = s.getPackageDir(args.Program)
+	} else {
+		s.config.Debugger.WorkingDir = "."
 	}
 
 	s.config.log.Debugf("running binary '%s' in '%s'", program, s.config.Debugger.WorkingDir)
@@ -993,6 +1035,16 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
 	s.send(&dap.LaunchResponse{Response: *newResponse(request.Request)})
 }
 
+func (s *Session) getPackageDir(pkg string) string {
+	cmd := exec.Command("go", "list", "-f", "{{.Dir}}", pkg)
+	out, err := cmd.Output()
+	if err != nil {
+		s.config.log.Debugf("failed to determin package directory for %v: %v\n%s", pkg, err, out)
+		return "."
+	}
+	return string(bytes.TrimSpace(out))
+}
+
 // newNoDebugProcess is called from onLaunchRequest (run goroutine) and
 // requires holding mu lock. It prepares process exec.Cmd to be started.
 func (s *Session) newNoDebugProcess(program string, targetArgs []string, wd string) (*exec.Cmd, error) {
@@ -1843,7 +1895,6 @@ func (s *Session) onScopesRequest(request *dap.ScopesRequest) {
 		s.sendErrorResponse(request.Request, UnableToListArgs, "Unable to list args", err.Error())
 		return
 	}
-	argScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Arguments%s", suffix), Children: slicePtrVarToSliceVar(args)}, "", true, 0}
 
 	// Retrieve local variables
 	locals, err := s.debugger.LocalVariables(goid, frame, 0, DefaultLoadConfig)
@@ -1851,11 +1902,9 @@ func (s *Session) onScopesRequest(request *dap.ScopesRequest) {
 		s.sendErrorResponse(request.Request, UnableToListLocals, "Unable to list locals", err.Error())
 		return
 	}
-	locScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Locals%s", suffix), Children: slicePtrVarToSliceVar(locals)}, "", true, 0}
-
-	scopeArgs := dap.Scope{Name: argScope.Name, VariablesReference: s.variableHandles.create(argScope)}
+	locScope := &fullyQualifiedVariable{&proc.Variable{Name: fmt.Sprintf("Locals%s", suffix), Children: slicePtrVarToSliceVar(append(args, locals...))}, "", true, 0}
 	scopeLocals := dap.Scope{Name: locScope.Name, VariablesReference: s.variableHandles.create(locScope)}
-	scopes := []dap.Scope{scopeArgs, scopeLocals}
+	scopes := []dap.Scope{scopeLocals}
 
 	if s.args.showGlobalVariables {
 		// Limit what global variables we will return to the current package only.
@@ -3153,7 +3202,7 @@ func (s *Session) handleLogPoints(state *api.DebuggerState) bool {
 	foundRealBreakpoint := false
 	for _, th := range state.Threads {
 		if bp := th.Breakpoint; bp != nil {
-			logged := s.logBreakpointMessage(bp)
+			logged := s.logBreakpointMessage(bp, th.GoroutineID)
 			if !logged {
 				foundRealBreakpoint = true
 			}
@@ -3162,18 +3211,21 @@ func (s *Session) handleLogPoints(state *api.DebuggerState) bool {
 	return foundRealBreakpoint
 }
 
-func (s *Session) logBreakpointMessage(bp *api.Breakpoint) bool {
+func (s *Session) logBreakpointMessage(bp *api.Breakpoint, goid int) bool {
 	if !bp.Tracepoint {
 		return false
 	}
-	// TODO(suzmue): allow evaluate expressions within log points and
-	// consider adding line and goid info to output.
+	// TODO(suzmue): allow evaluate expressions within log points.
 	if msg, ok := bp.UserData.(string); ok {
 		s.send(&dap.OutputEvent{
 			Event: *newEvent("output"),
 			Body: dap.OutputEventBody{
 				Category: "stdout",
-				Output:   msg + "\n",
+				Output:   fmt.Sprintf("> [Go %d]: %s\n", goid, msg),
+				Source: dap.Source{
+					Path: s.toClientPath(bp.File),
+				},
+				Line: bp.Line,
 			},
 		})
 	}
diff --git a/service/dap/types.go b/service/dap/types.go
index 832dfb67..0cbf4806 100644
--- a/service/dap/types.go
+++ b/service/dap/types.go
@@ -44,7 +44,6 @@ var (
 	}
 	defaultLaunchConfig = LaunchConfig{
 		Mode:                     "debug",
-		Output:                   defaultDebugBinary,
 		LaunchAttachCommonConfig: defaultLaunchAttachCommonConfig,
 	}
 	defaultAttachConfig = AttachConfig{
@@ -65,28 +64,34 @@ type LaunchConfig struct {
 	// Default is "debug".
 	Mode string `json:"mode,omitempty"`
 
-	// Required when mode is `debug`, `test`, or `exec`.
 	// Path to the program folder (or any go file within that folder)
 	// when in `debug` or `test` mode, and to the pre-built binary file
 	// to debug in `exec` mode.
 	// If it is not an absolute path, it will be interpreted as a path
-	// relative to the working directory of the delve process.
+	// relative to Delve's working directory.
+	// Required when mode is `debug`, `test`, `exec`, and `core`.
 	Program string `json:"program,omitempty"`
 
 	// Command line arguments passed to the debugged program.
+	// Relative paths used in Args will be interpreted as paths relative
+	// to `cwd`.
 	Args []string `json:"args,omitempty"`
 
-	// Working directory of the program being debugged
-	// if a non-empty value is specified. If a relative path is provided,
-	// it will be interpreted as a relative path to the delve's
-	// working directory.
+	// Working directory of the program being debugged.
+	// If a relative path is provided, it will be interpreted as
+	// a relative path to the delve's working directory. This is
+	// similar to delve's `--wd` flag.
 	//
-	// If not specified or empty, currently the built program's directory will
-	// be used.
-	// This is similar to delve's `--wd` flag.
+	// If not specified or empty, delve's working directory is
+	// used by default. But for `test` mode, delve tries to find
+	// the test's package source directory and run tests from there.
+	// This matches the behavior of `dlv test` and `go test`.
 	Cwd string `json:"cwd,omitempty"`
 
 	// Build flags, to be passed to the Go compiler.
+	// Relative paths used in BuildFlags will be interpreted as paths
+	// relative to Delve's current working directory.
+	//
 	// It is like delve's `--build-flags`. For example,
 	//
 	//    "buildFlags": "-tags=integration -mod=vendor -cover -v"
@@ -94,26 +99,38 @@ type LaunchConfig struct {
 
 	// Output path for the binary of the debugee.
 	// Relative path is interpreted as the path relative to
-	// the delve process's working directory.
+	// the delve's current working directory.
 	// This is deleted after the debug session ends.
-	//
-	// FIXIT: the built program's directory is used as the default
-	// working directory of the debugged program, which means
-	// the directory of `output` is used as the default working
-	// directory. This is a bug and needs fix.
 	Output string `json:"output,omitempty"`
 
 	// NoDebug is used to run the program without debugging.
 	NoDebug bool `json:"noDebug,omitempty"`
 
 	// TraceDirPath is the trace directory path for replay mode.
+	// Relative path is interpreted as a path relative to Delve's
+	// current working directory.
 	// This is required for "replay" mode but unused in other modes.
 	TraceDirPath string `json:"traceDirPath,omitempty"`
 
 	// CoreFilePath is the core file path for core mode.
+	//
 	// This is required for "core" mode but unused in other modes.
 	CoreFilePath string `json:"coreFilePath,omitempty"`
 
+	// DelveCwd is the working directory of Delve.
+	// If specified, the delve DAP server will change its working
+	// directory to the specified directory using os.Chdir.
+	// Any relative paths used in most of other attributes are
+	// interpreted as paths relative to Delve's working directory
+	// unless explicitely stated otherwise. When Delve needs to
+	// build the program (in debug/test modes), Delve runs the
+	// go command from the Delve's working directory.
+	//
+	// If a relative path is provided as DelveCwd, it will be
+	// interpreted as a path relative to Delve's current working
+	// directory.
+	DelveCwd string `json:"delveCwd,omitempty"`
+
 	LaunchAttachCommonConfig
 }
 


Test patch:
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 393525f8..5e18557f 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -13,6 +13,7 @@ import (
 	"path/filepath"
 	"regexp"
 	"runtime"
+	"strconv"
 	"strings"
 	"testing"
 	"time"
@@ -30,6 +31,8 @@ import (
 const stopOnEntry bool = true
 const hasChildren bool = true
 const noChildren bool = false
+const localsScope = 1000
+const globalsScope = 1001
 
 var testBackend string
 
@@ -595,22 +598,17 @@ func TestPreSetBreakpoint(t *testing.T) {
 
 		client.ScopesRequest(1000)
 		scopes := client.ExpectScopesResponse(t)
-		if len(scopes.Body.Scopes) > 2 {
-			t.Errorf("\ngot  %#v\nwant len(Scopes)=2 (Arguments & Locals)", scopes)
+		if len(scopes.Body.Scopes) > 1 {
+			t.Errorf("\ngot  %#v\nwant len(Scopes)=1 (Locals)", scopes)
 		}
-		checkScope(t, scopes, 0, "Arguments", 1000)
-		checkScope(t, scopes, 1, "Locals", 1001)
+		checkScope(t, scopes, 0, "Locals", localsScope)
 
-		client.VariablesRequest(1000) // Arguments
+		client.VariablesRequest(localsScope)
 		args := client.ExpectVariablesResponse(t)
-		checkChildren(t, args, "Arguments", 2)
+		checkChildren(t, args, "Locals", 2)
 		checkVarExact(t, args, 0, "y", "y", "0 = 0x0", "uint", noChildren)
 		checkVarExact(t, args, 1, "~r1", "", "0 = 0x0", "uint", noChildren)
 
-		client.VariablesRequest(1001) // Locals
-		locals := client.ExpectVariablesResponse(t)
-		checkChildren(t, locals, "Locals", 0)
-
 		client.ContinueRequest(1)
 		ctResp := client.ExpectContinueResponse(t)
 		if !ctResp.Body.AllThreadsContinued {
@@ -1081,17 +1079,22 @@ func TestScopesAndVariablesRequests(t *testing.T) {
 
 					client.ScopesRequest(1000)
 					scopes := client.ExpectScopesResponse(t)
-					checkScope(t, scopes, 0, "Arguments", 1000)
-					checkScope(t, scopes, 1, "Locals", 1001)
-					checkScope(t, scopes, 2, "Globals (package main)", 1002)
+					checkScope(t, scopes, 0, "Locals", localsScope)
+					checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
+
+					// Globals
 
-					// Arguments
+					client.VariablesRequest(globalsScope)
+					globals := client.ExpectVariablesResponse(t)
+					checkVarExact(t, globals, 0, "p1", "main.p1", "10", "int", noChildren)
 
-					client.VariablesRequest(1000)
-					args := client.ExpectVariablesResponse(t)
-					checkChildren(t, args, "Arguments", 2)
-					checkVarExact(t, args, 0, "baz", "baz", `"bazburzum"`, "string", noChildren)
-					ref := checkVarExact(t, args, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
+					// Locals
+
+					client.VariablesRequest(localsScope)
+					locals := client.ExpectVariablesResponse(t)
+					checkChildren(t, locals, "Locals", 33)
+					checkVarExact(t, locals, 0, "baz", "baz", `"bazburzum"`, "string", noChildren)
+					ref := checkVarExact(t, locals, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
 					if ref > 0 {
 						client.VariablesRequest(ref)
 						bar := client.ExpectVariablesResponse(t)
@@ -1102,18 +1105,6 @@ func TestScopesAndVariablesRequests(t *testing.T) {
 						validateEvaluateName(t, client, bar, 1)
 					}
 
-					// Globals
-
-					client.VariablesRequest(1002)
-					globals := client.ExpectVariablesResponse(t)
-					checkVarExact(t, globals, 0, "p1", "main.p1", "10", "int", noChildren)
-
-					// Locals
-
-					client.VariablesRequest(1001)
-					locals := client.ExpectVariablesResponse(t)
-					checkChildren(t, locals, "Locals", 31)
-
 					// reflect.Kind == Bool
 					checkVarExact(t, locals, -1, "b1", "b1", "true", "bool", noChildren)
 					checkVarExact(t, locals, -1, "b2", "b2", "false", "bool", noChildren)
@@ -1295,9 +1286,8 @@ func TestScopesAndVariablesRequests(t *testing.T) {
 
 					client.ScopesRequest(1000)
 					scopes := client.ExpectScopesResponse(t)
-					checkScope(t, scopes, 0, "Arguments", 1000)
-					checkScope(t, scopes, 1, "Locals", 1001)
-					checkScope(t, scopes, 2, "Globals (package main)", 1002)
+					checkScope(t, scopes, 0, "Locals", localsScope)
+					checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
 
 					client.ScopesRequest(1111)
 					erres := client.ExpectInvisibleErrorResponse(t)
@@ -1305,16 +1295,12 @@ func TestScopesAndVariablesRequests(t *testing.T) {
 						t.Errorf("\ngot %#v\nwant Format=\"Unable to list locals: unknown frame id 1111\"", erres)
 					}
 
-					client.VariablesRequest(1000) // Arguments
-					args := client.ExpectVariablesResponse(t)
-					checkChildren(t, args, "Arguments", 0)
-
-					client.VariablesRequest(1001) // Locals
+					client.VariablesRequest(localsScope)
 					locals := client.ExpectVariablesResponse(t)
 					checkChildren(t, locals, "Locals", 1)
 					checkVarExact(t, locals, -1, "a1", "a1", `"bur"`, "string", noChildren)
 
-					client.VariablesRequest(1002) // Globals
+					client.VariablesRequest(globalsScope)
 					globals := client.ExpectVariablesResponse(t)
 					checkVarExact(t, globals, 0, "p1", "main.p1", "10", "int", noChildren)
 
@@ -1348,8 +1334,7 @@ func TestScopesAndVariablesRequests2(t *testing.T) {
 
 					client.ScopesRequest(1000)
 					scopes := client.ExpectScopesResponse(t)
-					checkScope(t, scopes, 0, "Arguments", 1000)
-					checkScope(t, scopes, 1, "Locals", 1001)
+					checkScope(t, scopes, 0, "Locals", localsScope)
 				},
 				disconnect: false,
 			}, {
@@ -1360,21 +1345,13 @@ func TestScopesAndVariablesRequests2(t *testing.T) {
 
 					client.ScopesRequest(1000)
 					scopes := client.ExpectScopesResponse(t)
-					if len(scopes.Body.Scopes) > 2 {
-						t.Errorf("\ngot  %#v\nwant len(scopes)=2 (Argumes & Locals)", scopes)
+					if len(scopes.Body.Scopes) > 1 {
+						t.Errorf("\ngot  %#v\nwant len(scopes)=1 (Argumes & Locals)", scopes)
 					}
-					checkScope(t, scopes, 0, "Arguments", 1000)
-					checkScope(t, scopes, 1, "Locals", 1001)
-
-					// Arguments
-
-					client.VariablesRequest(1000)
-					args := client.ExpectVariablesResponse(t)
-					checkChildren(t, args, "Arguments", 0)
+					checkScope(t, scopes, 0, "Locals", localsScope)
 
 					// Locals
-
-					client.VariablesRequest(1001)
+					client.VariablesRequest(localsScope)
 					locals := client.ExpectVariablesResponse(t)
 
 					// reflect.Kind == Bool - see testvariables
@@ -1602,7 +1579,7 @@ 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
+// that the name of the "Locals" scope is 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) {
@@ -1633,9 +1610,8 @@ func TestScopesRequestsOptimized(t *testing.T) {
 
 					client.ScopesRequest(1000)
 					scopes := client.ExpectScopesResponse(t)
-					checkScope(t, scopes, 0, "Arguments (warning: optimized function)", 1000)
-					checkScope(t, scopes, 1, "Locals (warning: optimized function)", 1001)
-					checkScope(t, scopes, 2, "Globals (package main)", 1002)
+					checkScope(t, scopes, 0, "Locals (warning: optimized function)", localsScope)
+					checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
 				},
 				disconnect: false,
 			}, {
@@ -1648,9 +1624,8 @@ func TestScopesRequestsOptimized(t *testing.T) {
 
 					client.ScopesRequest(1000)
 					scopes := client.ExpectScopesResponse(t)
-					checkScope(t, scopes, 0, "Arguments (warning: optimized function)", 1000)
-					checkScope(t, scopes, 1, "Locals (warning: optimized function)", 1001)
-					checkScope(t, scopes, 2, "Globals (package main)", 1002)
+					checkScope(t, scopes, 0, "Locals (warning: optimized function)", localsScope)
+					checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
 				},
 				disconnect: false,
 			}})
@@ -1688,7 +1663,7 @@ func TestVariablesLoading(t *testing.T) {
 					client.ScopesRequest(1000)
 					client.ExpectScopesResponse(t)
 
-					client.VariablesRequest(1001) // Locals
+					client.VariablesRequest(localsScope)
 					locals := client.ExpectVariablesResponse(t)
 
 					// String partially missing based on LoadConfig.MaxStringLen
@@ -2009,7 +1984,7 @@ func TestVariablesMetadata(t *testing.T) {
 				execute: func() {
 					checkStop(t, client, 1, "main.main", 368)
 
-					client.VariablesRequest(1001) // Locals
+					client.VariablesRequest(localsScope)
 					locals := client.ExpectVariablesResponse(t)
 
 					checkNamedChildren := func(ref int, name, typeStr string, vals []string, evaluate bool) {
@@ -2101,11 +2076,10 @@ func TestGlobalScopeAndVariables(t *testing.T) {
 
 					client.ScopesRequest(1000)
 					scopes := client.ExpectScopesResponse(t)
-					checkScope(t, scopes, 0, "Arguments", 1000)
-					checkScope(t, scopes, 1, "Locals", 1001)
-					checkScope(t, scopes, 2, "Globals (package main)", 1002)
+					checkScope(t, scopes, 0, "Locals", localsScope)
+					checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
 
-					client.VariablesRequest(1002)
+					client.VariablesRequest(globalsScope)
 					client.ExpectVariablesResponse(t)
 					// The program has no user-defined globals.
 					// Depending on the Go version, there might
@@ -2123,11 +2097,10 @@ func TestGlobalScopeAndVariables(t *testing.T) {
 
 					client.ScopesRequest(1000)
 					scopes = client.ExpectScopesResponse(t)
-					checkScope(t, scopes, 0, "Arguments", 1000)
-					checkScope(t, scopes, 1, "Locals", 1001)
-					checkScope(t, scopes, 2, "Globals (package github.com/go-delve/delve/_fixtures/internal/dir0/pkg)", 1002)
+					checkScope(t, scopes, 0, "Locals", localsScope)
+					checkScope(t, scopes, 1, "Globals (package github.com/go-delve/delve/_fixtures/internal/dir0/pkg)", globalsScope)
 
-					client.VariablesRequest(1002)
+					client.VariablesRequest(globalsScope)
 					globals := client.ExpectVariablesResponse(t)
 					checkChildren(t, globals, "Globals", 1)
 					ref := checkVarExact(t, globals, 0, "SomeVar", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeVar", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeType {X: 0}", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeType", hasChildren)
@@ -2167,11 +2140,10 @@ func TestShadowedVariables(t *testing.T) {
 
 					client.ScopesRequest(1000)
 					scopes := client.ExpectScopesResponse(t)
-					checkScope(t, scopes, 0, "Arguments", 1000)
-					checkScope(t, scopes, 1, "Locals", 1001)
-					checkScope(t, scopes, 2, "Globals (package main)", 1002)
+					checkScope(t, scopes, 0, "Locals", localsScope)
+					checkScope(t, scopes, 1, "Globals (package main)", globalsScope)
 
-					client.VariablesRequest(1001)
+					client.VariablesRequest(localsScope)
 					locals := client.ExpectVariablesResponse(t)
 
 					checkVarExact(t, locals, 0, "(a)", "a", "0", "int", !hasChildren)
@@ -2285,7 +2257,7 @@ func TestSetBreakpoint(t *testing.T) {
 					client.ExpectContinueResponse(t)
 					client.ExpectStoppedEvent(t)
 					checkStop(t, client, 1, "main.loop", 8)
-					client.VariablesRequest(1001) // Locals
+					client.VariablesRequest(localsScope)
 					locals := client.ExpectVariablesResponse(t)
 					checkVarExact(t, locals, 0, "i", "i", "0", "int", noChildren) // i == 0
 
@@ -2298,7 +2270,7 @@ func TestSetBreakpoint(t *testing.T) {
 					client.ExpectContinueResponse(t)
 					client.ExpectStoppedEvent(t)
 					checkStop(t, client, 1, "main.loop", 8)
-					client.VariablesRequest(1001) // Locals
+					client.VariablesRequest(localsScope)
 					locals = client.ExpectVariablesResponse(t)
 					checkVarExact(t, locals, 0, "i", "i", "3", "int", noChildren) // i == 3
 
@@ -2311,7 +2283,7 @@ func TestSetBreakpoint(t *testing.T) {
 					client.ExpectContinueResponse(t)
 					client.ExpectStoppedEvent(t)
 					checkStop(t, client, 1, "main.loop", 8)
-					client.VariablesRequest(1001) // Locals
+					client.VariablesRequest(localsScope)
 					locals = client.ExpectVariablesResponse(t)
 					checkVarExact(t, locals, 0, "i", "i", "4", "int", noChildren) // i == 4
 
@@ -2734,11 +2706,7 @@ func TestLogPoints(t *testing.T) {
 
 						client.ContinueRequest(1)
 						client.ExpectContinueResponse(t)
-
-						oe := client.ExpectOutputEvent(t)
-						if oe.Body.Category != "stdout" || oe.Body.Output != "in callme!\n" {
-							t.Errorf("got output event = %#v, \nwant Category=\"stdout\" Output=\"in callme!\n\"", oe)
-						}
+						checkLogMessage(t, client.ExpectOutputEvent(t), 1, "in callme!", fixture.Source, 6)
 					}
 					se := client.ExpectStoppedEvent(t)
 					if se.Body.Reason != "breakpoint" || se.Body.ThreadId != 1 {
@@ -2749,10 +2717,7 @@ func TestLogPoints(t *testing.T) {
 					client.NextRequest(1)
 					client.ExpectNextResponse(t)
 
-					oe := client.ExpectOutputEvent(t)
-					if oe.Body.Category != "stdout" || oe.Body.Output != "in callme2!\n" {
-						t.Errorf("got output event = %#v, \nwant Category=\"stdout\" Output=\"in callme2!\n\"", oe)
-					}
+					checkLogMessage(t, client.ExpectOutputEvent(t), 1, "in callme2!", fixture.Source, 16)
 
 					se = client.ExpectStoppedEvent(t)
 					if se.Body.Reason != "step" || se.Body.ThreadId != 1 {
@@ -2765,6 +2730,20 @@ func TestLogPoints(t *testing.T) {
 	})
 }
 
+func checkLogMessage(t *testing.T, oe *dap.OutputEvent, goid int, text, path string, line int) {
+	t.Helper()
+	prefix := "> [Go "
+	if goid >= 0 {
+		prefix += strconv.Itoa(goid) + "]"
+	}
+	if oe.Body.Category != "stdout" || !strings.HasPrefix(oe.Body.Output, prefix) || !strings.HasSuffix(oe.Body.Output, text+"\n") {
+		t.Errorf("got output event = %#v, \nwant Category=\"stdout\" Output=\"%s: %s\\n\"", oe, prefix, text)
+	}
+	if oe.Body.Line != line || oe.Body.Source.Path != path {
+		t.Errorf("got output event = %#v, \nwant Line=%d Source.Path=%s", oe, line, path)
+	}
+}
+
 // TestHaltPreventsAutoResume tests that a pause request issued while processing
 // log messages will result in a real stop.
 func TestHaltPreventsAutoResume(t *testing.T) {
@@ -2826,10 +2805,7 @@ func TestHaltPreventsAutoResume(t *testing.T) {
 
 						client.ContinueRequest(1)
 						client.ExpectContinueResponse(t)
-						oe := client.ExpectOutputEvent(t)
-						if oe.Body.Category != "stdout" || oe.Body.Output != "in callme!\n" {
-							t.Errorf("got output event = %#v, \nwant Category=\"stdout\" Output=\"in callme!\n\"", oe)
-						}
+						checkLogMessage(t, client.ExpectOutputEvent(t), 1, "in callme!", fixture.Source, 6)
 						// Signal that the output event has been received.
 						close(outputDoneChan)
 						// Wait for the pause to be complete.
@@ -2888,9 +2864,7 @@ func TestConcurrentBreakpointsLogPoints(t *testing.T) {
 							seCount++
 							client.ContinueRequest(1)
 						case *dap.OutputEvent:
-							if m.Body.Category != "stdout" || m.Body.Output != "hello\n" {
-								t.Errorf("\ngot  %#v\nwant Category=\"stdout\" Output=\"hello\"", m)
-							}
+							checkLogMessage(t, m, -1, "hello", fixture.Source, 8)
 							oeCount++
 						case *dap.ContinueResponse:
 						case *dap.TerminatedEvent:
@@ -3095,7 +3069,7 @@ func TestHitConditionBreakpoints(t *testing.T) {
 					checkStop(t, client, 1, "main.main", 7)
 
 					// Check that we are stopped at the correct value of i.
-					client.VariablesRequest(1001)
+					client.VariablesRequest(localsScope)
 					locals := client.ExpectVariablesResponse(t)
 					checkVarExact(t, locals, 0, "i", "i", "4", "int", noChildren)
 
@@ -3109,7 +3083,7 @@ func TestHitConditionBreakpoints(t *testing.T) {
 					checkStop(t, client, 1, "main.main", 7)
 
 					// Check that we are stopped at the correct value of i.
-					client.VariablesRequest(1001)
+					client.VariablesRequest(localsScope)
 					locals = client.ExpectVariablesResponse(t)
 					checkVarExact(t, locals, 0, "i", "i", "6", "int", noChildren)
 
@@ -3126,7 +3100,7 @@ func TestHitConditionBreakpoints(t *testing.T) {
 					checkStop(t, client, 1, "main.main", 7)
 
 					// Check that we are stopped at the correct value of i.
-					client.VariablesRequest(1001)
+					client.VariablesRequest(localsScope)
 					locals = client.ExpectVariablesResponse(t)
 					checkVarExact(t, locals, 0, "i", "i", "7", "int", noChildren)
 
@@ -3253,7 +3227,7 @@ func TestWorkingDir(t *testing.T) {
 			[]onBreakpoint{{
 				execute: func() {
 					checkStop(t, client, 1, "main.main", 10)
-					client.VariablesRequest(1001) // Locals
+					client.VariablesRequest(localsScope)
 					locals := client.ExpectVariablesResponse(t)
 					checkChildren(t, locals, "Locals", 2)
 					for i := range locals.Body.Variables {
@@ -3487,7 +3461,7 @@ func TestVariableValueTruncation(t *testing.T) {
 				execute: func() {
 					checkStop(t, client, 1, "main.main", -1)
 
-					client.VariablesRequest(1001) // Locals
+					client.VariablesRequest(localsScope)
 					locals := client.ExpectVariablesResponse(t)
 
 					// Compound variable values may be truncated
@@ -3563,7 +3537,7 @@ func TestVariableLoadingOfLongStrings(t *testing.T) {
 				execute: func() {
 					checkStop(t, client, 1, "main.main", -1)
 
-					client.VariablesRequest(1001) // Locals
+					client.VariablesRequest(localsScope)
 					locals := client.ExpectVariablesResponse(t)
 
 					// Limits vary for evaluate requests with different contexts
@@ -4343,9 +4317,7 @@ func checkStop(t *testing.T, client *daptest.Client, thread int, name string, li
 	client.ScopesRequest(1000)
 	client.ExpectScopesResponse(t)
 
-	client.VariablesRequest(1000) // Arguments
-	client.ExpectVariablesResponse(t)
-	client.VariablesRequest(1001) // Locals
+	client.VariablesRequest(localsScope)
 	client.ExpectVariablesResponse(t)
 }
 
@@ -4504,10 +4476,9 @@ func TestLaunchRequestDefaults(t *testing.T) {
 	})
 	runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
 		runDebugSession(t, client, "launch", func() {
-			// Use the default output directory.
+			// Use the temporary output binary.
 			client.LaunchRequestWithArgs(map[string]interface{}{
 				"mode": "debug", "program": fixture.Source})
-			// writes to default output dir __debug_bin
 		})
 	})
 }
@@ -4673,16 +4644,95 @@ func TestLaunchRequestWithRelativeExecPath(t *testing.T) {
 }
 
 func TestLaunchTestRequest(t *testing.T) {
-	serverStopped := make(chan struct{})
-	client := startDapServerWithClient(t, serverStopped)
-	defer client.Close()
-	runDebugSession(t, client, "launch", func() {
-		fixtures := protest.FindFixturesDir()
-		testdir, _ := filepath.Abs(filepath.Join(fixtures, "buildtest"))
-		client.LaunchRequestWithArgs(map[string]interface{}{
-			"mode": "test", "program": testdir, "output": "__mytestdir"})
-	})
-	<-serverStopped
+	orgWD, _ := os.Getwd()
+	fixtures := protest.FindFixturesDir() // relative to current working directory.
+	absoluteFixturesDir, _ := filepath.Abs(fixtures)
+	absolutePkgDir, _ := filepath.Abs(filepath.Join(fixtures, "buildtest"))
+	testFile := filepath.Join(absolutePkgDir, "main_test.go")
+
+	for _, tc := range []struct {
+		name       string
+		dlvWD      string
+		launchArgs map[string]interface{}
+		wantWD     string
+	}{{
+		name: "default",
+		launchArgs: map[string]interface{}{
+			"mode": "test", "program": absolutePkgDir,
+		},
+		wantWD: absolutePkgDir,
+	}, {
+		name: "output",
+		launchArgs: map[string]interface{}{
+			"mode": "test", "program": absolutePkgDir, "output": "test.out",
+		},
+		wantWD: absolutePkgDir,
+	}, {
+		name: "delveCWD",
+		launchArgs: map[string]interface{}{
+			"mode": "test", "program": absolutePkgDir, "delveCWD": ".",
+		},
+		wantWD: absolutePkgDir,
+	}, {
+		name: "delveCWD2",
+		launchArgs: map[string]interface{}{
+			"mode": "test", "program": ".", "delveCWD": absolutePkgDir,
+		},
+		wantWD: absolutePkgDir,
+	}, {
+		name: "cwd",
+		launchArgs: map[string]interface{}{
+			"mode": "test", "program": absolutePkgDir, "cwd": fixtures, // fixtures is relative to the current working directory.
+		},
+		wantWD: absoluteFixturesDir,
+	}, {
+		name:  "dlv runs outside of module",
+		dlvWD: os.TempDir(),
+		launchArgs: map[string]interface{}{
+			"mode": "test", "program": absolutePkgDir, "delveCWD": absoluteFixturesDir,
+		},
+		wantWD: absolutePkgDir,
+	}, {
+		name:  "dlv builds in delveCWD but runs in cwd",
+		dlvWD: fixtures,
+		launchArgs: map[string]interface{}{
+			"mode": "test", "program": absolutePkgDir, "delveCWD": absolutePkgDir, "cwd": "..", // relative to delveCWD.
+		},
+		wantWD: absoluteFixturesDir,
+	}} {
+		t.Run(tc.name, func(t *testing.T) {
+			// Some test cases with delveCWD or dlvWD change process working directory.
+			defer os.Chdir(orgWD)
+			if tc.dlvWD != "" {
+				os.Chdir(tc.dlvWD)
+				defer os.Chdir(orgWD)
+			}
+			serverStopped := make(chan struct{})
+			client := startDapServerWithClient(t, serverStopped)
+			defer client.Close()
+
+			runDebugSessionWithBPs(t, client, "launch",
+				func() { // Launch
+					client.LaunchRequestWithArgs(tc.launchArgs)
+				},
+				testFile, []int{14},
+				[]onBreakpoint{{
+					execute: func() {
+						checkStop(t, client, -1, "github.com/go-delve/delve/_fixtures/buildtest.TestCurrentDirectory", 14)
+						client.VariablesRequest(1001) // Locals
+						locals := client.ExpectVariablesResponse(t)
+						checkChildren(t, locals, "Locals", 1)
+						for i := range locals.Body.Variables {
+							switch locals.Body.Variables[i].Name {
+							case "wd": // The test's working directory is the package directory by default.
+								checkVarExact(t, locals, i, "wd", "wd", fmt.Sprintf("%q", tc.wantWD), "string", noChildren)
+							}
+						}
+					}}})
+
+			<-serverStopped
+		})
+	}
 }
 
 // Tests that 'args' from LaunchRequest are parsed and passed to the target
@@ -4738,7 +4788,7 @@ func TestAttachRequest(t *testing.T) {
 				// Stop at line 8
 				execute: func() {
 					checkStop(t, client, 1, "main.loop", 8)
-					client.VariablesRequest(1001) // Locals
+					client.VariablesRequest(localsScope)
 					locals := client.ExpectVariablesResponse(t)
 					checkChildren(t, locals, "Locals", 1)
 					checkVarRegex(t, locals, 0, "i", "i", "[0-9]+", "int", noChildren)
@@ -4939,32 +4989,30 @@ func TestSetVariable(t *testing.T) {
 
 					checkStop(t, client, 1, "main.foobar", startLineno)
 
-					// Args of foobar(baz string, bar FooBar)
-					args := tester.variables(1000)
+					// Local variables
+					locals := tester.variables(localsScope)
 
-					checkVarExact(t, args, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
-					tester.failSetVariable(1000, "bar", `main.FooBar {Baz: 42, Bur: "ipsum"}`, "*ast.CompositeLit not implemented")
+					// Args of foobar(baz string, bar FooBar)
+					checkVarExact(t, locals, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
+					tester.failSetVariable(localsScope, "bar", `main.FooBar {Baz: 42, Bur: "ipsum"}`, "*ast.CompositeLit not implemented")
 
 					// Nested field.
-					barRef := checkVarExact(t, args, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
+					barRef := checkVarExact(t, locals, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
 					tester.expectSetVariable(barRef, "Baz", "42")
 					tester.evaluate("bar", `main.FooBar {Baz: 42, Bur: "lorem"}`, hasChildren)
 
 					tester.failSetVariable(barRef, "Baz", `"string"`, "can not convert")
 
-					// Local variables
-					locals := tester.variables(1001)
-
 					// int
 					checkVarExact(t, locals, -1, "a2", "a2", "6", "int", noChildren)
-					tester.expectSetVariable(1001, "a2", "42")
+					tester.expectSetVariable(localsScope, "a2", "42")
 					tester.evaluate("a2", "42", noChildren)
 
-					tester.failSetVariable(1001, "a2", "false", "can not convert")
+					tester.failSetVariable(localsScope, "a2", "false", "can not convert")
 
 					// float
 					checkVarExact(t, locals, -1, "a3", "a3", "7.23", "float64", noChildren)
-					tester.expectSetVariable(1001, "a3", "-0.1")
+					tester.expectSetVariable(localsScope, "a3", "-0.1")
 					tester.evaluate("a3", "-0.1", noChildren)
 
 					// array of int
@@ -4972,7 +5020,7 @@ func TestSetVariable(t *testing.T) {
 					tester.expectSetVariable(a4Ref, "[1]", "-7")
 					tester.evaluate("a4", "[2]int [1,-7]", hasChildren)
 
-					tester.failSetVariable(1001, "a4", "[2]int{3, 4}", "not implemented")
+					tester.failSetVariable(localsScope, "a4", "[2]int{3, 4}", "not implemented")
 
 					// slice of int
 					a5Ref := checkVarExact(t, locals, -1, "a5", "a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "[]int", hasChildren)
@@ -4988,7 +5036,7 @@ func TestSetVariable(t *testing.T) {
 
 					// pointer
 					checkVarExact(t, locals, -1, "a9", "a9", `*main.FooBar nil`, "*main.FooBar", noChildren)
-					tester.expectSetVariable(1001, "a9", "&a6")
+					tester.expectSetVariable(localsScope, "a9", "&a6")
 					tester.evaluate("a9", `*main.FooBar {Baz: 8, Bur: "word"}`, hasChildren)
 
 					// slice of pointers
@@ -5002,20 +5050,20 @@ func TestSetVariable(t *testing.T) {
 
 					// complex
 					tester.evaluate("c64", `(1 + 2i)`, hasChildren)
-					tester.expectSetVariable(1001, "c64", "(2 + 3i)")
+					tester.expectSetVariable(localsScope, "c64", "(2 + 3i)")
 					tester.evaluate("c64", `(2 + 3i)`, hasChildren)
 					// note: complex's real, imaginary part can't be directly mutable.
 
 					//
 					// Global variables
 					//    p1 = 10
-					client.VariablesRequest(1002)
+					client.VariablesRequest(globalsScope)
 					globals := client.ExpectVariablesResponse(t)
 
 					checkVarExact(t, globals, -1, "p1", "main.p1", "10", "int", noChildren)
-					tester.expectSetVariable(1002, "p1", "-10")
+					tester.expectSetVariable(globalsScope, "p1", "-10")
 					tester.evaluate("p1", "-10", noChildren)
-					tester.failSetVariable(1002, "p1", "0.1", "can not convert")
+					tester.failSetVariable(globalsScope, "p1", "0.1", "can not convert")
 				},
 				disconnect: true,
 			}})
@@ -5039,21 +5087,21 @@ func TestSetVariable(t *testing.T) {
 					}
 
 					checkStop(t, client, 1, "main.main", startLineno)
-					locals := tester.variables(1001)
+					locals := tester.variables(localsScope)
 
 					// channel
 					tester.evaluate("chnil", "chan int nil", noChildren)
-					tester.expectSetVariable(1001, "chnil", "ch1")
+					tester.expectSetVariable(localsScope, "chnil", "ch1")
 					tester.evaluate("chnil", "chan int 4/11", hasChildren)
 
 					// func
 					tester.evaluate("fn2", "nil", noChildren)
-					tester.expectSetVariable(1001, "fn2", "fn1")
+					tester.expectSetVariable(localsScope, "fn2", "fn1")
 					tester.evaluate("fn2", "main.afunc", noChildren)
 
 					// interface
 					tester.evaluate("ifacenil", "interface {} nil", noChildren)
-					tester.expectSetVariable(1001, "ifacenil", "iface1")
+					tester.expectSetVariable(localsScope, "ifacenil", "iface1")
 					tester.evaluate("ifacenil", "interface {}(*main.astruct) *{A: 1, B: 2}", hasChildren)
 
 					// interface.(data)
@@ -5089,7 +5137,7 @@ func TestSetVariable(t *testing.T) {
 
 					// unsigned pointer
 					checkVarRegex(t, locals, -1, "up1", "up1", `unsafe\.Pointer\(0x[0-9a-f]+\)`, "unsafe.Pointer", noChildren)
-					tester.expectSetVariable(1001, "up1", "unsafe.Pointer(0x0)")
+					tester.expectSetVariable(localsScope, "up1", "unsafe.Pointer(0x0)")
 					tester.evaluate("up1", "unsafe.Pointer(0x0)", noChildren)
 
 					// val := A{val: 1}
@@ -5128,23 +5176,20 @@ func TestSetVariableWithCall(t *testing.T) {
 
 					checkStop(t, client, 1, "main.foobar", startLineno)
 
-					// Args of foobar(baz string, bar FooBar)
-					args := tester.variables(1000)
+					// Local variables
+					locals := tester.variables(localsScope)
 
-					checkVarExact(t, args, 0, "baz", "baz", `"bazburzum"`, "string", noChildren)
-					tester.expectSetVariable(1000, "baz", `"BazBurZum"`)
+					// Args of foobar(baz string, bar FooBar)
+					checkVarExact(t, locals, 0, "baz", "baz", `"bazburzum"`, "string", noChildren)
+					tester.expectSetVariable(localsScope, "baz", `"BazBurZum"`)
 					tester.evaluate("baz", `"BazBurZum"`, noChildren)
 
-					args = tester.variables(1000)
-					barRef := checkVarExact(t, args, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
+					barRef := checkVarExact(t, locals, 1, "bar", "bar", `main.FooBar {Baz: 10, Bur: "lorem"}`, "main.FooBar", hasChildren)
 					tester.expectSetVariable(barRef, "Bur", `"ipsum"`)
 					tester.evaluate("bar", `main.FooBar {Baz: 10, Bur: "ipsum"}`, hasChildren)
 
-					// Local variables
-					locals := tester.variables(1001)
-
 					checkVarExact(t, locals, -1, "a1", "a1", `"foofoofoofoofoofoo"`, "string", noChildren)
-					tester.expectSetVariable(1001, "a1", `"barbarbar"`)
+					tester.expectSetVariable(localsScope, "a1", `"barbarbar"`)
 					tester.evaluate("a1", `"barbarbar"`, noChildren)
 
 					a6Ref := checkVarExact(t, locals, -1, "a6", "a6", `main.FooBar {Baz: 8, Bur: "word"}`, "main.FooBar", hasChildren)
@@ -5161,9 +5206,9 @@ func TestSetVariableWithCall(t *testing.T) {
 					checkStop(t, client, 1, "main.barfoo", -1)
 					// Test: set string 'a1' in main.barfoo.
 					// This shouldn't affect 'a1' in main.foobar - we will check that in the next breakpoint.
-					locals := tester.variables(1001)
+					locals := tester.variables(localsScope)
 					checkVarExact(t, locals, -1, "a1", "a1", `"bur"`, "string", noChildren)
-					tester.expectSetVariable(1001, "a1", `"fur"`)
+					tester.expectSetVariable(localsScope, "a1", `"fur"`)
 					tester.evaluate("a1", `"fur"`, noChildren)
 					// We will check a1 in main.foobar isn't affected from the next breakpoint.
 
@@ -5196,17 +5241,17 @@ func TestSetVariableWithCall(t *testing.T) {
 
 					checkStop(t, client, 1, "main.main", -1)
 
-					_ = tester.variables(1001)
+					_ = tester.variables(localsScope)
 
 					// successful variable set using a function call.
-					tester.expectSetVariable(1001, "str", `callstacktrace()`)
+					tester.expectSetVariable(localsScope, "str", `callstacktrace()`)
 					tester.evaluateRegex("str", `.*in main.callstacktrace at.*`, noChildren)
 
-					tester.failSetVariableAndStop(1001, "str", `callpanic()`, `callpanic panicked`)
+					tester.failSetVariableAndStop(localsScope, "str", `callpanic()`, `callpanic panicked`)
 					checkStop(t, client, 1, "main.main", -1)
 
 					// breakpoint during a function call.
-					tester.failSetVariableAndStop(1001, "str", `callbreak()`, "call stopped")
+					tester.failSetVariableAndStop(localsScope, "str", `callbreak()`, "call stopped")
 
 					// TODO(hyangah): continue after this causes runtime error while resuming
 					// unfinished injected call.