1// Copyright 2016 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package valuecollector
16
17import (
18	"fmt"
19	"testing"
20
21	"cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug"
22	"cloud.google.com/go/internal/testutil"
23	cd "google.golang.org/api/clouddebugger/v2"
24)
25
26const (
27	// Some arbitrary type IDs for the test, for use in debug.Var's TypeID field.
28	// A TypeID of 0 means the type is unknown, so we start at 1.
29	int16Type = iota + 1
30	stringType
31	structType
32	pointerType
33	arrayType
34	int32Type
35	debugStringType
36	mapType
37	channelType
38	sliceType
39)
40
41func TestValueCollector(t *testing.T) {
42	// Construct the collector.
43	c := NewCollector(&Program{}, 26)
44	// Add some variables of various types, whose values we want the collector to read.
45	variablesToAdd := []debug.LocalVar{
46		{Name: "a", Var: debug.Var{TypeID: int16Type, Address: 0x1}},
47		{Name: "b", Var: debug.Var{TypeID: stringType, Address: 0x2}},
48		{Name: "c", Var: debug.Var{TypeID: structType, Address: 0x3}},
49		{Name: "d", Var: debug.Var{TypeID: pointerType, Address: 0x4}},
50		{Name: "e", Var: debug.Var{TypeID: arrayType, Address: 0x5}},
51		{Name: "f", Var: debug.Var{TypeID: debugStringType, Address: 0x6}},
52		{Name: "g", Var: debug.Var{TypeID: mapType, Address: 0x7}},
53		{Name: "h", Var: debug.Var{TypeID: channelType, Address: 0x8}},
54		{Name: "i", Var: debug.Var{TypeID: sliceType, Address: 0x9}},
55	}
56	expectedResults := []*cd.Variable{
57		{Name: "a", VarTableIndex: 1},
58		{Name: "b", VarTableIndex: 2},
59		{Name: "c", VarTableIndex: 3},
60		{Name: "d", VarTableIndex: 4},
61		{Name: "e", VarTableIndex: 5},
62		{Name: "f", VarTableIndex: 6},
63		{Name: "g", VarTableIndex: 7},
64		{Name: "h", VarTableIndex: 8},
65		{Name: "i", VarTableIndex: 9},
66	}
67	for i, v := range variablesToAdd {
68		added := c.AddVariable(v)
69		if !testutil.Equal(added, expectedResults[i]) {
70			t.Errorf("AddVariable: got %+v want %+v", *added, *expectedResults[i])
71		}
72	}
73	// Read the values, compare the output to what we expect.
74	v := c.ReadValues()
75	expectedValues := []*cd.Variable{
76		{},
77		{Value: "1"},
78		{Value: `"hello"`},
79		{
80			Members: []*cd.Variable{
81				{Name: "x", VarTableIndex: 1},
82				{Name: "y", VarTableIndex: 2},
83			},
84		},
85		{
86			Members: []*cd.Variable{
87				{VarTableIndex: 1},
88			},
89			Value: "0x1",
90		},
91		{
92			Members: []*cd.Variable{
93				{Name: "[0]", VarTableIndex: 10},
94				{Name: "[1]", VarTableIndex: 11},
95				{Name: "[2]", VarTableIndex: 12},
96				{Name: "[3]", VarTableIndex: 13},
97			},
98			Value: "len = 4",
99		},
100		{Value: `"world"`},
101		{
102			Members: []*cd.Variable{
103				{Name: "⚫", VarTableIndex: 14},
104				{Name: "⚫", VarTableIndex: 15},
105				{Name: "⚫", VarTableIndex: 16},
106			},
107			Value: "len = 3",
108		},
109		{
110			Members: []*cd.Variable{
111				{Name: "[0]", VarTableIndex: 17},
112				{Name: "[1]", VarTableIndex: 18},
113			},
114			Value: "len = 2",
115		},
116		{
117			Members: []*cd.Variable{
118				{Name: "[0]", VarTableIndex: 19},
119				{Name: "[1]", VarTableIndex: 20},
120			},
121			Value: "len = 2",
122		},
123		{Value: "100"},
124		{Value: "104"},
125		{Value: "108"},
126		{Value: "112"},
127		{
128			Members: []*cd.Variable{
129				{Name: "key", VarTableIndex: 21},
130				{Name: "value", VarTableIndex: 22},
131			},
132		},
133		{
134			Members: []*cd.Variable{
135				{Name: "key", VarTableIndex: 23},
136				{Name: "value", VarTableIndex: 24},
137			},
138		},
139		{
140			Members: []*cd.Variable{
141				{Name: "key", VarTableIndex: 25},
142				{
143					Name: "value",
144					Status: &cd.StatusMessage{
145						Description: &cd.FormatMessage{
146							Format:     "$0",
147							Parameters: []string{"Not captured"},
148						},
149						IsError:  true,
150						RefersTo: "VARIABLE_NAME",
151					},
152				},
153			},
154		},
155		{Value: "246"},
156		{Value: "210"},
157		{Value: "300"},
158		{Value: "304"},
159		{Value: "400"},
160		{Value: "404"},
161		{Value: "1400"},
162		{Value: "1404"},
163		{Value: "2400"},
164	}
165	if !testutil.Equal(v, expectedValues) {
166		t.Errorf("ReadValues: got %v want %v", v, expectedValues)
167		// Do element-by-element comparisons, for more useful error messages.
168		for i := range v {
169			if i < len(expectedValues) && !testutil.Equal(v[i], expectedValues[i]) {
170				t.Errorf("element %d: got %+v want %+v", i, *v[i], *expectedValues[i])
171			}
172		}
173	}
174}
175
176// Program implements the similarly-named interface in x/debug.
177// ValueCollector should only call its Value and MapElement methods.
178type Program struct {
179	debug.Program
180}
181
182func (p *Program) Value(v debug.Var) (debug.Value, error) {
183	// We determine what to return using v.TypeID.
184	switch v.TypeID {
185	case int16Type:
186		// We use the address as the value, so that we're testing whether the right
187		// address was calculated.
188		return int16(v.Address), nil
189	case stringType:
190		// A string.
191		return "hello", nil
192	case structType:
193		// A struct with two elements.
194		return debug.Struct{
195			Fields: []debug.StructField{
196				{
197					Name: "x",
198					Var:  debug.Var{TypeID: int16Type, Address: 0x1},
199				},
200				{
201					Name: "y",
202					Var:  debug.Var{TypeID: stringType, Address: 0x2},
203				},
204			},
205		}, nil
206	case pointerType:
207		// A pointer to the first variable above.
208		return debug.Pointer{TypeID: int16Type, Address: 0x1}, nil
209	case arrayType:
210		// An array of 4 32-bit-wide elements.
211		return debug.Array{
212			ElementTypeID: int32Type,
213			Address:       0x64,
214			Length:        4,
215			StrideBits:    32,
216		}, nil
217	case debugStringType:
218		return debug.String{
219			Length: 5,
220			String: "world",
221		}, nil
222	case mapType:
223		return debug.Map{
224			TypeID:  99,
225			Address: 0x100,
226			Length:  3,
227		}, nil
228	case channelType:
229		return debug.Channel{
230			ElementTypeID: int32Type,
231			Address:       200,
232			Buffer:        210,
233			Length:        2,
234			Capacity:      10,
235			Stride:        4,
236			BufferStart:   9,
237		}, nil
238	case sliceType:
239		// A slice of 2 32-bit-wide elements.
240		return debug.Slice{
241			Array: debug.Array{
242				ElementTypeID: int32Type,
243				Address:       300,
244				Length:        2,
245				StrideBits:    32,
246			},
247			Capacity: 50,
248		}, nil
249	case int32Type:
250		// We use the address as the value, so that we're testing whether the right
251		// address was calculated.
252		return int32(v.Address), nil
253	}
254	return nil, fmt.Errorf("unexpected Value request")
255}
256
257func (p *Program) MapElement(m debug.Map, index uint64) (debug.Var, debug.Var, error) {
258	return debug.Var{TypeID: int16Type, Address: 1000*index + 400},
259		debug.Var{TypeID: int32Type, Address: 1000*index + 404},
260		nil
261}
262
263func TestLogString(t *testing.T) {
264	bp := cd.Breakpoint{
265		Action:           "LOG",
266		LogMessageFormat: "$0 hello, $$7world! $1 $2 $3 $4 $5$6 $7 $8",
267		EvaluatedExpressions: []*cd.Variable{
268			{Name: "a", VarTableIndex: 1},
269			{Name: "b", VarTableIndex: 2},
270			{Name: "c", VarTableIndex: 3},
271			{Name: "d", VarTableIndex: 4},
272			{Name: "e", VarTableIndex: 5},
273			{Name: "f", VarTableIndex: 6},
274			{Name: "g", VarTableIndex: 7},
275			{Name: "h", VarTableIndex: 8},
276			{Name: "i", VarTableIndex: 9},
277		},
278	}
279	varTable := []*cd.Variable{
280		{},
281		{Value: "1"},
282		{Value: `"hello"`},
283		{
284			Members: []*cd.Variable{
285				{Name: "x", Value: "1"},
286				{Name: "y", Value: `"hello"`},
287				{Name: "z", VarTableIndex: 3},
288			},
289		},
290		{
291			Members: []*cd.Variable{
292				{VarTableIndex: 1},
293			},
294			Value: "0x1",
295		},
296		{
297			Members: []*cd.Variable{
298				{Name: "[0]", VarTableIndex: 10},
299				{Name: "[1]", VarTableIndex: 11},
300				{Name: "[2]", VarTableIndex: 12},
301				{Name: "[3]", VarTableIndex: 13},
302			},
303			Value: "len = 4",
304		},
305		{Value: `"world"`},
306		{
307			Members: []*cd.Variable{
308				{Name: "⚫", VarTableIndex: 14},
309				{Name: "⚫", VarTableIndex: 15},
310				{Name: "⚫", VarTableIndex: 16},
311			},
312			Value: "len = 3",
313		},
314		{
315			Members: []*cd.Variable{
316				{Name: "[0]", VarTableIndex: 17},
317				{Name: "[1]", VarTableIndex: 18},
318			},
319			Value: "len = 2",
320		},
321		{
322			Members: []*cd.Variable{
323				{Name: "[0]", VarTableIndex: 19},
324				{Name: "[1]", VarTableIndex: 20},
325			},
326			Value: "len = 2",
327		},
328		{Value: "100"},
329		{Value: "104"},
330		{Value: "108"},
331		{Value: "112"},
332		{
333			Members: []*cd.Variable{
334				{Name: "key", VarTableIndex: 21},
335				{Name: "value", VarTableIndex: 22},
336			},
337		},
338		{
339			Members: []*cd.Variable{
340				{Name: "key", VarTableIndex: 23},
341				{Name: "value", VarTableIndex: 24},
342			},
343		},
344		{
345			Members: []*cd.Variable{
346				{Name: "key", VarTableIndex: 25},
347				{
348					Name: "value",
349					Status: &cd.StatusMessage{
350						Description: &cd.FormatMessage{
351							Format:     "$0",
352							Parameters: []string{"Not captured"},
353						},
354						IsError:  true,
355						RefersTo: "VARIABLE_NAME",
356					},
357				},
358			},
359		},
360		{Value: "246"},
361		{Value: "210"},
362		{Value: "300"},
363		{Value: "304"},
364		{Value: "400"},
365		{Value: "404"},
366		{Value: "1400"},
367		{Value: "1404"},
368		{Value: "2400"},
369	}
370	s := LogString(bp.LogMessageFormat, bp.EvaluatedExpressions, varTable)
371	expected := `LOGPOINT: 1 hello, $7world! "hello" {x:1, y:"hello", z:...} ` +
372		`0x1 {100, 104, 108, 112} "world"{400:404, 1400:1404, 2400:(Not captured)} ` +
373		`{246, 210} {300, 304}`
374	if s != expected {
375		t.Errorf("LogString: got %q want %q", s, expected)
376	}
377}
378
379func TestParseToken(t *testing.T) {
380	for _, c := range []struct {
381		s   string
382		max int
383		num int
384		n   int
385		ok  bool
386	}{
387		{"", 0, 0, 0, false},
388		{".", 0, 0, 0, false},
389		{"0", 0, 0, 1, true},
390		{"0", 1, 0, 1, true},
391		{"00", 0, 0, 2, true},
392		{"1.", 1, 1, 1, true},
393		{"1.", 0, 0, 0, false},
394		{"10", 10, 10, 2, true},
395		{"10..", 10, 10, 2, true},
396		{"10", 11, 10, 2, true},
397		{"10..", 11, 10, 2, true},
398		{"10", 9, 0, 0, false},
399		{"10..", 9, 0, 0, false},
400		{" 10", 10, 0, 0, false},
401		{"010", 10, 10, 3, true},
402		{"123456789", 123456789, 123456789, 9, true},
403		{"123456789", 123456788, 0, 0, false},
404		{"123456789123456789123456789", 999999999, 0, 0, false},
405	} {
406		num, n, ok := parseToken(c.s, c.max)
407		if ok != c.ok {
408			t.Errorf("parseToken(%q, %d): got ok=%t want ok=%t", c.s, c.max, ok, c.ok)
409			continue
410		}
411		if !ok {
412			continue
413		}
414		if num != c.num || n != c.n {
415			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)
416		}
417	}
418}
419