mirror of
				https://github.com/go-delve/delve.git
				synced 2025-10-31 10:47:27 +08:00 
			
		
		
		
	 c3f50742b9
			
		
	
	c3f50742b9
	
	
	
		
			
			Refactors some code, adds a bunch of docstrings and just generally fixes a bunch of linter complaints.
		
			
				
	
	
		
			321 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			321 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package proc_test
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"go/constant"
 | |
| 	"go/parser"
 | |
| 	"go/token"
 | |
| 	"math"
 | |
| 	"path/filepath"
 | |
| 	"reflect"
 | |
| 	"runtime"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/derekparker/delve/pkg/goversion"
 | |
| 	"github.com/derekparker/delve/pkg/proc"
 | |
| 	protest "github.com/derekparker/delve/pkg/proc/test"
 | |
| )
 | |
| 
 | |
| func TestScopeWithEscapedVariable(t *testing.T) {
 | |
| 	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 3, 0, ""}) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	withTestProcess("scopeescapevareval", t, func(p proc.Process, fixture protest.Fixture) {
 | |
| 		assertNoError(proc.Continue(p), t, "Continue")
 | |
| 
 | |
| 		// On the breakpoint there are two 'a' variables in scope, the one that
 | |
| 		// isn't shadowed is a variable that escapes to the heap and figures in
 | |
| 		// debug_info as '&a'. Evaluating 'a' should yield the escaped variable.
 | |
| 
 | |
| 		avar := evalVariable(p, t, "a")
 | |
| 		if aval, _ := constant.Int64Val(avar.Value); aval != 3 {
 | |
| 			t.Errorf("wrong value for variable a: %d", aval)
 | |
| 		}
 | |
| 
 | |
| 		if avar.Flags&proc.VariableEscaped == 0 {
 | |
| 			t.Errorf("variable a isn't escaped to the heap")
 | |
| 		}
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // TestScope will:
 | |
| // - run _fixtures/scopetest.go
 | |
| // - set a breakpoint on all lines containing a comment
 | |
| // - continue until the program ends
 | |
| // - every time a breakpoint is hit it will check that
 | |
| //   scope.FunctionArguments+scope.LocalVariables and scope.EvalExpression
 | |
| //   return what the corresponding comment describes they should return and
 | |
| //   removes the breakpoint.
 | |
| //
 | |
| // Each comment is a comma separated list of variable declarations, with
 | |
| // each variable declaration having the following format:
 | |
| //
 | |
| //  name type = initialvalue
 | |
| //
 | |
| // the = and the initial value are optional and can only be specified if the
 | |
| // type is an integer type, float32, float64 or bool.
 | |
| //
 | |
| // If multiple variables with the same name are specified
 | |
| // LocalVariables+FunctionArguments should return them in the same order and
 | |
| // EvalExpression should return the last one.
 | |
| func TestScope(t *testing.T) {
 | |
| 	if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	fixturesDir := protest.FindFixturesDir()
 | |
| 	scopetestPath := filepath.Join(fixturesDir, "scopetest.go")
 | |
| 
 | |
| 	scopeChecks := getScopeChecks(scopetestPath, t)
 | |
| 
 | |
| 	withTestProcess("scopetest", t, func(p proc.Process, fixture protest.Fixture) {
 | |
| 		for i := range scopeChecks {
 | |
| 			setFileBreakpoint(p, t, fixture, scopeChecks[i].line)
 | |
| 		}
 | |
| 
 | |
| 		t.Logf("%d breakpoints set", len(scopeChecks))
 | |
| 
 | |
| 		for {
 | |
| 			if err := proc.Continue(p); err != nil {
 | |
| 				if _, exited := err.(proc.ErrProcessExited); exited {
 | |
| 					break
 | |
| 				}
 | |
| 				assertNoError(err, t, "Continue()")
 | |
| 			}
 | |
| 			bp := p.CurrentThread().Breakpoint()
 | |
| 
 | |
| 			scopeCheck := findScopeCheck(scopeChecks, bp.Line)
 | |
| 			if scopeCheck == nil {
 | |
| 				t.Errorf("unknown stop position %s:%d %#x", bp.File, bp.Line, bp.Addr)
 | |
| 			}
 | |
| 
 | |
| 			scope, _ := scopeCheck.checkLocalsAndArgs(p, t)
 | |
| 
 | |
| 			var prev *varCheck
 | |
| 			for i := range scopeCheck.varChecks {
 | |
| 				vc := &scopeCheck.varChecks[i]
 | |
| 				if prev != nil && prev.name != vc.name {
 | |
| 					prev.checkInScope(scopeCheck.line, scope, t)
 | |
| 				}
 | |
| 				prev = vc
 | |
| 			}
 | |
| 			if prev != nil {
 | |
| 				prev.checkInScope(scopeCheck.line, scope, t)
 | |
| 			}
 | |
| 
 | |
| 			scopeCheck.ok = true
 | |
| 			_, err := p.ClearBreakpoint(bp.Addr)
 | |
| 			assertNoError(err, t, "ClearBreakpoint")
 | |
| 		}
 | |
| 	})
 | |
| 
 | |
| 	for i := range scopeChecks {
 | |
| 		if !scopeChecks[i].ok {
 | |
| 			t.Errorf("breakpoint at line %d not hit", scopeChecks[i].line)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| type scopeCheck struct {
 | |
| 	line      int
 | |
| 	varChecks []varCheck
 | |
| 	ok        bool // this scope check was passed
 | |
| }
 | |
| 
 | |
| type varCheck struct {
 | |
| 	name     string
 | |
| 	typ      string
 | |
| 	kind     reflect.Kind
 | |
| 	hasVal   bool
 | |
| 	intVal   int64
 | |
| 	uintVal  uint64
 | |
| 	floatVal float64
 | |
| 	boolVal  bool
 | |
| 
 | |
| 	ok bool // this variable check was passed
 | |
| }
 | |
| 
 | |
| func getScopeChecks(path string, t *testing.T) []scopeCheck {
 | |
| 	var fset token.FileSet
 | |
| 	root, err := parser.ParseFile(&fset, path, nil, parser.ParseComments)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("could not parse %s: %v", path, err)
 | |
| 	}
 | |
| 
 | |
| 	scopeChecks := []scopeCheck{}
 | |
| 
 | |
| 	for _, cmtg := range root.Comments {
 | |
| 		for _, cmt := range cmtg.List {
 | |
| 			pos := fset.Position(cmt.Slash)
 | |
| 
 | |
| 			scopeChecks = append(scopeChecks, scopeCheck{line: pos.Line})
 | |
| 			scopeChecks[len(scopeChecks)-1].Parse(cmt.Text[2:], t)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return scopeChecks
 | |
| }
 | |
| 
 | |
| func findScopeCheck(scopeChecks []scopeCheck, line int) *scopeCheck {
 | |
| 	for i := range scopeChecks {
 | |
| 		if scopeChecks[i].line == line {
 | |
| 			return &scopeChecks[i]
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (check *scopeCheck) Parse(descr string, t *testing.T) {
 | |
| 	decls := strings.Split(descr, ",")
 | |
| 	check.varChecks = make([]varCheck, len(decls))
 | |
| 	for i, decl := range decls {
 | |
| 		varcheck := &check.varChecks[i]
 | |
| 		value := ""
 | |
| 		if equal := strings.Index(decl, "="); equal >= 0 {
 | |
| 			value = strings.TrimSpace(decl[equal+1:])
 | |
| 			decl = strings.TrimSpace(decl[:equal])
 | |
| 			varcheck.hasVal = true
 | |
| 		} else {
 | |
| 			decl = strings.TrimSpace(decl)
 | |
| 		}
 | |
| 
 | |
| 		space := strings.Index(decl, " ")
 | |
| 		if space < 0 {
 | |
| 			t.Fatalf("could not parse scope comment %q (%q)", descr, decl)
 | |
| 		}
 | |
| 		varcheck.name = strings.TrimSpace(decl[:space])
 | |
| 		varcheck.typ = strings.TrimSpace(decl[space+1:])
 | |
| 		if strings.Index(varcheck.typ, " ") >= 0 {
 | |
| 			t.Fatalf("could not parse scope comment %q (%q)", descr, decl)
 | |
| 		}
 | |
| 
 | |
| 		if !varcheck.hasVal {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		switch varcheck.typ {
 | |
| 		case "int", "int8", "int16", "int32", "int64":
 | |
| 			var err error
 | |
| 			varcheck.kind = reflect.Int
 | |
| 			varcheck.intVal, err = strconv.ParseInt(value, 10, 64)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("could not parse scope comment %q: %v", descr, err)
 | |
| 			}
 | |
| 
 | |
| 		case "uint", "uint8", "uint16", "uint32", "uint64", "uintptr":
 | |
| 			var err error
 | |
| 			varcheck.kind = reflect.Uint
 | |
| 			varcheck.uintVal, err = strconv.ParseUint(value, 10, 64)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("could not parse scope comment %q: %v", descr, err)
 | |
| 			}
 | |
| 
 | |
| 		case "float32", "float64":
 | |
| 			var err error
 | |
| 			varcheck.kind = reflect.Float64
 | |
| 			varcheck.floatVal, err = strconv.ParseFloat(value, 64)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("could not parse scope comment %q: %v", descr, err)
 | |
| 			}
 | |
| 
 | |
| 		case "bool":
 | |
| 			var err error
 | |
| 			varcheck.kind = reflect.Bool
 | |
| 			varcheck.boolVal, err = strconv.ParseBool(value)
 | |
| 			if err != nil {
 | |
| 				t.Fatalf("could not parse scope comment %q: %v", descr, err)
 | |
| 			}
 | |
| 
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (scopeCheck *scopeCheck) checkLocalsAndArgs(p proc.Process, t *testing.T) (*proc.EvalScope, bool) {
 | |
| 	scope, err := proc.GoroutineScope(p.CurrentThread())
 | |
| 	assertNoError(err, t, "GoroutineScope()")
 | |
| 
 | |
| 	ok := true
 | |
| 
 | |
| 	args, err := scope.FunctionArguments(normalLoadConfig)
 | |
| 	assertNoError(err, t, "FunctionArguments()")
 | |
| 	locals, err := scope.LocalVariables(normalLoadConfig)
 | |
| 	assertNoError(err, t, "LocalVariables()")
 | |
| 
 | |
| 	for _, arg := range args {
 | |
| 		scopeCheck.checkVar(arg, t)
 | |
| 	}
 | |
| 
 | |
| 	for _, local := range locals {
 | |
| 		scopeCheck.checkVar(local, t)
 | |
| 	}
 | |
| 
 | |
| 	for i := range scopeCheck.varChecks {
 | |
| 		if !scopeCheck.varChecks[i].ok {
 | |
| 			t.Errorf("%d: variable %s not found", scopeCheck.line, scopeCheck.varChecks[i].name)
 | |
| 			ok = false
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return scope, ok
 | |
| }
 | |
| 
 | |
| func (check *scopeCheck) checkVar(v *proc.Variable, t *testing.T) {
 | |
| 	var varCheck *varCheck
 | |
| 	for i := range check.varChecks {
 | |
| 		if !check.varChecks[i].ok && (check.varChecks[i].name == v.Name) {
 | |
| 			varCheck = &check.varChecks[i]
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if varCheck == nil {
 | |
| 		t.Errorf("%d: unexpected variable %s", check.line, v.Name)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	varCheck.check(check.line, v, t, "FunctionArguments+LocalVariables")
 | |
| 	varCheck.ok = true
 | |
| }
 | |
| 
 | |
| func (varCheck *varCheck) checkInScope(line int, scope *proc.EvalScope, t *testing.T) {
 | |
| 	v, err := scope.EvalVariable(varCheck.name, normalLoadConfig)
 | |
| 	assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", varCheck.name))
 | |
| 	varCheck.check(line, v, t, "EvalExpression")
 | |
| 
 | |
| }
 | |
| 
 | |
| func (varCheck *varCheck) check(line int, v *proc.Variable, t *testing.T, ctxt string) {
 | |
| 	typ := v.DwarfType.String()
 | |
| 	typ = strings.Replace(typ, " ", "", -1)
 | |
| 	if typ != varCheck.typ {
 | |
| 		t.Errorf("%d: wrong type for %s (%s), got %s, expected %s", line, v.Name, ctxt, typ, varCheck.typ)
 | |
| 	}
 | |
| 
 | |
| 	if !varCheck.hasVal {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	switch varCheck.kind {
 | |
| 	case reflect.Int:
 | |
| 		if vv, _ := constant.Int64Val(v.Value); vv != varCheck.intVal {
 | |
| 			t.Errorf("%d: wrong value for %s (%s), got %d expected %d", line, v.Name, ctxt, vv, varCheck.intVal)
 | |
| 		}
 | |
| 	case reflect.Uint:
 | |
| 		if vv, _ := constant.Uint64Val(v.Value); vv != varCheck.uintVal {
 | |
| 			t.Errorf("%d: wrong value for %s (%s), got %d expected %d", line, v.Name, ctxt, vv, varCheck.uintVal)
 | |
| 		}
 | |
| 	case reflect.Float64:
 | |
| 		if vv, _ := constant.Float64Val(v.Value); math.Abs(vv-varCheck.floatVal) > 0.001 {
 | |
| 			t.Errorf("%d: wrong value for %s (%s), got %g expected %g", line, v.Name, ctxt, vv, varCheck.floatVal)
 | |
| 		}
 | |
| 	case reflect.Bool:
 | |
| 		if vv := constant.BoolVal(v.Value); vv != varCheck.boolVal {
 | |
| 			t.Errorf("%d: wrong value for %s (%s), got %v expected %v", line, v.Name, ctxt, vv, varCheck.boolVal)
 | |
| 		}
 | |
| 	}
 | |
| }
 |