mirror of
https://github.com/go-delve/delve.git
synced 2025-11-03 05:47:34 +08:00
service/dap: support goroutine filters in dap (#2759)
* service/dap: filter goroutines * adjust defaults * add tests * remove label change * fix typos * send invalidated areas * respond to review, and allow to clear goroutineFilters
This commit is contained in:
@ -772,27 +772,17 @@ func (a byGoroutineID) Len() int { return len(a) }
|
||||
func (a byGoroutineID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byGoroutineID) Less(i, j int) bool { return a[i].ID < a[j].ID }
|
||||
|
||||
// The number of goroutines we're going to request on each RPC call
|
||||
const goroutineBatchSize = 10000
|
||||
|
||||
type printGoroutinesFlags uint8
|
||||
|
||||
const (
|
||||
printGoroutinesStack printGoroutinesFlags = 1 << iota
|
||||
printGoroutinesLabels
|
||||
)
|
||||
|
||||
func printGoroutines(t *Term, indent string, gs []*api.Goroutine, fgl formatGoroutineLoc, flags printGoroutinesFlags, depth int, state *api.DebuggerState) error {
|
||||
func printGoroutines(t *Term, indent string, gs []*api.Goroutine, fgl api.FormatGoroutineLoc, flags api.PrintGoroutinesFlags, depth int, state *api.DebuggerState) error {
|
||||
for _, g := range gs {
|
||||
prefix := indent + " "
|
||||
if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
|
||||
prefix = indent + "* "
|
||||
}
|
||||
fmt.Printf("%sGoroutine %s\n", prefix, t.formatGoroutine(g, fgl))
|
||||
if flags&printGoroutinesLabels != 0 {
|
||||
if flags&api.PrintGoroutinesLabels != 0 {
|
||||
writeGoroutineLabels(os.Stdout, g, indent+"\t")
|
||||
}
|
||||
if flags&printGoroutinesStack != 0 {
|
||||
if flags&api.PrintGoroutinesStack != 0 {
|
||||
stack, err := t.client.Stacktrace(g.ID, depth, 0, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -803,83 +793,10 @@ func printGoroutines(t *Term, indent string, gs []*api.Goroutine, fgl formatGoro
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
maxGroupMembers = 5
|
||||
maxGoroutineGroups = 50
|
||||
)
|
||||
|
||||
func goroutines(t *Term, ctx callContext, argstr string) error {
|
||||
args := strings.Split(argstr, " ")
|
||||
var filters []api.ListGoroutinesFilter
|
||||
var group api.GoroutineGroupingOptions
|
||||
var fgl = fglUserCurrent
|
||||
var flags printGoroutinesFlags
|
||||
var depth = 10
|
||||
var batchSize = goroutineBatchSize
|
||||
|
||||
group.MaxGroupMembers = maxGroupMembers
|
||||
group.MaxGroups = maxGoroutineGroups
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
switch arg {
|
||||
case "-u":
|
||||
fgl = fglUserCurrent
|
||||
case "-r":
|
||||
fgl = fglRuntimeCurrent
|
||||
case "-g":
|
||||
fgl = fglGo
|
||||
case "-s":
|
||||
fgl = fglStart
|
||||
case "-l":
|
||||
flags |= printGoroutinesLabels
|
||||
case "-t":
|
||||
flags |= printGoroutinesStack
|
||||
// optional depth argument
|
||||
if i+1 < len(args) && len(args[i+1]) > 0 {
|
||||
n, err := strconv.Atoi(args[i+1])
|
||||
if err == nil {
|
||||
depth = n
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
case "-w", "-with":
|
||||
filter, err := readGoroutinesFilter(args, &i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filters = append(filters, *filter)
|
||||
|
||||
case "-wo", "-without":
|
||||
filter, err := readGoroutinesFilter(args, &i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter.Negated = true
|
||||
filters = append(filters, *filter)
|
||||
|
||||
case "-group":
|
||||
var err error
|
||||
group.GroupBy, err = readGoroutinesFilterKind(args, i+1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i++
|
||||
if group.GroupBy == api.GoroutineLabel {
|
||||
if i+1 >= len(args) {
|
||||
return errors.New("-group label must be followed by an argument")
|
||||
}
|
||||
group.GroupByKey = args[i+1]
|
||||
i++
|
||||
}
|
||||
batchSize = 0 // grouping only works well if run on all goroutines
|
||||
|
||||
case "":
|
||||
// nothing to do
|
||||
default:
|
||||
return fmt.Errorf("wrong argument: '%s'", arg)
|
||||
}
|
||||
filters, group, fgl, flags, depth, batchSize, err := api.ParseGoroutineArgs(argstr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state, err := t.client.GetState()
|
||||
@ -933,52 +850,6 @@ func goroutines(t *Term, ctx callContext, argstr string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func readGoroutinesFilterKind(args []string, i int) (api.GoroutineField, error) {
|
||||
if i >= len(args) {
|
||||
return api.GoroutineFieldNone, fmt.Errorf("%s must be followed by an argument", args[i-1])
|
||||
}
|
||||
|
||||
switch args[i] {
|
||||
case "curloc":
|
||||
return api.GoroutineCurrentLoc, nil
|
||||
case "userloc":
|
||||
return api.GoroutineUserLoc, nil
|
||||
case "goloc":
|
||||
return api.GoroutineGoLoc, nil
|
||||
case "startloc":
|
||||
return api.GoroutineStartLoc, nil
|
||||
case "label":
|
||||
return api.GoroutineLabel, nil
|
||||
case "running":
|
||||
return api.GoroutineRunning, nil
|
||||
case "user":
|
||||
return api.GoroutineUser, nil
|
||||
default:
|
||||
return api.GoroutineFieldNone, fmt.Errorf("unrecognized argument to %s %s", args[i-1], args[i])
|
||||
}
|
||||
}
|
||||
|
||||
func readGoroutinesFilter(args []string, pi *int) (*api.ListGoroutinesFilter, error) {
|
||||
r := new(api.ListGoroutinesFilter)
|
||||
var err error
|
||||
r.Kind, err = readGoroutinesFilterKind(args, *pi+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*pi++
|
||||
switch r.Kind {
|
||||
case api.GoroutineRunning, api.GoroutineUser:
|
||||
return r, nil
|
||||
}
|
||||
if *pi+1 >= len(args) {
|
||||
return nil, fmt.Errorf("%s %s needs to be followed by an expression", args[*pi-1], args[*pi])
|
||||
}
|
||||
r.Arg = args[*pi+1]
|
||||
*pi++
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func selectedGID(state *api.DebuggerState) int {
|
||||
if state.SelectedGoroutine == nil {
|
||||
return 0
|
||||
@ -1113,20 +984,11 @@ func (t *Term) formatThread(th *api.Thread) string {
|
||||
return fmt.Sprintf("%d at %s:%d", th.ID, t.formatPath(th.File), th.Line)
|
||||
}
|
||||
|
||||
type formatGoroutineLoc int
|
||||
|
||||
const (
|
||||
fglRuntimeCurrent = formatGoroutineLoc(iota)
|
||||
fglUserCurrent
|
||||
fglGo
|
||||
fglStart
|
||||
)
|
||||
|
||||
func (t *Term) formatLocation(loc api.Location) string {
|
||||
return fmt.Sprintf("%s:%d %s (%#v)", t.formatPath(loc.File), loc.Line, loc.Function.Name(), loc.PC)
|
||||
}
|
||||
|
||||
func (t *Term) formatGoroutine(g *api.Goroutine, fgl formatGoroutineLoc) string {
|
||||
func (t *Term) formatGoroutine(g *api.Goroutine, fgl api.FormatGoroutineLoc) string {
|
||||
if g == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
@ -1136,16 +998,16 @@ func (t *Term) formatGoroutine(g *api.Goroutine, fgl formatGoroutineLoc) string
|
||||
var locname string
|
||||
var loc api.Location
|
||||
switch fgl {
|
||||
case fglRuntimeCurrent:
|
||||
case api.FglRuntimeCurrent:
|
||||
locname = "Runtime"
|
||||
loc = g.CurrentLoc
|
||||
case fglUserCurrent:
|
||||
case api.FglUserCurrent:
|
||||
locname = "User"
|
||||
loc = g.UserCurrentLoc
|
||||
case fglGo:
|
||||
case api.FglGo:
|
||||
locname = "Go"
|
||||
loc = g.GoStatementLoc
|
||||
case fglStart:
|
||||
case api.FglStart:
|
||||
locname = "Start"
|
||||
loc = g.StartLoc
|
||||
}
|
||||
|
||||
153
service/api/command.go
Normal file
153
service/api/command.go
Normal file
@ -0,0 +1,153 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PrintGoroutinesFlags uint8
|
||||
|
||||
const (
|
||||
PrintGoroutinesStack PrintGoroutinesFlags = 1 << iota
|
||||
PrintGoroutinesLabels
|
||||
)
|
||||
|
||||
type FormatGoroutineLoc int
|
||||
|
||||
const (
|
||||
FglRuntimeCurrent = FormatGoroutineLoc(iota)
|
||||
FglUserCurrent
|
||||
FglGo
|
||||
FglStart
|
||||
)
|
||||
|
||||
const (
|
||||
maxGroupMembers = 5
|
||||
maxGoroutineGroups = 50
|
||||
)
|
||||
|
||||
// The number of goroutines we're going to request on each RPC call
|
||||
const goroutineBatchSize = 10000
|
||||
|
||||
func ParseGoroutineArgs(argstr string) ([]ListGoroutinesFilter, GoroutineGroupingOptions, FormatGoroutineLoc, PrintGoroutinesFlags, int, int, error) {
|
||||
args := strings.Split(argstr, " ")
|
||||
var filters []ListGoroutinesFilter
|
||||
var group GoroutineGroupingOptions
|
||||
var fgl = FglUserCurrent
|
||||
var flags PrintGoroutinesFlags
|
||||
var depth = 10
|
||||
var batchSize = goroutineBatchSize
|
||||
|
||||
group.MaxGroupMembers = maxGroupMembers
|
||||
group.MaxGroups = maxGoroutineGroups
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
switch arg {
|
||||
case "-u":
|
||||
fgl = FglUserCurrent
|
||||
case "-r":
|
||||
fgl = FglRuntimeCurrent
|
||||
case "-g":
|
||||
fgl = FglGo
|
||||
case "-s":
|
||||
fgl = FglStart
|
||||
case "-l":
|
||||
flags |= PrintGoroutinesLabels
|
||||
case "-t":
|
||||
flags |= PrintGoroutinesStack
|
||||
// optional depth argument
|
||||
if i+1 < len(args) && len(args[i+1]) > 0 {
|
||||
n, err := strconv.Atoi(args[i+1])
|
||||
if err == nil {
|
||||
depth = n
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
case "-w", "-with":
|
||||
filter, err := readGoroutinesFilter(args, &i)
|
||||
if err != nil {
|
||||
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, fmt.Errorf("wrong argument: '%s'", arg)
|
||||
}
|
||||
filters = append(filters, *filter)
|
||||
|
||||
case "-wo", "-without":
|
||||
filter, err := readGoroutinesFilter(args, &i)
|
||||
if err != nil {
|
||||
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, fmt.Errorf("wrong argument: '%s'", arg)
|
||||
}
|
||||
filter.Negated = true
|
||||
filters = append(filters, *filter)
|
||||
|
||||
case "-group":
|
||||
var err error
|
||||
group.GroupBy, err = readGoroutinesFilterKind(args, i+1)
|
||||
if err != nil {
|
||||
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, fmt.Errorf("wrong argument: '%s'", arg)
|
||||
}
|
||||
i++
|
||||
if group.GroupBy == GoroutineLabel {
|
||||
if i+1 >= len(args) {
|
||||
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, fmt.Errorf("wrong argument: '%s'", arg)
|
||||
}
|
||||
group.GroupByKey = args[i+1]
|
||||
i++
|
||||
}
|
||||
batchSize = 0 // grouping only works well if run on all goroutines
|
||||
|
||||
case "":
|
||||
// nothing to do
|
||||
default:
|
||||
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, fmt.Errorf("wrong argument: '%s'", arg)
|
||||
}
|
||||
}
|
||||
return filters, group, fgl, flags, depth, batchSize, nil
|
||||
}
|
||||
|
||||
func readGoroutinesFilterKind(args []string, i int) (GoroutineField, error) {
|
||||
if i >= len(args) {
|
||||
return GoroutineFieldNone, fmt.Errorf("%s must be followed by an argument", args[i-1])
|
||||
}
|
||||
|
||||
switch args[i] {
|
||||
case "curloc":
|
||||
return GoroutineCurrentLoc, nil
|
||||
case "userloc":
|
||||
return GoroutineUserLoc, nil
|
||||
case "goloc":
|
||||
return GoroutineGoLoc, nil
|
||||
case "startloc":
|
||||
return GoroutineStartLoc, nil
|
||||
case "label":
|
||||
return GoroutineLabel, nil
|
||||
case "running":
|
||||
return GoroutineRunning, nil
|
||||
case "user":
|
||||
return GoroutineUser, nil
|
||||
default:
|
||||
return GoroutineFieldNone, fmt.Errorf("unrecognized argument to %s %s", args[i-1], args[i])
|
||||
}
|
||||
}
|
||||
|
||||
func readGoroutinesFilter(args []string, pi *int) (*ListGoroutinesFilter, error) {
|
||||
r := new(ListGoroutinesFilter)
|
||||
var err error
|
||||
r.Kind, err = readGoroutinesFilterKind(args, *pi+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*pi++
|
||||
switch r.Kind {
|
||||
case GoroutineRunning, GoroutineUser:
|
||||
return r, nil
|
||||
}
|
||||
if *pi+1 >= len(args) {
|
||||
return nil, fmt.Errorf("%s %s needs to be followed by an expression", args[*pi-1], args[*pi])
|
||||
}
|
||||
r.Arg = args[*pi+1]
|
||||
*pi++
|
||||
|
||||
return r, nil
|
||||
}
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-delve/delve/pkg/config"
|
||||
"github.com/google/go-dap"
|
||||
)
|
||||
|
||||
func (s *Session) delveCmd(goid, frame int, cmdstr string) (string, error) {
|
||||
@ -48,6 +49,10 @@ Type "help" followed by the name of a command for more information about it.`
|
||||
|
||||
Show all configuration parameters.
|
||||
|
||||
config -list <parameter>
|
||||
|
||||
Show value of a configuration parameter.
|
||||
|
||||
dlv config <parameter> <value>
|
||||
|
||||
Changes the value of a configuration parameter.
|
||||
@ -109,16 +114,40 @@ func (s *Session) helpMessage(_, _ int, args string) (string, error) {
|
||||
func (s *Session) evaluateConfig(_, _ int, expr string) (string, error) {
|
||||
argv := config.Split2PartsBySpace(expr)
|
||||
name := argv[0]
|
||||
switch name {
|
||||
case "-list":
|
||||
return listConfig(&s.args), nil
|
||||
default:
|
||||
res, err := configureSet(&s.args, expr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if name == "-list" {
|
||||
if len(argv) > 1 {
|
||||
return config.ConfigureListByName(&s.args, argv[1], "cfgName"), nil
|
||||
}
|
||||
return res, nil
|
||||
return listConfig(&s.args), nil
|
||||
}
|
||||
updated, res, err := configureSet(&s.args, expr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if updated {
|
||||
// Send invalidated events for areas that are affected by configuration changes.
|
||||
switch name {
|
||||
case "showGlobalVariables", "showRegisters":
|
||||
// Variable data has become invalidated.
|
||||
s.send(&dap.InvalidatedEvent{
|
||||
Event: *newEvent("invalidated"),
|
||||
Body: dap.InvalidatedEventBody{
|
||||
Areas: []dap.InvalidatedAreas{"variables"},
|
||||
},
|
||||
})
|
||||
case "goroutineFilters", "hideSystemGoroutines":
|
||||
// Thread related data has become invalidated.
|
||||
s.send(&dap.InvalidatedEvent{
|
||||
Event: *newEvent("invalidated"),
|
||||
Body: dap.InvalidatedEventBody{
|
||||
Areas: []dap.InvalidatedAreas{"threads"},
|
||||
},
|
||||
})
|
||||
}
|
||||
res += "\nUpdated"
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *Session) sources(_, _ int, filter string) (string, error) {
|
||||
|
||||
@ -13,7 +13,7 @@ func listConfig(args *launchAttachArgs) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func configureSet(sargs *launchAttachArgs, args string) (string, error) {
|
||||
func configureSet(sargs *launchAttachArgs, args string) (bool, string, error) {
|
||||
v := config.Split2PartsBySpace(args)
|
||||
|
||||
cfgname := v[0]
|
||||
@ -24,28 +24,23 @@ func configureSet(sargs *launchAttachArgs, args string) (string, error) {
|
||||
|
||||
field := config.ConfigureFindFieldByName(sargs, cfgname, "cfgName")
|
||||
if !field.CanAddr() {
|
||||
return "", fmt.Errorf("%q is not a configuration parameter", cfgname)
|
||||
}
|
||||
|
||||
// If there were no arguments provided, just list the value.
|
||||
if len(v) == 1 {
|
||||
return config.ConfigureListByName(sargs, cfgname, "cfgName"), nil
|
||||
return false, "", fmt.Errorf("%q is not a configuration parameter", cfgname)
|
||||
}
|
||||
|
||||
if cfgname == "substitutePath" {
|
||||
err := configureSetSubstitutePath(sargs, rest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, "", err
|
||||
}
|
||||
// Print the updated client to server and server to client maps.
|
||||
return fmt.Sprintf("%s\nUpdated", config.ConfigureListByName(sargs, cfgname, "cfgName")), nil
|
||||
return true, config.ConfigureListByName(sargs, cfgname, "cfgName"), nil
|
||||
}
|
||||
|
||||
err := config.ConfigureSetSimple(rest, cfgname, field)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return false, "", err
|
||||
}
|
||||
return fmt.Sprintf("%s\nUpdated", config.ConfigureListByName(sargs, cfgname, "cfgName")), nil
|
||||
return true, config.ConfigureListByName(sargs, cfgname, "cfgName"), nil
|
||||
}
|
||||
|
||||
func configureSetSubstitutePath(args *launchAttachArgs, rest string) error {
|
||||
|
||||
@ -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, "", false, [][2]string{}),
|
||||
},
|
||||
{
|
||||
name: "default values",
|
||||
args: args{
|
||||
args: &defaultArgs,
|
||||
},
|
||||
want: formatConfig(50, false, false, false, [][2]string{}),
|
||||
want: formatConfig(50, false, false, "", false, [][2]string{}),
|
||||
},
|
||||
{
|
||||
name: "custom values",
|
||||
@ -37,7 +37,7 @@ func TestListConfig(t *testing.T) {
|
||||
substitutePathServerToClient: [][2]string{{"world", "hello"}},
|
||||
},
|
||||
},
|
||||
want: formatConfig(35, true, false, false, [][2]string{{"hello", "world"}}),
|
||||
want: formatConfig(35, true, false, "", false, [][2]string{{"hello", "world"}}),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
||||
@ -213,6 +213,8 @@ type launchAttachArgs struct {
|
||||
ShowGlobalVariables bool `cfgName:"showGlobalVariables"`
|
||||
// ShowRegisters indicates if register values should be loaded.
|
||||
ShowRegisters bool `cfgName:"showRegisters"`
|
||||
// GoroutineFilters are the filters used when loading goroutines.
|
||||
GoroutineFilters string `cfgName:"goroutineFilters"`
|
||||
// HideSystemGoroutines indicates if system goroutines should be removed from threads
|
||||
// responses.
|
||||
HideSystemGoroutines bool `cfgName:"hideSystemGoroutines"`
|
||||
@ -233,6 +235,7 @@ var defaultArgs = launchAttachArgs{
|
||||
ShowGlobalVariables: false,
|
||||
HideSystemGoroutines: false,
|
||||
ShowRegisters: false,
|
||||
GoroutineFilters: "",
|
||||
substitutePathClientToServer: [][2]string{},
|
||||
substitutePathServerToClient: [][2]string{},
|
||||
}
|
||||
@ -345,6 +348,7 @@ func (s *Session) setLaunchAttachArgs(args LaunchAttachCommonConfig) error {
|
||||
s.args.ShowGlobalVariables = args.ShowGlobalVariables
|
||||
s.args.ShowRegisters = args.ShowRegisters
|
||||
s.args.HideSystemGoroutines = args.HideSystemGoroutines
|
||||
s.args.GoroutineFilters = args.GoroutineFilters
|
||||
if paths := args.SubstitutePath; len(paths) > 0 {
|
||||
clientToServer := make([][2]string, 0, len(paths))
|
||||
serverToClient := make([][2]string, 0, len(paths))
|
||||
@ -1659,11 +1663,19 @@ func (s *Session) onThreadsRequest(request *dap.ThreadsRequest) {
|
||||
var next int
|
||||
if s.debugger != nil {
|
||||
gs, next, err = s.debugger.Goroutines(0, maxGoroutines)
|
||||
if err == nil && s.args.HideSystemGoroutines {
|
||||
gs = s.debugger.FilterGoroutines(gs, []api.ListGoroutinesFilter{{
|
||||
Kind: api.GoroutineUser,
|
||||
Negated: false,
|
||||
}})
|
||||
if err == nil {
|
||||
// Parse the goroutine arguments.
|
||||
filters, _, _, _, _, _, parseErr := api.ParseGoroutineArgs(s.args.GoroutineFilters)
|
||||
if parseErr != nil {
|
||||
s.logToConsole(parseErr.Error())
|
||||
}
|
||||
if s.args.HideSystemGoroutines {
|
||||
filters = append(filters, api.ListGoroutinesFilter{
|
||||
Kind: api.GoroutineUser,
|
||||
Negated: false,
|
||||
})
|
||||
}
|
||||
gs = s.debugger.FilterGoroutines(gs, filters)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -805,6 +805,113 @@ func checkStackFramesExact(t *testing.T, got *dap.StackTraceResponse,
|
||||
checkStackFramesNamed("", t, got, wantStartName, wantStartLine, wantStartID, wantFrames, wantTotalFrames, true)
|
||||
}
|
||||
|
||||
func TestFilterGoroutines(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
filter string
|
||||
want []string
|
||||
wantLen int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "user goroutines",
|
||||
filter: "-with user",
|
||||
want: []string{"main.main", "main.agoroutine"},
|
||||
wantLen: 11,
|
||||
},
|
||||
{
|
||||
name: "filter by user loc",
|
||||
filter: "-with userloc main.main",
|
||||
want: []string{"main.main"},
|
||||
wantLen: 1,
|
||||
},
|
||||
{
|
||||
name: "multiple filters",
|
||||
filter: "-with user -with userloc main.agoroutine",
|
||||
want: []string{"main.agoroutine"},
|
||||
wantLen: 10,
|
||||
},
|
||||
{
|
||||
name: "system goroutines",
|
||||
filter: "-without user",
|
||||
want: []string{"runtime."},
|
||||
},
|
||||
// Filters that should return all goroutines.
|
||||
{
|
||||
name: "empty filter string",
|
||||
filter: "",
|
||||
want: []string{"main.main", "main.agoroutine", "runtime."},
|
||||
wantLen: -1,
|
||||
},
|
||||
{
|
||||
name: "bad filter string",
|
||||
filter: "not parsable to filters",
|
||||
want: []string{"main.main", "main.agoroutine", "runtime."},
|
||||
wantLen: -1,
|
||||
wantErr: true,
|
||||
},
|
||||
// Filters that should produce none.
|
||||
{
|
||||
name: "no match to user loc",
|
||||
filter: "-with userloc main.NotAUserFrame",
|
||||
want: []string{"Dummy"},
|
||||
wantLen: 1,
|
||||
},
|
||||
{
|
||||
name: "no match to user and not user",
|
||||
filter: "-with user -without user",
|
||||
want: []string{"Dummy"},
|
||||
wantLen: 1,
|
||||
},
|
||||
}
|
||||
runTest(t, "goroutinestackprog", func(client *daptest.Client, fixture protest.Fixture) {
|
||||
runDebugSessionWithBPs(t, client, "launch",
|
||||
// Launch
|
||||
func() {
|
||||
client.LaunchRequestWithArgs(map[string]interface{}{
|
||||
"mode": "exec",
|
||||
"program": fixture.Path,
|
||||
"stopOnEntry": !stopOnEntry})
|
||||
},
|
||||
// Set breakpoints
|
||||
fixture.Source, []int{30},
|
||||
[]onBreakpoint{{
|
||||
// Stop at line 30
|
||||
execute: func() {
|
||||
for _, tc := range tt {
|
||||
command := fmt.Sprintf("dlv config goroutineFilters %s", tc.filter)
|
||||
client.EvaluateRequest(command, 1000, "repl")
|
||||
client.ExpectInvalidatedEvent(t)
|
||||
client.ExpectEvaluateResponse(t)
|
||||
|
||||
client.ThreadsRequest()
|
||||
if tc.wantErr {
|
||||
client.ExpectOutputEvent(t)
|
||||
}
|
||||
tr := client.ExpectThreadsResponse(t)
|
||||
if tc.wantLen > 0 && len(tr.Body.Threads) != tc.wantLen {
|
||||
t.Errorf("got Threads=%#v, want Len=%d\n", tr.Body.Threads, tc.wantLen)
|
||||
}
|
||||
for i, frame := range tr.Body.Threads {
|
||||
var found bool
|
||||
for _, wantName := range tc.want {
|
||||
if strings.Contains(frame.Name, wantName) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("got Threads[%d]=%#v, want Name=%v\n", i, frame, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
disconnect: false,
|
||||
}})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func checkStackFramesHasMore(t *testing.T, got *dap.StackTraceResponse,
|
||||
wantStartName string, wantStartLine, wantStartID, wantFrames, wantTotalFrames int) {
|
||||
t.Helper()
|
||||
@ -3822,14 +3929,15 @@ func TestEvaluateRequest(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func formatConfig(depth int, showGlobals, showRegisters, hideSystemGoroutines bool, substitutePath [][2]string) string {
|
||||
func formatConfig(depth int, showGlobals, showRegisters bool, goroutineFilters string, hideSystemGoroutines bool, substitutePath [][2]string) string {
|
||||
formatStr := `stackTraceDepth %d
|
||||
showGlobalVariables %v
|
||||
showRegisters %v
|
||||
goroutineFilters %q
|
||||
hideSystemGoroutines %v
|
||||
substitutePath %v
|
||||
`
|
||||
return fmt.Sprintf(formatStr, depth, showGlobals, showRegisters, hideSystemGoroutines, substitutePath)
|
||||
return fmt.Sprintf(formatStr, depth, showGlobals, showRegisters, goroutineFilters, hideSystemGoroutines, substitutePath)
|
||||
}
|
||||
|
||||
func TestEvaluateCommandRequest(t *testing.T) {
|
||||
@ -3863,10 +3971,10 @@ Type 'dlv help' followed by a command for full documentation.
|
||||
// Test config.
|
||||
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, "", false, [][2]string{}), noChildren)
|
||||
|
||||
// Read and modify showGlobalVariables.
|
||||
client.EvaluateRequest("dlv config showGlobalVariables", 1000, "repl")
|
||||
client.EvaluateRequest("dlv config -list showGlobalVariables", 1000, "repl")
|
||||
got = client.ExpectEvaluateResponse(t)
|
||||
checkEval(t, got, "showGlobalVariables\tfalse\n", noChildren)
|
||||
|
||||
@ -3878,12 +3986,13 @@ Type 'dlv help' followed by a command for full documentation.
|
||||
checkScope(t, scopes, 0, "Locals", -1)
|
||||
|
||||
client.EvaluateRequest("dlv config showGlobalVariables true", 1000, "repl")
|
||||
client.ExpectInvalidatedEvent(t)
|
||||
got = client.ExpectEvaluateResponse(t)
|
||||
checkEval(t, got, "showGlobalVariables\ttrue\n\nUpdated", noChildren)
|
||||
|
||||
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, "", false, [][2]string{}), noChildren)
|
||||
|
||||
client.ScopesRequest(1000)
|
||||
scopes = client.ExpectScopesResponse(t)
|
||||
@ -3894,7 +4003,7 @@ Type 'dlv help' followed by a command for full documentation.
|
||||
checkScope(t, scopes, 1, "Globals (package main)", -1)
|
||||
|
||||
// Read and modify substitutePath.
|
||||
client.EvaluateRequest("dlv config substitutePath", 1000, "repl")
|
||||
client.EvaluateRequest("dlv config -list substitutePath", 1000, "repl")
|
||||
got = client.ExpectEvaluateResponse(t)
|
||||
checkEval(t, got, "substitutePath\t[]\n", noChildren)
|
||||
|
||||
|
||||
@ -154,9 +154,14 @@ type LaunchAttachCommonConfig struct {
|
||||
ShowRegisters bool `json:"showRegisters,omitempty"`
|
||||
|
||||
// Boolean value to indicate whether system goroutines
|
||||
// should be should be hidden from the call stack view.
|
||||
// should be hidden from the call stack view.
|
||||
HideSystemGoroutines bool `json:"hideSystemGoroutines,omitempty"`
|
||||
|
||||
// String value to indicate which system goroutines should be
|
||||
// shown in the call stack view. See filtering documentation:
|
||||
// https://github.com/go-delve/delve/blob/master/Documentation/cli/README.md#goroutines
|
||||
GoroutineFilters string `json:"goroutineFilters,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.
|
||||
|
||||
Reference in New Issue
Block a user