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