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