blob: 731e783fb1af19e02ec75e1c36153ee34f5adcb2 [file] [log] [blame]
Bob Badourf8792242022-02-01 11:54:20 -08001// 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"
Bob Badourc778e4c2022-03-22 13:05:19 -070027
28 "android/soong/tools/compliance"
Bob Badourf8792242022-02-01 11:54:20 -080029)
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
Bob Badourc778e4c2022-03-22 13:05:19 -070050 outDir string
Bob Badourf8792242022-02-01 11:54:20 -080051 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
Bob Badourc778e4c2022-03-22 13:05:19 -0700465 ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "", []string{tt.stripPrefix}, "", &deps}
Bob Badourf8792242022-02-01 11:54:20 -0800466
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}