From 69485a639d36eee0fd2ce6b0ab33fd02fe36c458 Mon Sep 17 00:00:00 2001 From: polinasok <51177946+polinasok@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:27:38 -0700 Subject: [PATCH] service/dap: separate tests for var value truncation and str loading limits (#2539) Co-authored-by: Polina Sokolova --- _fixtures/testvariables2.go | 6 +- service/dap/server_test.go | 138 ++++++++++++++++++++++++++---------- 2 files changed, 103 insertions(+), 41 deletions(-) diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go index d59cbebc..a8a4ced7 100644 --- a/_fixtures/testvariables2.go +++ b/_fixtures/testvariables2.go @@ -328,7 +328,9 @@ func main() { longstr := "very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789" m5 := map[C]int{{longstr}: 1} - m6 := C{s: longstr} + m6 := map[string]int{longstr: 123} + m7 := map[C]C{{longstr}: {longstr}} + cl := C{s: longstr} var nilstruct *astruct = nil val := A{val: 1} // val vs val.val @@ -363,5 +365,5 @@ func main() { longslice := make([]int, 100, 100) runtime.Breakpoint() - fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, 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, rettm, errtypednil, emptyslice, emptymap, byteslice, runeslice, bytearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6) + fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, 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, rettm, errtypednil, emptyslice, emptymap, byteslice, runeslice, bytearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl) } diff --git a/service/dap/server_test.go b/service/dap/server_test.go index 45e08f91..e010be34 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -1322,14 +1322,6 @@ func TestScopesAndVariablesRequests2(t *testing.T) { validateEvaluateName(t, client, m3kv1, 1) } } - // key - compound + truncated, value - scalar - ref = checkVarExact(t, locals, -1, "m5", "m5", `map[main.C]int [{s: "very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"}: 1, ]`, "map[main.C]int", hasChildren) - if ref > 0 { - client.VariablesRequest(ref) - m5 := client.ExpectVariablesResponse(t) - checkChildren(t, m5, "m5", 1) - checkVarRegex(t, m5, 0, `main\.C {s: "very long string 0123456789a0123456789b0123456789c01\.\.\. @ 0x[0-9a-f]+`, `m5\[\(\*\(\*"main\.C"\)\(0x[0-9a-f]+\)\)\]`, "1", `int`, hasChildren) - } // key - compound, value - compound ref = checkVarExact(t, locals, -1, "m4", "m4", "map[main.astruct]main.astruct [{A: 1, B: 1}: {A: 11, B: 11}, {A: 2, B: 2}: {A: 22, B: 22}, ]", "map[main.astruct]main.astruct", hasChildren) if ref > 0 { @@ -2832,7 +2824,94 @@ func TestEvaluateRequest(t *testing.T) { }) } -func TestEvaluateRequestLongStrLargeValue(t *testing.T) { +// From testvariables2 fixture +const ( + // As defined in the code + longstrFull = `"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"` + // Loaded with MaxStringLen=64 + longstrLoaded64 = `"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"` + longstrLoaded64re = `\"very long string 0123456789a0123456789b0123456789c0123456789d012\.\.\.\+73 more\"` +) + +// TestVariableValueTruncation tests that in certain cases +// we truncate the loaded variable values to make display more user-friendly. +func TestVariableValueTruncation(t *testing.T) { + runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) { + runDebugSessionWithBPs(t, client, "launch", + // Launch + func() { + client.LaunchRequest("exec", fixture.Path, !stopOnEntry) + }, + // Breakpoint set within the program + fixture.Source, []int{}, + []onBreakpoint{{ + execute: func() { + checkStop(t, client, 1, "main.main", -1) + + client.VariablesRequest(1001) // Locals + locals := client.ExpectVariablesResponse(t) + + // Compound variable values may be truncated + m1Full := `map\[string\]main\.astruct \[(\"[A-Za-z]+\": {A: [0-9]+, B: [0-9]+}, )+,\.\.\.\+2 more\]` + m1Part := `map\[string\]main\.astruct \[(\"[A-Za-z]+\": {A: [0-9]+, B: [0-9]+}, )+.+\.\.\.` + + // In variable responses + checkVarRegex(t, locals, -1, "m1", "m1", m1Part, `map\[string\]main\.astruct`, hasChildren) + + // In evaluate responses (select contexts only) + tests := []struct { + context string + want string + }{ + {"", m1Part}, + {"watch", m1Part}, + {"repl", m1Part}, + {"hover", m1Part}, + {"variables", m1Full}, // used for copy + {"clipboard", m1Full}, // used for copy + {"somethingelse", m1Part}, + } + for _, tc := range tests { + t.Run(tc.context, func(t *testing.T) { + client.EvaluateRequest("m1", 0, tc.context) + checkEvalRegex(t, client.ExpectEvaluateResponse(t), tc.want, hasChildren) + }) + } + + // Compound map keys may be truncated even further + // As the keys are always inside of a map container, + // this applies to variable requests. + + // key - compound, value - scalar (inlined key:value display) => truncate key if too long + ref := checkVarExact(t, locals, -1, "m5", "m5", "map[main.C]int [{s: "+longstrLoaded64+"}: 1, ]", "map[main.C]int", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + // Key format: ... @
+ checkVarRegex(t, client.ExpectVariablesResponse(t), 0, `main\.C {s: "very long string 0123456789.+\.\.\. @ 0x[0-9a-f]+`, `m5\[\(\*\(\*"main\.C"\)\(0x[0-9a-f]+\)\)\]`, "1", `int`, hasChildren) + } + // key - scalar, value - scalar (inlined key:value display) => not truncated + ref = checkVarExact(t, locals, -1, "m6", "m6", "map[string]int ["+longstrLoaded64+": 123, ]", "map[string]int", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + checkVarRegex(t, client.ExpectVariablesResponse(t), 0, longstrLoaded64re, `m6[\(\*\(\*\"string\"\)\(0x[0-9a-f]+\)\)]`, "123", "string: int", noChildren) + } + // key - compound, value - compound (array-like display) => not truncated + ref = checkVarExact(t, locals, -1, "m7", "m7", "map[main.C]main.C [{s: "+longstrLoaded64+"}: {s: "+longstrLoaded64+"}, ]", "map[main.C]main.C", hasChildren) + if ref > 0 { + client.VariablesRequest(ref) + m7 := client.ExpectVariablesResponse(t) + checkVarRegex(t, m7, 0, "[key 0]", `\(\*\(\*\"main\.C\"\)\(0x[0-9a-f]+\)\)`, `main\.C {s: `+longstrLoaded64re+`}`, `main\.C`, hasChildren) + checkVarRegex(t, m7, 1, "[val 0]", `\(\*\(\*\"main\.C\"\)\(0x[0-9a-f]+\)\)`, `main\.C {s: `+longstrLoaded64re+`}`, `main\.C`, hasChildren) + } + }, + disconnect: true, + }}) + }) +} + +// TestVariableLoadingOfLargeStrings tests that different string loading limits +// apply that depending on the context. +func TestVariableLoadingOfLargeStrings(t *testing.T) { runTest(t, "testvariables2", func(client *daptest.Client, fixture protest.Fixture) { runDebugSessionWithBPs(t, client, "launch", // Launch @@ -2843,53 +2922,34 @@ func TestEvaluateRequestLongStrLargeValue(t *testing.T) { fixture.Source, []int{}, []onBreakpoint{{ execute: func() { - checkStop(t, client, 1, "main.main", -1) client.VariablesRequest(1001) // Locals locals := client.ExpectVariablesResponse(t) - // reflect.Kind == String, load with longer load limit if evaluated in repl/variables context. + // Limits vary for evaluate requests for _, evalContext := range []string{"", "watch", "repl", "variables", "hover", "clipboard", "somethingelse"} { t.Run(evalContext, func(t *testing.T) { - // long string - - longstr := `"very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"` - longstrTruncated := `"very long string 0123456789a0123456789b0123456789c0123456789d012...+73 more"` - + // long string by itself (limits vary) client.EvaluateRequest("longstr", 0, evalContext) got := client.ExpectEvaluateResponse(t) - want := longstrTruncated + want := longstrLoaded64 switch evalContext { case "repl", "variables", "hover", "clipboard": - want = longstr + want = longstrFull } checkEval(t, got, want, false) - // long string as a struct field - client.EvaluateRequest("(m6).s", 0, evalContext) + // long string as a child (same limits) + client.EvaluateRequest("(cl).s", 0, evalContext) got2 := client.ExpectEvaluateResponse(t) checkEval(t, got2, want, false) - - // variables are not affected. - checkVarExact(t, locals, -1, "longstr", "longstr", longstrTruncated, "string", noChildren) - checkVarExact(t, locals, -1, "m6", "m6", `main.C {s: `+longstrTruncated+`}`, "main.C", hasChildren) - - // large array - m1 := `map\[string\]main\.astruct \[.+\.\.\.\+2 more\]` - m1Truncated := `map\[string\]main\.astruct \[.+\.\.\.` - - client.EvaluateRequest("m1", 0, evalContext) - got3 := client.ExpectEvaluateResponse(t) - want3 := m1Truncated - switch evalContext { - case "variables", "clipboard": - want3 = m1 - } - checkEvalRegex(t, got3, want3, true) - checkVarRegex(t, locals, -1, "m1", "m1", m1Truncated, `map\[string\]main\.astruct`, true) }) } + + // Variables requests are not affected. + checkVarExact(t, locals, -1, "longstr", "longstr", longstrLoaded64, "string", noChildren) + checkVarExact(t, locals, -1, "cl", "cl", `main.C {s: `+longstrLoaded64+`}`, "main.C", hasChildren) }, disconnect: true, }}) @@ -4058,7 +4118,7 @@ func TestSetVariable(t *testing.T) { execute: func() { tester := &helperForSetVariable{t, client} - startLineno := 358 // after runtime.Breakpoint + startLineno := 360 // after runtime.Breakpoint if runtime.GOOS == "windows" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) { startLineno = -1 }