blob: 053b3980e14679f79063039de53c15c02376842e [file] [log] [blame]
Bob Badoura99ac622021-10-25 16:21:00 -07001// 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"
Bob Badoura99ac622021-10-25 16:21:00 -070020 "sort"
21 "strings"
22 "testing"
Bob Badourdc62de42022-10-12 20:10:17 -070023
24 "android/soong/tools/compliance/testfs"
Bob Badoura99ac622021-10-25 16:21:00 -070025)
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`
Bob Badour9ee7d032021-10-25 16:51:48 -070034
35 // GPL starts a test metadata file for GPL 2.0 licensing.
36 GPL = `` +
Colin Cross35f79c32022-01-27 15:18:52 -080037 `package_name: "Free Software"
Bob Badour9ee7d032021-10-25 16:51:48 -070038license_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 = `` +
Colin Cross35f79c32022-01-27 15:18:52 -080044 `package_name: "Free Software"
Bob Badour9ee7d032021-10-25 16:51:48 -070045license_kinds: "SPDX-license-identifier-GPL-2.0-with-classpath-exception"
Bob Badour10f5c482022-09-20 21:44:17 -070046license_conditions: "permissive"
Bob Badour9ee7d032021-10-25 16:51:48 -070047`
48
49 // DependentModule starts a test metadata file for a module in the same package as `Classpath`.
50 DependentModule = `` +
Colin Cross35f79c32022-01-27 15:18:52 -080051 `package_name: "Free Software"
Bob Badour9ee7d032021-10-25 16:51:48 -070052license_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 = `` +
Colin Cross35f79c32022-01-27 15:18:52 -080058 `package_name: "Free Library"
Bob Badour9ee7d032021-10-25 16:51:48 -070059license_kinds: "SPDX-license-identifier-LGPL-2.0"
Bob Badourcac8a3c2022-11-22 12:04:10 -080060license_conditions: "restricted_if_statically_linked"
Bob Badour9ee7d032021-10-25 16:51:48 -070061`
62
63 // MPL starts a test metadata file for a module with MPL 2.0 reciprical licensing.
64 MPL = `` +
Colin Cross35f79c32022-01-27 15:18:52 -080065 `package_name: "Reciprocal"
Bob Badour9ee7d032021-10-25 16:51:48 -070066license_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 = `` +
Colin Cross35f79c32022-01-27 15:18:52 -080072 `package_name: "Android"
Bob Badour9ee7d032021-10-25 16:51:48 -070073license_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 = `` +
Colin Cross35f79c32022-01-27 15:18:52 -080079 `package_name: "Android"
Bob Badour9ee7d032021-10-25 16:51:48 -070080license_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 = `` +
Colin Cross35f79c32022-01-27 15:18:52 -080086 `package_name: "Special"
Bob Badour9ee7d032021-10-25 16:51:48 -070087license_kinds: "legacy_by_exception_only"
88license_conditions: "by_exception_only"
89`
Bob Badour9ee7d032021-10-25 16:51:48 -070090)
91
92var (
93 // meta maps test file names to metadata file content without dependencies.
94 meta = map[string]string{
Colin Cross35f79c32022-01-27 15:18:52 -080095 "apacheBin.meta_lic": AOSP,
96 "apacheLib.meta_lic": AOSP,
97 "apacheContainer.meta_lic": AOSP + "is_container: true\n",
98 "dependentModule.meta_lic": DependentModule,
Bob Badour9ee7d032021-10-25 16:51:48 -070099 "gplWithClasspathException.meta_lic": Classpath,
Colin Cross35f79c32022-01-27 15:18:52 -0800100 "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,
Bob Badour9ee7d032021-10-25 16:51:48 -0700111 }
Bob Badoura99ac622021-10-25 16:21:00 -0700112)
113
Bob Badoura99ac622021-10-25 16:21:00 -0700114// newTestNode constructs a test node in the license graph.
115func newTestNode(lg *LicenseGraph, targetName string) *TargetNode {
Bob Badour103eb0f2022-01-10 13:50:57 -0800116 if tn, alreadyExists := lg.targets[targetName]; alreadyExists {
117 return tn
Bob Badoura99ac622021-10-25 16:21:00 -0700118 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800119 tn := &TargetNode{name: targetName}
120 lg.targets[targetName] = tn
121 return tn
122}
123
Bob Badoura6ee6d52022-12-16 13:50:41 -0800124// newTestCondition constructs a test license condition.
125func newTestCondition(conditionName string) LicenseCondition {
126 cl := LicenseConditionSetFromNames(conditionName).AsList()
Bob Badour103eb0f2022-01-10 13:50:57 -0800127 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]
Bob Badour103eb0f2022-01-10 13:50:57 -0800133 return lc
134}
135
Bob Badoura6ee6d52022-12-16 13:50:41 -0800136// newTestConditionSet constructs a test license condition set.
137func newTestConditionSet(conditionName []string) LicenseConditionSet {
138 cs := LicenseConditionSetFromNames(conditionName...)
Bob Badour103eb0f2022-01-10 13:50:57 -0800139 if cs.IsEmpty() {
140 panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName))
141 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800142 return cs
Bob Badoura99ac622021-10-25 16:21:00 -0700143}
144
Bob Badoura99ac622021-10-25 16:21:00 -0700145// 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.
Colin Cross35f79c32022-01-27 15:18:52 -0800159func (l byEdge) Len() int { return len(l) }
Bob Badoura99ac622021-10-25 16:21:00 -0700160
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
Bob Badour9ee7d032021-10-25 16:51:48 -0700174// 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 }
Colin Cross35f79c32022-01-27 15:18:52 -0800194 if len(e.annotations) != len(other.annotations) {
Bob Badour9ee7d032021-10-25 16:51:48 -0700195 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 }
Bob Badourdc62de42022-10-12 20:10:17 -0700223 fs := make(testfs.TestFS)
Bob Badour9ee7d032021-10-25 16:51:48 -0700224 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
Bob Badour103eb0f2022-01-10 13:50:57 -0800239// 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}
Bob Badour9ee7d032021-10-25 16:51:48 -0700254
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
Bob Badour103eb0f2022-01-10 13:50:57 -0800280// act describes test data resolution actions to define test action sets.
281type act struct {
Bob Badoura6ee6d52022-12-16 13:50:41 -0800282 actsOn, condition string
Bob Badour103eb0f2022-01-10 13:50:57 -0800283}
284
285// String returns a human-readable string representing the test action.
286func (a act) String() string {
Bob Badoura6ee6d52022-12-16 13:50:41 -0800287 return fmt.Sprintf("%s{%s}", a.actsOn, a.condition)
Bob Badour103eb0f2022-01-10 13:50:57 -0800288}
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)
Bob Badoura6ee6d52022-12-16 13:50:41 -0800295 cs := newTestConditionSet(strings.Split(a.condition, "|"))
Bob Badour103eb0f2022-01-10 13:50:57 -0800296 as[actsOn] = cs
297 }
298 return as
299}
300
Bob Badoura99ac622021-10-25 16:21:00 -0700301// res describes test data resolutions to define test resolution sets.
302type res struct {
Bob Badoura6ee6d52022-12-16 13:50:41 -0800303 attachesTo, actsOn, condition string
Bob Badoura99ac622021-10-25 16:21:00 -0700304}
305
306// toResolutionSet converts a list of res test data into a test resolution set.
Bob Badour103eb0f2022-01-10 13:50:57 -0800307func toResolutionSet(lg *LicenseGraph, data []res) ResolutionSet {
308 rmap := make(ResolutionSet)
Bob Badoura99ac622021-10-25 16:21:00 -0700309 for _, r := range data {
310 attachesTo := newTestNode(lg, r.attachesTo)
311 actsOn := newTestNode(lg, r.actsOn)
Bob Badoura99ac622021-10-25 16:21:00 -0700312 if _, ok := rmap[attachesTo]; !ok {
Bob Badour103eb0f2022-01-10 13:50:57 -0800313 rmap[attachesTo] = make(ActionSet)
Bob Badoura99ac622021-10-25 16:21:00 -0700314 }
Bob Badoura6ee6d52022-12-16 13:50:41 -0800315 cs := newTestConditionSet(strings.Split(r.condition, "|"))
Bob Badour103eb0f2022-01-10 13:50:57 -0800316 rmap[attachesTo][actsOn] |= cs
Bob Badoura99ac622021-10-25 16:21:00 -0700317 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800318 return rmap
Bob Badoura99ac622021-10-25 16:21:00 -0700319}
320
Bob Badour103eb0f2022-01-10 13:50:57 -0800321// 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.
Colin Cross35f79c32022-01-27 15:18:52 -0800355func (l actionList) Len() int { return len(l) }
Bob Badour103eb0f2022-01-10 13:50:57 -0800356
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.
Bob Badour9ee7d032021-10-25 16:51:48 -0700405type confl struct {
406 sourceNode, share, privacy string
407}
408
Bob Badour103eb0f2022-01-10 13:50:57 -0800409// toConflictList converts confl test data into an array of
410// SourceSharePrivacyConflict for comparison in a test.
Bob Badour9ee7d032021-10-25 16:51:48 -0700411func toConflictList(lg *LicenseGraph, data []confl) []SourceSharePrivacyConflict {
412 result := make([]SourceSharePrivacyConflict, 0, len(data))
413 for _, c := range data {
414 fields := strings.Split(c.share, ":")
Bob Badour9ee7d032021-10-25 16:51:48 -0700415 cshare := fields[1]
416 fields = strings.Split(c.privacy, ":")
Bob Badour9ee7d032021-10-25 16:51:48 -0700417 cprivacy := fields[1]
418 result = append(result, SourceSharePrivacyConflict{
Colin Cross35f79c32022-01-27 15:18:52 -0800419 newTestNode(lg, c.sourceNode),
Bob Badoura6ee6d52022-12-16 13:50:41 -0800420 newTestCondition(cshare),
421 newTestCondition(cprivacy),
Colin Cross35f79c32022-01-27 15:18:52 -0800422 })
Bob Badour9ee7d032021-10-25 16:51:48 -0700423 }
424 return result
425}
426
Bob Badoura99ac622021-10-25 16:21:00 -0700427// checkSameActions compares an actual action set to an expected action set for a test.
Bob Badour103eb0f2022-01-10 13:50:57 -0800428func checkSameActions(lg *LicenseGraph, asActual, asExpected ActionSet, t *testing.T) {
429 rsActual := make(ResolutionSet)
430 rsExpected := make(ResolutionSet)
Bob Badoura99ac622021-10-25 16:21:00 -0700431 testNode := newTestNode(lg, "test")
Bob Badour103eb0f2022-01-10 13:50:57 -0800432 rsActual[testNode] = asActual
433 rsExpected[testNode] = asExpected
434 checkSame(rsActual, rsExpected, t)
Bob Badoura99ac622021-10-25 16:21:00 -0700435}
436
437// checkSame compares an actual resolution set to an expected resolution set for a test.
Bob Badour103eb0f2022-01-10 13:50:57 -0800438func 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
Bob Badoura99ac622021-10-25 16:21:00 -0700445 expectedTargets := rsExpected.AttachesTo()
446 sort.Sort(expectedTargets)
Bob Badour103eb0f2022-01-10 13:50:57 -0800447
448 t.Logf("actual targets: %s", actualTargets.String())
449 t.Logf("expected targets: %s", expectedTargets.String())
450
Bob Badoura99ac622021-10-25 16:21:00 -0700451 for _, target := range expectedTargets {
452 if !rsActual.AttachesToTarget(target) {
Bob Badour103eb0f2022-01-10 13:50:57 -0800453 t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false, want true", target.name)
Bob Badoura99ac622021-10-25 16:21:00 -0700454 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) {
Bob Badour103eb0f2022-01-10 13:50:57 -0800461 t.Errorf("unexpected number of resolutions attach to %q: %d elements, %d elements",
462 target.name, len(actualRl), len(expectedRl))
Bob Badoura99ac622021-10-25 16:21:00 -0700463 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 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800471 expectedConditions := expectedRl[i].Resolves()
472 actualConditions := actualRl[i].Resolves()
473 if expectedConditions != actualConditions {
Bob Badour10f5c482022-09-20 21:44:17 -0700474 t.Errorf("unexpected conditions apply to %q acting on %q: got %#v with names %s, want %#v with names %s",
Bob Badoura99ac622021-10-25 16:21:00 -0700475 target.name, expectedRl[i].actsOn.name,
Bob Badour103eb0f2022-01-10 13:50:57 -0800476 actualConditions, actualConditions.Names(),
477 expectedConditions, expectedConditions.Names())
Bob Badoura99ac622021-10-25 16:21:00 -0700478 continue
479 }
Bob Badoura99ac622021-10-25 16:21:00 -0700480 }
481
482 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800483 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
Bob Badoura99ac622021-10-25 16:21:00 -0700507 actualTargets := rsActual.AttachesTo()
508 sort.Sort(actualTargets)
Bob Badour103eb0f2022-01-10 13:50:57 -0800509
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) {
Bob Badour10f5c482022-09-20 21:44:17 -0700539 t.Errorf("expected conditions missing from %q acting on %q: got %#v with names %s, want %#v with names %s",
Bob Badour103eb0f2022-01-10 13:50:57 -0800540 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 {
Bob Badoura99ac622021-10-25 16:21:00 -0700549 if !rsExpected.AttachesToTarget(target) {
Bob Badour103eb0f2022-01-10 13:50:57 -0800550 t.Errorf("unexpected extra target: got expected.AttachesTo(%q) is false, want true", target.name)
Bob Badoura99ac622021-10-25 16:21:00 -0700551 }
552 }
553}