1// Copyright 2021 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 main 16 17import ( 18 "bufio" 19 "bytes" 20 "fmt" 21 "html" 22 "os" 23 "reflect" 24 "regexp" 25 "strings" 26 "testing" 27 28 "android/soong/tools/compliance" 29) 30 31var ( 32 horizontalRule = regexp.MustCompile(`^\s*<hr>\s*$`) 33 bodyTag = regexp.MustCompile(`^\s*<body>\s*$`) 34 boilerPlate = regexp.MustCompile(`^\s*(?:<ul class="file-list">|<ul>|</.*)\s*$`) 35 tocTag = regexp.MustCompile(`^\s*<ul class="toc">\s*$`) 36 libraryName = regexp.MustCompile(`^\s*<strong>(.*)</strong>\s\s*used\s\s*by\s*:\s*$`) 37 licenseText = regexp.MustCompile(`^\s*<a id="[^"]{32}"/><pre class="license-text">(.*)$`) 38 titleTag = regexp.MustCompile(`^\s*<title>(.*)</title>\s*$`) 39 h1Tag = regexp.MustCompile(`^\s*<h1>(.*)</h1>\s*$`) 40 usedByTarget = regexp.MustCompile(`^\s*<li>(?:<a href="#id[0-9]+">)?((?:out/(?:[^/<]*/)+)[^/<]*)(?:</a>)?\s*$`) 41 installTarget = regexp.MustCompile(`^\s*<li id="id[0-9]+"><strong>(.*)</strong>\s*$`) 42 libReference = regexp.MustCompile(`^\s*<li><a href="#[^"]{32}">(.*)</a>\s*$`) 43) 44 45func TestMain(m *testing.M) { 46 // Change into the parent directory before running the tests 47 // so they can find the testdata directory. 48 if err := os.Chdir(".."); err != nil { 49 fmt.Printf("failed to change to testdata directory: %s\n", err) 50 os.Exit(1) 51 } 52 os.Exit(m.Run()) 53} 54 55func Test(t *testing.T) { 56 tests := []struct { 57 condition string 58 name string 59 outDir string 60 roots []string 61 includeTOC bool 62 stripPrefix string 63 title string 64 expectedOut []matcher 65 expectedDeps []string 66 }{ 67 { 68 condition: "firstparty", 69 name: "apex", 70 roots: []string{"highest.apex.meta_lic"}, 71 expectedOut: []matcher{ 72 hr{}, 73 library{"Android"}, 74 usedBy{"highest.apex"}, 75 usedBy{"highest.apex/bin/bin1"}, 76 usedBy{"highest.apex/bin/bin2"}, 77 usedBy{"highest.apex/lib/liba.so"}, 78 usedBy{"highest.apex/lib/libb.so"}, 79 firstParty{}, 80 }, 81 expectedDeps: []string{ 82 "testdata/firstparty/FIRST_PARTY_LICENSE", 83 "testdata/firstparty/bin/bin1.meta_lic", 84 "testdata/firstparty/bin/bin2.meta_lic", 85 "testdata/firstparty/highest.apex.meta_lic", 86 "testdata/firstparty/lib/liba.so.meta_lic", 87 "testdata/firstparty/lib/libb.so.meta_lic", 88 "testdata/firstparty/lib/libc.a.meta_lic", 89 "testdata/firstparty/lib/libd.so.meta_lic", 90 }, 91 }, 92 { 93 condition: "firstparty", 94 name: "apex+toc", 95 roots: []string{"highest.apex.meta_lic"}, 96 includeTOC: true, 97 expectedOut: []matcher{ 98 toc{}, 99 target{"highest.apex"}, 100 uses{"Android"}, 101 target{"highest.apex/bin/bin1"}, 102 uses{"Android"}, 103 target{"highest.apex/bin/bin2"}, 104 uses{"Android"}, 105 target{"highest.apex/lib/liba.so"}, 106 uses{"Android"}, 107 target{"highest.apex/lib/libb.so"}, 108 uses{"Android"}, 109 hr{}, 110 library{"Android"}, 111 usedBy{"highest.apex"}, 112 usedBy{"highest.apex/bin/bin1"}, 113 usedBy{"highest.apex/bin/bin2"}, 114 usedBy{"highest.apex/lib/liba.so"}, 115 usedBy{"highest.apex/lib/libb.so"}, 116 firstParty{}, 117 }, 118 expectedDeps: []string{ 119 "testdata/firstparty/FIRST_PARTY_LICENSE", 120 "testdata/firstparty/bin/bin1.meta_lic", 121 "testdata/firstparty/bin/bin2.meta_lic", 122 "testdata/firstparty/highest.apex.meta_lic", 123 "testdata/firstparty/lib/liba.so.meta_lic", 124 "testdata/firstparty/lib/libb.so.meta_lic", 125 "testdata/firstparty/lib/libc.a.meta_lic", 126 "testdata/firstparty/lib/libd.so.meta_lic", 127 }, 128 }, 129 { 130 condition: "firstparty", 131 name: "apex-with-title", 132 roots: []string{"highest.apex.meta_lic"}, 133 title: "Emperor", 134 expectedOut: []matcher{ 135 pageTitle{"Emperor"}, 136 hr{}, 137 library{"Android"}, 138 usedBy{"highest.apex"}, 139 usedBy{"highest.apex/bin/bin1"}, 140 usedBy{"highest.apex/bin/bin2"}, 141 usedBy{"highest.apex/lib/liba.so"}, 142 usedBy{"highest.apex/lib/libb.so"}, 143 firstParty{}, 144 }, 145 expectedDeps: []string{ 146 "testdata/firstparty/FIRST_PARTY_LICENSE", 147 "testdata/firstparty/bin/bin1.meta_lic", 148 "testdata/firstparty/bin/bin2.meta_lic", 149 "testdata/firstparty/highest.apex.meta_lic", 150 "testdata/firstparty/lib/liba.so.meta_lic", 151 "testdata/firstparty/lib/libb.so.meta_lic", 152 "testdata/firstparty/lib/libc.a.meta_lic", 153 "testdata/firstparty/lib/libd.so.meta_lic", 154 }, 155 }, 156 { 157 condition: "firstparty", 158 name: "apex-with-title+toc", 159 roots: []string{"highest.apex.meta_lic"}, 160 includeTOC: true, 161 title: "Emperor", 162 expectedOut: []matcher{ 163 pageTitle{"Emperor"}, 164 toc{}, 165 target{"highest.apex"}, 166 uses{"Android"}, 167 target{"highest.apex/bin/bin1"}, 168 uses{"Android"}, 169 target{"highest.apex/bin/bin2"}, 170 uses{"Android"}, 171 target{"highest.apex/lib/liba.so"}, 172 uses{"Android"}, 173 target{"highest.apex/lib/libb.so"}, 174 uses{"Android"}, 175 hr{}, 176 library{"Android"}, 177 usedBy{"highest.apex"}, 178 usedBy{"highest.apex/bin/bin1"}, 179 usedBy{"highest.apex/bin/bin2"}, 180 usedBy{"highest.apex/lib/liba.so"}, 181 usedBy{"highest.apex/lib/libb.so"}, 182 firstParty{}, 183 }, 184 expectedDeps: []string{ 185 "testdata/firstparty/FIRST_PARTY_LICENSE", 186 "testdata/firstparty/bin/bin1.meta_lic", 187 "testdata/firstparty/bin/bin2.meta_lic", 188 "testdata/firstparty/highest.apex.meta_lic", 189 "testdata/firstparty/lib/liba.so.meta_lic", 190 "testdata/firstparty/lib/libb.so.meta_lic", 191 "testdata/firstparty/lib/libc.a.meta_lic", 192 "testdata/firstparty/lib/libd.so.meta_lic", 193 }, 194 }, 195 { 196 condition: "firstparty", 197 name: "container", 198 roots: []string{"container.zip.meta_lic"}, 199 expectedOut: []matcher{ 200 hr{}, 201 library{"Android"}, 202 usedBy{"container.zip"}, 203 usedBy{"container.zip/bin1"}, 204 usedBy{"container.zip/bin2"}, 205 usedBy{"container.zip/liba.so"}, 206 usedBy{"container.zip/libb.so"}, 207 firstParty{}, 208 }, 209 expectedDeps: []string{ 210 "testdata/firstparty/FIRST_PARTY_LICENSE", 211 "testdata/firstparty/bin/bin1.meta_lic", 212 "testdata/firstparty/bin/bin2.meta_lic", 213 "testdata/firstparty/container.zip.meta_lic", 214 "testdata/firstparty/lib/liba.so.meta_lic", 215 "testdata/firstparty/lib/libb.so.meta_lic", 216 "testdata/firstparty/lib/libc.a.meta_lic", 217 "testdata/firstparty/lib/libd.so.meta_lic", 218 }, 219 }, 220 { 221 condition: "firstparty", 222 name: "application", 223 roots: []string{"application.meta_lic"}, 224 expectedOut: []matcher{ 225 hr{}, 226 library{"Android"}, 227 usedBy{"application"}, 228 firstParty{}, 229 }, 230 expectedDeps: []string{ 231 "testdata/firstparty/FIRST_PARTY_LICENSE", 232 "testdata/firstparty/application.meta_lic", 233 "testdata/firstparty/bin/bin3.meta_lic", 234 "testdata/firstparty/lib/liba.so.meta_lic", 235 "testdata/firstparty/lib/libb.so.meta_lic", 236 }, 237 }, 238 { 239 condition: "firstparty", 240 name: "binary", 241 roots: []string{"bin/bin1.meta_lic"}, 242 expectedOut: []matcher{ 243 hr{}, 244 library{"Android"}, 245 usedBy{"bin/bin1"}, 246 firstParty{}, 247 }, 248 expectedDeps: []string{ 249 "testdata/firstparty/FIRST_PARTY_LICENSE", 250 "testdata/firstparty/bin/bin1.meta_lic", 251 "testdata/firstparty/lib/liba.so.meta_lic", 252 "testdata/firstparty/lib/libc.a.meta_lic", 253 }, 254 }, 255 { 256 condition: "firstparty", 257 name: "library", 258 roots: []string{"lib/libd.so.meta_lic"}, 259 expectedOut: []matcher{ 260 hr{}, 261 library{"Android"}, 262 usedBy{"lib/libd.so"}, 263 firstParty{}, 264 }, 265 expectedDeps: []string{ 266 "testdata/firstparty/FIRST_PARTY_LICENSE", 267 "testdata/firstparty/lib/libd.so.meta_lic", 268 }, 269 }, 270 { 271 condition: "notice", 272 name: "apex", 273 roots: []string{"highest.apex.meta_lic"}, 274 expectedOut: []matcher{ 275 hr{}, 276 library{"Android"}, 277 usedBy{"highest.apex"}, 278 usedBy{"highest.apex/bin/bin1"}, 279 usedBy{"highest.apex/bin/bin2"}, 280 usedBy{"highest.apex/lib/libb.so"}, 281 firstParty{}, 282 hr{}, 283 library{"Device"}, 284 usedBy{"highest.apex/bin/bin1"}, 285 usedBy{"highest.apex/lib/liba.so"}, 286 library{"External"}, 287 usedBy{"highest.apex/bin/bin1"}, 288 notice{}, 289 }, 290 expectedDeps: []string{ 291 "testdata/firstparty/FIRST_PARTY_LICENSE", 292 "testdata/notice/NOTICE_LICENSE", 293 "testdata/notice/bin/bin1.meta_lic", 294 "testdata/notice/bin/bin2.meta_lic", 295 "testdata/notice/highest.apex.meta_lic", 296 "testdata/notice/lib/liba.so.meta_lic", 297 "testdata/notice/lib/libb.so.meta_lic", 298 "testdata/notice/lib/libc.a.meta_lic", 299 "testdata/notice/lib/libd.so.meta_lic", 300 }, 301 }, 302 { 303 condition: "notice", 304 name: "container", 305 roots: []string{"container.zip.meta_lic"}, 306 expectedOut: []matcher{ 307 hr{}, 308 library{"Android"}, 309 usedBy{"container.zip"}, 310 usedBy{"container.zip/bin1"}, 311 usedBy{"container.zip/bin2"}, 312 usedBy{"container.zip/libb.so"}, 313 firstParty{}, 314 hr{}, 315 library{"Device"}, 316 usedBy{"container.zip/bin1"}, 317 usedBy{"container.zip/liba.so"}, 318 library{"External"}, 319 usedBy{"container.zip/bin1"}, 320 notice{}, 321 }, 322 expectedDeps: []string{ 323 "testdata/firstparty/FIRST_PARTY_LICENSE", 324 "testdata/notice/NOTICE_LICENSE", 325 "testdata/notice/bin/bin1.meta_lic", 326 "testdata/notice/bin/bin2.meta_lic", 327 "testdata/notice/container.zip.meta_lic", 328 "testdata/notice/lib/liba.so.meta_lic", 329 "testdata/notice/lib/libb.so.meta_lic", 330 "testdata/notice/lib/libc.a.meta_lic", 331 "testdata/notice/lib/libd.so.meta_lic", 332 }, 333 }, 334 { 335 condition: "notice", 336 name: "application", 337 roots: []string{"application.meta_lic"}, 338 expectedOut: []matcher{ 339 hr{}, 340 library{"Android"}, 341 usedBy{"application"}, 342 firstParty{}, 343 hr{}, 344 library{"Device"}, 345 usedBy{"application"}, 346 notice{}, 347 }, 348 expectedDeps: []string{ 349 "testdata/firstparty/FIRST_PARTY_LICENSE", 350 "testdata/notice/NOTICE_LICENSE", 351 "testdata/notice/application.meta_lic", 352 "testdata/notice/bin/bin3.meta_lic", 353 "testdata/notice/lib/liba.so.meta_lic", 354 "testdata/notice/lib/libb.so.meta_lic", 355 }, 356 }, 357 { 358 condition: "notice", 359 name: "binary", 360 roots: []string{"bin/bin1.meta_lic"}, 361 expectedOut: []matcher{ 362 hr{}, 363 library{"Android"}, 364 usedBy{"bin/bin1"}, 365 firstParty{}, 366 hr{}, 367 library{"Device"}, 368 usedBy{"bin/bin1"}, 369 library{"External"}, 370 usedBy{"bin/bin1"}, 371 notice{}, 372 }, 373 expectedDeps: []string{ 374 "testdata/firstparty/FIRST_PARTY_LICENSE", 375 "testdata/notice/NOTICE_LICENSE", 376 "testdata/notice/bin/bin1.meta_lic", 377 "testdata/notice/lib/liba.so.meta_lic", 378 "testdata/notice/lib/libc.a.meta_lic", 379 }, 380 }, 381 { 382 condition: "notice", 383 name: "library", 384 roots: []string{"lib/libd.so.meta_lic"}, 385 expectedOut: []matcher{ 386 hr{}, 387 library{"External"}, 388 usedBy{"lib/libd.so"}, 389 notice{}, 390 }, 391 expectedDeps: []string{ 392 "testdata/notice/NOTICE_LICENSE", 393 "testdata/notice/lib/libd.so.meta_lic", 394 }, 395 }, 396 { 397 condition: "reciprocal", 398 name: "apex", 399 roots: []string{"highest.apex.meta_lic"}, 400 expectedOut: []matcher{ 401 hr{}, 402 library{"Android"}, 403 usedBy{"highest.apex"}, 404 usedBy{"highest.apex/bin/bin1"}, 405 usedBy{"highest.apex/bin/bin2"}, 406 usedBy{"highest.apex/lib/libb.so"}, 407 firstParty{}, 408 hr{}, 409 library{"Device"}, 410 usedBy{"highest.apex/bin/bin1"}, 411 usedBy{"highest.apex/lib/liba.so"}, 412 library{"External"}, 413 usedBy{"highest.apex/bin/bin1"}, 414 reciprocal{}, 415 }, 416 expectedDeps: []string{ 417 "testdata/firstparty/FIRST_PARTY_LICENSE", 418 "testdata/reciprocal/RECIPROCAL_LICENSE", 419 "testdata/reciprocal/bin/bin1.meta_lic", 420 "testdata/reciprocal/bin/bin2.meta_lic", 421 "testdata/reciprocal/highest.apex.meta_lic", 422 "testdata/reciprocal/lib/liba.so.meta_lic", 423 "testdata/reciprocal/lib/libb.so.meta_lic", 424 "testdata/reciprocal/lib/libc.a.meta_lic", 425 "testdata/reciprocal/lib/libd.so.meta_lic", 426 }, 427 }, 428 { 429 condition: "reciprocal", 430 name: "container", 431 roots: []string{"container.zip.meta_lic"}, 432 expectedOut: []matcher{ 433 hr{}, 434 library{"Android"}, 435 usedBy{"container.zip"}, 436 usedBy{"container.zip/bin1"}, 437 usedBy{"container.zip/bin2"}, 438 usedBy{"container.zip/libb.so"}, 439 firstParty{}, 440 hr{}, 441 library{"Device"}, 442 usedBy{"container.zip/bin1"}, 443 usedBy{"container.zip/liba.so"}, 444 library{"External"}, 445 usedBy{"container.zip/bin1"}, 446 reciprocal{}, 447 }, 448 expectedDeps: []string{ 449 "testdata/firstparty/FIRST_PARTY_LICENSE", 450 "testdata/reciprocal/RECIPROCAL_LICENSE", 451 "testdata/reciprocal/bin/bin1.meta_lic", 452 "testdata/reciprocal/bin/bin2.meta_lic", 453 "testdata/reciprocal/container.zip.meta_lic", 454 "testdata/reciprocal/lib/liba.so.meta_lic", 455 "testdata/reciprocal/lib/libb.so.meta_lic", 456 "testdata/reciprocal/lib/libc.a.meta_lic", 457 "testdata/reciprocal/lib/libd.so.meta_lic", 458 }, 459 }, 460 { 461 condition: "reciprocal", 462 name: "application", 463 roots: []string{"application.meta_lic"}, 464 expectedOut: []matcher{ 465 hr{}, 466 library{"Android"}, 467 usedBy{"application"}, 468 firstParty{}, 469 hr{}, 470 library{"Device"}, 471 usedBy{"application"}, 472 reciprocal{}, 473 }, 474 expectedDeps: []string{ 475 "testdata/firstparty/FIRST_PARTY_LICENSE", 476 "testdata/reciprocal/RECIPROCAL_LICENSE", 477 "testdata/reciprocal/application.meta_lic", 478 "testdata/reciprocal/bin/bin3.meta_lic", 479 "testdata/reciprocal/lib/liba.so.meta_lic", 480 "testdata/reciprocal/lib/libb.so.meta_lic", 481 }, 482 }, 483 { 484 condition: "reciprocal", 485 name: "binary", 486 roots: []string{"bin/bin1.meta_lic"}, 487 expectedOut: []matcher{ 488 hr{}, 489 library{"Android"}, 490 usedBy{"bin/bin1"}, 491 firstParty{}, 492 hr{}, 493 library{"Device"}, 494 usedBy{"bin/bin1"}, 495 library{"External"}, 496 usedBy{"bin/bin1"}, 497 reciprocal{}, 498 }, 499 expectedDeps: []string{ 500 "testdata/firstparty/FIRST_PARTY_LICENSE", 501 "testdata/reciprocal/RECIPROCAL_LICENSE", 502 "testdata/reciprocal/bin/bin1.meta_lic", 503 "testdata/reciprocal/lib/liba.so.meta_lic", 504 "testdata/reciprocal/lib/libc.a.meta_lic", 505 }, 506 }, 507 { 508 condition: "reciprocal", 509 name: "library", 510 roots: []string{"lib/libd.so.meta_lic"}, 511 expectedOut: []matcher{ 512 hr{}, 513 library{"External"}, 514 usedBy{"lib/libd.so"}, 515 notice{}, 516 }, 517 expectedDeps: []string{ 518 "testdata/notice/NOTICE_LICENSE", 519 "testdata/reciprocal/lib/libd.so.meta_lic", 520 }, 521 }, 522 { 523 condition: "restricted", 524 name: "apex", 525 roots: []string{"highest.apex.meta_lic"}, 526 expectedOut: []matcher{ 527 hr{}, 528 library{"Android"}, 529 usedBy{"highest.apex"}, 530 usedBy{"highest.apex/bin/bin1"}, 531 usedBy{"highest.apex/bin/bin2"}, 532 firstParty{}, 533 hr{}, 534 library{"Android"}, 535 usedBy{"highest.apex/bin/bin2"}, 536 usedBy{"highest.apex/lib/libb.so"}, 537 library{"Device"}, 538 usedBy{"highest.apex/bin/bin1"}, 539 usedBy{"highest.apex/lib/liba.so"}, 540 restricted{}, 541 hr{}, 542 library{"External"}, 543 usedBy{"highest.apex/bin/bin1"}, 544 reciprocal{}, 545 }, 546 expectedDeps: []string{ 547 "testdata/firstparty/FIRST_PARTY_LICENSE", 548 "testdata/reciprocal/RECIPROCAL_LICENSE", 549 "testdata/restricted/RESTRICTED_LICENSE", 550 "testdata/restricted/bin/bin1.meta_lic", 551 "testdata/restricted/bin/bin2.meta_lic", 552 "testdata/restricted/highest.apex.meta_lic", 553 "testdata/restricted/lib/liba.so.meta_lic", 554 "testdata/restricted/lib/libb.so.meta_lic", 555 "testdata/restricted/lib/libc.a.meta_lic", 556 "testdata/restricted/lib/libd.so.meta_lic", 557 }, 558 }, 559 { 560 condition: "restricted", 561 name: "container", 562 roots: []string{"container.zip.meta_lic"}, 563 expectedOut: []matcher{ 564 hr{}, 565 library{"Android"}, 566 usedBy{"container.zip"}, 567 usedBy{"container.zip/bin1"}, 568 usedBy{"container.zip/bin2"}, 569 firstParty{}, 570 hr{}, 571 library{"Android"}, 572 usedBy{"container.zip/bin2"}, 573 usedBy{"container.zip/libb.so"}, 574 library{"Device"}, 575 usedBy{"container.zip/bin1"}, 576 usedBy{"container.zip/liba.so"}, 577 restricted{}, 578 hr{}, 579 library{"External"}, 580 usedBy{"container.zip/bin1"}, 581 reciprocal{}, 582 }, 583 expectedDeps: []string{ 584 "testdata/firstparty/FIRST_PARTY_LICENSE", 585 "testdata/reciprocal/RECIPROCAL_LICENSE", 586 "testdata/restricted/RESTRICTED_LICENSE", 587 "testdata/restricted/bin/bin1.meta_lic", 588 "testdata/restricted/bin/bin2.meta_lic", 589 "testdata/restricted/container.zip.meta_lic", 590 "testdata/restricted/lib/liba.so.meta_lic", 591 "testdata/restricted/lib/libb.so.meta_lic", 592 "testdata/restricted/lib/libc.a.meta_lic", 593 "testdata/restricted/lib/libd.so.meta_lic", 594 }, 595 }, 596 { 597 condition: "restricted", 598 name: "application", 599 roots: []string{"application.meta_lic"}, 600 expectedOut: []matcher{ 601 hr{}, 602 library{"Android"}, 603 usedBy{"application"}, 604 firstParty{}, 605 hr{}, 606 library{"Device"}, 607 usedBy{"application"}, 608 restricted{}, 609 }, 610 expectedDeps: []string{ 611 "testdata/firstparty/FIRST_PARTY_LICENSE", 612 "testdata/restricted/RESTRICTED_LICENSE", 613 "testdata/restricted/application.meta_lic", 614 "testdata/restricted/bin/bin3.meta_lic", 615 "testdata/restricted/lib/liba.so.meta_lic", 616 "testdata/restricted/lib/libb.so.meta_lic", 617 }, 618 }, 619 { 620 condition: "restricted", 621 name: "binary", 622 roots: []string{"bin/bin1.meta_lic"}, 623 expectedOut: []matcher{ 624 hr{}, 625 library{"Android"}, 626 usedBy{"bin/bin1"}, 627 firstParty{}, 628 hr{}, 629 library{"Device"}, 630 usedBy{"bin/bin1"}, 631 restricted{}, 632 hr{}, 633 library{"External"}, 634 usedBy{"bin/bin1"}, 635 reciprocal{}, 636 }, 637 expectedDeps: []string{ 638 "testdata/firstparty/FIRST_PARTY_LICENSE", 639 "testdata/reciprocal/RECIPROCAL_LICENSE", 640 "testdata/restricted/RESTRICTED_LICENSE", 641 "testdata/restricted/bin/bin1.meta_lic", 642 "testdata/restricted/lib/liba.so.meta_lic", 643 "testdata/restricted/lib/libc.a.meta_lic", 644 }, 645 }, 646 { 647 condition: "restricted", 648 name: "library", 649 roots: []string{"lib/libd.so.meta_lic"}, 650 expectedOut: []matcher{ 651 hr{}, 652 library{"External"}, 653 usedBy{"lib/libd.so"}, 654 notice{}, 655 }, 656 expectedDeps: []string{ 657 "testdata/notice/NOTICE_LICENSE", 658 "testdata/restricted/lib/libd.so.meta_lic", 659 }, 660 }, 661 { 662 condition: "proprietary", 663 name: "apex", 664 roots: []string{"highest.apex.meta_lic"}, 665 expectedOut: []matcher{ 666 hr{}, 667 library{"Android"}, 668 usedBy{"highest.apex/bin/bin2"}, 669 usedBy{"highest.apex/lib/libb.so"}, 670 restricted{}, 671 hr{}, 672 library{"Android"}, 673 usedBy{"highest.apex"}, 674 usedBy{"highest.apex/bin/bin1"}, 675 firstParty{}, 676 hr{}, 677 library{"Android"}, 678 usedBy{"highest.apex/bin/bin2"}, 679 library{"Device"}, 680 usedBy{"highest.apex/bin/bin1"}, 681 usedBy{"highest.apex/lib/liba.so"}, 682 library{"External"}, 683 usedBy{"highest.apex/bin/bin1"}, 684 proprietary{}, 685 }, 686 expectedDeps: []string{ 687 "testdata/firstparty/FIRST_PARTY_LICENSE", 688 "testdata/proprietary/PROPRIETARY_LICENSE", 689 "testdata/proprietary/bin/bin1.meta_lic", 690 "testdata/proprietary/bin/bin2.meta_lic", 691 "testdata/proprietary/highest.apex.meta_lic", 692 "testdata/proprietary/lib/liba.so.meta_lic", 693 "testdata/proprietary/lib/libb.so.meta_lic", 694 "testdata/proprietary/lib/libc.a.meta_lic", 695 "testdata/proprietary/lib/libd.so.meta_lic", 696 "testdata/restricted/RESTRICTED_LICENSE", 697 }, 698 }, 699 { 700 condition: "proprietary", 701 name: "container", 702 roots: []string{"container.zip.meta_lic"}, 703 expectedOut: []matcher{ 704 hr{}, 705 library{"Android"}, 706 usedBy{"container.zip/bin2"}, 707 usedBy{"container.zip/libb.so"}, 708 restricted{}, 709 hr{}, 710 library{"Android"}, 711 usedBy{"container.zip"}, 712 usedBy{"container.zip/bin1"}, 713 firstParty{}, 714 hr{}, 715 library{"Android"}, 716 usedBy{"container.zip/bin2"}, 717 library{"Device"}, 718 usedBy{"container.zip/bin1"}, 719 usedBy{"container.zip/liba.so"}, 720 library{"External"}, 721 usedBy{"container.zip/bin1"}, 722 proprietary{}, 723 }, 724 expectedDeps: []string{ 725 "testdata/firstparty/FIRST_PARTY_LICENSE", 726 "testdata/proprietary/PROPRIETARY_LICENSE", 727 "testdata/proprietary/bin/bin1.meta_lic", 728 "testdata/proprietary/bin/bin2.meta_lic", 729 "testdata/proprietary/container.zip.meta_lic", 730 "testdata/proprietary/lib/liba.so.meta_lic", 731 "testdata/proprietary/lib/libb.so.meta_lic", 732 "testdata/proprietary/lib/libc.a.meta_lic", 733 "testdata/proprietary/lib/libd.so.meta_lic", 734 "testdata/restricted/RESTRICTED_LICENSE", 735 }, 736 }, 737 { 738 condition: "proprietary", 739 name: "application", 740 roots: []string{"application.meta_lic"}, 741 expectedOut: []matcher{ 742 hr{}, 743 library{"Android"}, 744 usedBy{"application"}, 745 firstParty{}, 746 hr{}, 747 library{"Device"}, 748 usedBy{"application"}, 749 proprietary{}, 750 }, 751 expectedDeps: []string{ 752 "testdata/firstparty/FIRST_PARTY_LICENSE", 753 "testdata/proprietary/PROPRIETARY_LICENSE", 754 "testdata/proprietary/application.meta_lic", 755 "testdata/proprietary/bin/bin3.meta_lic", 756 "testdata/proprietary/lib/liba.so.meta_lic", 757 "testdata/proprietary/lib/libb.so.meta_lic", 758 }, 759 }, 760 { 761 condition: "proprietary", 762 name: "binary", 763 roots: []string{"bin/bin1.meta_lic"}, 764 expectedOut: []matcher{ 765 hr{}, 766 library{"Android"}, 767 usedBy{"bin/bin1"}, 768 firstParty{}, 769 hr{}, 770 library{"Device"}, 771 usedBy{"bin/bin1"}, 772 library{"External"}, 773 usedBy{"bin/bin1"}, 774 proprietary{}, 775 }, 776 expectedDeps: []string{ 777 "testdata/firstparty/FIRST_PARTY_LICENSE", 778 "testdata/proprietary/PROPRIETARY_LICENSE", 779 "testdata/proprietary/bin/bin1.meta_lic", 780 "testdata/proprietary/lib/liba.so.meta_lic", 781 "testdata/proprietary/lib/libc.a.meta_lic", 782 }, 783 }, 784 { 785 condition: "proprietary", 786 name: "library", 787 roots: []string{"lib/libd.so.meta_lic"}, 788 expectedOut: []matcher{ 789 hr{}, 790 library{"External"}, 791 usedBy{"lib/libd.so"}, 792 notice{}, 793 }, 794 expectedDeps: []string{ 795 "testdata/notice/NOTICE_LICENSE", 796 "testdata/proprietary/lib/libd.so.meta_lic", 797 }, 798 }, 799 } 800 for _, tt := range tests { 801 t.Run(tt.condition+" "+tt.name, func(t *testing.T) { 802 stdout := &bytes.Buffer{} 803 stderr := &bytes.Buffer{} 804 805 rootFiles := make([]string, 0, len(tt.roots)) 806 for _, r := range tt.roots { 807 rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r) 808 } 809 810 var deps []string 811 812 ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), tt.includeTOC, "", []string{tt.stripPrefix}, tt.title, &deps} 813 814 err := htmlNotice(&ctx, rootFiles...) 815 if err != nil { 816 t.Fatalf("htmlnotice: error = %v, stderr = %v", err, stderr) 817 return 818 } 819 if stderr.Len() > 0 { 820 t.Errorf("htmlnotice: gotStderr = %v, want none", stderr) 821 } 822 823 t.Logf("got stdout: %s", stdout.String()) 824 825 t.Logf("want stdout: %s", matcherList(tt.expectedOut).String()) 826 827 out := bufio.NewScanner(stdout) 828 lineno := 0 829 inBody := false 830 hasTitle := false 831 ttle, expectTitle := tt.expectedOut[0].(pageTitle) 832 for out.Scan() { 833 line := out.Text() 834 if strings.TrimLeft(line, " ") == "" { 835 continue 836 } 837 if !inBody { 838 if expectTitle { 839 if tl := checkTitle(line); len(tl) > 0 { 840 if tl != ttle.t { 841 t.Errorf("htmlnotice: unexpected title: got %q, want %q", tl, ttle.t) 842 } 843 hasTitle = true 844 } 845 } 846 if bodyTag.MatchString(line) { 847 inBody = true 848 if expectTitle && !hasTitle { 849 t.Errorf("htmlnotice: missing title: got no <title> tag, want <title>%s</title>", ttle.t) 850 } 851 } 852 continue 853 } 854 if boilerPlate.MatchString(line) { 855 continue 856 } 857 if len(tt.expectedOut) <= lineno { 858 t.Errorf("htmlnotice: unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut)) 859 } else if !tt.expectedOut[lineno].isMatch(line) { 860 t.Errorf("htmlnotice: unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno].String()) 861 } 862 lineno++ 863 } 864 if !inBody { 865 t.Errorf("htmlnotice: missing body: got no <body> tag, want <body> tag followed by %s", matcherList(tt.expectedOut).String()) 866 return 867 } 868 for ; lineno < len(tt.expectedOut); lineno++ { 869 t.Errorf("htmlnotice: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno].String()) 870 } 871 872 t.Logf("got deps: %q", deps) 873 874 t.Logf("want deps: %q", tt.expectedDeps) 875 876 if g, w := deps, tt.expectedDeps; !reflect.DeepEqual(g, w) { 877 t.Errorf("unexpected deps, wanted:\n%s\ngot:\n%s\n", 878 strings.Join(w, "\n"), strings.Join(g, "\n")) 879 } 880 }) 881 } 882} 883 884func checkTitle(line string) string { 885 groups := titleTag.FindStringSubmatch(line) 886 if len(groups) != 2 { 887 return "" 888 } 889 return groups[1] 890} 891 892type matcher interface { 893 isMatch(line string) bool 894 String() string 895} 896 897type pageTitle struct { 898 t string 899} 900 901func (m pageTitle) isMatch(line string) bool { 902 groups := h1Tag.FindStringSubmatch(line) 903 if len(groups) != 2 { 904 return false 905 } 906 return groups[1] == html.EscapeString(m.t) 907} 908 909func (m pageTitle) String() string { 910 return " <h1>" + html.EscapeString(m.t) + "</h1>" 911} 912 913type toc struct{} 914 915func (m toc) isMatch(line string) bool { 916 return tocTag.MatchString(line) 917} 918 919func (m toc) String() string { 920 return ` <ul class="toc">` 921} 922 923type target struct { 924 name string 925} 926 927func (m target) isMatch(line string) bool { 928 groups := installTarget.FindStringSubmatch(line) 929 if len(groups) != 2 { 930 return false 931 } 932 return strings.HasPrefix(groups[1], "out/") && strings.HasSuffix(groups[1], "/"+html.EscapeString(m.name)) 933} 934 935func (m target) String() string { 936 return ` <li id="id#"><strong>` + html.EscapeString(m.name) + `</strong>` 937} 938 939type uses struct { 940 name string 941} 942 943func (m uses) isMatch(line string) bool { 944 groups := libReference.FindStringSubmatch(line) 945 if len(groups) != 2 { 946 return false 947 } 948 return groups[1] == html.EscapeString(m.name) 949} 950 951func (m uses) String() string { 952 return ` <li><a href="#hash">` + html.EscapeString(m.name) + `</a>` 953} 954 955type hr struct{} 956 957func (m hr) isMatch(line string) bool { 958 return horizontalRule.MatchString(line) 959} 960 961func (m hr) String() string { 962 return " <hr>" 963} 964 965type library struct { 966 name string 967} 968 969func (m library) isMatch(line string) bool { 970 groups := libraryName.FindStringSubmatch(line) 971 if len(groups) != 2 { 972 return false 973 } 974 return groups[1] == html.EscapeString(m.name) 975} 976 977func (m library) String() string { 978 return " <strong>" + html.EscapeString(m.name) + "</strong> used by:" 979} 980 981type usedBy struct { 982 name string 983} 984 985func (m usedBy) isMatch(line string) bool { 986 groups := usedByTarget.FindStringSubmatch(line) 987 if len(groups) != 2 { 988 return false 989 } 990 return strings.HasPrefix(groups[1], "out/") && strings.HasSuffix(groups[1], "/"+html.EscapeString(m.name)) 991} 992 993func (m usedBy) String() string { 994 return " <li>out/.../" + html.EscapeString(m.name) 995} 996 997func matchesText(line, text string) bool { 998 groups := licenseText.FindStringSubmatch(line) 999 if len(groups) != 2 { 1000 return false 1001 } 1002 return groups[1] == html.EscapeString(text) 1003} 1004 1005func expectedText(text string) string { 1006 return ` <a href="#hash"/><pre class="license-text">` + html.EscapeString(text) 1007} 1008 1009type firstParty struct{} 1010 1011func (m firstParty) isMatch(line string) bool { 1012 return matchesText(line, "&&&First Party License&&&") 1013} 1014 1015func (m firstParty) String() string { 1016 return expectedText("&&&First Party License&&&") 1017} 1018 1019type notice struct{} 1020 1021func (m notice) isMatch(line string) bool { 1022 return matchesText(line, "%%%Notice License%%%") 1023} 1024 1025func (m notice) String() string { 1026 return expectedText("%%%Notice License%%%") 1027} 1028 1029type reciprocal struct{} 1030 1031func (m reciprocal) isMatch(line string) bool { 1032 return matchesText(line, "$$$Reciprocal License$$$") 1033} 1034 1035func (m reciprocal) String() string { 1036 return expectedText("$$$Reciprocal License$$$") 1037} 1038 1039type restricted struct{} 1040 1041func (m restricted) isMatch(line string) bool { 1042 return matchesText(line, "###Restricted License###") 1043} 1044 1045func (m restricted) String() string { 1046 return expectedText("###Restricted License###") 1047} 1048 1049type proprietary struct{} 1050 1051func (m proprietary) isMatch(line string) bool { 1052 return matchesText(line, "@@@Proprietary License@@@") 1053} 1054 1055func (m proprietary) String() string { 1056 return expectedText("@@@Proprietary License@@@") 1057} 1058 1059type matcherList []matcher 1060 1061func (l matcherList) String() string { 1062 var sb strings.Builder 1063 for _, m := range l { 1064 s := m.String() 1065 if s[:3] == s[len(s)-3:] { 1066 fmt.Fprintln(&sb) 1067 } 1068 fmt.Fprintf(&sb, "%s\n", s) 1069 if s[:3] == s[len(s)-3:] { 1070 fmt.Fprintln(&sb) 1071 } 1072 } 1073 return sb.String() 1074} 1075