whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
package dap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-delve/delve/pkg/config"
|
||||
"github.com/google/go-dap"
|
||||
)
|
||||
|
||||
func (s *Session) delveCmd(goid, frame int, cmdstr string) (string, error) {
|
||||
vals := strings.SplitN(strings.TrimSpace(cmdstr), " ", 2)
|
||||
cmdname := vals[0]
|
||||
var args string
|
||||
if len(vals) > 1 {
|
||||
args = strings.TrimSpace(vals[1])
|
||||
}
|
||||
for _, cmd := range debugCommands(s) {
|
||||
for _, alias := range cmd.aliases {
|
||||
if alias == cmdname {
|
||||
return cmd.cmdFn(goid, frame, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errNoCmd
|
||||
}
|
||||
|
||||
type cmdfunc func(goid, frame int, args string) (string, error)
|
||||
|
||||
type command struct {
|
||||
aliases []string
|
||||
helpMsg string
|
||||
cmdFn cmdfunc
|
||||
}
|
||||
|
||||
const (
|
||||
msgHelp = `Prints the help message.
|
||||
|
||||
dlv help [command]
|
||||
|
||||
Type "help" followed by the name of a command for more information about it.`
|
||||
|
||||
msgConfig = `Changes configuration parameters.
|
||||
|
||||
dlv config -list
|
||||
|
||||
Show all configuration parameters.
|
||||
|
||||
dlv config -list <parameter>
|
||||
|
||||
Show value of a configuration parameter.
|
||||
|
||||
dlv config <parameter> <value>
|
||||
|
||||
Changes the value of a configuration parameter.
|
||||
|
||||
dlv config substitutePath <from> <to>
|
||||
dlv config substitutePath <from>
|
||||
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.
|
||||
|
||||
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>]
|
||||
|
||||
If regex is specified only the source files matching it will be returned.`
|
||||
)
|
||||
|
||||
// debugCommands returns a list of commands with default commands defined.
|
||||
func debugCommands(s *Session) []command {
|
||||
return []command{
|
||||
{aliases: []string{"help", "h"}, cmdFn: s.helpMessage, helpMsg: msgHelp},
|
||||
{aliases: []string{"config"}, cmdFn: s.evaluateConfig, helpMsg: msgConfig},
|
||||
{aliases: []string{"sources", "s"}, cmdFn: s.sources, helpMsg: msgSources},
|
||||
}
|
||||
}
|
||||
|
||||
var errNoCmd = errors.New("command not available")
|
||||
|
||||
func (s *Session) helpMessage(_, _ int, args string) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
if args != "" {
|
||||
for _, cmd := range debugCommands(s) {
|
||||
for _, alias := range cmd.aliases {
|
||||
if alias == args {
|
||||
return cmd.helpMsg, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errNoCmd
|
||||
}
|
||||
|
||||
fmt.Fprintln(&buf, "The following commands are available:")
|
||||
|
||||
for _, cmd := range debugCommands(s) {
|
||||
h := cmd.helpMsg
|
||||
if idx := strings.Index(h, "\n"); idx >= 0 {
|
||||
h = h[:idx]
|
||||
}
|
||||
if len(cmd.aliases) > 1 {
|
||||
fmt.Fprintf(&buf, " dlv %s (alias: %s) \t %s\n", cmd.aliases[0], strings.Join(cmd.aliases[1:], " | "), h)
|
||||
} else {
|
||||
fmt.Fprintf(&buf, " dlv %s \t %s\n", cmd.aliases[0], h)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(&buf)
|
||||
fmt.Fprintln(&buf, "Type 'dlv help' followed by a command for full documentation.")
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (s *Session) evaluateConfig(_, _ int, expr string) (string, error) {
|
||||
argv := config.Split2PartsBySpace(expr)
|
||||
name := argv[0]
|
||||
if name == "-list" {
|
||||
if len(argv) > 1 {
|
||||
return config.ConfigureListByName(&s.args, argv[1], "cfgName"), 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", "showPprofLabels":
|
||||
// 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) {
|
||||
sources, err := s.debugger.Sources(filter)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sort.Strings(sources)
|
||||
return strings.Join(sources, "\n"), nil
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package dap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-delve/delve/pkg/config"
|
||||
)
|
||||
|
||||
func listConfig(args *launchAttachArgs) string {
|
||||
var buf bytes.Buffer
|
||||
config.ConfigureList(&buf, args, "cfgName")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func configureSet(sargs *launchAttachArgs, args string) (bool, string, error) {
|
||||
v := config.Split2PartsBySpace(args)
|
||||
|
||||
cfgname := v[0]
|
||||
var rest string
|
||||
if len(v) == 2 {
|
||||
rest = v[1]
|
||||
}
|
||||
|
||||
field := config.ConfigureFindFieldByName(sargs, cfgname, "cfgName")
|
||||
if !field.CanAddr() {
|
||||
return false, "", fmt.Errorf("%q is not a configuration parameter", cfgname)
|
||||
}
|
||||
|
||||
if cfgname == "substitutePath" {
|
||||
err := configureSetSubstitutePath(sargs, rest)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
// Print the updated client to server and server to client maps.
|
||||
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
|
||||
}
|
||||
return true, config.ConfigureListByName(sargs, cfgname, "cfgName"), nil
|
||||
}
|
||||
|
||||
func configureSetSubstitutePath(args *launchAttachArgs, rest string) error {
|
||||
if strings.TrimSpace(rest) == "-clear" {
|
||||
args.substitutePathClientToServer = args.substitutePathClientToServer[:0]
|
||||
args.substitutePathServerToClient = args.substitutePathServerToClient[:0]
|
||||
return nil
|
||||
}
|
||||
argv := config.SplitQuotedFields(rest, '"')
|
||||
if len(argv) == 2 && argv[0] == "-clear" {
|
||||
argv = argv[1:]
|
||||
}
|
||||
switch len(argv) {
|
||||
case 0:
|
||||
// do nothing, let caller show the current list of substitute path rules
|
||||
return nil
|
||||
case 1: // delete substitute-path rule
|
||||
for i := range args.substitutePathClientToServer {
|
||||
if args.substitutePathClientToServer[i][0] == argv[0] {
|
||||
copy(args.substitutePathClientToServer[i:], args.substitutePathClientToServer[i+1:])
|
||||
args.substitutePathClientToServer = args.substitutePathClientToServer[:len(args.substitutePathClientToServer)-1]
|
||||
copy(args.substitutePathServerToClient[i:], args.substitutePathServerToClient[i+1:])
|
||||
args.substitutePathServerToClient = args.substitutePathServerToClient[:len(args.substitutePathServerToClient)-1]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("could not find rule for %q", argv[0])
|
||||
case 2: // add substitute-path rule
|
||||
for i := range args.substitutePathClientToServer {
|
||||
if args.substitutePathClientToServer[i][0] == argv[0] {
|
||||
args.substitutePathClientToServer[i][1] = argv[1]
|
||||
args.substitutePathServerToClient[i][0] = argv[1]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
args.substitutePathClientToServer = append(args.substitutePathClientToServer, [2]string{argv[0], argv[1]})
|
||||
args.substitutePathServerToClient = append(args.substitutePathServerToClient, [2]string{argv[1], argv[0]})
|
||||
|
||||
default:
|
||||
return errors.New("too many arguments to \"config substitutePath\"")
|
||||
}
|
||||
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 errors.New("too many arguments to \"config showPprofLabels\"")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
package dap
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestListConfig(t *testing.T) {
|
||||
type args struct {
|
||||
args *launchAttachArgs
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
args: args{
|
||||
args: &launchAttachArgs{},
|
||||
},
|
||||
want: formatConfig(0, false, false, "", []string{}, false, [][2]string{}),
|
||||
},
|
||||
{
|
||||
name: "default values",
|
||||
args: args{
|
||||
args: &defaultArgs,
|
||||
},
|
||||
want: formatConfig(50, false, false, "", []string{}, false, [][2]string{}),
|
||||
},
|
||||
{
|
||||
name: "custom values",
|
||||
args: args{
|
||||
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, "SomeFilter", []string{"SomeLabel"}, false, [][2]string{{"hello", "world"}}),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := listConfig(tt.args.args); got != tt.want {
|
||||
t.Errorf("listConfig() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigureSetSubstitutePath(t *testing.T) {
|
||||
type args struct {
|
||||
args *launchAttachArgs
|
||||
rest string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantRules [][2]string
|
||||
wantErr bool
|
||||
}{
|
||||
// Test add rule.
|
||||
{
|
||||
name: "add rule",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{},
|
||||
substitutePathServerToClient: [][2]string{},
|
||||
},
|
||||
rest: "/path/to/client/dir /path/to/server/dir",
|
||||
},
|
||||
wantRules: [][2]string{{"/path/to/client/dir", "/path/to/server/dir"}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "add rule (multiple)",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{
|
||||
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
|
||||
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
|
||||
},
|
||||
substitutePathServerToClient: [][2]string{
|
||||
{"/path/to/server/dir/a", "/path/to/client/dir/a"},
|
||||
{"/path/to/server/dir/b", "/path/to/client/dir/b"},
|
||||
},
|
||||
},
|
||||
rest: "/path/to/client/dir/c /path/to/server/dir/b",
|
||||
},
|
||||
wantRules: [][2]string{
|
||||
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
|
||||
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
|
||||
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "add rule from empty string",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{},
|
||||
substitutePathServerToClient: [][2]string{},
|
||||
},
|
||||
rest: `"" /path/to/client/dir`,
|
||||
},
|
||||
wantRules: [][2]string{{"", "/path/to/client/dir"}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "add rule to empty string",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{},
|
||||
substitutePathServerToClient: [][2]string{},
|
||||
},
|
||||
rest: `/path/to/client/dir ""`,
|
||||
},
|
||||
wantRules: [][2]string{{"/path/to/client/dir", ""}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "add rule from empty string(multiple)",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{
|
||||
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
|
||||
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
|
||||
},
|
||||
substitutePathServerToClient: [][2]string{
|
||||
{"/path/to/server/dir/a", "/path/to/client/dir/a"},
|
||||
{"/path/to/server/dir/b", "/path/to/client/dir/b"},
|
||||
},
|
||||
},
|
||||
rest: `"" /path/to/client/dir/c`,
|
||||
},
|
||||
wantRules: [][2]string{
|
||||
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
|
||||
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
|
||||
{"", "/path/to/client/dir/c"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "add rule to empty string(multiple)",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{
|
||||
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
|
||||
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
|
||||
},
|
||||
substitutePathServerToClient: [][2]string{
|
||||
{"/path/to/server/dir/a", "/path/to/client/dir/a"},
|
||||
{"/path/to/server/dir/b", "/path/to/client/dir/b"},
|
||||
},
|
||||
},
|
||||
rest: `/path/to/client/dir/c ""`,
|
||||
},
|
||||
wantRules: [][2]string{
|
||||
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
|
||||
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
|
||||
{"/path/to/client/dir/c", ""},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
// Test modify rule.
|
||||
{
|
||||
name: "modify rule",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{{"/path/to/client/dir", "/path/to/server/dir"}},
|
||||
substitutePathServerToClient: [][2]string{{"/path/to/server/dir", "/path/to/client/dir"}},
|
||||
},
|
||||
rest: "/path/to/client/dir /new/path/to/server/dir",
|
||||
},
|
||||
wantRules: [][2]string{{"/path/to/client/dir", "/new/path/to/server/dir"}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "modify rule with from as empty string",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{{"", "/path/to/server/dir"}},
|
||||
substitutePathServerToClient: [][2]string{{"/path/to/server/dir", ""}},
|
||||
},
|
||||
rest: `"" /new/path/to/server/dir`,
|
||||
},
|
||||
wantRules: [][2]string{{"", "/new/path/to/server/dir"}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "modify rule with to as empty string",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{{"/path/to/client/dir", ""}},
|
||||
substitutePathServerToClient: [][2]string{{"", "/path/to/client/dir"}},
|
||||
},
|
||||
rest: `/path/to/client/dir ""`,
|
||||
},
|
||||
wantRules: [][2]string{{"/path/to/client/dir", ""}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "modify rule (multiple)",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{
|
||||
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
|
||||
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
|
||||
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
|
||||
},
|
||||
substitutePathServerToClient: [][2]string{
|
||||
{"/path/to/server/dir/a", "/path/to/client/dir/a"},
|
||||
{"/path/to/server/dir/b", "/path/to/client/dir/b"},
|
||||
{"/path/to/server/dir/b", "/path/to/client/dir/c"},
|
||||
},
|
||||
},
|
||||
rest: "/path/to/client/dir/b /new/path",
|
||||
},
|
||||
wantRules: [][2]string{
|
||||
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
|
||||
{"/path/to/client/dir/b", "/new/path"},
|
||||
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "modify rule with from as empty string(multiple)",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{
|
||||
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
|
||||
{"", "/path/to/server/dir/b"},
|
||||
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
|
||||
},
|
||||
substitutePathServerToClient: [][2]string{
|
||||
{"/path/to/server/dir/a", "/path/to/client/dir/a"},
|
||||
{"/path/to/server/dir/b", ""},
|
||||
{"/path/to/server/dir/b", "/path/to/client/dir/c"},
|
||||
},
|
||||
},
|
||||
rest: `"" /new/path`,
|
||||
},
|
||||
wantRules: [][2]string{
|
||||
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
|
||||
{"", "/new/path"},
|
||||
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "modify rule with to as empty string(multiple)",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{
|
||||
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
|
||||
{"/path/to/client/dir/b", "/path/to/server/dir/b"},
|
||||
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
|
||||
},
|
||||
substitutePathServerToClient: [][2]string{
|
||||
{"/path/to/server/dir/a", "/path/to/client/dir/a"},
|
||||
{"/path/to/server/dir/b", "/path/to/client/dir/b"},
|
||||
{"/path/to/server/dir/b", "/path/to/client/dir/c"},
|
||||
},
|
||||
},
|
||||
rest: `/path/to/client/dir/b ""`,
|
||||
},
|
||||
wantRules: [][2]string{
|
||||
{"/path/to/client/dir/a", "/path/to/server/dir/a"},
|
||||
{"/path/to/client/dir/b", ""},
|
||||
{"/path/to/client/dir/c", "/path/to/server/dir/b"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
// Test delete rule.
|
||||
{
|
||||
name: "delete rule",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{{"/path/to/client/dir", "/path/to/server/dir"}},
|
||||
substitutePathServerToClient: [][2]string{{"/path/to/server/dir", "/path/to/client/dir"}},
|
||||
},
|
||||
rest: "/path/to/client/dir",
|
||||
},
|
||||
wantRules: [][2]string{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "delete rule, empty string",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{{"", "/path/to/server/dir"}},
|
||||
substitutePathServerToClient: [][2]string{{"/path/to/server/dir", ""}},
|
||||
},
|
||||
rest: `""`,
|
||||
},
|
||||
wantRules: [][2]string{},
|
||||
wantErr: false,
|
||||
},
|
||||
// Test invalid input.
|
||||
{
|
||||
name: "error on delete nonexistent rule",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{{"/path/to/client/dir", "/path/to/server/dir"}},
|
||||
substitutePathServerToClient: [][2]string{{"/path/to/server/dir", "/path/to/client/dir"}},
|
||||
},
|
||||
rest: "/path/to/server/dir",
|
||||
},
|
||||
wantRules: [][2]string{{"/path/to/client/dir", "/path/to/server/dir"}},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := configureSetSubstitutePath(tt.args.args, tt.args.rest)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("configureSetSubstitutePath() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if len(tt.args.args.substitutePathClientToServer) != len(tt.wantRules) {
|
||||
t.Errorf("configureSetSubstitutePath() got substitutePathClientToServer=%v, want %d rules", tt.args.args.substitutePathClientToServer, len(tt.wantRules))
|
||||
return
|
||||
}
|
||||
gotClient2Server := tt.args.args.substitutePathClientToServer
|
||||
gotServer2Client := tt.args.args.substitutePathServerToClient
|
||||
for i, rule := range tt.wantRules {
|
||||
if gotClient2Server[i][0] != rule[0] || gotClient2Server[i][1] != rule[1] {
|
||||
t.Errorf("configureSetSubstitutePath() got substitutePathClientToServer[%d]=%#v,\n want %#v rules", i, gotClient2Server[i], rule)
|
||||
}
|
||||
if gotServer2Client[i][1] != rule[0] || gotServer2Client[i][0] != rule[1] {
|
||||
reverseRule := [2]string{rule[1], rule[0]}
|
||||
t.Errorf("configureSetSubstitutePath() got substitutePathServerToClient[%d]=%#v,\n want %#v rules", i, gotClient2Server[i], reverseRule)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,648 @@
|
||||
// Package daptest provides a sample client with utilities
|
||||
// for DAP mode testing.
|
||||
package daptest
|
||||
|
||||
//go:generate go run ./gen/main.go -o ./resp.go
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-dap"
|
||||
)
|
||||
|
||||
// Client is a debugger service client that uses Debug Adaptor Protocol.
|
||||
// It does not (yet?) implement service.Client interface.
|
||||
// All client methods are synchronous.
|
||||
type Client struct {
|
||||
conn net.Conn
|
||||
reader *bufio.Reader
|
||||
// seq is used to track the sequence number of each
|
||||
// requests that the client sends to the server
|
||||
seq int
|
||||
}
|
||||
|
||||
// NewClient creates a new Client over a TCP connection.
|
||||
// Call Close() to close the connection.
|
||||
func NewClient(addr string) *Client {
|
||||
fmt.Println("Connecting to server at:", addr)
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
log.Fatal("dialing:", err)
|
||||
}
|
||||
return NewClientFromConn(conn)
|
||||
}
|
||||
|
||||
// NewClientFromConn creates a new Client with the given TCP connection.
|
||||
// Call Close to close the connection.
|
||||
func NewClientFromConn(conn net.Conn) *Client {
|
||||
c := &Client{conn: conn, reader: bufio.NewReader(conn)}
|
||||
c.seq = 1 // match VS Code numbering
|
||||
return c
|
||||
}
|
||||
|
||||
// Close closes the client connection.
|
||||
func (c *Client) Close() {
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *Client) send(request dap.Message) {
|
||||
dap.WriteProtocolMessage(c.conn, request)
|
||||
}
|
||||
|
||||
func (c *Client) ReadMessage() (dap.Message, error) {
|
||||
return dap.ReadProtocolMessage(c.reader)
|
||||
}
|
||||
|
||||
func (c *Client) ExpectMessage(t *testing.T) dap.Message {
|
||||
t.Helper()
|
||||
m, err := dap.ReadProtocolMessage(c.reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (c *Client) ExpectInvisibleErrorResponse(t *testing.T) *dap.ErrorResponse {
|
||||
t.Helper()
|
||||
er := c.ExpectErrorResponse(t)
|
||||
if er.Body.Error != nil && er.Body.Error.ShowUser {
|
||||
t.Errorf("\ngot %#v\nwant ShowUser=false", er)
|
||||
}
|
||||
return er
|
||||
}
|
||||
|
||||
func (c *Client) ExpectVisibleErrorResponse(t *testing.T) *dap.ErrorResponse {
|
||||
t.Helper()
|
||||
er := c.ExpectErrorResponse(t)
|
||||
if er.Body.Error == nil || !er.Body.Error.ShowUser {
|
||||
t.Errorf("\ngot %#v\nwant ShowUser=true", er)
|
||||
}
|
||||
return er
|
||||
}
|
||||
|
||||
func (c *Client) ExpectErrorResponseWith(t *testing.T, id int, message string, showUser bool) *dap.ErrorResponse {
|
||||
t.Helper()
|
||||
er := c.ExpectErrorResponse(t)
|
||||
if er.Body.Error == nil {
|
||||
t.Errorf("got nil, want Id=%d Format=%q ShowUser=%v", id, message, showUser)
|
||||
return er
|
||||
}
|
||||
if matched, _ := regexp.MatchString(message, er.Body.Error.Format); !matched || er.Body.Error.Id != id || er.Body.Error.ShowUser != showUser {
|
||||
t.Errorf("got %#v, want Id=%d Format=%q ShowUser=%v", er, id, message, showUser)
|
||||
}
|
||||
return er
|
||||
}
|
||||
|
||||
func (c *Client) ExpectInitializeResponseAndCapabilities(t *testing.T) *dap.InitializeResponse {
|
||||
t.Helper()
|
||||
initResp := c.ExpectInitializeResponse(t)
|
||||
wantCapabilities := dap.Capabilities{
|
||||
// the values set by dap.(*Server).onInitializeRequest.
|
||||
SupportsConfigurationDoneRequest: true,
|
||||
SupportsConditionalBreakpoints: true,
|
||||
SupportsDelayedStackTraceLoading: true,
|
||||
SupportsExceptionInfoRequest: true,
|
||||
SupportsSetVariable: true,
|
||||
SupportsFunctionBreakpoints: true,
|
||||
SupportsInstructionBreakpoints: true,
|
||||
SupportsEvaluateForHovers: true,
|
||||
SupportsClipboardContext: true,
|
||||
SupportsSteppingGranularity: true,
|
||||
SupportsLogPoints: true,
|
||||
SupportsDisassembleRequest: true,
|
||||
}
|
||||
if !reflect.DeepEqual(initResp.Body, wantCapabilities) {
|
||||
t.Errorf("capabilities in initializeResponse: got %+v, want %v", pretty(initResp.Body), pretty(wantCapabilities))
|
||||
}
|
||||
return initResp
|
||||
}
|
||||
|
||||
func pretty(v interface{}) string {
|
||||
s, _ := json.MarshalIndent(v, "", "\t")
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (c *Client) ExpectNotYetImplementedErrorResponse(t *testing.T) *dap.ErrorResponse {
|
||||
t.Helper()
|
||||
return c.ExpectErrorResponseWith(t, 7777, "Not yet implemented", false)
|
||||
}
|
||||
|
||||
func (c *Client) ExpectUnsupportedCommandErrorResponse(t *testing.T) *dap.ErrorResponse {
|
||||
t.Helper()
|
||||
return c.ExpectErrorResponseWith(t, 9999, "Unsupported command", false)
|
||||
}
|
||||
|
||||
func (c *Client) ExpectCapabilitiesEventSupportTerminateDebuggee(t *testing.T) *dap.CapabilitiesEvent {
|
||||
t.Helper()
|
||||
e := c.ExpectCapabilitiesEvent(t)
|
||||
if !e.Body.Capabilities.SupportTerminateDebuggee {
|
||||
t.Errorf("\ngot %#v\nwant SupportTerminateDebuggee=true", e.Body.Capabilities.SupportTerminateDebuggee)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (c *Client) ExpectOutputEventRegex(t *testing.T, want string) *dap.OutputEvent {
|
||||
t.Helper()
|
||||
e := c.ExpectOutputEvent(t)
|
||||
if matched, _ := regexp.MatchString(want, e.Body.Output); !matched {
|
||||
t.Errorf("\ngot %#v\nwant Output=%q", e, want)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
const ProcessExited = `Process [0-9]+ has exited with status %s\n`
|
||||
|
||||
func (c *Client) ExpectOutputEventProcessExited(t *testing.T, status int) *dap.OutputEvent {
|
||||
t.Helper()
|
||||
// We sometimes fail to return the correct exit status on Linux, so allow -1 here as well.
|
||||
return c.ExpectOutputEventRegex(t, fmt.Sprintf(ProcessExited, fmt.Sprintf("(%d|-1)", status)))
|
||||
}
|
||||
|
||||
func (c *Client) ExpectOutputEventProcessExitedAnyStatus(t *testing.T) *dap.OutputEvent {
|
||||
t.Helper()
|
||||
return c.ExpectOutputEventRegex(t, fmt.Sprintf(ProcessExited, `-?\d+`))
|
||||
}
|
||||
|
||||
func (c *Client) ExpectOutputEventDetaching(t *testing.T) *dap.OutputEvent {
|
||||
t.Helper()
|
||||
return c.ExpectOutputEventRegex(t, `Detaching\n`)
|
||||
}
|
||||
|
||||
func (c *Client) ExpectOutputEventDetachingKill(t *testing.T) *dap.OutputEvent {
|
||||
t.Helper()
|
||||
return c.ExpectOutputEventRegex(t, `Detaching and terminating target process\n`)
|
||||
}
|
||||
|
||||
func (c *Client) ExpectOutputEventDetachingNoKill(t *testing.T) *dap.OutputEvent {
|
||||
t.Helper()
|
||||
return c.ExpectOutputEventRegex(t, `Detaching without terminating target process\n`)
|
||||
}
|
||||
|
||||
func (c *Client) ExpectOutputEventTerminating(t *testing.T) *dap.OutputEvent {
|
||||
t.Helper()
|
||||
return c.ExpectOutputEventRegex(t, `Terminating process [0-9]+\n`)
|
||||
}
|
||||
|
||||
const ClosingClient = "Closing client session, but leaving multi-client DAP server at .+:[0-9]+ with debuggee %s\n"
|
||||
|
||||
func (c *Client) ExpectOutputEventClosingClient(t *testing.T, status string) *dap.OutputEvent {
|
||||
t.Helper()
|
||||
return c.ExpectOutputEventRegex(t, fmt.Sprintf(ClosingClient, status))
|
||||
}
|
||||
|
||||
func (c *Client) CheckStopLocation(t *testing.T, thread int, name string, line interface{}) {
|
||||
t.Helper()
|
||||
c.StackTraceRequest(thread, 0, 20)
|
||||
st := c.ExpectStackTraceResponse(t)
|
||||
if len(st.Body.StackFrames) < 1 {
|
||||
t.Errorf("\ngot %#v\nwant len(stackframes) => 1", st)
|
||||
} else {
|
||||
switch line := line.(type) {
|
||||
case int:
|
||||
if line != -1 && st.Body.StackFrames[0].Line != line {
|
||||
t.Errorf("\ngot %#v\nwant Line=%d", st, line)
|
||||
}
|
||||
case []int:
|
||||
found := false
|
||||
for _, line := range line {
|
||||
if st.Body.StackFrames[0].Line == line {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("\ngot %#v\nwant Line=%v", st, line)
|
||||
}
|
||||
}
|
||||
if st.Body.StackFrames[0].Name != name {
|
||||
t.Errorf("\ngot %#v\nwant Name=%q", st, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeRequest sends an 'initialize' request.
|
||||
func (c *Client) InitializeRequest() {
|
||||
request := &dap.InitializeRequest{Request: *c.newRequest("initialize")}
|
||||
request.Arguments = dap.InitializeRequestArguments{
|
||||
AdapterID: "go",
|
||||
PathFormat: "path",
|
||||
LinesStartAt1: true,
|
||||
ColumnsStartAt1: true,
|
||||
SupportsVariableType: true,
|
||||
SupportsVariablePaging: true,
|
||||
SupportsRunInTerminalRequest: true,
|
||||
Locale: "en-us",
|
||||
}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// InitializeRequestWithArgs sends an 'initialize' request with specified arguments.
|
||||
func (c *Client) InitializeRequestWithArgs(args dap.InitializeRequestArguments) {
|
||||
request := &dap.InitializeRequest{Request: *c.newRequest("initialize")}
|
||||
request.Arguments = args
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
func toRawMessage(in interface{}) json.RawMessage {
|
||||
out, _ := json.Marshal(in)
|
||||
return out
|
||||
}
|
||||
|
||||
// LaunchRequest sends a 'launch' request with the specified args.
|
||||
func (c *Client) LaunchRequest(mode, program string, stopOnEntry bool) {
|
||||
request := &dap.LaunchRequest{Request: *c.newRequest("launch")}
|
||||
request.Arguments = toRawMessage(map[string]interface{}{
|
||||
"request": "launch",
|
||||
"mode": mode,
|
||||
"program": program,
|
||||
"stopOnEntry": stopOnEntry,
|
||||
})
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// LaunchRequestWithArgs takes a map of untyped implementation-specific
|
||||
// arguments to send a 'launch' request. This version can be used to
|
||||
// test for values of unexpected types or unspecified values.
|
||||
func (c *Client) LaunchRequestWithArgs(arguments map[string]interface{}) {
|
||||
request := &dap.LaunchRequest{Request: *c.newRequest("launch")}
|
||||
request.Arguments = toRawMessage(arguments)
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// AttachRequest sends an 'attach' request with the specified
|
||||
// arguments.
|
||||
func (c *Client) AttachRequest(arguments map[string]interface{}) {
|
||||
request := &dap.AttachRequest{Request: *c.newRequest("attach")}
|
||||
request.Arguments = toRawMessage(arguments)
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// DisconnectRequest sends a 'disconnect' request.
|
||||
func (c *Client) DisconnectRequest() {
|
||||
request := &dap.DisconnectRequest{Request: *c.newRequest("disconnect")}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// DisconnectRequestWithKillOption sends a 'disconnect' request with an option to specify
|
||||
// `terminateDebuggee`.
|
||||
func (c *Client) DisconnectRequestWithKillOption(kill bool) {
|
||||
request := &dap.DisconnectRequest{Request: *c.newRequest("disconnect")}
|
||||
request.Arguments = &dap.DisconnectArguments{
|
||||
TerminateDebuggee: kill,
|
||||
}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// SetBreakpointsRequest sends a 'setBreakpoints' request.
|
||||
func (c *Client) SetBreakpointsRequest(file string, lines []int) {
|
||||
c.SetBreakpointsRequestWithArgs(file, lines, nil, nil, nil)
|
||||
}
|
||||
|
||||
// SetBreakpointsRequestWithArgs sends a 'setBreakpoints' request with an option to
|
||||
// specify conditions, hit conditions, and log messages.
|
||||
func (c *Client) SetBreakpointsRequestWithArgs(file string, lines []int, conditions, hitConditions, logMessages map[int]string) {
|
||||
request := &dap.SetBreakpointsRequest{Request: *c.newRequest("setBreakpoints")}
|
||||
request.Arguments = dap.SetBreakpointsArguments{
|
||||
Source: dap.Source{
|
||||
Name: filepath.Base(file),
|
||||
Path: file,
|
||||
},
|
||||
Breakpoints: make([]dap.SourceBreakpoint, len(lines)),
|
||||
}
|
||||
for i, l := range lines {
|
||||
request.Arguments.Breakpoints[i].Line = l
|
||||
if cond, ok := conditions[l]; ok {
|
||||
request.Arguments.Breakpoints[i].Condition = cond
|
||||
}
|
||||
if hitCond, ok := hitConditions[l]; ok {
|
||||
request.Arguments.Breakpoints[i].HitCondition = hitCond
|
||||
}
|
||||
if logMessage, ok := logMessages[l]; ok {
|
||||
request.Arguments.Breakpoints[i].LogMessage = logMessage
|
||||
}
|
||||
}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// SetExceptionBreakpointsRequest sends a 'setExceptionBreakpoints' request.
|
||||
func (c *Client) SetExceptionBreakpointsRequest() {
|
||||
request := &dap.SetBreakpointsRequest{Request: *c.newRequest("setExceptionBreakpoints")}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// ConfigurationDoneRequest sends a 'configurationDone' request.
|
||||
func (c *Client) ConfigurationDoneRequest() {
|
||||
request := &dap.ConfigurationDoneRequest{Request: *c.newRequest("configurationDone")}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// ContinueRequest sends a 'continue' request.
|
||||
func (c *Client) ContinueRequest(thread int) {
|
||||
request := &dap.ContinueRequest{Request: *c.newRequest("continue")}
|
||||
request.Arguments.ThreadId = thread
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// NextRequest sends a 'next' request.
|
||||
func (c *Client) NextRequest(thread int) {
|
||||
request := &dap.NextRequest{Request: *c.newRequest("next")}
|
||||
request.Arguments.ThreadId = thread
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// NextInstructionRequest sends a 'next' request with granularity 'instruction'.
|
||||
func (c *Client) NextInstructionRequest(thread int) {
|
||||
request := &dap.NextRequest{Request: *c.newRequest("next")}
|
||||
request.Arguments.ThreadId = thread
|
||||
request.Arguments.Granularity = "instruction"
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// StepInRequest sends a 'stepIn' request.
|
||||
func (c *Client) StepInRequest(thread int) {
|
||||
request := &dap.StepInRequest{Request: *c.newRequest("stepIn")}
|
||||
request.Arguments.ThreadId = thread
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// StepInInstructionRequest sends a 'stepIn' request with granularity 'instruction'.
|
||||
func (c *Client) StepInInstructionRequest(thread int) {
|
||||
request := &dap.StepInRequest{Request: *c.newRequest("stepIn")}
|
||||
request.Arguments.ThreadId = thread
|
||||
request.Arguments.Granularity = "instruction"
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// StepOutRequest sends a 'stepOut' request.
|
||||
func (c *Client) StepOutRequest(thread int) {
|
||||
request := &dap.StepOutRequest{Request: *c.newRequest("stepOut")}
|
||||
request.Arguments.ThreadId = thread
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// StepOutInstructionRequest sends a 'stepOut' request with granularity 'instruction'.
|
||||
func (c *Client) StepOutInstructionRequest(thread int) {
|
||||
request := &dap.StepOutRequest{Request: *c.newRequest("stepOut")}
|
||||
request.Arguments.ThreadId = thread
|
||||
request.Arguments.Granularity = "instruction"
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// PauseRequest sends a 'pause' request.
|
||||
func (c *Client) PauseRequest(threadId int) {
|
||||
request := &dap.PauseRequest{Request: *c.newRequest("pause")}
|
||||
request.Arguments.ThreadId = threadId
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// ThreadsRequest sends a 'threads' request.
|
||||
func (c *Client) ThreadsRequest() {
|
||||
request := &dap.ThreadsRequest{Request: *c.newRequest("threads")}
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// StackTraceRequest sends a 'stackTrace' request.
|
||||
func (c *Client) StackTraceRequest(threadID, startFrame, levels int) {
|
||||
request := &dap.StackTraceRequest{Request: *c.newRequest("stackTrace")}
|
||||
request.Arguments.ThreadId = threadID
|
||||
request.Arguments.StartFrame = startFrame
|
||||
request.Arguments.Levels = levels
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// ScopesRequest sends a 'scopes' request.
|
||||
func (c *Client) ScopesRequest(frameID int) {
|
||||
request := &dap.ScopesRequest{Request: *c.newRequest("scopes")}
|
||||
request.Arguments.FrameId = frameID
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// VariablesRequest sends a 'variables' request.
|
||||
func (c *Client) VariablesRequest(variablesReference int) {
|
||||
request := &dap.VariablesRequest{Request: *c.newRequest("variables")}
|
||||
request.Arguments.VariablesReference = variablesReference
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// IndexedVariablesRequest sends a 'variables' request.
|
||||
func (c *Client) IndexedVariablesRequest(variablesReference, start, count int) {
|
||||
request := &dap.VariablesRequest{Request: *c.newRequest("variables")}
|
||||
request.Arguments.VariablesReference = variablesReference
|
||||
request.Arguments.Filter = "indexed"
|
||||
request.Arguments.Start = start
|
||||
request.Arguments.Count = count
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// NamedVariablesRequest sends a 'variables' request.
|
||||
func (c *Client) NamedVariablesRequest(variablesReference int) {
|
||||
request := &dap.VariablesRequest{Request: *c.newRequest("variables")}
|
||||
request.Arguments.VariablesReference = variablesReference
|
||||
request.Arguments.Filter = "named"
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// TerminateRequest sends a 'terminate' request.
|
||||
func (c *Client) TerminateRequest() {
|
||||
c.send(&dap.TerminateRequest{Request: *c.newRequest("terminate")})
|
||||
}
|
||||
|
||||
// RestartRequest sends a 'restart' request.
|
||||
func (c *Client) RestartRequest() {
|
||||
c.send(&dap.RestartRequest{Request: *c.newRequest("restart")})
|
||||
}
|
||||
|
||||
// SetFunctionBreakpointsRequest sends a 'setFunctionBreakpoints' request.
|
||||
func (c *Client) SetFunctionBreakpointsRequest(breakpoints []dap.FunctionBreakpoint) {
|
||||
c.send(&dap.SetFunctionBreakpointsRequest{
|
||||
Request: *c.newRequest("setFunctionBreakpoints"),
|
||||
Arguments: dap.SetFunctionBreakpointsArguments{
|
||||
Breakpoints: breakpoints,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// SetInstructionBreakpointsRequest sends a 'setInstructionBreakpoints' request.
|
||||
func (c *Client) SetInstructionBreakpointsRequest(breakpoints []dap.InstructionBreakpoint) {
|
||||
c.send(&dap.SetInstructionBreakpointsRequest{
|
||||
Request: *c.newRequest("setInstructionBreakpoints"),
|
||||
Arguments: dap.SetInstructionBreakpointsArguments{
|
||||
Breakpoints: breakpoints,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// StepBackRequest sends a 'stepBack' request.
|
||||
func (c *Client) StepBackRequest() {
|
||||
c.send(&dap.StepBackRequest{Request: *c.newRequest("stepBack")})
|
||||
}
|
||||
|
||||
// ReverseContinueRequest sends a 'reverseContinue' request.
|
||||
func (c *Client) ReverseContinueRequest() {
|
||||
c.send(&dap.ReverseContinueRequest{Request: *c.newRequest("reverseContinue")})
|
||||
}
|
||||
|
||||
// SetVariableRequest sends a 'setVariable' request.
|
||||
func (c *Client) SetVariableRequest(variablesRef int, name, value string) {
|
||||
request := &dap.SetVariableRequest{Request: *c.newRequest("setVariable")}
|
||||
request.Arguments.VariablesReference = variablesRef
|
||||
request.Arguments.Name = name
|
||||
request.Arguments.Value = value
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// RestartFrameRequest sends a 'restartFrame' request.
|
||||
func (c *Client) RestartFrameRequest() {
|
||||
c.send(&dap.RestartFrameRequest{Request: *c.newRequest("restartFrame")})
|
||||
}
|
||||
|
||||
// GotoRequest sends a 'goto' request.
|
||||
func (c *Client) GotoRequest() {
|
||||
c.send(&dap.GotoRequest{Request: *c.newRequest("goto")})
|
||||
}
|
||||
|
||||
// SetExpressionRequest sends a 'setExpression' request.
|
||||
func (c *Client) SetExpressionRequest() {
|
||||
c.send(&dap.SetExpressionRequest{Request: *c.newRequest("setExpression")})
|
||||
}
|
||||
|
||||
// SourceRequest sends a 'source' request.
|
||||
func (c *Client) SourceRequest() {
|
||||
c.send(&dap.SourceRequest{Request: *c.newRequest("source")})
|
||||
}
|
||||
|
||||
// TerminateThreadsRequest sends a 'terminateThreads' request.
|
||||
func (c *Client) TerminateThreadsRequest() {
|
||||
c.send(&dap.TerminateThreadsRequest{Request: *c.newRequest("terminateThreads")})
|
||||
}
|
||||
|
||||
// EvaluateRequest sends a 'evaluate' request.
|
||||
func (c *Client) EvaluateRequest(expr string, fid int, context string) {
|
||||
request := &dap.EvaluateRequest{Request: *c.newRequest("evaluate")}
|
||||
request.Arguments.Expression = expr
|
||||
request.Arguments.FrameId = fid
|
||||
request.Arguments.Context = context
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// StepInTargetsRequest sends a 'stepInTargets' request.
|
||||
func (c *Client) StepInTargetsRequest() {
|
||||
c.send(&dap.StepInTargetsRequest{Request: *c.newRequest("stepInTargets")})
|
||||
}
|
||||
|
||||
// GotoTargetsRequest sends a 'gotoTargets' request.
|
||||
func (c *Client) GotoTargetsRequest() {
|
||||
c.send(&dap.GotoTargetsRequest{Request: *c.newRequest("gotoTargets")})
|
||||
}
|
||||
|
||||
// CompletionsRequest sends a 'completions' request.
|
||||
func (c *Client) CompletionsRequest() {
|
||||
c.send(&dap.CompletionsRequest{Request: *c.newRequest("completions")})
|
||||
}
|
||||
|
||||
// ExceptionInfoRequest sends a 'exceptionInfo' request.
|
||||
func (c *Client) ExceptionInfoRequest(threadID int) {
|
||||
request := &dap.ExceptionInfoRequest{Request: *c.newRequest("exceptionInfo")}
|
||||
request.Arguments.ThreadId = threadID
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// LoadedSourcesRequest sends a 'loadedSources' request.
|
||||
func (c *Client) LoadedSourcesRequest() {
|
||||
c.send(&dap.LoadedSourcesRequest{Request: *c.newRequest("loadedSources")})
|
||||
}
|
||||
|
||||
// DataBreakpointInfoRequest sends a 'dataBreakpointInfo' request.
|
||||
func (c *Client) DataBreakpointInfoRequest() {
|
||||
c.send(&dap.DataBreakpointInfoRequest{Request: *c.newRequest("dataBreakpointInfo")})
|
||||
}
|
||||
|
||||
// SetDataBreakpointsRequest sends a 'setDataBreakpoints' request.
|
||||
func (c *Client) SetDataBreakpointsRequest() {
|
||||
c.send(&dap.SetDataBreakpointsRequest{Request: *c.newRequest("setDataBreakpoints")})
|
||||
}
|
||||
|
||||
// ReadMemoryRequest sends a 'readMemory' request.
|
||||
func (c *Client) ReadMemoryRequest() {
|
||||
c.send(&dap.ReadMemoryRequest{Request: *c.newRequest("readMemory")})
|
||||
}
|
||||
|
||||
// DisassembleRequest sends a 'disassemble' request.
|
||||
func (c *Client) DisassembleRequest(memoryReference string, instructionOffset, instructionCount int) {
|
||||
c.send(&dap.DisassembleRequest{
|
||||
Request: *c.newRequest("disassemble"),
|
||||
Arguments: dap.DisassembleArguments{
|
||||
MemoryReference: memoryReference,
|
||||
Offset: 0,
|
||||
InstructionOffset: instructionOffset,
|
||||
InstructionCount: instructionCount,
|
||||
ResolveSymbols: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CancelRequest sends a 'cancel' request.
|
||||
func (c *Client) CancelRequest() {
|
||||
c.send(&dap.CancelRequest{Request: *c.newRequest("cancel")})
|
||||
}
|
||||
|
||||
// BreakpointLocationsRequest sends a 'breakpointLocations' request.
|
||||
func (c *Client) BreakpointLocationsRequest() {
|
||||
c.send(&dap.BreakpointLocationsRequest{Request: *c.newRequest("breakpointLocations")})
|
||||
}
|
||||
|
||||
// ModulesRequest sends a 'modules' request.
|
||||
func (c *Client) ModulesRequest() {
|
||||
c.send(&dap.ModulesRequest{Request: *c.newRequest("modules")})
|
||||
}
|
||||
|
||||
// UnknownRequest triggers dap.DecodeProtocolMessageFieldError.
|
||||
func (c *Client) UnknownRequest() {
|
||||
request := c.newRequest("unknown")
|
||||
c.send(request)
|
||||
}
|
||||
|
||||
// UnknownEvent triggers dap.DecodeProtocolMessageFieldError.
|
||||
func (c *Client) UnknownEvent() {
|
||||
event := &dap.Event{}
|
||||
event.Type = "event"
|
||||
event.Seq = -1
|
||||
event.Event = "unknown"
|
||||
c.send(event)
|
||||
}
|
||||
|
||||
// BadRequest triggers an unmarshal error.
|
||||
func (c *Client) BadRequest() {
|
||||
content := []byte("{malformedString}")
|
||||
contentLengthHeaderFmt := "Content-Length: %d\r\n\r\n"
|
||||
header := fmt.Sprintf(contentLengthHeaderFmt, len(content))
|
||||
c.conn.Write([]byte(header))
|
||||
c.conn.Write(content)
|
||||
}
|
||||
|
||||
// KnownEvent passes decode checks, but delve has no 'case' to
|
||||
// handle it. This behaves the same way a new request type
|
||||
// added to go-dap, but not to delve.
|
||||
func (c *Client) KnownEvent() {
|
||||
event := &dap.Event{}
|
||||
event.Type = "event"
|
||||
event.Seq = -1
|
||||
event.Event = "terminated"
|
||||
c.send(event)
|
||||
}
|
||||
|
||||
func (c *Client) newRequest(command string) *dap.Request {
|
||||
request := &dap.Request{}
|
||||
request.Type = "request"
|
||||
request.Command = command
|
||||
request.Seq = c.seq
|
||||
c.seq++
|
||||
return request
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Binary gen generates service/dap/daptest/responses.go.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"go/types"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
||||
_ "github.com/google/go-dap"
|
||||
)
|
||||
|
||||
var oFlag = flag.String("o", "", "output file name")
|
||||
|
||||
var tmpl = template.Must(template.New("assert").Parse(`package daptest
|
||||
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
// The code generator program is in ./gen directory.
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-dap"
|
||||
)
|
||||
{{range .}}
|
||||
// Expect{{.}} reads a protocol message from the connection
|
||||
// and fails the test if the read message is not *{{.}}.
|
||||
func (c *Client) Expect{{.}}(t *testing.T) *dap.{{.}} {
|
||||
t.Helper()
|
||||
m := c.ExpectMessage(t)
|
||||
return c.Check{{.}}(t, m)
|
||||
}
|
||||
|
||||
// Check{{.}} fails the test if m is not *{{.}}.
|
||||
func (c *Client) Check{{.}}(t *testing.T, m dap.Message) *dap.{{.}} {
|
||||
t.Helper(){{if or (or (eq . "StepInResponse") (eq . "StepOutResponse")) (eq . "NextResponse") }}
|
||||
_, ok := m.(*dap.ContinuedEvent)
|
||||
if !ok {
|
||||
t.Fatalf("got %#v, want *dap.ContinuedEvent", m)
|
||||
}
|
||||
m = c.ExpectMessage(t){{else}}{{if (eq . "ConfigurationDoneResponse") }}
|
||||
oe, ok := m.(*dap.OutputEvent)
|
||||
if !ok {
|
||||
t.Fatalf("got %#v, want *dap.OutputEvent", m)
|
||||
}
|
||||
if oe.Body.Output != "Type 'dlv help' for list of commands.\n" {
|
||||
t.Fatalf("got %#v, want Output=%q", m, "Type 'dlv help' for list of commands.\n")
|
||||
}
|
||||
m = c.ExpectMessage(t){{end}}{{end}}
|
||||
r, ok := m.(*dap.{{.}})
|
||||
if !ok {
|
||||
t.Fatalf("got %#v, want *dap.{{.}}", m)
|
||||
}
|
||||
return r
|
||||
}{{end}}
|
||||
`))
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *oFlag == "" {
|
||||
fmt.Fprintf(os.Stderr, "-o must be provided\n")
|
||||
}
|
||||
|
||||
pkgs, err := packages.Load(&packages.Config{
|
||||
Mode: packages.NeedTypes,
|
||||
}, "github.com/google/go-dap")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "load: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(pkgs) != 1 || pkgs[0].Types == nil {
|
||||
fmt.Fprintf(os.Stderr, "invalid package was loaded: %#v\n", pkgs)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
messages := []string{}
|
||||
scope := pkgs[0].Types.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
// Find only types that are embedding go-dap.Response message.
|
||||
obj := scope.Lookup(name)
|
||||
if !obj.Exported() {
|
||||
continue // skip unexported
|
||||
}
|
||||
u, ok := obj.Type().Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for i := 0; i < u.NumFields(); i++ {
|
||||
if f := u.Field(i); f.Embedded() && (f.Type().String() == "github.com/google/go-dap.Response" ||
|
||||
f.Type().String() == "github.com/google/go-dap.Event") {
|
||||
messages = append(messages, obj.Name())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
if err := tmpl.Execute(buf, messages); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to generate: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
formatted, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Generated invalid go code: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := os.WriteFile(*oFlag, formatted, 0o644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
package dap
|
||||
|
||||
// Unique identifiers for messages returned for errors from requests.
|
||||
// These values are not mandated by DAP (other than the uniqueness
|
||||
// requirement), so each implementation is free to choose their own.
|
||||
const (
|
||||
UnsupportedCommand int = 9999
|
||||
InternalError int = 8888
|
||||
NotYetImplemented int = 7777
|
||||
|
||||
// Where applicable and for consistency only,
|
||||
// values below are inspired the original vscode-go debug adaptor.
|
||||
|
||||
FailedToLaunch = 3000
|
||||
FailedToAttach = 3001
|
||||
FailedToInitialize = 3002
|
||||
UnableToSetBreakpoints = 2002
|
||||
UnableToDisplayThreads = 2003
|
||||
UnableToProduceStackTrace = 2004
|
||||
UnableToListLocals = 2005
|
||||
UnableToListArgs = 2006
|
||||
UnableToListGlobals = 2007
|
||||
UnableToLookupVariable = 2008
|
||||
UnableToEvaluateExpression = 2009
|
||||
UnableToHalt = 2010
|
||||
UnableToGetExceptionInfo = 2011
|
||||
UnableToSetVariable = 2012
|
||||
UnableToDisassemble = 2013
|
||||
UnableToListRegisters = 2014
|
||||
UnableToRunDlvCommand = 2015
|
||||
|
||||
// Add more codes as we support more requests
|
||||
|
||||
NoDebugIsRunning = 3000
|
||||
DebuggeeIsRunning = 4000
|
||||
DisconnectError = 5000
|
||||
)
|
||||
@@ -0,0 +1,75 @@
|
||||
package dap
|
||||
|
||||
import "github.com/go-delve/delve/pkg/proc"
|
||||
|
||||
const startHandle = 1000
|
||||
|
||||
// handlesMap maps arbitrary values to unique sequential ids.
|
||||
// This provides convenient abstraction of references, offering
|
||||
// opacity and allowing simplification of complex identifiers.
|
||||
// Based on
|
||||
// https://github.com/microsoft/vscode-debugadapter-node/blob/master/adapter/src/handles.ts
|
||||
type handlesMap struct {
|
||||
nextHandle int
|
||||
handleToVal map[int]interface{}
|
||||
}
|
||||
|
||||
type fullyQualifiedVariable struct {
|
||||
*proc.Variable
|
||||
// A way to load this variable by either using all names in the hierarchic
|
||||
// sequence above this variable (most readable when referenced by the UI)
|
||||
// if available or a special expression based on:
|
||||
// https://github.com/go-delve/delve/blob/master/Documentation/api/ClientHowto.md#loading-more-of-a-variable
|
||||
// Empty if the variable cannot or should not be reloaded.
|
||||
fullyQualifiedNameOrExpr string
|
||||
// True if this represents variable scope
|
||||
isScope bool
|
||||
// startIndex is the index of the first child for an array or slice.
|
||||
// This variable represents a chunk of the array, slice or map.
|
||||
startIndex int
|
||||
}
|
||||
|
||||
func newHandlesMap() *handlesMap {
|
||||
return &handlesMap{startHandle, make(map[int]interface{})}
|
||||
}
|
||||
|
||||
func (hs *handlesMap) reset() {
|
||||
hs.nextHandle = startHandle
|
||||
hs.handleToVal = make(map[int]interface{})
|
||||
}
|
||||
|
||||
func (hs *handlesMap) create(value interface{}) int {
|
||||
next := hs.nextHandle
|
||||
hs.nextHandle++
|
||||
hs.handleToVal[next] = value
|
||||
return next
|
||||
}
|
||||
|
||||
func (hs *handlesMap) get(handle int) (interface{}, bool) {
|
||||
v, ok := hs.handleToVal[handle]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
type variablesHandlesMap struct {
|
||||
m *handlesMap
|
||||
}
|
||||
|
||||
func newVariablesHandlesMap() *variablesHandlesMap {
|
||||
return &variablesHandlesMap{newHandlesMap()}
|
||||
}
|
||||
|
||||
func (hs *variablesHandlesMap) create(value *fullyQualifiedVariable) int {
|
||||
return hs.m.create(value)
|
||||
}
|
||||
|
||||
func (hs *variablesHandlesMap) get(handle int) (*fullyQualifiedVariable, bool) {
|
||||
v, ok := hs.m.get(handle)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return v.(*fullyQualifiedVariable), true
|
||||
}
|
||||
|
||||
func (hs *variablesHandlesMap) reset() {
|
||||
hs.m.reset()
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,313 @@
|
||||
package dap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Launch debug sessions support the following modes:
|
||||
//
|
||||
// -- [DEFAULT] "debug" - builds and launches debugger for specified program (similar to 'dlv debug')
|
||||
//
|
||||
// Required args: program
|
||||
// Optional args with default: output, cwd, noDebug
|
||||
// Optional args: buildFlags, args
|
||||
//
|
||||
// -- "test" - builds and launches debugger for specified test (similar to 'dlv test')
|
||||
//
|
||||
// same args as above
|
||||
//
|
||||
// -- "exec" - launches debugger for precompiled binary (similar to 'dlv exec')
|
||||
//
|
||||
// Required args: program
|
||||
// Optional args with default: cwd, noDebug
|
||||
// Optional args: args
|
||||
//
|
||||
// -- "replay" - replays a trace generated by mozilla rr. Mozilla rr must be installed.
|
||||
//
|
||||
// Required args: traceDirPath
|
||||
// Optional args: args
|
||||
//
|
||||
// -- "core" - examines a core dump (only supports linux and windows core dumps).
|
||||
//
|
||||
// Required args: program, coreFilePath
|
||||
// Optional args: args
|
||||
//
|
||||
// TODO(hyangah): change this to 'validateLaunchMode' that checks
|
||||
// all the required/optional fields mentioned above.
|
||||
func isValidLaunchMode(mode string) bool {
|
||||
switch mode {
|
||||
case "exec", "debug", "test", "replay", "core":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Default values for Launch/Attach configs.
|
||||
// Used to initialize configuration variables before decoding
|
||||
// arguments in launch/attach requests.
|
||||
var (
|
||||
defaultLaunchAttachCommonConfig = LaunchAttachCommonConfig{
|
||||
Backend: "default",
|
||||
StackTraceDepth: 50,
|
||||
}
|
||||
defaultLaunchConfig = LaunchConfig{
|
||||
Mode: "debug",
|
||||
LaunchAttachCommonConfig: defaultLaunchAttachCommonConfig,
|
||||
}
|
||||
defaultAttachConfig = AttachConfig{
|
||||
Mode: "local",
|
||||
LaunchAttachCommonConfig: defaultLaunchAttachCommonConfig,
|
||||
}
|
||||
)
|
||||
|
||||
// LaunchConfig is the collection of launch request attributes recognized by DAP implementation.
|
||||
type LaunchConfig struct {
|
||||
// Acceptable values are:
|
||||
// "debug": compiles your program with optimizations disabled, starts and attaches to it.
|
||||
// "test": compiles your unit test program with optimizations disabled, starts and attaches to it.
|
||||
// "exec": executes a precompiled binary and begins a debug session.
|
||||
// "replay": replays an rr trace.
|
||||
// "core": examines a core dump.
|
||||
//
|
||||
// Default is "debug".
|
||||
Mode string `json:"mode,omitempty"`
|
||||
|
||||
// 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 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 relative path is provided, it will be interpreted as
|
||||
// a relative path to Delve's working directory. This is
|
||||
// similar to `dlv --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 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 acceptable.
|
||||
// "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
|
||||
// the Delve's current working directory.
|
||||
// This is deleted after the debug session ends.
|
||||
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"`
|
||||
|
||||
// DlvCwd is the new working directory for Delve server.
|
||||
// If specified, the server will change its working
|
||||
// directory to the specified directory using os.Chdir.
|
||||
// Any other launch attributes with relative paths interpreted
|
||||
// using Delve's working directory will use this new directory.
|
||||
// When Delve needs to build the program (in debug/test modes),
|
||||
// it will run the go command from this directory as well.
|
||||
//
|
||||
// If a relative path is provided as DlvCwd, it will be
|
||||
// interpreted as a path relative to Delve's current working
|
||||
// directory.
|
||||
DlvCwd string `json:"dlvCwd,omitempty"`
|
||||
|
||||
// Env specifies optional environment variables for Delve server
|
||||
// in addition to the environment variables Delve initially
|
||||
// started with.
|
||||
// Variables with 'nil' values can be used to unset the named
|
||||
// environment variables.
|
||||
// Values are interpreted verbatim. Variable substitution or
|
||||
// reference to other environment variables is not supported.
|
||||
Env map[string]*string `json:"env,omitempty"`
|
||||
|
||||
// The output mode specifies how to handle the program's output.
|
||||
OutputMode string `json:"outputMode,omitempty"`
|
||||
|
||||
LaunchAttachCommonConfig
|
||||
}
|
||||
|
||||
// LaunchAttachCommonConfig is the attributes common in both launch/attach requests.
|
||||
type LaunchAttachCommonConfig struct {
|
||||
// Automatically stop program after launch or attach.
|
||||
StopOnEntry bool `json:"stopOnEntry,omitempty"`
|
||||
|
||||
// Backend used for debugging. See `dlv backend` for allowed values.
|
||||
// Default is "default".
|
||||
Backend string `json:"backend,omitempty"`
|
||||
|
||||
// Maximum depth of stack trace to return.
|
||||
// Default is 50.
|
||||
StackTraceDepth int `json:"stackTraceDepth,omitempty"`
|
||||
|
||||
// Boolean value to indicate whether global package variables
|
||||
// should be shown in the variables pane or not.
|
||||
ShowGlobalVariables bool `json:"showGlobalVariables,omitempty"`
|
||||
|
||||
// Boolean value to indicate whether registers should be shown
|
||||
// in the variables pane or not.
|
||||
ShowRegisters bool `json:"showRegisters,omitempty"`
|
||||
|
||||
// Boolean value to indicate whether system goroutines
|
||||
// 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"`
|
||||
|
||||
// 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.
|
||||
// The debug adapter will replace the local path with the remote path in all of the calls.
|
||||
// See also Documentation/cli/substitutepath.md.
|
||||
SubstitutePath []SubstitutePath `json:"substitutePath,omitempty"`
|
||||
}
|
||||
|
||||
// SubstitutePath defines a mapping from a local path to the remote path.
|
||||
// Both 'from' and 'to' must be specified and non-null.
|
||||
// Empty values can be used to add or remove absolute path prefixes when mapping.
|
||||
// For example, mapping with empty 'to' can be used to work with binaries with trimmed paths.
|
||||
type SubstitutePath struct {
|
||||
// The local path to be replaced when passing paths to the debugger.
|
||||
From string `json:"from,omitempty"`
|
||||
// The remote path to be replaced when passing paths back to the client.
|
||||
To string `json:"to,omitempty"`
|
||||
}
|
||||
|
||||
func (m *SubstitutePath) UnmarshalJSON(data []byte) error {
|
||||
// use custom unmarshal to check if both from/to are set.
|
||||
type tmpType struct {
|
||||
From *string
|
||||
To *string
|
||||
}
|
||||
var tmp tmpType
|
||||
|
||||
if err := json.Unmarshal(data, &tmp); err != nil {
|
||||
if _, ok := err.(*json.UnmarshalTypeError); ok {
|
||||
return fmt.Errorf(`cannot use %s as 'substitutePath' of type {"from":string, "to":string}`, data)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if tmp.From == nil || tmp.To == nil {
|
||||
return errors.New("'substitutePath' requires both 'from' and 'to' entries")
|
||||
}
|
||||
*m = SubstitutePath{*tmp.From, *tmp.To}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttachConfig is the collection of attach request attributes recognized by DAP implementation.
|
||||
// 'processId' and 'waitFor' are mutually exclusive, and can't be specified at the same time.
|
||||
type AttachConfig struct {
|
||||
// Acceptable values are:
|
||||
// "local": attaches to the local process with the given ProcessID.
|
||||
// "remote": expects the debugger to already be running to "attach" to an in-progress debug session.
|
||||
//
|
||||
// Default is "local".
|
||||
Mode string `json:"mode"`
|
||||
|
||||
// The numeric ID of the process to be debugged.
|
||||
ProcessID int `json:"processId,omitempty"`
|
||||
|
||||
// Wait for a process with a name beginning with this prefix.
|
||||
AttachWaitFor string `json:"waitFor,omitempty"`
|
||||
|
||||
LaunchAttachCommonConfig
|
||||
}
|
||||
|
||||
// unmarshalLaunchAttachArgs wraps unmarshaling of launch/attach request's
|
||||
// arguments attribute. Upon unmarshal failure, it returns an error massaged
|
||||
// to be suitable for end-users.
|
||||
func unmarshalLaunchAttachArgs(input json.RawMessage, config interface{}) error {
|
||||
if err := json.Unmarshal(input, config); err != nil {
|
||||
if uerr, ok := err.(*json.UnmarshalTypeError); ok {
|
||||
// Format json.UnmarshalTypeError error string in our own way. E.g.,
|
||||
// "json: cannot unmarshal number into Go struct field LaunchArgs.substitutePath of type dap.SubstitutePath"
|
||||
// => "cannot unmarshal number into 'substitutePath' of type {from:string, to:string}"
|
||||
// "json: cannot unmarshal number into Go struct field LaunchArgs.program of type string" (go1.16)
|
||||
// => "cannot unmarshal number into 'program' of type string"
|
||||
typ := uerr.Type.String()
|
||||
if uerr.Field == "substitutePath" {
|
||||
typ = `{"from":string, "to":string}`
|
||||
}
|
||||
return fmt.Errorf("cannot unmarshal %v into %q of type %v", uerr.Value, uerr.Field, typ)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func prettyPrint(config interface{}) string {
|
||||
pretty, err := json.MarshalIndent(config, "", "\t")
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%#v", config)
|
||||
}
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user