1// Copyright 2022 Google LLC 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 projectmetadata 16 17import ( 18 "fmt" 19 "strings" 20 "testing" 21 22 "android/soong/compliance/project_metadata_proto" 23 "android/soong/tools/compliance/testfs" 24) 25 26const ( 27 // EMPTY represents a METADATA file with no recognized fields 28 EMPTY = `` 29 30 // INVALID_NAME represents a METADATA file with the wrong type of name 31 INVALID_NAME = `name: a library\n` 32 33 // INVALID_DESCRIPTION represents a METADATA file with the wrong type of description 34 INVALID_DESCRIPTION = `description: unquoted text\n` 35 36 // INVALID_VERSION represents a METADATA file with the wrong type of version 37 INVALID_VERSION = `third_party { version: 1 }` 38 39 // MY_LIB_1_0 represents a METADATA file for version 1.0 of mylib 40 MY_LIB_1_0 = `name: "mylib" description: "my library" third_party { version: "1.0" }` 41 42 // NO_NAME_0_1 represents a METADATA file with a description but no name 43 NO_NAME_0_1 = `description: "my library" third_party { version: "0.1" }` 44 45 // URL values per type 46 GIT_URL = "http://example.github.com/my_lib" 47 SVN_URL = "http://example.svn.com/my_lib" 48 HG_URL = "http://example.hg.com/my_lib" 49 DARCS_URL = "http://example.darcs.com/my_lib" 50 PIPER_URL = "http://google3/third_party/my/package" 51 HOMEPAGE_URL = "http://example.com/homepage" 52 OTHER_URL = "http://google.com/" 53 ARCHIVE_URL = "http://ftp.example.com/" 54 LOCAL_SOURCE_URL = "https://android.googlesource.com/platform/external/apache-http/" 55) 56 57// libWithUrl returns a METADATA file with the right download url 58func libWithUrl(urlTypes ...string) string { 59 var sb strings.Builder 60 61 fmt.Fprintln(&sb, `name: "mylib" description: "my library" 62 third_party { 63 version: "1.0"`) 64 65 for _, urltype := range urlTypes { 66 var urlValue string 67 switch urltype { 68 case "GIT": 69 urlValue = GIT_URL 70 case "SVN": 71 urlValue = SVN_URL 72 case "HG": 73 urlValue = HG_URL 74 case "DARCS": 75 urlValue = DARCS_URL 76 case "PIPER": 77 urlValue = PIPER_URL 78 case "HOMEPAGE": 79 urlValue = HOMEPAGE_URL 80 case "OTHER": 81 urlValue = OTHER_URL 82 case "ARCHIVE": 83 urlValue = ARCHIVE_URL 84 case "LOCAL_SOURCE": 85 urlValue = LOCAL_SOURCE_URL 86 default: 87 panic(fmt.Errorf("unknown url type: %q. Please update libWithUrl() in build/make/tools/compliance/projectmetadata/projectmetadata_test.go", urltype)) 88 } 89 fmt.Fprintf(&sb, " url { type: %s value: %q }\n", urltype, urlValue) 90 } 91 fmt.Fprintln(&sb, `}`) 92 93 return sb.String() 94} 95 96func TestVerifyAllUrlTypes(t *testing.T) { 97 t.Run("verifyAllUrlTypes", func(t *testing.T) { 98 types := make([]string, 0, len(project_metadata_proto.URL_Type_value)) 99 for t := range project_metadata_proto.URL_Type_value { 100 types = append(types, t) 101 } 102 libWithUrl(types...) 103 }) 104} 105 106func TestUnknownPanics(t *testing.T) { 107 t.Run("Unknown panics", func(t *testing.T) { 108 defer func() { 109 if r := recover(); r == nil { 110 t.Errorf("unexpected success: got no error, want panic") 111 } 112 }() 113 libWithUrl("SOME WILD VALUE THAT DOES NOT EXIST") 114 }) 115} 116 117func TestReadMetadataForProjects(t *testing.T) { 118 tests := []struct { 119 name string 120 fs *testfs.TestFS 121 projects []string 122 expectedError string 123 expected []pmeta 124 }{ 125 { 126 name: "trivial", 127 fs: &testfs.TestFS{ 128 "/a/METADATA": []byte("name: \"Android\"\n"), 129 }, 130 projects: []string{"/a"}, 131 expected: []pmeta{{ 132 project: "/a", 133 versionedName: "Android", 134 name: "Android", 135 version: "", 136 downloadUrl: "", 137 }}, 138 }, 139 { 140 name: "versioned", 141 fs: &testfs.TestFS{ 142 "/a/METADATA": []byte(MY_LIB_1_0), 143 }, 144 projects: []string{"/a"}, 145 expected: []pmeta{{ 146 project: "/a", 147 versionedName: "mylib_v_1.0", 148 name: "mylib", 149 version: "1.0", 150 downloadUrl: "", 151 }}, 152 }, 153 { 154 name: "lib_with_homepage", 155 fs: &testfs.TestFS{ 156 "/a/METADATA": []byte(libWithUrl("HOMEPAGE")), 157 }, 158 projects: []string{"/a"}, 159 expected: []pmeta{{ 160 project: "/a", 161 versionedName: "mylib_v_1.0", 162 name: "mylib", 163 version: "1.0", 164 downloadUrl: "", 165 }}, 166 }, 167 { 168 name: "lib_with_git", 169 fs: &testfs.TestFS{ 170 "/a/METADATA": []byte(libWithUrl("GIT")), 171 }, 172 projects: []string{"/a"}, 173 expected: []pmeta{{ 174 project: "/a", 175 versionedName: "mylib_v_1.0", 176 name: "mylib", 177 version: "1.0", 178 downloadUrl: GIT_URL, 179 }}, 180 }, 181 { 182 name: "lib_with_svn", 183 fs: &testfs.TestFS{ 184 "/a/METADATA": []byte(libWithUrl("SVN")), 185 }, 186 projects: []string{"/a"}, 187 expected: []pmeta{{ 188 project: "/a", 189 versionedName: "mylib_v_1.0", 190 name: "mylib", 191 version: "1.0", 192 downloadUrl: SVN_URL, 193 }}, 194 }, 195 { 196 name: "lib_with_hg", 197 fs: &testfs.TestFS{ 198 "/a/METADATA": []byte(libWithUrl("HG")), 199 }, 200 projects: []string{"/a"}, 201 expected: []pmeta{{ 202 project: "/a", 203 versionedName: "mylib_v_1.0", 204 name: "mylib", 205 version: "1.0", 206 downloadUrl: HG_URL, 207 }}, 208 }, 209 { 210 name: "lib_with_darcs", 211 fs: &testfs.TestFS{ 212 "/a/METADATA": []byte(libWithUrl("DARCS")), 213 }, 214 projects: []string{"/a"}, 215 expected: []pmeta{{ 216 project: "/a", 217 versionedName: "mylib_v_1.0", 218 name: "mylib", 219 version: "1.0", 220 downloadUrl: DARCS_URL, 221 }}, 222 }, 223 { 224 name: "lib_with_piper", 225 fs: &testfs.TestFS{ 226 "/a/METADATA": []byte(libWithUrl("PIPER")), 227 }, 228 projects: []string{"/a"}, 229 expected: []pmeta{{ 230 project: "/a", 231 versionedName: "mylib_v_1.0", 232 name: "mylib", 233 version: "1.0", 234 downloadUrl: "", 235 }}, 236 }, 237 { 238 name: "lib_with_other", 239 fs: &testfs.TestFS{ 240 "/a/METADATA": []byte(libWithUrl("OTHER")), 241 }, 242 projects: []string{"/a"}, 243 expected: []pmeta{{ 244 project: "/a", 245 versionedName: "mylib_v_1.0", 246 name: "mylib", 247 version: "1.0", 248 downloadUrl: "", 249 }}, 250 }, 251 { 252 name: "lib_with_local_source", 253 fs: &testfs.TestFS{ 254 "/a/METADATA": []byte(libWithUrl("LOCAL_SOURCE")), 255 }, 256 projects: []string{"/a"}, 257 expected: []pmeta{{ 258 project: "/a", 259 versionedName: "mylib_v_1.0", 260 name: "mylib", 261 version: "1.0", 262 downloadUrl: "", 263 }}, 264 }, 265 { 266 name: "lib_with_archive", 267 fs: &testfs.TestFS{ 268 "/a/METADATA": []byte(libWithUrl("ARCHIVE")), 269 }, 270 projects: []string{"/a"}, 271 expected: []pmeta{{ 272 project: "/a", 273 versionedName: "mylib_v_1.0", 274 name: "mylib", 275 version: "1.0", 276 downloadUrl: "", 277 }}, 278 }, 279 { 280 name: "lib_with_all_downloads", 281 fs: &testfs.TestFS{ 282 "/a/METADATA": []byte(libWithUrl("DARCS", "HG", "SVN", "GIT")), 283 }, 284 projects: []string{"/a"}, 285 expected: []pmeta{{ 286 project: "/a", 287 versionedName: "mylib_v_1.0", 288 name: "mylib", 289 version: "1.0", 290 downloadUrl: GIT_URL, 291 }}, 292 }, 293 { 294 name: "lib_with_all_downloads_in_different_order", 295 fs: &testfs.TestFS{ 296 "/a/METADATA": []byte(libWithUrl("DARCS", "GIT", "SVN", "HG")), 297 }, 298 projects: []string{"/a"}, 299 expected: []pmeta{{ 300 project: "/a", 301 versionedName: "mylib_v_1.0", 302 name: "mylib", 303 version: "1.0", 304 downloadUrl: GIT_URL, 305 }}, 306 }, 307 { 308 name: "lib_with_all_but_git", 309 fs: &testfs.TestFS{ 310 "/a/METADATA": []byte(libWithUrl("DARCS", "HG", "SVN")), 311 }, 312 projects: []string{"/a"}, 313 expected: []pmeta{{ 314 project: "/a", 315 versionedName: "mylib_v_1.0", 316 name: "mylib", 317 version: "1.0", 318 downloadUrl: SVN_URL, 319 }}, 320 }, 321 { 322 name: "lib_with_all_but_git_and_svn", 323 fs: &testfs.TestFS{ 324 "/a/METADATA": []byte(libWithUrl("DARCS", "HG")), 325 }, 326 projects: []string{"/a"}, 327 expected: []pmeta{{ 328 project: "/a", 329 versionedName: "mylib_v_1.0", 330 name: "mylib", 331 version: "1.0", 332 downloadUrl: HG_URL, 333 }}, 334 }, 335 { 336 name: "lib_with_all_nondownloads_and_git", 337 fs: &testfs.TestFS{ 338 "/a/METADATA": []byte(libWithUrl("HOMEPAGE", "LOCAL_SOURCE", "PIPER", "ARCHIVE", "GIT")), 339 }, 340 projects: []string{"/a"}, 341 expected: []pmeta{{ 342 project: "/a", 343 versionedName: "mylib_v_1.0", 344 name: "mylib", 345 version: "1.0", 346 downloadUrl: GIT_URL, 347 }}, 348 }, 349 { 350 name: "lib_with_all_nondownloads", 351 fs: &testfs.TestFS{ 352 "/a/METADATA": []byte(libWithUrl("HOMEPAGE", "LOCAL_SOURCE", "PIPER", "ARCHIVE")), 353 }, 354 projects: []string{"/a"}, 355 expected: []pmeta{{ 356 project: "/a", 357 versionedName: "mylib_v_1.0", 358 name: "mylib", 359 version: "1.0", 360 downloadUrl: "", 361 }}, 362 }, 363 { 364 name: "lib_with_all_nondownloads", 365 fs: &testfs.TestFS{ 366 "/a/METADATA": []byte(libWithUrl()), 367 }, 368 projects: []string{"/a"}, 369 expected: []pmeta{{ 370 project: "/a", 371 versionedName: "mylib_v_1.0", 372 name: "mylib", 373 version: "1.0", 374 downloadUrl: "", 375 }}, 376 }, 377 { 378 name: "versioneddesc", 379 fs: &testfs.TestFS{ 380 "/a/METADATA": []byte(NO_NAME_0_1), 381 }, 382 projects: []string{"/a"}, 383 expected: []pmeta{{ 384 project: "/a", 385 versionedName: "my library", 386 name: "", 387 version: "0.1", 388 downloadUrl: "", 389 }}, 390 }, 391 { 392 name: "unterminated", 393 fs: &testfs.TestFS{ 394 "/a/METADATA": []byte("name: \"Android\n"), 395 }, 396 projects: []string{"/a"}, 397 expectedError: `invalid character '\n' in string`, 398 }, 399 { 400 name: "abc", 401 fs: &testfs.TestFS{ 402 "/a/METADATA": []byte(EMPTY), 403 "/b/METADATA": []byte(MY_LIB_1_0), 404 "/c/METADATA": []byte(NO_NAME_0_1), 405 }, 406 projects: []string{"/a", "/b", "/c"}, 407 expected: []pmeta{ 408 { 409 project: "/a", 410 versionedName: "", 411 name: "", 412 version: "", 413 downloadUrl: "", 414 }, 415 { 416 project: "/b", 417 versionedName: "mylib_v_1.0", 418 name: "mylib", 419 version: "1.0", 420 downloadUrl: "", 421 }, 422 { 423 project: "/c", 424 versionedName: "my library", 425 name: "", 426 version: "0.1", 427 downloadUrl: "", 428 }, 429 }, 430 }, 431 { 432 name: "ab", 433 fs: &testfs.TestFS{ 434 "/a/METADATA": []byte(EMPTY), 435 "/b/METADATA": []byte(MY_LIB_1_0), 436 }, 437 projects: []string{"/a", "/b", "/c"}, 438 expected: []pmeta{ 439 { 440 project: "/a", 441 versionedName: "", 442 name: "", 443 version: "", 444 downloadUrl: "", 445 }, 446 { 447 project: "/b", 448 versionedName: "mylib_v_1.0", 449 name: "mylib", 450 version: "1.0", 451 downloadUrl: "", 452 }, 453 }, 454 }, 455 { 456 name: "ac", 457 fs: &testfs.TestFS{ 458 "/a/METADATA": []byte(EMPTY), 459 "/c/METADATA": []byte(NO_NAME_0_1), 460 }, 461 projects: []string{"/a", "/b", "/c"}, 462 expected: []pmeta{ 463 { 464 project: "/a", 465 versionedName: "", 466 name: "", 467 version: "", 468 downloadUrl: "", 469 }, 470 { 471 project: "/c", 472 versionedName: "my library", 473 name: "", 474 version: "0.1", 475 downloadUrl: "", 476 }, 477 }, 478 }, 479 { 480 name: "bc", 481 fs: &testfs.TestFS{ 482 "/b/METADATA": []byte(MY_LIB_1_0), 483 "/c/METADATA": []byte(NO_NAME_0_1), 484 }, 485 projects: []string{"/a", "/b", "/c"}, 486 expected: []pmeta{ 487 { 488 project: "/b", 489 versionedName: "mylib_v_1.0", 490 name: "mylib", 491 version: "1.0", 492 downloadUrl: "", 493 }, 494 { 495 project: "/c", 496 versionedName: "my library", 497 name: "", 498 version: "0.1", 499 downloadUrl: "", 500 }, 501 }, 502 }, 503 { 504 name: "wrongnametype", 505 fs: &testfs.TestFS{ 506 "/a/METADATA": []byte(INVALID_NAME), 507 }, 508 projects: []string{"/a"}, 509 expectedError: `invalid value for string type`, 510 }, 511 { 512 name: "wrongdescriptiontype", 513 fs: &testfs.TestFS{ 514 "/a/METADATA": []byte(INVALID_DESCRIPTION), 515 }, 516 projects: []string{"/a"}, 517 expectedError: `invalid value for string type`, 518 }, 519 { 520 name: "wrongversiontype", 521 fs: &testfs.TestFS{ 522 "/a/METADATA": []byte(INVALID_VERSION), 523 }, 524 projects: []string{"/a"}, 525 expectedError: `invalid value for string type`, 526 }, 527 { 528 name: "wrongtype", 529 fs: &testfs.TestFS{ 530 "/a/METADATA": []byte(INVALID_NAME + INVALID_DESCRIPTION + INVALID_VERSION), 531 }, 532 projects: []string{"/a"}, 533 expectedError: `invalid value for string type`, 534 }, 535 { 536 name: "empty", 537 fs: &testfs.TestFS{ 538 "/a/METADATA": []byte(EMPTY), 539 }, 540 projects: []string{"/a"}, 541 expected: []pmeta{{ 542 project: "/a", 543 versionedName: "", 544 name: "", 545 version: "", 546 downloadUrl: "", 547 }}, 548 }, 549 { 550 name: "emptyother", 551 fs: &testfs.TestFS{ 552 "/a/METADATA.bp": []byte(EMPTY), 553 }, 554 projects: []string{"/a"}, 555 }, 556 { 557 name: "emptyfs", 558 fs: &testfs.TestFS{}, 559 projects: []string{"/a"}, 560 }, 561 { 562 name: "override", 563 fs: &testfs.TestFS{ 564 "/a/METADATA": []byte(INVALID_NAME + INVALID_DESCRIPTION + INVALID_VERSION), 565 "/a/METADATA.android": []byte(MY_LIB_1_0), 566 }, 567 projects: []string{"/a"}, 568 expected: []pmeta{{ 569 project: "/a", 570 versionedName: "mylib_v_1.0", 571 name: "mylib", 572 version: "1.0", 573 downloadUrl: "", 574 }}, 575 }, 576 { 577 name: "enchilada", 578 fs: &testfs.TestFS{ 579 "/a/METADATA": []byte(INVALID_NAME + INVALID_DESCRIPTION + INVALID_VERSION), 580 "/a/METADATA.android": []byte(EMPTY), 581 "/b/METADATA": []byte(MY_LIB_1_0), 582 "/c/METADATA": []byte(NO_NAME_0_1), 583 }, 584 projects: []string{"/a", "/b", "/c"}, 585 expected: []pmeta{ 586 { 587 project: "/a", 588 versionedName: "", 589 name: "", 590 version: "", 591 downloadUrl: "", 592 }, 593 { 594 project: "/b", 595 versionedName: "mylib_v_1.0", 596 name: "mylib", 597 version: "1.0", 598 downloadUrl: "", 599 }, 600 { 601 project: "/c", 602 versionedName: "my library", 603 name: "", 604 version: "0.1", 605 downloadUrl: "", 606 }, 607 }, 608 }, 609 } 610 for _, tt := range tests { 611 t.Run(tt.name, func(t *testing.T) { 612 ix := NewIndex(tt.fs) 613 pms, err := ix.MetadataForProjects(tt.projects...) 614 if err != nil { 615 if len(tt.expectedError) == 0 { 616 t.Errorf("unexpected error: got %s, want no error", err) 617 } else if !strings.Contains(err.Error(), tt.expectedError) { 618 t.Errorf("unexpected error: got %s, want %q", err, tt.expectedError) 619 } 620 return 621 } 622 t.Logf("actual %d project metadata", len(pms)) 623 for _, pm := range pms { 624 t.Logf(" %v", pm.String()) 625 } 626 t.Logf("expected %d project metadata", len(tt.expected)) 627 for _, pm := range tt.expected { 628 t.Logf(" %s", pm.String()) 629 } 630 if len(tt.expectedError) > 0 { 631 t.Errorf("unexpected success: got no error, want %q err", tt.expectedError) 632 return 633 } 634 if len(pms) != len(tt.expected) { 635 t.Errorf("missing project metadata: got %d project metadata, want %d", len(pms), len(tt.expected)) 636 } 637 for i := 0; i < len(pms) && i < len(tt.expected); i++ { 638 if msg := tt.expected[i].difference(pms[i]); msg != "" { 639 t.Errorf("unexpected metadata starting at index %d: %s", i, msg) 640 return 641 } 642 } 643 if len(pms) < len(tt.expected) { 644 t.Errorf("missing metadata starting at index %d: got nothing, want %s", len(pms), tt.expected[len(pms)].String()) 645 } 646 if len(tt.expected) < len(pms) { 647 t.Errorf("unexpected metadata starting at index %d: got %s, want nothing", len(tt.expected), pms[len(tt.expected)].String()) 648 } 649 }) 650 } 651} 652 653type pmeta struct { 654 project string 655 versionedName string 656 name string 657 version string 658 downloadUrl string 659} 660 661func (pm pmeta) String() string { 662 return fmt.Sprintf("project: %q versionedName: %q name: %q version: %q downloadUrl: %q\n", pm.project, pm.versionedName, pm.name, pm.version, pm.downloadUrl) 663} 664 665func (pm pmeta) equals(other *ProjectMetadata) bool { 666 if pm.project != other.project { 667 return false 668 } 669 if pm.versionedName != other.VersionedName() { 670 return false 671 } 672 if pm.name != other.Name() { 673 return false 674 } 675 if pm.version != other.Version() { 676 return false 677 } 678 if pm.downloadUrl != other.UrlsByTypeName().DownloadUrl() { 679 return false 680 } 681 return true 682} 683 684func (pm pmeta) difference(other *ProjectMetadata) string { 685 if pm.equals(other) { 686 return "" 687 } 688 var sb strings.Builder 689 fmt.Fprintf(&sb, "got") 690 if pm.project != other.project { 691 fmt.Fprintf(&sb, " project: %q", other.project) 692 } 693 if pm.versionedName != other.VersionedName() { 694 fmt.Fprintf(&sb, " versionedName: %q", other.VersionedName()) 695 } 696 if pm.name != other.Name() { 697 fmt.Fprintf(&sb, " name: %q", other.Name()) 698 } 699 if pm.version != other.Version() { 700 fmt.Fprintf(&sb, " version: %q", other.Version()) 701 } 702 if pm.downloadUrl != other.UrlsByTypeName().DownloadUrl() { 703 fmt.Fprintf(&sb, " downloadUrl: %q", other.UrlsByTypeName().DownloadUrl()) 704 } 705 fmt.Fprintf(&sb, ", want") 706 if pm.project != other.project { 707 fmt.Fprintf(&sb, " project: %q", pm.project) 708 } 709 if pm.versionedName != other.VersionedName() { 710 fmt.Fprintf(&sb, " versionedName: %q", pm.versionedName) 711 } 712 if pm.name != other.Name() { 713 fmt.Fprintf(&sb, " name: %q", pm.name) 714 } 715 if pm.version != other.Version() { 716 fmt.Fprintf(&sb, " version: %q", pm.version) 717 } 718 if pm.downloadUrl != other.UrlsByTypeName().DownloadUrl() { 719 fmt.Fprintf(&sb, " downloadUrl: %q", pm.downloadUrl) 720 } 721 return sb.String() 722} 723