• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package compliance
16
17import (
18	"strings"
19	"testing"
20)
21
22func TestConditionSet(t *testing.T) {
23	tests := []struct {
24		name        string
25		conditions  []string
26		plus        *[]string
27		minus       *[]string
28		matchingAny map[string][]string
29		expected    []string
30	}{
31		{
32			name:       "empty",
33			conditions: []string{},
34			plus:       &[]string{},
35			matchingAny: map[string][]string{
36				"notice":                []string{},
37				"restricted":            []string{},
38				"restricted|reciprocal": []string{},
39			},
40			expected: []string{},
41		},
42		{
43			name:       "emptyminusnothing",
44			conditions: []string{},
45			minus:      &[]string{},
46			matchingAny: map[string][]string{
47				"notice":                []string{},
48				"restricted":            []string{},
49				"restricted|reciprocal": []string{},
50			},
51			expected: []string{},
52		},
53		{
54			name:       "emptyminusnotice",
55			conditions: []string{},
56			minus:      &[]string{"notice"},
57			matchingAny: map[string][]string{
58				"notice":                []string{},
59				"restricted":            []string{},
60				"restricted|reciprocal": []string{},
61			},
62			expected: []string{},
63		},
64		{
65			name:       "noticeonly",
66			conditions: []string{"notice"},
67			matchingAny: map[string][]string{
68				"notice":             []string{"notice"},
69				"notice|proprietary": []string{"notice"},
70				"restricted":         []string{},
71			},
72			expected: []string{"notice"},
73		},
74		{
75			name:       "allnoticeonly",
76			conditions: []string{"notice"},
77			plus:       &[]string{"notice"},
78			matchingAny: map[string][]string{
79				"notice":             []string{"notice"},
80				"notice|proprietary": []string{"notice"},
81				"restricted":         []string{},
82			},
83			expected: []string{"notice"},
84		},
85		{
86			name:       "emptyplusnotice",
87			conditions: []string{},
88			plus:       &[]string{"notice"},
89			matchingAny: map[string][]string{
90				"notice":             []string{"notice"},
91				"notice|proprietary": []string{"notice"},
92				"restricted":         []string{},
93			},
94			expected: []string{"notice"},
95		},
96		{
97			name:       "everything",
98			conditions: []string{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary"},
99			plus:       &[]string{"restricted_with_classpath_exception", "restricted_allows_dynamic_linking", "by_exception_only", "not_allowed"},
100			matchingAny: map[string][]string{
101				"unencumbered":                        []string{"unencumbered"},
102				"permissive":                          []string{"permissive"},
103				"notice":                              []string{"notice"},
104				"reciprocal":                          []string{"reciprocal"},
105				"restricted":                          []string{"restricted"},
106				"restricted_with_classpath_exception": []string{"restricted_with_classpath_exception"},
107				"restricted_allows_dynamic_linking":   []string{"restricted_allows_dynamic_linking"},
108				"proprietary":                         []string{"proprietary"},
109				"by_exception_only":                   []string{"by_exception_only"},
110				"not_allowed":                         []string{"not_allowed"},
111				"notice|proprietary":                  []string{"notice", "proprietary"},
112			},
113			expected: []string{
114				"unencumbered",
115				"permissive",
116				"notice",
117				"reciprocal",
118				"restricted",
119				"restricted_with_classpath_exception",
120				"restricted_allows_dynamic_linking",
121				"proprietary",
122				"by_exception_only",
123				"not_allowed",
124			},
125		},
126		{
127			name: "everythingplusminusnothing",
128			conditions: []string{
129				"unencumbered",
130				"permissive",
131				"notice",
132				"reciprocal",
133				"restricted",
134				"restricted_with_classpath_exception",
135				"restricted_allows_dynamic_linking",
136				"proprietary",
137				"by_exception_only",
138				"not_allowed",
139			},
140			plus:  &[]string{},
141			minus: &[]string{},
142			matchingAny: map[string][]string{
143				"unencumbered|permissive|notice": []string{"unencumbered", "permissive", "notice"},
144				"restricted|reciprocal":          []string{"reciprocal", "restricted"},
145				"proprietary|by_exception_only":  []string{"proprietary", "by_exception_only"},
146				"not_allowed":                    []string{"not_allowed"},
147			},
148			expected: []string{
149				"unencumbered",
150				"permissive",
151				"notice",
152				"reciprocal",
153				"restricted",
154				"restricted_with_classpath_exception",
155				"restricted_allows_dynamic_linking",
156				"proprietary",
157				"by_exception_only",
158				"not_allowed",
159			},
160		},
161		{
162			name:       "allbutone",
163			conditions: []string{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary"},
164			plus:       &[]string{"restricted_allows_dynamic_linking", "by_exception_only", "not_allowed"},
165			matchingAny: map[string][]string{
166				"unencumbered":                        []string{"unencumbered"},
167				"permissive":                          []string{"permissive"},
168				"notice":                              []string{"notice"},
169				"reciprocal":                          []string{"reciprocal"},
170				"restricted":                          []string{"restricted"},
171				"restricted_with_classpath_exception": []string{},
172				"restricted_allows_dynamic_linking":   []string{"restricted_allows_dynamic_linking"},
173				"proprietary":                         []string{"proprietary"},
174				"by_exception_only":                   []string{"by_exception_only"},
175				"not_allowed":                         []string{"not_allowed"},
176				"notice|proprietary":                  []string{"notice", "proprietary"},
177			},
178			expected: []string{
179				"unencumbered",
180				"permissive",
181				"notice",
182				"reciprocal",
183				"restricted",
184				"restricted_allows_dynamic_linking",
185				"proprietary",
186				"by_exception_only",
187				"not_allowed",
188			},
189		},
190		{
191			name: "everythingminusone",
192			conditions: []string{
193				"unencumbered",
194				"permissive",
195				"notice",
196				"reciprocal",
197				"restricted",
198				"restricted_with_classpath_exception",
199				"restricted_allows_dynamic_linking",
200				"proprietary",
201				"by_exception_only",
202				"not_allowed",
203			},
204			minus: &[]string{"restricted_allows_dynamic_linking"},
205			matchingAny: map[string][]string{
206				"unencumbered":                        []string{"unencumbered"},
207				"permissive":                          []string{"permissive"},
208				"notice":                              []string{"notice"},
209				"reciprocal":                          []string{"reciprocal"},
210				"restricted":                          []string{"restricted"},
211				"restricted_with_classpath_exception": []string{"restricted_with_classpath_exception"},
212				"restricted_allows_dynamic_linking":   []string{},
213				"proprietary":                         []string{"proprietary"},
214				"by_exception_only":                   []string{"by_exception_only"},
215				"not_allowed":                         []string{"not_allowed"},
216				"restricted|proprietary":              []string{"restricted", "proprietary"},
217			},
218			expected: []string{
219				"unencumbered",
220				"permissive",
221				"notice",
222				"reciprocal",
223				"restricted",
224				"restricted_with_classpath_exception",
225				"proprietary",
226				"by_exception_only",
227				"not_allowed",
228			},
229		},
230		{
231			name: "everythingminuseverything",
232			conditions: []string{
233				"unencumbered",
234				"permissive",
235				"notice",
236				"reciprocal",
237				"restricted",
238				"restricted_with_classpath_exception",
239				"restricted_allows_dynamic_linking",
240				"proprietary",
241				"by_exception_only",
242				"not_allowed",
243			},
244			minus: &[]string{
245				"unencumbered",
246				"permissive",
247				"notice",
248				"reciprocal",
249				"restricted",
250				"restricted_with_classpath_exception",
251				"restricted_allows_dynamic_linking",
252				"proprietary",
253				"by_exception_only",
254				"not_allowed",
255			},
256			matchingAny: map[string][]string{
257				"unencumbered":                        []string{},
258				"permissive":                          []string{},
259				"notice":                              []string{},
260				"reciprocal":                          []string{},
261				"restricted":                          []string{},
262				"restricted_with_classpath_exception": []string{},
263				"restricted_allows_dynamic_linking":   []string{},
264				"proprietary":                         []string{},
265				"by_exception_only":                   []string{},
266				"not_allowed":                         []string{},
267				"restricted|proprietary":              []string{},
268			},
269			expected: []string{},
270		},
271		{
272			name:       "restrictedplus",
273			conditions: []string{"restricted", "restricted_with_classpath_exception", "restricted_allows_dynamic_linking"},
274			plus:       &[]string{"permissive", "notice", "restricted", "proprietary"},
275			matchingAny: map[string][]string{
276				"unencumbered":                        []string{},
277				"permissive":                          []string{"permissive"},
278				"notice":                              []string{"notice"},
279				"restricted":                          []string{"restricted"},
280				"restricted_with_classpath_exception": []string{"restricted_with_classpath_exception"},
281				"restricted_allows_dynamic_linking":   []string{"restricted_allows_dynamic_linking"},
282				"proprietary":                         []string{"proprietary"},
283				"restricted|proprietary":              []string{"restricted", "proprietary"},
284				"by_exception_only":                   []string{},
285				"proprietary|by_exception_only":       []string{"proprietary"},
286			},
287			expected: []string{"permissive", "notice", "restricted", "restricted_with_classpath_exception", "restricted_allows_dynamic_linking", "proprietary"},
288		},
289	}
290	for _, tt := range tests {
291		toConditions := func(names []string) []LicenseCondition {
292			result := make([]LicenseCondition, 0, len(names))
293			for _, name := range names {
294				result = append(result, RecognizedConditionNames[name])
295			}
296			return result
297		}
298		populate := func() LicenseConditionSet {
299			testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
300			if tt.plus != nil {
301				testSet = testSet.Plus(toConditions(*tt.plus)...)
302			}
303			if tt.minus != nil {
304				testSet = testSet.Minus(toConditions(*tt.minus)...)
305			}
306			return testSet
307		}
308		populateSet := func() LicenseConditionSet {
309			testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
310			if tt.plus != nil {
311				testSet = testSet.Union(NewLicenseConditionSet(toConditions(*tt.plus)...))
312			}
313			if tt.minus != nil {
314				testSet = testSet.Difference(NewLicenseConditionSet(toConditions(*tt.minus)...))
315			}
316			return testSet
317		}
318		populatePlusSet := func() LicenseConditionSet {
319			testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
320			if tt.plus != nil {
321				testSet = testSet.Union(NewLicenseConditionSet(toConditions(*tt.plus)...))
322			}
323			if tt.minus != nil {
324				testSet = testSet.Minus(toConditions(*tt.minus)...)
325			}
326			return testSet
327		}
328		populateMinusSet := func() LicenseConditionSet {
329			testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
330			if tt.plus != nil {
331				testSet = testSet.Plus(toConditions(*tt.plus)...)
332			}
333			if tt.minus != nil {
334				testSet = testSet.Difference(NewLicenseConditionSet(toConditions(*tt.minus)...))
335			}
336			return testSet
337		}
338		checkMatching := func(cs LicenseConditionSet, t *testing.T) {
339			for data, expectedNames := range tt.matchingAny {
340				expectedConditions := toConditions(expectedNames)
341				expected := NewLicenseConditionSet(expectedConditions...)
342				actual := cs.MatchingAny(toConditions(strings.Split(data, "|"))...)
343				actualNames := actual.Names()
344
345				t.Logf("MatchingAny(%s): actual set %04x %s", data, actual, actual.String())
346				t.Logf("MatchingAny(%s): expected set %04x %s", data, expected, expected.String())
347
348				if actual != expected {
349					t.Errorf("MatchingAny(%s): got %04x, want %04x", data, actual, expected)
350					continue
351				}
352				if len(actualNames) != len(expectedNames) {
353					t.Errorf("len(MatchinAny(%s).Names()): got %d, want %d",
354						data, len(actualNames), len(expectedNames))
355				} else {
356					for i := 0; i < len(actualNames); i++ {
357						if actualNames[i] != expectedNames[i] {
358							t.Errorf("MatchingAny(%s).Names()[%d]: got %s, want %s",
359								data, i, actualNames[i], expectedNames[i])
360							break
361						}
362					}
363				}
364				actualConditions := actual.AsList()
365				if len(actualConditions) != len(expectedConditions) {
366					t.Errorf("len(MatchingAny(%s).AsList()):  got %d, want %d",
367						data, len(actualNames), len(expectedNames))
368				} else {
369					for i := 0; i < len(actualNames); i++ {
370						if actualNames[i] != expectedNames[i] {
371							t.Errorf("MatchingAny(%s).AsList()[%d]: got %s, want %s",
372								data, i, actualNames[i], expectedNames[i])
373							break
374						}
375					}
376				}
377			}
378		}
379		checkMatchingSet := func(cs LicenseConditionSet, t *testing.T) {
380			for data, expectedNames := range tt.matchingAny {
381				expected := NewLicenseConditionSet(toConditions(expectedNames)...)
382				actual := cs.MatchingAnySet(NewLicenseConditionSet(toConditions(strings.Split(data, "|"))...))
383				actualNames := actual.Names()
384
385				t.Logf("MatchingAnySet(%s): actual set %04x %s", data, actual, actual.String())
386				t.Logf("MatchingAnySet(%s): expected set %04x %s", data, expected, expected.String())
387
388				if actual != expected {
389					t.Errorf("MatchingAnySet(%s): got %04x, want %04x", data, actual, expected)
390					continue
391				}
392				if len(actualNames) != len(expectedNames) {
393					t.Errorf("len(MatchingAnySet(%s).Names()): got %d, want %d",
394						data, len(actualNames), len(expectedNames))
395				} else {
396					for i := 0; i < len(actualNames); i++ {
397						if actualNames[i] != expectedNames[i] {
398							t.Errorf("MatchingAnySet(%s).Names()[%d]: got %s, want %s",
399								data, i, actualNames[i], expectedNames[i])
400							break
401						}
402					}
403				}
404				expectedConditions := toConditions(expectedNames)
405				actualConditions := actual.AsList()
406				if len(actualConditions) != len(expectedConditions) {
407					t.Errorf("len(MatchingAnySet(%s).AsList()): got %d, want %d",
408						data, len(actualNames), len(expectedNames))
409				} else {
410					for i := 0; i < len(actualNames); i++ {
411						if actualNames[i] != expectedNames[i] {
412							t.Errorf("MatchingAnySet(%s).AsList()[%d]: got %s, want %s",
413								data, i, actualNames[i], expectedNames[i])
414							break
415						}
416					}
417				}
418			}
419		}
420
421		checkExpected := func(actual LicenseConditionSet, t *testing.T) bool {
422			t.Logf("checkExpected{%s}", strings.Join(tt.expected, ", "))
423
424			expectedConditions := toConditions(tt.expected)
425			expected := NewLicenseConditionSet(expectedConditions...)
426
427			actualNames := actual.Names()
428
429			t.Logf("actual license condition set: %04x %s", actual, actual.String())
430			t.Logf("expected license condition set: %04x %s", expected, expected.String())
431
432			if actual != expected {
433				t.Errorf("checkExpected: got %04x, want %04x", actual, expected)
434				return false
435			}
436
437			if len(actualNames) != len(tt.expected) {
438				t.Errorf("len(actual.Names()): got %d, want %d", len(actualNames), len(tt.expected))
439			} else {
440				for i := 0; i < len(actualNames); i++ {
441					if actualNames[i] != tt.expected[i] {
442						t.Errorf("actual.Names()[%d]: got %s, want %s", i, actualNames[i], tt.expected[i])
443						break
444					}
445				}
446			}
447
448			actualConditions := actual.AsList()
449			if len(actualConditions) != len(expectedConditions) {
450				t.Errorf("len(actual.AsList()): got %d, want %d", len(actualConditions), len(expectedConditions))
451			} else {
452				for i := 0; i < len(actualConditions); i++ {
453					if actualConditions[i] != expectedConditions[i] {
454						t.Errorf("actual.AsList()[%d]: got %s, want %s",
455							i, actualConditions[i].Name(), expectedConditions[i].Name())
456						break
457					}
458				}
459			}
460
461			if len(tt.expected) == 0 {
462				if !actual.IsEmpty() {
463					t.Errorf("actual.IsEmpty(): got false, want true")
464				}
465				if actual.HasAny(expectedConditions...) {
466					t.Errorf("actual.HasAny(): got true, want false")
467				}
468			} else {
469				if actual.IsEmpty() {
470					t.Errorf("actual.IsEmpty(): got true, want false")
471				}
472				if !actual.HasAny(expectedConditions...) {
473					t.Errorf("actual.HasAny(all expected): got false, want true")
474				}
475			}
476			if !actual.HasAll(expectedConditions...) {
477				t.Errorf("actual.Hasll(all expected): want true, got false")
478			}
479			for _, expectedCondition := range expectedConditions {
480				if !actual.HasAny(expectedCondition) {
481					t.Errorf("actual.HasAny(%q): got false, want true", expectedCondition.Name())
482				}
483				if !actual.HasAll(expectedCondition) {
484					t.Errorf("actual.HasAll(%q): got false, want true", expectedCondition.Name())
485				}
486			}
487
488			notExpected := (AllLicenseConditions &^ expected)
489			notExpectedList := notExpected.AsList()
490			t.Logf("not expected license condition set: %04x %s", notExpected, notExpected.String())
491
492			if len(tt.expected) == 0 {
493				if actual.HasAny(append(expectedConditions, notExpectedList...)...) {
494					t.Errorf("actual.HasAny(all conditions): want false, got true")
495				}
496			} else {
497				if !actual.HasAny(append(expectedConditions, notExpectedList...)...) {
498					t.Errorf("actual.HasAny(all conditions): want true, got false")
499				}
500			}
501			if len(notExpectedList) == 0 {
502				if !actual.HasAll(append(expectedConditions, notExpectedList...)...) {
503					t.Errorf("actual.HasAll(all conditions): want true, got false")
504				}
505			} else {
506				if actual.HasAll(append(expectedConditions, notExpectedList...)...) {
507					t.Errorf("actual.HasAll(all conditions): want false, got true")
508				}
509			}
510			for _, unexpectedCondition := range notExpectedList {
511				if actual.HasAny(unexpectedCondition) {
512					t.Errorf("actual.HasAny(%q): got true, want false", unexpectedCondition.Name())
513				}
514				if actual.HasAll(unexpectedCondition) {
515					t.Errorf("actual.HasAll(%q): got true, want false", unexpectedCondition.Name())
516				}
517			}
518			return true
519		}
520
521		checkExpectedSet := func(actual LicenseConditionSet, t *testing.T) bool {
522			t.Logf("checkExpectedSet{%s}", strings.Join(tt.expected, ", "))
523
524			expectedConditions := toConditions(tt.expected)
525			expected := NewLicenseConditionSet(expectedConditions...)
526
527			actualNames := actual.Names()
528
529			t.Logf("actual license condition set: %04x %s", actual, actual.String())
530			t.Logf("expected license condition set: %04x %s", expected, expected.String())
531
532			if actual != expected {
533				t.Errorf("checkExpectedSet: got %04x, want %04x", actual, expected)
534				return false
535			}
536
537			if len(actualNames) != len(tt.expected) {
538				t.Errorf("len(actual.Names()): got %d, want %d", len(actualNames), len(tt.expected))
539			} else {
540				for i := 0; i < len(actualNames); i++ {
541					if actualNames[i] != tt.expected[i] {
542						t.Errorf("actual.Names()[%d]: got %s, want %s", i, actualNames[i], tt.expected[i])
543						break
544					}
545				}
546			}
547
548			actualConditions := actual.AsList()
549			if len(actualConditions) != len(expectedConditions) {
550				t.Errorf("len(actual.AsList()): got %d, want %d", len(actualConditions), len(expectedConditions))
551			} else {
552				for i := 0; i < len(actualConditions); i++ {
553					if actualConditions[i] != expectedConditions[i] {
554						t.Errorf("actual.AsList()[%d}: got %s, want %s",
555							i, actualConditions[i].Name(), expectedConditions[i].Name())
556						break
557					}
558				}
559			}
560
561			if len(tt.expected) == 0 {
562				if !actual.IsEmpty() {
563					t.Errorf("actual.IsEmpty(): got false, want true")
564				}
565				if actual.MatchesAnySet(expected) {
566					t.Errorf("actual.MatchesAnySet({}): got true, want false")
567				}
568				if actual.MatchesEverySet(expected, expected) {
569					t.Errorf("actual.MatchesEverySet({}, {}): want false, got true")
570				}
571			} else {
572				if actual.IsEmpty() {
573					t.Errorf("actual.IsEmpty(): got true, want false")
574				}
575				if !actual.MatchesAnySet(expected) {
576					t.Errorf("actual.MatchesAnySet({all expected}): want true, got false")
577				}
578				if !actual.MatchesEverySet(expected, expected) {
579					t.Errorf("actual.MatchesEverySet({all expected}, {all expected}): want true, got false")
580				}
581			}
582
583			notExpected := (AllLicenseConditions &^ expected)
584			t.Logf("not expected license condition set: %04x %s", notExpected, notExpected.String())
585
586			if len(tt.expected) == 0 {
587				if actual.MatchesAnySet(expected, notExpected) {
588					t.Errorf("empty actual.MatchesAnySet({expected}, {not expected}): want false, got true")
589				}
590			} else {
591				if !actual.MatchesAnySet(expected, notExpected) {
592					t.Errorf("actual.MatchesAnySet({expected}, {not expected}): want true, got false")
593				}
594			}
595			if actual.MatchesAnySet(notExpected) {
596				t.Errorf("actual.MatchesAnySet({not expected}): want false, got true")
597			}
598			if actual.MatchesEverySet(notExpected) {
599				t.Errorf("actual.MatchesEverySet({not expected}): want false, got true")
600			}
601			if actual.MatchesEverySet(expected, notExpected) {
602				t.Errorf("actual.MatchesEverySet({expected}, {not expected}): want false, got true")
603			}
604
605			if !actual.Difference(expected).IsEmpty() {
606				t.Errorf("actual.Difference({expected}).IsEmpty(): want true, got false")
607			}
608			if expected != actual.Intersection(expected) {
609				t.Errorf("expected == actual.Intersection({expected}): want true, got false (%04x != %04x)", expected, actual.Intersection(expected))
610			}
611			if actual != actual.Intersection(expected) {
612				t.Errorf("actual == actual.Intersection({expected}): want true, got false (%04x != %04x)", actual, actual.Intersection(expected))
613			}
614			return true
615		}
616
617		t.Run(tt.name, func(t *testing.T) {
618			cs := populate()
619			if checkExpected(cs, t) {
620				checkMatching(cs, t)
621			}
622			if checkExpectedSet(cs, t) {
623				checkMatchingSet(cs, t)
624			}
625		})
626
627		t.Run(tt.name+"_sets", func(t *testing.T) {
628			cs := populateSet()
629			if checkExpected(cs, t) {
630				checkMatching(cs, t)
631			}
632			if checkExpectedSet(cs, t) {
633				checkMatchingSet(cs, t)
634			}
635		})
636
637		t.Run(tt.name+"_plusset", func(t *testing.T) {
638			cs := populatePlusSet()
639			if checkExpected(cs, t) {
640				checkMatching(cs, t)
641			}
642			if checkExpectedSet(cs, t) {
643				checkMatchingSet(cs, t)
644			}
645		})
646
647		t.Run(tt.name+"_minusset", func(t *testing.T) {
648			cs := populateMinusSet()
649			if checkExpected(cs, t) {
650				checkMatching(cs, t)
651			}
652			if checkExpectedSet(cs, t) {
653				checkMatchingSet(cs, t)
654			}
655		})
656	}
657}
658