github.com/neilgarb/delve@v1.9.2-nobreaks/pkg/proc/scope_test.go (about)

     1  package proc_test
     2  
     3  import (
     4  	"fmt"
     5  	"go/constant"
     6  	"go/parser"
     7  	"go/token"
     8  	"math"
     9  	"path/filepath"
    10  	"reflect"
    11  	"runtime"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/go-delve/delve/pkg/goversion"
    17  	"github.com/go-delve/delve/pkg/proc"
    18  	protest "github.com/go-delve/delve/pkg/proc/test"
    19  )
    20  
    21  func TestScopeWithEscapedVariable(t *testing.T) {
    22  	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1, Beta: 3}) {
    23  		return
    24  	}
    25  
    26  	withTestProcess("scopeescapevareval", t, func(p *proc.Target, fixture protest.Fixture) {
    27  		assertNoError(p.Continue(), t, "Continue")
    28  
    29  		// On the breakpoint there are two 'a' variables in scope, the one that
    30  		// isn't shadowed is a variable that escapes to the heap and figures in
    31  		// debug_info as '&a'. Evaluating 'a' should yield the escaped variable.
    32  
    33  		avar := evalVariable(p, t, "a")
    34  		if aval, _ := constant.Int64Val(avar.Value); aval != 3 {
    35  			t.Errorf("wrong value for variable a: %d", aval)
    36  		}
    37  
    38  		if avar.Flags&proc.VariableEscaped == 0 {
    39  			t.Errorf("variable a isn't escaped to the heap")
    40  		}
    41  	})
    42  }
    43  
    44  // TestScope will:
    45  //   - run _fixtures/scopetest.go
    46  //   - set a breakpoint on all lines containing a comment
    47  //   - continue until the program ends
    48  //   - every time a breakpoint is hit it will check that
    49  //     scope.FunctionArguments+scope.LocalVariables and scope.EvalExpression
    50  //     return what the corresponding comment describes they should return and
    51  //     removes the breakpoint.
    52  //
    53  // Each comment is a comma separated list of variable declarations, with
    54  // each variable declaration having the following format:
    55  //
    56  //	name type = initialvalue
    57  //
    58  // the = and the initial value are optional and can only be specified if the
    59  // type is an integer type, float32, float64 or bool.
    60  //
    61  // If multiple variables with the same name are specified:
    62  //  1. LocalVariables+FunctionArguments should return them in the same order and
    63  //     every variable except the last one should be marked as shadowed
    64  //  2. EvalExpression should return the last one.
    65  func TestScope(t *testing.T) {
    66  	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) {
    67  		return
    68  	}
    69  
    70  	fixturesDir := protest.FindFixturesDir()
    71  	scopetestPath := filepath.Join(fixturesDir, "scopetest.go")
    72  
    73  	scopeChecks := getScopeChecks(scopetestPath, t)
    74  
    75  	withTestProcess("scopetest", t, func(p *proc.Target, fixture protest.Fixture) {
    76  		for i := range scopeChecks {
    77  			setFileBreakpoint(p, t, fixture.Source, scopeChecks[i].line)
    78  		}
    79  
    80  		t.Logf("%d breakpoints set", len(scopeChecks))
    81  
    82  		for {
    83  			if err := p.Continue(); err != nil {
    84  				if _, exited := err.(proc.ErrProcessExited); exited {
    85  					break
    86  				}
    87  				assertNoError(err, t, "Continue()")
    88  			}
    89  			bp := p.CurrentThread().Breakpoint()
    90  
    91  			scopeCheck := findScopeCheck(scopeChecks, bp.Line)
    92  			if scopeCheck == nil {
    93  				t.Errorf("unknown stop position %s:%d %#x", bp.File, bp.Line, bp.Addr)
    94  			}
    95  
    96  			scope, _ := scopeCheck.checkLocalsAndArgs(p, t)
    97  
    98  			for i := range scopeCheck.varChecks {
    99  				vc := &scopeCheck.varChecks[i]
   100  				if vc.shdw {
   101  					continue
   102  				}
   103  				vc.checkInScope(scopeCheck.line, scope, t)
   104  			}
   105  
   106  			scopeCheck.ok = true
   107  			err := p.ClearBreakpoint(bp.Addr)
   108  			assertNoError(err, t, "ClearBreakpoint")
   109  		}
   110  	})
   111  
   112  	for i := range scopeChecks {
   113  		if !scopeChecks[i].ok {
   114  			t.Errorf("breakpoint at line %d not hit", scopeChecks[i].line)
   115  		}
   116  	}
   117  
   118  }
   119  
   120  type scopeCheck struct {
   121  	line      int
   122  	varChecks []varCheck
   123  	ok        bool // this scope check was passed
   124  }
   125  
   126  type varCheck struct {
   127  	name     string
   128  	typ      string
   129  	kind     reflect.Kind
   130  	shdw     bool // this variable should be shadowed
   131  	hasVal   bool
   132  	intVal   int64
   133  	uintVal  uint64
   134  	floatVal float64
   135  	boolVal  bool
   136  
   137  	ok bool // this variable check was passed
   138  }
   139  
   140  func getScopeChecks(path string, t *testing.T) []scopeCheck {
   141  	var fset token.FileSet
   142  	root, err := parser.ParseFile(&fset, path, nil, parser.ParseComments)
   143  	if err != nil {
   144  		t.Fatalf("could not parse %s: %v", path, err)
   145  	}
   146  
   147  	scopeChecks := []scopeCheck{}
   148  
   149  	for _, cmtg := range root.Comments {
   150  		for _, cmt := range cmtg.List {
   151  			pos := fset.Position(cmt.Slash)
   152  
   153  			scopeChecks = append(scopeChecks, scopeCheck{line: pos.Line})
   154  			scopeChecks[len(scopeChecks)-1].Parse(cmt.Text[2:], t)
   155  		}
   156  	}
   157  
   158  	return scopeChecks
   159  }
   160  
   161  func findScopeCheck(scopeChecks []scopeCheck, line int) *scopeCheck {
   162  	for i := range scopeChecks {
   163  		if scopeChecks[i].line == line {
   164  			return &scopeChecks[i]
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  func (check *scopeCheck) Parse(descr string, t *testing.T) {
   171  	decls := strings.Split(descr, ",")
   172  	check.varChecks = make([]varCheck, len(decls))
   173  	for i, decl := range decls {
   174  		varcheck := &check.varChecks[i]
   175  		value := ""
   176  		if equal := strings.Index(decl, "="); equal >= 0 {
   177  			value = strings.TrimSpace(decl[equal+1:])
   178  			decl = strings.TrimSpace(decl[:equal])
   179  			varcheck.hasVal = true
   180  		} else {
   181  			decl = strings.TrimSpace(decl)
   182  		}
   183  
   184  		space := strings.Index(decl, " ")
   185  		if space < 0 {
   186  			t.Fatalf("could not parse scope comment %q (%q)", descr, decl)
   187  		}
   188  		varcheck.name = strings.TrimSpace(decl[:space])
   189  		varcheck.typ = strings.TrimSpace(decl[space+1:])
   190  		if strings.Index(varcheck.typ, " ") >= 0 {
   191  			t.Fatalf("could not parse scope comment %q (%q)", descr, decl)
   192  		}
   193  
   194  		if !varcheck.hasVal {
   195  			continue
   196  		}
   197  
   198  		switch varcheck.typ {
   199  		case "int", "int8", "int16", "int32", "int64":
   200  			var err error
   201  			varcheck.kind = reflect.Int
   202  			varcheck.intVal, err = strconv.ParseInt(value, 10, 64)
   203  			if err != nil {
   204  				t.Fatalf("could not parse scope comment %q: %v", descr, err)
   205  			}
   206  
   207  		case "uint", "uint8", "uint16", "uint32", "uint64", "uintptr":
   208  			var err error
   209  			varcheck.kind = reflect.Uint
   210  			varcheck.uintVal, err = strconv.ParseUint(value, 10, 64)
   211  			if err != nil {
   212  				t.Fatalf("could not parse scope comment %q: %v", descr, err)
   213  			}
   214  
   215  		case "float32", "float64":
   216  			var err error
   217  			varcheck.kind = reflect.Float64
   218  			varcheck.floatVal, err = strconv.ParseFloat(value, 64)
   219  			if err != nil {
   220  				t.Fatalf("could not parse scope comment %q: %v", descr, err)
   221  			}
   222  
   223  		case "bool":
   224  			var err error
   225  			varcheck.kind = reflect.Bool
   226  			varcheck.boolVal, err = strconv.ParseBool(value)
   227  			if err != nil {
   228  				t.Fatalf("could not parse scope comment %q: %v", descr, err)
   229  			}
   230  		}
   231  	}
   232  
   233  	for i := 1; i < len(check.varChecks); i++ {
   234  		if check.varChecks[i-1].name == check.varChecks[i].name {
   235  			check.varChecks[i-1].shdw = true
   236  		}
   237  	}
   238  }
   239  
   240  func (scopeCheck *scopeCheck) checkLocalsAndArgs(p *proc.Target, t *testing.T) (*proc.EvalScope, bool) {
   241  	scope, err := proc.GoroutineScope(p, p.CurrentThread())
   242  	assertNoError(err, t, "GoroutineScope()")
   243  
   244  	ok := true
   245  
   246  	args, err := scope.FunctionArguments(normalLoadConfig)
   247  	assertNoError(err, t, "FunctionArguments()")
   248  	locals, err := scope.LocalVariables(normalLoadConfig)
   249  	assertNoError(err, t, "LocalVariables()")
   250  
   251  	for _, arg := range args {
   252  		scopeCheck.checkVar(arg, t)
   253  	}
   254  
   255  	for _, local := range locals {
   256  		scopeCheck.checkVar(local, t)
   257  	}
   258  
   259  	for i := range scopeCheck.varChecks {
   260  		if !scopeCheck.varChecks[i].ok {
   261  			t.Errorf("%d: variable %s not found", scopeCheck.line, scopeCheck.varChecks[i].name)
   262  			ok = false
   263  		}
   264  	}
   265  
   266  	return scope, ok
   267  }
   268  
   269  func (check *scopeCheck) checkVar(v *proc.Variable, t *testing.T) {
   270  	var varCheck *varCheck
   271  	for i := range check.varChecks {
   272  		if !check.varChecks[i].ok && (check.varChecks[i].name == v.Name) {
   273  			varCheck = &check.varChecks[i]
   274  			break
   275  		}
   276  	}
   277  
   278  	if varCheck == nil {
   279  		t.Errorf("%d: unexpected variable %s", check.line, v.Name)
   280  		return
   281  	}
   282  
   283  	varCheck.check(check.line, v, t, "FunctionArguments+LocalVariables")
   284  	varCheck.ok = true
   285  }
   286  
   287  func (varCheck *varCheck) checkInScope(line int, scope *proc.EvalScope, t *testing.T) {
   288  	v, err := scope.EvalExpression(varCheck.name, normalLoadConfig)
   289  	assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", varCheck.name))
   290  	varCheck.check(line, v, t, "EvalExpression")
   291  
   292  }
   293  
   294  func (varCheck *varCheck) check(line int, v *proc.Variable, t *testing.T, ctxt string) {
   295  	typ := v.DwarfType.String()
   296  	typ = strings.Replace(typ, " ", "", -1)
   297  	if typ != varCheck.typ {
   298  		t.Errorf("%d: wrong type for %s (%s), got %s, expected %s", line, v.Name, ctxt, typ, varCheck.typ)
   299  	}
   300  
   301  	if varCheck.shdw && v.Flags&proc.VariableShadowed == 0 {
   302  		t.Errorf("%d: expected shadowed %s variable", line, v.Name)
   303  	}
   304  
   305  	if !varCheck.hasVal {
   306  		return
   307  	}
   308  
   309  	switch varCheck.kind {
   310  	case reflect.Int:
   311  		if vv, _ := constant.Int64Val(v.Value); vv != varCheck.intVal {
   312  			t.Errorf("%d: wrong value for %s (%s), got %d expected %d", line, v.Name, ctxt, vv, varCheck.intVal)
   313  		}
   314  	case reflect.Uint:
   315  		if vv, _ := constant.Uint64Val(v.Value); vv != varCheck.uintVal {
   316  			t.Errorf("%d: wrong value for %s (%s), got %d expected %d", line, v.Name, ctxt, vv, varCheck.uintVal)
   317  		}
   318  	case reflect.Float64:
   319  		if vv, _ := constant.Float64Val(v.Value); math.Abs(vv-varCheck.floatVal) > 0.001 {
   320  			t.Errorf("%d: wrong value for %s (%s), got %g expected %g", line, v.Name, ctxt, vv, varCheck.floatVal)
   321  		}
   322  	case reflect.Bool:
   323  		if vv := constant.BoolVal(v.Value); vv != varCheck.boolVal {
   324  			t.Errorf("%d: wrong value for %s (%s), got %v expected %v", line, v.Name, ctxt, vv, varCheck.boolVal)
   325  		}
   326  	}
   327  }