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