Repository: go-delve/delve (go)

Issue:
dap: consider []string for Launch BuildFlags
DAP [`LaunchConfig.BuildFlags`](https://github.com/go-delve/delve/blob/c7e2a9b6fb902d685e77b28b001c0c95e5eb95b3/service/dap/types.go#L101) is defined as `string` type.
It is `string` because VS Code Go defined the `buildFlags` in that way in its legacy debug adapter that invokes `dlv debug` or `dlv test`, and `dlv` command line takes the build flags (--build-flags) as a string.

DAP can take Launch configuration in JSON, which can support more rich types than a simple command line flag.
Historically, the subtle difference between how dlv is parsing the build flags and how go command is parsing the flags was annoying and sometimes frustrating (e.g. https://github.com/golang/vscode-go/issues/1027).

Please consider to take `[]string` and bypass delve's flag parsing.

Pull request:
service/dap: accept a string list as launch request's buildFlags
This change accepts both string type and []string type (actually, []interface{} type due to Go's json decoding behavior).

Fixes #2718
For golang/vscode-go#1831, golang/vscode-go#1027

cc @suzmue

Patch:
diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md
index 2754e695..a288dd1e 100644
--- a/Documentation/cli/README.md
+++ b/Documentation/cli/README.md
@@ -89,6 +89,7 @@ Command | Description
 [help](#help) | Prints the help message.
 [libraries](#libraries) | List loaded dynamic libraries
 [list](#list) | Show source code.
+[packages](#packages) | Print list of packages.
 [source](#source) | Executes a file containing a list of delve commands
 [sources](#sources) | Print list of source files.
 [target](#target) | Manages child process debugging.
@@ -539,6 +540,14 @@ The command 'on <bp> cond <cond-arguments>' is equivalent to 'cond <bp> <cond-ar
 The command 'on x -edit' can be used to edit the list of commands executed when the breakpoint is hit.
 
 
+## packages
+Print list of packages.
+
+	packages [<regex>]
+
+If regex is specified only the packages matching it will be returned.
+
+
 ## print
 Evaluate an expression.
 
diff --git a/Documentation/cli/starlark.md b/Documentation/cli/starlark.md
index da0b3d3a..7546961d 100644
--- a/Documentation/cli/starlark.md
+++ b/Documentation/cli/starlark.md
@@ -54,7 +54,7 @@ functions(Filter) | Equivalent to API call [ListFunctions](https://godoc.org/git
 goroutines(Start, Count, Filters, GoroutineGroupingOptions, EvalScope) | Equivalent to API call [ListGoroutines](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListGoroutines)
 local_vars(Scope, Cfg) | Equivalent to API call [ListLocalVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListLocalVars)
 package_vars(Filter, Cfg) | Equivalent to API call [ListPackageVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackageVars)
-packages_build_info(IncludeFiles) | Equivalent to API call [ListPackagesBuildInfo](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackagesBuildInfo)
+packages_build_info(IncludeFiles, Filter) | Equivalent to API call [ListPackagesBuildInfo](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackagesBuildInfo)
 registers(ThreadID, IncludeFp, Scope) | Equivalent to API call [ListRegisters](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListRegisters)
 sources(Filter) | Equivalent to API call [ListSources](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListSources)
 targets() | Equivalent to API call [ListTargets](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListTargets)
diff --git a/_scripts/gen-cli-docs.go b/_scripts/gen-cli-docs.go
index 63b00c00..20246946 100644
--- a/_scripts/gen-cli-docs.go
+++ b/_scripts/gen-cli-docs.go
@@ -12,7 +12,7 @@ import (
 )
 
 func main() {
-	fh, err := os.Create(os.ExpandEnv("./Documentation/cli/README.md"))
+	fh, err := os.Create("./Documentation/cli/README.md")
 	if err != nil {
 		log.Fatalf("could not create README.md: %v", err)
 	}
diff --git a/pkg/gobuild/gobuild.go b/pkg/gobuild/gobuild.go
index b495c2e1..9799336d 100644
--- a/pkg/gobuild/gobuild.go
+++ b/pkg/gobuild/gobuild.go
@@ -41,8 +41,11 @@ 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) (string, []byte, error) {
-	args := goBuildArgs(debugname, pkgs, buildflags, false)
+func GoBuildCombinedOutput(debugname string, pkgs []string, buildflags interface{}) (string, []byte, error) {
+	args, err := goBuildArgs2(debugname, pkgs, buildflags, false)
+	if err != nil {
+		return "", nil, err
+	}
 	return gocommandCombinedOutput("build", args...)
 }
 
@@ -55,8 +58,11 @@ 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) (string, []byte, error) {
-	args := goBuildArgs(debugname, pkgs, buildflags, true)
+func GoTestBuildCombinedOutput(debugname string, pkgs []string, buildflags interface{}) (string, []byte, error) {
+	args, err := goBuildArgs2(debugname, pkgs, buildflags, true)
+	if err != nil {
+		return "", nil, err
+	}
 	return gocommandCombinedOutput("test", args...)
 }
 
@@ -84,6 +90,27 @@ func goBuildArgs(debugname string, pkgs []string, buildflags string, isTest bool
 	return args
 }
 
+// goBuildArgs2 is like goBuildArgs, but takes either string or []string.
+func goBuildArgs2(debugname string, pkgs []string, buildflags interface{}, isTest bool) ([]string, error) {
+	var args []string
+	switch buildflags := buildflags.(type) {
+	case string:
+		return goBuildArgs(debugname, pkgs, buildflags, isTest), nil
+	case nil:
+	case []string:
+		args = append(args, buildflags...)
+	default:
+		return nil, fmt.Errorf("invalid buildflags type %T", buildflags)
+	}
+
+	args = append(args, "-o", debugname)
+	if isTest {
+		args = append([]string{"-c"}, args...)
+	}
+	args = append(args, "-gcflags", "all=-N -l")
+	return append(args, pkgs...), nil
+}
+
 func gocommandRun(command string, args ...string) error {
 	_, goBuild := gocommandExecCmd(command, args...)
 	goBuild.Stderr = os.Stdout
diff --git a/pkg/proc/internal/ebpf/helpers.go b/pkg/proc/internal/ebpf/helpers.go
index cddb4af7..3f4fde40 100644
--- a/pkg/proc/internal/ebpf/helpers.go
+++ b/pkg/proc/internal/ebpf/helpers.go
@@ -71,10 +71,8 @@ func (ctx *EBPFContext) Close() {
 	if ctx.objs != nil {
 		ctx.objs.Close()
 	}
-	if ctx.links != nil {
-		for _, l := range ctx.links {
-			l.Close()
-		}
+	for _, l := range ctx.links {
+		l.Close()
 	}
 }
 
diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go
index 886eb27a..abe6a867 100644
--- a/pkg/terminal/command.go
+++ b/pkg/terminal/command.go
@@ -356,6 +356,11 @@ If regex is specified only the functions matching it will be returned.`},
 	types [<regex>]
 
 If regex is specified only the types matching it will be returned.`},
+		{aliases: []string{"packages"}, cmdFn: packages, helpMsg: `Print list of packages.
+
+	packages [<regex>]
+
+If regex is specified only the packages matching it will be returned.`},
 		{aliases: []string{"args"}, allowedPrefixes: onPrefix | deferredPrefix, group: dataCmds, cmdFn: args, helpMsg: `Print function arguments.
 
 	[goroutine <n>] [frame <m>] args [-v] [<regex>]
@@ -2224,6 +2229,18 @@ func sources(t *Term, ctx callContext, args string) error {
 	return t.printSortedStrings(t.client.ListSources(args))
 }
 
+func packages(t *Term, ctx callContext, args string) error {
+	info, err := t.client.ListPackagesBuildInfo(args, false)
+	if err != nil {
+		return err
+	}
+	pkgs := make([]string, 0, len(info))
+	for _, i := range info {
+		pkgs = append(pkgs, i.ImportPath)
+	}
+	return t.printSortedStrings(pkgs, nil)
+}
+
 func funcs(t *Term, ctx callContext, args string) error {
 	return t.printSortedStrings(t.client.ListFunctions(args))
 }
diff --git a/pkg/terminal/starbind/starlark_mapping.go b/pkg/terminal/starbind/starlark_mapping.go
index 967e1bf9..35f1f075 100644
--- a/pkg/terminal/starbind/starlark_mapping.go
+++ b/pkg/terminal/starbind/starlark_mapping.go
@@ -1275,11 +1275,19 @@ func (env *Env) starlarkPredeclare() (starlark.StringDict, map[string]string) {
 				return starlark.None, decorateError(thread, err)
 			}
 		}
+		if len(args) > 1 && args[1] != starlark.None {
+			err := unmarshalStarlarkValue(args[1], &rpcArgs.Filter, "Filter")
+			if err != nil {
+				return starlark.None, decorateError(thread, err)
+			}
+		}
 		for _, kv := range kwargs {
 			var err error
 			switch kv[0].(starlark.String) {
 			case "IncludeFiles":
 				err = unmarshalStarlarkValue(kv[1], &rpcArgs.IncludeFiles, "IncludeFiles")
+			case "Filter":
+				err = unmarshalStarlarkValue(kv[1], &rpcArgs.Filter, "Filter")
 			default:
 				err = fmt.Errorf("unknown argument %q", kv[0])
 			}
@@ -1293,7 +1301,7 @@ func (env *Env) starlarkPredeclare() (starlark.StringDict, map[string]string) {
 		}
 		return env.interfaceToStarlarkValue(rpcRet), nil
 	})
-	doc["packages_build_info"] = "builtin packages_build_info(IncludeFiles)\n\npackages_build_info returns the list of packages used by the program along with\nthe directory where each package was compiled and optionally the list of\nfiles constituting the package.\nNote that the directory path is a best guess and may be wrong is a tool\nother than cmd/go is used to perform the build."
+	doc["packages_build_info"] = "builtin packages_build_info(IncludeFiles, Filter)\n\npackages_build_info returns the list of packages used by the program along with\nthe directory where each package was compiled and optionally the list of\nfiles constituting the package.\nNote that the directory path is a best guess and may be wrong is a tool\nother than cmd/go is used to perform the build."
 	r["registers"] = starlark.NewBuiltin("registers", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
 		if err := isCancelled(thread); err != nil {
 			return starlark.None, decorateError(thread, err)
diff --git a/service/client.go b/service/client.go
index 6e42224f..a76d0a1d 100644
--- a/service/client.go
+++ b/service/client.go
@@ -108,6 +108,8 @@ type Client interface {
 	ListFunctions(filter string) ([]string, error)
 	// ListTypes lists all types in the process matching filter.
 	ListTypes(filter string) ([]string, error)
+	// ListPackagesBuildInfo lists all packages in the process matching filter.
+	ListPackagesBuildInfo(filter string, includeFiles bool) ([]api.PackageBuildInfo, error)
 	// ListLocalVariables lists all local variables in scope.
 	ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error)
 	// ListFunctionArgs lists all arguments to the current function.
diff --git a/service/dap/server.go b/service/dap/server.go
index 5bb38f1b..7a6459b9 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -982,9 +982,9 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
 
 		switch args.Mode {
 		case "debug":
-			cmd, out, err = gobuild.GoBuildCombinedOutput(args.Output, []string{args.Program}, args.BuildFlags)
+			cmd, out, err = gobuild.GoBuildCombinedOutput(args.Output, []string{args.Program}, args.BuildFlags.value)
 		case "test":
-			cmd, out, err = gobuild.GoTestBuildCombinedOutput(args.Output, []string{args.Program}, args.BuildFlags)
+			cmd, out, err = gobuild.GoTestBuildCombinedOutput(args.Output, []string{args.Program}, args.BuildFlags.value)
 		}
 		args.DlvCwd, _ = filepath.Abs(args.DlvCwd)
 		s.config.log.Debugf("building from %q: [%s]", args.DlvCwd, cmd)
@@ -1684,7 +1684,17 @@ func fnName(loc *proc.Location) string {
 	if loc.Fn == nil {
 		return "???"
 	}
-	return loc.Fn.Name
+	fullSymbol := loc.Fn.Name
+	packagePath := loc.Fn.PackageName()
+	lastSlash := strings.LastIndex(packagePath, "/")
+	if lastSlash >= 0 {
+		// strip everything until the last slash from the package path
+		return fullSymbol[lastSlash+1:]
+	}
+
+	// We either have no package name at all, or it doesn't contain a slash:
+	// return name unchanged
+	return fullSymbol
 }
 
 func fnPackageName(loc *proc.Location) string {
diff --git a/service/dap/types.go b/service/dap/types.go
index c7aed4a2..aee23573 100644
--- a/service/dap/types.go
+++ b/service/dap/types.go
@@ -102,9 +102,15 @@ type LaunchConfig struct {
 	// Relative paths used in BuildFlags will be interpreted as paths
 	// relative to Delve's current working directory.
 	//
-	// It is like `dlv --build-flags`. For example,
-	//    "buildFlags": "-tags=integration -mod=vendor -cover -v"
-	BuildFlags string `json:"buildFlags,omitempty"`
+	// It should be a string like `dlv --build-flags`, or
+	// an array of strings that is augmented when invoking the go build or
+	// test command through os/exec.Command API.
+	// For example, both forms are acceptible.
+	//    "buildFlags": "-tags=integration -ldflags='-X main.Hello=World'"
+	// or
+	//    "buildFlags": ["-tags=integration", "-ldflags=-X main.Hello=World"]
+	// Using other types is an error.
+	BuildFlags BuildFlags `json:"buildFlags,omitempty"`
 
 	// Output path for the binary of the debuggee.
 	// Relative path is interpreted as the path relative to
@@ -268,3 +274,30 @@ func prettyPrint(config interface{}) string {
 	}
 	return string(pretty)
 }
+
+// BuildFlags is either string or []string.
+type BuildFlags struct {
+	value interface{}
+}
+
+func (s *BuildFlags) UnmarshalJSON(b []byte) error {
+	if v := string(b); v == "" || v == "null" {
+		s.value = nil
+		return nil
+	}
+	var strs []string
+	if err := json.Unmarshal(b, &strs); err == nil {
+		s.value = strs
+		return nil
+	}
+	var str string
+	if err := json.Unmarshal(b, &str); err != nil {
+		s.value = nil
+		if uerr, ok := err.(*json.UnmarshalTypeError); ok {
+			return fmt.Errorf(`cannot unmarshal %v into "buildFlags" of type []string or string`, uerr.Value)
+		}
+		return err
+	}
+	s.value = str
+	return nil
+}
diff --git a/service/rpc2/client.go b/service/rpc2/client.go
index 22f88382..085ab043 100644
--- a/service/rpc2/client.go
+++ b/service/rpc2/client.go
@@ -357,6 +357,12 @@ func (c *RPCClient) ListPackageVariables(filter string, cfg api.LoadConfig) ([]a
 	return out.Variables, err
 }
 
+func (c *RPCClient) ListPackagesBuildInfo(filter string, includeFiles bool) ([]api.PackageBuildInfo, error) {
+	var out ListPackagesBuildInfoOut
+	err := c.call("ListPackagesBuildInfo", ListPackagesBuildInfoIn{Filter: filter, IncludeFiles: includeFiles}, &out)
+	return out.List, err
+}
+
 func (c *RPCClient) ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) {
 	var out ListLocalVarsOut
 	err := c.call("ListLocalVars", ListLocalVarsIn{scope, cfg}, &out)
diff --git a/service/rpc2/server.go b/service/rpc2/server.go
index ab18d232..dbc39b48 100644
--- a/service/rpc2/server.go
+++ b/service/rpc2/server.go
@@ -3,6 +3,7 @@ package rpc2
 import (
 	"errors"
 	"fmt"
+	"regexp"
 	"sort"
 	"time"
 
@@ -912,12 +913,13 @@ func (s *RPCServer) ListDynamicLibraries(in ListDynamicLibrariesIn, out *ListDyn
 	return nil
 }
 
-// ListPackagesBuildInfoIn holds the arguments of ListPackages.
+// ListPackagesBuildInfoIn holds the arguments of ListPackagesBuildInfo.
 type ListPackagesBuildInfoIn struct {
 	IncludeFiles bool
+	Filter       string // if not empty, returns only packages matching the regexp.
 }
 
-// ListPackagesBuildInfoOut holds the return values of ListPackages.
+// ListPackagesBuildInfoOut holds the return values of ListPackagesBuildInfo.
 type ListPackagesBuildInfoOut struct {
 	List []api.PackageBuildInfo
 }
@@ -928,9 +930,20 @@ type ListPackagesBuildInfoOut struct {
 // Note that the directory path is a best guess and may be wrong is a tool
 // other than cmd/go is used to perform the build.
 func (s *RPCServer) ListPackagesBuildInfo(in ListPackagesBuildInfoIn, out *ListPackagesBuildInfoOut) error {
+	var pattern *regexp.Regexp
+	if in.Filter != "" {
+		p, err := regexp.Compile(in.Filter)
+		if err != nil {
+			return fmt.Errorf("invalid Filter pattern: %v", err)
+		}
+		pattern = p
+	}
 	pkgs := s.debugger.ListPackagesBuildInfo(in.IncludeFiles)
 	out.List = make([]api.PackageBuildInfo, 0, len(pkgs))
 	for _, pkg := range pkgs {
+		if pattern != nil && !pattern.MatchString(pkg.ImportPath) {
+			continue
+		}
 		var files []string
 
 		if len(pkg.Files) > 0 {


Test patch:
diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go
index 550e7290..d6ceaafa 100644
--- a/cmd/dlv/dlv_test.go
+++ b/cmd/dlv/dlv_test.go
@@ -208,7 +208,7 @@ func getDlvBin(t *testing.T) string {
 	// from getDlvBinEBPF lets clear it here so
 	// we can ensure we don't get build errors
 	// depending on the test ordering.
-	os.Setenv("CGO_LDFLAGS", ldFlags)
+	t.Setenv("CGO_LDFLAGS", ldFlags)
 	var tags string
 	if runtime.GOOS == "windows" && runtime.GOARCH == "arm64" {
 		tags = "-tags=exp.winarm64"
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index 6de1ba1b..b2f3a3bb 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -2981,7 +2981,7 @@ func TestPackageWithPathVar(t *testing.T) {
 
 func TestEnvironment(t *testing.T) {
 	protest.AllowRecording(t)
-	os.Setenv("SOMEVAR", "bah")
+	t.Setenv("SOMEVAR", "bah")
 	withTestProcess("testenv", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 		assertNoError(grp.Continue(), t, "Continue()")
 		v := evalVariable(p, t, "x")
@@ -3047,7 +3047,7 @@ func TestIssue877(t *testing.T) {
 		t.Skip("broken")
 	}
 	const envval = "/usr/local/lib"
-	os.Setenv("DYLD_LIBRARY_PATH", envval)
+	t.Setenv("DYLD_LIBRARY_PATH", envval)
 	withTestProcess("issue877", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 		assertNoError(grp.Continue(), t, "Continue()")
 		v := evalVariable(p, t, "dyldenv")
@@ -4479,12 +4479,7 @@ func TestListImages(t *testing.T) {
 }
 
 func TestAncestors(t *testing.T) {
-	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
-		t.Skip("not supported on Go <= 1.10")
-	}
-	savedGodebug := os.Getenv("GODEBUG")
-	os.Setenv("GODEBUG", "tracebackancestors=100")
-	defer os.Setenv("GODEBUG", savedGodebug)
+	t.Setenv("GODEBUG", "tracebackancestors=100")
 	withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
 		setFunctionBreakpoint(p, t, "main.testgoroutine")
 		assertNoError(grp.Continue(), t, "Continue()")
diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go
index 403c9e8d..3654fc26 100644
--- a/pkg/terminal/command_test.go
+++ b/pkg/terminal/command_test.go
@@ -122,7 +122,7 @@ func withTestTerminalBuildFlags(name string, t testing.TB, buildFlags test.Build
 	if testBackend == "rr" {
 		test.MustHaveRecordingAllowed(t)
 	}
-	os.Setenv("TERM", "dumb")
+	t.Setenv("TERM", "dumb")
 	listener, err := net.Listen("tcp", "127.0.0.1:0")
 	if err != nil {
 		t.Fatalf("couldn't start listener: %s\n", err)
@@ -1445,3 +1445,31 @@ func TestRestartBreakpoints(t *testing.T) {
 		}
 	})
 }
+
+func TestListPackages(t *testing.T) {
+	test.AllowRecording(t)
+	withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) {
+		out := term.MustExec("packages")
+		t.Logf("> packages\n%s", out)
+		seen := map[string]bool{}
+		for _, p := range strings.Split(strings.TrimSpace(out), "\n") {
+			seen[p] = true
+		}
+		if !seen["main"] || !seen["runtime"] {
+			t.Error("output omits 'main' and 'runtime'")
+		}
+
+		out = term.MustExec("packages runtime")
+		t.Logf("> packages runtime\n%s", out)
+
+		for _, p := range strings.Split(strings.TrimSpace(out), "\n") {
+			if !strings.Contains(p, "runtime") {
+				t.Errorf("output includes unexpected %q", p)
+			}
+			seen[p] = true
+		}
+		if !seen["runtime"] {
+			t.Error("output omits 'runtime'")
+		}
+	})
+}
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 26b642d7..d54f332d 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -1269,6 +1269,71 @@ func TestStackTraceRequest(t *testing.T) {
 	})
 }
 
+func TestFunctionNameFormattingInStackTrace(t *testing.T) {
+	runTest(t, "consts", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSessionWithBPs(t, client, "launch",
+			// Launch
+			func() {
+				client.LaunchRequestWithArgs(map[string]interface{}{
+					"mode": "exec", "program": fixture.Path,
+				})
+			},
+			// Breakpoints are set within the program
+			fixture.Source, []int{},
+			[]onBreakpoint{{
+				// Stop at line 36
+				execute: func() {
+					if runtime.GOARCH == "386" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) {
+						client.StepInRequest(1)
+						client.ExpectStepInResponse(t)
+						client.ExpectStoppedEvent(t)
+					}
+					client.StackTraceRequest(1, 0, 20)
+					stack := client.ExpectStackTraceResponse(t)
+					checkStackFramesExact(t, stack, "main.main", 36, 1000, 3, 3)
+
+					// Step into pkg.AnotherMethod()
+					client.StepInRequest(1)
+					client.ExpectStepInResponse(t)
+					client.ExpectStoppedEvent(t)
+
+					client.StackTraceRequest(1, 0, 20)
+					stack = client.ExpectStackTraceResponse(t)
+					checkStackFramesExact(t, stack, "pkg.(*SomeType).AnotherMethod", 13, 1000, 4, 4)
+				},
+				disconnect: true,
+			}})
+	})
+}
+
+func Test_fnName(t *testing.T) {
+	tests := []struct {
+		symbol string
+		want   string
+	}{
+		{
+			symbol: "pkg.functionName",
+			want:   "pkg.functionName",
+		},
+		{
+			symbol: "github.com/some/long/package/path/pkg.(*SomeType).Method",
+			want:   "pkg.(*SomeType).Method",
+		},
+		{
+			symbol: "github.com/some/path/pkg.typeparametric[go.shape.struct { example.com/blah/otherpkg.x int }]",
+			want:   "pkg.typeparametric[go.shape.struct { example.com/blah/otherpkg.x int }]",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.symbol, func(t *testing.T) {
+			loc := proc.Location{Fn: &proc.Function{Name: tt.symbol}}
+			if got := fnName(&loc); got != tt.want {
+				t.Errorf("fnName() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
 func TestSelectedThreadsRequest(t *testing.T) {
 	runTest(t, "goroutinestackprog", func(client *daptest.Client, fixture protest.Fixture) {
 		runDebugSessionWithBPs(t, client, "launch",
@@ -5547,7 +5612,7 @@ func TestLaunchTestRequest(t *testing.T) {
 				testFile, []int{14},
 				[]onBreakpoint{{
 					execute: func() {
-						checkStop(t, client, -1, "github.com/go-delve/delve/_fixtures/buildtest.TestCurrentDirectory", 14)
+						checkStop(t, client, -1, "buildtest.TestCurrentDirectory", 14)
 						client.VariablesRequest(1001) // Locals
 						locals := client.ExpectVariablesResponse(t)
 						checkChildren(t, locals, "Locals", 1)
@@ -5594,6 +5659,18 @@ func TestLaunchRequestWithBuildFlags(t *testing.T) {
 	})
 }
 
+func TestLaunchRequestWithBuildFlags2(t *testing.T) {
+	runTest(t, "buildflagtest", func(client *daptest.Client, fixture protest.Fixture) {
+		runDebugSession(t, client, "launch", func() {
+			// We reuse the harness that builds, but ignore the built binary,
+			// only relying on the source to be built in response to LaunchRequest.
+			client.LaunchRequestWithArgs(map[string]interface{}{
+				"mode": "debug", "program": fixture.Source, "output": "__mybin",
+				"buildFlags": []string{"-ldflags", "-X main.Hello=World"}})
+		})
+	})
+}
+
 func TestLaunchRequestWithEnv(t *testing.T) {
 	// testenv fixture will lookup SOMEVAR with
 	//   x, y := os.Lookup("SOMEVAR")
@@ -5654,16 +5731,9 @@ func TestLaunchRequestWithEnv(t *testing.T) {
 	} {
 
 		t.Run(tc.name, func(t *testing.T) {
-			// cleanup
-			defer func() {
-				os.Unsetenv("SOMEVAR")
-				os.Unsetenv("SOMEVAR2")
-				os.Unsetenv("SOMEVAR3")
-			}()
-
 			for k, v := range tc.initEnv {
 				if v != nil {
-					os.Setenv(k, *v)
+					t.Setenv(k, *v)
 				}
 			}
 
@@ -6304,7 +6374,7 @@ func TestBadLaunchRequests(t *testing.T) {
 		// Bad "buildFlags"
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "buildFlags": 123})
 		checkFailedToLaunchWithMessage(client.ExpectVisibleErrorResponse(t),
-			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"buildFlags\" of type string")
+			"Failed to launch: invalid debug configuration - cannot unmarshal number into \"buildFlags\" of type []string or string")
 
 		// Bad "backend"
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "backend": 123})
diff --git a/service/debugger/debugger_test.go b/service/debugger/debugger_test.go
index 3e78257b..c493ff98 100644
--- a/service/debugger/debugger_test.go
+++ b/service/debugger/debugger_test.go
@@ -34,13 +34,6 @@ func TestDebugger_LaunchNoMain(t *testing.T) {
 }
 
 func TestDebugger_LaunchInvalidFormat(t *testing.T) {
-	goos := os.Getenv("GOOS")
-	goarch := os.Getenv("GOARCH")
-	defer func() {
-		// restore environment values
-		os.Setenv("GOOS", goos)
-		os.Setenv("GOARCH", goarch)
-	}()
 	fixturesDir := protest.FindFixturesDir()
 	buildtestdir := filepath.Join(fixturesDir, "buildtest")
 	debugname := "debug"
@@ -51,12 +44,12 @@ func TestDebugger_LaunchInvalidFormat(t *testing.T) {
 		"linux":   "windows",
 	}
 	if runtime.GOARCH == "arm64" && runtime.GOOS == "linux" {
-		os.Setenv("GOARCH", "amd64")
+		t.Setenv("GOARCH", "amd64")
 	}
 	if runtime.GOARCH == "ppc64le" && runtime.GOOS == "linux" {
-		os.Setenv("GOARCH", "amd64")
+		t.Setenv("GOARCH", "amd64")
 	}
-	os.Setenv("GOOS", switchOS[runtime.GOOS])
+	t.Setenv("GOOS", switchOS[runtime.GOOS])
 	exepath := filepath.Join(buildtestdir, debugname)
 	if err := gobuild.GoBuild(debugname, []string{buildtestdir}, fmt.Sprintf("-o %s", exepath)); err != nil {
 		t.Fatalf("go build error %v", err)
diff --git a/service/debugger/debugger_unix_test.go b/service/debugger/debugger_unix_test.go
index d0bc009a..12a196bf 100644
--- a/service/debugger/debugger_unix_test.go
+++ b/service/debugger/debugger_unix_test.go
@@ -20,10 +20,6 @@ import (
 )
 
 func TestDebugger_LaunchNoExecutablePerm(t *testing.T) {
-	defer func() {
-		os.Setenv("GOOS", runtime.GOOS)
-		os.Setenv("GOARCH", runtime.GOARCH)
-	}()
 	fixturesDir := protest.FindFixturesDir()
 	buildtestdir := filepath.Join(fixturesDir, "buildtest")
 	debugname := "debug"
@@ -34,12 +30,12 @@ func TestDebugger_LaunchNoExecutablePerm(t *testing.T) {
 		"linux":   "windows",
 	}
 	if runtime.GOARCH == "arm64" && runtime.GOOS == "linux" {
-		os.Setenv("GOARCH", "amd64")
+		t.Setenv("GOARCH", "amd64")
 	}
 	if runtime.GOARCH == "ppc64le" && runtime.GOOS == "linux" {
-		os.Setenv("GOARCH", "amd64")
+		t.Setenv("GOARCH", "amd64")
 	}
-	os.Setenv("GOOS", switchOS[runtime.GOOS])
+	t.Setenv("GOOS", switchOS[runtime.GOOS])
 	exepath := filepath.Join(buildtestdir, debugname)
 	defer os.Remove(exepath)
 	if err := gobuild.GoBuild(debugname, []string{buildtestdir}, fmt.Sprintf("-o %s", exepath)); err != nil {
@@ -65,8 +61,8 @@ func TestDebugger_LaunchWithTTY(t *testing.T) {
 		}
 	}
 	// Ensure no env meddling is leftover from previous tests.
-	os.Setenv("GOOS", runtime.GOOS)
-	os.Setenv("GOARCH", runtime.GOARCH)
+	t.Setenv("GOOS", runtime.GOOS)
+	t.Setenv("GOARCH", runtime.GOARCH)
 
 	p, tty, err := pty.Open()
 	if err != nil {
diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go
index fa41dbfc..936f0784 100644
--- a/service/test/integration2_test.go
+++ b/service/test/integration2_test.go
@@ -231,7 +231,7 @@ func main() {
 
 func TestRestart_rebuild(t *testing.T) {
 	// In the original fixture file the env var tested for is SOMEVAR.
-	os.Setenv("SOMEVAR", "bah")
+	t.Setenv("SOMEVAR", "bah")
 
 	withTestClient2Extended("testenv", t, 0, [3]string{}, nil, func(c service.Client, f protest.Fixture) {
 		<-c.Continue()
@@ -258,7 +258,7 @@ func TestRestart_rebuild(t *testing.T) {
 
 		// First set our new env var and ensure later that the
 		// modified source code picks it up.
-		os.Setenv("SOMEMODIFIEDVAR", "foobar")
+		t.Setenv("SOMEMODIFIEDVAR", "foobar")
 
 		// Restart the program, rebuilding from source.
 		_, err = c.Restart(true)
@@ -2194,9 +2194,7 @@ func TestAncestors(t *testing.T) {
 	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) {
 		t.Skip("not supported on Go <= 1.10")
 	}
-	savedGodebug := os.Getenv("GODEBUG")
-	os.Setenv("GODEBUG", "tracebackancestors=100")
-	defer os.Setenv("GODEBUG", savedGodebug)
+	t.Setenv("GODEBUG", "tracebackancestors=100")
 	withTestClient2("testnextprog", t, func(c service.Client) {
 		_, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.testgoroutine", Line: -1})
 		assertNoError(err, t, "CreateBreakpoint")