blob: 6c50d3e761c73319dd4c82b446af814e5086b5f2 [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"
60license_conditions: "restricted"
61`
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
124// newTestCondition constructs a test license condition in the license graph.
125func newTestCondition(lg *LicenseGraph, targetName string, conditionName string) LicenseCondition {
126 tn := newTestNode(lg, targetName)
127 cl := LicenseConditionSetFromNames(tn, conditionName).AsList()
128 if len(cl) == 0 {
129 panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName))
130 } else if len(cl) != 1 {
131 panic(fmt.Errorf("unexpected multiple conditions from condition name: %q: got %d, want 1", conditionName, len(cl)))
132 }
133 lc := cl[0]
134 tn.licenseConditions = tn.licenseConditions.Plus(lc)
135 return lc
136}
137
138// newTestConditionSet constructs a test license condition set in the license graph.
139func newTestConditionSet(lg *LicenseGraph, targetName string, conditionName []string) LicenseConditionSet {
140 tn := newTestNode(lg, targetName)
141 cs := LicenseConditionSetFromNames(tn, conditionName...)
142 if cs.IsEmpty() {
143 panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName))
144 }
145 tn.licenseConditions = tn.licenseConditions.Union(cs)
146 return cs
Bob Badoura99ac622021-10-25 16:21:00 -0700147}
148
Bob Badoura99ac622021-10-25 16:21:00 -0700149// edge describes test data edges to define test graphs.
150type edge struct {
151 target, dep string
152}
153
154// String returns a string representation of the edge.
155func (e edge) String() string {
156 return e.target + " -> " + e.dep
157}
158
159// byEdge orders edges by target then dep name then annotations.
160type byEdge []edge
161
162// Len returns the count of elements in the slice.
Colin Cross35f79c32022-01-27 15:18:52 -0800163func (l byEdge) Len() int { return len(l) }
Bob Badoura99ac622021-10-25 16:21:00 -0700164
165// Swap rearranges 2 elements of the slice so that each occupies the other's
166// former position.
167func (l byEdge) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
168
169// Less returns true when the `i`th element is lexicographically less than
170// the `j`th element.
171func (l byEdge) Less(i, j int) bool {
172 if l[i].target == l[j].target {
173 return l[i].dep < l[j].dep
174 }
175 return l[i].target < l[j].target
176}
177
Bob Badour9ee7d032021-10-25 16:51:48 -0700178// annotated describes annotated test data edges to define test graphs.
179type annotated struct {
180 target, dep string
181 annotations []string
182}
183
184func (e annotated) String() string {
185 if e.annotations != nil {
186 return e.target + " -> " + e.dep + " [" + strings.Join(e.annotations, ", ") + "]"
187 }
188 return e.target + " -> " + e.dep
189}
190
191func (e annotated) IsEqualTo(other annotated) bool {
192 if e.target != other.target {
193 return false
194 }
195 if e.dep != other.dep {
196 return false
197 }
Colin Cross35f79c32022-01-27 15:18:52 -0800198 if len(e.annotations) != len(other.annotations) {
Bob Badour9ee7d032021-10-25 16:51:48 -0700199 return false
200 }
201 a1 := append([]string{}, e.annotations...)
202 a2 := append([]string{}, other.annotations...)
203 for i := 0; i < len(a1); i++ {
204 if a1[i] != a2[i] {
205 return false
206 }
207 }
208 return true
209}
210
211// toGraph converts a list of roots and a list of annotated edges into a test license graph.
212func toGraph(stderr io.Writer, roots []string, edges []annotated) (*LicenseGraph, error) {
213 deps := make(map[string][]annotated)
214 for _, root := range roots {
215 deps[root] = []annotated{}
216 }
217 for _, edge := range edges {
218 if prev, ok := deps[edge.target]; ok {
219 deps[edge.target] = append(prev, edge)
220 } else {
221 deps[edge.target] = []annotated{edge}
222 }
223 if _, ok := deps[edge.dep]; !ok {
224 deps[edge.dep] = []annotated{}
225 }
226 }
Bob Badourdc62de42022-10-12 20:10:17 -0700227 fs := make(testfs.TestFS)
Bob Badour9ee7d032021-10-25 16:51:48 -0700228 for file, edges := range deps {
229 body := meta[file]
230 for _, edge := range edges {
231 body += fmt.Sprintf("deps: {\n file: %q\n", edge.dep)
232 for _, ann := range edge.annotations {
233 body += fmt.Sprintf(" annotations: %q\n", ann)
234 }
235 body += "}\n"
236 }
237 fs[file] = []byte(body)
238 }
239
240 return ReadLicenseGraph(&fs, stderr, roots)
241}
242
Bob Badour103eb0f2022-01-10 13:50:57 -0800243// logGraph outputs a representation of the graph to a test log.
244func logGraph(lg *LicenseGraph, t *testing.T) {
245 t.Logf("license graph:")
246 t.Logf(" targets:")
247 for _, target := range lg.Targets() {
248 t.Logf(" %s%s in package %q", target.Name(), target.LicenseConditions().String(), target.PackageName())
249 }
250 t.Logf(" /targets")
251 t.Logf(" edges:")
252 for _, edge := range lg.Edges() {
253 t.Logf(" %s", edge.String())
254 }
255 t.Logf(" /edges")
256 t.Logf("/license graph")
257}
Bob Badour9ee7d032021-10-25 16:51:48 -0700258
259// byAnnotatedEdge orders edges by target then dep name then annotations.
260type byAnnotatedEdge []annotated
261
262func (l byAnnotatedEdge) Len() int { return len(l) }
263func (l byAnnotatedEdge) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
264func (l byAnnotatedEdge) Less(i, j int) bool {
265 if l[i].target == l[j].target {
266 if l[i].dep == l[j].dep {
267 ai := append([]string{}, l[i].annotations...)
268 aj := append([]string{}, l[j].annotations...)
269 sort.Strings(ai)
270 sort.Strings(aj)
271 for k := 0; k < len(ai) && k < len(aj); k++ {
272 if ai[k] == aj[k] {
273 continue
274 }
275 return ai[k] < aj[k]
276 }
277 return len(ai) < len(aj)
278 }
279 return l[i].dep < l[j].dep
280 }
281 return l[i].target < l[j].target
282}
283
Bob Badour103eb0f2022-01-10 13:50:57 -0800284// act describes test data resolution actions to define test action sets.
285type act struct {
286 actsOn, origin, condition string
287}
288
289// String returns a human-readable string representing the test action.
290func (a act) String() string {
291 return fmt.Sprintf("%s{%s:%s}", a.actsOn, a.origin, a.condition)
292}
293
294// toActionSet converts a list of act test data into a test action set.
295func toActionSet(lg *LicenseGraph, data []act) ActionSet {
296 as := make(ActionSet)
297 for _, a := range data {
298 actsOn := newTestNode(lg, a.actsOn)
299 cs := newTestConditionSet(lg, a.origin, strings.Split(a.condition, "|"))
300 as[actsOn] = cs
301 }
302 return as
303}
304
Bob Badoura99ac622021-10-25 16:21:00 -0700305// res describes test data resolutions to define test resolution sets.
306type res struct {
307 attachesTo, actsOn, origin, condition string
308}
309
310// toResolutionSet converts a list of res test data into a test resolution set.
Bob Badour103eb0f2022-01-10 13:50:57 -0800311func toResolutionSet(lg *LicenseGraph, data []res) ResolutionSet {
312 rmap := make(ResolutionSet)
Bob Badoura99ac622021-10-25 16:21:00 -0700313 for _, r := range data {
314 attachesTo := newTestNode(lg, r.attachesTo)
315 actsOn := newTestNode(lg, r.actsOn)
Bob Badoura99ac622021-10-25 16:21:00 -0700316 if _, ok := rmap[attachesTo]; !ok {
Bob Badour103eb0f2022-01-10 13:50:57 -0800317 rmap[attachesTo] = make(ActionSet)
Bob Badoura99ac622021-10-25 16:21:00 -0700318 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800319 cs := newTestConditionSet(lg, r.origin, strings.Split(r.condition, ":"))
320 rmap[attachesTo][actsOn] |= cs
Bob Badoura99ac622021-10-25 16:21:00 -0700321 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800322 return rmap
Bob Badoura99ac622021-10-25 16:21:00 -0700323}
324
Bob Badour103eb0f2022-01-10 13:50:57 -0800325// tcond associates a target name with '|' separated string conditions.
326type tcond struct {
327 target, conditions string
328}
329
330// action represents a single element of an ActionSet for testing.
331type action struct {
332 target *TargetNode
333 cs LicenseConditionSet
334}
335
336// String returns a human-readable string representation of the action.
337func (a action) String() string {
338 return fmt.Sprintf("%s%s", a.target.Name(), a.cs.String())
339}
340
341// actionList represents an array of actions and a total order defined by
342// target name followed by license condition set.
343type actionList []action
344
345// String returns a human-readable string representation of the list.
346func (l actionList) String() string {
347 var sb strings.Builder
348 fmt.Fprintf(&sb, "[")
349 sep := ""
350 for _, a := range l {
351 fmt.Fprintf(&sb, "%s%s", sep, a.String())
352 sep = ", "
353 }
354 fmt.Fprintf(&sb, "]")
355 return sb.String()
356}
357
358// Len returns the count of elements in the slice.
Colin Cross35f79c32022-01-27 15:18:52 -0800359func (l actionList) Len() int { return len(l) }
Bob Badour103eb0f2022-01-10 13:50:57 -0800360
361// Swap rearranges 2 elements of the slice so that each occupies the other's
362// former position.
363func (l actionList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
364
365// Less returns true when the `i`th element is lexicographically less than
366// the `j`th element.
367func (l actionList) Less(i, j int) bool {
368 if l[i].target == l[j].target {
369 return l[i].cs < l[j].cs
370 }
371 return l[i].target.Name() < l[j].target.Name()
372}
373
374// asActionList represents the resolved license conditions in a license graph
375// as an actionList for comparison in a test.
376func asActionList(lg *LicenseGraph) actionList {
377 result := make(actionList, 0, len(lg.targets))
378 for _, target := range lg.targets {
379 cs := target.resolution
380 if cs.IsEmpty() {
381 continue
382 }
383 result = append(result, action{target, cs})
384 }
385 return result
386}
387
388// toActionList converts an array of tcond into an actionList for comparison
389// in a test.
390func toActionList(lg *LicenseGraph, actions []tcond) actionList {
391 result := make(actionList, 0, len(actions))
392 for _, actn := range actions {
393 target := newTestNode(lg, actn.target)
394 cs := NewLicenseConditionSet()
395 for _, name := range strings.Split(actn.conditions, "|") {
396 lc, ok := RecognizedConditionNames[name]
397 if !ok {
398 panic(fmt.Errorf("Unrecognized test condition name: %q", name))
399 }
400 cs = cs.Plus(lc)
401 }
402 result = append(result, action{target, cs})
403 }
404 return result
405}
406
407// confl defines test data for a SourceSharePrivacyConflict as a target name,
408// source condition name, privacy condition name triple.
Bob Badour9ee7d032021-10-25 16:51:48 -0700409type confl struct {
410 sourceNode, share, privacy string
411}
412
Bob Badour103eb0f2022-01-10 13:50:57 -0800413// toConflictList converts confl test data into an array of
414// SourceSharePrivacyConflict for comparison in a test.
Bob Badour9ee7d032021-10-25 16:51:48 -0700415func toConflictList(lg *LicenseGraph, data []confl) []SourceSharePrivacyConflict {
416 result := make([]SourceSharePrivacyConflict, 0, len(data))
417 for _, c := range data {
418 fields := strings.Split(c.share, ":")
419 oshare := fields[0]
420 cshare := fields[1]
421 fields = strings.Split(c.privacy, ":")
422 oprivacy := fields[0]
423 cprivacy := fields[1]
424 result = append(result, SourceSharePrivacyConflict{
Colin Cross35f79c32022-01-27 15:18:52 -0800425 newTestNode(lg, c.sourceNode),
426 newTestCondition(lg, oshare, cshare),
427 newTestCondition(lg, oprivacy, cprivacy),
428 })
Bob Badour9ee7d032021-10-25 16:51:48 -0700429 }
430 return result
431}
432
Bob Badoura99ac622021-10-25 16:21:00 -0700433// checkSameActions compares an actual action set to an expected action set for a test.
Bob Badour103eb0f2022-01-10 13:50:57 -0800434func checkSameActions(lg *LicenseGraph, asActual, asExpected ActionSet, t *testing.T) {
435 rsActual := make(ResolutionSet)
436 rsExpected := make(ResolutionSet)
Bob Badoura99ac622021-10-25 16:21:00 -0700437 testNode := newTestNode(lg, "test")
Bob Badour103eb0f2022-01-10 13:50:57 -0800438 rsActual[testNode] = asActual
439 rsExpected[testNode] = asExpected
440 checkSame(rsActual, rsExpected, t)
Bob Badoura99ac622021-10-25 16:21:00 -0700441}
442
443// checkSame compares an actual resolution set to an expected resolution set for a test.
Bob Badour103eb0f2022-01-10 13:50:57 -0800444func checkSame(rsActual, rsExpected ResolutionSet, t *testing.T) {
445 t.Logf("actual resolution set: %s", rsActual.String())
446 t.Logf("expected resolution set: %s", rsExpected.String())
447
448 actualTargets := rsActual.AttachesTo()
449 sort.Sort(actualTargets)
450
Bob Badoura99ac622021-10-25 16:21:00 -0700451 expectedTargets := rsExpected.AttachesTo()
452 sort.Sort(expectedTargets)
Bob Badour103eb0f2022-01-10 13:50:57 -0800453
454 t.Logf("actual targets: %s", actualTargets.String())
455 t.Logf("expected targets: %s", expectedTargets.String())
456
Bob Badoura99ac622021-10-25 16:21:00 -0700457 for _, target := range expectedTargets {
458 if !rsActual.AttachesToTarget(target) {
Bob Badour103eb0f2022-01-10 13:50:57 -0800459 t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false, want true", target.name)
Bob Badoura99ac622021-10-25 16:21:00 -0700460 continue
461 }
462 expectedRl := rsExpected.Resolutions(target)
463 sort.Sort(expectedRl)
464 actualRl := rsActual.Resolutions(target)
465 sort.Sort(actualRl)
466 if len(expectedRl) != len(actualRl) {
Bob Badour103eb0f2022-01-10 13:50:57 -0800467 t.Errorf("unexpected number of resolutions attach to %q: %d elements, %d elements",
468 target.name, len(actualRl), len(expectedRl))
Bob Badoura99ac622021-10-25 16:21:00 -0700469 continue
470 }
471 for i := 0; i < len(expectedRl); i++ {
472 if expectedRl[i].attachesTo.name != actualRl[i].attachesTo.name || expectedRl[i].actsOn.name != actualRl[i].actsOn.name {
473 t.Errorf("unexpected resolution attaches to %q at index %d: got %s, want %s",
474 target.name, i, actualRl[i].asString(), expectedRl[i].asString())
475 continue
476 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800477 expectedConditions := expectedRl[i].Resolves()
478 actualConditions := actualRl[i].Resolves()
479 if expectedConditions != actualConditions {
Bob Badour10f5c482022-09-20 21:44:17 -0700480 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 -0700481 target.name, expectedRl[i].actsOn.name,
Bob Badour103eb0f2022-01-10 13:50:57 -0800482 actualConditions, actualConditions.Names(),
483 expectedConditions, expectedConditions.Names())
Bob Badoura99ac622021-10-25 16:21:00 -0700484 continue
485 }
Bob Badoura99ac622021-10-25 16:21:00 -0700486 }
487
488 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800489 for _, target := range actualTargets {
490 if !rsExpected.AttachesToTarget(target) {
491 t.Errorf("unexpected extra target: got expected.AttachesTo(%q) is false, want true", target.name)
492 }
493 }
494}
495
496// checkResolvesActions compares an actual action set to an expected action set for a test verifying the actual set
497// resolves all of the expected conditions.
498func checkResolvesActions(lg *LicenseGraph, asActual, asExpected ActionSet, t *testing.T) {
499 rsActual := make(ResolutionSet)
500 rsExpected := make(ResolutionSet)
501 testNode := newTestNode(lg, "test")
502 rsActual[testNode] = asActual
503 rsExpected[testNode] = asExpected
504 checkResolves(rsActual, rsExpected, t)
505}
506
507// checkResolves compares an actual resolution set to an expected resolution set for a test verifying the actual set
508// resolves all of the expected conditions.
509func checkResolves(rsActual, rsExpected ResolutionSet, t *testing.T) {
510 t.Logf("actual resolution set: %s", rsActual.String())
511 t.Logf("expected resolution set: %s", rsExpected.String())
512
Bob Badoura99ac622021-10-25 16:21:00 -0700513 actualTargets := rsActual.AttachesTo()
514 sort.Sort(actualTargets)
Bob Badour103eb0f2022-01-10 13:50:57 -0800515
516 expectedTargets := rsExpected.AttachesTo()
517 sort.Sort(expectedTargets)
518
519 t.Logf("actual targets: %s", actualTargets.String())
520 t.Logf("expected targets: %s", expectedTargets.String())
521
522 for _, target := range expectedTargets {
523 if !rsActual.AttachesToTarget(target) {
524 t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false, want true", target.name)
525 continue
526 }
527 expectedRl := rsExpected.Resolutions(target)
528 sort.Sort(expectedRl)
529 actualRl := rsActual.Resolutions(target)
530 sort.Sort(actualRl)
531 if len(expectedRl) != len(actualRl) {
532 t.Errorf("unexpected number of resolutions attach to %q: %d elements, %d elements",
533 target.name, len(actualRl), len(expectedRl))
534 continue
535 }
536 for i := 0; i < len(expectedRl); i++ {
537 if expectedRl[i].attachesTo.name != actualRl[i].attachesTo.name || expectedRl[i].actsOn.name != actualRl[i].actsOn.name {
538 t.Errorf("unexpected resolution attaches to %q at index %d: got %s, want %s",
539 target.name, i, actualRl[i].asString(), expectedRl[i].asString())
540 continue
541 }
542 expectedConditions := expectedRl[i].Resolves()
543 actualConditions := actualRl[i].Resolves()
544 if expectedConditions != (expectedConditions & actualConditions) {
Bob Badour10f5c482022-09-20 21:44:17 -0700545 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 -0800546 target.name, expectedRl[i].actsOn.name,
547 actualConditions, actualConditions.Names(),
548 expectedConditions, expectedConditions.Names())
549 continue
550 }
551 }
552
553 }
554 for _, target := range actualTargets {
Bob Badoura99ac622021-10-25 16:21:00 -0700555 if !rsExpected.AttachesToTarget(target) {
Bob Badour103eb0f2022-01-10 13:50:57 -0800556 t.Errorf("unexpected extra target: got expected.AttachesTo(%q) is false, want true", target.name)
Bob Badoura99ac622021-10-25 16:21:00 -0700557 }
558 }
559}