From 58ea3234effba03c02e1d7ea3780cab3f2f56c9c Mon Sep 17 00:00:00 2001 From: polinasok <51177946+polinasok@users.noreply.github.com> Date: Tue, 11 Aug 2020 08:34:27 -0700 Subject: [PATCH] service/dap: Initial support for scopes and variables requests (#2111) * Initial support for scopes and variables requests * Add detailed variables test * Address review comments * Fix typo and redudant escaped characters * Bug fix for uninitialized interfaces; no refs needed for 0-size vars * Minor cosmetic tweaks * Add incomplete loading test * Make DeepSource happy * Remove unnecessary t.Helper() calls * Update broken test after merge * Add missing return * Rework test harness to abort testvariables2 before stack overflow * Remove accidentally duplicated disconnet * Test for invalid interface type with regex * Drop testvariables3, clean up and test unreadable case * Respond to review comments * Make expectVar test helper less fragile * Make DeepSource happy * Use proc.LoadConfig directly * Adjust test to avoid var count discrepency between Go 1.15 and earlier * Make compound keys in a map unique for correct display * Remove locals num check that will break if more vars are added Co-authored-by: Polina Sokolova --- _fixtures/testvariables2.go | 7 +- service/dap/daptest/client.go | 20 +- service/dap/error_ids.go | 4 + service/dap/server.go | 220 +++++++++++- service/dap/server_test.go | 623 ++++++++++++++++++++++++++++++++-- 5 files changed, 833 insertions(+), 41 deletions(-) diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go index 333d49a3..99caa7b3 100644 --- a/_fixtures/testvariables2.go +++ b/_fixtures/testvariables2.go @@ -114,6 +114,7 @@ func main() { p1 := &i1 s1 := []string{"one", "two", "three", "four", "five"} s3 := make([]int, 0, 6) + a0 := [0]int{} a1 := [5]string{"one", "two", "three", "four", "five"} c1 := cstruct{&bstruct{astruct{1, 2}}, []*astruct{&astruct{1, 2}, &astruct{2, 3}, &astruct{4, 5}}} s2 := []astruct{{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}, {11, 12}, {13, 14}, {15, 16}} @@ -202,6 +203,8 @@ func main() { var mnil map[string]astruct = nil m2 := map[int]*astruct{1: &astruct{10, 11}} m3 := map[astruct]int{{1, 1}: 42, {2, 2}: 43} + m4 := map[astruct]astruct{{1, 1}: {11, 11}, {2, 2}: {22, 22}} + upnil := unsafe.Pointer(nil) up1 := unsafe.Pointer(&i1) i4 := 800 i5 := -3 @@ -260,6 +263,7 @@ func main() { ni8 := int8(-5) ni16 := int16(-5) ni32 := int32(-5) + ni64 := int64(-5) pinf := math.Inf(+1) ninf := math.Inf(-1) @@ -306,6 +310,7 @@ func main() { } ll := &List{0, &List{1, &List{2, &List{3, &List{4, nil}}}}} + unread := (*int)(unsafe.Pointer(uintptr(12345))) var amb1 = 1 runtime.Breakpoint() @@ -314,5 +319,5 @@ func main() { } runtime.Breakpoint() - fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, errtypednil, emptyslice, emptymap, byteslice, runeslice, bytearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll) + fmt.Println(i1, i2, i3, p1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, errtypednil, emptyslice, emptymap, byteslice, runeslice, bytearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread) } diff --git a/service/dap/daptest/client.go b/service/dap/daptest/client.go index 6b8fce3e..31f512f2 100644 --- a/service/dap/daptest/client.go +++ b/service/dap/daptest/client.go @@ -147,6 +147,16 @@ func (c *Client) ExpectStackTraceResponse(t *testing.T) *dap.StackTraceResponse return c.expectReadProtocolMessage(t).(*dap.StackTraceResponse) } +func (c *Client) ExpectScopesResponse(t *testing.T) *dap.ScopesResponse { + t.Helper() + return c.expectReadProtocolMessage(t).(*dap.ScopesResponse) +} + +func (c *Client) ExpectVariablesResponse(t *testing.T) *dap.VariablesResponse { + t.Helper() + return c.expectReadProtocolMessage(t).(*dap.VariablesResponse) +} + func (c *Client) ExpectTerminateResponse(t *testing.T) *dap.TerminateResponse { t.Helper() return c.expectReadProtocolMessage(t).(*dap.TerminateResponse) @@ -377,14 +387,16 @@ func (c *Client) StackTraceRequest(threadID, startFrame, levels int) { } // ScopesRequest sends a 'scopes' request. -func (c *Client) ScopesRequest() { +func (c *Client) ScopesRequest(frameID int) { request := &dap.ScopesRequest{Request: *c.newRequest("scopes")} + request.Arguments.FrameId = frameID c.send(request) } -// VariablesRequest sends a 'scopes' request. -func (c *Client) VariablesRequest() { - request := &dap.ScopesRequest{Request: *c.newRequest("variables")} +// 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) } diff --git a/service/dap/error_ids.go b/service/dap/error_ids.go index 52a08b73..84903eb0 100644 --- a/service/dap/error_ids.go +++ b/service/dap/error_ids.go @@ -14,5 +14,9 @@ const ( FailedtoAttach = 3001 UnableToDisplayThreads = 2003 UnableToProduceStackTrace = 2004 + UnableToListLocals = 2005 + UnableToListArgs = 2006 + UnableToListGlobals = 2007 + UnableToLookupVariable = 2008 // Add more codes as we support more requests ) diff --git a/service/dap/server.go b/service/dap/server.go index cf2481d0..75b38e34 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -16,6 +16,7 @@ import ( "net" "os" "path/filepath" + "reflect" "github.com/go-delve/delve/pkg/gobuild" "github.com/go-delve/delve/pkg/logflags" @@ -54,8 +55,11 @@ type Server struct { log *logrus.Entry // binaryToRemove is the compiled binary to be removed on disconnect. binaryToRemove string - // stackFrameHandles keep track of unique ids created across all goroutines. + // stackFrameHandles maps frames of each goroutine to unique ids across all goroutines. stackFrameHandles *handlesMap + // variableHandles maps compound variables to unique references within their stack frame. + // See also comment for convertVariable. + variableHandles *handlesMap // args tracks special settings for handling debug session requests. args launchAttachArgs } @@ -89,6 +93,7 @@ func NewServer(config *service.Config) *Server { stopChan: make(chan struct{}), log: logger, stackFrameHandles: newHandlesMap(), + variableHandles: newHandlesMap(), args: defaultArgs, } } @@ -285,11 +290,9 @@ func (s *Server) handleRequest(request dap.Message) { s.onStackTraceRequest(request) case *dap.ScopesRequest: // Required - // TODO: implement this request in V0 s.onScopesRequest(request) case *dap.VariablesRequest: // Required - // TODO: implement this request in V0 s.onVariablesRequest(request) case *dap.SetVariableRequest: // Optional (capability ‘supportsSetVariable’) @@ -686,16 +689,214 @@ func (s *Server) onStackTraceRequest(request *dap.StackTraceRequest) { s.send(response) } -// onScopesRequest sends a not-yet-implemented error response. +// onScopesRequest handles 'scopes' requests. // This is a mandatory request to support. -func (s *Server) onScopesRequest(request *dap.ScopesRequest) { // TODO V0 - s.sendNotYetImplementedErrorResponse(request.Request) +func (s *Server) onScopesRequest(request *dap.ScopesRequest) { + sf, ok := s.stackFrameHandles.get(request.Arguments.FrameId) + if !ok { + s.sendErrorResponse(request.Request, UnableToListLocals, "Unable to list locals", fmt.Sprintf("unknown frame id %d", request.Arguments.FrameId)) + return + } + + scope := api.EvalScope{GoroutineID: sf.(stackFrame).goroutineID, Frame: sf.(stackFrame).frameIndex} + // TODO(polina): Support setting config via launch/attach args + cfg := proc.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1} + + // Retrieve arguments + args, err := s.debugger.FunctionArguments(scope, cfg) + if err != nil { + s.sendErrorResponse(request.Request, UnableToListArgs, "Unable to list args", err.Error()) + return + } + argScope := api.Variable{Name: "Arguments", Children: args} + + // Retrieve local variables + locals, err := s.debugger.LocalVariables(scope, cfg) + if err != nil { + s.sendErrorResponse(request.Request, UnableToListLocals, "Unable to list local vars", err.Error()) + return + } + locScope := api.Variable{Name: "Locals", Children: locals} + + // TODO(polina): Annotate shadowed variables + // TODO(polina): Retrieve global variables + + scopeArgs := dap.Scope{Name: argScope.Name, VariablesReference: s.variableHandles.create(argScope)} + scopeLocals := dap.Scope{Name: locScope.Name, VariablesReference: s.variableHandles.create(locScope)} + scopes := []dap.Scope{scopeArgs, scopeLocals} + + response := &dap.ScopesResponse{ + Response: *newResponse(request.Request), + Body: dap.ScopesResponseBody{Scopes: scopes}, + } + s.send(response) } -// onVariablesRequest sends a not-yet-implemented error response. +// onVariablesRequest handles 'variables' requests. // This is a mandatory request to support. -func (s *Server) onVariablesRequest(request *dap.VariablesRequest) { // TODO V0 - s.sendNotYetImplementedErrorResponse(request.Request) +func (s *Server) onVariablesRequest(request *dap.VariablesRequest) { + variable, ok := s.variableHandles.get(request.Arguments.VariablesReference) + if !ok { + s.sendErrorResponse(request.Request, UnableToLookupVariable, "Unable to lookup variable", fmt.Sprintf("unknown reference %d", request.Arguments.VariablesReference)) + return + } + v := variable.(api.Variable) + children := make([]dap.Variable, 0) + // TODO(polina): check and handle if variable loaded incompletely + // https://github.com/go-delve/delve/blob/master/Documentation/api/ClientHowto.md#looking-into-variables + + switch v.Kind { + case reflect.Map: + for i := 0; i < len(v.Children); i += 2 { + // A map will have twice as many children as there are key-value elements. + kvIndex := i / 2 + // Process children in pairs: even indices are map keys, odd indices are values. + key, keyref := s.convertVariable(v.Children[i]) + val, valref := s.convertVariable(v.Children[i+1]) + // If key or value or both are scalars, we can use + // a single variable to represet key:value format. + // Otherwise, we must return separate variables for both. + if keyref > 0 && valref > 0 { // Both are not scalars + keyvar := dap.Variable{ + Name: fmt.Sprintf("[key %d]", kvIndex), + Value: key, + VariablesReference: keyref, + } + valvar := dap.Variable{ + Name: fmt.Sprintf("[val %d]", kvIndex), + Value: val, + VariablesReference: valref, + } + children = append(children, keyvar, valvar) + } else { // At least one is a scalar + kvvar := dap.Variable{ + Name: key, + Value: val, + } + if keyref != 0 { // key is a type to be expanded + kvvar.Name = fmt.Sprintf("%s[%d]", kvvar.Name, kvIndex) // Make the name unique + kvvar.VariablesReference = keyref + } else if valref != 0 { // val is a type to be expanded + kvvar.VariablesReference = valref + } + children = append(children, kvvar) + } + } + case reflect.Slice, reflect.Array: + children = make([]dap.Variable, len(v.Children)) + for i, c := range v.Children { + value, varref := s.convertVariable(c) + children[i] = dap.Variable{ + Name: fmt.Sprintf("[%d]", i), + Value: value, + VariablesReference: varref, + } + } + default: + children = make([]dap.Variable, len(v.Children)) + for i, c := range v.Children { + value, variablesReference := s.convertVariable(c) + children[i] = dap.Variable{ + Name: c.Name, + Value: value, + VariablesReference: variablesReference, + } + } + } + response := &dap.VariablesResponse{ + Response: *newResponse(request.Request), + Body: dap.VariablesResponseBody{Variables: children}, + // TODO(polina): support evaluateName field + } + s.send(response) +} + +// convertVariable converts api.Variable to dap.Variable value and reference. +// Variable reference is used to keep track of the children associated with each +// variable. It is shared with the host via a scopes response and is an index to +// the s.variableHandles map, so it can be referenced from a subsequent variables +// request. A positive reference signals the host that another variables request +// can be issued to get the elements of the compound variable. As a custom, a zero +// reference, reminiscent of a zero pointer, is used to indicate that a scalar +// variable cannot be "dereferenced" to get its elements (as there are none). +func (s *Server) convertVariable(v api.Variable) (value string, variablesReference int) { + if v.Unreadable != "" { + value = fmt.Sprintf("unreadable <%s>", v.Unreadable) + return + } + switch v.Kind { + case reflect.UnsafePointer: + if len(v.Children) == 0 { + value = "unsafe.Pointer(nil)" + } else { + value = fmt.Sprintf("unsafe.Pointer(%#x)", v.Children[0].Addr) + } + case reflect.Ptr: + if v.Type == "" || len(v.Children) == 0 { + value = "nil" + } else if v.Children[0].Addr == 0 { + value = "nil <" + v.Type + ">" + } else if v.Children[0].Type == "void" { + value = "void" + } else { + value = fmt.Sprintf("<%s>(%#x)", v.Type, v.Children[0].Addr) + variablesReference = s.variableHandles.create(v) + } + case reflect.Array: + value = "<" + v.Type + ">" + if len(v.Children) > 0 { + variablesReference = s.variableHandles.create(v) + } + case reflect.Slice: + if v.Base == 0 { + value = "nil <" + v.Type + ">" + } else { + value = fmt.Sprintf("<%s> (length: %d, cap: %d)", v.Type, v.Len, v.Cap) + if len(v.Children) > 0 { + variablesReference = s.variableHandles.create(v) + } + } + case reflect.Map: + if v.Base == 0 { + value = "nil <" + v.Type + ">" + } else { + value = fmt.Sprintf("<%s> (length: %d)", v.Type, v.Len) + if len(v.Children) > 0 { + variablesReference = s.variableHandles.create(v) + } + } + case reflect.String: + lenNotLoaded := v.Len - int64(len(v.Value)) + vvalue := v.Value + if lenNotLoaded > 0 { + vvalue += fmt.Sprintf("...+%d more", lenNotLoaded) + } + value = fmt.Sprintf("%q", vvalue) + case reflect.Chan: + if len(v.Children) == 0 { + value = "nil <" + v.Type + ">" + } else { + value = "<" + v.Type + ">" + variablesReference = s.variableHandles.create(v) + } + case reflect.Interface: + if len(v.Children) == 0 || v.Children[0].Kind == reflect.Invalid && v.Children[0].Addr == 0 { + value = "nil <" + v.Type + ">" + } else { + value = "<" + v.Type + ">" + variablesReference = s.variableHandles.create(v) + } + default: // Struct, complex, scalar + if v.Value != "" { + value = v.Value + } else { + value = "<" + v.Type + ">" + } + if len(v.Children) > 0 { + variablesReference = s.variableHandles.create(v) + } + } + return } // onEvaluateRequest sends a not-yet-implemented error response. @@ -846,6 +1047,7 @@ func (s *Server) doContinue() { return } s.stackFrameHandles.reset() + s.variableHandles.reset() if state.Exited { e := &dap.TerminatedEvent{Event: *newEvent("terminated")} s.send(e) diff --git a/service/dap/server_test.go b/service/dap/server_test.go index 4223cc6b..2135cf96 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "reflect" + "regexp" "strings" "sync" "testing" @@ -21,6 +22,8 @@ import ( ) const stopOnEntry bool = true +const hasChildren bool = true +const noChildren bool = false func TestMain(m *testing.M) { var logOutput string @@ -332,8 +335,20 @@ func TestSetBreakpoint(t *testing.T) { expectFrame(stResp.Body.StackFrames[5], 1005, "runtime.goexit", "", -1) } - // TODO(polina): add other status checking requests - // that are not yet supported (scopes, variables) + client.ScopesRequest(1000) + scopes := client.ExpectScopesResponse(t) + expectScope(t, scopes, 0, "Arguments", 1000) + expectScope(t, scopes, 1, "Locals", 1001) + + client.VariablesRequest(1000) // Arguments + args := client.ExpectVariablesResponse(t) + expectChildren(t, args, "Arguments", 2) + expectVarExact(t, args, 0, "y", "0", noChildren) + expectVarExact(t, args, 1, "~r1", "0", noChildren) + + client.VariablesRequest(1001) // Locals + locals := client.ExpectVariablesResponse(t) + expectChildren(t, locals, "Locals", 0) client.ContinueRequest(1) client.ExpectContinueResponse(t) @@ -375,6 +390,85 @@ func expectStackFrames(t *testing.T, got *dap.StackTraceResponse, } } +// expectScope is a helper for verifying the values within a ScopesResponse. +// i - index of the scope within ScopesRespose.Body.Scopes array +// name - name of the scope +// varRef - reference to retrieve variables of this scope +func expectScope(t *testing.T, got *dap.ScopesResponse, i int, name string, varRef int) { + t.Helper() + if len(got.Body.Scopes) <= i { + t.Errorf("\ngot %d\nwant len(Scopes)>%d", len(got.Body.Scopes), i) + } + goti := got.Body.Scopes[i] + if goti.Name != name || goti.VariablesReference != varRef || goti.Expensive { + t.Errorf("\ngot %#v\nwant Name=%q VariablesReference=%d Expensive=false", goti, name, varRef) + } +} + +// expectChildren is a helper for verifying the number of variables within a VariablesResponse. +// parentName - name of the enclosing variable or scope +// numChildren - number of variables/fields/elements of this variable +func expectChildren(t *testing.T, got *dap.VariablesResponse, parentName string, numChildren int) { + t.Helper() + if len(got.Body.Variables) != numChildren { + t.Errorf("\ngot len(%s)=%d\nwant %d", parentName, len(got.Body.Variables), numChildren) + } +} + +// expectVar is a helper for verifying the values within a VariablesResponse. +// i - index of the variable within VariablesRespose.Body.Variables array (-1 will search all vars for a match) +// name - name of the variable +// value - the value of the variable +// useExactMatch - true if value is to be compared to exactly, false if to be used as regex +// hasRef - true if the variable should have children and therefore a non-0 variable reference +// ref - reference to retrieve children of this variable (0 if none) +func expectVar(t *testing.T, got *dap.VariablesResponse, i int, name, value string, useExactMatch, hasRef bool) (ref int) { + t.Helper() + if len(got.Body.Variables) <= i { + t.Errorf("\ngot len=%d\nwant len>%d", len(got.Body.Variables), i) + return + } + if i < 0 { + for vi, v := range got.Body.Variables { + if v.Name == name { + i = vi + break + } + } + } + if i < 0 { + t.Errorf("\ngot %#v\nwant Variables[i].Name=%q", got, name) + return 0 + } + + goti := got.Body.Variables[i] + if goti.Name != name || (goti.VariablesReference > 0) != hasRef { + t.Errorf("\ngot %#v\nwant Name=%q hasRef=%t", goti, name, hasRef) + } + matched := false + if useExactMatch { + matched = (goti.Value == value) + } else { + matched, _ = regexp.MatchString(value, goti.Value) + } + if !matched { + t.Errorf("\ngot %s=%q\nwant %q", name, goti.Value, value) + } + return goti.VariablesReference +} + +// expectVarExact is a helper like expectVar that matches value exactly. +func expectVarExact(t *testing.T, got *dap.VariablesResponse, i int, name, value string, hasRef bool) (ref int) { + t.Helper() + return expectVar(t, got, i, name, value, true, hasRef) +} + +// expectVarRegex is a helper like expectVar that treats value as a regex. +func expectVarRegex(t *testing.T, got *dap.VariablesResponse, i int, name, value string, hasRef bool) (ref int) { + t.Helper() + return expectVar(t, got, i, name, value, false, hasRef) +} + // TestStackTraceRequest executes to a breakpoint (similarly to TestSetBreakpoint // that includes more thorough checking of that sequence) and tests different // good and bad configurations of 'stackTrace' requests. @@ -388,10 +482,9 @@ func TestStackTraceRequest(t *testing.T) { }, // Set breakpoints fixture.Source, []int{8, 18}, - []func(){ + []onBreakpoint{{ // Stop at line 8 - func() { - t.Helper() + execute: func() { client.StackTraceRequest(1, 0, 0) stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, 8, 1000, 6, 6) @@ -415,9 +508,10 @@ func TestStackTraceRequest(t *testing.T) { stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, -1, -1, 0, 6) }, + disconnect: false, + }, { // Stop at line 18 - func() { - t.Helper() + execute: func() { // Frame ids get reset at each breakpoint. client.StackTraceRequest(1, 0, 0) stResp = client.ExpectStackTraceResponse(t) @@ -442,7 +536,471 @@ func TestStackTraceRequest(t *testing.T) { client.StackTraceRequest(1, 1, 2) stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, -1, 1016, 2, 3) // Don't test for runtime line we don't control - }}) + }, + disconnect: false, + }}) + }) +} + +// TestScopesAndVariablesRequests executes to a breakpoint and tests different +// configurations of 'scopes' and 'variables' requests. +func TestScopesAndVariablesRequests(t *testing.T) { + runTest(t, "testvariables", func(client *daptest.Client, fixture protest.Fixture) { + runDebugSessionWithBPs(t, client, + // Launch + func() { + client.LaunchRequest("exec", fixture.Path, !stopOnEntry) + }, + // Breakpoints are set within the program + fixture.Source, []int{}, + []onBreakpoint{{ + // Stop at line 62 + execute: func() { + client.StackTraceRequest(1, 0, 20) + stack := client.ExpectStackTraceResponse(t) + expectStackFrames(t, stack, 62, 1000, 4, 4) + + client.ScopesRequest(1000) + scopes := client.ExpectScopesResponse(t) + expectScope(t, scopes, 0, "Arguments", 1000) + expectScope(t, scopes, 1, "Locals", 1001) + + // Arguments + + client.VariablesRequest(1000) + args := client.ExpectVariablesResponse(t) + expectChildren(t, args, "Arguments", 2) + expectVarExact(t, args, 0, "baz", `"bazburzum"`, noChildren) + ref := expectVarExact(t, args, 1, "bar", ``, hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + bar := client.ExpectVariablesResponse(t) + expectChildren(t, bar, "bar", 2) + expectVarExact(t, bar, 0, "Baz", "10", noChildren) + expectVarExact(t, bar, 1, "Bur", `"lorem"`, noChildren) + } + + // Locals + + client.VariablesRequest(1001) + locals := client.ExpectVariablesResponse(t) + expectChildren(t, locals, "Locals", 29) + + // reflect.Kind == Bool + expectVarExact(t, locals, -1, "b1", "true", noChildren) + expectVarExact(t, locals, -1, "b2", "false", noChildren) + // reflect.Kind == Int + expectVarExact(t, locals, -1, "a2", "6", noChildren) + expectVarExact(t, locals, -1, "neg", "-1", noChildren) + // reflect.Kind == Int8 + expectVarExact(t, locals, -1, "i8", "1", noChildren) + // reflect.Kind == Int16 - see testvariables2 + // reflect.Kind == Int32 - see testvariables2 + // reflect.Kind == Int64 - see testvariables2 + // reflect.Kind == Uint + // reflect.Kind == Uint8 + expectVarExact(t, locals, -1, "u8", "255", noChildren) + // reflect.Kind == Uint16 + expectVarExact(t, locals, -1, "u16", "65535", noChildren) + // reflect.Kind == Uint32 + expectVarExact(t, locals, -1, "u32", "4294967295", noChildren) + // reflect.Kind == Uint64 + expectVarExact(t, locals, -1, "u64", "18446744073709551615", noChildren) + // reflect.Kind == Uintptr + expectVarExact(t, locals, -1, "up", "5", noChildren) + // reflect.Kind == Float32 + expectVarExact(t, locals, -1, "f32", "1.2", noChildren) + // reflect.Kind == Float64 + expectVarExact(t, locals, -1, "a3", "7.23", noChildren) + // reflect.Kind == Complex64 + ref = expectVarExact(t, locals, -1, "c64", "(1 + 2i)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + c64 := client.ExpectVariablesResponse(t) + expectChildren(t, c64, "c64", 2) + expectVarExact(t, c64, 0, "real", "1", noChildren) + expectVarExact(t, c64, 1, "imaginary", "2", noChildren) + } + // reflect.Kind == Complex128 + ref = expectVarExact(t, locals, -1, "c128", "(2 + 3i)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + c128 := client.ExpectVariablesResponse(t) + expectChildren(t, c128, "c128", 2) + expectVarExact(t, c128, 0, "real", "2", noChildren) + expectVarExact(t, c128, 1, "imaginary", "3", noChildren) + } + // reflect.Kind == Array + ref = expectVarExact(t, locals, -1, "a4", "<[2]int>", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + a4 := client.ExpectVariablesResponse(t) + expectChildren(t, a4, "a4", 2) + expectVarExact(t, a4, 0, "[0]", "1", noChildren) + expectVarExact(t, a4, 1, "[1]", "2", noChildren) + } + ref = expectVarExact(t, locals, -1, "a11", "<[3]main.FooBar>", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + a11 := client.ExpectVariablesResponse(t) + expectChildren(t, a11, "a11", 3) + expectVarExact(t, a11, 0, "[0]", "", hasChildren) + ref = expectVarExact(t, a11, 1, "[1]", "", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + a11_1 := client.ExpectVariablesResponse(t) + expectChildren(t, a11_1, "a11[1]", 2) + expectVarExact(t, a11_1, 0, "Baz", "2", noChildren) + expectVarExact(t, a11_1, 1, "Bur", `"b"`, noChildren) + + } + expectVarExact(t, a11, 2, "[2]", "", hasChildren) + } + + // reflect.Kind == Chan - see testvariables2 + // reflect.Kind == Func - see testvariables2 + // reflect.Kind == Interface - see testvariables2 + // reflect.Kind == Map - see testvariables2 + // reflect.Kind == Ptr + ref = expectVarRegex(t, locals, -1, "a7", "<\\*main\\.FooBar>\\(0x[0-9a-f]+\\)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + a7 := client.ExpectVariablesResponse(t) + expectChildren(t, a7, "a7", 1) + ref = expectVarExact(t, a7, 0, "", "", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + a7val := client.ExpectVariablesResponse(t) + expectChildren(t, a7val, "*a7", 2) + expectVarExact(t, a7val, 0, "Baz", "5", noChildren) + expectVarExact(t, a7val, 1, "Bur", `"strum"`, noChildren) + } + } + // TODO(polina): how to test for "nil" (without type) and "void"? + expectVarExact(t, locals, -1, "a9", "nil <*main.FooBar>", noChildren) + // reflect.Kind == Slice + ref = expectVarExact(t, locals, -1, "a5", "<[]int> (length: 5, cap: 5)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + a5 := client.ExpectVariablesResponse(t) + expectChildren(t, a5, "a5", 5) + expectVarExact(t, a5, 0, "[0]", "1", noChildren) + expectVarExact(t, a5, 4, "[4]", "5", noChildren) + } + ref = expectVarExact(t, locals, -1, "a12", "<[]main.FooBar> (length: 2, cap: 2)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + a12 := client.ExpectVariablesResponse(t) + expectChildren(t, a12, "a12", 2) + expectVarExact(t, a12, 0, "[0]", "", hasChildren) + ref = expectVarExact(t, a12, 1, "[1]", "", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + a12_1 := client.ExpectVariablesResponse(t) + expectChildren(t, a12_1, "a12[1]", 2) + expectVarExact(t, a12_1, 0, "Baz", "5", noChildren) + expectVarExact(t, a12_1, 1, "Bur", `"e"`, noChildren) + } + } + ref = expectVarExact(t, locals, -1, "a13", "<[]*main.FooBar> (length: 3, cap: 3)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + a13 := client.ExpectVariablesResponse(t) + expectChildren(t, a13, "a13", 3) + expectVarRegex(t, a13, 0, "[0]", "<\\*main\\.FooBar>\\(0x[0-9a-f]+\\)", hasChildren) + expectVarRegex(t, a13, 1, "[1]", "<\\*main\\.FooBar>\\(0x[0-9a-f]+\\)", hasChildren) + ref = expectVarRegex(t, a13, 2, "[2]", "<\\*main\\.FooBar>\\(0x[0-9a-f]+\\)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + a13_2 := client.ExpectVariablesResponse(t) + expectChildren(t, a13_2, "a13[2]", 1) + ref = expectVarExact(t, a13_2, 0, "", "", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + val := client.ExpectVariablesResponse(t) + expectChildren(t, val, "*a13[2]", 2) + expectVarExact(t, val, 0, "Baz", "8", noChildren) + expectVarExact(t, val, 1, "Bur", `"h"`, noChildren) + } + } + } + // reflect.Kind == String + expectVarExact(t, locals, -1, "a1", `"foofoofoofoofoofoo"`, noChildren) + expectVarExact(t, locals, -1, "a10", `"ofo"`, noChildren) + // reflect.Kind == Struct + ref = expectVarExact(t, locals, -1, "a6", "", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + a6 := client.ExpectVariablesResponse(t) + expectChildren(t, a6, "a6", 2) + expectVarExact(t, a6, 0, "Baz", "8", noChildren) + expectVarExact(t, a6, 1, "Bur", `"word"`, noChildren) + } + ref = expectVarExact(t, locals, -1, "a8", "", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + a8 := client.ExpectVariablesResponse(t) + expectChildren(t, a8, "a8", 2) + expectVarExact(t, a8, 0, "Bur", "10", noChildren) + expectVarExact(t, a8, 1, "Baz", `"feh"`, noChildren) + } + // reflect.Kind == UnsafePointer - see testvariables2 + }, + disconnect: false, + }, { + // Stop at line 25 + execute: func() { + // Frame ids get reset at each breakpoint. + client.StackTraceRequest(1, 0, 20) + stack := client.ExpectStackTraceResponse(t) + expectStackFrames(t, stack, 25, 1000, 5, 5) + + client.ScopesRequest(1000) + scopes := client.ExpectScopesResponse(t) + expectScope(t, scopes, 0, "Arguments", 1000) + expectScope(t, scopes, 1, "Locals", 1001) + + client.ScopesRequest(1111) + erres := client.ExpectErrorResponse(t) + if erres.Body.Error.Format != "Unable to list locals: unknown frame id 1111" { + t.Errorf("\ngot %#v\nwant Format=\"Unable to list locals: unknown frame id 1111\"", erres) + } + + client.VariablesRequest(1000) // Arguments + args := client.ExpectVariablesResponse(t) + expectChildren(t, args, "Arguments", 0) + + client.VariablesRequest(1001) // Locals + locals := client.ExpectVariablesResponse(t) + expectChildren(t, locals, "Locals", 1) + expectVarExact(t, locals, -1, "a1", `"bur"`, noChildren) + + client.VariablesRequest(7777) + erres = client.ExpectErrorResponse(t) + if erres.Body.Error.Format != "Unable to lookup variable: unknown reference 7777" { + t.Errorf("\ngot %#v\nwant Format=\"Unable to lookup variable: unknown reference 7777\"", erres) + } + }, + disconnect: false, + }}) + }) +} + +// TestScopesAndVariablesRequests2 executes to a breakpoint and tests different +// configurations of 'scopes' and 'variables' requests. +func TestScopesAndVariablesRequests2(t *testing.T) { + runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) { + runDebugSessionWithBPs(t, client, + // Launch + func() { + client.LaunchRequest("exec", fixture.Path, !stopOnEntry) + }, + // Breakpoints are set within the program + fixture.Source, []int{}, + []onBreakpoint{{ + // Stop at line 317 + execute: func() { + client.StackTraceRequest(1, 0, 20) + stack := client.ExpectStackTraceResponse(t) + expectStackFrames(t, stack, 317, 1000, 3, 3) + + client.ScopesRequest(1000) + scopes := client.ExpectScopesResponse(t) + expectScope(t, scopes, 0, "Arguments", 1000) + expectScope(t, scopes, 1, "Locals", 1001) + }, + disconnect: false, + }, { + // Stop at line 322 + execute: func() { + client.StackTraceRequest(1, 0, 20) + stack := client.ExpectStackTraceResponse(t) + expectStackFrames(t, stack, 322, 1000, 3, 3) + + client.ScopesRequest(1000) + scopes := client.ExpectScopesResponse(t) + expectScope(t, scopes, 0, "Arguments", 1000) + expectScope(t, scopes, 1, "Locals", 1001) + + // Arguments + + client.VariablesRequest(1000) + args := client.ExpectVariablesResponse(t) + expectChildren(t, args, "Arguments", 0) + + // Locals + + client.VariablesRequest(1001) + locals := client.ExpectVariablesResponse(t) + + // reflect.Kind == Bool - see testvariables + // reflect.Kind == Int - see testvariables + // reflect.Kind == Int8 + expectVarExact(t, locals, -1, "ni8", "-5", noChildren) + // reflect.Kind == Int16 + expectVarExact(t, locals, -1, "ni16", "-5", noChildren) + // reflect.Kind == Int32 + expectVarExact(t, locals, -1, "ni32", "-5", noChildren) + // reflect.Kind == Int64 + expectVarExact(t, locals, -1, "ni64", "-5", noChildren) + // reflect.Kind == Uint + // reflect.Kind == Uint8 - see testvariables + // reflect.Kind == Uint16 - see testvariables + // reflect.Kind == Uint32 - see testvariables + // reflect.Kind == Uint64 - see testvariables + // reflect.Kind == Uintptr - see testvariables + // reflect.Kind == Float32 - see testvariables + // reflect.Kind == Float64 + expectVarExact(t, locals, -1, "pinf", "+Inf", noChildren) + expectVarExact(t, locals, -1, "ninf", "-Inf", noChildren) + expectVarExact(t, locals, -1, "nan", "NaN", noChildren) + // reflect.Kind == Complex64 - see testvariables + // reflect.Kind == Complex128 - see testvariables + // reflect.Kind == Array + expectVarExact(t, locals, -1, "a0", "<[0]int>", noChildren) + // reflect.Kind == Chan + ref := expectVarExact(t, locals, -1, "ch1", "", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + ch1 := client.ExpectVariablesResponse(t) + expectChildren(t, ch1, "ch1", 11) + expectVarExact(t, ch1, 0, "qcount", "4", noChildren) + expectVarExact(t, ch1, 10, "lock", "", hasChildren) + } + expectVarExact(t, locals, -1, "chnil", "nil ", noChildren) + // reflect.Kind == Func + expectVarExact(t, locals, -1, "fn1", "main.afunc", noChildren) + expectVarExact(t, locals, -1, "fn2", "", noChildren) + // reflect.Kind == Interface + expectVarExact(t, locals, -1, "ifacenil", "nil ", noChildren) + ref = expectVarExact(t, locals, -1, "iface2", "", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + iface2 := client.ExpectVariablesResponse(t) + expectChildren(t, iface2, "iface2", 1) + expectVarExact(t, iface2, 0, "data", `"test"`, noChildren) + } + ref = expectVarExact(t, locals, -1, "iface4", "", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + iface4 := client.ExpectVariablesResponse(t) + expectChildren(t, iface4, "iface4", 1) + ref = expectVarExact(t, iface4, 0, "data", "<[]go/constant.Value> (length: 1, cap: 1)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + iface4data := client.ExpectVariablesResponse(t) + expectChildren(t, iface4data, "iface4.data", 1) + expectVarExact(t, iface4data, 0, "[0]", "", hasChildren) + + } + } + // reflect.Kind == Map + expectVarExact(t, locals, -1, "mnil", "nil ", noChildren) + ref = expectVarExact(t, locals, -1, "m2", " (length: 1)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + m2 := client.ExpectVariablesResponse(t) + expectChildren(t, m2, "m2", 1) + ref = expectVarRegex(t, m2, 0, "1", "<\\*main\\.astruct>\\(0x[0-9a-f]+\\)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + m2_1 := client.ExpectVariablesResponse(t) + expectChildren(t, m2_1, "m2[1]", 1) + ref = expectVarExact(t, m2_1, 0, "", "", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + m2_1val := client.ExpectVariablesResponse(t) + expectChildren(t, m2_1val, "*m2[1]", 2) + expectVarExact(t, m2_1val, 0, "A", "10", noChildren) + expectVarExact(t, m2_1val, 1, "B", "11", noChildren) + } + } + } + ref = expectVarExact(t, locals, -1, "m3", " (length: 2)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + m3 := client.ExpectVariablesResponse(t) + expectChildren(t, m3, "m3", 2) + ref = expectVarExact(t, m3, 0, "[0]", "42", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + m3_0 := client.ExpectVariablesResponse(t) + expectChildren(t, m3_0, "m3[0]", 2) + expectVarExact(t, m3_0, 0, "A", "1", noChildren) + expectVarExact(t, m3_0, 1, "B", "1", noChildren) + } + ref = expectVarExact(t, m3, 1, "[1]", "43", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + m3_1 := client.ExpectVariablesResponse(t) + expectChildren(t, m3_1, "m3[1]", 2) + expectVarExact(t, m3_1, 0, "A", "2", noChildren) + expectVarExact(t, m3_1, 1, "B", "2", noChildren) + } + } + ref = expectVarExact(t, locals, -1, "m4", " (length: 2)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + m4 := client.ExpectVariablesResponse(t) + expectChildren(t, m4, "m4", 4) + expectVarExact(t, m4, 0, "[key 0]", "", hasChildren) + expectVarExact(t, m4, 1, "[val 0]", "", hasChildren) + ref = expectVarExact(t, m4, 2, "[key 1]", "", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + m4_key1 := client.ExpectVariablesResponse(t) + expectChildren(t, m4_key1, "m4_key1", 2) + expectVarExact(t, m4_key1, 0, "A", "2", noChildren) + expectVarExact(t, m4_key1, 1, "B", "2", noChildren) + } + ref = expectVarExact(t, m4, 3, "[val 1]", "", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + m4_val1 := client.ExpectVariablesResponse(t) + expectChildren(t, m4_val1, "m4_val1", 2) + expectVarExact(t, m4_val1, 0, "A", "22", noChildren) + expectVarExact(t, m4_val1, 1, "B", "22", noChildren) + } + } + expectVarExact(t, locals, -1, "emptymap", " (length: 0)", noChildren) + // reflect.Kind == Ptr - see testvariables + // reflect.Kind == Slice + ref = expectVarExact(t, locals, -1, "zsslice", "<[]struct {}> (length: 3, cap: 3)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + zsslice := client.ExpectVariablesResponse(t) + expectChildren(t, zsslice, "zsslice", 3) + } + expectVarExact(t, locals, -1, "emptyslice", "<[]string> (length: 0, cap: 0)", noChildren) + expectVarExact(t, locals, -1, "nilslice", "nil <[]int>", noChildren) + // reflect.Kind == String + expectVarExact(t, locals, -1, "longstr", "\"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more\"", noChildren) + // reflect.Kind == Struct + expectVarExact(t, locals, -1, "zsvar", "", noChildren) + // reflect.Kind == UnsafePointer + // TODO(polina): how do I test for unsafe.Pointer(nil)? + expectVarRegex(t, locals, -1, "upnil", "unsafe\\.Pointer\\(0x0\\)", noChildren) + expectVarRegex(t, locals, -1, "up1", "unsafe\\.Pointer\\(0x[0-9a-f]+\\)", noChildren) + + // Test unreadable variable + ref = expectVarExact(t, locals, -1, "unread", "<*int>(0x3039)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + val := client.ExpectVariablesResponse(t) + expectChildren(t, val, "*unread", 1) + expectVarRegex(t, val, 0, "", "unreadable <.+>", noChildren) + } + + // Test that variables are not yet loaded completely. + ref = expectVarExact(t, locals, -1, "m1", " (length: 66)", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + m1 := client.ExpectVariablesResponse(t) + expectChildren(t, m1, "m1", 64) // TODO(polina): should be 66. + } + }, + disconnect: true, + }}) }) } @@ -460,29 +1018,37 @@ func TestLaunchRequestWithStackTraceDepth(t *testing.T) { }, // Set breakpoints fixture.Source, []int{8}, - []func(){ - // Stop at line 8 - func() { - t.Helper() + []onBreakpoint{{ // Stop at line 8 + execute: func() { client.StackTraceRequest(1, 0, 0) stResp = client.ExpectStackTraceResponse(t) expectStackFrames(t, stResp, 8, 1000, 2, 2) - }}) + }, + disconnect: false, + }}) }) } -// runDebugSesionWithBPs is a helper for executing the common init and shutdown +// onBreakpoint specifies what the test harness should simulate at +// a stopped breakpoint. First execute() is to be called to test +// specified editor-driven or user-driven requests. Then if +// disconnect is true, the test harness will abort the program +// execution. Otherwise, a continue will be issued and the +// program will continue to the next breakpoint or termination. +type onBreakpoint struct { + execute func() + disconnect bool +} + +// runDebugSessionWithBPs is a helper for executing the common init and shutdown // sequences for a program that does not stop on entry // while specifying breakpoints and unique launch criteria via parameters. // launchRequest - a function that sends a launch request, so the test author // has full control of its arguments. Note that he rest of // the test sequence assumes that stopOneEntry is false. // breakpoints - list of lines, where breakpoints are to be set -// onBreakpoints - list of functions to be called at each of the above breakpoints. -// These can be used to simulate additional editor-driven or -// user-driven requests that could occur at each stopped breakpoint. -func runDebugSessionWithBPs(t *testing.T, client *daptest.Client, launchRequest func(), source string, breakpoints []int, onBreakpoints []func()) { - t.Helper() +// onBreakpoints - list of test sequences to execute at each of the set breakpoints. +func runDebugSessionWithBPs(t *testing.T, client *daptest.Client, launchRequest func(), source string, breakpoints []int, onBPs []onBreakpoint) { client.InitializeRequest() client.ExpectInitializeResponse(t) @@ -500,9 +1066,18 @@ func runDebugSessionWithBPs(t *testing.T, client *daptest.Client, launchRequest // Program automatically continues to breakpoint or completion - for _, onBP := range onBreakpoints { + // TODO(polina): See if we can make this more like withTestProcessArgs in proc_test: + // a single function pointer gets called here and then if it wants to continue it calls + // client.ContinueRequest/client.ExpectContinueResponse/client.ExpectStoppedEvent + // (possibly using a helper function). + for _, onBP := range onBPs { client.ExpectStoppedEvent(t) - onBP() + onBP.execute() + if onBP.disconnect { + client.DisconnectRequest() + client.ExpectDisconnectResponse(t) + return + } client.ContinueRequest(1) client.ExpectContinueResponse(t) // "Continue" is triggered after the response is sent @@ -654,12 +1229,6 @@ func TestRequiredNotYetImplementedResponses(t *testing.T) { client.PauseRequest() expectNotYetImplemented("pause") - client.ScopesRequest() - expectNotYetImplemented("scopes") - - client.VariablesRequest() - expectNotYetImplemented("variables") - client.EvaluateRequest() expectNotYetImplemented("evaluate") })