• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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