// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package compliance import ( "strings" "testing" ) func TestConditionSet(t *testing.T) { tests := []struct { name string conditions []string plus *[]string minus *[]string matchingAny map[string][]string expected []string }{ { name: "empty", conditions: []string{}, plus: &[]string{}, matchingAny: map[string][]string{ "notice": []string{}, "restricted": []string{}, "restricted|reciprocal": []string{}, }, expected: []string{}, }, { name: "emptyminusnothing", conditions: []string{}, minus: &[]string{}, matchingAny: map[string][]string{ "notice": []string{}, "restricted": []string{}, "restricted|reciprocal": []string{}, }, expected: []string{}, }, { name: "emptyminusnotice", conditions: []string{}, minus: &[]string{"notice"}, matchingAny: map[string][]string{ "notice": []string{}, "restricted": []string{}, "restricted|reciprocal": []string{}, }, expected: []string{}, }, { name: "noticeonly", conditions: []string{"notice"}, matchingAny: map[string][]string{ "notice": []string{"notice"}, "notice|proprietary": []string{"notice"}, "restricted": []string{}, }, expected: []string{"notice"}, }, { name: "allnoticeonly", conditions: []string{"notice"}, plus: &[]string{"notice"}, matchingAny: map[string][]string{ "notice": []string{"notice"}, "notice|proprietary": []string{"notice"}, "restricted": []string{}, }, expected: []string{"notice"}, }, { name: "emptyplusnotice", conditions: []string{}, plus: &[]string{"notice"}, matchingAny: map[string][]string{ "notice": []string{"notice"}, "notice|proprietary": []string{"notice"}, "restricted": []string{}, }, expected: []string{"notice"}, }, { name: "everything", conditions: []string{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary"}, plus: &[]string{"restricted_if_statically_linked", "by_exception_only", "not_allowed"}, matchingAny: map[string][]string{ "unencumbered": []string{"unencumbered"}, "permissive": []string{"permissive"}, "notice": []string{"notice"}, "reciprocal": []string{"reciprocal"}, "restricted": []string{"restricted"}, "restricted_if_statically_linked": []string{"restricted_if_statically_linked"}, "proprietary": []string{"proprietary"}, "by_exception_only": []string{"by_exception_only"}, "not_allowed": []string{"not_allowed"}, "notice|proprietary": []string{"notice", "proprietary"}, }, expected: []string{ "unencumbered", "permissive", "notice", "reciprocal", "restricted", "restricted_if_statically_linked", "proprietary", "by_exception_only", "not_allowed", }, }, { name: "everythingplusminusnothing", conditions: []string{ "unencumbered", "permissive", "notice", "reciprocal", "restricted", "restricted_if_statically_linked", "proprietary", "by_exception_only", "not_allowed", }, plus: &[]string{}, minus: &[]string{}, matchingAny: map[string][]string{ "unencumbered|permissive|notice": []string{"unencumbered", "permissive", "notice"}, "restricted|reciprocal": []string{"reciprocal", "restricted"}, "proprietary|by_exception_only": []string{"proprietary", "by_exception_only"}, "not_allowed": []string{"not_allowed"}, }, expected: []string{ "unencumbered", "permissive", "notice", "reciprocal", "restricted", "restricted_if_statically_linked", "proprietary", "by_exception_only", "not_allowed", }, }, { name: "allbutone", conditions: []string{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary"}, plus: &[]string{"restricted_if_statically_linked", "by_exception_only", "not_allowed"}, matchingAny: map[string][]string{ "unencumbered": []string{"unencumbered"}, "permissive": []string{"permissive"}, "notice": []string{"notice"}, "reciprocal": []string{"reciprocal"}, "restricted": []string{"restricted"}, "restricted_if_statically_linked": []string{"restricted_if_statically_linked"}, "proprietary": []string{"proprietary"}, "by_exception_only": []string{"by_exception_only"}, "not_allowed": []string{"not_allowed"}, "notice|proprietary": []string{"notice", "proprietary"}, }, expected: []string{ "unencumbered", "permissive", "notice", "reciprocal", "restricted", "restricted_if_statically_linked", "proprietary", "by_exception_only", "not_allowed", }, }, { name: "everythingminusone", conditions: []string{ "unencumbered", "permissive", "notice", "reciprocal", "restricted", "restricted_if_statically_linked", "proprietary", "by_exception_only", "not_allowed", }, minus: &[]string{"restricted_if_statically_linked"}, matchingAny: map[string][]string{ "unencumbered": []string{"unencumbered"}, "permissive": []string{"permissive"}, "notice": []string{"notice"}, "reciprocal": []string{"reciprocal"}, "restricted": []string{"restricted"}, "restricted_if_statically_linked": []string{}, "proprietary": []string{"proprietary"}, "by_exception_only": []string{"by_exception_only"}, "not_allowed": []string{"not_allowed"}, "restricted|proprietary": []string{"restricted", "proprietary"}, }, expected: []string{ "unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary", "by_exception_only", "not_allowed", }, }, { name: "everythingminuseverything", conditions: []string{ "unencumbered", "permissive", "notice", "reciprocal", "restricted", "restricted_if_statically_linked", "proprietary", "by_exception_only", "not_allowed", }, minus: &[]string{ "unencumbered", "permissive", "notice", "reciprocal", "restricted", "restricted_if_statically_linked", "proprietary", "by_exception_only", "not_allowed", }, matchingAny: map[string][]string{ "unencumbered": []string{}, "permissive": []string{}, "notice": []string{}, "reciprocal": []string{}, "restricted": []string{}, "restricted_if_statically_linked": []string{}, "proprietary": []string{}, "by_exception_only": []string{}, "not_allowed": []string{}, "restricted|proprietary": []string{}, }, expected: []string{}, }, { name: "restrictedplus", conditions: []string{"restricted", "restricted_if_statically_linked"}, plus: &[]string{"permissive", "notice", "restricted", "proprietary"}, matchingAny: map[string][]string{ "unencumbered": []string{}, "permissive": []string{"permissive"}, "notice": []string{"notice"}, "restricted": []string{"restricted"}, "restricted_if_statically_linked": []string{"restricted_if_statically_linked"}, "proprietary": []string{"proprietary"}, "restricted|proprietary": []string{"restricted", "proprietary"}, "by_exception_only": []string{}, "proprietary|by_exception_only": []string{"proprietary"}, }, expected: []string{"permissive", "notice", "restricted", "restricted_if_statically_linked", "proprietary"}, }, } for _, tt := range tests { toConditions := func(names []string) []LicenseCondition { result := make([]LicenseCondition, 0, len(names)) for _, name := range names { result = append(result, RecognizedConditionNames[name]) } return result } populate := func() LicenseConditionSet { testSet := NewLicenseConditionSet(toConditions(tt.conditions)...) if tt.plus != nil { testSet = testSet.Plus(toConditions(*tt.plus)...) } if tt.minus != nil { testSet = testSet.Minus(toConditions(*tt.minus)...) } return testSet } populateSet := func() LicenseConditionSet { testSet := NewLicenseConditionSet(toConditions(tt.conditions)...) if tt.plus != nil { testSet = testSet.Union(NewLicenseConditionSet(toConditions(*tt.plus)...)) } if tt.minus != nil { testSet = testSet.Difference(NewLicenseConditionSet(toConditions(*tt.minus)...)) } return testSet } populatePlusSet := func() LicenseConditionSet { testSet := NewLicenseConditionSet(toConditions(tt.conditions)...) if tt.plus != nil { testSet = testSet.Union(NewLicenseConditionSet(toConditions(*tt.plus)...)) } if tt.minus != nil { testSet = testSet.Minus(toConditions(*tt.minus)...) } return testSet } populateMinusSet := func() LicenseConditionSet { testSet := NewLicenseConditionSet(toConditions(tt.conditions)...) if tt.plus != nil { testSet = testSet.Plus(toConditions(*tt.plus)...) } if tt.minus != nil { testSet = testSet.Difference(NewLicenseConditionSet(toConditions(*tt.minus)...)) } return testSet } checkMatching := func(cs LicenseConditionSet, t *testing.T) { for data, expectedNames := range tt.matchingAny { expectedConditions := toConditions(expectedNames) expected := NewLicenseConditionSet(expectedConditions...) actual := cs.MatchingAny(toConditions(strings.Split(data, "|"))...) actualNames := actual.Names() t.Logf("MatchingAny(%s): actual set %#v %s", data, actual, actual.String()) t.Logf("MatchingAny(%s): expected set %#v %s", data, expected, expected.String()) if actual != expected { t.Errorf("MatchingAny(%s): got %#v, want %#v", data, actual, expected) continue } if len(actualNames) != len(expectedNames) { t.Errorf("len(MatchinAny(%s).Names()): got %d, want %d", data, len(actualNames), len(expectedNames)) } else { for i := 0; i < len(actualNames); i++ { if actualNames[i] != expectedNames[i] { t.Errorf("MatchingAny(%s).Names()[%d]: got %s, want %s", data, i, actualNames[i], expectedNames[i]) break } } } actualConditions := actual.AsList() if len(actualConditions) != len(expectedConditions) { t.Errorf("len(MatchingAny(%s).AsList()): got %d, want %d", data, len(actualNames), len(expectedNames)) } else { for i := 0; i < len(actualNames); i++ { if actualNames[i] != expectedNames[i] { t.Errorf("MatchingAny(%s).AsList()[%d]: got %s, want %s", data, i, actualNames[i], expectedNames[i]) break } } } } } checkMatchingSet := func(cs LicenseConditionSet, t *testing.T) { for data, expectedNames := range tt.matchingAny { expected := NewLicenseConditionSet(toConditions(expectedNames)...) actual := cs.MatchingAnySet(NewLicenseConditionSet(toConditions(strings.Split(data, "|"))...)) actualNames := actual.Names() t.Logf("MatchingAnySet(%s): actual set %#v %s", data, actual, actual.String()) t.Logf("MatchingAnySet(%s): expected set %#v %s", data, expected, expected.String()) if actual != expected { t.Errorf("MatchingAnySet(%s): got %#v, want %#v", data, actual, expected) continue } if len(actualNames) != len(expectedNames) { t.Errorf("len(MatchingAnySet(%s).Names()): got %d, want %d", data, len(actualNames), len(expectedNames)) } else { for i := 0; i < len(actualNames); i++ { if actualNames[i] != expectedNames[i] { t.Errorf("MatchingAnySet(%s).Names()[%d]: got %s, want %s", data, i, actualNames[i], expectedNames[i]) break } } } expectedConditions := toConditions(expectedNames) actualConditions := actual.AsList() if len(actualConditions) != len(expectedConditions) { t.Errorf("len(MatchingAnySet(%s).AsList()): got %d, want %d", data, len(actualNames), len(expectedNames)) } else { for i := 0; i < len(actualNames); i++ { if actualNames[i] != expectedNames[i] { t.Errorf("MatchingAnySet(%s).AsList()[%d]: got %s, want %s", data, i, actualNames[i], expectedNames[i]) break } } } } } checkExpected := func(actual LicenseConditionSet, t *testing.T) bool { t.Logf("checkExpected{%s}", strings.Join(tt.expected, ", ")) expectedConditions := toConditions(tt.expected) expected := NewLicenseConditionSet(expectedConditions...) actualNames := actual.Names() t.Logf("actual license condition set: %#v %s", actual, actual.String()) t.Logf("expected license condition set: %#v %s", expected, expected.String()) if actual != expected { t.Errorf("checkExpected: got %#v, want %#v", actual, expected) return false } if len(actualNames) != len(tt.expected) { t.Errorf("len(actual.Names()): got %d, want %d", len(actualNames), len(tt.expected)) } else { for i := 0; i < len(actualNames); i++ { if actualNames[i] != tt.expected[i] { t.Errorf("actual.Names()[%d]: got %s, want %s", i, actualNames[i], tt.expected[i]) break } } } actualConditions := actual.AsList() if len(actualConditions) != len(expectedConditions) { t.Errorf("len(actual.AsList()): got %d, want %d", len(actualConditions), len(expectedConditions)) } else { for i := 0; i < len(actualConditions); i++ { if actualConditions[i] != expectedConditions[i] { t.Errorf("actual.AsList()[%d]: got %s, want %s", i, actualConditions[i].Name(), expectedConditions[i].Name()) break } } } if len(tt.expected) == 0 { if !actual.IsEmpty() { t.Errorf("actual.IsEmpty(): got false, want true") } if actual.HasAny(expectedConditions...) { t.Errorf("actual.HasAny(): got true, want false") } } else { if actual.IsEmpty() { t.Errorf("actual.IsEmpty(): got true, want false") } if !actual.HasAny(expectedConditions...) { t.Errorf("actual.HasAny(all expected): got false, want true") } } if !actual.HasAll(expectedConditions...) { t.Errorf("actual.Hasll(all expected): want true, got false") } for _, expectedCondition := range expectedConditions { if !actual.HasAny(expectedCondition) { t.Errorf("actual.HasAny(%q): got false, want true", expectedCondition.Name()) } if !actual.HasAll(expectedCondition) { t.Errorf("actual.HasAll(%q): got false, want true", expectedCondition.Name()) } } notExpected := (AllLicenseConditions &^ expected) notExpectedList := notExpected.AsList() t.Logf("not expected license condition set: %#v %s", notExpected, notExpected.String()) if len(tt.expected) == 0 { if actual.HasAny(append(expectedConditions, notExpectedList...)...) { t.Errorf("actual.HasAny(all conditions): want false, got true") } } else { if !actual.HasAny(append(expectedConditions, notExpectedList...)...) { t.Errorf("actual.HasAny(all conditions): want true, got false") } } if len(notExpectedList) == 0 { if !actual.HasAll(append(expectedConditions, notExpectedList...)...) { t.Errorf("actual.HasAll(all conditions): want true, got false") } } else { if actual.HasAll(append(expectedConditions, notExpectedList...)...) { t.Errorf("actual.HasAll(all conditions): want false, got true") } } for _, unexpectedCondition := range notExpectedList { if actual.HasAny(unexpectedCondition) { t.Errorf("actual.HasAny(%q): got true, want false", unexpectedCondition.Name()) } if actual.HasAll(unexpectedCondition) { t.Errorf("actual.HasAll(%q): got true, want false", unexpectedCondition.Name()) } } return true } checkExpectedSet := func(actual LicenseConditionSet, t *testing.T) bool { t.Logf("checkExpectedSet{%s}", strings.Join(tt.expected, ", ")) expectedConditions := toConditions(tt.expected) expected := NewLicenseConditionSet(expectedConditions...) actualNames := actual.Names() t.Logf("actual license condition set: %#v %s", actual, actual.String()) t.Logf("expected license condition set: %#v %s", expected, expected.String()) if actual != expected { t.Errorf("checkExpectedSet: got %#v, want %#v", actual, expected) return false } if len(actualNames) != len(tt.expected) { t.Errorf("len(actual.Names()): got %d, want %d", len(actualNames), len(tt.expected)) } else { for i := 0; i < len(actualNames); i++ { if actualNames[i] != tt.expected[i] { t.Errorf("actual.Names()[%d]: got %s, want %s", i, actualNames[i], tt.expected[i]) break } } } actualConditions := actual.AsList() if len(actualConditions) != len(expectedConditions) { t.Errorf("len(actual.AsList()): got %d, want %d", len(actualConditions), len(expectedConditions)) } else { for i := 0; i < len(actualConditions); i++ { if actualConditions[i] != expectedConditions[i] { t.Errorf("actual.AsList()[%d}: got %s, want %s", i, actualConditions[i].Name(), expectedConditions[i].Name()) break } } } if len(tt.expected) == 0 { if !actual.IsEmpty() { t.Errorf("actual.IsEmpty(): got false, want true") } if actual.MatchesAnySet(expected) { t.Errorf("actual.MatchesAnySet({}): got true, want false") } if actual.MatchesEverySet(expected, expected) { t.Errorf("actual.MatchesEverySet({}, {}): want false, got true") } } else { if actual.IsEmpty() { t.Errorf("actual.IsEmpty(): got true, want false") } if !actual.MatchesAnySet(expected) { t.Errorf("actual.MatchesAnySet({all expected}): want true, got false") } if !actual.MatchesEverySet(expected, expected) { t.Errorf("actual.MatchesEverySet({all expected}, {all expected}): want true, got false") } } notExpected := (AllLicenseConditions &^ expected) t.Logf("not expected license condition set: %#v %s", notExpected, notExpected.String()) if len(tt.expected) == 0 { if actual.MatchesAnySet(expected, notExpected) { t.Errorf("empty actual.MatchesAnySet({expected}, {not expected}): want false, got true") } } else { if !actual.MatchesAnySet(expected, notExpected) { t.Errorf("actual.MatchesAnySet({expected}, {not expected}): want true, got false") } } if actual.MatchesAnySet(notExpected) { t.Errorf("actual.MatchesAnySet({not expected}): want false, got true") } if actual.MatchesEverySet(notExpected) { t.Errorf("actual.MatchesEverySet({not expected}): want false, got true") } if actual.MatchesEverySet(expected, notExpected) { t.Errorf("actual.MatchesEverySet({expected}, {not expected}): want false, got true") } if !actual.Difference(expected).IsEmpty() { t.Errorf("actual.Difference({expected}).IsEmpty(): want true, got false") } if expected != actual.Intersection(expected) { t.Errorf("expected == actual.Intersection({expected}): want true, got false (%#v != %#v)", expected, actual.Intersection(expected)) } if actual != actual.Intersection(expected) { t.Errorf("actual == actual.Intersection({expected}): want true, got false (%#v != %#v)", actual, actual.Intersection(expected)) } return true } t.Run(tt.name, func(t *testing.T) { cs := populate() if checkExpected(cs, t) { checkMatching(cs, t) } if checkExpectedSet(cs, t) { checkMatchingSet(cs, t) } }) t.Run(tt.name+"_sets", func(t *testing.T) { cs := populateSet() if checkExpected(cs, t) { checkMatching(cs, t) } if checkExpectedSet(cs, t) { checkMatchingSet(cs, t) } }) t.Run(tt.name+"_plusset", func(t *testing.T) { cs := populatePlusSet() if checkExpected(cs, t) { checkMatching(cs, t) } if checkExpectedSet(cs, t) { checkMatchingSet(cs, t) } }) t.Run(tt.name+"_minusset", func(t *testing.T) { cs := populateMinusSet() if checkExpected(cs, t) { checkMatching(cs, t) } if checkExpectedSet(cs, t) { checkMatchingSet(cs, t) } }) } }