vendor: Update vendoring for the exec client and server implementations
Signed-off-by: Jacek J. Łakis <jacek.lakis@intel.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
parent
d25b88583f
commit
bf51655a7b
2124 changed files with 809703 additions and 5 deletions
460
vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector.go
generated
vendored
Normal file
460
vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector.go
generated
vendored
Normal file
|
@ -0,0 +1,460 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package valuecollector is used to collect the values of variables in a program.
|
||||
package valuecollector
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/debug"
|
||||
cd "google.golang.org/api/clouddebugger/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
maxArrayLength = 50
|
||||
maxMapLength = 20
|
||||
)
|
||||
|
||||
// Collector is given references to variables from a program being debugged
|
||||
// using AddVariable. Then when ReadValues is called, the Collector will fetch
|
||||
// the values of those variables. Any variables referred to by those values
|
||||
// will also be fetched; e.g. the targets of pointers, members of structs,
|
||||
// elements of slices, etc. This continues iteratively, building a graph of
|
||||
// values, until all the reachable values are fetched, or a size limit is
|
||||
// reached.
|
||||
//
|
||||
// Variables are passed to the Collector as debug.Var, which is used by x/debug
|
||||
// to represent references to variables. Values are returned as cd.Variable,
|
||||
// which is used by the Debuglet Controller to represent the graph of values.
|
||||
//
|
||||
// For example, if the program has a struct variable:
|
||||
//
|
||||
// foo := SomeStruct{a:42, b:"xyz"}
|
||||
//
|
||||
// and we call AddVariable with a reference to foo, we will get back a result
|
||||
// like:
|
||||
//
|
||||
// cd.Variable{Name:"foo", VarTableIndex:10}
|
||||
//
|
||||
// which denotes a variable named "foo" which will have its value stored in
|
||||
// element 10 of the table that will later be returned by ReadValues. That
|
||||
// element might be:
|
||||
//
|
||||
// out[10] = &cd.Variable{Members:{{Name:"a", VarTableIndex:11},{Name:"b", VarTableIndex:12}}}
|
||||
//
|
||||
// which denotes a struct with two members a and b, whose values are in elements
|
||||
// 11 and 12 of the output table:
|
||||
//
|
||||
// out[11] = &cd.Variable{Value:"42"}
|
||||
// out[12] = &cd.Variable{Value:"xyz"}
|
||||
type Collector struct {
|
||||
// prog is the program being debugged.
|
||||
prog debug.Program
|
||||
// limit is the maximum size of the output slice of values.
|
||||
limit int
|
||||
// index is a map from references (variables and map elements) to their
|
||||
// locations in the table.
|
||||
index map[reference]int
|
||||
// table contains the references, including those given to the
|
||||
// Collector directly and those the Collector itself found.
|
||||
// If VarTableIndex is set to 0 in a cd.Variable, it is ignored, so the first entry
|
||||
// of table can't be used. On initialization we put a dummy value there.
|
||||
table []reference
|
||||
}
|
||||
|
||||
// reference represents a value which is in the queue to be read by the
|
||||
// collector. It is either a debug.Var, or a mapElement.
|
||||
type reference interface{}
|
||||
|
||||
// mapElement represents an element of a map in the debugged program's memory.
|
||||
type mapElement struct {
|
||||
debug.Map
|
||||
index uint64
|
||||
}
|
||||
|
||||
// NewCollector returns a Collector for the given program and size limit.
|
||||
// The limit is the maximum size of the slice of values returned by ReadValues.
|
||||
func NewCollector(prog debug.Program, limit int) *Collector {
|
||||
return &Collector{
|
||||
prog: prog,
|
||||
limit: limit,
|
||||
index: make(map[reference]int),
|
||||
table: []reference{debug.Var{}},
|
||||
}
|
||||
}
|
||||
|
||||
// AddVariable adds another variable to be collected.
|
||||
// The Collector doesn't get the value immediately; it returns a cd.Variable
|
||||
// that contains an index into the table which will later be returned by
|
||||
// ReadValues.
|
||||
func (c *Collector) AddVariable(lv debug.LocalVar) *cd.Variable {
|
||||
ret := &cd.Variable{Name: lv.Name}
|
||||
if index, ok := c.add(lv.Var); !ok {
|
||||
// If the add call failed, it's because we reached the size limit.
|
||||
// The Debuglet Controller's convention is to pass it a "Not Captured" error
|
||||
// in this case.
|
||||
ret.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
|
||||
} else {
|
||||
ret.VarTableIndex = int64(index)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// add adds a reference to the set of values to be read from the
|
||||
// program. It returns the index in the output table that will contain the
|
||||
// corresponding value. It fails if the table has reached the size limit.
|
||||
// It deduplicates references, so the index may be the same as one that was
|
||||
// returned from an earlier add call.
|
||||
func (c *Collector) add(r reference) (outputIndex int, ok bool) {
|
||||
if i, ok := c.index[r]; ok {
|
||||
return i, true
|
||||
}
|
||||
i := len(c.table)
|
||||
if i >= c.limit {
|
||||
return 0, false
|
||||
}
|
||||
c.index[r] = i
|
||||
c.table = append(c.table, r)
|
||||
return i, true
|
||||
}
|
||||
|
||||
func addMember(v *cd.Variable, name string) *cd.Variable {
|
||||
v2 := &cd.Variable{Name: name}
|
||||
v.Members = append(v.Members, v2)
|
||||
return v2
|
||||
}
|
||||
|
||||
// ReadValues fetches values of the variables that were passed to the Collector
|
||||
// with AddVariable. The values of any new variables found are also fetched,
|
||||
// e.g. the targets of pointers or the members of structs, until we reach the
|
||||
// size limit or we run out of values to fetch.
|
||||
// The results are output as a []*cd.Variable, which is the type we need to send
|
||||
// to the Debuglet Controller after we trigger a breakpoint.
|
||||
func (c *Collector) ReadValues() (out []*cd.Variable) {
|
||||
for i := 0; i < len(c.table); i++ {
|
||||
// Create a new cd.Variable for this value, and append it to the output.
|
||||
dcv := new(cd.Variable)
|
||||
out = append(out, dcv)
|
||||
if i == 0 {
|
||||
// The first element is unused.
|
||||
continue
|
||||
}
|
||||
switch x := c.table[i].(type) {
|
||||
case mapElement:
|
||||
key, value, err := c.prog.MapElement(x.Map, x.index)
|
||||
if err != nil {
|
||||
dcv.Status = statusMessage(err.Error(), true, refersToVariableValue)
|
||||
continue
|
||||
}
|
||||
// Add a member for the key.
|
||||
member := addMember(dcv, "key")
|
||||
if index, ok := c.add(key); !ok {
|
||||
// The table is full.
|
||||
member.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
|
||||
continue
|
||||
} else {
|
||||
member.VarTableIndex = int64(index)
|
||||
}
|
||||
// Add a member for the value.
|
||||
member = addMember(dcv, "value")
|
||||
if index, ok := c.add(value); !ok {
|
||||
// The table is full.
|
||||
member.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
|
||||
} else {
|
||||
member.VarTableIndex = int64(index)
|
||||
}
|
||||
case debug.Var:
|
||||
if v, err := c.prog.Value(x); err != nil {
|
||||
dcv.Status = statusMessage(err.Error(), true, refersToVariableValue)
|
||||
} else {
|
||||
c.FillValue(v, dcv)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// indexable is an interface for arrays, slices and channels.
|
||||
type indexable interface {
|
||||
Len() uint64
|
||||
Element(uint64) debug.Var
|
||||
}
|
||||
|
||||
// channel implements indexable.
|
||||
type channel struct {
|
||||
debug.Channel
|
||||
}
|
||||
|
||||
func (c channel) Len() uint64 {
|
||||
return c.Length
|
||||
}
|
||||
|
||||
var (
|
||||
_ indexable = debug.Array{}
|
||||
_ indexable = debug.Slice{}
|
||||
_ indexable = channel{}
|
||||
)
|
||||
|
||||
// FillValue copies a value into a cd.Variable. Any variables referred to by
|
||||
// that value, e.g. struct members and pointer targets, are added to the
|
||||
// collector's queue, to be fetched later by ReadValues.
|
||||
func (c *Collector) FillValue(v debug.Value, dcv *cd.Variable) {
|
||||
if c, ok := v.(debug.Channel); ok {
|
||||
// Convert to channel, which implements indexable.
|
||||
v = channel{c}
|
||||
}
|
||||
// Fill in dcv in a manner depending on the type of the value we got.
|
||||
switch val := v.(type) {
|
||||
case int8, int16, int32, int64, bool, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128:
|
||||
// For simple types, we just print the value to dcv.Value.
|
||||
dcv.Value = fmt.Sprint(val)
|
||||
case string:
|
||||
// Put double quotes around strings.
|
||||
dcv.Value = strconv.Quote(val)
|
||||
case debug.String:
|
||||
if uint64(len(val.String)) < val.Length {
|
||||
// This string value was truncated.
|
||||
dcv.Value = strconv.Quote(val.String + "...")
|
||||
} else {
|
||||
dcv.Value = strconv.Quote(val.String)
|
||||
}
|
||||
case debug.Struct:
|
||||
// For structs, we add an entry to dcv.Members for each field in the
|
||||
// struct.
|
||||
// Each member will contain the name of the field, and the index in the
|
||||
// output table which will contain the value of that field.
|
||||
for _, f := range val.Fields {
|
||||
member := addMember(dcv, f.Name)
|
||||
if index, ok := c.add(f.Var); !ok {
|
||||
// The table is full.
|
||||
member.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
|
||||
} else {
|
||||
member.VarTableIndex = int64(index)
|
||||
}
|
||||
}
|
||||
case debug.Map:
|
||||
dcv.Value = fmt.Sprintf("len = %d", val.Length)
|
||||
for i := uint64(0); i < val.Length; i++ {
|
||||
field := addMember(dcv, `⚫`)
|
||||
if i == maxMapLength {
|
||||
field.Name = "..."
|
||||
field.Status = statusMessage(messageTruncated, true, refersToVariableName)
|
||||
break
|
||||
}
|
||||
if index, ok := c.add(mapElement{val, i}); !ok {
|
||||
// The value table is full; add a member to contain the error message.
|
||||
field.Name = "..."
|
||||
field.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
|
||||
break
|
||||
} else {
|
||||
field.VarTableIndex = int64(index)
|
||||
}
|
||||
}
|
||||
case debug.Pointer:
|
||||
if val.Address == 0 {
|
||||
dcv.Value = "<nil>"
|
||||
} else if val.TypeID == 0 {
|
||||
// We don't know the type of the pointer, so just output the address as
|
||||
// the value.
|
||||
dcv.Value = fmt.Sprintf("0x%X", val.Address)
|
||||
dcv.Status = statusMessage(messageUnknownPointerType, false, refersToVariableName)
|
||||
} else {
|
||||
// Adds the pointed-to variable to the table, and links this value to
|
||||
// that table entry through VarTableIndex.
|
||||
dcv.Value = fmt.Sprintf("0x%X", val.Address)
|
||||
target := addMember(dcv, "")
|
||||
if index, ok := c.add(debug.Var(val)); !ok {
|
||||
target.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
|
||||
} else {
|
||||
target.VarTableIndex = int64(index)
|
||||
}
|
||||
}
|
||||
case indexable:
|
||||
// Arrays, slices and channels.
|
||||
dcv.Value = "len = " + fmt.Sprint(val.Len())
|
||||
for j := uint64(0); j < val.Len(); j++ {
|
||||
field := addMember(dcv, fmt.Sprint(`[`, j, `]`))
|
||||
if j == maxArrayLength {
|
||||
field.Name = "..."
|
||||
field.Status = statusMessage(messageTruncated, true, refersToVariableName)
|
||||
break
|
||||
}
|
||||
vr := val.Element(j)
|
||||
if index, ok := c.add(vr); !ok {
|
||||
// The value table is full; add a member to contain the error message.
|
||||
field.Name = "..."
|
||||
field.Status = statusMessage(messageNotCaptured, true, refersToVariableName)
|
||||
break
|
||||
} else {
|
||||
// Add a member with the index as the name.
|
||||
field.VarTableIndex = int64(index)
|
||||
}
|
||||
}
|
||||
default:
|
||||
dcv.Status = statusMessage(messageUnknownType, false, refersToVariableName)
|
||||
}
|
||||
}
|
||||
|
||||
// statusMessage returns a *cd.StatusMessage with the given message, IsError
|
||||
// field and refersTo field.
|
||||
func statusMessage(msg string, isError bool, refersTo int) *cd.StatusMessage {
|
||||
return &cd.StatusMessage{
|
||||
Description: &cd.FormatMessage{Format: "$0", Parameters: []string{msg}},
|
||||
IsError: isError,
|
||||
RefersTo: refersToString[refersTo],
|
||||
}
|
||||
}
|
||||
|
||||
// LogString produces a string for a logpoint, substituting in variable values
|
||||
// using evaluatedExpressions and varTable.
|
||||
func LogString(s string, evaluatedExpressions []*cd.Variable, varTable []*cd.Variable) string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "LOGPOINT: ")
|
||||
seen := make(map[*cd.Variable]bool)
|
||||
for i := 0; i < len(s); {
|
||||
if s[i] == '$' {
|
||||
i++
|
||||
if num, n, ok := parseToken(s[i:], len(evaluatedExpressions)-1); ok {
|
||||
// This token is one of $0, $1, etc. Write the corresponding expression.
|
||||
writeExpression(&buf, evaluatedExpressions[num], false, varTable, seen)
|
||||
i += n
|
||||
} else {
|
||||
// Something else, like $$.
|
||||
buf.WriteByte(s[i])
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
buf.WriteByte(s[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func parseToken(s string, max int) (num int, bytesRead int, ok bool) {
|
||||
var i int
|
||||
for i < len(s) && s[i] >= '0' && s[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
num, err := strconv.Atoi(s[:i])
|
||||
return num, i, err == nil && num <= max
|
||||
}
|
||||
|
||||
// writeExpression recursively writes variables to buf, in a format suitable
|
||||
// for logging. If printName is true, writes the name of the variable.
|
||||
func writeExpression(buf *bytes.Buffer, v *cd.Variable, printName bool, varTable []*cd.Variable, seen map[*cd.Variable]bool) {
|
||||
if v == nil {
|
||||
// Shouldn't happen.
|
||||
return
|
||||
}
|
||||
name, value, status, members := v.Name, v.Value, v.Status, v.Members
|
||||
|
||||
// If v.VarTableIndex is not zero, it refers to an element of varTable.
|
||||
// We merge its fields with the fields we got from v.
|
||||
var other *cd.Variable
|
||||
if idx := int(v.VarTableIndex); idx > 0 && idx < len(varTable) {
|
||||
other = varTable[idx]
|
||||
}
|
||||
if other != nil {
|
||||
if name == "" {
|
||||
name = other.Name
|
||||
}
|
||||
if value == "" {
|
||||
value = other.Value
|
||||
}
|
||||
if status == nil {
|
||||
status = other.Status
|
||||
}
|
||||
if len(members) == 0 {
|
||||
members = other.Members
|
||||
}
|
||||
}
|
||||
if printName && name != "" {
|
||||
buf.WriteString(name)
|
||||
buf.WriteByte(':')
|
||||
}
|
||||
|
||||
// If we have seen this value before, write "..." rather than repeating it.
|
||||
if seen[v] {
|
||||
buf.WriteString("...")
|
||||
return
|
||||
}
|
||||
seen[v] = true
|
||||
if other != nil {
|
||||
if seen[other] {
|
||||
buf.WriteString("...")
|
||||
return
|
||||
}
|
||||
seen[other] = true
|
||||
}
|
||||
|
||||
if value != "" && !strings.HasPrefix(value, "len = ") {
|
||||
// A plain value.
|
||||
buf.WriteString(value)
|
||||
} else if status != nil && status.Description != nil {
|
||||
// An error.
|
||||
for _, p := range status.Description.Parameters {
|
||||
buf.WriteByte('(')
|
||||
buf.WriteString(p)
|
||||
buf.WriteByte(')')
|
||||
}
|
||||
} else if name == `⚫` {
|
||||
// A map element.
|
||||
first := true
|
||||
for _, member := range members {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
buf.WriteByte(':')
|
||||
}
|
||||
writeExpression(buf, member, false, varTable, seen)
|
||||
}
|
||||
} else {
|
||||
// A map, array, slice, channel, or struct.
|
||||
isStruct := value == ""
|
||||
first := true
|
||||
buf.WriteByte('{')
|
||||
for _, member := range members {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
writeExpression(buf, member, isStruct, varTable, seen)
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// Error messages for cd.StatusMessage
|
||||
messageNotCaptured = "Not captured"
|
||||
messageTruncated = "Truncated"
|
||||
messageUnknownPointerType = "Unknown pointer type"
|
||||
messageUnknownType = "Unknown type"
|
||||
// RefersTo values for cd.StatusMessage.
|
||||
refersToVariableName = iota
|
||||
refersToVariableValue
|
||||
)
|
||||
|
||||
// refersToString contains the strings for each refersTo value.
|
||||
// See the definition of StatusMessage in the v2/clouddebugger package.
|
||||
var refersToString = map[int]string{
|
||||
refersToVariableName: "VARIABLE_NAME",
|
||||
refersToVariableValue: "VARIABLE_VALUE",
|
||||
}
|
418
vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector_test.go
generated
vendored
Normal file
418
vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector/valuecollector_test.go
generated
vendored
Normal file
|
@ -0,0 +1,418 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package valuecollector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/debug"
|
||||
cd "google.golang.org/api/clouddebugger/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// Some arbitrary type IDs for the test, for use in debug.Var's TypeID field.
|
||||
// A TypeID of 0 means the type is unknown, so we start at 1.
|
||||
int16Type = iota + 1
|
||||
stringType
|
||||
structType
|
||||
pointerType
|
||||
arrayType
|
||||
int32Type
|
||||
debugStringType
|
||||
mapType
|
||||
channelType
|
||||
sliceType
|
||||
)
|
||||
|
||||
func TestValueCollector(t *testing.T) {
|
||||
// Construct the collector.
|
||||
c := NewCollector(&Program{}, 26)
|
||||
// Add some variables of various types, whose values we want the collector to read.
|
||||
variablesToAdd := []debug.LocalVar{
|
||||
{Name: "a", Var: debug.Var{int16Type, 0x1}},
|
||||
{Name: "b", Var: debug.Var{stringType, 0x2}},
|
||||
{Name: "c", Var: debug.Var{structType, 0x3}},
|
||||
{Name: "d", Var: debug.Var{pointerType, 0x4}},
|
||||
{Name: "e", Var: debug.Var{arrayType, 0x5}},
|
||||
{Name: "f", Var: debug.Var{debugStringType, 0x6}},
|
||||
{Name: "g", Var: debug.Var{mapType, 0x7}},
|
||||
{Name: "h", Var: debug.Var{channelType, 0x8}},
|
||||
{Name: "i", Var: debug.Var{sliceType, 0x9}},
|
||||
}
|
||||
expectedResults := []*cd.Variable{
|
||||
&cd.Variable{Name: "a", VarTableIndex: 1},
|
||||
&cd.Variable{Name: "b", VarTableIndex: 2},
|
||||
&cd.Variable{Name: "c", VarTableIndex: 3},
|
||||
&cd.Variable{Name: "d", VarTableIndex: 4},
|
||||
&cd.Variable{Name: "e", VarTableIndex: 5},
|
||||
&cd.Variable{Name: "f", VarTableIndex: 6},
|
||||
&cd.Variable{Name: "g", VarTableIndex: 7},
|
||||
&cd.Variable{Name: "h", VarTableIndex: 8},
|
||||
&cd.Variable{Name: "i", VarTableIndex: 9},
|
||||
}
|
||||
for i, v := range variablesToAdd {
|
||||
added := c.AddVariable(v)
|
||||
if !reflect.DeepEqual(added, expectedResults[i]) {
|
||||
t.Errorf("AddVariable: got %+v want %+v", *added, *expectedResults[i])
|
||||
}
|
||||
}
|
||||
// Read the values, compare the output to what we expect.
|
||||
v := c.ReadValues()
|
||||
expectedValues := []*cd.Variable{
|
||||
&cd.Variable{},
|
||||
&cd.Variable{Value: "1"},
|
||||
&cd.Variable{Value: `"hello"`},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "x", VarTableIndex: 1},
|
||||
&cd.Variable{Name: "y", VarTableIndex: 2},
|
||||
},
|
||||
},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{VarTableIndex: 1},
|
||||
},
|
||||
Value: "0x1",
|
||||
},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "[0]", VarTableIndex: 10},
|
||||
&cd.Variable{Name: "[1]", VarTableIndex: 11},
|
||||
&cd.Variable{Name: "[2]", VarTableIndex: 12},
|
||||
&cd.Variable{Name: "[3]", VarTableIndex: 13},
|
||||
},
|
||||
Value: "len = 4",
|
||||
},
|
||||
&cd.Variable{Value: `"world"`},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "⚫", VarTableIndex: 14},
|
||||
&cd.Variable{Name: "⚫", VarTableIndex: 15},
|
||||
&cd.Variable{Name: "⚫", VarTableIndex: 16},
|
||||
},
|
||||
Value: "len = 3",
|
||||
},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "[0]", VarTableIndex: 17},
|
||||
&cd.Variable{Name: "[1]", VarTableIndex: 18},
|
||||
},
|
||||
Value: "len = 2",
|
||||
},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "[0]", VarTableIndex: 19},
|
||||
&cd.Variable{Name: "[1]", VarTableIndex: 20},
|
||||
},
|
||||
Value: "len = 2",
|
||||
},
|
||||
&cd.Variable{Value: "100"},
|
||||
&cd.Variable{Value: "104"},
|
||||
&cd.Variable{Value: "108"},
|
||||
&cd.Variable{Value: "112"},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "key", VarTableIndex: 21},
|
||||
&cd.Variable{Name: "value", VarTableIndex: 22},
|
||||
},
|
||||
},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "key", VarTableIndex: 23},
|
||||
&cd.Variable{Name: "value", VarTableIndex: 24},
|
||||
},
|
||||
},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "key", VarTableIndex: 25},
|
||||
&cd.Variable{
|
||||
Name: "value",
|
||||
Status: &cd.StatusMessage{
|
||||
Description: &cd.FormatMessage{
|
||||
Format: "$0",
|
||||
Parameters: []string{"Not captured"},
|
||||
},
|
||||
IsError: true,
|
||||
RefersTo: "VARIABLE_NAME",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&cd.Variable{Value: "246"},
|
||||
&cd.Variable{Value: "210"},
|
||||
&cd.Variable{Value: "300"},
|
||||
&cd.Variable{Value: "304"},
|
||||
&cd.Variable{Value: "400"},
|
||||
&cd.Variable{Value: "404"},
|
||||
&cd.Variable{Value: "1400"},
|
||||
&cd.Variable{Value: "1404"},
|
||||
&cd.Variable{Value: "2400"},
|
||||
}
|
||||
if !reflect.DeepEqual(v, expectedValues) {
|
||||
t.Errorf("ReadValues: got %v want %v", v, expectedValues)
|
||||
// Do element-by-element comparisons, for more useful error messages.
|
||||
for i := range v {
|
||||
if i < len(expectedValues) && !reflect.DeepEqual(v[i], expectedValues[i]) {
|
||||
t.Errorf("element %d: got %+v want %+v", i, *v[i], *expectedValues[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Program implements the similarly-named interface in x/debug.
|
||||
// ValueCollector should only call its Value and MapElement methods.
|
||||
type Program struct {
|
||||
debug.Program
|
||||
}
|
||||
|
||||
func (p *Program) Value(v debug.Var) (debug.Value, error) {
|
||||
// We determine what to return using v.TypeID.
|
||||
switch v.TypeID {
|
||||
case int16Type:
|
||||
// We use the address as the value, so that we're testing whether the right
|
||||
// address was calculated.
|
||||
return int16(v.Address), nil
|
||||
case stringType:
|
||||
// A string.
|
||||
return "hello", nil
|
||||
case structType:
|
||||
// A struct with two elements.
|
||||
return debug.Struct{
|
||||
Fields: []debug.StructField{
|
||||
{
|
||||
Name: "x",
|
||||
Var: debug.Var{int16Type, 0x1},
|
||||
},
|
||||
{
|
||||
Name: "y",
|
||||
Var: debug.Var{stringType, 0x2},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case pointerType:
|
||||
// A pointer to the first variable above.
|
||||
return debug.Pointer{int16Type, 0x1}, nil
|
||||
case arrayType:
|
||||
// An array of 4 32-bit-wide elements.
|
||||
return debug.Array{
|
||||
ElementTypeID: int32Type,
|
||||
Address: 0x64,
|
||||
Length: 4,
|
||||
StrideBits: 32,
|
||||
}, nil
|
||||
case debugStringType:
|
||||
return debug.String{
|
||||
Length: 5,
|
||||
String: "world",
|
||||
}, nil
|
||||
case mapType:
|
||||
return debug.Map{
|
||||
TypeID: 99,
|
||||
Address: 0x100,
|
||||
Length: 3,
|
||||
}, nil
|
||||
case channelType:
|
||||
return debug.Channel{
|
||||
ElementTypeID: int32Type,
|
||||
Address: 200,
|
||||
Buffer: 210,
|
||||
Length: 2,
|
||||
Capacity: 10,
|
||||
Stride: 4,
|
||||
BufferStart: 9,
|
||||
}, nil
|
||||
case sliceType:
|
||||
// A slice of 2 32-bit-wide elements.
|
||||
return debug.Slice{
|
||||
Array: debug.Array{
|
||||
ElementTypeID: int32Type,
|
||||
Address: 300,
|
||||
Length: 2,
|
||||
StrideBits: 32,
|
||||
},
|
||||
Capacity: 50,
|
||||
}, nil
|
||||
case int32Type:
|
||||
// We use the address as the value, so that we're testing whether the right
|
||||
// address was calculated.
|
||||
return int32(v.Address), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected Value request")
|
||||
}
|
||||
|
||||
func (p *Program) MapElement(m debug.Map, index uint64) (debug.Var, debug.Var, error) {
|
||||
return debug.Var{TypeID: int16Type, Address: 1000*index + 400},
|
||||
debug.Var{TypeID: int32Type, Address: 1000*index + 404},
|
||||
nil
|
||||
}
|
||||
|
||||
func TestLogString(t *testing.T) {
|
||||
bp := cd.Breakpoint{
|
||||
Action: "LOG",
|
||||
LogMessageFormat: "$0 hello, $$7world! $1 $2 $3 $4 $5$6 $7 $8",
|
||||
EvaluatedExpressions: []*cd.Variable{
|
||||
&cd.Variable{Name: "a", VarTableIndex: 1},
|
||||
&cd.Variable{Name: "b", VarTableIndex: 2},
|
||||
&cd.Variable{Name: "c", VarTableIndex: 3},
|
||||
&cd.Variable{Name: "d", VarTableIndex: 4},
|
||||
&cd.Variable{Name: "e", VarTableIndex: 5},
|
||||
&cd.Variable{Name: "f", VarTableIndex: 6},
|
||||
&cd.Variable{Name: "g", VarTableIndex: 7},
|
||||
&cd.Variable{Name: "h", VarTableIndex: 8},
|
||||
&cd.Variable{Name: "i", VarTableIndex: 9},
|
||||
},
|
||||
}
|
||||
varTable := []*cd.Variable{
|
||||
&cd.Variable{},
|
||||
&cd.Variable{Value: "1"},
|
||||
&cd.Variable{Value: `"hello"`},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "x", Value: "1"},
|
||||
&cd.Variable{Name: "y", Value: `"hello"`},
|
||||
&cd.Variable{Name: "z", VarTableIndex: 3},
|
||||
},
|
||||
},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{VarTableIndex: 1},
|
||||
},
|
||||
Value: "0x1",
|
||||
},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "[0]", VarTableIndex: 10},
|
||||
&cd.Variable{Name: "[1]", VarTableIndex: 11},
|
||||
&cd.Variable{Name: "[2]", VarTableIndex: 12},
|
||||
&cd.Variable{Name: "[3]", VarTableIndex: 13},
|
||||
},
|
||||
Value: "len = 4",
|
||||
},
|
||||
&cd.Variable{Value: `"world"`},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "⚫", VarTableIndex: 14},
|
||||
&cd.Variable{Name: "⚫", VarTableIndex: 15},
|
||||
&cd.Variable{Name: "⚫", VarTableIndex: 16},
|
||||
},
|
||||
Value: "len = 3",
|
||||
},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "[0]", VarTableIndex: 17},
|
||||
&cd.Variable{Name: "[1]", VarTableIndex: 18},
|
||||
},
|
||||
Value: "len = 2",
|
||||
},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "[0]", VarTableIndex: 19},
|
||||
&cd.Variable{Name: "[1]", VarTableIndex: 20},
|
||||
},
|
||||
Value: "len = 2",
|
||||
},
|
||||
&cd.Variable{Value: "100"},
|
||||
&cd.Variable{Value: "104"},
|
||||
&cd.Variable{Value: "108"},
|
||||
&cd.Variable{Value: "112"},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "key", VarTableIndex: 21},
|
||||
&cd.Variable{Name: "value", VarTableIndex: 22},
|
||||
},
|
||||
},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "key", VarTableIndex: 23},
|
||||
&cd.Variable{Name: "value", VarTableIndex: 24},
|
||||
},
|
||||
},
|
||||
&cd.Variable{
|
||||
Members: []*cd.Variable{
|
||||
&cd.Variable{Name: "key", VarTableIndex: 25},
|
||||
&cd.Variable{
|
||||
Name: "value",
|
||||
Status: &cd.StatusMessage{
|
||||
Description: &cd.FormatMessage{
|
||||
Format: "$0",
|
||||
Parameters: []string{"Not captured"},
|
||||
},
|
||||
IsError: true,
|
||||
RefersTo: "VARIABLE_NAME",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&cd.Variable{Value: "246"},
|
||||
&cd.Variable{Value: "210"},
|
||||
&cd.Variable{Value: "300"},
|
||||
&cd.Variable{Value: "304"},
|
||||
&cd.Variable{Value: "400"},
|
||||
&cd.Variable{Value: "404"},
|
||||
&cd.Variable{Value: "1400"},
|
||||
&cd.Variable{Value: "1404"},
|
||||
&cd.Variable{Value: "2400"},
|
||||
}
|
||||
s := LogString(bp.LogMessageFormat, bp.EvaluatedExpressions, varTable)
|
||||
expected := `LOGPOINT: 1 hello, $7world! "hello" {x:1, y:"hello", z:...} ` +
|
||||
`0x1 {100, 104, 108, 112} "world"{400:404, 1400:1404, 2400:(Not captured)} ` +
|
||||
`{246, 210} {300, 304}`
|
||||
if s != expected {
|
||||
t.Errorf("LogString: got %q want %q", s, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseToken(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
s string
|
||||
max int
|
||||
num int
|
||||
n int
|
||||
ok bool
|
||||
}{
|
||||
{"", 0, 0, 0, false},
|
||||
{".", 0, 0, 0, false},
|
||||
{"0", 0, 0, 1, true},
|
||||
{"0", 1, 0, 1, true},
|
||||
{"00", 0, 0, 2, true},
|
||||
{"1.", 1, 1, 1, true},
|
||||
{"1.", 0, 0, 0, false},
|
||||
{"10", 10, 10, 2, true},
|
||||
{"10..", 10, 10, 2, true},
|
||||
{"10", 11, 10, 2, true},
|
||||
{"10..", 11, 10, 2, true},
|
||||
{"10", 9, 0, 0, false},
|
||||
{"10..", 9, 0, 0, false},
|
||||
{" 10", 10, 0, 0, false},
|
||||
{"010", 10, 10, 3, true},
|
||||
{"123456789", 123456789, 123456789, 9, true},
|
||||
{"123456789", 123456788, 0, 0, false},
|
||||
{"123456789123456789123456789", 999999999, 0, 0, false},
|
||||
} {
|
||||
num, n, ok := parseToken(c.s, c.max)
|
||||
if ok != c.ok {
|
||||
t.Errorf("parseToken(%q, %d): got ok=%t want ok=%t", c.s, c.max, ok, c.ok)
|
||||
continue
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if num != c.num || n != c.n {
|
||||
t.Errorf("parseToken(%q, %d): got %d,%d,%t want %d,%d,%t", c.s, c.max, num, n, ok, c.num, c.n, c.ok)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue