Home | History | Annotate | Download | only in blueprint
      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