1// Copyright 2020 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 15package bpmodify 16 17import ( 18 "strings" 19 "testing" 20) 21 22func must(err error) { 23 if err != nil { 24 panic(err) 25 } 26} 27 28func must2[T any](v T, err error) T { 29 if err != nil { 30 panic(err) 31 } 32 return v 33} 34 35func simplifyModuleDefinition(def string) string { 36 var result string 37 for _, line := range strings.Split(def, "\n") { 38 result += strings.TrimSpace(line) 39 } 40 return result 41} 42func TestBpModify(t *testing.T) { 43 var testCases = []struct { 44 name string 45 input string 46 output string 47 err string 48 modified bool 49 f func(bp *Blueprint) 50 }{ 51 { 52 name: "add", 53 input: ` 54 cc_foo { 55 name: "foo", 56 } 57 `, 58 output: ` 59 cc_foo { 60 name: "foo", 61 deps: ["bar"], 62 } 63 `, 64 modified: true, 65 f: func(bp *Blueprint) { 66 props := must2(bp.ModulesByName("foo").GetOrCreateProperty(List, "deps")) 67 must(props.AddStringToList("bar")) 68 }, 69 }, 70 { 71 name: "remove", 72 input: ` 73 cc_foo { 74 name: "foo", 75 deps: ["bar"], 76 } 77 `, 78 output: ` 79 cc_foo { 80 name: "foo", 81 deps: [], 82 } 83 `, 84 modified: true, 85 f: func(bp *Blueprint) { 86 props := must2(bp.ModulesByName("foo").GetProperty("deps")) 87 must(props.RemoveStringFromList("bar")) 88 }, 89 }, 90 { 91 name: "nested add", 92 input: ` 93 cc_foo { 94 name: "foo", 95 } 96 `, 97 output: ` 98 cc_foo { 99 name: "foo", 100 arch: { 101 arm: { 102 deps: [ 103 "dep2", 104 "nested_dep",], 105 }, 106 }, 107 } 108 `, 109 modified: true, 110 f: func(bp *Blueprint) { 111 props := must2(bp.ModulesByName("foo").GetOrCreateProperty(List, "arch.arm.deps")) 112 must(props.AddStringToList("nested_dep", "dep2")) 113 }, 114 }, 115 { 116 name: "nested remove", 117 input: ` 118 cc_foo { 119 name: "foo", 120 arch: { 121 arm: { 122 deps: [ 123 "dep2", 124 "nested_dep", 125 ], 126 }, 127 }, 128 } 129 `, 130 output: ` 131 cc_foo { 132 name: "foo", 133 arch: { 134 arm: { 135 deps: [ 136 ], 137 }, 138 }, 139 } 140 `, 141 modified: true, 142 f: func(bp *Blueprint) { 143 props := must2(bp.ModulesByName("foo").GetProperty("arch.arm.deps")) 144 must(props.RemoveStringFromList("nested_dep", "dep2")) 145 }, 146 }, 147 { 148 name: "add existing", 149 input: ` 150 cc_foo { 151 name: "foo", 152 arch: { 153 arm: { 154 deps: [ 155 "nested_dep", 156 "dep2", 157 ], 158 }, 159 }, 160 } 161 `, 162 output: ` 163 cc_foo { 164 name: "foo", 165 arch: { 166 arm: { 167 deps: [ 168 "nested_dep", 169 "dep2", 170 ], 171 }, 172 }, 173 } 174 `, 175 modified: false, 176 f: func(bp *Blueprint) { 177 props := must2(bp.ModulesByName("foo").GetOrCreateProperty(List, "arch.arm.deps")) 178 must(props.AddStringToList("dep2", "dep2")) 179 }, 180 }, 181 { 182 name: "remove missing", 183 input: ` 184 cc_foo { 185 name: "foo", 186 arch: { 187 arm: { 188 deps: [ 189 "nested_dep", 190 "dep2", 191 ], 192 }, 193 }, 194 } 195 `, 196 output: ` 197 cc_foo { 198 name: "foo", 199 arch: { 200 arm: { 201 deps: [ 202 "nested_dep", 203 "dep2", 204 ], 205 }, 206 }, 207 } 208 `, 209 modified: false, 210 f: func(bp *Blueprint) { 211 props := must2(bp.ModulesByName("foo").GetProperty("arch.arm.deps")) 212 must(props.RemoveStringFromList("dep3", "dep4")) 213 }, 214 }, 215 { 216 name: "remove non existent", 217 input: ` 218 cc_foo { 219 name: "foo", 220 } 221 `, 222 output: ` 223 cc_foo { 224 name: "foo", 225 } 226 `, 227 modified: false, 228 f: func(bp *Blueprint) { 229 props := must2(bp.ModulesByName("foo").GetProperty("deps")) 230 must(props.RemoveStringFromList("bar")) 231 }, 232 }, 233 { 234 name: "remove non existent nested", 235 input: ` 236 cc_foo { 237 name: "foo", 238 arch: {}, 239 } 240 `, 241 output: ` 242 cc_foo { 243 name: "foo", 244 arch: {}, 245 } 246 `, 247 modified: false, 248 f: func(bp *Blueprint) { 249 props := must2(bp.ModulesByName("foo").GetProperty("arch.arm.deps")) 250 must(props.RemoveStringFromList("bar")) 251 }, 252 }, 253 { 254 name: "add numeric sorted", 255 input: ` 256 cc_foo { 257 name: "foo", 258 versions: ["1", "2", "20"], 259 } 260 `, 261 output: ` 262 cc_foo { 263 name: "foo", 264 versions: [ 265 "1", 266 "2", 267 "10", 268 "20", 269 ], 270 } 271 `, 272 modified: true, 273 f: func(bp *Blueprint) { 274 props := must2(bp.ModulesByName("foo").GetProperty("versions")) 275 must(props.AddStringToList("10")) 276 }, 277 }, 278 { 279 name: "add mixed sorted", 280 input: ` 281 cc_foo { 282 name: "foo", 283 deps: ["bar-v1-bar", "bar-v2-bar"], 284 } 285 `, 286 output: ` 287 cc_foo { 288 name: "foo", 289 deps: [ 290 "bar-v1-bar", 291 "bar-v2-bar", 292 "bar-v10-bar", 293 ], 294 } 295 `, 296 modified: true, 297 f: func(bp *Blueprint) { 298 props := must2(bp.ModulesByName("foo").GetProperty("deps")) 299 must(props.AddStringToList("bar-v10-bar")) 300 }, 301 }, 302 { 303 name: "add a struct with literal", 304 input: `cc_foo {name: "foo"}`, 305 output: `cc_foo { 306 name: "foo", 307 structs: [ 308 { 309 version: "1", 310 311 imports: [ 312 "bar1", 313 "bar2", 314 ], 315 }, 316 ], 317 } 318 `, 319 modified: true, 320 f: func(bp *Blueprint) { 321 props := must2(bp.ModulesByName("foo").GetOrCreateProperty(List, "structs")) 322 must(props.AddLiteral(`{version: "1", imports: ["bar1", "bar2"]}`)) 323 }, 324 }, 325 { 326 name: "set string", 327 input: ` 328 cc_foo { 329 name: "foo", 330 } 331 `, 332 output: ` 333 cc_foo { 334 name: "foo", 335 foo: "bar", 336 } 337 `, 338 modified: true, 339 f: func(bp *Blueprint) { 340 props := must2(bp.ModulesByName("foo").GetOrCreateProperty(String, "foo")) 341 must(props.SetString("bar")) 342 }, 343 }, 344 { 345 name: "set existing string", 346 input: ` 347 cc_foo { 348 name: "foo", 349 foo: "baz", 350 } 351 `, 352 output: ` 353 cc_foo { 354 name: "foo", 355 foo: "bar", 356 } 357 `, 358 modified: true, 359 f: func(bp *Blueprint) { 360 props := must2(bp.ModulesByName("foo").GetOrCreateProperty(String, "foo")) 361 must(props.SetString("bar")) 362 }, 363 }, 364 { 365 name: "set bool", 366 input: ` 367 cc_foo { 368 name: "foo", 369 } 370 `, 371 output: ` 372 cc_foo { 373 name: "foo", 374 foo: true, 375 } 376 `, 377 modified: true, 378 f: func(bp *Blueprint) { 379 props := must2(bp.ModulesByName("foo").GetOrCreateProperty(Bool, "foo")) 380 must(props.SetBool(true)) 381 }, 382 }, 383 { 384 name: "set existing bool", 385 input: ` 386 cc_foo { 387 name: "foo", 388 foo: true, 389 } 390 `, 391 output: ` 392 cc_foo { 393 name: "foo", 394 foo: false, 395 } 396 `, 397 modified: true, 398 f: func(bp *Blueprint) { 399 props := must2(bp.ModulesByName("foo").GetOrCreateProperty(Bool, "foo")) 400 must(props.SetBool(false)) 401 }, 402 }, 403 { 404 name: "remove existing property", 405 input: ` 406 cc_foo { 407 name: "foo", 408 foo: "baz", 409 } 410 `, 411 output: ` 412 cc_foo { 413 name: "foo", 414 } 415 `, 416 modified: true, 417 f: func(bp *Blueprint) { 418 must(bp.ModulesByName("foo").RemoveProperty("foo")) 419 }, 420 }, { 421 name: "remove nested property", 422 input: ` 423 cc_foo { 424 name: "foo", 425 foo: { 426 bar: "baz", 427 }, 428 } 429 `, 430 output: ` 431 cc_foo { 432 name: "foo", 433 foo: {}, 434 } 435 `, 436 modified: true, 437 f: func(bp *Blueprint) { 438 must(bp.ModulesByName("foo").RemoveProperty("foo.bar")) 439 }, 440 }, { 441 name: "remove non-existing property", 442 input: ` 443 cc_foo { 444 name: "foo", 445 foo: "baz", 446 } 447 `, 448 output: ` 449 cc_foo { 450 name: "foo", 451 foo: "baz", 452 } 453 `, 454 modified: false, 455 f: func(bp *Blueprint) { 456 must(bp.ModulesByName("foo").RemoveProperty("bar")) 457 }, 458 }, { 459 name: "replace property", 460 input: ` 461 cc_foo { 462 name: "foo", 463 deps: ["baz", "unchanged"], 464 } 465 `, 466 output: ` 467 cc_foo { 468 name: "foo", 469 deps: [ 470 "baz_lib", 471 "unchanged", 472 ], 473 } 474 `, 475 modified: true, 476 f: func(bp *Blueprint) { 477 props := must2(bp.ModulesByName("foo").GetProperty("deps")) 478 must(props.ReplaceStrings(map[string]string{"baz": "baz_lib", "foobar": "foobar_lib"})) 479 }, 480 }, { 481 name: "replace property multiple modules", 482 input: ` 483 cc_foo { 484 name: "foo", 485 deps: ["baz", "unchanged"], 486 unchanged: ["baz"], 487 required: ["foobar"], 488 } 489 `, 490 output: ` 491 cc_foo { 492 name: "foo", 493 deps: [ 494 "baz_lib", 495 "unchanged", 496 ], 497 unchanged: ["baz"], 498 required: ["foobar_lib"], 499 } 500 `, 501 modified: true, 502 f: func(bp *Blueprint) { 503 props := must2(bp.ModulesByName("foo").GetProperty("deps", "required")) 504 must(props.ReplaceStrings(map[string]string{"baz": "baz_lib", "foobar": "foobar_lib"})) 505 }, 506 }, { 507 name: "replace property string value", 508 input: ` 509 cc_foo { 510 name: "foo", 511 deps: ["baz"], 512 unchanged: ["baz"], 513 required: ["foobar"], 514 } 515 `, 516 output: ` 517 cc_foo { 518 name: "foo_lib", 519 deps: ["baz"], 520 unchanged: ["baz"], 521 required: ["foobar"], 522 } 523 `, 524 modified: true, 525 f: func(bp *Blueprint) { 526 props := must2(bp.ModulesByName("foo").GetProperty("name")) 527 must(props.ReplaceStrings(map[string]string{"foo": "foo_lib"})) 528 }, 529 }, { 530 name: "replace property string and list values", 531 input: ` 532 cc_foo { 533 name: "foo", 534 deps: ["baz"], 535 unchanged: ["baz"], 536 required: ["foobar"], 537 } 538 `, 539 output: ` 540 cc_foo { 541 name: "foo_lib", 542 deps: ["baz_lib"], 543 unchanged: ["baz"], 544 required: ["foobar"], 545 } 546 `, 547 modified: true, 548 f: func(bp *Blueprint) { 549 props := must2(bp.ModulesByName("foo").GetProperty("name", "deps")) 550 must(props.ReplaceStrings(map[string]string{"foo": "foo_lib", "baz": "baz_lib"})) 551 }, 552 }, { 553 name: "move contents of property into non-existing property", 554 input: ` 555 cc_foo { 556 name: "foo", 557 bar: ["barContents"], 558 } 559`, 560 output: ` 561 cc_foo { 562 name: "foo", 563 baz: ["barContents"], 564 } 565 `, 566 modified: true, 567 f: func(bp *Blueprint) { 568 must(bp.ModulesByName("foo").MoveProperty("baz", "bar")) 569 }, 570 }, { 571 name: "move contents of property into existing property", 572 input: ` 573 cc_foo { 574 name: "foo", 575 baz: ["bazContents"], 576 bar: ["barContents"], 577 } 578 `, 579 output: ` 580 cc_foo { 581 name: "foo", 582 baz: [ 583 "bazContents", 584 "barContents", 585 ], 586 587 } 588 `, 589 modified: true, 590 f: func(bp *Blueprint) { 591 must(bp.ModulesByName("foo").MoveProperty("baz", "bar")) 592 }, 593 }, { 594 name: "replace nested", 595 input: ` 596 cc_foo { 597 name: "foo", 598 foo: { 599 bar: "baz", 600 }, 601 } 602 `, 603 output: ` 604 cc_foo { 605 name: "foo", 606 foo: { 607 bar: "baz2", 608 }, 609 } 610 `, 611 modified: true, 612 f: func(bp *Blueprint) { 613 props := must2(bp.ModulesByName("foo").GetProperty("foo.bar")) 614 must(props.ReplaceStrings(map[string]string{"baz": "baz2"})) 615 }, 616 }, 617 } 618 619 for i, testCase := range testCases { 620 t.Run(testCase.name, func(t *testing.T) { 621 bp, err := NewBlueprint("", []byte(testCase.input)) 622 if err != nil { 623 t.Fatalf("error creating Blueprint: %s", err) 624 } 625 err = nil 626 func() { 627 defer func() { 628 if r := recover(); r != nil { 629 if recoveredErr, ok := r.(error); ok { 630 err = recoveredErr 631 } else { 632 t.Fatalf("unexpected panic: %q", r) 633 } 634 } 635 }() 636 testCase.f(bp) 637 }() 638 if err != nil { 639 if testCase.err != "" { 640 if g, w := err.Error(), testCase.err; !strings.Contains(w, g) { 641 t.Errorf("unexpected error, want %q, got %q", g, w) 642 } 643 } else { 644 t.Errorf("unexpected error %q", err.Error()) 645 } 646 } else { 647 if testCase.err != "" { 648 t.Errorf("missing error, expected %q", testCase.err) 649 } 650 } 651 652 if g, w := bp.Modified(), testCase.modified; g != w { 653 t.Errorf("incorrect bp.Modified() value, want %v, got %v", w, g) 654 } 655 656 inModuleString := bp.String() 657 if simplifyModuleDefinition(inModuleString) != simplifyModuleDefinition(testCase.output) { 658 t.Errorf("test case %d:", i) 659 t.Errorf("expected module definition:") 660 t.Errorf(" %s", testCase.output) 661 t.Errorf("actual module definition:") 662 t.Errorf(" %s", inModuleString) 663 } 664 }) 665 } 666} 667