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