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