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 			}{
    118 				Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"},
    119 				Empty: []string{},
    120 				Nil:   nil,
    121 			},
    122 		},
    123 	},
    124 
    125 	{
    126 		input: `
    127 			m {
    128 				nested: {
    129 					name: "abc",
    130 				}
    131 			}
    132 		`,
    133 		output: []interface{}{
    134 			struct {
    135 				Nested struct {
    136 					Name string
    137 				}
    138 			}{
    139 				Nested: struct{ Name string }{
    140 					Name: "abc",
    141 				},
    142 			},
    143 		},
    144 	},
    145 
    146 	{
    147 		input: `
    148 			m {
    149 				nested: {
    150 					name: "def",
    151 				}
    152 			}
    153 		`,
    154 		output: []interface{}{
    155 			struct {
    156 				Nested interface{}
    157 			}{
    158 				Nested: &struct{ Name string }{
    159 					Name: "def",
    160 				},
    161 			},
    162 		},
    163 	},
    164 
    165 	{
    166 		input: `
    167 			m {
    168 				nested: {
    169 					foo: "abc",
    170 				},
    171 				bar: false,
    172 				baz: ["def", "ghi"],
    173 			}
    174 		`,
    175 		output: []interface{}{
    176 			struct {
    177 				Nested struct {
    178 					Foo string
    179 				}
    180 				Bar bool
    181 				Baz []string
    182 			}{
    183 				Nested: struct{ Foo string }{
    184 					Foo: "abc",
    185 				},
    186 				Bar: false,
    187 				Baz: []string{"def", "ghi"},
    188 			},
    189 		},
    190 	},
    191 
    192 	{
    193 		input: `
    194 			m {
    195 				nested: {
    196 					foo: "abc",
    197 				},
    198 				bar: false,
    199 				baz: ["def", "ghi"],
    200 			}
    201 		`,
    202 		output: []interface{}{
    203 			struct {
    204 				Nested struct {
    205 					Foo string `allowNested:"true"`
    206 				} `blueprint:"filter(allowNested:\"true\")"`
    207 				Bar bool
    208 				Baz []string
    209 			}{
    210 				Nested: struct {
    211 					Foo string `allowNested:"true"`
    212 				}{
    213 					Foo: "abc",
    214 				},
    215 				Bar: false,
    216 				Baz: []string{"def", "ghi"},
    217 			},
    218 		},
    219 	},
    220 
    221 	{
    222 		input: `
    223 			m {
    224 				nested: {
    225 					foo: "abc",
    226 				},
    227 				bar: false,
    228 				baz: ["def", "ghi"],
    229 			}
    230 		`,
    231 		output: []interface{}{
    232 			struct {
    233 				Nested struct {
    234 					Foo string
    235 				} `blueprint:"filter(allowNested:\"true\")"`
    236 				Bar bool
    237 				Baz []string
    238 			}{
    239 				Nested: struct{ Foo string }{
    240 					Foo: "",
    241 				},
    242 				Bar: false,
    243 				Baz: []string{"def", "ghi"},
    244 			},
    245 		},
    246 		errs: []error{
    247 			&BlueprintError{
    248 				Err: fmt.Errorf("filtered field nested.foo cannot be set in a Blueprint file"),
    249 				Pos: mkpos(30, 4, 9),
    250 			},
    251 		},
    252 	},
    253 
    254 	// Anonymous struct
    255 	{
    256 		input: `
    257 			m {
    258 				name: "abc",
    259 				nested: {
    260 					name: "def",
    261 				},
    262 			}
    263 		`,
    264 		output: []interface{}{
    265 			struct {
    266 				EmbeddedStruct
    267 				Nested struct {
    268 					EmbeddedStruct
    269 				}
    270 			}{
    271 				EmbeddedStruct: EmbeddedStruct{
    272 					Name: "abc",
    273 				},
    274 				Nested: struct {
    275 					EmbeddedStruct
    276 				}{
    277 					EmbeddedStruct: EmbeddedStruct{
    278 						Name: "def",
    279 					},
    280 				},
    281 			},
    282 		},
    283 	},
    284 
    285 	// Anonymous interface
    286 	{
    287 		input: `
    288 			m {
    289 				name: "abc",
    290 				nested: {
    291 					name: "def",
    292 				},
    293 			}
    294 		`,
    295 		output: []interface{}{
    296 			struct {
    297 				EmbeddedInterface
    298 				Nested struct {
    299 					EmbeddedInterface
    300 				}
    301 			}{
    302 				EmbeddedInterface: &struct{ Name string }{
    303 					Name: "abc",
    304 				},
    305 				Nested: struct {
    306 					EmbeddedInterface
    307 				}{
    308 					EmbeddedInterface: &struct{ Name string }{
    309 						Name: "def",
    310 					},
    311 				},
    312 			},
    313 		},
    314 	},
    315 
    316 	// Anonymous struct with name collision
    317 	{
    318 		input: `
    319 			m {
    320 				name: "abc",
    321 				nested: {
    322 					name: "def",
    323 				},
    324 			}
    325 		`,
    326 		output: []interface{}{
    327 			struct {
    328 				Name string
    329 				EmbeddedStruct
    330 				Nested struct {
    331 					Name string
    332 					EmbeddedStruct
    333 				}
    334 			}{
    335 				Name: "abc",
    336 				EmbeddedStruct: EmbeddedStruct{
    337 					Name: "abc",
    338 				},
    339 				Nested: struct {
    340 					Name string
    341 					EmbeddedStruct
    342 				}{
    343 					Name: "def",
    344 					EmbeddedStruct: EmbeddedStruct{
    345 						Name: "def",
    346 					},
    347 				},
    348 			},
    349 		},
    350 	},
    351 
    352 	// Anonymous interface with name collision
    353 	{
    354 		input: `
    355 			m {
    356 				name: "abc",
    357 				nested: {
    358 					name: "def",
    359 				},
    360 			}
    361 		`,
    362 		output: []interface{}{
    363 			struct {
    364 				Name string
    365 				EmbeddedInterface
    366 				Nested struct {
    367 					Name string
    368 					EmbeddedInterface
    369 				}
    370 			}{
    371 				Name: "abc",
    372 				EmbeddedInterface: &struct{ Name string }{
    373 					Name: "abc",
    374 				},
    375 				Nested: struct {
    376 					Name string
    377 					EmbeddedInterface
    378 				}{
    379 					Name: "def",
    380 					EmbeddedInterface: &struct{ Name string }{
    381 						Name: "def",
    382 					},
    383 				},
    384 			},
    385 		},
    386 	},
    387 
    388 	// Variables
    389 	{
    390 		input: `
    391 			list = ["abc"]
    392 			string = "def"
    393 			list_with_variable = [string]
    394 			m {
    395 				name: string,
    396 				list: list,
    397 				list2: list_with_variable,
    398 			}
    399 		`,
    400 		output: []interface{}{
    401 			struct {
    402 				Name  string
    403 				List  []string
    404 				List2 []string
    405 			}{
    406 				Name:  "def",
    407 				List:  []string{"abc"},
    408 				List2: []string{"def"},
    409 			},
    410 		},
    411 	},
    412 
    413 	// Multiple property structs
    414 	{
    415 		input: `
    416 			m {
    417 				nested: {
    418 					name: "abc",
    419 				}
    420 			}
    421 		`,
    422 		output: []interface{}{
    423 			struct {
    424 				Nested struct {
    425 					Name string
    426 				}
    427 			}{
    428 				Nested: struct{ Name string }{
    429 					Name: "abc",
    430 				},
    431 			},
    432 			struct {
    433 				Nested struct {
    434 					Name string
    435 				}
    436 			}{
    437 				Nested: struct{ Name string }{
    438 					Name: "abc",
    439 				},
    440 			},
    441 			struct {
    442 			}{},
    443 		},
    444 	},
    445 
    446 	// Nil pointer to struct
    447 	{
    448 		input: `
    449 			m {
    450 				nested: {
    451 					name: "abc",
    452 				}
    453 			}
    454 		`,
    455 		output: []interface{}{
    456 			struct {
    457 				Nested *struct {
    458 					Name string
    459 				}
    460 			}{
    461 				Nested: &struct{ Name string }{
    462 					Name: "abc",
    463 				},
    464 			},
    465 		},
    466 		empty: []interface{}{
    467 			&struct {
    468 				Nested *struct {
    469 					Name string
    470 				}
    471 			}{},
    472 		},
    473 	},
    474 
    475 	// Interface containing nil pointer to struct
    476 	{
    477 		input: `
    478 			m {
    479 				nested: {
    480 					name: "abc",
    481 				}
    482 			}
    483 		`,
    484 		output: []interface{}{
    485 			struct {
    486 				Nested interface{}
    487 			}{
    488 				Nested: &EmbeddedStruct{
    489 					Name: "abc",
    490 				},
    491 			},
    492 		},
    493 		empty: []interface{}{
    494 			&struct {
    495 				Nested interface{}
    496 			}{
    497 				Nested: (*EmbeddedStruct)(nil),
    498 			},
    499 		},
    500 	},
    501 }
    502 
    503 type EmbeddedStruct struct{ Name string }
    504 type EmbeddedInterface interface{}
    505 
    506 func TestUnpackProperties(t *testing.T) {
    507 	for _, testCase := range validUnpackTestCases {
    508 		r := bytes.NewBufferString(testCase.input)
    509 		file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
    510 		if len(errs) != 0 {
    511 			t.Errorf("test case: %s", testCase.input)
    512 			t.Errorf("unexpected parse errors:")
    513 			for _, err := range errs {
    514 				t.Errorf("  %s", err)
    515 			}
    516 			t.FailNow()
    517 		}
    518 
    519 		for _, def := range file.Defs {
    520 			module, ok := def.(*parser.Module)
    521 			if !ok {
    522 				continue
    523 			}
    524 
    525 			var output []interface{}
    526 			if len(testCase.empty) > 0 {
    527 				output = testCase.empty
    528 			} else {
    529 				for _, p := range testCase.output {
    530 					output = append(output, proptools.CloneEmptyProperties(reflect.ValueOf(p)).Interface())
    531 				}
    532 			}
    533 			_, errs = unpackProperties(module.Properties, output...)
    534 			if len(errs) != 0 && len(testCase.errs) == 0 {
    535 				t.Errorf("test case: %s", testCase.input)
    536 				t.Errorf("unexpected unpack errors:")
    537 				for _, err := range errs {
    538 					t.Errorf("  %s", err)
    539 				}
    540 				t.FailNow()
    541 			} else if !reflect.DeepEqual(errs, testCase.errs) {
    542 				t.Errorf("test case: %s", testCase.input)
    543 				t.Errorf("incorrect errors:")
    544 				t.Errorf("  expected: %+v", testCase.errs)
    545 				t.Errorf("       got: %+v", errs)
    546 			}
    547 
    548 			if len(output) != len(testCase.output) {
    549 				t.Fatalf("incorrect number of property structs, expected %d got %d",
    550 					len(testCase.output), len(output))
    551 			}
    552 
    553 			for i := range output {
    554 				got := reflect.ValueOf(output[i]).Elem().Interface()
    555 				if !reflect.DeepEqual(got, testCase.output[i]) {
    556 					t.Errorf("test case: %s", testCase.input)
    557 					t.Errorf("incorrect output:")
    558 					t.Errorf("  expected: %+v", testCase.output[i])
    559 					t.Errorf("       got: %+v", got)
    560 				}
    561 			}
    562 		}
    563 	}
    564 }
    565 
    566 func mkpos(offset, line, column int) scanner.Position {
    567 	return scanner.Position{
    568 		Offset: offset,
    569 		Line:   line,
    570 		Column: column,
    571 	}
    572 }
    573