
Refactored variables.go to separate calculation of a variable's address from reading its value. This change is useful to implement the 'set' command as well as the evaluation of more complex expressions (in the future).
305 lines
10 KiB
Go
305 lines
10 KiB
Go
package proc
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"testing"
|
|
|
|
protest "github.com/derekparker/delve/proc/test"
|
|
)
|
|
|
|
type varTest struct {
|
|
name string
|
|
value string
|
|
varType string
|
|
err error
|
|
}
|
|
|
|
func assertVariable(t *testing.T, variable *Variable, expected varTest) {
|
|
if variable.Name != expected.name {
|
|
t.Fatalf("Expected %s got %s\n", expected.name, variable.Name)
|
|
}
|
|
|
|
if variable.Type != expected.varType {
|
|
t.Fatalf("Expected %s got %s (for variable %s)\n", expected.varType, variable.Type, expected.name)
|
|
}
|
|
|
|
if variable.Value != expected.value {
|
|
t.Fatalf("Expected %#v got %#v (for variable %s)\n", expected.value, variable.Value, expected.name)
|
|
}
|
|
}
|
|
|
|
func evalVariable(p *Process, symbol string) (*Variable, error) {
|
|
scope, err := p.CurrentThread.Scope()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return scope.EvalVariable(symbol)
|
|
}
|
|
|
|
const varTestBreakpointLineNumber = 59
|
|
|
|
func TestVariableEvaluation(t *testing.T) {
|
|
testcases := []varTest{
|
|
{"a1", "foofoofoofoofoofoo", "struct string", nil},
|
|
{"a10", "ofo", "struct string", nil},
|
|
{"a11", "[3]main.FooBar [{Baz: 1, Bur: a},{Baz: 2, Bur: b},{Baz: 3, Bur: c}]", "[3]main.FooBar", nil},
|
|
{"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: d},{Baz: 5, Bur: e}]", "struct []main.FooBar", nil},
|
|
{"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: f},*{Baz: 7, Bur: g},*{Baz: 8, Bur: h}]", "struct []*main.FooBar", nil},
|
|
{"a2", "6", "int", nil},
|
|
{"a3", "7.23", "float64", nil},
|
|
{"a4", "[2]int [1,2]", "[2]int", nil},
|
|
{"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "struct []int", nil},
|
|
{"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar", nil},
|
|
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar", nil},
|
|
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil},
|
|
{"a9", "*main.FooBar nil", "*main.FooBar", nil},
|
|
{"baz", "bazburzum", "struct string", nil},
|
|
{"neg", "-1", "int", nil},
|
|
{"f32", "1.2", "float32", nil},
|
|
{"c64", "(1 + 2i)", "complex64", nil},
|
|
{"c128", "(2 + 3i)", "complex128", nil},
|
|
{"a6.Baz", "8", "int", nil},
|
|
{"a7.Baz", "5", "int", nil},
|
|
{"a8.Baz", "feh", "struct string", nil},
|
|
{"a9.Baz", "nil", "int", fmt.Errorf("a9 is nil")},
|
|
{"a9.NonExistent", "nil", "int", fmt.Errorf("a9 has no member NonExistent")},
|
|
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil}, // reread variable after member
|
|
{"i32", "[2]int32 [1,2]", "[2]int32", nil},
|
|
{"b1", "true", "bool", nil},
|
|
{"b2", "false", "bool", nil}, {"i8", "1", "int8", nil},
|
|
{"u16", "65535", "uint16", nil},
|
|
{"u32", "4294967295", "uint32", nil},
|
|
{"u64", "18446744073709551615", "uint64", nil},
|
|
{"u8", "255", "uint8", nil},
|
|
{"up", "5", "uintptr", nil},
|
|
{"f", "main.barfoo", "func()", nil},
|
|
{"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "struct []int", nil},
|
|
{"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *main.Nest {...}}}", "main.Nest", nil},
|
|
{"main.p1", "10", "int", nil},
|
|
{"p1", "10", "int", nil},
|
|
{"NonExistent", "", "", fmt.Errorf("could not find symbol value for NonExistent")},
|
|
}
|
|
|
|
withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) {
|
|
pc, _, _ := p.goSymTable.LineToPC(fixture.Source, varTestBreakpointLineNumber)
|
|
|
|
_, err := p.SetBreakpoint(pc)
|
|
assertNoError(err, t, "SetBreakpoint() returned an error")
|
|
|
|
err = p.Continue()
|
|
assertNoError(err, t, "Continue() returned an error")
|
|
|
|
for _, tc := range testcases {
|
|
variable, err := evalVariable(p, tc.name)
|
|
if tc.err == nil {
|
|
assertNoError(err, t, "EvalVariable() returned an error")
|
|
assertVariable(t, variable, tc)
|
|
} else {
|
|
if tc.err.Error() != err.Error() {
|
|
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestVariableFunctionScoping(t *testing.T) {
|
|
withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) {
|
|
pc, _, _ := p.goSymTable.LineToPC(fixture.Source, varTestBreakpointLineNumber)
|
|
|
|
_, err := p.SetBreakpoint(pc)
|
|
assertNoError(err, t, "SetBreakpoint() returned an error")
|
|
|
|
err = p.Continue()
|
|
assertNoError(err, t, "Continue() returned an error")
|
|
p.ClearBreakpoint(pc)
|
|
|
|
_, err = evalVariable(p, "a1")
|
|
assertNoError(err, t, "Unable to find variable a1")
|
|
|
|
_, err = evalVariable(p, "a2")
|
|
assertNoError(err, t, "Unable to find variable a1")
|
|
|
|
// Move scopes, a1 exists here by a2 does not
|
|
pc, _, _ = p.goSymTable.LineToPC(fixture.Source, 23)
|
|
|
|
_, err = p.SetBreakpoint(pc)
|
|
assertNoError(err, t, "SetBreakpoint() returned an error")
|
|
|
|
err = p.Continue()
|
|
assertNoError(err, t, "Continue() returned an error")
|
|
|
|
_, err = evalVariable(p, "a1")
|
|
assertNoError(err, t, "Unable to find variable a1")
|
|
|
|
_, err = evalVariable(p, "a2")
|
|
if err == nil {
|
|
t.Fatalf("Can eval out of scope variable a2")
|
|
}
|
|
})
|
|
}
|
|
|
|
type varArray []*Variable
|
|
|
|
// Len is part of sort.Interface.
|
|
func (s varArray) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
// Swap is part of sort.Interface.
|
|
func (s varArray) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
|
|
func (s varArray) Less(i, j int) bool {
|
|
return s[i].Name < s[j].Name
|
|
}
|
|
|
|
func TestLocalVariables(t *testing.T) {
|
|
testcases := []struct {
|
|
fn func(*EvalScope) ([]*Variable, error)
|
|
output []varTest
|
|
}{
|
|
{(*EvalScope).LocalVariables,
|
|
[]varTest{
|
|
{"a1", "foofoofoofoofoofoo", "struct string", nil},
|
|
{"a10", "ofo", "struct string", nil},
|
|
{"a11", "[3]main.FooBar [{Baz: 1, Bur: a},{Baz: 2, Bur: b},{Baz: 3, Bur: c}]", "[3]main.FooBar", nil},
|
|
{"a12", "[]main.FooBar len: 2, cap: 2, [{Baz: 4, Bur: d},{Baz: 5, Bur: e}]", "struct []main.FooBar", nil},
|
|
{"a13", "[]*main.FooBar len: 3, cap: 3, [*{Baz: 6, Bur: f},*{Baz: 7, Bur: g},*{Baz: 8, Bur: h}]", "struct []*main.FooBar", nil},
|
|
{"a2", "6", "int", nil},
|
|
{"a3", "7.23", "float64", nil},
|
|
{"a4", "[2]int [1,2]", "[2]int", nil},
|
|
{"a5", "[]int len: 5, cap: 5, [1,2,3,4,5]", "struct []int", nil},
|
|
{"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar", nil},
|
|
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar", nil},
|
|
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil},
|
|
{"a9", "*main.FooBar nil", "*main.FooBar", nil},
|
|
{"b1", "true", "bool", nil},
|
|
{"b2", "false", "bool", nil},
|
|
{"ba", "[]int len: 200, cap: 200, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...+136 more]", "struct []int", nil},
|
|
{"c128", "(2 + 3i)", "complex128", nil},
|
|
{"c64", "(1 + 2i)", "complex64", nil},
|
|
{"f", "main.barfoo", "func()", nil},
|
|
{"f32", "1.2", "float32", nil},
|
|
{"i32", "[2]int32 [1,2]", "[2]int32", nil},
|
|
{"i8", "1", "int8", nil},
|
|
{"ms", "main.Nest {Level: 0, Nest: *main.Nest {Level: 1, Nest: *main.Nest {...}}}", "main.Nest", nil},
|
|
{"neg", "-1", "int", nil},
|
|
{"u16", "65535", "uint16", nil},
|
|
{"u32", "4294967295", "uint32", nil},
|
|
{"u64", "18446744073709551615", "uint64", nil},
|
|
{"u8", "255", "uint8", nil},
|
|
{"up", "5", "uintptr", nil}}},
|
|
{(*EvalScope).FunctionArguments,
|
|
[]varTest{
|
|
{"bar", "main.FooBar {Baz: 10, Bur: lorem}", "main.FooBar", nil},
|
|
{"baz", "bazburzum", "struct string", nil}}},
|
|
}
|
|
|
|
withTestProcess("testvariables", t, func(p *Process, fixture protest.Fixture) {
|
|
pc, _, _ := p.goSymTable.LineToPC(fixture.Source, varTestBreakpointLineNumber)
|
|
|
|
_, err := p.SetBreakpoint(pc)
|
|
assertNoError(err, t, "SetBreakpoint() returned an error")
|
|
|
|
err = p.Continue()
|
|
assertNoError(err, t, "Continue() returned an error")
|
|
|
|
for _, tc := range testcases {
|
|
scope, err := p.CurrentThread.Scope()
|
|
assertNoError(err, t, "AsScope()")
|
|
vars, err := tc.fn(scope)
|
|
assertNoError(err, t, "LocalVariables() returned an error")
|
|
|
|
sort.Sort(varArray(vars))
|
|
|
|
if len(tc.output) != len(vars) {
|
|
t.Fatalf("Invalid variable count. Expected %d got %d.", len(tc.output), len(vars))
|
|
}
|
|
|
|
for i, variable := range vars {
|
|
assertVariable(t, variable, tc.output[i])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRecursiveStructure(t *testing.T) {
|
|
withTestProcess("testvariables2", t, func(p *Process, fixture protest.Fixture) {
|
|
assertNoError(p.Continue(), t, "Continue()")
|
|
v, err := evalVariable(p, "aas")
|
|
assertNoError(err, t, "EvalVariable()")
|
|
t.Logf("v: %v\n", v)
|
|
})
|
|
}
|
|
|
|
func TestFrameEvaluation(t *testing.T) {
|
|
withTestProcess("goroutinestackprog", t, func(p *Process, fixture protest.Fixture) {
|
|
_, err := setFunctionBreakpoint(p, "main.stacktraceme")
|
|
assertNoError(err, t, "setFunctionBreakpoint")
|
|
assertNoError(p.Continue(), t, "Continue()")
|
|
|
|
/**** Testing evaluation on goroutines ****/
|
|
gs, err := p.GoroutinesInfo()
|
|
assertNoError(err, t, "GoroutinesInfo")
|
|
found := make([]bool, 10)
|
|
for _, g := range gs {
|
|
frame := -1
|
|
frames, err := p.GoroutineStacktrace(g, 10)
|
|
assertNoError(err, t, "GoroutineStacktrace()")
|
|
for i := range frames {
|
|
if frames[i].Call.Fn != nil && frames[i].Call.Fn.Name == "main.agoroutine" {
|
|
frame = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if frame < 0 {
|
|
t.Logf("Goroutine %d: could not find correct frame", g.Id)
|
|
continue
|
|
}
|
|
|
|
scope, err := p.ConvertEvalScope(g.Id, frame)
|
|
assertNoError(err, t, "ConvertEvalScope()")
|
|
t.Logf("scope = %v", scope)
|
|
v, err := scope.EvalVariable("i")
|
|
t.Logf("v = %v", v)
|
|
if err != nil {
|
|
t.Logf("Goroutine %d: %v\n", g.Id, err)
|
|
continue
|
|
}
|
|
i, err := strconv.Atoi(v.Value)
|
|
assertNoError(err, t, fmt.Sprintf("strconv.Atoi(%s)", v.Value))
|
|
found[i] = true
|
|
}
|
|
|
|
for i := range found {
|
|
if !found[i] {
|
|
t.Fatalf("Goroutine %d not found\n", i)
|
|
}
|
|
}
|
|
|
|
/**** Testing evaluation on frames ****/
|
|
assertNoError(p.Continue(), t, "Continue() 2")
|
|
g, err := p.CurrentThread.GetG()
|
|
assertNoError(err, t, "GetG()")
|
|
|
|
for i := 0; i <= 3; i++ {
|
|
scope, err := p.ConvertEvalScope(g.Id, i+1)
|
|
assertNoError(err, t, fmt.Sprintf("ConvertEvalScope() on frame %d", i+1))
|
|
v, err := scope.EvalVariable("n")
|
|
assertNoError(err, t, fmt.Sprintf("EvalVariable() on frame %d", i+1))
|
|
n, err := strconv.Atoi(v.Value)
|
|
assertNoError(err, t, fmt.Sprintf("strconv.Atoi(%s) on frame %d", v.Value, i+1))
|
|
t.Logf("frame %d n %d\n", i+1, n)
|
|
if n != 3-i {
|
|
t.Fatalf("On frame %d value of n is %d (not %d)", i+1, n, 3-i)
|
|
}
|
|
}
|
|
})
|
|
}
|