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 "os" 22 "reflect" 23 "regexp" 24 "strings" 25 "testing" 26 27 "android/soong/tools/compliance" 28) 29 30var ( 31 horizontalRule = regexp.MustCompile("^===[=]*===$") 32) 33 34func TestMain(m *testing.M) { 35 // Change into the parent directory before running the tests 36 // so they can find the testdata directory. 37 if err := os.Chdir(".."); err != nil { 38 fmt.Printf("failed to change to testdata directory: %s\n", err) 39 os.Exit(1) 40 } 41 os.Exit(m.Run()) 42} 43 44func Test(t *testing.T) { 45 tests := []struct { 46 condition string 47 name string 48 outDir string 49 roots []string 50 stripPrefix string 51 expectedOut []matcher 52 expectedDeps []string 53 }{ 54 { 55 condition: "firstparty", 56 name: "apex", 57 roots: []string{"highest.apex.meta_lic"}, 58 expectedOut: []matcher{ 59 hr{}, 60 library{"Android"}, 61 usedBy{"highest.apex"}, 62 usedBy{"highest.apex/bin/bin1"}, 63 usedBy{"highest.apex/bin/bin2"}, 64 usedBy{"highest.apex/lib/liba.so"}, 65 usedBy{"highest.apex/lib/libb.so"}, 66 firstParty{}, 67 }, 68 expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"}, 69 }, 70 { 71 condition: "firstparty", 72 name: "container", 73 roots: []string{"container.zip.meta_lic"}, 74 expectedOut: []matcher{ 75 hr{}, 76 library{"Android"}, 77 usedBy{"container.zip"}, 78 usedBy{"container.zip/bin1"}, 79 usedBy{"container.zip/bin2"}, 80 usedBy{"container.zip/liba.so"}, 81 usedBy{"container.zip/libb.so"}, 82 firstParty{}, 83 }, 84 expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"}, 85 }, 86 { 87 condition: "firstparty", 88 name: "application", 89 roots: []string{"application.meta_lic"}, 90 expectedOut: []matcher{ 91 hr{}, 92 library{"Android"}, 93 usedBy{"application"}, 94 firstParty{}, 95 }, 96 expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"}, 97 }, 98 { 99 condition: "firstparty", 100 name: "binary", 101 roots: []string{"bin/bin1.meta_lic"}, 102 expectedOut: []matcher{ 103 hr{}, 104 library{"Android"}, 105 usedBy{"bin/bin1"}, 106 firstParty{}, 107 }, 108 expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"}, 109 }, 110 { 111 condition: "firstparty", 112 name: "library", 113 roots: []string{"lib/libd.so.meta_lic"}, 114 expectedOut: []matcher{ 115 hr{}, 116 library{"Android"}, 117 usedBy{"lib/libd.so"}, 118 firstParty{}, 119 }, 120 expectedDeps: []string{"testdata/firstparty/FIRST_PARTY_LICENSE"}, 121 }, 122 { 123 condition: "notice", 124 name: "apex", 125 roots: []string{"highest.apex.meta_lic"}, 126 expectedOut: []matcher{ 127 hr{}, 128 library{"Android"}, 129 usedBy{"highest.apex"}, 130 usedBy{"highest.apex/bin/bin1"}, 131 usedBy{"highest.apex/bin/bin2"}, 132 usedBy{"highest.apex/lib/libb.so"}, 133 firstParty{}, 134 hr{}, 135 library{"Device"}, 136 usedBy{"highest.apex/bin/bin1"}, 137 usedBy{"highest.apex/lib/liba.so"}, 138 library{"External"}, 139 usedBy{"highest.apex/bin/bin1"}, 140 notice{}, 141 }, 142 expectedDeps: []string{ 143 "testdata/firstparty/FIRST_PARTY_LICENSE", 144 "testdata/notice/NOTICE_LICENSE", 145 }, 146 }, 147 { 148 condition: "notice", 149 name: "container", 150 roots: []string{"container.zip.meta_lic"}, 151 expectedOut: []matcher{ 152 hr{}, 153 library{"Android"}, 154 usedBy{"container.zip"}, 155 usedBy{"container.zip/bin1"}, 156 usedBy{"container.zip/bin2"}, 157 usedBy{"container.zip/libb.so"}, 158 firstParty{}, 159 hr{}, 160 library{"Device"}, 161 usedBy{"container.zip/bin1"}, 162 usedBy{"container.zip/liba.so"}, 163 library{"External"}, 164 usedBy{"container.zip/bin1"}, 165 notice{}, 166 }, 167 expectedDeps: []string{ 168 "testdata/firstparty/FIRST_PARTY_LICENSE", 169 "testdata/notice/NOTICE_LICENSE", 170 }, 171 }, 172 { 173 condition: "notice", 174 name: "application", 175 roots: []string{"application.meta_lic"}, 176 expectedOut: []matcher{ 177 hr{}, 178 library{"Android"}, 179 usedBy{"application"}, 180 firstParty{}, 181 hr{}, 182 library{"Device"}, 183 usedBy{"application"}, 184 notice{}, 185 }, 186 expectedDeps: []string{ 187 "testdata/firstparty/FIRST_PARTY_LICENSE", 188 "testdata/notice/NOTICE_LICENSE", 189 }, 190 }, 191 { 192 condition: "notice", 193 name: "binary", 194 roots: []string{"bin/bin1.meta_lic"}, 195 expectedOut: []matcher{ 196 hr{}, 197 library{"Android"}, 198 usedBy{"bin/bin1"}, 199 firstParty{}, 200 hr{}, 201 library{"Device"}, 202 usedBy{"bin/bin1"}, 203 library{"External"}, 204 usedBy{"bin/bin1"}, 205 notice{}, 206 }, 207 expectedDeps: []string{ 208 "testdata/firstparty/FIRST_PARTY_LICENSE", 209 "testdata/notice/NOTICE_LICENSE", 210 }, 211 }, 212 { 213 condition: "notice", 214 name: "library", 215 roots: []string{"lib/libd.so.meta_lic"}, 216 expectedOut: []matcher{ 217 hr{}, 218 library{"External"}, 219 usedBy{"lib/libd.so"}, 220 notice{}, 221 }, 222 expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"}, 223 }, 224 { 225 condition: "reciprocal", 226 name: "apex", 227 roots: []string{"highest.apex.meta_lic"}, 228 expectedOut: []matcher{ 229 hr{}, 230 library{"Android"}, 231 usedBy{"highest.apex"}, 232 usedBy{"highest.apex/bin/bin1"}, 233 usedBy{"highest.apex/bin/bin2"}, 234 usedBy{"highest.apex/lib/libb.so"}, 235 firstParty{}, 236 hr{}, 237 library{"Device"}, 238 usedBy{"highest.apex/bin/bin1"}, 239 usedBy{"highest.apex/lib/liba.so"}, 240 library{"External"}, 241 usedBy{"highest.apex/bin/bin1"}, 242 reciprocal{}, 243 }, 244 expectedDeps: []string{ 245 "testdata/firstparty/FIRST_PARTY_LICENSE", 246 "testdata/reciprocal/RECIPROCAL_LICENSE", 247 }, 248 }, 249 { 250 condition: "reciprocal", 251 name: "container", 252 roots: []string{"container.zip.meta_lic"}, 253 expectedOut: []matcher{ 254 hr{}, 255 library{"Android"}, 256 usedBy{"container.zip"}, 257 usedBy{"container.zip/bin1"}, 258 usedBy{"container.zip/bin2"}, 259 usedBy{"container.zip/libb.so"}, 260 firstParty{}, 261 hr{}, 262 library{"Device"}, 263 usedBy{"container.zip/bin1"}, 264 usedBy{"container.zip/liba.so"}, 265 library{"External"}, 266 usedBy{"container.zip/bin1"}, 267 reciprocal{}, 268 }, 269 expectedDeps: []string{ 270 "testdata/firstparty/FIRST_PARTY_LICENSE", 271 "testdata/reciprocal/RECIPROCAL_LICENSE", 272 }, 273 }, 274 { 275 condition: "reciprocal", 276 name: "application", 277 roots: []string{"application.meta_lic"}, 278 expectedOut: []matcher{ 279 hr{}, 280 library{"Android"}, 281 usedBy{"application"}, 282 firstParty{}, 283 hr{}, 284 library{"Device"}, 285 usedBy{"application"}, 286 reciprocal{}, 287 }, 288 expectedDeps: []string{ 289 "testdata/firstparty/FIRST_PARTY_LICENSE", 290 "testdata/reciprocal/RECIPROCAL_LICENSE", 291 }, 292 }, 293 { 294 condition: "reciprocal", 295 name: "binary", 296 roots: []string{"bin/bin1.meta_lic"}, 297 expectedOut: []matcher{ 298 hr{}, 299 library{"Android"}, 300 usedBy{"bin/bin1"}, 301 firstParty{}, 302 hr{}, 303 library{"Device"}, 304 usedBy{"bin/bin1"}, 305 library{"External"}, 306 usedBy{"bin/bin1"}, 307 reciprocal{}, 308 }, 309 expectedDeps: []string{ 310 "testdata/firstparty/FIRST_PARTY_LICENSE", 311 "testdata/reciprocal/RECIPROCAL_LICENSE", 312 }, 313 }, 314 { 315 condition: "reciprocal", 316 name: "library", 317 roots: []string{"lib/libd.so.meta_lic"}, 318 expectedOut: []matcher{ 319 hr{}, 320 library{"External"}, 321 usedBy{"lib/libd.so"}, 322 notice{}, 323 }, 324 expectedDeps: []string{ 325 "testdata/notice/NOTICE_LICENSE", 326 }, 327 }, 328 { 329 condition: "restricted", 330 name: "apex", 331 roots: []string{"highest.apex.meta_lic"}, 332 expectedOut: []matcher{ 333 hr{}, 334 library{"Android"}, 335 usedBy{"highest.apex"}, 336 usedBy{"highest.apex/bin/bin1"}, 337 usedBy{"highest.apex/bin/bin2"}, 338 firstParty{}, 339 hr{}, 340 library{"Android"}, 341 usedBy{"highest.apex/bin/bin2"}, 342 usedBy{"highest.apex/lib/libb.so"}, 343 library{"Device"}, 344 usedBy{"highest.apex/bin/bin1"}, 345 usedBy{"highest.apex/lib/liba.so"}, 346 restricted{}, 347 hr{}, 348 library{"External"}, 349 usedBy{"highest.apex/bin/bin1"}, 350 reciprocal{}, 351 }, 352 expectedDeps: []string{ 353 "testdata/firstparty/FIRST_PARTY_LICENSE", 354 "testdata/reciprocal/RECIPROCAL_LICENSE", 355 "testdata/restricted/RESTRICTED_LICENSE", 356 }, 357 }, 358 { 359 condition: "restricted", 360 name: "container", 361 roots: []string{"container.zip.meta_lic"}, 362 expectedOut: []matcher{ 363 hr{}, 364 library{"Android"}, 365 usedBy{"container.zip"}, 366 usedBy{"container.zip/bin1"}, 367 usedBy{"container.zip/bin2"}, 368 firstParty{}, 369 hr{}, 370 library{"Android"}, 371 usedBy{"container.zip/bin2"}, 372 usedBy{"container.zip/libb.so"}, 373 library{"Device"}, 374 usedBy{"container.zip/bin1"}, 375 usedBy{"container.zip/liba.so"}, 376 restricted{}, 377 hr{}, 378 library{"External"}, 379 usedBy{"container.zip/bin1"}, 380 reciprocal{}, 381 }, 382 expectedDeps: []string{ 383 "testdata/firstparty/FIRST_PARTY_LICENSE", 384 "testdata/reciprocal/RECIPROCAL_LICENSE", 385 "testdata/restricted/RESTRICTED_LICENSE", 386 }, 387 }, 388 { 389 condition: "restricted", 390 name: "application", 391 roots: []string{"application.meta_lic"}, 392 expectedOut: []matcher{ 393 hr{}, 394 library{"Android"}, 395 usedBy{"application"}, 396 firstParty{}, 397 hr{}, 398 library{"Device"}, 399 usedBy{"application"}, 400 restricted{}, 401 }, 402 expectedDeps: []string{ 403 "testdata/firstparty/FIRST_PARTY_LICENSE", 404 "testdata/restricted/RESTRICTED_LICENSE", 405 }, 406 }, 407 { 408 condition: "restricted", 409 name: "binary", 410 roots: []string{"bin/bin1.meta_lic"}, 411 expectedOut: []matcher{ 412 hr{}, 413 library{"Android"}, 414 usedBy{"bin/bin1"}, 415 firstParty{}, 416 hr{}, 417 library{"Device"}, 418 usedBy{"bin/bin1"}, 419 restricted{}, 420 hr{}, 421 library{"External"}, 422 usedBy{"bin/bin1"}, 423 reciprocal{}, 424 }, 425 expectedDeps: []string{ 426 "testdata/firstparty/FIRST_PARTY_LICENSE", 427 "testdata/reciprocal/RECIPROCAL_LICENSE", 428 "testdata/restricted/RESTRICTED_LICENSE", 429 }, 430 }, 431 { 432 condition: "restricted", 433 name: "library", 434 roots: []string{"lib/libd.so.meta_lic"}, 435 expectedOut: []matcher{ 436 hr{}, 437 library{"External"}, 438 usedBy{"lib/libd.so"}, 439 notice{}, 440 }, 441 expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"}, 442 }, 443 { 444 condition: "proprietary", 445 name: "apex", 446 roots: []string{"highest.apex.meta_lic"}, 447 expectedOut: []matcher{ 448 hr{}, 449 library{"Android"}, 450 usedBy{"highest.apex/bin/bin2"}, 451 usedBy{"highest.apex/lib/libb.so"}, 452 restricted{}, 453 hr{}, 454 library{"Android"}, 455 usedBy{"highest.apex"}, 456 usedBy{"highest.apex/bin/bin1"}, 457 firstParty{}, 458 hr{}, 459 library{"Android"}, 460 usedBy{"highest.apex/bin/bin2"}, 461 library{"Device"}, 462 usedBy{"highest.apex/bin/bin1"}, 463 usedBy{"highest.apex/lib/liba.so"}, 464 library{"External"}, 465 usedBy{"highest.apex/bin/bin1"}, 466 proprietary{}, 467 }, 468 expectedDeps: []string{ 469 "testdata/firstparty/FIRST_PARTY_LICENSE", 470 "testdata/proprietary/PROPRIETARY_LICENSE", 471 "testdata/restricted/RESTRICTED_LICENSE", 472 }, 473 }, 474 { 475 condition: "proprietary", 476 name: "container", 477 roots: []string{"container.zip.meta_lic"}, 478 expectedOut: []matcher{ 479 hr{}, 480 library{"Android"}, 481 usedBy{"container.zip/bin2"}, 482 usedBy{"container.zip/libb.so"}, 483 restricted{}, 484 hr{}, 485 library{"Android"}, 486 usedBy{"container.zip"}, 487 usedBy{"container.zip/bin1"}, 488 firstParty{}, 489 hr{}, 490 library{"Android"}, 491 usedBy{"container.zip/bin2"}, 492 library{"Device"}, 493 usedBy{"container.zip/bin1"}, 494 usedBy{"container.zip/liba.so"}, 495 library{"External"}, 496 usedBy{"container.zip/bin1"}, 497 proprietary{}, 498 }, 499 expectedDeps: []string{ 500 "testdata/firstparty/FIRST_PARTY_LICENSE", 501 "testdata/proprietary/PROPRIETARY_LICENSE", 502 "testdata/restricted/RESTRICTED_LICENSE", 503 }, 504 }, 505 { 506 condition: "proprietary", 507 name: "application", 508 roots: []string{"application.meta_lic"}, 509 expectedOut: []matcher{ 510 hr{}, 511 library{"Android"}, 512 usedBy{"application"}, 513 firstParty{}, 514 hr{}, 515 library{"Device"}, 516 usedBy{"application"}, 517 proprietary{}, 518 }, 519 expectedDeps: []string{ 520 "testdata/firstparty/FIRST_PARTY_LICENSE", 521 "testdata/proprietary/PROPRIETARY_LICENSE", 522 }, 523 }, 524 { 525 condition: "proprietary", 526 name: "binary", 527 roots: []string{"bin/bin1.meta_lic"}, 528 expectedOut: []matcher{ 529 hr{}, 530 library{"Android"}, 531 usedBy{"bin/bin1"}, 532 firstParty{}, 533 hr{}, 534 library{"Device"}, 535 usedBy{"bin/bin1"}, 536 library{"External"}, 537 usedBy{"bin/bin1"}, 538 proprietary{}, 539 }, 540 expectedDeps: []string{ 541 "testdata/firstparty/FIRST_PARTY_LICENSE", 542 "testdata/proprietary/PROPRIETARY_LICENSE", 543 }, 544 }, 545 { 546 condition: "proprietary", 547 name: "library", 548 roots: []string{"lib/libd.so.meta_lic"}, 549 expectedOut: []matcher{ 550 hr{}, 551 library{"External"}, 552 usedBy{"lib/libd.so"}, 553 notice{}, 554 }, 555 expectedDeps: []string{"testdata/notice/NOTICE_LICENSE"}, 556 }, 557 } 558 for _, tt := range tests { 559 t.Run(tt.condition+" "+tt.name, func(t *testing.T) { 560 stdout := &bytes.Buffer{} 561 stderr := &bytes.Buffer{} 562 563 rootFiles := make([]string, 0, len(tt.roots)) 564 for _, r := range tt.roots { 565 rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r) 566 } 567 568 var deps []string 569 570 ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "", []string{tt.stripPrefix}, "", &deps} 571 572 err := textNotice(&ctx, rootFiles...) 573 if err != nil { 574 t.Fatalf("textnotice: error = %v, stderr = %v", err, stderr) 575 return 576 } 577 if stderr.Len() > 0 { 578 t.Errorf("textnotice: gotStderr = %v, want none", stderr) 579 } 580 581 t.Logf("got stdout: %s", stdout.String()) 582 583 t.Logf("want stdout: %s", matcherList(tt.expectedOut).String()) 584 585 out := bufio.NewScanner(stdout) 586 lineno := 0 587 for out.Scan() { 588 line := out.Text() 589 if strings.TrimLeft(line, " ") == "" { 590 continue 591 } 592 if len(tt.expectedOut) <= lineno { 593 t.Errorf("unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut)) 594 } else if !tt.expectedOut[lineno].isMatch(line) { 595 t.Errorf("unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno].String()) 596 } 597 lineno++ 598 } 599 for ; lineno < len(tt.expectedOut); lineno++ { 600 t.Errorf("textnotice: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno].String()) 601 } 602 603 t.Logf("got deps: %q", deps) 604 605 t.Logf("want deps: %q", tt.expectedDeps) 606 607 if g, w := deps, tt.expectedDeps; !reflect.DeepEqual(g, w) { 608 t.Errorf("unexpected deps, wanted:\n%s\ngot:\n%s\n", 609 strings.Join(w, "\n"), strings.Join(g, "\n")) 610 } 611 }) 612 } 613} 614 615type matcher interface { 616 isMatch(line string) bool 617 String() string 618} 619 620type hr struct{} 621 622func (m hr) isMatch(line string) bool { 623 return horizontalRule.MatchString(line) 624} 625 626func (m hr) String() string { 627 return " ================================================== " 628} 629 630type library struct { 631 name string 632} 633 634func (m library) isMatch(line string) bool { 635 return strings.HasPrefix(line, m.name+" ") 636} 637 638func (m library) String() string { 639 return m.name + " used by:" 640} 641 642type usedBy struct { 643 name string 644} 645 646func (m usedBy) isMatch(line string) bool { 647 return len(line) > 0 && line[0] == ' ' && strings.HasPrefix(strings.TrimLeft(line, " "), "out/") && strings.HasSuffix(line, "/"+m.name) 648} 649 650func (m usedBy) String() string { 651 return " out/.../" + m.name 652} 653 654type firstParty struct{} 655 656func (m firstParty) isMatch(line string) bool { 657 return strings.HasPrefix(strings.TrimLeft(line, " "), "&&&First Party License&&&") 658} 659 660func (m firstParty) String() string { 661 return "&&&First Party License&&&" 662} 663 664type notice struct{} 665 666func (m notice) isMatch(line string) bool { 667 return strings.HasPrefix(strings.TrimLeft(line, " "), "%%%Notice License%%%") 668} 669 670func (m notice) String() string { 671 return "%%%Notice License%%%" 672} 673 674type reciprocal struct{} 675 676func (m reciprocal) isMatch(line string) bool { 677 return strings.HasPrefix(strings.TrimLeft(line, " "), "$$$Reciprocal License$$$") 678} 679 680func (m reciprocal) String() string { 681 return "$$$Reciprocal License$$$" 682} 683 684type restricted struct{} 685 686func (m restricted) isMatch(line string) bool { 687 return strings.HasPrefix(strings.TrimLeft(line, " "), "###Restricted License###") 688} 689 690func (m restricted) String() string { 691 return "###Restricted License###" 692} 693 694type proprietary struct{} 695 696func (m proprietary) isMatch(line string) bool { 697 return strings.HasPrefix(strings.TrimLeft(line, " "), "@@@Proprietary License@@@") 698} 699 700func (m proprietary) String() string { 701 return "@@@Proprietary License@@@" 702} 703 704type matcherList []matcher 705 706func (l matcherList) String() string { 707 var sb strings.Builder 708 for _, m := range l { 709 s := m.String() 710 if s[:3] == s[len(s)-3:] { 711 fmt.Fprintln(&sb) 712 } 713 fmt.Fprintf(&sb, "%s\n", s) 714 if s[:3] == s[len(s)-3:] { 715 fmt.Fprintln(&sb) 716 } 717 } 718 return sb.String() 719} 720