1 // Copyright 2014 Google Inc. All rights reserved. 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 15 package blueprint 16 17 import ( 18 "bytes" 19 "fmt" 20 "reflect" 21 "testing" 22 "text/scanner" 23 24 "github.com/google/blueprint/parser" 25 "github.com/google/blueprint/proptools" 26 ) 27 28 var validUnpackTestCases = []struct { 29 input string 30 output []interface{} 31 empty []interface{} 32 errs []error 33 }{ 34 { 35 input: ` 36 m { 37 name: "abc", 38 blank: "", 39 } 40 `, 41 output: []interface{}{ 42 struct { 43 Name *string 44 Blank *string 45 Unset *string 46 }{ 47 Name: proptools.StringPtr("abc"), 48 Blank: proptools.StringPtr(""), 49 Unset: nil, 50 }, 51 }, 52 }, 53 54 { 55 input: ` 56 m { 57 name: "abc", 58 } 59 `, 60 output: []interface{}{ 61 struct { 62 Name string 63 }{ 64 Name: "abc", 65 }, 66 }, 67 }, 68 69 { 70 input: ` 71 m { 72 isGood: true, 73 } 74 `, 75 output: []interface{}{ 76 struct { 77 IsGood bool 78 }{ 79 IsGood: true, 80 }, 81 }, 82 }, 83 84 { 85 input: ` 86 m { 87 isGood: true, 88 isBad: false, 89 } 90 `, 91 output: []interface{}{ 92 struct { 93 IsGood *bool 94 IsBad *bool 95 IsUgly *bool 96 }{ 97 IsGood: proptools.BoolPtr(true), 98 IsBad: proptools.BoolPtr(false), 99 IsUgly: nil, 100 }, 101 }, 102 }, 103 104 { 105 input: ` 106 m { 107 stuff: ["asdf", "jkl;", "qwert", 108 "uiop", "bnm,"], 109 empty: [] 110 } 111 `, 112 output: []interface{}{ 113 struct { 114 Stuff []string 115 Empty []string 116 Nil []string 117 NonString []struct{ S string } `blueprint:"mutated"` 118 }{ 119 Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"}, 120 Empty: []string{}, 121 Nil: nil, 122 NonString: nil, 123 }, 124 }, 125 }, 126 127 { 128 input: ` 129 m { 130 nested: { 131 name: "abc", 132 } 133 } 134 `, 135 output: []interface{}{ 136 struct { 137 Nested struct { 138 Name string 139 } 140 }{ 141 Nested: struct{ Name string }{ 142 Name: "abc", 143 }, 144 }, 145 }, 146 }, 147 148 { 149 input: ` 150 m { 151 nested: { 152 name: "def", 153 } 154 } 155 `, 156 output: []interface{}{ 157 struct { 158 Nested interface{} 159 }{ 160 Nested: &struct{ Name string }{ 161 Name: "def", 162 }, 163 }, 164 }, 165 }, 166 167 { 168 input: ` 169 m { 170 nested: { 171 foo: "abc", 172 }, 173 bar: false, 174 baz: ["def", "ghi"], 175 } 176 `, 177 output: []interface{}{ 178 struct { 179 Nested struct { 180 Foo string 181 } 182 Bar bool 183 Baz []string 184 }{ 185 Nested: struct{ Foo string }{ 186 Foo: "abc", 187 }, 188 Bar: false, 189 Baz: []string{"def", "ghi"}, 190 }, 191 }, 192 }, 193 194 { 195 input: ` 196 m { 197 nested: { 198 foo: "abc", 199 }, 200 bar: false, 201 baz: ["def", "ghi"], 202 } 203 `, 204 output: []interface{}{ 205 struct { 206 Nested struct { 207 Foo string `allowNested:"true"` 208 } `blueprint:"filter(allowNested:\"true\")"` 209 Bar bool 210 Baz []string 211 }{ 212 Nested: struct { 213 Foo string `allowNested:"true"` 214 }{ 215 Foo: "abc", 216 }, 217 Bar: false, 218 Baz: []string{"def", "ghi"}, 219 }, 220 }, 221 }, 222 223 { 224 input: ` 225 m { 226 nested: { 227 foo: "abc", 228 }, 229 bar: false, 230 baz: ["def", "ghi"], 231 } 232 `, 233 output: []interface{}{ 234 struct { 235 Nested struct { 236 Foo string 237 } `blueprint:"filter(allowNested:\"true\")"` 238 Bar bool 239 Baz []string 240 }{ 241 Nested: struct{ Foo string }{ 242 Foo: "", 243 }, 244 Bar: false, 245 Baz: []string{"def", "ghi"}, 246 }, 247 }, 248 errs: []error{ 249 &BlueprintError{ 250 Err: fmt.Errorf("filtered field nested.foo cannot be set in a Blueprint file"), 251 Pos: mkpos(30, 4, 9), 252 }, 253 }, 254 }, 255 256 // Anonymous struct 257 { 258 input: ` 259 m { 260 name: "abc", 261 nested: { 262 name: "def", 263 }, 264 } 265 `, 266 output: []interface{}{ 267 struct { 268 EmbeddedStruct 269 Nested struct { 270 EmbeddedStruct 271 } 272 }{ 273 EmbeddedStruct: EmbeddedStruct{ 274 Name: "abc", 275 }, 276 Nested: struct { 277 EmbeddedStruct 278 }{ 279 EmbeddedStruct: EmbeddedStruct{ 280 Name: "def", 281 }, 282 }, 283 }, 284 }, 285 }, 286 287 // Anonymous interface 288 { 289 input: ` 290 m { 291 name: "abc", 292 nested: { 293 name: "def", 294 }, 295 } 296 `, 297 output: []interface{}{ 298 struct { 299 EmbeddedInterface 300 Nested struct { 301 EmbeddedInterface 302 } 303 }{ 304 EmbeddedInterface: &struct{ Name string }{ 305 Name: "abc", 306 }, 307 Nested: struct { 308 EmbeddedInterface 309 }{ 310 EmbeddedInterface: &struct{ Name string }{ 311 Name: "def", 312 }, 313 }, 314 }, 315 }, 316 }, 317 318 // Anonymous struct with name collision 319 { 320 input: ` 321 m { 322 name: "abc", 323 nested: { 324 name: "def", 325 }, 326 } 327 `, 328 output: []interface{}{ 329 struct { 330 Name string 331 EmbeddedStruct 332 Nested struct { 333 Name string 334 EmbeddedStruct 335 } 336 }{ 337 Name: "abc", 338 EmbeddedStruct: EmbeddedStruct{ 339 Name: "abc", 340 }, 341 Nested: struct { 342 Name string 343 EmbeddedStruct 344 }{ 345 Name: "def", 346 EmbeddedStruct: EmbeddedStruct{ 347 Name: "def", 348 }, 349 }, 350 }, 351 }, 352 }, 353 354 // Anonymous interface with name collision 355 { 356 input: ` 357 m { 358 name: "abc", 359 nested: { 360 name: "def", 361 }, 362 } 363 `, 364 output: []interface{}{ 365 struct { 366 Name string 367 EmbeddedInterface 368 Nested struct { 369 Name string 370 EmbeddedInterface 371 } 372 }{ 373 Name: "abc", 374 EmbeddedInterface: &struct{ Name string }{ 375 Name: "abc", 376 }, 377 Nested: struct { 378 Name string 379 EmbeddedInterface 380 }{ 381 Name: "def", 382 EmbeddedInterface: &struct{ Name string }{ 383 Name: "def", 384 }, 385 }, 386 }, 387 }, 388 }, 389 390 // Variables 391 { 392 input: ` 393 list = ["abc"] 394 string = "def" 395 list_with_variable = [string] 396 m { 397 name: string, 398 list: list, 399 list2: list_with_variable, 400 } 401 `, 402 output: []interface{}{ 403 struct { 404 Name string 405 List []string 406 List2 []string 407 }{ 408 Name: "def", 409 List: []string{"abc"}, 410 List2: []string{"def"}, 411 }, 412 }, 413 }, 414 415 // Multiple property structs 416 { 417 input: ` 418 m { 419 nested: { 420 name: "abc", 421 } 422 } 423 `, 424 output: []interface{}{ 425 struct { 426 Nested struct { 427 Name string 428 } 429 }{ 430 Nested: struct{ Name string }{ 431 Name: "abc", 432 }, 433 }, 434 struct { 435 Nested struct { 436 Name string 437 } 438 }{ 439 Nested: struct{ Name string }{ 440 Name: "abc", 441 }, 442 }, 443 struct { 444 }{}, 445 }, 446 }, 447 448 // Nil pointer to struct 449 { 450 input: ` 451 m { 452 nested: { 453 name: "abc", 454 } 455 } 456 `, 457 output: []interface{}{ 458 struct { 459 Nested *struct { 460 Name string 461 } 462 }{ 463 Nested: &struct{ Name string }{ 464 Name: "abc", 465 }, 466 }, 467 }, 468 empty: []interface{}{ 469 &struct { 470 Nested *struct { 471 Name string 472 } 473 }{}, 474 }, 475 }, 476 477 // Interface containing nil pointer to struct 478 { 479 input: ` 480 m { 481 nested: { 482 name: "abc", 483 } 484 } 485 `, 486 output: []interface{}{ 487 struct { 488 Nested interface{} 489 }{ 490 Nested: &EmbeddedStruct{ 491 Name: "abc", 492 }, 493 }, 494 }, 495 empty: []interface{}{ 496 &struct { 497 Nested interface{} 498 }{ 499 Nested: (*EmbeddedStruct)(nil), 500 }, 501 }, 502 }, 503 504 // Factory set properties 505 { 506 input: ` 507 m { 508 string: "abc", 509 string_ptr: "abc", 510 bool: false, 511 bool_ptr: false, 512 list: ["a", "b", "c"], 513 } 514 `, 515 output: []interface{}{ 516 struct { 517 String string 518 String_ptr *string 519 Bool bool 520 Bool_ptr *bool 521 List []string 522 }{ 523 String: "012abc", 524 String_ptr: proptools.StringPtr("abc"), 525 Bool: true, 526 Bool_ptr: proptools.BoolPtr(false), 527 List: []string{"0", "1", "2", "a", "b", "c"}, 528 }, 529 }, 530 empty: []interface{}{ 531 &struct { 532 String string 533 String_ptr *string 534 Bool bool 535 Bool_ptr *bool 536 List []string 537 }{ 538 String: "012", 539 String_ptr: proptools.StringPtr("012"), 540 Bool: true, 541 Bool_ptr: proptools.BoolPtr(true), 542 List: []string{"0", "1", "2"}, 543 }, 544 }, 545 }, 546 } 547 548 type EmbeddedStruct struct{ Name string } 549 type EmbeddedInterface interface{} 550 551 func TestUnpackProperties(t *testing.T) { 552 for _, testCase := range validUnpackTestCases { 553 r := bytes.NewBufferString(testCase.input) 554 file, errs := parser.ParseAndEval("", r, parser.NewScope(nil)) 555 if len(errs) != 0 { 556 t.Errorf("test case: %s", testCase.input) 557 t.Errorf("unexpected parse errors:") 558 for _, err := range errs { 559 t.Errorf(" %s", err) 560 } 561 t.FailNow() 562 } 563 564 for _, def := range file.Defs { 565 module, ok := def.(*parser.Module) 566 if !ok { 567 continue 568 } 569 570 var output []interface{} 571 if len(testCase.empty) > 0 { 572 output = testCase.empty 573 } else { 574 for _, p := range testCase.output { 575 output = append(output, proptools.CloneEmptyProperties(reflect.ValueOf(p)).Interface()) 576 } 577 } 578 _, errs = unpackProperties(module.Properties, output...) 579 if len(errs) != 0 && len(testCase.errs) == 0 { 580 t.Errorf("test case: %s", testCase.input) 581 t.Errorf("unexpected unpack errors:") 582 for _, err := range errs { 583 t.Errorf(" %s", err) 584 } 585 t.FailNow() 586 } else if !reflect.DeepEqual(errs, testCase.errs) { 587 t.Errorf("test case: %s", testCase.input) 588 t.Errorf("incorrect errors:") 589 t.Errorf(" expected: %+v", testCase.errs) 590 t.Errorf(" got: %+v", errs) 591 } 592 593 if len(output) != len(testCase.output) { 594 t.Fatalf("incorrect number of property structs, expected %d got %d", 595 len(testCase.output), len(output)) 596 } 597 598 for i := range output { 599 got := reflect.ValueOf(output[i]).Elem().Interface() 600 if !reflect.DeepEqual(got, testCase.output[i]) { 601 t.Errorf("test case: %s", testCase.input) 602 t.Errorf("incorrect output:") 603 t.Errorf(" expected: %+v", testCase.output[i]) 604 t.Errorf(" got: %+v", got) 605 } 606 } 607 } 608 } 609 } 610 611 func mkpos(offset, line, column int) scanner.Position { 612 return scanner.Position{ 613 Offset: offset, 614 Line: line, 615 Column: column, 616 } 617 } 618