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 "fmt" 19 "io" 20 "sort" 21 "strings" 22 "testing" 23 24 "android/soong/tools/compliance/testfs" 25) 26 27const ( 28 // AOSP starts a test metadata file for Android Apache-2.0 licensing. 29 AOSP = `` + 30 `package_name: "Android" 31license_kinds: "SPDX-license-identifier-Apache-2.0" 32license_conditions: "notice" 33` 34 35 // GPL starts a test metadata file for GPL 2.0 licensing. 36 GPL = `` + 37 `package_name: "Free Software" 38license_kinds: "SPDX-license-identifier-GPL-2.0" 39license_conditions: "restricted" 40` 41 42 // Classpath starts a test metadata file for GPL 2.0 with classpath exception licensing. 43 Classpath = `` + 44 `package_name: "Free Software" 45license_kinds: "SPDX-license-identifier-GPL-2.0-with-classpath-exception" 46license_conditions: "permissive" 47` 48 49 // DependentModule starts a test metadata file for a module in the same package as `Classpath`. 50 DependentModule = `` + 51 `package_name: "Free Software" 52license_kinds: "SPDX-license-identifier-MIT" 53license_conditions: "notice" 54` 55 56 // LGPL starts a test metadata file for a module with LGPL 2.0 licensing. 57 LGPL = `` + 58 `package_name: "Free Library" 59license_kinds: "SPDX-license-identifier-LGPL-2.0" 60license_conditions: "restricted_if_statically_linked" 61` 62 63 // MPL starts a test metadata file for a module with MPL 2.0 reciprical licensing. 64 MPL = `` + 65 `package_name: "Reciprocal" 66license_kinds: "SPDX-license-identifier-MPL-2.0" 67license_conditions: "reciprocal" 68` 69 70 // MIT starts a test metadata file for a module with generic notice (MIT) licensing. 71 MIT = `` + 72 `package_name: "Android" 73license_kinds: "SPDX-license-identifier-MIT" 74license_conditions: "notice" 75` 76 77 // Proprietary starts a test metadata file for a module with proprietary licensing. 78 Proprietary = `` + 79 `package_name: "Android" 80license_kinds: "legacy_proprietary" 81license_conditions: "proprietary" 82` 83 84 // ByException starts a test metadata file for a module with by_exception_only licensing. 85 ByException = `` + 86 `package_name: "Special" 87license_kinds: "legacy_by_exception_only" 88license_conditions: "by_exception_only" 89` 90) 91 92var ( 93 // meta maps test file names to metadata file content without dependencies. 94 meta = map[string]string{ 95 "apacheBin.meta_lic": AOSP, 96 "apacheLib.meta_lic": AOSP, 97 "apacheContainer.meta_lic": AOSP + "is_container: true\n", 98 "dependentModule.meta_lic": DependentModule, 99 "gplWithClasspathException.meta_lic": Classpath, 100 "gplBin.meta_lic": GPL, 101 "gplLib.meta_lic": GPL, 102 "gplContainer.meta_lic": GPL + "is_container: true\n", 103 "lgplBin.meta_lic": LGPL, 104 "lgplLib.meta_lic": LGPL, 105 "mitBin.meta_lic": MIT, 106 "mitLib.meta_lic": MIT, 107 "mplBin.meta_lic": MPL, 108 "mplLib.meta_lic": MPL, 109 "proprietary.meta_lic": Proprietary, 110 "by_exception.meta_lic": ByException, 111 } 112) 113 114// newTestNode constructs a test node in the license graph. 115func newTestNode(lg *LicenseGraph, targetName string) *TargetNode { 116 if tn, alreadyExists := lg.targets[targetName]; alreadyExists { 117 return tn 118 } 119 tn := &TargetNode{name: targetName} 120 lg.targets[targetName] = tn 121 return tn 122} 123 124// newTestCondition constructs a test license condition. 125func newTestCondition(conditionName string) LicenseCondition { 126 cl := LicenseConditionSetFromNames(conditionName).AsList() 127 if len(cl) == 0 { 128 panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName)) 129 } else if len(cl) != 1 { 130 panic(fmt.Errorf("unexpected multiple conditions from condition name: %q: got %d, want 1", conditionName, len(cl))) 131 } 132 lc := cl[0] 133 return lc 134} 135 136// newTestConditionSet constructs a test license condition set. 137func newTestConditionSet(conditionName []string) LicenseConditionSet { 138 cs := LicenseConditionSetFromNames(conditionName...) 139 if cs.IsEmpty() { 140 panic(fmt.Errorf("attempt to create unrecognized condition: %q", conditionName)) 141 } 142 return cs 143} 144 145// edge describes test data edges to define test graphs. 146type edge struct { 147 target, dep string 148} 149 150// String returns a string representation of the edge. 151func (e edge) String() string { 152 return e.target + " -> " + e.dep 153} 154 155// byEdge orders edges by target then dep name then annotations. 156type byEdge []edge 157 158// Len returns the count of elements in the slice. 159func (l byEdge) Len() int { return len(l) } 160 161// Swap rearranges 2 elements of the slice so that each occupies the other's 162// former position. 163func (l byEdge) Swap(i, j int) { l[i], l[j] = l[j], l[i] } 164 165// Less returns true when the `i`th element is lexicographically less than 166// the `j`th element. 167func (l byEdge) Less(i, j int) bool { 168 if l[i].target == l[j].target { 169 return l[i].dep < l[j].dep 170 } 171 return l[i].target < l[j].target 172} 173 174// annotated describes annotated test data edges to define test graphs. 175type annotated struct { 176 target, dep string 177 annotations []string 178} 179 180func (e annotated) String() string { 181 if e.annotations != nil { 182 return e.target + " -> " + e.dep + " [" + strings.Join(e.annotations, ", ") + "]" 183 } 184 return e.target + " -> " + e.dep 185} 186 187func (e annotated) IsEqualTo(other annotated) bool { 188 if e.target != other.target { 189 return false 190 } 191 if e.dep != other.dep { 192 return false 193 } 194 if len(e.annotations) != len(other.annotations) { 195 return false 196 } 197 a1 := append([]string{}, e.annotations...) 198 a2 := append([]string{}, other.annotations...) 199 for i := 0; i < len(a1); i++ { 200 if a1[i] != a2[i] { 201 return false 202 } 203 } 204 return true 205} 206 207// toGraph converts a list of roots and a list of annotated edges into a test license graph. 208func toGraph(stderr io.Writer, roots []string, edges []annotated) (*LicenseGraph, error) { 209 deps := make(map[string][]annotated) 210 for _, root := range roots { 211 deps[root] = []annotated{} 212 } 213 for _, edge := range edges { 214 if prev, ok := deps[edge.target]; ok { 215 deps[edge.target] = append(prev, edge) 216 } else { 217 deps[edge.target] = []annotated{edge} 218 } 219 if _, ok := deps[edge.dep]; !ok { 220 deps[edge.dep] = []annotated{} 221 } 222 } 223 fs := make(testfs.TestFS) 224 for file, edges := range deps { 225 body := meta[file] 226 for _, edge := range edges { 227 body += fmt.Sprintf("deps: {\n file: %q\n", edge.dep) 228 for _, ann := range edge.annotations { 229 body += fmt.Sprintf(" annotations: %q\n", ann) 230 } 231 body += "}\n" 232 } 233 fs[file] = []byte(body) 234 } 235 236 return ReadLicenseGraph(&fs, stderr, roots) 237} 238 239// logGraph outputs a representation of the graph to a test log. 240func logGraph(lg *LicenseGraph, t *testing.T) { 241 t.Logf("license graph:") 242 t.Logf(" targets:") 243 for _, target := range lg.Targets() { 244 t.Logf(" %s%s in package %q", target.Name(), target.LicenseConditions().String(), target.PackageName()) 245 } 246 t.Logf(" /targets") 247 t.Logf(" edges:") 248 for _, edge := range lg.Edges() { 249 t.Logf(" %s", edge.String()) 250 } 251 t.Logf(" /edges") 252 t.Logf("/license graph") 253} 254 255// byAnnotatedEdge orders edges by target then dep name then annotations. 256type byAnnotatedEdge []annotated 257 258func (l byAnnotatedEdge) Len() int { return len(l) } 259func (l byAnnotatedEdge) Swap(i, j int) { l[i], l[j] = l[j], l[i] } 260func (l byAnnotatedEdge) Less(i, j int) bool { 261 if l[i].target == l[j].target { 262 if l[i].dep == l[j].dep { 263 ai := append([]string{}, l[i].annotations...) 264 aj := append([]string{}, l[j].annotations...) 265 sort.Strings(ai) 266 sort.Strings(aj) 267 for k := 0; k < len(ai) && k < len(aj); k++ { 268 if ai[k] == aj[k] { 269 continue 270 } 271 return ai[k] < aj[k] 272 } 273 return len(ai) < len(aj) 274 } 275 return l[i].dep < l[j].dep 276 } 277 return l[i].target < l[j].target 278} 279 280// act describes test data resolution actions to define test action sets. 281type act struct { 282 actsOn, condition string 283} 284 285// String returns a human-readable string representing the test action. 286func (a act) String() string { 287 return fmt.Sprintf("%s{%s}", a.actsOn, a.condition) 288} 289 290// toActionSet converts a list of act test data into a test action set. 291func toActionSet(lg *LicenseGraph, data []act) ActionSet { 292 as := make(ActionSet) 293 for _, a := range data { 294 actsOn := newTestNode(lg, a.actsOn) 295 cs := newTestConditionSet(strings.Split(a.condition, "|")) 296 as[actsOn] = cs 297 } 298 return as 299} 300 301// res describes test data resolutions to define test resolution sets. 302type res struct { 303 attachesTo, actsOn, condition string 304} 305 306// toResolutionSet converts a list of res test data into a test resolution set. 307func toResolutionSet(lg *LicenseGraph, data []res) ResolutionSet { 308 rmap := make(ResolutionSet) 309 for _, r := range data { 310 attachesTo := newTestNode(lg, r.attachesTo) 311 actsOn := newTestNode(lg, r.actsOn) 312 if _, ok := rmap[attachesTo]; !ok { 313 rmap[attachesTo] = make(ActionSet) 314 } 315 cs := newTestConditionSet(strings.Split(r.condition, "|")) 316 rmap[attachesTo][actsOn] |= cs 317 } 318 return rmap 319} 320 321// tcond associates a target name with '|' separated string conditions. 322type tcond struct { 323 target, conditions string 324} 325 326// action represents a single element of an ActionSet for testing. 327type action struct { 328 target *TargetNode 329 cs LicenseConditionSet 330} 331 332// String returns a human-readable string representation of the action. 333func (a action) String() string { 334 return fmt.Sprintf("%s%s", a.target.Name(), a.cs.String()) 335} 336 337// actionList represents an array of actions and a total order defined by 338// target name followed by license condition set. 339type actionList []action 340 341// String returns a human-readable string representation of the list. 342func (l actionList) String() string { 343 var sb strings.Builder 344 fmt.Fprintf(&sb, "[") 345 sep := "" 346 for _, a := range l { 347 fmt.Fprintf(&sb, "%s%s", sep, a.String()) 348 sep = ", " 349 } 350 fmt.Fprintf(&sb, "]") 351 return sb.String() 352} 353 354// Len returns the count of elements in the slice. 355func (l actionList) Len() int { return len(l) } 356 357// Swap rearranges 2 elements of the slice so that each occupies the other's 358// former position. 359func (l actionList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } 360 361// Less returns true when the `i`th element is lexicographically less than 362// the `j`th element. 363func (l actionList) Less(i, j int) bool { 364 if l[i].target == l[j].target { 365 return l[i].cs < l[j].cs 366 } 367 return l[i].target.Name() < l[j].target.Name() 368} 369 370// asActionList represents the resolved license conditions in a license graph 371// as an actionList for comparison in a test. 372func asActionList(lg *LicenseGraph) actionList { 373 result := make(actionList, 0, len(lg.targets)) 374 for _, target := range lg.targets { 375 cs := target.resolution 376 if cs.IsEmpty() { 377 continue 378 } 379 result = append(result, action{target, cs}) 380 } 381 return result 382} 383 384// toActionList converts an array of tcond into an actionList for comparison 385// in a test. 386func toActionList(lg *LicenseGraph, actions []tcond) actionList { 387 result := make(actionList, 0, len(actions)) 388 for _, actn := range actions { 389 target := newTestNode(lg, actn.target) 390 cs := NewLicenseConditionSet() 391 for _, name := range strings.Split(actn.conditions, "|") { 392 lc, ok := RecognizedConditionNames[name] 393 if !ok { 394 panic(fmt.Errorf("Unrecognized test condition name: %q", name)) 395 } 396 cs = cs.Plus(lc) 397 } 398 result = append(result, action{target, cs}) 399 } 400 return result 401} 402 403// confl defines test data for a SourceSharePrivacyConflict as a target name, 404// source condition name, privacy condition name triple. 405type confl struct { 406 sourceNode, share, privacy string 407} 408 409// toConflictList converts confl test data into an array of 410// SourceSharePrivacyConflict for comparison in a test. 411func toConflictList(lg *LicenseGraph, data []confl) []SourceSharePrivacyConflict { 412 result := make([]SourceSharePrivacyConflict, 0, len(data)) 413 for _, c := range data { 414 fields := strings.Split(c.share, ":") 415 cshare := fields[1] 416 fields = strings.Split(c.privacy, ":") 417 cprivacy := fields[1] 418 result = append(result, SourceSharePrivacyConflict{ 419 newTestNode(lg, c.sourceNode), 420 newTestCondition(cshare), 421 newTestCondition(cprivacy), 422 }) 423 } 424 return result 425} 426 427// checkSameActions compares an actual action set to an expected action set for a test. 428func checkSameActions(lg *LicenseGraph, asActual, asExpected ActionSet, t *testing.T) { 429 rsActual := make(ResolutionSet) 430 rsExpected := make(ResolutionSet) 431 testNode := newTestNode(lg, "test") 432 rsActual[testNode] = asActual 433 rsExpected[testNode] = asExpected 434 checkSame(rsActual, rsExpected, t) 435} 436 437// checkSame compares an actual resolution set to an expected resolution set for a test. 438func checkSame(rsActual, rsExpected ResolutionSet, t *testing.T) { 439 t.Logf("actual resolution set: %s", rsActual.String()) 440 t.Logf("expected resolution set: %s", rsExpected.String()) 441 442 actualTargets := rsActual.AttachesTo() 443 sort.Sort(actualTargets) 444 445 expectedTargets := rsExpected.AttachesTo() 446 sort.Sort(expectedTargets) 447 448 t.Logf("actual targets: %s", actualTargets.String()) 449 t.Logf("expected targets: %s", expectedTargets.String()) 450 451 for _, target := range expectedTargets { 452 if !rsActual.AttachesToTarget(target) { 453 t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false, want true", target.name) 454 continue 455 } 456 expectedRl := rsExpected.Resolutions(target) 457 sort.Sort(expectedRl) 458 actualRl := rsActual.Resolutions(target) 459 sort.Sort(actualRl) 460 if len(expectedRl) != len(actualRl) { 461 t.Errorf("unexpected number of resolutions attach to %q: %d elements, %d elements", 462 target.name, len(actualRl), len(expectedRl)) 463 continue 464 } 465 for i := 0; i < len(expectedRl); i++ { 466 if expectedRl[i].attachesTo.name != actualRl[i].attachesTo.name || expectedRl[i].actsOn.name != actualRl[i].actsOn.name { 467 t.Errorf("unexpected resolution attaches to %q at index %d: got %s, want %s", 468 target.name, i, actualRl[i].asString(), expectedRl[i].asString()) 469 continue 470 } 471 expectedConditions := expectedRl[i].Resolves() 472 actualConditions := actualRl[i].Resolves() 473 if expectedConditions != actualConditions { 474 t.Errorf("unexpected conditions apply to %q acting on %q: got %#v with names %s, want %#v with names %s", 475 target.name, expectedRl[i].actsOn.name, 476 actualConditions, actualConditions.Names(), 477 expectedConditions, expectedConditions.Names()) 478 continue 479 } 480 } 481 482 } 483 for _, target := range actualTargets { 484 if !rsExpected.AttachesToTarget(target) { 485 t.Errorf("unexpected extra target: got expected.AttachesTo(%q) is false, want true", target.name) 486 } 487 } 488} 489 490// checkResolvesActions compares an actual action set to an expected action set for a test verifying the actual set 491// resolves all of the expected conditions. 492func checkResolvesActions(lg *LicenseGraph, asActual, asExpected ActionSet, t *testing.T) { 493 rsActual := make(ResolutionSet) 494 rsExpected := make(ResolutionSet) 495 testNode := newTestNode(lg, "test") 496 rsActual[testNode] = asActual 497 rsExpected[testNode] = asExpected 498 checkResolves(rsActual, rsExpected, t) 499} 500 501// checkResolves compares an actual resolution set to an expected resolution set for a test verifying the actual set 502// resolves all of the expected conditions. 503func checkResolves(rsActual, rsExpected ResolutionSet, t *testing.T) { 504 t.Logf("actual resolution set: %s", rsActual.String()) 505 t.Logf("expected resolution set: %s", rsExpected.String()) 506 507 actualTargets := rsActual.AttachesTo() 508 sort.Sort(actualTargets) 509 510 expectedTargets := rsExpected.AttachesTo() 511 sort.Sort(expectedTargets) 512 513 t.Logf("actual targets: %s", actualTargets.String()) 514 t.Logf("expected targets: %s", expectedTargets.String()) 515 516 for _, target := range expectedTargets { 517 if !rsActual.AttachesToTarget(target) { 518 t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false, want true", target.name) 519 continue 520 } 521 expectedRl := rsExpected.Resolutions(target) 522 sort.Sort(expectedRl) 523 actualRl := rsActual.Resolutions(target) 524 sort.Sort(actualRl) 525 if len(expectedRl) != len(actualRl) { 526 t.Errorf("unexpected number of resolutions attach to %q: %d elements, %d elements", 527 target.name, len(actualRl), len(expectedRl)) 528 continue 529 } 530 for i := 0; i < len(expectedRl); i++ { 531 if expectedRl[i].attachesTo.name != actualRl[i].attachesTo.name || expectedRl[i].actsOn.name != actualRl[i].actsOn.name { 532 t.Errorf("unexpected resolution attaches to %q at index %d: got %s, want %s", 533 target.name, i, actualRl[i].asString(), expectedRl[i].asString()) 534 continue 535 } 536 expectedConditions := expectedRl[i].Resolves() 537 actualConditions := actualRl[i].Resolves() 538 if expectedConditions != (expectedConditions & actualConditions) { 539 t.Errorf("expected conditions missing from %q acting on %q: got %#v with names %s, want %#v with names %s", 540 target.name, expectedRl[i].actsOn.name, 541 actualConditions, actualConditions.Names(), 542 expectedConditions, expectedConditions.Names()) 543 continue 544 } 545 } 546 547 } 548 for _, target := range actualTargets { 549 if !rsExpected.AttachesToTarget(target) { 550 t.Errorf("unexpected extra target: got expected.AttachesTo(%q) is false, want true", target.name) 551 } 552 } 553} 554