xref: /aosp_15_r20/build/make/tools/compliance/cmd/htmlnotice/htmlnotice_test.go (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
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	"html"
22	"os"
23	"reflect"
24	"regexp"
25	"strings"
26	"testing"
27
28	"android/soong/tools/compliance"
29)
30
31var (
32	horizontalRule = regexp.MustCompile(`^\s*<hr>\s*$`)
33	bodyTag        = regexp.MustCompile(`^\s*<body>\s*$`)
34	boilerPlate    = regexp.MustCompile(`^\s*(?:<ul class="file-list">|<ul>|</.*)\s*$`)
35	tocTag         = regexp.MustCompile(`^\s*<ul class="toc">\s*$`)
36	libraryName    = regexp.MustCompile(`^\s*<strong>(.*)</strong>\s\s*used\s\s*by\s*:\s*$`)
37	licenseText    = regexp.MustCompile(`^\s*<a id="[^"]{32}"/><pre class="license-text">(.*)$`)
38	titleTag       = regexp.MustCompile(`^\s*<title>(.*)</title>\s*$`)
39	h1Tag          = regexp.MustCompile(`^\s*<h1>(.*)</h1>\s*$`)
40	usedByTarget   = regexp.MustCompile(`^\s*<li>(?:<a href="#id[0-9]+">)?((?:out/(?:[^/<]*/)+)[^/<]*)(?:</a>)?\s*$`)
41	installTarget  = regexp.MustCompile(`^\s*<li id="id[0-9]+"><strong>(.*)</strong>\s*$`)
42	libReference   = regexp.MustCompile(`^\s*<li><a href="#[^"]{32}">(.*)</a>\s*$`)
43)
44
45func TestMain(m *testing.M) {
46	// Change into the parent directory before running the tests
47	// so they can find the testdata directory.
48	if err := os.Chdir(".."); err != nil {
49		fmt.Printf("failed to change to testdata directory: %s\n", err)
50		os.Exit(1)
51	}
52	os.Exit(m.Run())
53}
54
55func Test(t *testing.T) {
56	tests := []struct {
57		condition    string
58		name         string
59		outDir       string
60		roots        []string
61		includeTOC   bool
62		stripPrefix  string
63		title        string
64		expectedOut  []matcher
65		expectedDeps []string
66	}{
67		{
68			condition: "firstparty",
69			name:      "apex",
70			roots:     []string{"highest.apex.meta_lic"},
71			expectedOut: []matcher{
72				hr{},
73				library{"Android"},
74				usedBy{"highest.apex"},
75				usedBy{"highest.apex/bin/bin1"},
76				usedBy{"highest.apex/bin/bin2"},
77				usedBy{"highest.apex/lib/liba.so"},
78				usedBy{"highest.apex/lib/libb.so"},
79				firstParty{},
80			},
81			expectedDeps: []string{
82				"testdata/firstparty/FIRST_PARTY_LICENSE",
83				"testdata/firstparty/bin/bin1.meta_lic",
84				"testdata/firstparty/bin/bin2.meta_lic",
85				"testdata/firstparty/highest.apex.meta_lic",
86				"testdata/firstparty/lib/liba.so.meta_lic",
87				"testdata/firstparty/lib/libb.so.meta_lic",
88				"testdata/firstparty/lib/libc.a.meta_lic",
89				"testdata/firstparty/lib/libd.so.meta_lic",
90			},
91		},
92		{
93			condition:  "firstparty",
94			name:       "apex+toc",
95			roots:      []string{"highest.apex.meta_lic"},
96			includeTOC: true,
97			expectedOut: []matcher{
98				toc{},
99				target{"highest.apex"},
100				uses{"Android"},
101				target{"highest.apex/bin/bin1"},
102				uses{"Android"},
103				target{"highest.apex/bin/bin2"},
104				uses{"Android"},
105				target{"highest.apex/lib/liba.so"},
106				uses{"Android"},
107				target{"highest.apex/lib/libb.so"},
108				uses{"Android"},
109				hr{},
110				library{"Android"},
111				usedBy{"highest.apex"},
112				usedBy{"highest.apex/bin/bin1"},
113				usedBy{"highest.apex/bin/bin2"},
114				usedBy{"highest.apex/lib/liba.so"},
115				usedBy{"highest.apex/lib/libb.so"},
116				firstParty{},
117			},
118			expectedDeps: []string{
119				"testdata/firstparty/FIRST_PARTY_LICENSE",
120				"testdata/firstparty/bin/bin1.meta_lic",
121				"testdata/firstparty/bin/bin2.meta_lic",
122				"testdata/firstparty/highest.apex.meta_lic",
123				"testdata/firstparty/lib/liba.so.meta_lic",
124				"testdata/firstparty/lib/libb.so.meta_lic",
125				"testdata/firstparty/lib/libc.a.meta_lic",
126				"testdata/firstparty/lib/libd.so.meta_lic",
127			},
128		},
129		{
130			condition: "firstparty",
131			name:      "apex-with-title",
132			roots:     []string{"highest.apex.meta_lic"},
133			title:     "Emperor",
134			expectedOut: []matcher{
135				pageTitle{"Emperor"},
136				hr{},
137				library{"Android"},
138				usedBy{"highest.apex"},
139				usedBy{"highest.apex/bin/bin1"},
140				usedBy{"highest.apex/bin/bin2"},
141				usedBy{"highest.apex/lib/liba.so"},
142				usedBy{"highest.apex/lib/libb.so"},
143				firstParty{},
144			},
145			expectedDeps: []string{
146				"testdata/firstparty/FIRST_PARTY_LICENSE",
147				"testdata/firstparty/bin/bin1.meta_lic",
148				"testdata/firstparty/bin/bin2.meta_lic",
149				"testdata/firstparty/highest.apex.meta_lic",
150				"testdata/firstparty/lib/liba.so.meta_lic",
151				"testdata/firstparty/lib/libb.so.meta_lic",
152				"testdata/firstparty/lib/libc.a.meta_lic",
153				"testdata/firstparty/lib/libd.so.meta_lic",
154			},
155		},
156		{
157			condition:  "firstparty",
158			name:       "apex-with-title+toc",
159			roots:      []string{"highest.apex.meta_lic"},
160			includeTOC: true,
161			title:      "Emperor",
162			expectedOut: []matcher{
163				pageTitle{"Emperor"},
164				toc{},
165				target{"highest.apex"},
166				uses{"Android"},
167				target{"highest.apex/bin/bin1"},
168				uses{"Android"},
169				target{"highest.apex/bin/bin2"},
170				uses{"Android"},
171				target{"highest.apex/lib/liba.so"},
172				uses{"Android"},
173				target{"highest.apex/lib/libb.so"},
174				uses{"Android"},
175				hr{},
176				library{"Android"},
177				usedBy{"highest.apex"},
178				usedBy{"highest.apex/bin/bin1"},
179				usedBy{"highest.apex/bin/bin2"},
180				usedBy{"highest.apex/lib/liba.so"},
181				usedBy{"highest.apex/lib/libb.so"},
182				firstParty{},
183			},
184			expectedDeps: []string{
185				"testdata/firstparty/FIRST_PARTY_LICENSE",
186				"testdata/firstparty/bin/bin1.meta_lic",
187				"testdata/firstparty/bin/bin2.meta_lic",
188				"testdata/firstparty/highest.apex.meta_lic",
189				"testdata/firstparty/lib/liba.so.meta_lic",
190				"testdata/firstparty/lib/libb.so.meta_lic",
191				"testdata/firstparty/lib/libc.a.meta_lic",
192				"testdata/firstparty/lib/libd.so.meta_lic",
193			},
194		},
195		{
196			condition: "firstparty",
197			name:      "container",
198			roots:     []string{"container.zip.meta_lic"},
199			expectedOut: []matcher{
200				hr{},
201				library{"Android"},
202				usedBy{"container.zip"},
203				usedBy{"container.zip/bin1"},
204				usedBy{"container.zip/bin2"},
205				usedBy{"container.zip/liba.so"},
206				usedBy{"container.zip/libb.so"},
207				firstParty{},
208			},
209			expectedDeps: []string{
210				"testdata/firstparty/FIRST_PARTY_LICENSE",
211				"testdata/firstparty/bin/bin1.meta_lic",
212				"testdata/firstparty/bin/bin2.meta_lic",
213				"testdata/firstparty/container.zip.meta_lic",
214				"testdata/firstparty/lib/liba.so.meta_lic",
215				"testdata/firstparty/lib/libb.so.meta_lic",
216				"testdata/firstparty/lib/libc.a.meta_lic",
217				"testdata/firstparty/lib/libd.so.meta_lic",
218			},
219		},
220		{
221			condition: "firstparty",
222			name:      "application",
223			roots:     []string{"application.meta_lic"},
224			expectedOut: []matcher{
225				hr{},
226				library{"Android"},
227				usedBy{"application"},
228				firstParty{},
229			},
230			expectedDeps: []string{
231				"testdata/firstparty/FIRST_PARTY_LICENSE",
232				"testdata/firstparty/application.meta_lic",
233				"testdata/firstparty/bin/bin3.meta_lic",
234				"testdata/firstparty/lib/liba.so.meta_lic",
235				"testdata/firstparty/lib/libb.so.meta_lic",
236			},
237		},
238		{
239			condition: "firstparty",
240			name:      "binary",
241			roots:     []string{"bin/bin1.meta_lic"},
242			expectedOut: []matcher{
243				hr{},
244				library{"Android"},
245				usedBy{"bin/bin1"},
246				firstParty{},
247			},
248			expectedDeps: []string{
249				"testdata/firstparty/FIRST_PARTY_LICENSE",
250				"testdata/firstparty/bin/bin1.meta_lic",
251				"testdata/firstparty/lib/liba.so.meta_lic",
252				"testdata/firstparty/lib/libc.a.meta_lic",
253			},
254		},
255		{
256			condition: "firstparty",
257			name:      "library",
258			roots:     []string{"lib/libd.so.meta_lic"},
259			expectedOut: []matcher{
260				hr{},
261				library{"Android"},
262				usedBy{"lib/libd.so"},
263				firstParty{},
264			},
265			expectedDeps: []string{
266				"testdata/firstparty/FIRST_PARTY_LICENSE",
267				"testdata/firstparty/lib/libd.so.meta_lic",
268			},
269		},
270		{
271			condition: "notice",
272			name:      "apex",
273			roots:     []string{"highest.apex.meta_lic"},
274			expectedOut: []matcher{
275				hr{},
276				library{"Android"},
277				usedBy{"highest.apex"},
278				usedBy{"highest.apex/bin/bin1"},
279				usedBy{"highest.apex/bin/bin2"},
280				usedBy{"highest.apex/lib/libb.so"},
281				firstParty{},
282				hr{},
283				library{"Device"},
284				usedBy{"highest.apex/bin/bin1"},
285				usedBy{"highest.apex/lib/liba.so"},
286				library{"External"},
287				usedBy{"highest.apex/bin/bin1"},
288				notice{},
289			},
290			expectedDeps: []string{
291				"testdata/firstparty/FIRST_PARTY_LICENSE",
292				"testdata/notice/NOTICE_LICENSE",
293				"testdata/notice/bin/bin1.meta_lic",
294				"testdata/notice/bin/bin2.meta_lic",
295				"testdata/notice/highest.apex.meta_lic",
296				"testdata/notice/lib/liba.so.meta_lic",
297				"testdata/notice/lib/libb.so.meta_lic",
298				"testdata/notice/lib/libc.a.meta_lic",
299				"testdata/notice/lib/libd.so.meta_lic",
300			},
301		},
302		{
303			condition: "notice",
304			name:      "container",
305			roots:     []string{"container.zip.meta_lic"},
306			expectedOut: []matcher{
307				hr{},
308				library{"Android"},
309				usedBy{"container.zip"},
310				usedBy{"container.zip/bin1"},
311				usedBy{"container.zip/bin2"},
312				usedBy{"container.zip/libb.so"},
313				firstParty{},
314				hr{},
315				library{"Device"},
316				usedBy{"container.zip/bin1"},
317				usedBy{"container.zip/liba.so"},
318				library{"External"},
319				usedBy{"container.zip/bin1"},
320				notice{},
321			},
322			expectedDeps: []string{
323				"testdata/firstparty/FIRST_PARTY_LICENSE",
324				"testdata/notice/NOTICE_LICENSE",
325				"testdata/notice/bin/bin1.meta_lic",
326				"testdata/notice/bin/bin2.meta_lic",
327				"testdata/notice/container.zip.meta_lic",
328				"testdata/notice/lib/liba.so.meta_lic",
329				"testdata/notice/lib/libb.so.meta_lic",
330				"testdata/notice/lib/libc.a.meta_lic",
331				"testdata/notice/lib/libd.so.meta_lic",
332			},
333		},
334		{
335			condition: "notice",
336			name:      "application",
337			roots:     []string{"application.meta_lic"},
338			expectedOut: []matcher{
339				hr{},
340				library{"Android"},
341				usedBy{"application"},
342				firstParty{},
343				hr{},
344				library{"Device"},
345				usedBy{"application"},
346				notice{},
347			},
348			expectedDeps: []string{
349				"testdata/firstparty/FIRST_PARTY_LICENSE",
350				"testdata/notice/NOTICE_LICENSE",
351				"testdata/notice/application.meta_lic",
352				"testdata/notice/bin/bin3.meta_lic",
353				"testdata/notice/lib/liba.so.meta_lic",
354				"testdata/notice/lib/libb.so.meta_lic",
355			},
356		},
357		{
358			condition: "notice",
359			name:      "binary",
360			roots:     []string{"bin/bin1.meta_lic"},
361			expectedOut: []matcher{
362				hr{},
363				library{"Android"},
364				usedBy{"bin/bin1"},
365				firstParty{},
366				hr{},
367				library{"Device"},
368				usedBy{"bin/bin1"},
369				library{"External"},
370				usedBy{"bin/bin1"},
371				notice{},
372			},
373			expectedDeps: []string{
374				"testdata/firstparty/FIRST_PARTY_LICENSE",
375				"testdata/notice/NOTICE_LICENSE",
376				"testdata/notice/bin/bin1.meta_lic",
377				"testdata/notice/lib/liba.so.meta_lic",
378				"testdata/notice/lib/libc.a.meta_lic",
379			},
380		},
381		{
382			condition: "notice",
383			name:      "library",
384			roots:     []string{"lib/libd.so.meta_lic"},
385			expectedOut: []matcher{
386				hr{},
387				library{"External"},
388				usedBy{"lib/libd.so"},
389				notice{},
390			},
391			expectedDeps: []string{
392				"testdata/notice/NOTICE_LICENSE",
393				"testdata/notice/lib/libd.so.meta_lic",
394			},
395		},
396		{
397			condition: "reciprocal",
398			name:      "apex",
399			roots:     []string{"highest.apex.meta_lic"},
400			expectedOut: []matcher{
401				hr{},
402				library{"Android"},
403				usedBy{"highest.apex"},
404				usedBy{"highest.apex/bin/bin1"},
405				usedBy{"highest.apex/bin/bin2"},
406				usedBy{"highest.apex/lib/libb.so"},
407				firstParty{},
408				hr{},
409				library{"Device"},
410				usedBy{"highest.apex/bin/bin1"},
411				usedBy{"highest.apex/lib/liba.so"},
412				library{"External"},
413				usedBy{"highest.apex/bin/bin1"},
414				reciprocal{},
415			},
416			expectedDeps: []string{
417				"testdata/firstparty/FIRST_PARTY_LICENSE",
418				"testdata/reciprocal/RECIPROCAL_LICENSE",
419				"testdata/reciprocal/bin/bin1.meta_lic",
420				"testdata/reciprocal/bin/bin2.meta_lic",
421				"testdata/reciprocal/highest.apex.meta_lic",
422				"testdata/reciprocal/lib/liba.so.meta_lic",
423				"testdata/reciprocal/lib/libb.so.meta_lic",
424				"testdata/reciprocal/lib/libc.a.meta_lic",
425				"testdata/reciprocal/lib/libd.so.meta_lic",
426			},
427		},
428		{
429			condition: "reciprocal",
430			name:      "container",
431			roots:     []string{"container.zip.meta_lic"},
432			expectedOut: []matcher{
433				hr{},
434				library{"Android"},
435				usedBy{"container.zip"},
436				usedBy{"container.zip/bin1"},
437				usedBy{"container.zip/bin2"},
438				usedBy{"container.zip/libb.so"},
439				firstParty{},
440				hr{},
441				library{"Device"},
442				usedBy{"container.zip/bin1"},
443				usedBy{"container.zip/liba.so"},
444				library{"External"},
445				usedBy{"container.zip/bin1"},
446				reciprocal{},
447			},
448			expectedDeps: []string{
449				"testdata/firstparty/FIRST_PARTY_LICENSE",
450				"testdata/reciprocal/RECIPROCAL_LICENSE",
451				"testdata/reciprocal/bin/bin1.meta_lic",
452				"testdata/reciprocal/bin/bin2.meta_lic",
453				"testdata/reciprocal/container.zip.meta_lic",
454				"testdata/reciprocal/lib/liba.so.meta_lic",
455				"testdata/reciprocal/lib/libb.so.meta_lic",
456				"testdata/reciprocal/lib/libc.a.meta_lic",
457				"testdata/reciprocal/lib/libd.so.meta_lic",
458			},
459		},
460		{
461			condition: "reciprocal",
462			name:      "application",
463			roots:     []string{"application.meta_lic"},
464			expectedOut: []matcher{
465				hr{},
466				library{"Android"},
467				usedBy{"application"},
468				firstParty{},
469				hr{},
470				library{"Device"},
471				usedBy{"application"},
472				reciprocal{},
473			},
474			expectedDeps: []string{
475				"testdata/firstparty/FIRST_PARTY_LICENSE",
476				"testdata/reciprocal/RECIPROCAL_LICENSE",
477				"testdata/reciprocal/application.meta_lic",
478				"testdata/reciprocal/bin/bin3.meta_lic",
479				"testdata/reciprocal/lib/liba.so.meta_lic",
480				"testdata/reciprocal/lib/libb.so.meta_lic",
481			},
482		},
483		{
484			condition: "reciprocal",
485			name:      "binary",
486			roots:     []string{"bin/bin1.meta_lic"},
487			expectedOut: []matcher{
488				hr{},
489				library{"Android"},
490				usedBy{"bin/bin1"},
491				firstParty{},
492				hr{},
493				library{"Device"},
494				usedBy{"bin/bin1"},
495				library{"External"},
496				usedBy{"bin/bin1"},
497				reciprocal{},
498			},
499			expectedDeps: []string{
500				"testdata/firstparty/FIRST_PARTY_LICENSE",
501				"testdata/reciprocal/RECIPROCAL_LICENSE",
502				"testdata/reciprocal/bin/bin1.meta_lic",
503				"testdata/reciprocal/lib/liba.so.meta_lic",
504				"testdata/reciprocal/lib/libc.a.meta_lic",
505			},
506		},
507		{
508			condition: "reciprocal",
509			name:      "library",
510			roots:     []string{"lib/libd.so.meta_lic"},
511			expectedOut: []matcher{
512				hr{},
513				library{"External"},
514				usedBy{"lib/libd.so"},
515				notice{},
516			},
517			expectedDeps: []string{
518				"testdata/notice/NOTICE_LICENSE",
519				"testdata/reciprocal/lib/libd.so.meta_lic",
520			},
521		},
522		{
523			condition: "restricted",
524			name:      "apex",
525			roots:     []string{"highest.apex.meta_lic"},
526			expectedOut: []matcher{
527				hr{},
528				library{"Android"},
529				usedBy{"highest.apex"},
530				usedBy{"highest.apex/bin/bin1"},
531				usedBy{"highest.apex/bin/bin2"},
532				firstParty{},
533				hr{},
534				library{"Android"},
535				usedBy{"highest.apex/bin/bin2"},
536				usedBy{"highest.apex/lib/libb.so"},
537				library{"Device"},
538				usedBy{"highest.apex/bin/bin1"},
539				usedBy{"highest.apex/lib/liba.so"},
540				restricted{},
541				hr{},
542				library{"External"},
543				usedBy{"highest.apex/bin/bin1"},
544				reciprocal{},
545			},
546			expectedDeps: []string{
547				"testdata/firstparty/FIRST_PARTY_LICENSE",
548				"testdata/reciprocal/RECIPROCAL_LICENSE",
549				"testdata/restricted/RESTRICTED_LICENSE",
550				"testdata/restricted/bin/bin1.meta_lic",
551				"testdata/restricted/bin/bin2.meta_lic",
552				"testdata/restricted/highest.apex.meta_lic",
553				"testdata/restricted/lib/liba.so.meta_lic",
554				"testdata/restricted/lib/libb.so.meta_lic",
555				"testdata/restricted/lib/libc.a.meta_lic",
556				"testdata/restricted/lib/libd.so.meta_lic",
557			},
558		},
559		{
560			condition: "restricted",
561			name:      "container",
562			roots:     []string{"container.zip.meta_lic"},
563			expectedOut: []matcher{
564				hr{},
565				library{"Android"},
566				usedBy{"container.zip"},
567				usedBy{"container.zip/bin1"},
568				usedBy{"container.zip/bin2"},
569				firstParty{},
570				hr{},
571				library{"Android"},
572				usedBy{"container.zip/bin2"},
573				usedBy{"container.zip/libb.so"},
574				library{"Device"},
575				usedBy{"container.zip/bin1"},
576				usedBy{"container.zip/liba.so"},
577				restricted{},
578				hr{},
579				library{"External"},
580				usedBy{"container.zip/bin1"},
581				reciprocal{},
582			},
583			expectedDeps: []string{
584				"testdata/firstparty/FIRST_PARTY_LICENSE",
585				"testdata/reciprocal/RECIPROCAL_LICENSE",
586				"testdata/restricted/RESTRICTED_LICENSE",
587				"testdata/restricted/bin/bin1.meta_lic",
588				"testdata/restricted/bin/bin2.meta_lic",
589				"testdata/restricted/container.zip.meta_lic",
590				"testdata/restricted/lib/liba.so.meta_lic",
591				"testdata/restricted/lib/libb.so.meta_lic",
592				"testdata/restricted/lib/libc.a.meta_lic",
593				"testdata/restricted/lib/libd.so.meta_lic",
594			},
595		},
596		{
597			condition: "restricted",
598			name:      "application",
599			roots:     []string{"application.meta_lic"},
600			expectedOut: []matcher{
601				hr{},
602				library{"Android"},
603				usedBy{"application"},
604				firstParty{},
605				hr{},
606				library{"Device"},
607				usedBy{"application"},
608				restricted{},
609			},
610			expectedDeps: []string{
611				"testdata/firstparty/FIRST_PARTY_LICENSE",
612				"testdata/restricted/RESTRICTED_LICENSE",
613				"testdata/restricted/application.meta_lic",
614				"testdata/restricted/bin/bin3.meta_lic",
615				"testdata/restricted/lib/liba.so.meta_lic",
616				"testdata/restricted/lib/libb.so.meta_lic",
617			},
618		},
619		{
620			condition: "restricted",
621			name:      "binary",
622			roots:     []string{"bin/bin1.meta_lic"},
623			expectedOut: []matcher{
624				hr{},
625				library{"Android"},
626				usedBy{"bin/bin1"},
627				firstParty{},
628				hr{},
629				library{"Device"},
630				usedBy{"bin/bin1"},
631				restricted{},
632				hr{},
633				library{"External"},
634				usedBy{"bin/bin1"},
635				reciprocal{},
636			},
637			expectedDeps: []string{
638				"testdata/firstparty/FIRST_PARTY_LICENSE",
639				"testdata/reciprocal/RECIPROCAL_LICENSE",
640				"testdata/restricted/RESTRICTED_LICENSE",
641				"testdata/restricted/bin/bin1.meta_lic",
642				"testdata/restricted/lib/liba.so.meta_lic",
643				"testdata/restricted/lib/libc.a.meta_lic",
644			},
645		},
646		{
647			condition: "restricted",
648			name:      "library",
649			roots:     []string{"lib/libd.so.meta_lic"},
650			expectedOut: []matcher{
651				hr{},
652				library{"External"},
653				usedBy{"lib/libd.so"},
654				notice{},
655			},
656			expectedDeps: []string{
657				"testdata/notice/NOTICE_LICENSE",
658				"testdata/restricted/lib/libd.so.meta_lic",
659			},
660		},
661		{
662			condition: "proprietary",
663			name:      "apex",
664			roots:     []string{"highest.apex.meta_lic"},
665			expectedOut: []matcher{
666				hr{},
667				library{"Android"},
668				usedBy{"highest.apex/bin/bin2"},
669				usedBy{"highest.apex/lib/libb.so"},
670				restricted{},
671				hr{},
672				library{"Android"},
673				usedBy{"highest.apex"},
674				usedBy{"highest.apex/bin/bin1"},
675				firstParty{},
676				hr{},
677				library{"Android"},
678				usedBy{"highest.apex/bin/bin2"},
679				library{"Device"},
680				usedBy{"highest.apex/bin/bin1"},
681				usedBy{"highest.apex/lib/liba.so"},
682				library{"External"},
683				usedBy{"highest.apex/bin/bin1"},
684				proprietary{},
685			},
686			expectedDeps: []string{
687				"testdata/firstparty/FIRST_PARTY_LICENSE",
688				"testdata/proprietary/PROPRIETARY_LICENSE",
689				"testdata/proprietary/bin/bin1.meta_lic",
690				"testdata/proprietary/bin/bin2.meta_lic",
691				"testdata/proprietary/highest.apex.meta_lic",
692				"testdata/proprietary/lib/liba.so.meta_lic",
693				"testdata/proprietary/lib/libb.so.meta_lic",
694				"testdata/proprietary/lib/libc.a.meta_lic",
695				"testdata/proprietary/lib/libd.so.meta_lic",
696				"testdata/restricted/RESTRICTED_LICENSE",
697			},
698		},
699		{
700			condition: "proprietary",
701			name:      "container",
702			roots:     []string{"container.zip.meta_lic"},
703			expectedOut: []matcher{
704				hr{},
705				library{"Android"},
706				usedBy{"container.zip/bin2"},
707				usedBy{"container.zip/libb.so"},
708				restricted{},
709				hr{},
710				library{"Android"},
711				usedBy{"container.zip"},
712				usedBy{"container.zip/bin1"},
713				firstParty{},
714				hr{},
715				library{"Android"},
716				usedBy{"container.zip/bin2"},
717				library{"Device"},
718				usedBy{"container.zip/bin1"},
719				usedBy{"container.zip/liba.so"},
720				library{"External"},
721				usedBy{"container.zip/bin1"},
722				proprietary{},
723			},
724			expectedDeps: []string{
725				"testdata/firstparty/FIRST_PARTY_LICENSE",
726				"testdata/proprietary/PROPRIETARY_LICENSE",
727				"testdata/proprietary/bin/bin1.meta_lic",
728				"testdata/proprietary/bin/bin2.meta_lic",
729				"testdata/proprietary/container.zip.meta_lic",
730				"testdata/proprietary/lib/liba.so.meta_lic",
731				"testdata/proprietary/lib/libb.so.meta_lic",
732				"testdata/proprietary/lib/libc.a.meta_lic",
733				"testdata/proprietary/lib/libd.so.meta_lic",
734				"testdata/restricted/RESTRICTED_LICENSE",
735			},
736		},
737		{
738			condition: "proprietary",
739			name:      "application",
740			roots:     []string{"application.meta_lic"},
741			expectedOut: []matcher{
742				hr{},
743				library{"Android"},
744				usedBy{"application"},
745				firstParty{},
746				hr{},
747				library{"Device"},
748				usedBy{"application"},
749				proprietary{},
750			},
751			expectedDeps: []string{
752				"testdata/firstparty/FIRST_PARTY_LICENSE",
753				"testdata/proprietary/PROPRIETARY_LICENSE",
754				"testdata/proprietary/application.meta_lic",
755				"testdata/proprietary/bin/bin3.meta_lic",
756				"testdata/proprietary/lib/liba.so.meta_lic",
757				"testdata/proprietary/lib/libb.so.meta_lic",
758			},
759		},
760		{
761			condition: "proprietary",
762			name:      "binary",
763			roots:     []string{"bin/bin1.meta_lic"},
764			expectedOut: []matcher{
765				hr{},
766				library{"Android"},
767				usedBy{"bin/bin1"},
768				firstParty{},
769				hr{},
770				library{"Device"},
771				usedBy{"bin/bin1"},
772				library{"External"},
773				usedBy{"bin/bin1"},
774				proprietary{},
775			},
776			expectedDeps: []string{
777				"testdata/firstparty/FIRST_PARTY_LICENSE",
778				"testdata/proprietary/PROPRIETARY_LICENSE",
779				"testdata/proprietary/bin/bin1.meta_lic",
780				"testdata/proprietary/lib/liba.so.meta_lic",
781				"testdata/proprietary/lib/libc.a.meta_lic",
782			},
783		},
784		{
785			condition: "proprietary",
786			name:      "library",
787			roots:     []string{"lib/libd.so.meta_lic"},
788			expectedOut: []matcher{
789				hr{},
790				library{"External"},
791				usedBy{"lib/libd.so"},
792				notice{},
793			},
794			expectedDeps: []string{
795				"testdata/notice/NOTICE_LICENSE",
796				"testdata/proprietary/lib/libd.so.meta_lic",
797			},
798		},
799	}
800	for _, tt := range tests {
801		t.Run(tt.condition+" "+tt.name, func(t *testing.T) {
802			stdout := &bytes.Buffer{}
803			stderr := &bytes.Buffer{}
804
805			rootFiles := make([]string, 0, len(tt.roots))
806			for _, r := range tt.roots {
807				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
808			}
809
810			var deps []string
811
812			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), tt.includeTOC, "", []string{tt.stripPrefix}, tt.title, &deps}
813
814			err := htmlNotice(&ctx, rootFiles...)
815			if err != nil {
816				t.Fatalf("htmlnotice: error = %v, stderr = %v", err, stderr)
817				return
818			}
819			if stderr.Len() > 0 {
820				t.Errorf("htmlnotice: gotStderr = %v, want none", stderr)
821			}
822
823			t.Logf("got stdout: %s", stdout.String())
824
825			t.Logf("want stdout: %s", matcherList(tt.expectedOut).String())
826
827			out := bufio.NewScanner(stdout)
828			lineno := 0
829			inBody := false
830			hasTitle := false
831			ttle, expectTitle := tt.expectedOut[0].(pageTitle)
832			for out.Scan() {
833				line := out.Text()
834				if strings.TrimLeft(line, " ") == "" {
835					continue
836				}
837				if !inBody {
838					if expectTitle {
839						if tl := checkTitle(line); len(tl) > 0 {
840							if tl != ttle.t {
841								t.Errorf("htmlnotice: unexpected title: got %q, want %q", tl, ttle.t)
842							}
843							hasTitle = true
844						}
845					}
846					if bodyTag.MatchString(line) {
847						inBody = true
848						if expectTitle && !hasTitle {
849							t.Errorf("htmlnotice: missing title: got no <title> tag, want <title>%s</title>", ttle.t)
850						}
851					}
852					continue
853				}
854				if boilerPlate.MatchString(line) {
855					continue
856				}
857				if len(tt.expectedOut) <= lineno {
858					t.Errorf("htmlnotice: unexpected output at line %d: got %q, want nothing (wanted %d lines)", lineno+1, line, len(tt.expectedOut))
859				} else if !tt.expectedOut[lineno].isMatch(line) {
860					t.Errorf("htmlnotice: unexpected output at line %d: got %q, want %q", lineno+1, line, tt.expectedOut[lineno].String())
861				}
862				lineno++
863			}
864			if !inBody {
865				t.Errorf("htmlnotice: missing body: got no <body> tag, want <body> tag followed by %s", matcherList(tt.expectedOut).String())
866				return
867			}
868			for ; lineno < len(tt.expectedOut); lineno++ {
869				t.Errorf("htmlnotice: missing output line %d: ended early, want %q", lineno+1, tt.expectedOut[lineno].String())
870			}
871
872			t.Logf("got deps: %q", deps)
873
874			t.Logf("want deps: %q", tt.expectedDeps)
875
876			if g, w := deps, tt.expectedDeps; !reflect.DeepEqual(g, w) {
877				t.Errorf("unexpected deps, wanted:\n%s\ngot:\n%s\n",
878					strings.Join(w, "\n"), strings.Join(g, "\n"))
879			}
880		})
881	}
882}
883
884func checkTitle(line string) string {
885	groups := titleTag.FindStringSubmatch(line)
886	if len(groups) != 2 {
887		return ""
888	}
889	return groups[1]
890}
891
892type matcher interface {
893	isMatch(line string) bool
894	String() string
895}
896
897type pageTitle struct {
898	t string
899}
900
901func (m pageTitle) isMatch(line string) bool {
902	groups := h1Tag.FindStringSubmatch(line)
903	if len(groups) != 2 {
904		return false
905	}
906	return groups[1] == html.EscapeString(m.t)
907}
908
909func (m pageTitle) String() string {
910	return "  <h1>" + html.EscapeString(m.t) + "</h1>"
911}
912
913type toc struct{}
914
915func (m toc) isMatch(line string) bool {
916	return tocTag.MatchString(line)
917}
918
919func (m toc) String() string {
920	return `  <ul class="toc">`
921}
922
923type target struct {
924	name string
925}
926
927func (m target) isMatch(line string) bool {
928	groups := installTarget.FindStringSubmatch(line)
929	if len(groups) != 2 {
930		return false
931	}
932	return strings.HasPrefix(groups[1], "out/") && strings.HasSuffix(groups[1], "/"+html.EscapeString(m.name))
933}
934
935func (m target) String() string {
936	return `  <li id="id#"><strong>` + html.EscapeString(m.name) + `</strong>`
937}
938
939type uses struct {
940	name string
941}
942
943func (m uses) isMatch(line string) bool {
944	groups := libReference.FindStringSubmatch(line)
945	if len(groups) != 2 {
946		return false
947	}
948	return groups[1] == html.EscapeString(m.name)
949}
950
951func (m uses) String() string {
952	return `  <li><a href="#hash">` + html.EscapeString(m.name) + `</a>`
953}
954
955type hr struct{}
956
957func (m hr) isMatch(line string) bool {
958	return horizontalRule.MatchString(line)
959}
960
961func (m hr) String() string {
962	return "  <hr>"
963}
964
965type library struct {
966	name string
967}
968
969func (m library) isMatch(line string) bool {
970	groups := libraryName.FindStringSubmatch(line)
971	if len(groups) != 2 {
972		return false
973	}
974	return groups[1] == html.EscapeString(m.name)
975}
976
977func (m library) String() string {
978	return "  <strong>" + html.EscapeString(m.name) + "</strong> used by:"
979}
980
981type usedBy struct {
982	name string
983}
984
985func (m usedBy) isMatch(line string) bool {
986	groups := usedByTarget.FindStringSubmatch(line)
987	if len(groups) != 2 {
988		return false
989	}
990	return strings.HasPrefix(groups[1], "out/") && strings.HasSuffix(groups[1], "/"+html.EscapeString(m.name))
991}
992
993func (m usedBy) String() string {
994	return "  <li>out/.../" + html.EscapeString(m.name)
995}
996
997func matchesText(line, text string) bool {
998	groups := licenseText.FindStringSubmatch(line)
999	if len(groups) != 2 {
1000		return false
1001	}
1002	return groups[1] == html.EscapeString(text)
1003}
1004
1005func expectedText(text string) string {
1006	return `  <a href="#hash"/><pre class="license-text">` + html.EscapeString(text)
1007}
1008
1009type firstParty struct{}
1010
1011func (m firstParty) isMatch(line string) bool {
1012	return matchesText(line, "&&&First Party License&&&")
1013}
1014
1015func (m firstParty) String() string {
1016	return expectedText("&&&First Party License&&&")
1017}
1018
1019type notice struct{}
1020
1021func (m notice) isMatch(line string) bool {
1022	return matchesText(line, "%%%Notice License%%%")
1023}
1024
1025func (m notice) String() string {
1026	return expectedText("%%%Notice License%%%")
1027}
1028
1029type reciprocal struct{}
1030
1031func (m reciprocal) isMatch(line string) bool {
1032	return matchesText(line, "$$$Reciprocal License$$$")
1033}
1034
1035func (m reciprocal) String() string {
1036	return expectedText("$$$Reciprocal License$$$")
1037}
1038
1039type restricted struct{}
1040
1041func (m restricted) isMatch(line string) bool {
1042	return matchesText(line, "###Restricted License###")
1043}
1044
1045func (m restricted) String() string {
1046	return expectedText("###Restricted License###")
1047}
1048
1049type proprietary struct{}
1050
1051func (m proprietary) isMatch(line string) bool {
1052	return matchesText(line, "@@@Proprietary License@@@")
1053}
1054
1055func (m proprietary) String() string {
1056	return expectedText("@@@Proprietary License@@@")
1057}
1058
1059type matcherList []matcher
1060
1061func (l matcherList) String() string {
1062	var sb strings.Builder
1063	for _, m := range l {
1064		s := m.String()
1065		if s[:3] == s[len(s)-3:] {
1066			fmt.Fprintln(&sb)
1067		}
1068		fmt.Fprintf(&sb, "%s\n", s)
1069		if s[:3] == s[len(s)-3:] {
1070			fmt.Fprintln(&sb)
1071		}
1072	}
1073	return sb.String()
1074}
1075