service/dap: add config expressions to evaluate requests (#2750)
We want to provide more flexibility for users to make changes to their configurations while the debug session is running. This could be accomplished by creating a custom request, but that were require a new UI as well, and every client of dlv dap to provide its own UI for this. By using the evaluate context, users can use the already existing debug console to change their configurations. This change includes a refactor of the terminal code in order to share the code with the dap package. This change provides a very similar to UI as the terminal package, but there are different configuration options that are DAP specific. We plan to use this same mechanism to expose a few other commands including "sources" to help users debug an ineffective substitutePath configuration.
This commit is contained in:
parent
b48ceec161
commit
922c4cebd4
@ -2,6 +2,11 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -61,3 +66,138 @@ func SplitQuotedFields(in string, quote rune) []string {
|
|||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ConfigureSetSimple(rest string, cfgname string, field reflect.Value) error {
|
||||||
|
simpleArg := func(typ reflect.Type) (reflect.Value, error) {
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Int:
|
||||||
|
n, err := strconv.Atoi(rest)
|
||||||
|
if err != nil {
|
||||||
|
return reflect.ValueOf(nil), fmt.Errorf("argument to %q must be a number", cfgname)
|
||||||
|
}
|
||||||
|
if n < 0 {
|
||||||
|
return reflect.ValueOf(nil), fmt.Errorf("argument to %q must be a number greater than zero", cfgname)
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(&n), nil
|
||||||
|
case reflect.Bool:
|
||||||
|
v := rest == "true"
|
||||||
|
return reflect.ValueOf(&v), nil
|
||||||
|
case reflect.String:
|
||||||
|
return reflect.ValueOf(&rest), nil
|
||||||
|
default:
|
||||||
|
return reflect.ValueOf(nil), fmt.Errorf("unsupported type for configuration key %q", cfgname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.Kind() == reflect.Ptr {
|
||||||
|
val, err := simpleArg(field.Type().Elem())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.Set(val)
|
||||||
|
} else {
|
||||||
|
val, err := simpleArg(field.Type())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.Set(val.Elem())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfigureList(w io.Writer, config interface{}, tag string) {
|
||||||
|
it := IterateConfiguration(config, tag)
|
||||||
|
for it.Next() {
|
||||||
|
fieldName, field := it.Field()
|
||||||
|
if fieldName == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
writeField(w, field, fieldName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeField(w io.Writer, field reflect.Value, fieldName string) {
|
||||||
|
switch field.Kind() {
|
||||||
|
case reflect.Interface:
|
||||||
|
switch field := field.Interface().(type) {
|
||||||
|
case string:
|
||||||
|
fmt.Fprintf(w, "%s\t%q\n", fieldName, field)
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(w, "%s\t%v\n", fieldName, field)
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
if !field.IsNil() {
|
||||||
|
fmt.Fprintf(w, "%s\t%v\n", fieldName, field.Elem())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "%s\t<not defined>\n", fieldName)
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
fmt.Fprintf(w, "%s\t%q\n", fieldName, field)
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(w, "%s\t%v\n", fieldName, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type configureIterator struct {
|
||||||
|
cfgValue reflect.Value
|
||||||
|
cfgType reflect.Type
|
||||||
|
i int
|
||||||
|
tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func IterateConfiguration(conf interface{}, tag string) *configureIterator {
|
||||||
|
cfgValue := reflect.ValueOf(conf).Elem()
|
||||||
|
cfgType := cfgValue.Type()
|
||||||
|
|
||||||
|
return &configureIterator{cfgValue, cfgType, -1, tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *configureIterator) Next() bool {
|
||||||
|
it.i++
|
||||||
|
return it.i < it.cfgValue.NumField()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *configureIterator) Field() (name string, field reflect.Value) {
|
||||||
|
name = it.cfgType.Field(it.i).Tag.Get(it.tag)
|
||||||
|
if comma := strings.Index(name, ","); comma >= 0 {
|
||||||
|
name = name[:comma]
|
||||||
|
}
|
||||||
|
field = it.cfgValue.Field(it.i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfigureListByName(conf interface{}, name, tag string) string {
|
||||||
|
if name == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
it := IterateConfiguration(conf, tag)
|
||||||
|
for it.Next() {
|
||||||
|
fieldName, field := it.Field()
|
||||||
|
if fieldName == name {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writeField(&buf, field, fieldName)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfigureFindFieldByName(conf interface{}, name, tag string) reflect.Value {
|
||||||
|
it := IterateConfiguration(conf, tag)
|
||||||
|
for it.Next() {
|
||||||
|
fieldName, field := it.Field()
|
||||||
|
if fieldName == name {
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Split2PartsBySpace(s string) []string {
|
||||||
|
v := strings.SplitN(s, " ", 2)
|
||||||
|
for i := range v {
|
||||||
|
v[i] = strings.TrimSpace(v[i])
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
@ -35,3 +35,67 @@ func TestSplitDoubleQuotedFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigureListByName(t *testing.T) {
|
||||||
|
type testConfig struct {
|
||||||
|
boolArg bool `cfgName:"bool-arg"`
|
||||||
|
listArg []string `cfgName:"list-arg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
sargs *testConfig
|
||||||
|
cfgname string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic bool",
|
||||||
|
args: args{
|
||||||
|
sargs: &testConfig{
|
||||||
|
boolArg: true,
|
||||||
|
listArg: []string{},
|
||||||
|
},
|
||||||
|
cfgname: "bool-arg",
|
||||||
|
},
|
||||||
|
want: "bool-arg true\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list arg",
|
||||||
|
args: args{
|
||||||
|
sargs: &testConfig{
|
||||||
|
boolArg: true,
|
||||||
|
listArg: []string{"item 1", "item 2"},
|
||||||
|
},
|
||||||
|
|
||||||
|
cfgname: "list-arg",
|
||||||
|
},
|
||||||
|
want: "list-arg [item 1 item 2]\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
args: args{
|
||||||
|
sargs: &testConfig{},
|
||||||
|
cfgname: "",
|
||||||
|
},
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid",
|
||||||
|
args: args{
|
||||||
|
sargs: &testConfig{},
|
||||||
|
cfgname: "nonexistent",
|
||||||
|
},
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := ConfigureListByName(tt.args.sargs, tt.args.cfgname, "cfgName"); got != tt.want {
|
||||||
|
t.Errorf("ConfigureListByName() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cosiner/argv"
|
"github.com/cosiner/argv"
|
||||||
|
"github.com/go-delve/delve/pkg/config"
|
||||||
"github.com/go-delve/delve/pkg/locspec"
|
"github.com/go-delve/delve/pkg/locspec"
|
||||||
"github.com/go-delve/delve/pkg/terminal/colorize"
|
"github.com/go-delve/delve/pkg/terminal/colorize"
|
||||||
"github.com/go-delve/delve/service"
|
"github.com/go-delve/delve/service"
|
||||||
@ -985,16 +986,8 @@ func selectedGID(state *api.DebuggerState) int {
|
|||||||
return state.SelectedGoroutine.ID
|
return state.SelectedGoroutine.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
func split2PartsBySpace(s string) []string {
|
|
||||||
v := strings.SplitN(s, " ", 2)
|
|
||||||
for i := range v {
|
|
||||||
v[i] = strings.TrimSpace(v[i])
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Commands) goroutine(t *Term, ctx callContext, argstr string) error {
|
func (c *Commands) goroutine(t *Term, ctx callContext, argstr string) error {
|
||||||
args := split2PartsBySpace(argstr)
|
args := config.Split2PartsBySpace(argstr)
|
||||||
|
|
||||||
if ctx.Prefix == onPrefix {
|
if ctx.Prefix == onPrefix {
|
||||||
if len(args) != 1 || args[0] != "" {
|
if len(args) != 1 || args[0] != "" {
|
||||||
@ -1043,7 +1036,7 @@ func (c *Commands) frameCommand(t *Term, ctx callContext, argstr string, directi
|
|||||||
return errors.New("not enough arguments")
|
return errors.New("not enough arguments")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
args := split2PartsBySpace(argstr)
|
args := config.Split2PartsBySpace(argstr)
|
||||||
var err error
|
var err error
|
||||||
if frame, err = strconv.Atoi(args[0]); err != nil {
|
if frame, err = strconv.Atoi(args[0]); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1258,7 +1251,7 @@ func restart(t *Term, ctx callContext, args string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func restartRecorded(t *Term, ctx callContext, args string) error {
|
func restartRecorded(t *Term, ctx callContext, args string) error {
|
||||||
v := split2PartsBySpace(args)
|
v := config.Split2PartsBySpace(args)
|
||||||
|
|
||||||
rerecord := false
|
rerecord := false
|
||||||
resetArgs := false
|
resetArgs := false
|
||||||
@ -1810,7 +1803,7 @@ func formatBreakpointAttrs(prefix string, bp *api.Breakpoint, includeTrace bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) ([]*api.Breakpoint, error) {
|
func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) ([]*api.Breakpoint, error) {
|
||||||
args := split2PartsBySpace(argstr)
|
args := config.Split2PartsBySpace(argstr)
|
||||||
|
|
||||||
requestedBp := &api.Breakpoint{}
|
requestedBp := &api.Breakpoint{}
|
||||||
spec := ""
|
spec := ""
|
||||||
@ -2212,7 +2205,7 @@ func types(t *Term, ctx callContext, args string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseVarArguments(args string, t *Term) (filter string, cfg api.LoadConfig) {
|
func parseVarArguments(args string, t *Term) (filter string, cfg api.LoadConfig) {
|
||||||
if v := split2PartsBySpace(args); len(v) >= 1 && v[0] == "-v" {
|
if v := config.Split2PartsBySpace(args); len(v) >= 1 && v[0] == "-v" {
|
||||||
if len(v) == 2 {
|
if len(v) == 2 {
|
||||||
return v[1], t.loadConfig()
|
return v[1], t.loadConfig()
|
||||||
} else {
|
} else {
|
||||||
@ -2486,7 +2479,7 @@ func disassCommand(t *Term, ctx callContext, args string) error {
|
|||||||
var cmd, rest string
|
var cmd, rest string
|
||||||
|
|
||||||
if args != "" {
|
if args != "" {
|
||||||
argv := split2PartsBySpace(args)
|
argv := config.Split2PartsBySpace(args)
|
||||||
if len(argv) != 2 {
|
if len(argv) != 2 {
|
||||||
return errDisasmUsage
|
return errDisasmUsage
|
||||||
}
|
}
|
||||||
@ -2517,7 +2510,7 @@ func disassCommand(t *Term, ctx callContext, args string) error {
|
|||||||
}
|
}
|
||||||
disasm, disasmErr = t.client.DisassemblePC(ctx.Scope, locs[0].PC, flavor)
|
disasm, disasmErr = t.client.DisassemblePC(ctx.Scope, locs[0].PC, flavor)
|
||||||
case "-a":
|
case "-a":
|
||||||
v := split2PartsBySpace(rest)
|
v := config.Split2PartsBySpace(rest)
|
||||||
if len(v) != 2 {
|
if len(v) != 2 {
|
||||||
return errDisasmUsage
|
return errDisasmUsage
|
||||||
}
|
}
|
||||||
@ -2839,7 +2832,7 @@ func getBreakpointByIDOrName(t *Term, arg string) (*api.Breakpoint, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) onCmd(t *Term, ctx callContext, argstr string) error {
|
func (c *Commands) onCmd(t *Term, ctx callContext, argstr string) error {
|
||||||
args := split2PartsBySpace(argstr)
|
args := config.Split2PartsBySpace(argstr)
|
||||||
|
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return errors.New("not enough arguments")
|
return errors.New("not enough arguments")
|
||||||
@ -2916,7 +2909,7 @@ func (c *Commands) parseBreakpointAttrs(t *Term, ctx callContext, r io.Reader) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func conditionCmd(t *Term, ctx callContext, argstr string) error {
|
func conditionCmd(t *Term, ctx callContext, argstr string) error {
|
||||||
args := split2PartsBySpace(argstr)
|
args := config.Split2PartsBySpace(argstr)
|
||||||
|
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return fmt.Errorf("not enough arguments")
|
return fmt.Errorf("not enough arguments")
|
||||||
@ -2930,7 +2923,7 @@ func conditionCmd(t *Term, ctx callContext, argstr string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
args = split2PartsBySpace(args[1])
|
args = config.Split2PartsBySpace(args[1])
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return fmt.Errorf("not enough arguments")
|
return fmt.Errorf("not enough arguments")
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/go-delve/delve/pkg/config"
|
"github.com/go-delve/delve/pkg/config"
|
||||||
@ -32,80 +30,15 @@ func configureCmd(t *Term, ctx callContext, args string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type configureIterator struct {
|
|
||||||
cfgValue reflect.Value
|
|
||||||
cfgType reflect.Type
|
|
||||||
i int
|
|
||||||
}
|
|
||||||
|
|
||||||
func iterateConfiguration(conf *config.Config) *configureIterator {
|
|
||||||
cfgValue := reflect.ValueOf(conf).Elem()
|
|
||||||
cfgType := cfgValue.Type()
|
|
||||||
|
|
||||||
return &configureIterator{cfgValue, cfgType, -1}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *configureIterator) Next() bool {
|
|
||||||
it.i++
|
|
||||||
return it.i < it.cfgValue.NumField()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *configureIterator) Field() (name string, field reflect.Value) {
|
|
||||||
name = it.cfgType.Field(it.i).Tag.Get("yaml")
|
|
||||||
if comma := strings.Index(name, ","); comma >= 0 {
|
|
||||||
name = name[:comma]
|
|
||||||
}
|
|
||||||
field = it.cfgValue.Field(it.i)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func configureFindFieldByName(conf *config.Config, name string) reflect.Value {
|
|
||||||
it := iterateConfiguration(conf)
|
|
||||||
for it.Next() {
|
|
||||||
fieldName, field := it.Field()
|
|
||||||
if fieldName == name {
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reflect.ValueOf(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func configureList(t *Term) error {
|
func configureList(t *Term) error {
|
||||||
w := new(tabwriter.Writer)
|
w := new(tabwriter.Writer)
|
||||||
w.Init(os.Stdout, 0, 8, 1, ' ', 0)
|
w.Init(os.Stdout, 0, 8, 1, ' ', 0)
|
||||||
|
config.ConfigureList(w, t.conf, "yaml")
|
||||||
it := iterateConfiguration(t.conf)
|
|
||||||
for it.Next() {
|
|
||||||
fieldName, field := it.Field()
|
|
||||||
if fieldName == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch field.Kind() {
|
|
||||||
case reflect.Interface:
|
|
||||||
switch field := field.Interface().(type) {
|
|
||||||
case string:
|
|
||||||
fmt.Fprintf(w, "%s\t%q\n", fieldName, field)
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(w, "%s\t%v\n", fieldName, field)
|
|
||||||
}
|
|
||||||
case reflect.Ptr:
|
|
||||||
if !field.IsNil() {
|
|
||||||
fmt.Fprintf(w, "%s\t%v\n", fieldName, field.Elem())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(w, "%s\t<not defined>\n", fieldName)
|
|
||||||
}
|
|
||||||
case reflect.String:
|
|
||||||
fmt.Fprintf(w, "%s\t%q\n", fieldName, field)
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(w, "%s\t%v\n", fieldName, field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w.Flush()
|
return w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureSet(t *Term, args string) error {
|
func configureSet(t *Term, args string) error {
|
||||||
v := split2PartsBySpace(args)
|
v := config.Split2PartsBySpace(args)
|
||||||
|
|
||||||
cfgname := v[0]
|
cfgname := v[0]
|
||||||
var rest string
|
var rest string
|
||||||
@ -117,7 +50,7 @@ func configureSet(t *Term, args string) error {
|
|||||||
return configureSetAlias(t, rest)
|
return configureSetAlias(t, rest)
|
||||||
}
|
}
|
||||||
|
|
||||||
field := configureFindFieldByName(t.conf, cfgname)
|
field := config.ConfigureFindFieldByName(t.conf, cfgname, "yaml")
|
||||||
if !field.CanAddr() {
|
if !field.CanAddr() {
|
||||||
return fmt.Errorf("%q is not a configuration parameter", cfgname)
|
return fmt.Errorf("%q is not a configuration parameter", cfgname)
|
||||||
}
|
}
|
||||||
@ -126,41 +59,7 @@ func configureSet(t *Term, args string) error {
|
|||||||
return configureSetSubstitutePath(t, rest)
|
return configureSetSubstitutePath(t, rest)
|
||||||
}
|
}
|
||||||
|
|
||||||
simpleArg := func(typ reflect.Type) (reflect.Value, error) {
|
return config.ConfigureSetSimple(rest, cfgname, field)
|
||||||
switch typ.Kind() {
|
|
||||||
case reflect.Int:
|
|
||||||
n, err := strconv.Atoi(rest)
|
|
||||||
if err != nil {
|
|
||||||
return reflect.ValueOf(nil), fmt.Errorf("argument to %q must be a number", cfgname)
|
|
||||||
}
|
|
||||||
if n < 0 {
|
|
||||||
return reflect.ValueOf(nil), fmt.Errorf("argument to %q must be a number greater than zero", cfgname)
|
|
||||||
}
|
|
||||||
return reflect.ValueOf(&n), nil
|
|
||||||
case reflect.Bool:
|
|
||||||
v := rest == "true"
|
|
||||||
return reflect.ValueOf(&v), nil
|
|
||||||
case reflect.String:
|
|
||||||
return reflect.ValueOf(&rest), nil
|
|
||||||
default:
|
|
||||||
return reflect.ValueOf(nil), fmt.Errorf("unsupported type for configuration key %q", cfgname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if field.Kind() == reflect.Ptr {
|
|
||||||
val, err := simpleArg(field.Type().Elem())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
field.Set(val)
|
|
||||||
} else {
|
|
||||||
val, err := simpleArg(field.Type())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
field.Set(val.Elem())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureSetSubstitutePath(t *Term, rest string) error {
|
func configureSetSubstitutePath(t *Term, rest string) error {
|
||||||
|
115
service/dap/command.go
Normal file
115
service/dap/command.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package dap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-delve/delve/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
help [command]
|
||||||
|
|
||||||
|
Type "help" followed by the name of a command for more information about it.`
|
||||||
|
|
||||||
|
msgConfig = `Changes configuration parameters.
|
||||||
|
|
||||||
|
config -list
|
||||||
|
|
||||||
|
Show all configuration parameters.
|
||||||
|
|
||||||
|
config <parameter> <value>
|
||||||
|
|
||||||
|
Changes the value of a configuration parameter.
|
||||||
|
|
||||||
|
config substitutePath <from> <to>
|
||||||
|
config substitutePath <from>
|
||||||
|
|
||||||
|
Adds or removes a path substitution rule.`
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, " %s (alias: %s) \t %s\n", cmd.aliases[0], strings.Join(cmd.aliases[1:], " | "), h)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(&buf, " %s \t %s\n", cmd.aliases[0], h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(&buf)
|
||||||
|
fmt.Fprintln(&buf, "Type 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]
|
||||||
|
switch name {
|
||||||
|
case "-list":
|
||||||
|
return listConfig(&s.args), nil
|
||||||
|
default:
|
||||||
|
res, err := configureSet(&s.args, expr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
80
service/dap/config.go
Normal file
80
service/dap/config.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package dap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"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) (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 "", 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
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfgname == "substitutePath" {
|
||||||
|
err := configureSetSubstitutePath(sargs, rest)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Print the updated client to server and server to client maps.
|
||||||
|
return fmt.Sprintf("%s\nUpdated", config.ConfigureListByName(sargs, cfgname, "cfgName")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := config.ConfigureSetSimple(rest, cfgname, field)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s\nUpdated", config.ConfigureListByName(sargs, cfgname, "cfgName")), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureSetSubstitutePath(args *launchAttachArgs, rest string) error {
|
||||||
|
argv := config.SplitQuotedFields(rest, '"')
|
||||||
|
switch len(argv) {
|
||||||
|
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 fmt.Errorf("too many arguments to \"config substitute-path\"")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
197
service/dap/config_test.go
Normal file
197
service/dap/config_test.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
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, false, [][2]string{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default values",
|
||||||
|
args: args{
|
||||||
|
args: &defaultArgs,
|
||||||
|
},
|
||||||
|
want: formatConfig(50, false, false, false, [][2]string{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom values",
|
||||||
|
args: args{
|
||||||
|
args: &launchAttachArgs{
|
||||||
|
StackTraceDepth: 35,
|
||||||
|
ShowGlobalVariables: true,
|
||||||
|
substitutePathClientToServer: [][2]string{{"hello", "world"}},
|
||||||
|
substitutePathServerToClient: [][2]string{{"world", "hello"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: formatConfig(35, true, false, 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,
|
||||||
|
},
|
||||||
|
// 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 (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,
|
||||||
|
},
|
||||||
|
// 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,
|
||||||
|
},
|
||||||
|
// Test invalid input.
|
||||||
|
{
|
||||||
|
name: "error on empty args",
|
||||||
|
args: args{
|
||||||
|
args: &launchAttachArgs{
|
||||||
|
substitutePathClientToServer: [][2]string{},
|
||||||
|
substitutePathServerToClient: [][2]string{},
|
||||||
|
},
|
||||||
|
rest: " \n\r ",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -44,7 +44,15 @@ func (c *Client) Check{{.}}(t *testing.T, m dap.Message) *dap.{{.}} {
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("got %#v, want *dap.ContinuedEvent", m)
|
t.Fatalf("got %#v, want *dap.ContinuedEvent", m)
|
||||||
}
|
}
|
||||||
m = c.ExpectMessage(t){{end}}
|
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.{{.}})
|
r, ok := m.(*dap.{{.}})
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("got %#v, want *dap.{{.}}", m)
|
t.Fatalf("got %#v, want *dap.{{.}}", m)
|
||||||
|
@ -128,6 +128,14 @@ func (c *Client) ExpectConfigurationDoneResponse(t *testing.T) *dap.Configuratio
|
|||||||
// CheckConfigurationDoneResponse fails the test if m is not *ConfigurationDoneResponse.
|
// CheckConfigurationDoneResponse fails the test if m is not *ConfigurationDoneResponse.
|
||||||
func (c *Client) CheckConfigurationDoneResponse(t *testing.T, m dap.Message) *dap.ConfigurationDoneResponse {
|
func (c *Client) CheckConfigurationDoneResponse(t *testing.T, m dap.Message) *dap.ConfigurationDoneResponse {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
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)
|
||||||
r, ok := m.(*dap.ConfigurationDoneResponse)
|
r, ok := m.(*dap.ConfigurationDoneResponse)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("got %#v, want *dap.ConfigurationDoneResponse", m)
|
t.Fatalf("got %#v, want *dap.ConfigurationDoneResponse", m)
|
||||||
@ -441,6 +449,24 @@ func (c *Client) CheckLoadedSourcesResponse(t *testing.T, m dap.Message) *dap.Lo
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExpectMemoryEvent reads a protocol message from the connection
|
||||||
|
// and fails the test if the read message is not *MemoryEvent.
|
||||||
|
func (c *Client) ExpectMemoryEvent(t *testing.T) *dap.MemoryEvent {
|
||||||
|
t.Helper()
|
||||||
|
m := c.ExpectMessage(t)
|
||||||
|
return c.CheckMemoryEvent(t, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckMemoryEvent fails the test if m is not *MemoryEvent.
|
||||||
|
func (c *Client) CheckMemoryEvent(t *testing.T, m dap.Message) *dap.MemoryEvent {
|
||||||
|
t.Helper()
|
||||||
|
r, ok := m.(*dap.MemoryEvent)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("got %#v, want *dap.MemoryEvent", m)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
// ExpectModuleEvent reads a protocol message from the connection
|
// ExpectModuleEvent reads a protocol message from the connection
|
||||||
// and fails the test if the read message is not *ModuleEvent.
|
// and fails the test if the read message is not *ModuleEvent.
|
||||||
func (c *Client) ExpectModuleEvent(t *testing.T) *dap.ModuleEvent {
|
func (c *Client) ExpectModuleEvent(t *testing.T) *dap.ModuleEvent {
|
||||||
@ -1085,3 +1111,21 @@ func (c *Client) CheckVariablesResponse(t *testing.T, m dap.Message) *dap.Variab
|
|||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExpectWriteMemoryResponse reads a protocol message from the connection
|
||||||
|
// and fails the test if the read message is not *WriteMemoryResponse.
|
||||||
|
func (c *Client) ExpectWriteMemoryResponse(t *testing.T) *dap.WriteMemoryResponse {
|
||||||
|
t.Helper()
|
||||||
|
m := c.ExpectMessage(t)
|
||||||
|
return c.CheckWriteMemoryResponse(t, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckWriteMemoryResponse fails the test if m is not *WriteMemoryResponse.
|
||||||
|
func (c *Client) CheckWriteMemoryResponse(t *testing.T, m dap.Message) *dap.WriteMemoryResponse {
|
||||||
|
t.Helper()
|
||||||
|
r, ok := m.(*dap.WriteMemoryResponse)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("got %#v, want *dap.WriteMemoryResponse", m)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
@ -26,6 +26,7 @@ const (
|
|||||||
UnableToSetVariable = 2012
|
UnableToSetVariable = 2012
|
||||||
UnableToDisassemble = 2013
|
UnableToDisassemble = 2013
|
||||||
UnableToListRegisters = 2014
|
UnableToListRegisters = 2014
|
||||||
|
UnableToRunDlvCommand = 2015
|
||||||
// Add more codes as we support more requests
|
// Add more codes as we support more requests
|
||||||
NoDebugIsRunning = 3000
|
NoDebugIsRunning = 3000
|
||||||
DebuggeeIsRunning = 4000
|
DebuggeeIsRunning = 4000
|
||||||
|
@ -178,21 +178,22 @@ type process struct {
|
|||||||
|
|
||||||
// launchAttachArgs captures arguments from launch/attach request that
|
// launchAttachArgs captures arguments from launch/attach request that
|
||||||
// impact handling of subsequent requests.
|
// impact handling of subsequent requests.
|
||||||
|
// The fields with cfgName tag can be updated through an evaluation request.
|
||||||
type launchAttachArgs struct {
|
type launchAttachArgs struct {
|
||||||
// stopOnEntry is set to automatically stop the debugee after start.
|
// stopOnEntry is set to automatically stop the debugee after start.
|
||||||
stopOnEntry bool
|
stopOnEntry bool
|
||||||
// stackTraceDepth is the maximum length of the returned list of stack frames.
|
// StackTraceDepth is the maximum length of the returned list of stack frames.
|
||||||
stackTraceDepth int
|
StackTraceDepth int `cfgName:"stackTraceDepth"`
|
||||||
// showGlobalVariables indicates if global package variables should be loaded.
|
// ShowGlobalVariables indicates if global package variables should be loaded.
|
||||||
showGlobalVariables bool
|
ShowGlobalVariables bool `cfgName:"showGlobalVariables"`
|
||||||
// hideSystemGoroutines indicates if system goroutines should be removed from threads
|
// ShowRegisters indicates if register values should be loaded.
|
||||||
|
ShowRegisters bool `cfgName:"showRegisters"`
|
||||||
|
// HideSystemGoroutines indicates if system goroutines should be removed from threads
|
||||||
// responses.
|
// responses.
|
||||||
hideSystemGoroutines bool
|
HideSystemGoroutines bool `cfgName:"hideSystemGoroutines"`
|
||||||
// showRegisters indicates if register values should be loaded.
|
|
||||||
showRegisters bool
|
|
||||||
// substitutePathClientToServer indicates rules for converting file paths between client and debugger.
|
// substitutePathClientToServer indicates rules for converting file paths between client and debugger.
|
||||||
// These must be directory paths.
|
// These must be directory paths.
|
||||||
substitutePathClientToServer [][2]string
|
substitutePathClientToServer [][2]string `cfgName:"substitutePath"`
|
||||||
// substitutePathServerToClient indicates rules for converting file paths between debugger and client.
|
// substitutePathServerToClient indicates rules for converting file paths between debugger and client.
|
||||||
// These must be directory paths.
|
// These must be directory paths.
|
||||||
substitutePathServerToClient [][2]string
|
substitutePathServerToClient [][2]string
|
||||||
@ -203,10 +204,10 @@ type launchAttachArgs struct {
|
|||||||
// in favor of default*Config variables defined in types.go.
|
// in favor of default*Config variables defined in types.go.
|
||||||
var defaultArgs = launchAttachArgs{
|
var defaultArgs = launchAttachArgs{
|
||||||
stopOnEntry: false,
|
stopOnEntry: false,
|
||||||
stackTraceDepth: 50,
|
StackTraceDepth: 50,
|
||||||
showGlobalVariables: false,
|
ShowGlobalVariables: false,
|
||||||
hideSystemGoroutines: false,
|
HideSystemGoroutines: false,
|
||||||
showRegisters: false,
|
ShowRegisters: false,
|
||||||
substitutePathClientToServer: [][2]string{},
|
substitutePathClientToServer: [][2]string{},
|
||||||
substitutePathServerToClient: [][2]string{},
|
substitutePathServerToClient: [][2]string{},
|
||||||
}
|
}
|
||||||
@ -310,11 +311,11 @@ func NewSession(conn io.ReadWriteCloser, config *Config, debugger *debugger.Debu
|
|||||||
func (s *Session) setLaunchAttachArgs(args LaunchAttachCommonConfig) error {
|
func (s *Session) setLaunchAttachArgs(args LaunchAttachCommonConfig) error {
|
||||||
s.args.stopOnEntry = args.StopOnEntry
|
s.args.stopOnEntry = args.StopOnEntry
|
||||||
if depth := args.StackTraceDepth; depth > 0 {
|
if depth := args.StackTraceDepth; depth > 0 {
|
||||||
s.args.stackTraceDepth = depth
|
s.args.StackTraceDepth = depth
|
||||||
}
|
}
|
||||||
s.args.showGlobalVariables = args.ShowGlobalVariables
|
s.args.ShowGlobalVariables = args.ShowGlobalVariables
|
||||||
s.args.hideSystemGoroutines = args.HideSystemGoroutines
|
s.args.ShowRegisters = args.ShowRegisters
|
||||||
s.args.showRegisters = args.ShowRegisters
|
s.args.HideSystemGoroutines = args.HideSystemGoroutines
|
||||||
if paths := args.SubstitutePath; len(paths) > 0 {
|
if paths := args.SubstitutePath; len(paths) > 0 {
|
||||||
clientToServer := make([][2]string, 0, len(paths))
|
clientToServer := make([][2]string, 0, len(paths))
|
||||||
serverToClient := make([][2]string, 0, len(paths))
|
serverToClient := make([][2]string, 0, len(paths))
|
||||||
@ -1555,7 +1556,9 @@ func (s *Session) onConfigurationDoneRequest(request *dap.ConfigurationDoneReque
|
|||||||
}
|
}
|
||||||
s.debugger.Target().KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints | proc.TracepointKeepsSteppingBreakpoints
|
s.debugger.Target().KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints | proc.TracepointKeepsSteppingBreakpoints
|
||||||
|
|
||||||
|
s.logToConsole("Type 'dlv help' for list of commands.")
|
||||||
s.send(&dap.ConfigurationDoneResponse{Response: *newResponse(request.Request)})
|
s.send(&dap.ConfigurationDoneResponse{Response: *newResponse(request.Request)})
|
||||||
|
|
||||||
if !s.args.stopOnEntry {
|
if !s.args.stopOnEntry {
|
||||||
s.runUntilStopAndNotify(api.Continue, allowNextStateChange)
|
s.runUntilStopAndNotify(api.Continue, allowNextStateChange)
|
||||||
}
|
}
|
||||||
@ -1601,7 +1604,7 @@ func (s *Session) onThreadsRequest(request *dap.ThreadsRequest) {
|
|||||||
var next int
|
var next int
|
||||||
if s.debugger != nil {
|
if s.debugger != nil {
|
||||||
gs, next, err = s.debugger.Goroutines(0, maxGoroutines)
|
gs, next, err = s.debugger.Goroutines(0, maxGoroutines)
|
||||||
if err == nil && s.args.hideSystemGoroutines {
|
if err == nil && s.args.HideSystemGoroutines {
|
||||||
gs = s.debugger.FilterGoroutines(gs, []api.ListGoroutinesFilter{{
|
gs = s.debugger.FilterGoroutines(gs, []api.ListGoroutinesFilter{{
|
||||||
Kind: api.GoroutineUser,
|
Kind: api.GoroutineUser,
|
||||||
Negated: false,
|
Negated: false,
|
||||||
@ -1916,7 +1919,7 @@ func (s *Session) onStackTraceRequest(request *dap.StackTraceRequest) {
|
|||||||
if start < 0 {
|
if start < 0 {
|
||||||
start = 0
|
start = 0
|
||||||
}
|
}
|
||||||
levels := s.args.stackTraceDepth
|
levels := s.args.StackTraceDepth
|
||||||
if request.Arguments.Levels > 0 {
|
if request.Arguments.Levels > 0 {
|
||||||
levels = request.Arguments.Levels
|
levels = request.Arguments.Levels
|
||||||
}
|
}
|
||||||
@ -1960,7 +1963,7 @@ func (s *Session) onStackTraceRequest(request *dap.StackTraceRequest) {
|
|||||||
// We don't know the exact number of available stack frames, so
|
// We don't know the exact number of available stack frames, so
|
||||||
// add an arbitrary number so the client knows to request additional
|
// add an arbitrary number so the client knows to request additional
|
||||||
// frames.
|
// frames.
|
||||||
totalFrames += s.args.stackTraceDepth
|
totalFrames += s.args.StackTraceDepth
|
||||||
}
|
}
|
||||||
response := &dap.StackTraceResponse{
|
response := &dap.StackTraceResponse{
|
||||||
Response: *newResponse(request.Request),
|
Response: *newResponse(request.Request),
|
||||||
@ -2011,7 +2014,7 @@ func (s *Session) onScopesRequest(request *dap.ScopesRequest) {
|
|||||||
scopeLocals := dap.Scope{Name: locScope.Name, VariablesReference: s.variableHandles.create(locScope)}
|
scopeLocals := dap.Scope{Name: locScope.Name, VariablesReference: s.variableHandles.create(locScope)}
|
||||||
scopes := []dap.Scope{scopeLocals}
|
scopes := []dap.Scope{scopeLocals}
|
||||||
|
|
||||||
if s.args.showGlobalVariables {
|
if s.args.ShowGlobalVariables {
|
||||||
// Limit what global variables we will return to the current package only.
|
// Limit what global variables we will return to the current package only.
|
||||||
// TODO(polina): This is how vscode-go currently does it to make
|
// TODO(polina): This is how vscode-go currently does it to make
|
||||||
// the amount of the returned data manageable. In fact, this is
|
// the amount of the returned data manageable. In fact, this is
|
||||||
@ -2046,7 +2049,7 @@ func (s *Session) onScopesRequest(request *dap.ScopesRequest) {
|
|||||||
scopes = append(scopes, scopeGlobals)
|
scopes = append(scopes, scopeGlobals)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.args.showRegisters {
|
if s.args.ShowRegisters {
|
||||||
// Retrieve registers
|
// Retrieve registers
|
||||||
regs, err := s.debugger.ScopeRegisters(goid, frame, 0, false)
|
regs, err := s.debugger.ScopeRegisters(goid, frame, 0, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -2565,6 +2568,7 @@ func (s *Session) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr
|
|||||||
// Support the following expressions:
|
// Support the following expressions:
|
||||||
// -- {expression} - evaluates the expression and returns the result as a variable
|
// -- {expression} - evaluates the expression and returns the result as a variable
|
||||||
// -- call {function} - injects a function call and returns the result as a variable
|
// -- call {function} - injects a function call and returns the result as a variable
|
||||||
|
// -- config {expression} - updates configuration paramaters
|
||||||
// TODO(polina): users have complained about having to click to expand multi-level
|
// TODO(polina): users have complained about having to click to expand multi-level
|
||||||
// variables, so consider also adding the following:
|
// variables, so consider also adding the following:
|
||||||
// -- print {expression} - return the result as a string like from dlv cli
|
// -- print {expression} - return the result as a string like from dlv cli
|
||||||
@ -2584,9 +2588,20 @@ func (s *Session) onEvaluateRequest(request *dap.EvaluateRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
response := &dap.EvaluateResponse{Response: *newResponse(request.Request)}
|
response := &dap.EvaluateResponse{Response: *newResponse(request.Request)}
|
||||||
isCall, err := regexp.MatchString(`^\s*call\s+\S+`, request.Arguments.Expression)
|
expr := request.Arguments.Expression
|
||||||
if err == nil && isCall { // call {expression}
|
|
||||||
expr := strings.Replace(request.Arguments.Expression, "call ", "", 1)
|
if isConfig, err := regexp.MatchString(`^\s*dlv\s+\S+`, expr); err == nil && isConfig { // dlv {command}
|
||||||
|
expr := strings.Replace(expr, "dlv ", "", 1)
|
||||||
|
result, err := s.delveCmd(goid, frame, expr)
|
||||||
|
if err != nil {
|
||||||
|
s.sendErrorResponseWithOpts(request.Request, UnableToRunDlvCommand, "Unable to run dlv command", err.Error(), showErrorToUser)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Body = dap.EvaluateResponseBody{
|
||||||
|
Result: result,
|
||||||
|
}
|
||||||
|
} else if isCall, err := regexp.MatchString(`^\s*call\s+\S+`, expr); err == nil && isCall { // call {expression}
|
||||||
|
expr := strings.Replace(expr, "call ", "", 1)
|
||||||
_, retVars, err := s.doCall(goid, frame, expr)
|
_, retVars, err := s.doCall(goid, frame, expr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser)
|
s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser)
|
||||||
@ -2608,7 +2623,7 @@ func (s *Session) onEvaluateRequest(request *dap.EvaluateRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // {expression}
|
} else { // {expression}
|
||||||
exprVar, err := s.debugger.EvalVariableInScope(goid, frame, 0, request.Arguments.Expression, DefaultLoadConfig)
|
exprVar, err := s.debugger.EvalVariableInScope(goid, frame, 0, expr, DefaultLoadConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser)
|
s.sendErrorResponseWithOpts(request.Request, UnableToEvaluateExpression, "Unable to evaluate expression", err.Error(), showErrorToUser)
|
||||||
return
|
return
|
||||||
@ -3201,7 +3216,7 @@ func (s *Session) onExceptionInfoRequest(request *dap.ExceptionInfoRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) stacktrace(goroutineID int, g *proc.G) (string, error) {
|
func (s *Session) stacktrace(goroutineID int, g *proc.G) (string, error) {
|
||||||
frames, err := s.debugger.Stacktrace(goroutineID, s.args.stackTraceDepth, 0)
|
frames, err := s.debugger.Stacktrace(goroutineID, s.args.StackTraceDepth, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -844,14 +844,14 @@ func checkStackFramesNamed(testName string, t *testing.T, got *dap.StackTraceRes
|
|||||||
// checkScope is a helper for verifying the values within a ScopesResponse.
|
// checkScope is a helper for verifying the values within a ScopesResponse.
|
||||||
// i - index of the scope within ScopesRespose.Body.Scopes array
|
// i - index of the scope within ScopesRespose.Body.Scopes array
|
||||||
// name - name of the scope
|
// name - name of the scope
|
||||||
// varRef - reference to retrieve variables of this scope
|
// varRef - reference to retrieve variables of this scope. If varRef is negative, the reference is not checked.
|
||||||
func checkScope(t *testing.T, got *dap.ScopesResponse, i int, name string, varRef int) {
|
func checkScope(t *testing.T, got *dap.ScopesResponse, i int, name string, varRef int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if len(got.Body.Scopes) <= i {
|
if len(got.Body.Scopes) <= i {
|
||||||
t.Errorf("\ngot %d\nwant len(Scopes)>%d", len(got.Body.Scopes), i)
|
t.Errorf("\ngot %d\nwant len(Scopes)>%d", len(got.Body.Scopes), i)
|
||||||
}
|
}
|
||||||
goti := got.Body.Scopes[i]
|
goti := got.Body.Scopes[i]
|
||||||
if goti.Name != name || goti.VariablesReference != varRef || goti.Expensive {
|
if goti.Name != name || (varRef >= 0 && goti.VariablesReference != varRef) || goti.Expensive {
|
||||||
t.Errorf("\ngot %#v\nwant Name=%q VariablesReference=%d Expensive=false", goti, name, varRef)
|
t.Errorf("\ngot %#v\nwant Name=%q VariablesReference=%d Expensive=false", goti, name, varRef)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1063,6 +1063,7 @@ func TestStackTraceRequest(t *testing.T) {
|
|||||||
client.StackTraceRequest(1, 0, 0)
|
client.StackTraceRequest(1, 0, 0)
|
||||||
stResp = client.ExpectStackTraceResponse(t)
|
stResp = client.ExpectStackTraceResponse(t)
|
||||||
checkStackFramesExact(t, stResp, "main.main", 18, startHandle, 3, 3)
|
checkStackFramesExact(t, stResp, "main.main", 18, startHandle, 3, 3)
|
||||||
|
|
||||||
},
|
},
|
||||||
disconnect: false,
|
disconnect: false,
|
||||||
}})
|
}})
|
||||||
@ -2842,7 +2843,9 @@ func TestHitBreakpointIds(t *testing.T) {
|
|||||||
client.ContinueRequest(1)
|
client.ContinueRequest(1)
|
||||||
client.ExpectContinueResponse(t)
|
client.ExpectContinueResponse(t)
|
||||||
se = client.ExpectStoppedEvent(t)
|
se = client.ExpectStoppedEvent(t)
|
||||||
|
|
||||||
checkHitBreakpointIds(t, se, "function breakpoint", functionBps[1].Id)
|
checkHitBreakpointIds(t, se, "function breakpoint", functionBps[1].Id)
|
||||||
|
|
||||||
checkStop(t, client, 1, "main.anotherFunction", 27)
|
checkStop(t, client, 1, "main.anotherFunction", 27)
|
||||||
},
|
},
|
||||||
disconnect: true,
|
disconnect: true,
|
||||||
@ -3805,6 +3808,105 @@ func TestEvaluateRequest(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatConfig(depth int, showGlobals, showRegisters, hideSystemGoroutines bool, substitutePath [][2]string) string {
|
||||||
|
formatStr := `stackTraceDepth %d
|
||||||
|
showGlobalVariables %v
|
||||||
|
showRegisters %v
|
||||||
|
hideSystemGoroutines %v
|
||||||
|
substitutePath %v
|
||||||
|
`
|
||||||
|
return fmt.Sprintf(formatStr, depth, showGlobals, showRegisters, hideSystemGoroutines, substitutePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEvaluateCommandRequest(t *testing.T) {
|
||||||
|
runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) {
|
||||||
|
runDebugSessionWithBPs(t, client, "launch",
|
||||||
|
// Launch
|
||||||
|
func() {
|
||||||
|
client.LaunchRequest("exec", fixture.Path, !stopOnEntry)
|
||||||
|
},
|
||||||
|
fixture.Source, []int{}, // Breakpoint set in the program
|
||||||
|
[]onBreakpoint{{ // Stop at first breakpoint
|
||||||
|
execute: func() {
|
||||||
|
checkStop(t, client, 1, "main.foobar", 66)
|
||||||
|
|
||||||
|
// Request help.
|
||||||
|
const dlvHelp = `The following commands are available:
|
||||||
|
help (alias: h) Prints the help message.
|
||||||
|
config Changes configuration parameters.
|
||||||
|
|
||||||
|
Type help followed by a command for full documentation.
|
||||||
|
`
|
||||||
|
client.EvaluateRequest("dlv help", 1000, "repl")
|
||||||
|
got := client.ExpectEvaluateResponse(t)
|
||||||
|
checkEval(t, got, dlvHelp, noChildren)
|
||||||
|
|
||||||
|
client.EvaluateRequest("dlv help config", 1000, "repl")
|
||||||
|
got = client.ExpectEvaluateResponse(t)
|
||||||
|
checkEval(t, got, msgConfig, noChildren)
|
||||||
|
|
||||||
|
// Test config.
|
||||||
|
client.EvaluateRequest("dlv config -list", 1000, "repl")
|
||||||
|
got = client.ExpectEvaluateResponse(t)
|
||||||
|
checkEval(t, got, formatConfig(50, false, false, false, [][2]string{}), noChildren)
|
||||||
|
|
||||||
|
// Read and modify showGlobalVariables.
|
||||||
|
client.EvaluateRequest("dlv config showGlobalVariables", 1000, "repl")
|
||||||
|
got = client.ExpectEvaluateResponse(t)
|
||||||
|
checkEval(t, got, "showGlobalVariables\tfalse\n", noChildren)
|
||||||
|
|
||||||
|
client.ScopesRequest(1000)
|
||||||
|
scopes := client.ExpectScopesResponse(t)
|
||||||
|
if len(scopes.Body.Scopes) > 1 {
|
||||||
|
t.Errorf("\ngot %#v\nwant len(scopes)=1 (Locals)", scopes)
|
||||||
|
}
|
||||||
|
checkScope(t, scopes, 0, "Locals", -1)
|
||||||
|
|
||||||
|
client.EvaluateRequest("dlv config showGlobalVariables true", 1000, "repl")
|
||||||
|
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)
|
||||||
|
|
||||||
|
client.ScopesRequest(1000)
|
||||||
|
scopes = client.ExpectScopesResponse(t)
|
||||||
|
if len(scopes.Body.Scopes) < 2 {
|
||||||
|
t.Errorf("\ngot %#v\nwant len(scopes)=2 (Locals & Globals)", scopes)
|
||||||
|
}
|
||||||
|
checkScope(t, scopes, 0, "Locals", -1)
|
||||||
|
checkScope(t, scopes, 1, "Globals (package main)", -1)
|
||||||
|
|
||||||
|
// Read and modify substitutePath.
|
||||||
|
client.EvaluateRequest("dlv config substitutePath", 1000, "repl")
|
||||||
|
got = client.ExpectEvaluateResponse(t)
|
||||||
|
checkEval(t, got, "substitutePath\t[]\n", noChildren)
|
||||||
|
|
||||||
|
client.EvaluateRequest(fmt.Sprintf("dlv config substitutePath %q %q", "my/client/path", "your/server/path"), 1000, "repl")
|
||||||
|
got = client.ExpectEvaluateResponse(t)
|
||||||
|
checkEval(t, got, "substitutePath\t[[my/client/path your/server/path]]\n\nUpdated", noChildren)
|
||||||
|
|
||||||
|
client.EvaluateRequest(fmt.Sprintf("dlv config substitutePath %q %q", "my/client/path", "new/your/server/path"), 1000, "repl")
|
||||||
|
got = client.ExpectEvaluateResponse(t)
|
||||||
|
checkEval(t, got, "substitutePath\t[[my/client/path new/your/server/path]]\n\nUpdated", noChildren)
|
||||||
|
|
||||||
|
client.EvaluateRequest(fmt.Sprintf("dlv config substitutePath %q", "my/client/path"), 1000, "repl")
|
||||||
|
got = client.ExpectEvaluateResponse(t)
|
||||||
|
checkEval(t, got, "substitutePath\t[]\n\nUpdated", noChildren)
|
||||||
|
|
||||||
|
// Test bad inputs.
|
||||||
|
client.EvaluateRequest("dlv help bad", 1000, "repl")
|
||||||
|
client.ExpectErrorResponse(t)
|
||||||
|
|
||||||
|
client.EvaluateRequest("dlv bad", 1000, "repl")
|
||||||
|
client.ExpectErrorResponse(t)
|
||||||
|
},
|
||||||
|
disconnect: true,
|
||||||
|
}})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// From testvariables2 fixture
|
// From testvariables2 fixture
|
||||||
const (
|
const (
|
||||||
// As defined in the code
|
// As defined in the code
|
||||||
|
Loading…
Reference in New Issue
Block a user