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