xref: /aosp_15_r20/build/make/tools/compliance/conditionset_test.go (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
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_if_statically_linked", "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_if_statically_linked": []string{"restricted_if_statically_linked"},
107				"proprietary":                     []string{"proprietary"},
108				"by_exception_only":               []string{"by_exception_only"},
109				"not_allowed":                     []string{"not_allowed"},
110				"notice|proprietary":              []string{"notice", "proprietary"},
111			},
112			expected: []string{
113				"unencumbered",
114				"permissive",
115				"notice",
116				"reciprocal",
117				"restricted",
118				"restricted_if_statically_linked",
119				"proprietary",
120				"by_exception_only",
121				"not_allowed",
122			},
123		},
124		{
125			name: "everythingplusminusnothing",
126			conditions: []string{
127				"unencumbered",
128				"permissive",
129				"notice",
130				"reciprocal",
131				"restricted",
132				"restricted_if_statically_linked",
133				"proprietary",
134				"by_exception_only",
135				"not_allowed",
136			},
137			plus:  &[]string{},
138			minus: &[]string{},
139			matchingAny: map[string][]string{
140				"unencumbered|permissive|notice": []string{"unencumbered", "permissive", "notice"},
141				"restricted|reciprocal":          []string{"reciprocal", "restricted"},
142				"proprietary|by_exception_only":  []string{"proprietary", "by_exception_only"},
143				"not_allowed":                    []string{"not_allowed"},
144			},
145			expected: []string{
146				"unencumbered",
147				"permissive",
148				"notice",
149				"reciprocal",
150				"restricted",
151				"restricted_if_statically_linked",
152				"proprietary",
153				"by_exception_only",
154				"not_allowed",
155			},
156		},
157		{
158			name:       "allbutone",
159			conditions: []string{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary"},
160			plus:       &[]string{"restricted_if_statically_linked", "by_exception_only", "not_allowed"},
161			matchingAny: map[string][]string{
162				"unencumbered":                    []string{"unencumbered"},
163				"permissive":                      []string{"permissive"},
164				"notice":                          []string{"notice"},
165				"reciprocal":                      []string{"reciprocal"},
166				"restricted":                      []string{"restricted"},
167				"restricted_if_statically_linked": []string{"restricted_if_statically_linked"},
168				"proprietary":                     []string{"proprietary"},
169				"by_exception_only":               []string{"by_exception_only"},
170				"not_allowed":                     []string{"not_allowed"},
171				"notice|proprietary":              []string{"notice", "proprietary"},
172			},
173			expected: []string{
174				"unencumbered",
175				"permissive",
176				"notice",
177				"reciprocal",
178				"restricted",
179				"restricted_if_statically_linked",
180				"proprietary",
181				"by_exception_only",
182				"not_allowed",
183			},
184		},
185		{
186			name: "everythingminusone",
187			conditions: []string{
188				"unencumbered",
189				"permissive",
190				"notice",
191				"reciprocal",
192				"restricted",
193				"restricted_if_statically_linked",
194				"proprietary",
195				"by_exception_only",
196				"not_allowed",
197			},
198			minus: &[]string{"restricted_if_statically_linked"},
199			matchingAny: map[string][]string{
200				"unencumbered":                    []string{"unencumbered"},
201				"permissive":                      []string{"permissive"},
202				"notice":                          []string{"notice"},
203				"reciprocal":                      []string{"reciprocal"},
204				"restricted":                      []string{"restricted"},
205				"restricted_if_statically_linked": []string{},
206				"proprietary":                     []string{"proprietary"},
207				"by_exception_only":               []string{"by_exception_only"},
208				"not_allowed":                     []string{"not_allowed"},
209				"restricted|proprietary":          []string{"restricted", "proprietary"},
210			},
211			expected: []string{
212				"unencumbered",
213				"permissive",
214				"notice",
215				"reciprocal",
216				"restricted",
217				"proprietary",
218				"by_exception_only",
219				"not_allowed",
220			},
221		},
222		{
223			name: "everythingminuseverything",
224			conditions: []string{
225				"unencumbered",
226				"permissive",
227				"notice",
228				"reciprocal",
229				"restricted",
230				"restricted_if_statically_linked",
231				"proprietary",
232				"by_exception_only",
233				"not_allowed",
234			},
235			minus: &[]string{
236				"unencumbered",
237				"permissive",
238				"notice",
239				"reciprocal",
240				"restricted",
241				"restricted_if_statically_linked",
242				"proprietary",
243				"by_exception_only",
244				"not_allowed",
245			},
246			matchingAny: map[string][]string{
247				"unencumbered":                    []string{},
248				"permissive":                      []string{},
249				"notice":                          []string{},
250				"reciprocal":                      []string{},
251				"restricted":                      []string{},
252				"restricted_if_statically_linked": []string{},
253				"proprietary":                     []string{},
254				"by_exception_only":               []string{},
255				"not_allowed":                     []string{},
256				"restricted|proprietary":          []string{},
257			},
258			expected: []string{},
259		},
260		{
261			name:       "restrictedplus",
262			conditions: []string{"restricted", "restricted_if_statically_linked"},
263			plus:       &[]string{"permissive", "notice", "restricted", "proprietary"},
264			matchingAny: map[string][]string{
265				"unencumbered":                    []string{},
266				"permissive":                      []string{"permissive"},
267				"notice":                          []string{"notice"},
268				"restricted":                      []string{"restricted"},
269				"restricted_if_statically_linked": []string{"restricted_if_statically_linked"},
270				"proprietary":                     []string{"proprietary"},
271				"restricted|proprietary":          []string{"restricted", "proprietary"},
272				"by_exception_only":               []string{},
273				"proprietary|by_exception_only":   []string{"proprietary"},
274			},
275			expected: []string{"permissive", "notice", "restricted", "restricted_if_statically_linked", "proprietary"},
276		},
277	}
278	for _, tt := range tests {
279		toConditions := func(names []string) []LicenseCondition {
280			result := make([]LicenseCondition, 0, len(names))
281			for _, name := range names {
282				result = append(result, RecognizedConditionNames[name])
283			}
284			return result
285		}
286		populate := func() LicenseConditionSet {
287			testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
288			if tt.plus != nil {
289				testSet = testSet.Plus(toConditions(*tt.plus)...)
290			}
291			if tt.minus != nil {
292				testSet = testSet.Minus(toConditions(*tt.minus)...)
293			}
294			return testSet
295		}
296		populateSet := func() LicenseConditionSet {
297			testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
298			if tt.plus != nil {
299				testSet = testSet.Union(NewLicenseConditionSet(toConditions(*tt.plus)...))
300			}
301			if tt.minus != nil {
302				testSet = testSet.Difference(NewLicenseConditionSet(toConditions(*tt.minus)...))
303			}
304			return testSet
305		}
306		populatePlusSet := func() LicenseConditionSet {
307			testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
308			if tt.plus != nil {
309				testSet = testSet.Union(NewLicenseConditionSet(toConditions(*tt.plus)...))
310			}
311			if tt.minus != nil {
312				testSet = testSet.Minus(toConditions(*tt.minus)...)
313			}
314			return testSet
315		}
316		populateMinusSet := func() LicenseConditionSet {
317			testSet := NewLicenseConditionSet(toConditions(tt.conditions)...)
318			if tt.plus != nil {
319				testSet = testSet.Plus(toConditions(*tt.plus)...)
320			}
321			if tt.minus != nil {
322				testSet = testSet.Difference(NewLicenseConditionSet(toConditions(*tt.minus)...))
323			}
324			return testSet
325		}
326		checkMatching := func(cs LicenseConditionSet, t *testing.T) {
327			for data, expectedNames := range tt.matchingAny {
328				expectedConditions := toConditions(expectedNames)
329				expected := NewLicenseConditionSet(expectedConditions...)
330				actual := cs.MatchingAny(toConditions(strings.Split(data, "|"))...)
331				actualNames := actual.Names()
332
333				t.Logf("MatchingAny(%s): actual set %#v %s", data, actual, actual.String())
334				t.Logf("MatchingAny(%s): expected set %#v %s", data, expected, expected.String())
335
336				if actual != expected {
337					t.Errorf("MatchingAny(%s): got %#v, want %#v", data, actual, expected)
338					continue
339				}
340				if len(actualNames) != len(expectedNames) {
341					t.Errorf("len(MatchinAny(%s).Names()): got %d, want %d",
342						data, len(actualNames), len(expectedNames))
343				} else {
344					for i := 0; i < len(actualNames); i++ {
345						if actualNames[i] != expectedNames[i] {
346							t.Errorf("MatchingAny(%s).Names()[%d]: got %s, want %s",
347								data, i, actualNames[i], expectedNames[i])
348							break
349						}
350					}
351				}
352				actualConditions := actual.AsList()
353				if len(actualConditions) != len(expectedConditions) {
354					t.Errorf("len(MatchingAny(%s).AsList()):  got %d, want %d",
355						data, len(actualNames), len(expectedNames))
356				} else {
357					for i := 0; i < len(actualNames); i++ {
358						if actualNames[i] != expectedNames[i] {
359							t.Errorf("MatchingAny(%s).AsList()[%d]: got %s, want %s",
360								data, i, actualNames[i], expectedNames[i])
361							break
362						}
363					}
364				}
365			}
366		}
367		checkMatchingSet := func(cs LicenseConditionSet, t *testing.T) {
368			for data, expectedNames := range tt.matchingAny {
369				expected := NewLicenseConditionSet(toConditions(expectedNames)...)
370				actual := cs.MatchingAnySet(NewLicenseConditionSet(toConditions(strings.Split(data, "|"))...))
371				actualNames := actual.Names()
372
373				t.Logf("MatchingAnySet(%s): actual set %#v %s", data, actual, actual.String())
374				t.Logf("MatchingAnySet(%s): expected set %#v %s", data, expected, expected.String())
375
376				if actual != expected {
377					t.Errorf("MatchingAnySet(%s): got %#v, want %#v", data, actual, expected)
378					continue
379				}
380				if len(actualNames) != len(expectedNames) {
381					t.Errorf("len(MatchingAnySet(%s).Names()): got %d, want %d",
382						data, len(actualNames), len(expectedNames))
383				} else {
384					for i := 0; i < len(actualNames); i++ {
385						if actualNames[i] != expectedNames[i] {
386							t.Errorf("MatchingAnySet(%s).Names()[%d]: got %s, want %s",
387								data, i, actualNames[i], expectedNames[i])
388							break
389						}
390					}
391				}
392				expectedConditions := toConditions(expectedNames)
393				actualConditions := actual.AsList()
394				if len(actualConditions) != len(expectedConditions) {
395					t.Errorf("len(MatchingAnySet(%s).AsList()): got %d, want %d",
396						data, len(actualNames), len(expectedNames))
397				} else {
398					for i := 0; i < len(actualNames); i++ {
399						if actualNames[i] != expectedNames[i] {
400							t.Errorf("MatchingAnySet(%s).AsList()[%d]: got %s, want %s",
401								data, i, actualNames[i], expectedNames[i])
402							break
403						}
404					}
405				}
406			}
407		}
408
409		checkExpected := func(actual LicenseConditionSet, t *testing.T) bool {
410			t.Logf("checkExpected{%s}", strings.Join(tt.expected, ", "))
411
412			expectedConditions := toConditions(tt.expected)
413			expected := NewLicenseConditionSet(expectedConditions...)
414
415			actualNames := actual.Names()
416
417			t.Logf("actual license condition set: %#v %s", actual, actual.String())
418			t.Logf("expected license condition set: %#v %s", expected, expected.String())
419
420			if actual != expected {
421				t.Errorf("checkExpected: got %#v, want %#v", actual, expected)
422				return false
423			}
424
425			if len(actualNames) != len(tt.expected) {
426				t.Errorf("len(actual.Names()): got %d, want %d", len(actualNames), len(tt.expected))
427			} else {
428				for i := 0; i < len(actualNames); i++ {
429					if actualNames[i] != tt.expected[i] {
430						t.Errorf("actual.Names()[%d]: got %s, want %s", i, actualNames[i], tt.expected[i])
431						break
432					}
433				}
434			}
435
436			actualConditions := actual.AsList()
437			if len(actualConditions) != len(expectedConditions) {
438				t.Errorf("len(actual.AsList()): got %d, want %d", len(actualConditions), len(expectedConditions))
439			} else {
440				for i := 0; i < len(actualConditions); i++ {
441					if actualConditions[i] != expectedConditions[i] {
442						t.Errorf("actual.AsList()[%d]: got %s, want %s",
443							i, actualConditions[i].Name(), expectedConditions[i].Name())
444						break
445					}
446				}
447			}
448
449			if len(tt.expected) == 0 {
450				if !actual.IsEmpty() {
451					t.Errorf("actual.IsEmpty(): got false, want true")
452				}
453				if actual.HasAny(expectedConditions...) {
454					t.Errorf("actual.HasAny(): got true, want false")
455				}
456			} else {
457				if actual.IsEmpty() {
458					t.Errorf("actual.IsEmpty(): got true, want false")
459				}
460				if !actual.HasAny(expectedConditions...) {
461					t.Errorf("actual.HasAny(all expected): got false, want true")
462				}
463			}
464			if !actual.HasAll(expectedConditions...) {
465				t.Errorf("actual.Hasll(all expected): want true, got false")
466			}
467			for _, expectedCondition := range expectedConditions {
468				if !actual.HasAny(expectedCondition) {
469					t.Errorf("actual.HasAny(%q): got false, want true", expectedCondition.Name())
470				}
471				if !actual.HasAll(expectedCondition) {
472					t.Errorf("actual.HasAll(%q): got false, want true", expectedCondition.Name())
473				}
474			}
475
476			notExpected := (AllLicenseConditions &^ expected)
477			notExpectedList := notExpected.AsList()
478			t.Logf("not expected license condition set: %#v %s", notExpected, notExpected.String())
479
480			if len(tt.expected) == 0 {
481				if actual.HasAny(append(expectedConditions, notExpectedList...)...) {
482					t.Errorf("actual.HasAny(all conditions): want false, got true")
483				}
484			} else {
485				if !actual.HasAny(append(expectedConditions, notExpectedList...)...) {
486					t.Errorf("actual.HasAny(all conditions): want true, got false")
487				}
488			}
489			if len(notExpectedList) == 0 {
490				if !actual.HasAll(append(expectedConditions, notExpectedList...)...) {
491					t.Errorf("actual.HasAll(all conditions): want true, got false")
492				}
493			} else {
494				if actual.HasAll(append(expectedConditions, notExpectedList...)...) {
495					t.Errorf("actual.HasAll(all conditions): want false, got true")
496				}
497			}
498			for _, unexpectedCondition := range notExpectedList {
499				if actual.HasAny(unexpectedCondition) {
500					t.Errorf("actual.HasAny(%q): got true, want false", unexpectedCondition.Name())
501				}
502				if actual.HasAll(unexpectedCondition) {
503					t.Errorf("actual.HasAll(%q): got true, want false", unexpectedCondition.Name())
504				}
505			}
506			return true
507		}
508
509		checkExpectedSet := func(actual LicenseConditionSet, t *testing.T) bool {
510			t.Logf("checkExpectedSet{%s}", strings.Join(tt.expected, ", "))
511
512			expectedConditions := toConditions(tt.expected)
513			expected := NewLicenseConditionSet(expectedConditions...)
514
515			actualNames := actual.Names()
516
517			t.Logf("actual license condition set: %#v %s", actual, actual.String())
518			t.Logf("expected license condition set: %#v %s", expected, expected.String())
519
520			if actual != expected {
521				t.Errorf("checkExpectedSet: got %#v, want %#v", actual, expected)
522				return false
523			}
524
525			if len(actualNames) != len(tt.expected) {
526				t.Errorf("len(actual.Names()): got %d, want %d", len(actualNames), len(tt.expected))
527			} else {
528				for i := 0; i < len(actualNames); i++ {
529					if actualNames[i] != tt.expected[i] {
530						t.Errorf("actual.Names()[%d]: got %s, want %s", i, actualNames[i], tt.expected[i])
531						break
532					}
533				}
534			}
535
536			actualConditions := actual.AsList()
537			if len(actualConditions) != len(expectedConditions) {
538				t.Errorf("len(actual.AsList()): got %d, want %d", len(actualConditions), len(expectedConditions))
539			} else {
540				for i := 0; i < len(actualConditions); i++ {
541					if actualConditions[i] != expectedConditions[i] {
542						t.Errorf("actual.AsList()[%d}: got %s, want %s",
543							i, actualConditions[i].Name(), expectedConditions[i].Name())
544						break
545					}
546				}
547			}
548
549			if len(tt.expected) == 0 {
550				if !actual.IsEmpty() {
551					t.Errorf("actual.IsEmpty(): got false, want true")
552				}
553				if actual.MatchesAnySet(expected) {
554					t.Errorf("actual.MatchesAnySet({}): got true, want false")
555				}
556				if actual.MatchesEverySet(expected, expected) {
557					t.Errorf("actual.MatchesEverySet({}, {}): want false, got true")
558				}
559			} else {
560				if actual.IsEmpty() {
561					t.Errorf("actual.IsEmpty(): got true, want false")
562				}
563				if !actual.MatchesAnySet(expected) {
564					t.Errorf("actual.MatchesAnySet({all expected}): want true, got false")
565				}
566				if !actual.MatchesEverySet(expected, expected) {
567					t.Errorf("actual.MatchesEverySet({all expected}, {all expected}): want true, got false")
568				}
569			}
570
571			notExpected := (AllLicenseConditions &^ expected)
572			t.Logf("not expected license condition set: %#v %s", notExpected, notExpected.String())
573
574			if len(tt.expected) == 0 {
575				if actual.MatchesAnySet(expected, notExpected) {
576					t.Errorf("empty actual.MatchesAnySet({expected}, {not expected}): want false, got true")
577				}
578			} else {
579				if !actual.MatchesAnySet(expected, notExpected) {
580					t.Errorf("actual.MatchesAnySet({expected}, {not expected}): want true, got false")
581				}
582			}
583			if actual.MatchesAnySet(notExpected) {
584				t.Errorf("actual.MatchesAnySet({not expected}): want false, got true")
585			}
586			if actual.MatchesEverySet(notExpected) {
587				t.Errorf("actual.MatchesEverySet({not expected}): want false, got true")
588			}
589			if actual.MatchesEverySet(expected, notExpected) {
590				t.Errorf("actual.MatchesEverySet({expected}, {not expected}): want false, got true")
591			}
592
593			if !actual.Difference(expected).IsEmpty() {
594				t.Errorf("actual.Difference({expected}).IsEmpty(): want true, got false")
595			}
596			if expected != actual.Intersection(expected) {
597				t.Errorf("expected == actual.Intersection({expected}): want true, got false (%#v != %#v)", expected, actual.Intersection(expected))
598			}
599			if actual != actual.Intersection(expected) {
600				t.Errorf("actual == actual.Intersection({expected}): want true, got false (%#v != %#v)", actual, actual.Intersection(expected))
601			}
602			return true
603		}
604
605		t.Run(tt.name, func(t *testing.T) {
606			cs := populate()
607			if checkExpected(cs, t) {
608				checkMatching(cs, t)
609			}
610			if checkExpectedSet(cs, t) {
611				checkMatchingSet(cs, t)
612			}
613		})
614
615		t.Run(tt.name+"_sets", func(t *testing.T) {
616			cs := populateSet()
617			if checkExpected(cs, t) {
618				checkMatching(cs, t)
619			}
620			if checkExpectedSet(cs, t) {
621				checkMatchingSet(cs, t)
622			}
623		})
624
625		t.Run(tt.name+"_plusset", func(t *testing.T) {
626			cs := populatePlusSet()
627			if checkExpected(cs, t) {
628				checkMatching(cs, t)
629			}
630			if checkExpectedSet(cs, t) {
631				checkMatchingSet(cs, t)
632			}
633		})
634
635		t.Run(tt.name+"_minusset", func(t *testing.T) {
636			cs := populateMinusSet()
637			if checkExpected(cs, t) {
638				checkMatching(cs, t)
639			}
640			if checkExpectedSet(cs, t) {
641				checkMatchingSet(cs, t)
642			}
643		})
644	}
645}
646