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