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 compliance
16
17import (
18	"fmt"
19	"io"
20	"sort"
21	"strings"
22	"testing"
23
24	"android/soong/tools/compliance/testfs"
25)
26
27const (
28	// AOSP starts a test metadata file for Android Apache-2.0 licensing.
29	AOSP = `` +
30		`package_name: "Android"
31license_kinds: "SPDX-license-identifier-Apache-2.0"
32license_conditions: "notice"
33`
34
35	// GPL starts a test metadata file for GPL 2.0 licensing.
36	GPL = `` +
37		`package_name: "Free Software"
38license_kinds: "SPDX-license-identifier-GPL-2.0"
39license_conditions: "restricted"
40`
41
42	// Classpath starts a test metadata file for GPL 2.0 with classpath exception licensing.
43	Classpath = `` +
44		`package_name: "Free Software"
45license_kinds: "SPDX-license-identifier-GPL-2.0-with-classpath-exception"
46license_conditions: "permissive"
47`
48
49	// DependentModule starts a test metadata file for a module in the same package as `Classpath`.
50	DependentModule = `` +
51		`package_name: "Free Software"
52license_kinds: "SPDX-license-identifier-MIT"
53license_conditions: "notice"
54`
55
56	// LGPL starts a test metadata file for a module with LGPL 2.0 licensing.
57	LGPL = `` +
58		`package_name: "Free Library"
59license_kinds: "SPDX-license-identifier-LGPL-2.0"
60license_conditions: "restricted_if_statically_linked"
61`
62
63	// MPL starts a test metadata file for a module with MPL 2.0 reciprical licensing.
64	MPL = `` +
65		`package_name: "Reciprocal"
66license_kinds: "SPDX-license-identifier-MPL-2.0"
67license_conditions: "reciprocal"
68`
69
70	// MIT starts a test metadata file for a module with generic notice (MIT) licensing.
71	MIT = `` +
72		`package_name: "Android"
73license_kinds: "SPDX-license-identifier-MIT"
74license_conditions: "notice"
75`
76
77	// Proprietary starts a test metadata file for a module with proprietary licensing.
78	Proprietary = `` +
79		`package_name: "Android"
80license_kinds: "legacy_proprietary"
81license_conditions: "proprietary"
82`
83
84	// ByException starts a test metadata file for a module with by_exception_only licensing.
85	ByException = `` +
86		`package_name: "Special"
87license_kinds: "legacy_by_exception_only"
88license_conditions: "by_exception_only"
89`
90)
91
92var (
93	// meta maps test file names to metadata file content without dependencies.
94	meta = map[string]string{
95		"apacheBin.meta_lic":                 AOSP,
96		"apacheLib.meta_lic":                 AOSP,
97		"apacheContainer.meta_lic":           AOSP + "is_container: true\n",
98		"dependentModule.meta_lic":           DependentModule,
99		"gplWithClasspathException.meta_lic": Classpath,
100		"gplBin.meta_lic":                    GPL,
101		"gplLib.meta_lic":                    GPL,
102		"gplContainer.meta_lic":              GPL + "is_container: true\n",
103		"lgplBin.meta_lic":                   LGPL,
104		"lgplLib.meta_lic":                   LGPL,
105		"mitBin.meta_lic":                    MIT,
106		"mitLib.meta_lic":                    MIT,
107		"mplBin.meta_lic":                    MPL,
108		"mplLib.meta_lic":                    MPL,
109		"proprietary.meta_lic":               Proprietary,
110		"by_exception.meta_lic":              ByException,
111	}
112)
113
114// newTestNode constructs a test node in the license graph.
115func newTestNode(lg *LicenseGraph, targetName string) *TargetNode {
116	if tn, alreadyExists := lg.targets[targetName]; alreadyExists {
117		return tn
118	}
119	tn := &TargetNode{name: targetName}
120	lg.targets[targetName] = tn
121	return tn
122}
123
124// newTestCondition constructs a test license condition.
125func newTestCondition(conditionName string) LicenseCondition {
126	cl := LicenseConditionSetFromNames(conditionName).AsList()
127	if len(cl) == 0 {
128		panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName))
129	} else if len(cl) != 1 {
130		panic(fmt.Errorf("unexpected multiple conditions from condition name: %q: got %d, want 1", conditionName, len(cl)))
131	}
132	lc := cl[0]
133	return lc
134}
135
136// newTestConditionSet constructs a test license condition set.
137func newTestConditionSet(conditionName []string) LicenseConditionSet {
138	cs := LicenseConditionSetFromNames(conditionName...)
139	if cs.IsEmpty() {
140		panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName))
141	}
142	return cs
143}
144
145// edge describes test data edges to define test graphs.
146type edge struct {
147	target, dep string
148}
149
150// String returns a string representation of the edge.
151func (e edge) String() string {
152	return e.target + " -> " + e.dep
153}
154
155// byEdge orders edges by target then dep name then annotations.
156type byEdge []edge
157
158// Len returns the count of elements in the slice.
159func (l byEdge) Len() int { return len(l) }
160
161// Swap rearranges 2 elements of the slice so that each occupies the other's
162// former position.
163func (l byEdge) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
164
165// Less returns true when the `i`th element is lexicographically less than
166// the `j`th element.
167func (l byEdge) Less(i, j int) bool {
168	if l[i].target == l[j].target {
169		return l[i].dep < l[j].dep
170	}
171	return l[i].target < l[j].target
172}
173
174// annotated describes annotated test data edges to define test graphs.
175type annotated struct {
176	target, dep string
177	annotations []string
178}
179
180func (e annotated) String() string {
181	if e.annotations != nil {
182		return e.target + " -> " + e.dep + " [" + strings.Join(e.annotations, ", ") + "]"
183	}
184	return e.target + " -> " + e.dep
185}
186
187func (e annotated) IsEqualTo(other annotated) bool {
188	if e.target != other.target {
189		return false
190	}
191	if e.dep != other.dep {
192		return false
193	}
194	if len(e.annotations) != len(other.annotations) {
195		return false
196	}
197	a1 := append([]string{}, e.annotations...)
198	a2 := append([]string{}, other.annotations...)
199	for i := 0; i < len(a1); i++ {
200		if a1[i] != a2[i] {
201			return false
202		}
203	}
204	return true
205}
206
207// toGraph converts a list of roots and a list of annotated edges into a test license graph.
208func toGraph(stderr io.Writer, roots []string, edges []annotated) (*LicenseGraph, error) {
209	deps := make(map[string][]annotated)
210	for _, root := range roots {
211		deps[root] = []annotated{}
212	}
213	for _, edge := range edges {
214		if prev, ok := deps[edge.target]; ok {
215			deps[edge.target] = append(prev, edge)
216		} else {
217			deps[edge.target] = []annotated{edge}
218		}
219		if _, ok := deps[edge.dep]; !ok {
220			deps[edge.dep] = []annotated{}
221		}
222	}
223	fs := make(testfs.TestFS)
224	for file, edges := range deps {
225		body := meta[file]
226		for _, edge := range edges {
227			body += fmt.Sprintf("deps: {\n  file: %q\n", edge.dep)
228			for _, ann := range edge.annotations {
229				body += fmt.Sprintf("  annotations: %q\n", ann)
230			}
231			body += "}\n"
232		}
233		fs[file] = []byte(body)
234	}
235
236	return ReadLicenseGraph(&fs, stderr, roots)
237}
238
239// logGraph outputs a representation of the graph to a test log.
240func logGraph(lg *LicenseGraph, t *testing.T) {
241	t.Logf("license graph:")
242	t.Logf("  targets:")
243	for _, target := range lg.Targets() {
244		t.Logf("    %s%s in package %q", target.Name(), target.LicenseConditions().String(), target.PackageName())
245	}
246	t.Logf("  /targets")
247	t.Logf("  edges:")
248	for _, edge := range lg.Edges() {
249		t.Logf("    %s", edge.String())
250	}
251	t.Logf("  /edges")
252	t.Logf("/license graph")
253}
254
255// byAnnotatedEdge orders edges by target then dep name then annotations.
256type byAnnotatedEdge []annotated
257
258func (l byAnnotatedEdge) Len() int      { return len(l) }
259func (l byAnnotatedEdge) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
260func (l byAnnotatedEdge) Less(i, j int) bool {
261	if l[i].target == l[j].target {
262		if l[i].dep == l[j].dep {
263			ai := append([]string{}, l[i].annotations...)
264			aj := append([]string{}, l[j].annotations...)
265			sort.Strings(ai)
266			sort.Strings(aj)
267			for k := 0; k < len(ai) && k < len(aj); k++ {
268				if ai[k] == aj[k] {
269					continue
270				}
271				return ai[k] < aj[k]
272			}
273			return len(ai) < len(aj)
274		}
275		return l[i].dep < l[j].dep
276	}
277	return l[i].target < l[j].target
278}
279
280// act describes test data resolution actions to define test action sets.
281type act struct {
282	actsOn, condition string
283}
284
285// String returns a human-readable string representing the test action.
286func (a act) String() string {
287	return fmt.Sprintf("%s{%s}", a.actsOn, a.condition)
288}
289
290// toActionSet converts a list of act test data into a test action set.
291func toActionSet(lg *LicenseGraph, data []act) ActionSet {
292	as := make(ActionSet)
293	for _, a := range data {
294		actsOn := newTestNode(lg, a.actsOn)
295		cs := newTestConditionSet(strings.Split(a.condition, "|"))
296		as[actsOn] = cs
297	}
298	return as
299}
300
301// res describes test data resolutions to define test resolution sets.
302type res struct {
303	attachesTo, actsOn, condition string
304}
305
306// toResolutionSet converts a list of res test data into a test resolution set.
307func toResolutionSet(lg *LicenseGraph, data []res) ResolutionSet {
308	rmap := make(ResolutionSet)
309	for _, r := range data {
310		attachesTo := newTestNode(lg, r.attachesTo)
311		actsOn := newTestNode(lg, r.actsOn)
312		if _, ok := rmap[attachesTo]; !ok {
313			rmap[attachesTo] = make(ActionSet)
314		}
315		cs := newTestConditionSet(strings.Split(r.condition, "|"))
316		rmap[attachesTo][actsOn] |= cs
317	}
318	return rmap
319}
320
321// tcond associates a target name with '|' separated string conditions.
322type tcond struct {
323	target, conditions string
324}
325
326// action represents a single element of an ActionSet for testing.
327type action struct {
328	target *TargetNode
329	cs     LicenseConditionSet
330}
331
332// String returns a human-readable string representation of the action.
333func (a action) String() string {
334	return fmt.Sprintf("%s%s", a.target.Name(), a.cs.String())
335}
336
337// actionList represents an array of actions and a total order defined by
338// target name followed by license condition set.
339type actionList []action
340
341// String returns a human-readable string representation of the list.
342func (l actionList) String() string {
343	var sb strings.Builder
344	fmt.Fprintf(&sb, "[")
345	sep := ""
346	for _, a := range l {
347		fmt.Fprintf(&sb, "%s%s", sep, a.String())
348		sep = ", "
349	}
350	fmt.Fprintf(&sb, "]")
351	return sb.String()
352}
353
354// Len returns the count of elements in the slice.
355func (l actionList) Len() int { return len(l) }
356
357// Swap rearranges 2 elements of the slice so that each occupies the other's
358// former position.
359func (l actionList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
360
361// Less returns true when the `i`th element is lexicographically less than
362// the `j`th element.
363func (l actionList) Less(i, j int) bool {
364	if l[i].target == l[j].target {
365		return l[i].cs < l[j].cs
366	}
367	return l[i].target.Name() < l[j].target.Name()
368}
369
370// asActionList represents the resolved license conditions in a license graph
371// as an actionList for comparison in a test.
372func asActionList(lg *LicenseGraph) actionList {
373	result := make(actionList, 0, len(lg.targets))
374	for _, target := range lg.targets {
375		cs := target.resolution
376		if cs.IsEmpty() {
377			continue
378		}
379		result = append(result, action{target, cs})
380	}
381	return result
382}
383
384// toActionList converts an array of tcond into an actionList for comparison
385// in a test.
386func toActionList(lg *LicenseGraph, actions []tcond) actionList {
387	result := make(actionList, 0, len(actions))
388	for _, actn := range actions {
389		target := newTestNode(lg, actn.target)
390		cs := NewLicenseConditionSet()
391		for _, name := range strings.Split(actn.conditions, "|") {
392			lc, ok := RecognizedConditionNames[name]
393			if !ok {
394				panic(fmt.Errorf("Unrecognized test condition name: %q", name))
395			}
396			cs = cs.Plus(lc)
397		}
398		result = append(result, action{target, cs})
399	}
400	return result
401}
402
403// confl defines test data for a SourceSharePrivacyConflict as a target name,
404// source condition name, privacy condition name triple.
405type confl struct {
406	sourceNode, share, privacy string
407}
408
409// toConflictList converts confl test data into an array of
410// SourceSharePrivacyConflict for comparison in a test.
411func toConflictList(lg *LicenseGraph, data []confl) []SourceSharePrivacyConflict {
412	result := make([]SourceSharePrivacyConflict, 0, len(data))
413	for _, c := range data {
414		fields := strings.Split(c.share, ":")
415		cshare := fields[1]
416		fields = strings.Split(c.privacy, ":")
417		cprivacy := fields[1]
418		result = append(result, SourceSharePrivacyConflict{
419			newTestNode(lg, c.sourceNode),
420			newTestCondition(cshare),
421			newTestCondition(cprivacy),
422		})
423	}
424	return result
425}
426
427// checkSameActions compares an actual action set to an expected action set for a test.
428func checkSameActions(lg *LicenseGraph, asActual, asExpected ActionSet, t *testing.T) {
429	rsActual := make(ResolutionSet)
430	rsExpected := make(ResolutionSet)
431	testNode := newTestNode(lg, "test")
432	rsActual[testNode] = asActual
433	rsExpected[testNode] = asExpected
434	checkSame(rsActual, rsExpected, t)
435}
436
437// checkSame compares an actual resolution set to an expected resolution set for a test.
438func checkSame(rsActual, rsExpected ResolutionSet, t *testing.T) {
439	t.Logf("actual resolution set: %s", rsActual.String())
440	t.Logf("expected resolution set: %s", rsExpected.String())
441
442	actualTargets := rsActual.AttachesTo()
443	sort.Sort(actualTargets)
444
445	expectedTargets := rsExpected.AttachesTo()
446	sort.Sort(expectedTargets)
447
448	t.Logf("actual targets: %s", actualTargets.String())
449	t.Logf("expected targets: %s", expectedTargets.String())
450
451	for _, target := range expectedTargets {
452		if !rsActual.AttachesToTarget(target) {
453			t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false, want true", target.name)
454			continue
455		}
456		expectedRl := rsExpected.Resolutions(target)
457		sort.Sort(expectedRl)
458		actualRl := rsActual.Resolutions(target)
459		sort.Sort(actualRl)
460		if len(expectedRl) != len(actualRl) {
461			t.Errorf("unexpected number of resolutions attach to %q: %d elements, %d elements",
462				target.name, len(actualRl), len(expectedRl))
463			continue
464		}
465		for i := 0; i < len(expectedRl); i++ {
466			if expectedRl[i].attachesTo.name != actualRl[i].attachesTo.name || expectedRl[i].actsOn.name != actualRl[i].actsOn.name {
467				t.Errorf("unexpected resolution attaches to %q at index %d: got %s, want %s",
468					target.name, i, actualRl[i].asString(), expectedRl[i].asString())
469				continue
470			}
471			expectedConditions := expectedRl[i].Resolves()
472			actualConditions := actualRl[i].Resolves()
473			if expectedConditions != actualConditions {
474				t.Errorf("unexpected conditions apply to %q acting on %q: got %#v with names %s, want %#v with names %s",
475					target.name, expectedRl[i].actsOn.name,
476					actualConditions, actualConditions.Names(),
477					expectedConditions, expectedConditions.Names())
478				continue
479			}
480		}
481
482	}
483	for _, target := range actualTargets {
484		if !rsExpected.AttachesToTarget(target) {
485			t.Errorf("unexpected extra target: got expected.AttachesTo(%q) is false, want true", target.name)
486		}
487	}
488}
489
490// checkResolvesActions compares an actual action set to an expected action set for a test verifying the actual set
491// resolves all of the expected conditions.
492func checkResolvesActions(lg *LicenseGraph, asActual, asExpected ActionSet, t *testing.T) {
493	rsActual := make(ResolutionSet)
494	rsExpected := make(ResolutionSet)
495	testNode := newTestNode(lg, "test")
496	rsActual[testNode] = asActual
497	rsExpected[testNode] = asExpected
498	checkResolves(rsActual, rsExpected, t)
499}
500
501// checkResolves compares an actual resolution set to an expected resolution set for a test verifying the actual set
502// resolves all of the expected conditions.
503func checkResolves(rsActual, rsExpected ResolutionSet, t *testing.T) {
504	t.Logf("actual resolution set: %s", rsActual.String())
505	t.Logf("expected resolution set: %s", rsExpected.String())
506
507	actualTargets := rsActual.AttachesTo()
508	sort.Sort(actualTargets)
509
510	expectedTargets := rsExpected.AttachesTo()
511	sort.Sort(expectedTargets)
512
513	t.Logf("actual targets: %s", actualTargets.String())
514	t.Logf("expected targets: %s", expectedTargets.String())
515
516	for _, target := range expectedTargets {
517		if !rsActual.AttachesToTarget(target) {
518			t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false, want true", target.name)
519			continue
520		}
521		expectedRl := rsExpected.Resolutions(target)
522		sort.Sort(expectedRl)
523		actualRl := rsActual.Resolutions(target)
524		sort.Sort(actualRl)
525		if len(expectedRl) != len(actualRl) {
526			t.Errorf("unexpected number of resolutions attach to %q: %d elements, %d elements",
527				target.name, len(actualRl), len(expectedRl))
528			continue
529		}
530		for i := 0; i < len(expectedRl); i++ {
531			if expectedRl[i].attachesTo.name != actualRl[i].attachesTo.name || expectedRl[i].actsOn.name != actualRl[i].actsOn.name {
532				t.Errorf("unexpected resolution attaches to %q at index %d: got %s, want %s",
533					target.name, i, actualRl[i].asString(), expectedRl[i].asString())
534				continue
535			}
536			expectedConditions := expectedRl[i].Resolves()
537			actualConditions := actualRl[i].Resolves()
538			if expectedConditions != (expectedConditions & actualConditions) {
539				t.Errorf("expected conditions missing from %q acting on %q: got %#v with names %s, want %#v with names %s",
540					target.name, expectedRl[i].actsOn.name,
541					actualConditions, actualConditions.Names(),
542					expectedConditions, expectedConditions.Names())
543				continue
544			}
545		}
546
547	}
548	for _, target := range actualTargets {
549		if !rsExpected.AttachesToTarget(target) {
550			t.Errorf("unexpected extra target: got expected.AttachesTo(%q) is false, want true", target.name)
551		}
552	}
553}
554