blob: eac0680486741c26cf906471bad3d55f67914eaa [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 "sort"
19 "strings"
20 "testing"
21)
22
23type byName map[string][]string
24
25func (bn byName) checkPublic(ls *LicenseConditionSet, t *testing.T) {
26 for names, expected := range bn {
27 name := ConditionNames(strings.Split(names, ":"))
28 if ls.HasAnyByName(name) {
29 if len(expected) == 0 {
30 t.Errorf("unexpected LicenseConditionSet.HasAnyByName(%q): got true, want false", name)
31 }
32 } else {
33 if len(expected) != 0 {
34 t.Errorf("unexpected LicenseConditionSet.HasAnyByName(%q): got false, want true", name)
35 }
36 }
37 if len(expected) != ls.CountByName(name) {
38 t.Errorf("unexpected LicenseConditionSet.CountByName(%q): got %d, want %d", name, ls.CountByName(name), len(expected))
39 }
40 byName := ls.ByName(name).AsList()
41 if len(expected) != len(byName) {
42 t.Errorf("unexpected LicenseConditionSet.ByName(%q): got %v, want %v", name, byName, expected)
43 } else {
44 sort.Strings(expected)
45 actual := make([]string, 0, len(byName))
46 for _, lc := range byName {
47 actual = append(actual, lc.Origin().Name())
48 }
49 sort.Strings(actual)
50 for i := 0; i < len(expected); i++ {
51 if expected[i] != actual[i] {
52 t.Errorf("unexpected LicenseConditionSet.ByName(%q) index %d in %v: got %s, want %s", name, i, actual, actual[i], expected[i])
53 }
54 }
55 }
56 }
57}
58
59type byOrigin map[string][]string
60
61func (bo byOrigin) checkPublic(lg *LicenseGraph, ls *LicenseConditionSet, t *testing.T) {
62 expectedCount := 0
63 for origin, expected := range bo {
64 expectedCount += len(expected)
65 onode := newTestNode(lg, origin)
66 if ls.HasAnyByOrigin(onode) {
67 if len(expected) == 0 {
68 t.Errorf("unexpected LicenseConditionSet.HasAnyByOrigin(%q): got true, want false", origin)
69 }
70 } else {
71 if len(expected) != 0 {
72 t.Errorf("unexpected LicenseConditionSet.HasAnyByOrigin(%q): got false, want true", origin)
73 }
74 }
75 if len(expected) != ls.CountByOrigin(onode) {
76 t.Errorf("unexpected LicenseConditionSet.CountByOrigin(%q): got %d, want %d", origin, ls.CountByOrigin(onode), len(expected))
77 }
78 byOrigin := ls.ByOrigin(onode).AsList()
79 if len(expected) != len(byOrigin) {
80 t.Errorf("unexpected LicenseConditionSet.ByOrigin(%q): got %v, want %v", origin, byOrigin, expected)
81 } else {
82 sort.Strings(expected)
83 actual := make([]string, 0, len(byOrigin))
84 for _, lc := range byOrigin {
85 actual = append(actual, lc.Name())
86 }
87 sort.Strings(actual)
88 for i := 0; i < len(expected); i++ {
89 if expected[i] != actual[i] {
90 t.Errorf("unexpected LicenseConditionSet.ByOrigin(%q) index %d in %v: got %s, want %s", origin, i, actual, actual[i], expected[i])
91 }
92 }
93 }
94 }
95 if expectedCount != ls.Count() {
96 t.Errorf("unexpected LicenseConditionSet.Count(): got %d, want %d", ls.Count(), expectedCount)
97 }
98 if ls.IsEmpty() {
99 if expectedCount != 0 {
100 t.Errorf("unexpected LicenseConditionSet.IsEmpty(): got true, want false")
101 }
102 } else {
103 if expectedCount == 0 {
104 t.Errorf("unexpected LicenseConditionSet.IsEmpty(): got false, want true")
105 }
106 }
107}
108
109func TestConditionSet(t *testing.T) {
110 tests := []struct {
111 name string
112 conditions map[string][]string
113 add map[string][]string
114 byName map[string][]string
115 byOrigin map[string][]string
116 }{
117 {
118 name: "empty",
119 conditions: map[string][]string{},
120 add: map[string][]string{},
121 byName: map[string][]string{
122 "notice": []string{},
123 "restricted": []string{},
124 },
125 byOrigin: map[string][]string{
126 "bin1": []string{},
127 "lib1": []string{},
128 "bin2": []string{},
129 "lib2": []string{},
130 },
131 },
132 {
133 name: "noticeonly",
134 conditions: map[string][]string{
135 "notice": []string{"bin1", "lib1"},
136 },
137 byName: map[string][]string{
138 "notice": []string{"bin1", "lib1"},
139 "restricted": []string{},
140 },
141 byOrigin: map[string][]string{
142 "bin1": []string{"notice"},
143 "lib1": []string{"notice"},
144 "bin2": []string{},
145 "lib2": []string{},
146 },
147 },
148 {
149 name: "noticeonlyadded",
150 conditions: map[string][]string{
151 "notice": []string{"bin1", "lib1"},
152 },
153 add: map[string][]string{
154 "notice": []string{"bin1", "bin2"},
155 },
156 byName: map[string][]string{
157 "notice": []string{"bin1", "bin2", "lib1"},
158 "restricted": []string{},
159 },
160 byOrigin: map[string][]string{
161 "bin1": []string{"notice"},
162 "lib1": []string{"notice"},
163 "bin2": []string{"notice"},
164 "lib2": []string{},
165 },
166 },
167 {
168 name: "everything",
169 conditions: map[string][]string{
170 "notice": []string{"bin1", "bin2", "lib1", "lib2"},
171 "reciprocal": []string{"bin1", "bin2", "lib1", "lib2"},
172 "restricted": []string{"bin1", "bin2", "lib1", "lib2"},
173 "by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
174 },
175 add: map[string][]string{
176 "notice": []string{"bin1", "bin2", "lib1", "lib2"},
177 "reciprocal": []string{"bin1", "bin2", "lib1", "lib2"},
178 "restricted": []string{"bin1", "bin2", "lib1", "lib2"},
179 "by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
180 },
181 byName: map[string][]string{
182 "permissive": []string{},
183 "notice": []string{"bin1", "bin2", "lib1", "lib2"},
184 "reciprocal": []string{"bin1", "bin2", "lib1", "lib2"},
185 "restricted": []string{"bin1", "bin2", "lib1", "lib2"},
186 "by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
187 },
188 byOrigin: map[string][]string{
189 "bin1": []string{"notice", "reciprocal", "restricted", "by_exception_only"},
190 "bin2": []string{"notice", "reciprocal", "restricted", "by_exception_only"},
191 "lib1": []string{"notice", "reciprocal", "restricted", "by_exception_only"},
192 "lib2": []string{"notice", "reciprocal", "restricted", "by_exception_only"},
193 "other": []string{},
194 },
195 },
196 {
197 name: "allbutoneeach",
198 conditions: map[string][]string{
199 "notice": []string{"bin2", "lib1", "lib2"},
200 "reciprocal": []string{"bin1", "lib1", "lib2"},
201 "restricted": []string{"bin1", "bin2", "lib2"},
202 "by_exception_only": []string{"bin1", "bin2", "lib1"},
203 },
204 byName: map[string][]string{
205 "permissive": []string{},
206 "notice": []string{"bin2", "lib1", "lib2"},
207 "reciprocal": []string{"bin1", "lib1", "lib2"},
208 "restricted": []string{"bin1", "bin2", "lib2"},
209 "by_exception_only": []string{"bin1", "bin2", "lib1"},
210 },
211 byOrigin: map[string][]string{
212 "bin1": []string{"reciprocal", "restricted", "by_exception_only"},
213 "bin2": []string{"notice", "restricted", "by_exception_only"},
214 "lib1": []string{"notice", "reciprocal", "by_exception_only"},
215 "lib2": []string{"notice", "reciprocal", "restricted"},
216 "other": []string{},
217 },
218 },
219 {
220 name: "allbutoneeachadded",
221 conditions: map[string][]string{
222 "notice": []string{"bin2", "lib1", "lib2"},
223 "reciprocal": []string{"bin1", "lib1", "lib2"},
224 "restricted": []string{"bin1", "bin2", "lib2"},
225 "by_exception_only": []string{"bin1", "bin2", "lib1"},
226 },
227 add: map[string][]string{
228 "notice": []string{"bin2", "lib1", "lib2"},
229 "reciprocal": []string{"bin1", "lib1", "lib2"},
230 "restricted": []string{"bin1", "bin2", "lib2"},
231 "by_exception_only": []string{"bin1", "bin2", "lib1"},
232 },
233 byName: map[string][]string{
234 "permissive": []string{},
235 "notice": []string{"bin2", "lib1", "lib2"},
236 "reciprocal": []string{"bin1", "lib1", "lib2"},
237 "restricted": []string{"bin1", "bin2", "lib2"},
238 "by_exception_only": []string{"bin1", "bin2", "lib1"},
239 },
240 byOrigin: map[string][]string{
241 "bin1": []string{"reciprocal", "restricted", "by_exception_only"},
242 "bin2": []string{"notice", "restricted", "by_exception_only"},
243 "lib1": []string{"notice", "reciprocal", "by_exception_only"},
244 "lib2": []string{"notice", "reciprocal", "restricted"},
245 "other": []string{},
246 },
247 },
248 {
249 name: "allbutoneeachfilled",
250 conditions: map[string][]string{
251 "notice": []string{"bin2", "lib1", "lib2"},
252 "reciprocal": []string{"bin1", "lib1", "lib2"},
253 "restricted": []string{"bin1", "bin2", "lib2"},
254 "by_exception_only": []string{"bin1", "bin2", "lib1"},
255 },
256 add: map[string][]string{
257 "notice": []string{"bin1", "bin2", "lib1"},
258 "reciprocal": []string{"bin1", "bin2", "lib2"},
259 "restricted": []string{"bin1", "lib1", "lib2"},
260 "by_exception_only": []string{"bin2", "lib1", "lib2"},
261 },
262 byName: map[string][]string{
263 "permissive": []string{},
264 "notice": []string{"bin1", "bin2", "lib1", "lib2"},
265 "reciprocal": []string{"bin1", "bin2", "lib1", "lib2"},
266 "restricted": []string{"bin1", "bin2", "lib1", "lib2"},
267 "by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
268 },
269 byOrigin: map[string][]string{
270 "bin1": []string{"notice", "reciprocal", "restricted", "by_exception_only"},
271 "bin2": []string{"notice", "reciprocal", "restricted", "by_exception_only"},
272 "lib1": []string{"notice", "reciprocal", "restricted", "by_exception_only"},
273 "lib2": []string{"notice", "reciprocal", "restricted", "by_exception_only"},
274 "other": []string{},
275 },
276 },
277 {
278 name: "oneeach",
279 conditions: map[string][]string{
280 "notice": []string{"bin1"},
281 "reciprocal": []string{"bin2"},
282 "restricted": []string{"lib1"},
283 "by_exception_only": []string{"lib2"},
284 },
285 byName: map[string][]string{
286 "permissive": []string{},
287 "notice": []string{"bin1"},
288 "reciprocal": []string{"bin2"},
289 "restricted": []string{"lib1"},
290 "by_exception_only": []string{"lib2"},
291 },
292 byOrigin: map[string][]string{
293 "bin1": []string{"notice"},
294 "bin2": []string{"reciprocal"},
295 "lib1": []string{"restricted"},
296 "lib2": []string{"by_exception_only"},
297 "other": []string{},
298 },
299 },
300 {
301 name: "oneeachoverlap",
302 conditions: map[string][]string{
303 "notice": []string{"bin1"},
304 "reciprocal": []string{"bin2"},
305 "restricted": []string{"lib1"},
306 "by_exception_only": []string{"lib2"},
307 },
308 add: map[string][]string{
309 "notice": []string{"lib2"},
310 "reciprocal": []string{"lib1"},
311 "restricted": []string{"bin2"},
312 "by_exception_only": []string{"bin1"},
313 },
314 byName: map[string][]string{
315 "permissive": []string{},
316 "notice": []string{"bin1", "lib2"},
317 "reciprocal": []string{"bin2", "lib1"},
318 "restricted": []string{"bin2", "lib1"},
319 "by_exception_only": []string{"bin1", "lib2"},
320 },
321 byOrigin: map[string][]string{
322 "bin1": []string{"by_exception_only", "notice"},
323 "bin2": []string{"reciprocal", "restricted"},
324 "lib1": []string{"reciprocal", "restricted"},
325 "lib2": []string{"by_exception_only", "notice"},
326 "other": []string{},
327 },
328 },
329 {
330 name: "oneeachadded",
331 conditions: map[string][]string{
332 "notice": []string{"bin1"},
333 "reciprocal": []string{"bin2"},
334 "restricted": []string{"lib1"},
335 "by_exception_only": []string{"lib2"},
336 },
337 add: map[string][]string{
338 "notice": []string{"bin1"},
339 "reciprocal": []string{"bin2"},
340 "restricted": []string{"lib1"},
341 "by_exception_only": []string{"lib2"},
342 },
343 byName: map[string][]string{
344 "permissive": []string{},
345 "notice": []string{"bin1"},
346 "reciprocal": []string{"bin2"},
347 "restricted": []string{"lib1"},
348 "by_exception_only": []string{"lib2"},
349 },
350 byOrigin: map[string][]string{
351 "bin1": []string{"notice"},
352 "bin2": []string{"reciprocal"},
353 "lib1": []string{"restricted"},
354 "lib2": []string{"by_exception_only"},
355 "other": []string{},
356 },
357 },
358 }
359 for _, tt := range tests {
360 testPublicInterface := func(lg *LicenseGraph, cs *LicenseConditionSet, t *testing.T) {
361 byName(tt.byName).checkPublic(cs, t)
362 byOrigin(tt.byOrigin).checkPublic(lg, cs, t)
363 }
364 t.Run(tt.name+"_public_interface", func(t *testing.T) {
365 lg := newLicenseGraph()
366 cs := NewLicenseConditionSet(toConditionList(lg, tt.conditions)...)
367 if tt.add != nil {
368 cs.Add(toConditionList(lg, tt.add)...)
369 }
370 testPublicInterface(lg, cs, t)
371 })
372
373 t.Run("Copy() of "+tt.name+"_public_interface", func(t *testing.T) {
374 lg := newLicenseGraph()
375 cs := NewLicenseConditionSet(toConditionList(lg, tt.conditions)...)
376 if tt.add != nil {
377 cs.Add(toConditionList(lg, tt.add)...)
378 }
379 testPublicInterface(lg, cs.Copy(), t)
380 })
381
382 testPrivateInterface := func(lg *LicenseGraph, cs *LicenseConditionSet, t *testing.T) {
383 slist := make([]string, 0, cs.Count())
384 for origin, expected := range tt.byOrigin {
385 for _, name := range expected {
386 slist = append(slist, origin+";"+name)
387 }
388 }
389 actualSlist := cs.asStringList(";")
390 if len(slist) != len(actualSlist) {
391 t.Errorf("unexpected LicenseConditionSet.asStringList(\";\"): got %v, want %v", actualSlist, slist)
392 } else {
393 sort.Strings(slist)
394 sort.Strings(actualSlist)
395 for i := 0; i < len(slist); i++ {
396 if slist[i] != actualSlist[i] {
397 t.Errorf("unexpected LicenseConditionSet.asStringList(\";\") index %d in %v: got %s, want %s", i, actualSlist, actualSlist[i], slist[i])
398 }
399 }
400 }
401 }
402
403 t.Run(tt.name+"_private_list_interface", func(t *testing.T) {
404 lg := newLicenseGraph()
405 cs := newLicenseConditionSet()
406 for name, origins := range tt.conditions {
407 for _, origin := range origins {
408 cs.add(newTestNode(lg, origin), name)
409 }
410 }
411 if tt.add != nil {
412 cs.Add(toConditionList(lg, tt.add)...)
413 }
414 testPrivateInterface(lg, cs, t)
415 })
416
417 t.Run(tt.name+"_private_set_interface", func(t *testing.T) {
418 lg := newLicenseGraph()
419 cs := newLicenseConditionSet()
420 for name, origins := range tt.conditions {
421 for _, origin := range origins {
422 cs.add(newTestNode(lg, origin), name)
423 }
424 }
425 if tt.add != nil {
426 other := newLicenseConditionSet()
427 for name, origins := range tt.add {
428 for _, origin := range origins {
429 other.add(newTestNode(lg, origin), name)
430 }
431 }
432 cs.AddSet(other)
433 }
434 testPrivateInterface(lg, cs, t)
435 })
436 }
437}
438
439func TestConditionSet_Removals(t *testing.T) {
440 tests := []struct {
441 name string
442 conditions map[string][]string
443 removeByName []ConditionNames
444 removeSet map[string][]string
445 byName map[string][]string
446 byOrigin map[string][]string
447 }{
448 {
449 name: "emptybyname",
450 conditions: map[string][]string{},
451 removeByName: []ConditionNames{{"reciprocal", "restricted"}},
452 byName: map[string][]string{
453 "notice": []string{},
454 "restricted": []string{},
455 },
456 byOrigin: map[string][]string{
457 "bin1": []string{},
458 "lib1": []string{},
459 "bin2": []string{},
460 "lib2": []string{},
461 },
462 },
463 {
464 name: "emptybyset",
465 conditions: map[string][]string{},
466 removeSet: map[string][]string{
467 "notice": []string{"bin1", "bin2"},
468 "restricted": []string{"lib1", "lib2"},
469 },
470 byName: map[string][]string{
471 "notice": []string{},
472 "restricted": []string{},
473 },
474 byOrigin: map[string][]string{
475 "bin1": []string{},
476 "lib1": []string{},
477 "bin2": []string{},
478 "lib2": []string{},
479 },
480 },
481 {
482 name: "everythingremovenone",
483 conditions: map[string][]string{
484 "notice": []string{"bin1", "bin2", "lib1", "lib2"},
485 "reciprocal": []string{"bin1", "bin2", "lib1", "lib2"},
486 "restricted": []string{"bin1", "bin2", "lib1", "lib2"},
487 "by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
488 },
489 removeByName: []ConditionNames{{"permissive", "unencumbered"}},
490 removeSet: map[string][]string{
491 "notice": []string{"apk1"},
492 },
493 byName: map[string][]string{
494 "permissive": []string{},
495 "notice": []string{"bin1", "bin2", "lib1", "lib2"},
496 "reciprocal": []string{"bin1", "bin2", "lib1", "lib2"},
497 "restricted": []string{"bin1", "bin2", "lib1", "lib2"},
498 "by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
499 },
500 byOrigin: map[string][]string{
501 "bin1": []string{"notice", "reciprocal", "restricted", "by_exception_only"},
502 "bin2": []string{"notice", "reciprocal", "restricted", "by_exception_only"},
503 "lib1": []string{"notice", "reciprocal", "restricted", "by_exception_only"},
504 "lib2": []string{"notice", "reciprocal", "restricted", "by_exception_only"},
505 "other": []string{},
506 },
507 },
508 {
509 name: "everythingremovesome",
510 conditions: map[string][]string{
511 "notice": []string{"bin1", "bin2", "lib1", "lib2"},
512 "reciprocal": []string{"bin1", "bin2", "lib1", "lib2"},
513 "restricted": []string{"bin1", "bin2", "lib1", "lib2"},
514 "by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
515 },
516 removeByName: []ConditionNames{{"restricted", "by_exception_only"}},
517 removeSet: map[string][]string{
518 "notice": []string{"lib1"},
519 },
520 byName: map[string][]string{
521 "permissive": []string{},
522 "notice": []string{"bin1", "bin2", "lib2"},
523 "reciprocal": []string{"bin1", "bin2", "lib1", "lib2"},
524 "restricted": []string{},
525 "by_exception_only": []string{},
526 },
527 byOrigin: map[string][]string{
528 "bin1": []string{"notice", "reciprocal"},
529 "bin2": []string{"notice", "reciprocal"},
530 "lib1": []string{"reciprocal"},
531 "lib2": []string{"notice", "reciprocal"},
532 "other": []string{},
533 },
534 },
535 {
536 name: "everythingremoveall",
537 conditions: map[string][]string{
538 "notice": []string{"bin1", "bin2", "lib1", "lib2"},
539 "reciprocal": []string{"bin1", "bin2", "lib1", "lib2"},
540 "restricted": []string{"bin1", "bin2", "lib1", "lib2"},
541 "by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
542 },
543 removeByName: []ConditionNames{{"restricted", "by_exception_only"}},
544 removeSet: map[string][]string{
545 "notice": []string{"bin1", "bin2", "lib1", "lib2"},
546 "reciprocal": []string{"bin1", "bin2", "lib1", "lib2"},
547 "restricted": []string{"bin1"},
548 },
549 byName: map[string][]string{
550 "permissive": []string{},
551 "notice": []string{},
552 "reciprocal": []string{},
553 "restricted": []string{},
554 "by_exception_only": []string{},
555 },
556 byOrigin: map[string][]string{
557 "bin1": []string{},
558 "bin2": []string{},
559 "lib1": []string{},
560 "lib2": []string{},
561 "other": []string{},
562 },
563 },
564 }
565 for _, tt := range tests {
566 t.Run(tt.name, func(t *testing.T) {
567 lg := newLicenseGraph()
568 cs := newLicenseConditionSet()
569 for name, origins := range tt.conditions {
570 for _, origin := range origins {
571 cs.add(newTestNode(lg, origin), name)
572 }
573 }
574 if tt.removeByName != nil {
575 cs.RemoveAllByName(tt.removeByName...)
576 }
577 if tt.removeSet != nil {
578 other := newLicenseConditionSet()
579 for name, origins := range tt.removeSet {
580 for _, origin := range origins {
581 other.add(newTestNode(lg, origin), name)
582 }
583 }
584 cs.RemoveSet(other)
585 }
586 byName(tt.byName).checkPublic(cs, t)
587 byOrigin(tt.byOrigin).checkPublic(lg, cs, t)
588 })
589 }
590}