1// Copyright 2015 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 android 16 17import ( 18 "errors" 19 "fmt" 20 "reflect" 21 "strconv" 22 "strings" 23 "testing" 24 25 "github.com/google/blueprint/proptools" 26) 27 28type strsTestCase struct { 29 in []string 30 out string 31 err []error 32} 33 34var commonValidatePathTestCases = []strsTestCase{ 35 { 36 in: []string{""}, 37 out: "", 38 }, 39 { 40 in: []string{"a/b"}, 41 out: "a/b", 42 }, 43 { 44 in: []string{"a/b", "c"}, 45 out: "a/b/c", 46 }, 47 { 48 in: []string{"a/.."}, 49 out: ".", 50 }, 51 { 52 in: []string{"."}, 53 out: ".", 54 }, 55 { 56 in: []string{".."}, 57 out: "", 58 err: []error{errors.New("Path is outside directory: ..")}, 59 }, 60 { 61 in: []string{"../a"}, 62 out: "", 63 err: []error{errors.New("Path is outside directory: ../a")}, 64 }, 65 { 66 in: []string{"b/../../a"}, 67 out: "", 68 err: []error{errors.New("Path is outside directory: ../a")}, 69 }, 70 { 71 in: []string{"/a"}, 72 out: "", 73 err: []error{errors.New("Path is outside directory: /a")}, 74 }, 75 { 76 in: []string{"a", "../b"}, 77 out: "", 78 err: []error{errors.New("Path is outside directory: ../b")}, 79 }, 80 { 81 in: []string{"a", "b/../../c"}, 82 out: "", 83 err: []error{errors.New("Path is outside directory: ../c")}, 84 }, 85 { 86 in: []string{"a", "./.."}, 87 out: "", 88 err: []error{errors.New("Path is outside directory: ..")}, 89 }, 90} 91 92var validateSafePathTestCases = append(commonValidatePathTestCases, []strsTestCase{ 93 { 94 in: []string{"$host/../$a"}, 95 out: "$a", 96 }, 97}...) 98 99var validatePathTestCases = append(commonValidatePathTestCases, []strsTestCase{ 100 { 101 in: []string{"$host/../$a"}, 102 out: "", 103 err: []error{errors.New("Path contains invalid character($): $host/../$a")}, 104 }, 105 { 106 in: []string{"$host/.."}, 107 out: "", 108 err: []error{errors.New("Path contains invalid character($): $host/..")}, 109 }, 110}...) 111 112func TestValidateSafePath(t *testing.T) { 113 for _, testCase := range validateSafePathTestCases { 114 t.Run(strings.Join(testCase.in, ","), func(t *testing.T) { 115 ctx := &configErrorWrapper{} 116 out, err := validateSafePath(testCase.in...) 117 if err != nil { 118 reportPathError(ctx, err) 119 } 120 check(t, "validateSafePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err) 121 }) 122 } 123} 124 125func TestValidatePath(t *testing.T) { 126 for _, testCase := range validatePathTestCases { 127 t.Run(strings.Join(testCase.in, ","), func(t *testing.T) { 128 ctx := &configErrorWrapper{} 129 out, err := validatePath(testCase.in...) 130 if err != nil { 131 reportPathError(ctx, err) 132 } 133 check(t, "validatePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err) 134 }) 135 } 136} 137 138func TestOptionalPath(t *testing.T) { 139 var path OptionalPath 140 checkInvalidOptionalPath(t, path) 141 142 path = OptionalPathForPath(nil) 143 checkInvalidOptionalPath(t, path) 144 145 path = OptionalPathForPath(PathForTesting("path")) 146 checkValidOptionalPath(t, path, "path") 147} 148 149func checkInvalidOptionalPath(t *testing.T, path OptionalPath) { 150 t.Helper() 151 if path.Valid() { 152 t.Errorf("Uninitialized OptionalPath should not be valid") 153 } 154 if path.String() != "" { 155 t.Errorf("Uninitialized OptionalPath String() should return \"\", not %q", path.String()) 156 } 157 paths := path.AsPaths() 158 if len(paths) != 0 { 159 t.Errorf("Uninitialized OptionalPath AsPaths() should return empty Paths, not %q", paths) 160 } 161 defer func() { 162 if r := recover(); r == nil { 163 t.Errorf("Expected a panic when calling Path() on an uninitialized OptionalPath") 164 } 165 }() 166 path.Path() 167} 168 169func checkValidOptionalPath(t *testing.T, path OptionalPath, expectedString string) { 170 t.Helper() 171 if !path.Valid() { 172 t.Errorf("Initialized OptionalPath should not be invalid") 173 } 174 if path.String() != expectedString { 175 t.Errorf("Initialized OptionalPath String() should return %q, not %q", expectedString, path.String()) 176 } 177 paths := path.AsPaths() 178 if len(paths) != 1 { 179 t.Errorf("Initialized OptionalPath AsPaths() should return Paths with length 1, not %q", paths) 180 } 181 path.Path() 182} 183 184func check(t *testing.T, testType, testString string, 185 got interface{}, err []error, 186 expected interface{}, expectedErr []error) { 187 t.Helper() 188 189 printedTestCase := false 190 e := func(s string, expected, got interface{}) { 191 t.Helper() 192 if !printedTestCase { 193 t.Errorf("test case %s: %s", testType, testString) 194 printedTestCase = true 195 } 196 t.Errorf("incorrect %s", s) 197 t.Errorf(" expected: %s", p(expected)) 198 t.Errorf(" got: %s", p(got)) 199 } 200 201 if !reflect.DeepEqual(expectedErr, err) { 202 e("errors:", expectedErr, err) 203 } 204 205 if !reflect.DeepEqual(expected, got) { 206 e("output:", expected, got) 207 } 208} 209 210func p(in interface{}) string { 211 if v, ok := in.([]interface{}); ok { 212 s := make([]string, len(v)) 213 for i := range v { 214 s[i] = fmt.Sprintf("%#v", v[i]) 215 } 216 return "[" + strings.Join(s, ", ") + "]" 217 } else { 218 return fmt.Sprintf("%#v", in) 219 } 220} 221 222func pathTestConfig(buildDir string) Config { 223 return TestConfig(buildDir, nil, "", nil) 224} 225 226func TestPathForModuleInstall(t *testing.T) { 227 testConfig := pathTestConfig("") 228 229 hostTarget := Target{Os: Linux, Arch: Arch{ArchType: X86}} 230 deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}} 231 232 testCases := []struct { 233 name string 234 ctx *testModuleInstallPathContext 235 in []string 236 out string 237 partitionDir string 238 }{ 239 { 240 name: "host binary", 241 ctx: &testModuleInstallPathContext{ 242 baseModuleContext: baseModuleContext{ 243 os: hostTarget.Os, 244 target: hostTarget, 245 }, 246 }, 247 in: []string{"bin", "my_test"}, 248 out: "host/linux-x86/bin/my_test", 249 partitionDir: "host/linux-x86", 250 }, 251 252 { 253 name: "system binary", 254 ctx: &testModuleInstallPathContext{ 255 baseModuleContext: baseModuleContext{ 256 os: deviceTarget.Os, 257 target: deviceTarget, 258 }, 259 }, 260 in: []string{"bin", "my_test"}, 261 out: "target/product/test_device/system/bin/my_test", 262 partitionDir: "target/product/test_device/system", 263 }, 264 { 265 name: "vendor binary", 266 ctx: &testModuleInstallPathContext{ 267 baseModuleContext: baseModuleContext{ 268 os: deviceTarget.Os, 269 target: deviceTarget, 270 earlyModuleContext: earlyModuleContext{ 271 kind: socSpecificModule, 272 }, 273 }, 274 }, 275 in: []string{"bin", "my_test"}, 276 out: "target/product/test_device/vendor/bin/my_test", 277 partitionDir: "target/product/test_device/vendor", 278 }, 279 { 280 name: "odm binary", 281 ctx: &testModuleInstallPathContext{ 282 baseModuleContext: baseModuleContext{ 283 os: deviceTarget.Os, 284 target: deviceTarget, 285 earlyModuleContext: earlyModuleContext{ 286 kind: deviceSpecificModule, 287 }, 288 }, 289 }, 290 in: []string{"bin", "my_test"}, 291 out: "target/product/test_device/odm/bin/my_test", 292 partitionDir: "target/product/test_device/odm", 293 }, 294 { 295 name: "product binary", 296 ctx: &testModuleInstallPathContext{ 297 baseModuleContext: baseModuleContext{ 298 os: deviceTarget.Os, 299 target: deviceTarget, 300 earlyModuleContext: earlyModuleContext{ 301 kind: productSpecificModule, 302 }, 303 }, 304 }, 305 in: []string{"bin", "my_test"}, 306 out: "target/product/test_device/product/bin/my_test", 307 partitionDir: "target/product/test_device/product", 308 }, 309 { 310 name: "system_ext binary", 311 ctx: &testModuleInstallPathContext{ 312 baseModuleContext: baseModuleContext{ 313 os: deviceTarget.Os, 314 target: deviceTarget, 315 earlyModuleContext: earlyModuleContext{ 316 kind: systemExtSpecificModule, 317 }, 318 }, 319 }, 320 in: []string{"bin", "my_test"}, 321 out: "target/product/test_device/system_ext/bin/my_test", 322 partitionDir: "target/product/test_device/system_ext", 323 }, 324 { 325 name: "root binary", 326 ctx: &testModuleInstallPathContext{ 327 baseModuleContext: baseModuleContext{ 328 os: deviceTarget.Os, 329 target: deviceTarget, 330 }, 331 inRoot: true, 332 }, 333 in: []string{"my_test"}, 334 out: "target/product/test_device/root/my_test", 335 partitionDir: "target/product/test_device/root", 336 }, 337 { 338 name: "recovery binary", 339 ctx: &testModuleInstallPathContext{ 340 baseModuleContext: baseModuleContext{ 341 os: deviceTarget.Os, 342 target: deviceTarget, 343 }, 344 inRecovery: true, 345 }, 346 in: []string{"bin/my_test"}, 347 out: "target/product/test_device/recovery/root/system/bin/my_test", 348 partitionDir: "target/product/test_device/recovery/root/system", 349 }, 350 { 351 name: "recovery root binary", 352 ctx: &testModuleInstallPathContext{ 353 baseModuleContext: baseModuleContext{ 354 os: deviceTarget.Os, 355 target: deviceTarget, 356 }, 357 inRecovery: true, 358 inRoot: true, 359 }, 360 in: []string{"my_test"}, 361 out: "target/product/test_device/recovery/root/my_test", 362 partitionDir: "target/product/test_device/recovery/root", 363 }, 364 365 { 366 name: "ramdisk binary", 367 ctx: &testModuleInstallPathContext{ 368 baseModuleContext: baseModuleContext{ 369 os: deviceTarget.Os, 370 target: deviceTarget, 371 }, 372 inRamdisk: true, 373 }, 374 in: []string{"my_test"}, 375 out: "target/product/test_device/ramdisk/system/my_test", 376 partitionDir: "target/product/test_device/ramdisk/system", 377 }, 378 { 379 name: "ramdisk root binary", 380 ctx: &testModuleInstallPathContext{ 381 baseModuleContext: baseModuleContext{ 382 os: deviceTarget.Os, 383 target: deviceTarget, 384 }, 385 inRamdisk: true, 386 inRoot: true, 387 }, 388 in: []string{"my_test"}, 389 out: "target/product/test_device/ramdisk/my_test", 390 partitionDir: "target/product/test_device/ramdisk", 391 }, 392 { 393 name: "vendor_ramdisk binary", 394 ctx: &testModuleInstallPathContext{ 395 baseModuleContext: baseModuleContext{ 396 os: deviceTarget.Os, 397 target: deviceTarget, 398 }, 399 inVendorRamdisk: true, 400 }, 401 in: []string{"my_test"}, 402 out: "target/product/test_device/vendor_ramdisk/system/my_test", 403 partitionDir: "target/product/test_device/vendor_ramdisk/system", 404 }, 405 { 406 name: "vendor_ramdisk root binary", 407 ctx: &testModuleInstallPathContext{ 408 baseModuleContext: baseModuleContext{ 409 os: deviceTarget.Os, 410 target: deviceTarget, 411 }, 412 inVendorRamdisk: true, 413 inRoot: true, 414 }, 415 in: []string{"my_test"}, 416 out: "target/product/test_device/vendor_ramdisk/my_test", 417 partitionDir: "target/product/test_device/vendor_ramdisk", 418 }, 419 { 420 name: "debug_ramdisk binary", 421 ctx: &testModuleInstallPathContext{ 422 baseModuleContext: baseModuleContext{ 423 os: deviceTarget.Os, 424 target: deviceTarget, 425 }, 426 inDebugRamdisk: true, 427 }, 428 in: []string{"my_test"}, 429 out: "target/product/test_device/debug_ramdisk/my_test", 430 partitionDir: "target/product/test_device/debug_ramdisk", 431 }, 432 { 433 name: "system native test binary", 434 ctx: &testModuleInstallPathContext{ 435 baseModuleContext: baseModuleContext{ 436 os: deviceTarget.Os, 437 target: deviceTarget, 438 }, 439 inData: true, 440 }, 441 in: []string{"nativetest", "my_test"}, 442 out: "target/product/test_device/data/nativetest/my_test", 443 partitionDir: "target/product/test_device/data", 444 }, 445 { 446 name: "vendor native test binary", 447 ctx: &testModuleInstallPathContext{ 448 baseModuleContext: baseModuleContext{ 449 os: deviceTarget.Os, 450 target: deviceTarget, 451 earlyModuleContext: earlyModuleContext{ 452 kind: socSpecificModule, 453 }, 454 }, 455 inData: true, 456 }, 457 in: []string{"nativetest", "my_test"}, 458 out: "target/product/test_device/data/nativetest/my_test", 459 partitionDir: "target/product/test_device/data", 460 }, 461 { 462 name: "odm native test binary", 463 ctx: &testModuleInstallPathContext{ 464 baseModuleContext: baseModuleContext{ 465 os: deviceTarget.Os, 466 target: deviceTarget, 467 earlyModuleContext: earlyModuleContext{ 468 kind: deviceSpecificModule, 469 }, 470 }, 471 inData: true, 472 }, 473 in: []string{"nativetest", "my_test"}, 474 out: "target/product/test_device/data/nativetest/my_test", 475 partitionDir: "target/product/test_device/data", 476 }, 477 { 478 name: "product native test binary", 479 ctx: &testModuleInstallPathContext{ 480 baseModuleContext: baseModuleContext{ 481 os: deviceTarget.Os, 482 target: deviceTarget, 483 earlyModuleContext: earlyModuleContext{ 484 kind: productSpecificModule, 485 }, 486 }, 487 inData: true, 488 }, 489 in: []string{"nativetest", "my_test"}, 490 out: "target/product/test_device/data/nativetest/my_test", 491 partitionDir: "target/product/test_device/data", 492 }, 493 494 { 495 name: "system_ext native test binary", 496 ctx: &testModuleInstallPathContext{ 497 baseModuleContext: baseModuleContext{ 498 os: deviceTarget.Os, 499 target: deviceTarget, 500 earlyModuleContext: earlyModuleContext{ 501 kind: systemExtSpecificModule, 502 }, 503 }, 504 inData: true, 505 }, 506 in: []string{"nativetest", "my_test"}, 507 out: "target/product/test_device/data/nativetest/my_test", 508 partitionDir: "target/product/test_device/data", 509 }, 510 511 { 512 name: "sanitized system binary", 513 ctx: &testModuleInstallPathContext{ 514 baseModuleContext: baseModuleContext{ 515 os: deviceTarget.Os, 516 target: deviceTarget, 517 }, 518 inSanitizerDir: true, 519 }, 520 in: []string{"bin", "my_test"}, 521 out: "target/product/test_device/data/asan/system/bin/my_test", 522 partitionDir: "target/product/test_device/data/asan/system", 523 }, 524 { 525 name: "sanitized vendor binary", 526 ctx: &testModuleInstallPathContext{ 527 baseModuleContext: baseModuleContext{ 528 os: deviceTarget.Os, 529 target: deviceTarget, 530 earlyModuleContext: earlyModuleContext{ 531 kind: socSpecificModule, 532 }, 533 }, 534 inSanitizerDir: true, 535 }, 536 in: []string{"bin", "my_test"}, 537 out: "target/product/test_device/data/asan/vendor/bin/my_test", 538 partitionDir: "target/product/test_device/data/asan/vendor", 539 }, 540 { 541 name: "sanitized odm binary", 542 ctx: &testModuleInstallPathContext{ 543 baseModuleContext: baseModuleContext{ 544 os: deviceTarget.Os, 545 target: deviceTarget, 546 earlyModuleContext: earlyModuleContext{ 547 kind: deviceSpecificModule, 548 }, 549 }, 550 inSanitizerDir: true, 551 }, 552 in: []string{"bin", "my_test"}, 553 out: "target/product/test_device/data/asan/odm/bin/my_test", 554 partitionDir: "target/product/test_device/data/asan/odm", 555 }, 556 { 557 name: "sanitized product binary", 558 ctx: &testModuleInstallPathContext{ 559 baseModuleContext: baseModuleContext{ 560 os: deviceTarget.Os, 561 target: deviceTarget, 562 earlyModuleContext: earlyModuleContext{ 563 kind: productSpecificModule, 564 }, 565 }, 566 inSanitizerDir: true, 567 }, 568 in: []string{"bin", "my_test"}, 569 out: "target/product/test_device/data/asan/product/bin/my_test", 570 partitionDir: "target/product/test_device/data/asan/product", 571 }, 572 573 { 574 name: "sanitized system_ext binary", 575 ctx: &testModuleInstallPathContext{ 576 baseModuleContext: baseModuleContext{ 577 os: deviceTarget.Os, 578 target: deviceTarget, 579 earlyModuleContext: earlyModuleContext{ 580 kind: systemExtSpecificModule, 581 }, 582 }, 583 inSanitizerDir: true, 584 }, 585 in: []string{"bin", "my_test"}, 586 out: "target/product/test_device/data/asan/system_ext/bin/my_test", 587 partitionDir: "target/product/test_device/data/asan/system_ext", 588 }, 589 590 { 591 name: "sanitized system native test binary", 592 ctx: &testModuleInstallPathContext{ 593 baseModuleContext: baseModuleContext{ 594 os: deviceTarget.Os, 595 target: deviceTarget, 596 }, 597 inData: true, 598 inSanitizerDir: true, 599 }, 600 in: []string{"nativetest", "my_test"}, 601 out: "target/product/test_device/data/asan/data/nativetest/my_test", 602 partitionDir: "target/product/test_device/data/asan/data", 603 }, 604 { 605 name: "sanitized vendor native test binary", 606 ctx: &testModuleInstallPathContext{ 607 baseModuleContext: baseModuleContext{ 608 os: deviceTarget.Os, 609 target: deviceTarget, 610 earlyModuleContext: earlyModuleContext{ 611 kind: socSpecificModule, 612 }, 613 }, 614 inData: true, 615 inSanitizerDir: true, 616 }, 617 in: []string{"nativetest", "my_test"}, 618 out: "target/product/test_device/data/asan/data/nativetest/my_test", 619 partitionDir: "target/product/test_device/data/asan/data", 620 }, 621 { 622 name: "sanitized odm native test binary", 623 ctx: &testModuleInstallPathContext{ 624 baseModuleContext: baseModuleContext{ 625 os: deviceTarget.Os, 626 target: deviceTarget, 627 earlyModuleContext: earlyModuleContext{ 628 kind: deviceSpecificModule, 629 }, 630 }, 631 inData: true, 632 inSanitizerDir: true, 633 }, 634 in: []string{"nativetest", "my_test"}, 635 out: "target/product/test_device/data/asan/data/nativetest/my_test", 636 partitionDir: "target/product/test_device/data/asan/data", 637 }, 638 { 639 name: "sanitized product native test binary", 640 ctx: &testModuleInstallPathContext{ 641 baseModuleContext: baseModuleContext{ 642 os: deviceTarget.Os, 643 target: deviceTarget, 644 earlyModuleContext: earlyModuleContext{ 645 kind: productSpecificModule, 646 }, 647 }, 648 inData: true, 649 inSanitizerDir: true, 650 }, 651 in: []string{"nativetest", "my_test"}, 652 out: "target/product/test_device/data/asan/data/nativetest/my_test", 653 partitionDir: "target/product/test_device/data/asan/data", 654 }, 655 { 656 name: "sanitized system_ext native test binary", 657 ctx: &testModuleInstallPathContext{ 658 baseModuleContext: baseModuleContext{ 659 os: deviceTarget.Os, 660 target: deviceTarget, 661 earlyModuleContext: earlyModuleContext{ 662 kind: systemExtSpecificModule, 663 }, 664 }, 665 inData: true, 666 inSanitizerDir: true, 667 }, 668 in: []string{"nativetest", "my_test"}, 669 out: "target/product/test_device/data/asan/data/nativetest/my_test", 670 partitionDir: "target/product/test_device/data/asan/data", 671 }, { 672 name: "device testcases", 673 ctx: &testModuleInstallPathContext{ 674 baseModuleContext: baseModuleContext{ 675 os: deviceTarget.Os, 676 target: deviceTarget, 677 }, 678 inTestcases: true, 679 }, 680 in: []string{"my_test", "my_test_bin"}, 681 out: "target/product/test_device/testcases/my_test/my_test_bin", 682 partitionDir: "target/product/test_device/testcases", 683 }, { 684 name: "host testcases", 685 ctx: &testModuleInstallPathContext{ 686 baseModuleContext: baseModuleContext{ 687 os: hostTarget.Os, 688 target: hostTarget, 689 }, 690 inTestcases: true, 691 }, 692 in: []string{"my_test", "my_test_bin"}, 693 out: "host/linux-x86/testcases/my_test/my_test_bin", 694 partitionDir: "host/linux-x86/testcases", 695 }, { 696 name: "forced host testcases", 697 ctx: &testModuleInstallPathContext{ 698 baseModuleContext: baseModuleContext{ 699 os: deviceTarget.Os, 700 target: deviceTarget, 701 }, 702 inTestcases: true, 703 forceOS: &Linux, 704 forceArch: &X86, 705 }, 706 in: []string{"my_test", "my_test_bin"}, 707 out: "host/linux-x86/testcases/my_test/my_test_bin", 708 partitionDir: "host/linux-x86/testcases", 709 }, 710 } 711 712 for _, tc := range testCases { 713 t.Run(tc.name, func(t *testing.T) { 714 tc.ctx.baseModuleContext.config = testConfig 715 output := PathForModuleInstall(tc.ctx, tc.in...) 716 if output.basePath.path != tc.out { 717 t.Errorf("unexpected path:\n got: %q\nwant: %q\n", 718 output.basePath.path, 719 tc.out) 720 } 721 if output.partitionDir != tc.partitionDir { 722 t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n", 723 output.partitionDir, tc.partitionDir) 724 } 725 }) 726 } 727} 728 729func TestPathForModuleInstallRecoveryAsBoot(t *testing.T) { 730 testConfig := pathTestConfig("") 731 testConfig.TestProductVariables.BoardUsesRecoveryAsBoot = proptools.BoolPtr(true) 732 testConfig.TestProductVariables.BoardMoveRecoveryResourcesToVendorBoot = proptools.BoolPtr(true) 733 deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}} 734 735 testCases := []struct { 736 name string 737 ctx *testModuleInstallPathContext 738 in []string 739 out string 740 partitionDir string 741 }{ 742 { 743 name: "ramdisk binary", 744 ctx: &testModuleInstallPathContext{ 745 baseModuleContext: baseModuleContext{ 746 os: deviceTarget.Os, 747 target: deviceTarget, 748 }, 749 inRamdisk: true, 750 inRoot: true, 751 }, 752 in: []string{"my_test"}, 753 out: "target/product/test_device/recovery/root/first_stage_ramdisk/my_test", 754 partitionDir: "target/product/test_device/recovery/root/first_stage_ramdisk", 755 }, 756 757 { 758 name: "vendor_ramdisk binary", 759 ctx: &testModuleInstallPathContext{ 760 baseModuleContext: baseModuleContext{ 761 os: deviceTarget.Os, 762 target: deviceTarget, 763 }, 764 inVendorRamdisk: true, 765 inRoot: true, 766 }, 767 in: []string{"my_test"}, 768 out: "target/product/test_device/vendor_ramdisk/first_stage_ramdisk/my_test", 769 partitionDir: "target/product/test_device/vendor_ramdisk/first_stage_ramdisk", 770 }, 771 } 772 773 for _, tc := range testCases { 774 t.Run(tc.name, func(t *testing.T) { 775 tc.ctx.baseModuleContext.config = testConfig 776 output := PathForModuleInstall(tc.ctx, tc.in...) 777 if output.basePath.path != tc.out { 778 t.Errorf("unexpected path:\n got: %q\nwant: %q\n", 779 output.basePath.path, 780 tc.out) 781 } 782 if output.partitionDir != tc.partitionDir { 783 t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n", 784 output.partitionDir, tc.partitionDir) 785 } 786 }) 787 } 788} 789 790func TestBaseDirForInstallPath(t *testing.T) { 791 testConfig := pathTestConfig("") 792 deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}} 793 794 ctx := &testModuleInstallPathContext{ 795 baseModuleContext: baseModuleContext{ 796 os: deviceTarget.Os, 797 target: deviceTarget, 798 }, 799 } 800 ctx.baseModuleContext.config = testConfig 801 802 actual := PathForModuleInstall(ctx, "foo", "bar") 803 expectedBaseDir := "target/product/test_device/system" 804 if actual.partitionDir != expectedBaseDir { 805 t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n", actual.partitionDir, expectedBaseDir) 806 } 807 expectedRelPath := "foo/bar" 808 if actual.Rel() != expectedRelPath { 809 t.Errorf("unexpected Rel():\n got: %q\nwant: %q\n", actual.Rel(), expectedRelPath) 810 } 811 812 actualAfterJoin := actual.Join(ctx, "baz") 813 // partitionDir is preserved even after joining 814 if actualAfterJoin.partitionDir != expectedBaseDir { 815 t.Errorf("unexpected partitionDir after joining:\n got: %q\nwant: %q\n", actualAfterJoin.partitionDir, expectedBaseDir) 816 } 817 // Rel() is updated though 818 expectedRelAfterJoin := "baz" 819 if actualAfterJoin.Rel() != expectedRelAfterJoin { 820 t.Errorf("unexpected Rel() after joining:\n got: %q\nwant: %q\n", actualAfterJoin.Rel(), expectedRelAfterJoin) 821 } 822} 823 824func TestDirectorySortedPaths(t *testing.T) { 825 config := TestConfig("out", nil, "", map[string][]byte{ 826 "Android.bp": nil, 827 "a.txt": nil, 828 "a/txt": nil, 829 "a/b/c": nil, 830 "a/b/d": nil, 831 "b": nil, 832 "b/b.txt": nil, 833 "a/a.txt": nil, 834 }) 835 836 ctx := PathContextForTesting(config) 837 838 makePaths := func() Paths { 839 return Paths{ 840 PathForSource(ctx, "a.txt"), 841 PathForSource(ctx, "a/txt"), 842 PathForSource(ctx, "a/b/c"), 843 PathForSource(ctx, "a/b/d"), 844 PathForSource(ctx, "b"), 845 PathForSource(ctx, "b/b.txt"), 846 PathForSource(ctx, "a/a.txt"), 847 } 848 } 849 850 expected := []string{ 851 "a.txt", 852 "a/a.txt", 853 "a/b/c", 854 "a/b/d", 855 "a/txt", 856 "b", 857 "b/b.txt", 858 } 859 860 paths := makePaths() 861 reversePaths := ReversePaths(paths) 862 863 sortedPaths := PathsToDirectorySortedPaths(paths) 864 reverseSortedPaths := PathsToDirectorySortedPaths(reversePaths) 865 866 if !reflect.DeepEqual(Paths(sortedPaths).Strings(), expected) { 867 t.Fatalf("sorted paths:\n %#v\n != \n %#v", paths.Strings(), expected) 868 } 869 870 if !reflect.DeepEqual(Paths(reverseSortedPaths).Strings(), expected) { 871 t.Fatalf("sorted reversed paths:\n %#v\n !=\n %#v", reversePaths.Strings(), expected) 872 } 873 874 expectedA := []string{ 875 "a/a.txt", 876 "a/b/c", 877 "a/b/d", 878 "a/txt", 879 } 880 881 inA := sortedPaths.PathsInDirectory("a") 882 if !reflect.DeepEqual(inA.Strings(), expectedA) { 883 t.Errorf("FilesInDirectory(a):\n %#v\n != \n %#v", inA.Strings(), expectedA) 884 } 885 886 expectedA_B := []string{ 887 "a/b/c", 888 "a/b/d", 889 } 890 891 inA_B := sortedPaths.PathsInDirectory("a/b") 892 if !reflect.DeepEqual(inA_B.Strings(), expectedA_B) { 893 t.Errorf("FilesInDirectory(a/b):\n %#v\n != \n %#v", inA_B.Strings(), expectedA_B) 894 } 895 896 expectedB := []string{ 897 "b/b.txt", 898 } 899 900 inB := sortedPaths.PathsInDirectory("b") 901 if !reflect.DeepEqual(inB.Strings(), expectedB) { 902 t.Errorf("FilesInDirectory(b):\n %#v\n != \n %#v", inA.Strings(), expectedA) 903 } 904} 905 906func TestMaybeRel(t *testing.T) { 907 testCases := []struct { 908 name string 909 base string 910 target string 911 out string 912 isRel bool 913 }{ 914 { 915 name: "normal", 916 base: "a/b/c", 917 target: "a/b/c/d", 918 out: "d", 919 isRel: true, 920 }, 921 { 922 name: "parent", 923 base: "a/b/c/d", 924 target: "a/b/c", 925 isRel: false, 926 }, 927 { 928 name: "not relative", 929 base: "a/b", 930 target: "c/d", 931 isRel: false, 932 }, 933 { 934 name: "abs1", 935 base: "/a", 936 target: "a", 937 isRel: false, 938 }, 939 { 940 name: "abs2", 941 base: "a", 942 target: "/a", 943 isRel: false, 944 }, 945 } 946 947 for _, testCase := range testCases { 948 t.Run(testCase.name, func(t *testing.T) { 949 ctx := &configErrorWrapper{} 950 out, isRel := MaybeRel(ctx, testCase.base, testCase.target) 951 if len(ctx.errors) > 0 { 952 t.Errorf("MaybeRel(..., %s, %s) reported unexpected errors %v", 953 testCase.base, testCase.target, ctx.errors) 954 } 955 if isRel != testCase.isRel || out != testCase.out { 956 t.Errorf("MaybeRel(..., %s, %s) want %v, %v got %v, %v", 957 testCase.base, testCase.target, testCase.out, testCase.isRel, out, isRel) 958 } 959 }) 960 } 961} 962 963func TestPathForSource(t *testing.T) { 964 testCases := []struct { 965 name string 966 buildDir string 967 src string 968 err string 969 }{ 970 { 971 name: "normal", 972 buildDir: "out", 973 src: "a/b/c", 974 }, 975 { 976 name: "abs", 977 buildDir: "out", 978 src: "/a/b/c", 979 err: "is outside directory", 980 }, 981 { 982 name: "in out dir", 983 buildDir: "out", 984 src: "out/a/b/c", 985 err: "is in output", 986 }, 987 } 988 989 funcs := []struct { 990 name string 991 f func(ctx PathContext, pathComponents ...string) (SourcePath, error) 992 }{ 993 {"pathForSource", pathForSource}, 994 {"safePathForSource", safePathForSource}, 995 } 996 997 for _, f := range funcs { 998 t.Run(f.name, func(t *testing.T) { 999 for _, test := range testCases { 1000 t.Run(test.name, func(t *testing.T) { 1001 testConfig := pathTestConfig(test.buildDir) 1002 ctx := &configErrorWrapper{config: testConfig} 1003 _, err := f.f(ctx, test.src) 1004 if len(ctx.errors) > 0 { 1005 t.Fatalf("unexpected errors %v", ctx.errors) 1006 } 1007 if err != nil { 1008 if test.err == "" { 1009 t.Fatalf("unexpected error %q", err.Error()) 1010 } else if !strings.Contains(err.Error(), test.err) { 1011 t.Fatalf("incorrect error, want substring %q got %q", test.err, err.Error()) 1012 } 1013 } else { 1014 if test.err != "" { 1015 t.Fatalf("missing error %q", test.err) 1016 } 1017 } 1018 }) 1019 } 1020 }) 1021 } 1022} 1023 1024type pathForModuleSrcTestModule struct { 1025 ModuleBase 1026 props struct { 1027 Srcs []string `android:"path"` 1028 Exclude_srcs []string `android:"path"` 1029 1030 Src *string `android:"path"` 1031 1032 Module_handles_missing_deps bool 1033 } 1034 1035 src string 1036 rel string 1037 1038 srcs []string 1039 rels []string 1040 1041 missingDeps []string 1042} 1043 1044func pathForModuleSrcTestModuleFactory() Module { 1045 module := &pathForModuleSrcTestModule{} 1046 module.AddProperties(&module.props) 1047 InitAndroidModule(module) 1048 return module 1049} 1050 1051func (p *pathForModuleSrcTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { 1052 var srcs Paths 1053 if p.props.Module_handles_missing_deps { 1054 srcs, p.missingDeps = PathsAndMissingDepsForModuleSrcExcludes(ctx, p.props.Srcs, p.props.Exclude_srcs) 1055 } else { 1056 srcs = PathsForModuleSrcExcludes(ctx, p.props.Srcs, p.props.Exclude_srcs) 1057 } 1058 p.srcs = srcs.Strings() 1059 1060 for _, src := range srcs { 1061 p.rels = append(p.rels, src.Rel()) 1062 } 1063 1064 if p.props.Src != nil { 1065 src := PathForModuleSrc(ctx, *p.props.Src) 1066 if src != nil { 1067 p.src = src.String() 1068 p.rel = src.Rel() 1069 } 1070 } 1071 1072 if !p.props.Module_handles_missing_deps { 1073 p.missingDeps = ctx.GetMissingDependencies() 1074 } 1075 1076 ctx.Build(pctx, BuildParams{ 1077 Rule: Touch, 1078 Output: PathForModuleOut(ctx, "output"), 1079 }) 1080} 1081 1082type pathForModuleSrcOutputFileProviderModule struct { 1083 ModuleBase 1084 props struct { 1085 Outs []string 1086 Tagged []string 1087 } 1088 1089 outs Paths 1090 tagged Paths 1091} 1092 1093func pathForModuleSrcOutputFileProviderModuleFactory() Module { 1094 module := &pathForModuleSrcOutputFileProviderModule{} 1095 module.AddProperties(&module.props) 1096 InitAndroidModule(module) 1097 return module 1098} 1099 1100func (p *pathForModuleSrcOutputFileProviderModule) GenerateAndroidBuildActions(ctx ModuleContext) { 1101 for _, out := range p.props.Outs { 1102 p.outs = append(p.outs, PathForModuleOut(ctx, out)) 1103 } 1104 1105 for _, tagged := range p.props.Tagged { 1106 p.tagged = append(p.tagged, PathForModuleOut(ctx, tagged)) 1107 } 1108} 1109 1110func (p *pathForModuleSrcOutputFileProviderModule) OutputFiles(tag string) (Paths, error) { 1111 switch tag { 1112 case "": 1113 return p.outs, nil 1114 case ".tagged": 1115 return p.tagged, nil 1116 default: 1117 return nil, fmt.Errorf("unsupported tag %q", tag) 1118 } 1119} 1120 1121type pathForModuleSrcTestCase struct { 1122 name string 1123 bp string 1124 srcs []string 1125 rels []string 1126 src string 1127 rel string 1128} 1129 1130func testPathForModuleSrc(t *testing.T, tests []pathForModuleSrcTestCase) { 1131 for _, test := range tests { 1132 t.Run(test.name, func(t *testing.T) { 1133 fgBp := ` 1134 filegroup { 1135 name: "a", 1136 srcs: ["src/a"], 1137 } 1138 ` 1139 1140 ofpBp := ` 1141 output_file_provider { 1142 name: "b", 1143 outs: ["gen/b"], 1144 tagged: ["gen/c"], 1145 } 1146 ` 1147 1148 mockFS := MockFS{ 1149 "fg/Android.bp": []byte(fgBp), 1150 "foo/Android.bp": []byte(test.bp), 1151 "ofp/Android.bp": []byte(ofpBp), 1152 "fg/src/a": nil, 1153 "foo/src/b": nil, 1154 "foo/src/c": nil, 1155 "foo/src/d": nil, 1156 "foo/src/e/e": nil, 1157 "foo/src_special/$": nil, 1158 } 1159 1160 result := GroupFixturePreparers( 1161 FixtureRegisterWithContext(func(ctx RegistrationContext) { 1162 ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory) 1163 ctx.RegisterModuleType("output_file_provider", pathForModuleSrcOutputFileProviderModuleFactory) 1164 ctx.RegisterModuleType("filegroup", FileGroupFactory) 1165 }), 1166 mockFS.AddToFixture(), 1167 ).RunTest(t) 1168 1169 m := result.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule) 1170 1171 AssertStringPathsRelativeToTopEquals(t, "srcs", result.Config, test.srcs, m.srcs) 1172 AssertStringPathsRelativeToTopEquals(t, "rels", result.Config, test.rels, m.rels) 1173 AssertStringPathRelativeToTopEquals(t, "src", result.Config, test.src, m.src) 1174 AssertStringPathRelativeToTopEquals(t, "rel", result.Config, test.rel, m.rel) 1175 }) 1176 } 1177} 1178 1179func TestPathsForModuleSrc(t *testing.T) { 1180 tests := []pathForModuleSrcTestCase{ 1181 { 1182 name: "path", 1183 bp: ` 1184 test { 1185 name: "foo", 1186 srcs: ["src/b"], 1187 }`, 1188 srcs: []string{"foo/src/b"}, 1189 rels: []string{"src/b"}, 1190 }, 1191 { 1192 name: "glob", 1193 bp: ` 1194 test { 1195 name: "foo", 1196 srcs: [ 1197 "src/*", 1198 "src/e/*", 1199 ], 1200 }`, 1201 srcs: []string{"foo/src/b", "foo/src/c", "foo/src/d", "foo/src/e/e"}, 1202 rels: []string{"src/b", "src/c", "src/d", "src/e/e"}, 1203 }, 1204 { 1205 name: "recursive glob", 1206 bp: ` 1207 test { 1208 name: "foo", 1209 srcs: ["src/**/*"], 1210 }`, 1211 srcs: []string{"foo/src/b", "foo/src/c", "foo/src/d", "foo/src/e/e"}, 1212 rels: []string{"src/b", "src/c", "src/d", "src/e/e"}, 1213 }, 1214 { 1215 name: "filegroup", 1216 bp: ` 1217 test { 1218 name: "foo", 1219 srcs: [":a"], 1220 }`, 1221 srcs: []string{"fg/src/a"}, 1222 rels: []string{"src/a"}, 1223 }, 1224 { 1225 name: "output file provider", 1226 bp: ` 1227 test { 1228 name: "foo", 1229 srcs: [":b"], 1230 }`, 1231 srcs: []string{"out/soong/.intermediates/ofp/b/gen/b"}, 1232 rels: []string{"gen/b"}, 1233 }, 1234 { 1235 name: "output file provider tagged", 1236 bp: ` 1237 test { 1238 name: "foo", 1239 srcs: [":b{.tagged}"], 1240 }`, 1241 srcs: []string{"out/soong/.intermediates/ofp/b/gen/c"}, 1242 rels: []string{"gen/c"}, 1243 }, 1244 { 1245 name: "output file provider with exclude", 1246 bp: ` 1247 test { 1248 name: "foo", 1249 srcs: [":b", ":c"], 1250 exclude_srcs: [":c"] 1251 } 1252 output_file_provider { 1253 name: "c", 1254 outs: ["gen/c"], 1255 }`, 1256 srcs: []string{"out/soong/.intermediates/ofp/b/gen/b"}, 1257 rels: []string{"gen/b"}, 1258 }, 1259 { 1260 name: "special characters glob", 1261 bp: ` 1262 test { 1263 name: "foo", 1264 srcs: ["src_special/*"], 1265 }`, 1266 srcs: []string{"foo/src_special/$"}, 1267 rels: []string{"src_special/$"}, 1268 }, 1269 } 1270 1271 testPathForModuleSrc(t, tests) 1272} 1273 1274func TestPathForModuleSrc(t *testing.T) { 1275 tests := []pathForModuleSrcTestCase{ 1276 { 1277 name: "path", 1278 bp: ` 1279 test { 1280 name: "foo", 1281 src: "src/b", 1282 }`, 1283 src: "foo/src/b", 1284 rel: "src/b", 1285 }, 1286 { 1287 name: "glob", 1288 bp: ` 1289 test { 1290 name: "foo", 1291 src: "src/e/*", 1292 }`, 1293 src: "foo/src/e/e", 1294 rel: "src/e/e", 1295 }, 1296 { 1297 name: "filegroup", 1298 bp: ` 1299 test { 1300 name: "foo", 1301 src: ":a", 1302 }`, 1303 src: "fg/src/a", 1304 rel: "src/a", 1305 }, 1306 { 1307 name: "output file provider", 1308 bp: ` 1309 test { 1310 name: "foo", 1311 src: ":b", 1312 }`, 1313 src: "out/soong/.intermediates/ofp/b/gen/b", 1314 rel: "gen/b", 1315 }, 1316 { 1317 name: "output file provider tagged", 1318 bp: ` 1319 test { 1320 name: "foo", 1321 src: ":b{.tagged}", 1322 }`, 1323 src: "out/soong/.intermediates/ofp/b/gen/c", 1324 rel: "gen/c", 1325 }, 1326 { 1327 name: "special characters glob", 1328 bp: ` 1329 test { 1330 name: "foo", 1331 src: "src_special/*", 1332 }`, 1333 src: "foo/src_special/$", 1334 rel: "src_special/$", 1335 }, 1336 } 1337 1338 testPathForModuleSrc(t, tests) 1339} 1340 1341func TestPathsForModuleSrc_AllowMissingDependencies(t *testing.T) { 1342 bp := ` 1343 test { 1344 name: "foo", 1345 srcs: [":a"], 1346 exclude_srcs: [":b"], 1347 src: ":c", 1348 } 1349 1350 test { 1351 name: "bar", 1352 srcs: [":d"], 1353 exclude_srcs: [":e"], 1354 module_handles_missing_deps: true, 1355 } 1356 ` 1357 1358 result := GroupFixturePreparers( 1359 PrepareForTestWithAllowMissingDependencies, 1360 FixtureRegisterWithContext(func(ctx RegistrationContext) { 1361 ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory) 1362 }), 1363 FixtureWithRootAndroidBp(bp), 1364 ).RunTest(t) 1365 1366 foo := result.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule) 1367 1368 AssertArrayString(t, "foo missing deps", []string{"a", "b", "c"}, foo.missingDeps) 1369 AssertArrayString(t, "foo srcs", []string{}, foo.srcs) 1370 AssertStringEquals(t, "foo src", "", foo.src) 1371 1372 bar := result.ModuleForTests("bar", "").Module().(*pathForModuleSrcTestModule) 1373 1374 AssertArrayString(t, "bar missing deps", []string{"d", "e"}, bar.missingDeps) 1375 AssertArrayString(t, "bar srcs", []string{}, bar.srcs) 1376} 1377 1378func TestPathRelativeToTop(t *testing.T) { 1379 testConfig := pathTestConfig("/tmp/build/top") 1380 deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}} 1381 1382 ctx := &testModuleInstallPathContext{ 1383 baseModuleContext: baseModuleContext{ 1384 os: deviceTarget.Os, 1385 target: deviceTarget, 1386 }, 1387 } 1388 ctx.baseModuleContext.config = testConfig 1389 1390 t.Run("install for soong", func(t *testing.T) { 1391 p := PathForModuleInstall(ctx, "install/path") 1392 AssertPathRelativeToTopEquals(t, "install path for soong", "out/soong/target/product/test_device/system/install/path", p) 1393 }) 1394 t.Run("install for make", func(t *testing.T) { 1395 p := PathForModuleInstall(ctx, "install/path").ToMakePath() 1396 AssertPathRelativeToTopEquals(t, "install path for make", "out/target/product/test_device/system/install/path", p) 1397 }) 1398 t.Run("output", func(t *testing.T) { 1399 p := PathForOutput(ctx, "output/path") 1400 AssertPathRelativeToTopEquals(t, "output path", "out/soong/output/path", p) 1401 }) 1402 t.Run("source", func(t *testing.T) { 1403 p := PathForSource(ctx, "source/path") 1404 AssertPathRelativeToTopEquals(t, "source path", "source/path", p) 1405 }) 1406 t.Run("mixture", func(t *testing.T) { 1407 paths := Paths{ 1408 PathForModuleInstall(ctx, "install/path"), 1409 PathForModuleInstall(ctx, "install/path").ToMakePath(), 1410 PathForOutput(ctx, "output/path"), 1411 PathForSource(ctx, "source/path"), 1412 } 1413 1414 expected := []string{ 1415 "out/soong/target/product/test_device/system/install/path", 1416 "out/target/product/test_device/system/install/path", 1417 "out/soong/output/path", 1418 "source/path", 1419 } 1420 AssertPathsRelativeToTopEquals(t, "mixture", expected, paths) 1421 }) 1422} 1423 1424func ExampleOutputPath_ReplaceExtension() { 1425 ctx := &configErrorWrapper{ 1426 config: TestConfig("out", nil, "", nil), 1427 } 1428 p := PathForOutput(ctx, "system/framework").Join(ctx, "boot.art") 1429 p2 := p.ReplaceExtension(ctx, "oat") 1430 fmt.Println(p, p2) 1431 fmt.Println(p.Rel(), p2.Rel()) 1432 1433 // Output: 1434 // out/system/framework/boot.art out/system/framework/boot.oat 1435 // boot.art boot.oat 1436} 1437 1438func ExampleOutputPath_InSameDir() { 1439 ctx := &configErrorWrapper{ 1440 config: TestConfig("out", nil, "", nil), 1441 } 1442 p := PathForOutput(ctx, "system/framework").Join(ctx, "boot.art") 1443 p2 := p.InSameDir(ctx, "oat", "arm", "boot.vdex") 1444 fmt.Println(p, p2) 1445 fmt.Println(p.Rel(), p2.Rel()) 1446 1447 // Output: 1448 // out/system/framework/boot.art out/system/framework/oat/arm/boot.vdex 1449 // boot.art oat/arm/boot.vdex 1450} 1451 1452func BenchmarkFirstUniquePaths(b *testing.B) { 1453 implementations := []struct { 1454 name string 1455 f func(Paths) Paths 1456 }{ 1457 { 1458 name: "list", 1459 f: firstUniquePathsList, 1460 }, 1461 { 1462 name: "map", 1463 f: firstUniquePathsMap, 1464 }, 1465 } 1466 const maxSize = 1024 1467 uniquePaths := make(Paths, maxSize) 1468 for i := range uniquePaths { 1469 uniquePaths[i] = PathForTesting(strconv.Itoa(i)) 1470 } 1471 samePath := make(Paths, maxSize) 1472 for i := range samePath { 1473 samePath[i] = uniquePaths[0] 1474 } 1475 1476 f := func(b *testing.B, imp func(Paths) Paths, paths Paths) { 1477 for i := 0; i < b.N; i++ { 1478 b.ReportAllocs() 1479 paths = append(Paths(nil), paths...) 1480 imp(paths) 1481 } 1482 } 1483 1484 for n := 1; n <= maxSize; n <<= 1 { 1485 b.Run(strconv.Itoa(n), func(b *testing.B) { 1486 for _, implementation := range implementations { 1487 b.Run(implementation.name, func(b *testing.B) { 1488 b.Run("same", func(b *testing.B) { 1489 f(b, implementation.f, samePath[:n]) 1490 }) 1491 b.Run("unique", func(b *testing.B) { 1492 f(b, implementation.f, uniquePaths[:n]) 1493 }) 1494 }) 1495 } 1496 }) 1497 } 1498} 1499