Show pprof labels in thread names (#3501)

* Add pprofLabelForThreadNames config

The config is a string value that indicates the key of a pprof label whose value
should be shown as a goroutine name in the threads view.
This commit is contained in:
Stefan Haller 2023-12-04 15:44:10 +01:00 committed by GitHub
parent f558ca4f32
commit f8c8b33da3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 243 additions and 64 deletions

@ -33,6 +33,7 @@ In addition to the general [DAP spec](https://microsoft.github.io/debug-adapter-
stackTraceDepth<br>
showGlobalVariables<br>
showRegisters<br>
showPprofLabels<br>
hideSystemGoroutines<br>
goroutineFilters
</tr>

@ -62,7 +62,14 @@ Type "help" followed by the name of a command for more information about it.`
dlv config substitutePath -clear
Adds or removes a path substitution rule. If -clear is used all substitutePath rules are removed.
See also Documentation/cli/substitutepath.md.`
See also Documentation/cli/substitutepath.md.
dlv config showPprofLabels <label>
dlv config showPprofLabels -clear <label>
dlv config showPprofLabels -clear
Adds or removes a label key to show in the callstack view. If -clear is used without an argument,
all labels are removed.`
msgSources = `Print list of source files.
dlv sources [<regex>]
@ -138,7 +145,7 @@ func (s *Session) evaluateConfig(_, _ int, expr string) (string, error) {
Areas: []dap.InvalidatedAreas{"variables"},
},
})
case "goroutineFilters", "hideSystemGoroutines":
case "goroutineFilters", "hideSystemGoroutines", "showPprofLabels":
// Thread related data has become invalidated.
s.send(&dap.InvalidatedEvent{
Event: *newEvent("invalidated"),

@ -37,6 +37,15 @@ func configureSet(sargs *launchAttachArgs, args string) (bool, string, error) {
return true, config.ConfigureListByName(sargs, cfgname, "cfgName"), nil
}
if cfgname == "showPprofLabels" {
err := configureSetShowPprofLabels(sargs, rest)
if err != nil {
return false, "", err
}
// Print the updated labels
return true, config.ConfigureListByName(sargs, cfgname, "cfgName"), nil
}
err := config.ConfigureSetSimple(rest, cfgname, field)
if err != nil {
return false, "", err
@ -85,3 +94,37 @@ func configureSetSubstitutePath(args *launchAttachArgs, rest string) error {
}
return nil
}
func configureSetShowPprofLabels(args *launchAttachArgs, rest string) error {
if strings.TrimSpace(rest) == "-clear" {
args.ShowPprofLabels = args.ShowPprofLabels[:0]
return nil
}
delete := false
argv := config.SplitQuotedFields(rest, '"')
if len(argv) == 2 && argv[0] == "-clear" {
argv = argv[1:]
delete = true
}
switch len(argv) {
case 0:
// do nothing, let caller show the current list of labels
return nil
case 1:
if delete {
for i := range args.ShowPprofLabels {
if args.ShowPprofLabels[i] == argv[0] {
copy(args.ShowPprofLabels[i:], args.ShowPprofLabels[i+1:])
args.ShowPprofLabels = args.ShowPprofLabels[:len(args.ShowPprofLabels)-1]
return nil
}
}
return fmt.Errorf("could not find label %q", argv[0])
} else {
args.ShowPprofLabels = append(args.ShowPprofLabels, argv[0])
}
default:
return fmt.Errorf("too many arguments to \"config showPprofLabels\"")
}
return nil
}

@ -18,14 +18,14 @@ func TestListConfig(t *testing.T) {
args: args{
args: &launchAttachArgs{},
},
want: formatConfig(0, false, false, "", false, [][2]string{}),
want: formatConfig(0, false, false, "", []string{}, false, [][2]string{}),
},
{
name: "default values",
args: args{
args: &defaultArgs,
},
want: formatConfig(50, false, false, "", false, [][2]string{}),
want: formatConfig(50, false, false, "", []string{}, false, [][2]string{}),
},
{
name: "custom values",
@ -33,11 +33,13 @@ func TestListConfig(t *testing.T) {
args: &launchAttachArgs{
StackTraceDepth: 35,
ShowGlobalVariables: true,
GoroutineFilters: "SomeFilter",
ShowPprofLabels: []string{"SomeLabel"},
substitutePathClientToServer: [][2]string{{"hello", "world"}},
substitutePathServerToClient: [][2]string{{"world", "hello"}},
},
},
want: formatConfig(35, true, false, "", false, [][2]string{{"hello", "world"}}),
want: formatConfig(35, true, false, "SomeFilter", []string{"SomeLabel"}, false, [][2]string{{"hello", "world"}}),
},
}
for _, tt := range tests {

@ -222,6 +222,11 @@ type launchAttachArgs struct {
ShowRegisters bool `cfgName:"showRegisters"`
// GoroutineFilters are the filters used when loading goroutines.
GoroutineFilters string `cfgName:"goroutineFilters"`
// ShowPprofLabels is an array of keys of pprof labels to show as a
// goroutine name in the threads view. If the array has one element, only
// that label's value will be shown; otherwise, each of the labels will be
// shown as "key:value". To show all labels, specify the single element "*".
ShowPprofLabels []string `cfgName:"showPprofLabels"`
// HideSystemGoroutines indicates if system goroutines should be removed from threads
// responses.
HideSystemGoroutines bool `cfgName:"hideSystemGoroutines"`
@ -241,6 +246,7 @@ var defaultArgs = launchAttachArgs{
HideSystemGoroutines: false,
ShowRegisters: false,
GoroutineFilters: "",
ShowPprofLabels: []string{},
substitutePathClientToServer: [][2]string{},
substitutePathServerToClient: [][2]string{},
}
@ -280,11 +286,9 @@ const (
maxStringLenInCallRetVars = 1 << 10 // 1024
)
var (
// Max number of goroutines that we will return.
// This is a var for testing
maxGoroutines = 1 << 10
)
// Max number of goroutines that we will return.
// This is a var for testing
var maxGoroutines = 1 << 10
// NewServer creates a new DAP Server. It takes an opened Listener
// via config and assumes its ownership. config.DisconnectChan has to be set;
@ -355,6 +359,7 @@ func (s *Session) setLaunchAttachArgs(args LaunchAttachCommonConfig) {
s.args.ShowRegisters = args.ShowRegisters
s.args.HideSystemGoroutines = args.HideSystemGoroutines
s.args.GoroutineFilters = args.GoroutineFilters
s.args.ShowPprofLabels = args.ShowPprofLabels
if paths := args.SubstitutePath; len(paths) > 0 {
clientToServer := make([][2]string, 0, len(paths))
serverToClient := make([][2]string, 0, len(paths))
@ -817,7 +822,8 @@ func (s *Session) logToConsole(msg string) {
Body: dap.OutputEventBody{
Output: msg + "\n",
Category: "console",
}})
},
})
}
func (s *Session) onInitializeRequest(request *dap.InitializeRequest) {
@ -889,7 +895,7 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
return
}
var args = defaultLaunchConfig // narrow copy for initializing non-zero default values
args := defaultLaunchConfig // narrow copy for initializing non-zero default values
if err := unmarshalLaunchAttachArgs(request.Arguments, &args); err != nil {
s.sendShowUserErrorResponse(request.Request,
FailedToLaunch, "Failed to launch", fmt.Sprintf("invalid debug configuration - %v", err))
@ -1002,7 +1008,8 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
Body: dap.OutputEventBody{
Output: fmt.Sprintf("Build Error: %s\n%s (%s)\n", cmd, strings.TrimSpace(string(out)), err.Error()),
Category: "stderr",
}})
},
})
// Users are used to checking the Debug Console for build errors.
// No need to bother them with a visible pop-up.
s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
@ -1035,7 +1042,7 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
argsToLog.Cwd, _ = filepath.Abs(args.Cwd)
s.config.log.Debugf("launching binary '%s' with config: %s", debugbinary, prettyPrint(argsToLog))
var redirected = false
redirected := false
switch args.OutputMode {
case "remote":
redirected = true
@ -1062,7 +1069,8 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
Body: dap.OutputEventBody{
Output: outs,
Category: category,
}})
},
})
}
if err != nil {
if err == io.EOF {
@ -1687,7 +1695,8 @@ func (s *Session) onConfigurationDoneRequest(request *dap.ConfigurationDoneReque
func (s *Session) onContinueRequest(request *dap.ContinueRequest, allowNextStateChange *syncflag) {
s.send(&dap.ContinueResponse{
Response: *newResponse(request.Request),
Body: dap.ContinueResponseBody{AllThreadsContinued: true}})
Body: dap.ContinueResponseBody{AllThreadsContinued: true},
})
s.runUntilStopAndNotify(api.Continue, allowNextStateChange)
}
@ -1762,7 +1771,8 @@ func (s *Session) onThreadsRequest(request *dap.ThreadsRequest) {
Body: dap.OutputEventBody{
Output: fmt.Sprintf("Unable to retrieve goroutines: %s\n", err.Error()),
Category: "stderr",
}})
},
})
}
threads = []dap.Thread{{Id: 1, Name: "Dummy"}}
} else if len(gs) == 0 {
@ -1812,10 +1822,41 @@ func (s *Session) onThreadsRequest(request *dap.ThreadsRequest) {
if g.Thread != nil && g.Thread.ThreadID() != 0 {
thread = fmt.Sprintf(" (Thread %d)", g.Thread.ThreadID())
}
var labels strings.Builder
writeLabelsForKeys := func(keys []string) {
for _, k := range keys {
labelValue := g.Labels()[k]
if labelValue != "" {
labels.WriteByte(' ')
labels.WriteString(k)
labels.WriteByte(':')
labels.WriteString(labelValue)
}
}
}
if len(s.args.ShowPprofLabels) == 1 {
labelKey := s.args.ShowPprofLabels[0]
if labelKey == "*" {
keys := make([]string, 0, len(g.Labels()))
for k := range g.Labels() {
keys = append(keys, k)
}
sort.Strings(keys)
writeLabelsForKeys(keys)
} else {
labelValue := g.Labels()[labelKey]
if labelValue != "" {
labels.WriteByte(' ')
labels.WriteString(labelValue)
}
}
} else {
writeLabelsForKeys(s.args.ShowPprofLabels)
}
// File name and line number are communicated via `stackTrace`
// so no need to include them here.
loc := g.UserCurrent()
threads[i].Name = fmt.Sprintf("%s[Go %d] %s%s", selected, g.ID, fnName(&loc), thread)
threads[i].Name = fmt.Sprintf("%s[Go %d%s] %s%s", selected, g.ID, labels.String(), fnName(&loc), thread)
threads[i].Id = int(g.ID)
}
}
@ -1836,7 +1877,7 @@ func (s *Session) onThreadsRequest(request *dap.ThreadsRequest) {
// - "remote" -- attaches client to a debugger already attached to a process.
// Required args: none (host/port are used externally to connect)
func (s *Session) onAttachRequest(request *dap.AttachRequest) {
var args = defaultAttachConfig // narrow copy for initializing non-zero default values
args := defaultAttachConfig // narrow copy for initializing non-zero default values
if err := unmarshalLaunchAttachArgs(request.Arguments, &args); err != nil {
s.sendShowUserErrorResponse(request.Request, FailedToAttach, "Failed to attach", fmt.Sprintf("invalid debug configuration - %v", err))
return
@ -2597,7 +2638,7 @@ func (s *Session) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr
// Some of the types might be fully or partially not loaded based on LoadConfig.
// Those that are fully missing (e.g. due to hitting MaxVariableRecurse), can be reloaded in place.
var reloadVariable = func(v *proc.Variable, qualifiedNameOrExpr string) (value string) {
reloadVariable := func(v *proc.Variable, qualifiedNameOrExpr string) (value string) {
// We might be loading variables from the frame that's not topmost, so use
// frame-independent address-based expression, not fully-qualified name as per
// https://github.com/go-delve/delve/blob/master/Documentation/api/ClientHowto.md#looking-into-variables.
@ -3531,6 +3572,7 @@ func newEvent(event string) *dap.Event {
const BetterBadAccessError = `invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation]
Unable to propagate EXC_BAD_ACCESS signal to target process and panic (see https://github.com/go-delve/delve/issues/852)`
const BetterNextWhileNextingError = `Unable to step while the previous step is interrupted by a breakpoint.
Use 'Continue' to resume the original step command.`

@ -31,12 +31,16 @@ import (
"github.com/google/go-dap"
)
const stopOnEntry bool = true
const hasChildren bool = true
const noChildren bool = false
const (
stopOnEntry bool = true
hasChildren bool = true
noChildren bool = false
)
const localsScope = 1000
const globalsScope = 1001
const (
localsScope = 1000
globalsScope = 1001
)
var testBackend string
@ -288,7 +292,8 @@ func TestSessionStop(t *testing.T) {
}
session := NewSession(conn, &Config{
Config: &service.Config{DisconnectChan: make(chan struct{})},
StopTriggered: make(chan struct{})}, nil)
StopTriggered: make(chan struct{}),
}, nil)
serveDAPCodecDone := make(chan struct{})
go func() {
session.ServeDAPCodec()
@ -812,7 +817,8 @@ func TestPreSetBreakpoint(t *testing.T) {
// wantFrames - number of frames returned (length of StackTraceResponse.Body.StackFrames array).
// wantTotalFrames - total number of stack frames available (StackTraceResponse.Body.TotalFrames).
func checkStackFramesExact(t *testing.T, got *dap.StackTraceResponse,
wantStartName string, wantStartLine interface{}, wantStartID, wantFrames, wantTotalFrames int) {
wantStartName string, wantStartLine interface{}, wantStartID, wantFrames, wantTotalFrames int,
) {
t.Helper()
checkStackFramesNamed("", t, got, wantStartName, wantStartLine, wantStartID, wantFrames, wantTotalFrames, true)
}
@ -883,7 +889,8 @@ func TestFilterGoroutines(t *testing.T) {
client.LaunchRequestWithArgs(map[string]interface{}{
"mode": "exec",
"program": fixture.Path,
"stopOnEntry": !stopOnEntry})
"stopOnEntry": !stopOnEntry,
})
},
// Set breakpoints
fixture.Source, []int{30},
@ -920,17 +927,19 @@ func TestFilterGoroutines(t *testing.T) {
},
disconnect: false,
}})
})
}
func checkStackFramesHasMore(t *testing.T, got *dap.StackTraceResponse,
wantStartName string, wantStartLine, wantStartID, wantFrames, wantTotalFrames int) {
wantStartName string, wantStartLine, wantStartID, wantFrames, wantTotalFrames int,
) {
t.Helper()
checkStackFramesNamed("", t, got, wantStartName, wantStartLine, wantStartID, wantFrames, wantTotalFrames, false)
}
func checkStackFramesNamed(testName string, t *testing.T, got *dap.StackTraceResponse,
wantStartName string, wantStartLine interface{}, wantStartID, wantFrames, wantTotalFrames int, totalExact bool) {
wantStartName string, wantStartLine interface{}, wantStartID, wantFrames, wantTotalFrames int, totalExact bool,
) {
t.Helper()
if totalExact && got.Body.TotalFrames != wantTotalFrames {
t.Errorf("%s\ngot %#v\nwant TotalFrames=%d", testName, got.Body.TotalFrames, wantTotalFrames)
@ -1202,7 +1211,6 @@ func TestStackTraceRequest(t *testing.T) {
client.StackTraceRequest(1, 0, 0)
stResp = client.ExpectStackTraceResponse(t)
checkStackFramesExact(t, stResp, "main.main", 18, startHandle, 3, 3)
},
disconnect: false,
}})
@ -1382,7 +1390,6 @@ func TestSelectedThreadsRequest(t *testing.T) {
oe := client.ExpectOutputEvent(t)
if !strings.HasPrefix(oe.Body.Output, "Too many goroutines") {
t.Errorf("got %#v, expected Output=\"Too many goroutines...\"\n", oe)
}
tr := client.ExpectThreadsResponse(t)
@ -1403,10 +1410,71 @@ func TestSelectedThreadsRequest(t *testing.T) {
},
disconnect: true,
}})
})
}
func TestGoroutineLabels(t *testing.T) {
tests := []struct {
showPprofLabelsConfig []string
expectedPrefixWithLabel string
}{
{[]string{}, "* [Go 1]"},
{[]string{"k1"}, "* [Go 1 v1]"},
{[]string{"k2"}, "* [Go 1 v2]"},
{[]string{"k2", "k1"}, "* [Go 1 k2:v2 k1:v1]"}, // When passing keys explicitly, we show them in the given order
{[]string{"unknown"}, "* [Go 1]"},
{[]string{"unknown", "k1"}, "* [Go 1 k1:v1]"},
{[]string{"*"}, "* [Go 1 k1:v1 k2:v2]"}, // Special case for showing all labels; labels are shown sorted by key
}
for _, tc := range tests {
runTest(t, "goroutineLabels", func(client *daptest.Client, fixture protest.Fixture) {
runDebugSessionWithBPs(t, client, "launch",
// Launch
func() {
client.LaunchRequestWithArgs(map[string]interface{}{
"mode": "exec",
"program": fixture.Path,
"hideSystemGoroutines": true,
"showPprofLabels": tc.showPprofLabelsConfig,
"stopOnEntry": !stopOnEntry,
})
},
// Breakpoints are set within the program
"", []int{},
[]onBreakpoint{{
execute: func() {
client.ThreadsRequest()
tr := client.ExpectThreadsResponse(t)
if len(tr.Body.Threads) != 1 {
t.Errorf("got %d threads, expected 1\n", len(tr.Body.Threads))
}
// The first breakpoint is before the call to pprof.Do; no labels yet:
expectedPrefix := "* [Go 1]"
if !strings.HasPrefix(tr.Body.Threads[0].Name, expectedPrefix) {
t.Errorf("got %s, expected %s\n", tr.Body.Threads[0].Name, expectedPrefix)
}
client.ContinueRequest(1)
client.ExpectContinueResponse(t)
client.ExpectStoppedEvent(t)
checkStop(t, client, 1, "main.f", 21)
client.ThreadsRequest()
tr = client.ExpectThreadsResponse(t)
if len(tr.Body.Threads) != 1 {
t.Errorf("got %d threads, expected 1\n", len(tr.Body.Threads))
}
// The second breakpoint is inside pprof.Do, so there are labels:
if !strings.HasPrefix(tr.Body.Threads[0].Name, tc.expectedPrefixWithLabel) {
t.Errorf("got %s, expected %s\n", tr.Body.Threads[0].Name, tc.expectedPrefixWithLabel)
}
},
disconnect: true,
}},
)
})
}
}
func TestHideSystemGoroutinesRequest(t *testing.T) {
tests := []struct{ hideSystemGoroutines bool }{
{hideSystemGoroutines: true},
@ -2288,7 +2356,7 @@ func TestVariablesLoading(t *testing.T) {
client.StackTraceRequest(1, 0, 0)
client.ExpectStackTraceResponse(t)
var loadvars = func(frame int) {
loadvars := func(frame int) {
client.ScopesRequest(frame)
scopes := client.ExpectScopesResponse(t)
localsRef := 0
@ -3311,7 +3379,6 @@ func TestSetFunctionBreakpoints(t *testing.T) {
t.Errorf("got %#v, want Reason=\"function breakpoint\", ThreadId=1", se)
}
checkStop(t, client, 1, "main.anotherFunction", 26)
},
disconnect: true,
}})
@ -3613,7 +3680,6 @@ func TestSetBreakpointWhileRunning(t *testing.T) {
t.Errorf("\ngot %#v\nwant Reason='breakpoint' AllThreadsStopped=true ThreadId=1", se)
}
checkStop(t, client, 1, "main.sayhi", 9)
},
disconnect: true,
}})
@ -3668,7 +3734,6 @@ func TestSetFunctionBreakpointWhileRunning(t *testing.T) {
t.Errorf("\ngot %#v\nwant Reason='breakpoint' AllThreadsStopped=true ThreadId=1", se)
}
checkStop(t, client, 1, "main.main", 16)
},
disconnect: true,
}})
@ -3805,7 +3870,6 @@ func substitutePathTestHelper(t *testing.T, fixture protest.Fixture, client *dap
// Set breakpoints
filepath.Join(nonexistentDir, "loopprog.go"), []int{8},
[]onBreakpoint{{
execute: func() {
checkStop(t, client, 1, "main.loop", 8)
},
@ -4063,15 +4127,16 @@ func TestEvaluateRequest(t *testing.T) {
})
}
func formatConfig(depth int, showGlobals, showRegisters bool, goroutineFilters string, hideSystemGoroutines bool, substitutePath [][2]string) string {
func formatConfig(depth int, showGlobals, showRegisters bool, goroutineFilters string, showPprofLabels []string, hideSystemGoroutines bool, substitutePath [][2]string) string {
formatStr := `stackTraceDepth %d
showGlobalVariables %v
showRegisters %v
goroutineFilters %q
showPprofLabels %v
hideSystemGoroutines %v
substitutePath %v
`
return fmt.Sprintf(formatStr, depth, showGlobals, showRegisters, goroutineFilters, hideSystemGoroutines, substitutePath)
return fmt.Sprintf(formatStr, depth, showGlobals, showRegisters, goroutineFilters, showPprofLabels, hideSystemGoroutines, substitutePath)
}
func TestEvaluateCommandRequest(t *testing.T) {
@ -4108,7 +4173,7 @@ Type 'dlv help' followed by a command for full documentation.
client.EvaluateRequest("dlv config -list", 1000, "repl")
got = client.ExpectEvaluateResponse(t)
checkEval(t, got, formatConfig(50, false, false, "", false, [][2]string{}), noChildren)
checkEval(t, got, formatConfig(50, false, false, "", []string{}, false, [][2]string{}), noChildren)
// Read and modify showGlobalVariables.
client.EvaluateRequest("dlv config -list showGlobalVariables", 1000, "repl")
@ -4129,7 +4194,7 @@ Type 'dlv help' followed by a command for full documentation.
client.EvaluateRequest("dlv config -list", 1000, "repl")
got = client.ExpectEvaluateResponse(t)
checkEval(t, got, formatConfig(50, true, false, "", false, [][2]string{}), noChildren)
checkEval(t, got, formatConfig(50, true, false, "", []string{}, false, [][2]string{}), noChildren)
client.ScopesRequest(1000)
scopes = client.ExpectScopesResponse(t)
@ -4719,7 +4784,7 @@ func testNextParkedHelper(t *testing.T, client *daptest.Client, fixture protest.
client.SetBreakpointsRequest(fixture.Source, []int{8})
client.ExpectSetBreakpointsResponse(t)
var parkedGoid = -1
parkedGoid := -1
for parkedGoid < 0 {
client.ContinueRequest(1)
client.ExpectContinueResponse(t)
@ -4849,6 +4914,7 @@ func TestStepOutPreservesGoroutine(t *testing.T) {
}})
})
}
func checkStopOnNextWhileNextingError(t *testing.T, client *daptest.Client, threadID int) {
t.Helper()
oe := client.ExpectOutputEvent(t)
@ -5046,7 +5112,6 @@ func TestPanicBreakpointOnContinue(t *testing.T) {
} else if frame.Source != nil && frame.Source.PresentationHint != "" {
t.Errorf("\ngot Body.StackFrames[%d]=%#v\nwant Source.PresentationHint=\"\"", i, frame)
}
}
},
disconnect: true,
@ -5131,7 +5196,6 @@ func TestFatalThrowBreakpoint(t *testing.T) {
if eInfo.Body.ExceptionId != "fatal error" || !strings.HasPrefix(eInfo.Body.Description, errorPrefix) {
t.Errorf("\ngot %#v\nwant ExceptionId=\"runtime error\" Text=%s", eInfo, errorPrefix)
}
},
disconnect: true,
}})
@ -5307,7 +5371,8 @@ func TestLaunchDebugRequest(t *testing.T) {
// only relying on the source to be built in response to LaunchRequest.
runDebugSession(t, client, "launch", func() {
client.LaunchRequestWithArgs(map[string]interface{}{
"mode": "debug", "program": fixture.Source, "output": tmpBin})
"mode": "debug", "program": fixture.Source, "output": tmpBin,
})
})
})
// Wait for the test to finish to capture all stderr
@ -5343,20 +5408,23 @@ func TestLaunchRequestDefaults(t *testing.T) {
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
runDebugSession(t, client, "launch", func() {
client.LaunchRequestWithArgs(map[string]interface{}{
"mode": "" /*"debug" by default*/, "program": fixture.Source, "output": "__mybin"})
"mode": "" /*"debug" by default*/, "program": fixture.Source, "output": "__mybin",
})
})
})
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
runDebugSession(t, client, "launch", func() {
client.LaunchRequestWithArgs(map[string]interface{}{
/*"mode":"debug" by default*/ "program": fixture.Source, "output": "__mybin"})
/*"mode":"debug" by default*/ "program": fixture.Source, "output": "__mybin",
})
})
})
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
runDebugSession(t, client, "launch", func() {
// Use the temporary output binary.
client.LaunchRequestWithArgs(map[string]interface{}{
"mode": "debug", "program": fixture.Source})
"mode": "debug", "program": fixture.Source,
})
})
})
}
@ -5373,7 +5441,8 @@ func TestLaunchRequestOutputPath(t *testing.T) {
func() {
client.LaunchRequestWithArgs(map[string]interface{}{
"mode": "debug", "program": fixture.Source, "output": inrel,
"cwd": filepath.Dir(wd)})
"cwd": filepath.Dir(wd),
})
},
// Set breakpoints
fixture.Source, []int{12},
@ -5421,7 +5490,8 @@ func TestNoDebug_GoodExitStatus(t *testing.T) {
runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) {
runNoDebugSession(t, client, func() {
client.LaunchRequestWithArgs(map[string]interface{}{
"noDebug": true, "mode": "debug", "program": fixture.Source, "output": "__mybin"})
"noDebug": true, "mode": "debug", "program": fixture.Source, "output": "__mybin",
})
}, 0)
})
}
@ -5430,7 +5500,8 @@ func TestNoDebug_BadExitStatus(t *testing.T) {
runTest(t, "issue1101", func(client *daptest.Client, fixture protest.Fixture) {
runNoDebugSession(t, client, func() {
client.LaunchRequestWithArgs(map[string]interface{}{
"noDebug": true, "mode": "exec", "program": fixture.Path})
"noDebug": true, "mode": "exec", "program": fixture.Path,
})
}, 2)
})
}
@ -5458,11 +5529,12 @@ func TestNoDebug_AcceptNoRequestsButDisconnect(t *testing.T) {
client.InitializeRequest()
client.ExpectInitializeResponseAndCapabilities(t)
client.LaunchRequestWithArgs(map[string]interface{}{
"noDebug": true, "mode": "exec", "program": fixture.Path})
"noDebug": true, "mode": "exec", "program": fixture.Path,
})
client.ExpectLaunchResponse(t)
// Anything other than disconnect should get rejected
var ExpectNoDebugError = func(cmd string) {
ExpectNoDebugError := func(cmd string) {
er := client.ExpectErrorResponse(t)
if !checkErrorMessageFormat(er.Body.Error, fmt.Sprintf("noDebug mode: unable to process '%s' request", cmd)) {
t.Errorf("\ngot %#v\nwant 'noDebug mode: unable to process '%s' request'", er, cmd)
@ -5531,7 +5603,8 @@ func TestLaunchRequestWithRelativeBuildPath(t *testing.T) {
dlvwd, _ := os.Getwd()
runDebugSession(t, client, "launch", func() {
client.LaunchRequestWithArgs(map[string]interface{}{
"mode": "debug", "program": program, "cwd": filepath.Dir(dlvwd)})
"mode": "debug", "program": program, "cwd": filepath.Dir(dlvwd),
})
})
<-serverStopped
}
@ -5550,7 +5623,8 @@ func TestLaunchRequestWithRelativeExecPath(t *testing.T) {
defer os.Remove(symlink)
runDebugSession(t, client, "launch", func() {
client.LaunchRequestWithArgs(map[string]interface{}{
"mode": "exec", "program": symlink})
"mode": "exec", "program": symlink,
})
})
})
}
@ -5640,7 +5714,8 @@ func TestLaunchTestRequest(t *testing.T) {
checkVarExact(t, locals, i, "wd", "wd", fmt.Sprintf("%q", tc.wantWD), "string", noChildren)
}
}
}}})
},
}})
<-serverStopped
})
@ -5656,7 +5731,8 @@ func TestLaunchRequestWithArgs(t *testing.T) {
runDebugSession(t, client, "launch", func() {
client.LaunchRequestWithArgs(map[string]interface{}{
"mode": "exec", "program": fixture.Path,
"args": []string{"test", "pass flag"}})
"args": []string{"test", "pass flag"},
})
})
})
}
@ -5672,7 +5748,8 @@ func TestLaunchRequestWithBuildFlags(t *testing.T) {
// 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": "-ldflags '-X main.Hello=World'"})
"buildFlags": "-ldflags '-X main.Hello=World'",
})
})
})
}
@ -5684,7 +5761,8 @@ func TestLaunchRequestWithBuildFlags2(t *testing.T) {
// 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"}})
"buildFlags": []string{"-ldflags", "-X main.Hello=World"},
})
})
})
}
@ -5747,7 +5825,6 @@ func TestLaunchRequestWithEnv(t *testing.T) {
wantY: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
for k, v := range tc.initEnv {
if v != nil {
@ -5794,7 +5871,8 @@ func TestAttachRequest(t *testing.T) {
// Attach
func() {
client.AttachRequest(map[string]interface{}{
/*"mode": "local" by default*/ "processId": cmd.Process.Pid, "stopOnEntry": false})
/*"mode": "local" by default*/ "processId": cmd.Process.Pid, "stopOnEntry": false,
})
client.ExpectCapabilitiesEventSupportTerminateDebuggee(t)
},
// Set breakpoints
@ -7126,7 +7204,7 @@ func TestDisassemble(t *testing.T) {
}
// Request invalid instructions.
var checkInvalidInstruction = func(instructions []dap.DisassembledInstruction, count int, address uint64) {
checkInvalidInstruction := func(instructions []dap.DisassembledInstruction, count int, address uint64) {
if len(instructions) != count {
t.Errorf("\ngot %#v\nwant len(instructions) = %d", dr, count)
}

@ -190,6 +190,12 @@ type LaunchAttachCommonConfig struct {
// https://github.com/go-delve/delve/blob/master/Documentation/cli/README.md#goroutines
GoroutineFilters string `json:"goroutineFilters,omitempty"`
// Array of string values indicating the keys of pprof labels to show as a
// goroutine name in the threads view. If the array has one element, only
// that label's value will be shown; otherwise, each of the labels will be
// shown as "key:value". To show all labels, specify the single element "*".
ShowPprofLabels []string `json:"showPprofLabels,omitempty"`
// An array of mappings from a local path (client) to the remote path (debugger).
// This setting is useful when working in a file system with symbolic links,
// running remote debugging, or debugging an executable compiled externally.