blob: 30da2cc31bcd0cb7215772303af16f8cb03d2451 [file] [log] [blame]
// Copyright 2016 Google LLC
//
// 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"
"testing"
"cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug"
"cloud.google.com/go/internal/testutil"
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{TypeID: int16Type, Address: 0x1}},
{Name: "b", Var: debug.Var{TypeID: stringType, Address: 0x2}},
{Name: "c", Var: debug.Var{TypeID: structType, Address: 0x3}},
{Name: "d", Var: debug.Var{TypeID: pointerType, Address: 0x4}},
{Name: "e", Var: debug.Var{TypeID: arrayType, Address: 0x5}},
{Name: "f", Var: debug.Var{TypeID: debugStringType, Address: 0x6}},
{Name: "g", Var: debug.Var{TypeID: mapType, Address: 0x7}},
{Name: "h", Var: debug.Var{TypeID: channelType, Address: 0x8}},
{Name: "i", Var: debug.Var{TypeID: sliceType, Address: 0x9}},
}
expectedResults := []*cd.Variable{
{Name: "a", VarTableIndex: 1},
{Name: "b", VarTableIndex: 2},
{Name: "c", VarTableIndex: 3},
{Name: "d", VarTableIndex: 4},
{Name: "e", VarTableIndex: 5},
{Name: "f", VarTableIndex: 6},
{Name: "g", VarTableIndex: 7},
{Name: "h", VarTableIndex: 8},
{Name: "i", VarTableIndex: 9},
}
for i, v := range variablesToAdd {
added := c.AddVariable(v)
if !testutil.Equal(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{
{},
{Value: "1"},
{Value: `"hello"`},
{
Members: []*cd.Variable{
{Name: "x", VarTableIndex: 1},
{Name: "y", VarTableIndex: 2},
},
},
{
Members: []*cd.Variable{
{VarTableIndex: 1},
},
Value: "0x1",
},
{
Members: []*cd.Variable{
{Name: "[0]", VarTableIndex: 10},
{Name: "[1]", VarTableIndex: 11},
{Name: "[2]", VarTableIndex: 12},
{Name: "[3]", VarTableIndex: 13},
},
Value: "len = 4",
},
{Value: `"world"`},
{
Members: []*cd.Variable{
{Name: "⚫", VarTableIndex: 14},
{Name: "⚫", VarTableIndex: 15},
{Name: "⚫", VarTableIndex: 16},
},
Value: "len = 3",
},
{
Members: []*cd.Variable{
{Name: "[0]", VarTableIndex: 17},
{Name: "[1]", VarTableIndex: 18},
},
Value: "len = 2",
},
{
Members: []*cd.Variable{
{Name: "[0]", VarTableIndex: 19},
{Name: "[1]", VarTableIndex: 20},
},
Value: "len = 2",
},
{Value: "100"},
{Value: "104"},
{Value: "108"},
{Value: "112"},
{
Members: []*cd.Variable{
{Name: "key", VarTableIndex: 21},
{Name: "value", VarTableIndex: 22},
},
},
{
Members: []*cd.Variable{
{Name: "key", VarTableIndex: 23},
{Name: "value", VarTableIndex: 24},
},
},
{
Members: []*cd.Variable{
{Name: "key", VarTableIndex: 25},
{
Name: "value",
Status: &cd.StatusMessage{
Description: &cd.FormatMessage{
Format: "$0",
Parameters: []string{"Not captured"},
},
IsError: true,
RefersTo: "VARIABLE_NAME",
},
},
},
},
{Value: "246"},
{Value: "210"},
{Value: "300"},
{Value: "304"},
{Value: "400"},
{Value: "404"},
{Value: "1400"},
{Value: "1404"},
{Value: "2400"},
}
if !testutil.Equal(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) && !testutil.Equal(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{TypeID: int16Type, Address: 0x1},
},
{
Name: "y",
Var: debug.Var{TypeID: stringType, Address: 0x2},
},
},
}, nil
case pointerType:
// A pointer to the first variable above.
return debug.Pointer{TypeID: int16Type, Address: 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{
{Name: "a", VarTableIndex: 1},
{Name: "b", VarTableIndex: 2},
{Name: "c", VarTableIndex: 3},
{Name: "d", VarTableIndex: 4},
{Name: "e", VarTableIndex: 5},
{Name: "f", VarTableIndex: 6},
{Name: "g", VarTableIndex: 7},
{Name: "h", VarTableIndex: 8},
{Name: "i", VarTableIndex: 9},
},
}
varTable := []*cd.Variable{
{},
{Value: "1"},
{Value: `"hello"`},
{
Members: []*cd.Variable{
{Name: "x", Value: "1"},
{Name: "y", Value: `"hello"`},
{Name: "z", VarTableIndex: 3},
},
},
{
Members: []*cd.Variable{
{VarTableIndex: 1},
},
Value: "0x1",
},
{
Members: []*cd.Variable{
{Name: "[0]", VarTableIndex: 10},
{Name: "[1]", VarTableIndex: 11},
{Name: "[2]", VarTableIndex: 12},
{Name: "[3]", VarTableIndex: 13},
},
Value: "len = 4",
},
{Value: `"world"`},
{
Members: []*cd.Variable{
{Name: "⚫", VarTableIndex: 14},
{Name: "⚫", VarTableIndex: 15},
{Name: "⚫", VarTableIndex: 16},
},
Value: "len = 3",
},
{
Members: []*cd.Variable{
{Name: "[0]", VarTableIndex: 17},
{Name: "[1]", VarTableIndex: 18},
},
Value: "len = 2",
},
{
Members: []*cd.Variable{
{Name: "[0]", VarTableIndex: 19},
{Name: "[1]", VarTableIndex: 20},
},
Value: "len = 2",
},
{Value: "100"},
{Value: "104"},
{Value: "108"},
{Value: "112"},
{
Members: []*cd.Variable{
{Name: "key", VarTableIndex: 21},
{Name: "value", VarTableIndex: 22},
},
},
{
Members: []*cd.Variable{
{Name: "key", VarTableIndex: 23},
{Name: "value", VarTableIndex: 24},
},
},
{
Members: []*cd.Variable{
{Name: "key", VarTableIndex: 25},
{
Name: "value",
Status: &cd.StatusMessage{
Description: &cd.FormatMessage{
Format: "$0",
Parameters: []string{"Not captured"},
},
IsError: true,
RefersTo: "VARIABLE_NAME",
},
},
},
},
{Value: "246"},
{Value: "210"},
{Value: "300"},
{Value: "304"},
{Value: "400"},
{Value: "404"},
{Value: "1400"},
{Value: "1404"},
{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)
}
}
}