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 "bytes" 19 "fmt" 20 "sort" 21 "strings" 22 "testing" 23) 24 25func TestPolicy_edgeConditions(t *testing.T) { 26 tests := []struct { 27 name string 28 edge annotated 29 treatAsAggregate bool 30 otherCondition string 31 expectedDepActions []string 32 expectedTargetConditions []string 33 }{ 34 { 35 name: "firstparty", 36 edge: annotated{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, 37 expectedDepActions: []string{}, 38 expectedTargetConditions: []string{}, 39 }, 40 { 41 name: "notice", 42 edge: annotated{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, 43 expectedDepActions: []string{}, 44 expectedTargetConditions: []string{}, 45 }, 46 { 47 name: "fponlgpl", 48 edge: annotated{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}}, 49 expectedDepActions: []string{ 50 "apacheBin.meta_lic:lgplLib.meta_lic:restricted_allows_dynamic_linking", 51 "lgplLib.meta_lic:lgplLib.meta_lic:restricted_allows_dynamic_linking", 52 }, 53 expectedTargetConditions: []string{}, 54 }, 55 { 56 name: "fponlgpldynamic", 57 edge: annotated{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, 58 expectedDepActions: []string{}, 59 expectedTargetConditions: []string{}, 60 }, 61 { 62 name: "fpongpl", 63 edge: annotated{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}}, 64 expectedDepActions: []string{ 65 "apacheBin.meta_lic:gplLib.meta_lic:restricted", 66 "gplLib.meta_lic:gplLib.meta_lic:restricted", 67 }, 68 expectedTargetConditions: []string{}, 69 }, 70 { 71 name: "fpongpldynamic", 72 edge: annotated{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, 73 expectedDepActions: []string{ 74 "apacheBin.meta_lic:gplLib.meta_lic:restricted", 75 "gplLib.meta_lic:gplLib.meta_lic:restricted", 76 }, 77 expectedTargetConditions: []string{}, 78 }, 79 { 80 name: "independentmodule", 81 edge: annotated{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, 82 expectedDepActions: []string{}, 83 expectedTargetConditions: []string{}, 84 }, 85 { 86 name: "independentmodulestatic", 87 edge: annotated{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}}, 88 expectedDepActions: []string{ 89 "apacheBin.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception", 90 "gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception", 91 }, 92 expectedTargetConditions: []string{}, 93 }, 94 { 95 name: "dependentmodule", 96 edge: annotated{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, 97 expectedDepActions: []string{ 98 "dependentModule.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception", 99 "gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted_with_classpath_exception", 100 }, 101 expectedTargetConditions: []string{}, 102 }, 103 104 { 105 name: "lgplonfp", 106 edge: annotated{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, 107 expectedDepActions: []string{}, 108 expectedTargetConditions: []string{"lgplBin.meta_lic:restricted_allows_dynamic_linking"}, 109 }, 110 { 111 name: "lgplonfpdynamic", 112 edge: annotated{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, 113 expectedDepActions: []string{}, 114 expectedTargetConditions: []string{}, 115 }, 116 { 117 name: "gplonfp", 118 edge: annotated{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, 119 expectedDepActions: []string{}, 120 expectedTargetConditions: []string{"gplBin.meta_lic:restricted"}, 121 }, 122 { 123 name: "gplcontainer", 124 edge: annotated{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}}, 125 treatAsAggregate: true, 126 expectedDepActions: []string{}, 127 expectedTargetConditions: []string{"gplContainer.meta_lic:restricted"}, 128 }, 129 { 130 name: "gploncontainer", 131 edge: annotated{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}}, 132 treatAsAggregate: true, 133 otherCondition: "gplLib.meta_lic:restricted", 134 expectedDepActions: []string{ 135 "apacheContainer.meta_lic:gplLib.meta_lic:restricted", 136 "apacheLib.meta_lic:gplLib.meta_lic:restricted", 137 "gplLib.meta_lic:gplLib.meta_lic:restricted", 138 }, 139 expectedTargetConditions: []string{}, 140 }, 141 { 142 name: "gplonbin", 143 edge: annotated{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, 144 treatAsAggregate: false, 145 otherCondition: "gplLib.meta_lic:restricted", 146 expectedDepActions: []string{ 147 "apacheBin.meta_lic:gplLib.meta_lic:restricted", 148 "apacheLib.meta_lic:gplLib.meta_lic:restricted", 149 "gplLib.meta_lic:gplLib.meta_lic:restricted", 150 }, 151 expectedTargetConditions: []string{"gplLib.meta_lic:restricted"}, 152 }, 153 { 154 name: "gplonfpdynamic", 155 edge: annotated{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, 156 expectedDepActions: []string{}, 157 expectedTargetConditions: []string{"gplBin.meta_lic:restricted"}, 158 }, 159 { 160 name: "independentmodulereverse", 161 edge: annotated{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}}, 162 expectedDepActions: []string{}, 163 expectedTargetConditions: []string{}, 164 }, 165 { 166 name: "independentmodulereversestatic", 167 edge: annotated{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}}, 168 expectedDepActions: []string{}, 169 expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted_with_classpath_exception"}, 170 }, 171 { 172 name: "dependentmodulereverse", 173 edge: annotated{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}}, 174 expectedDepActions: []string{}, 175 expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted_with_classpath_exception"}, 176 }, 177 { 178 name: "ponr", 179 edge: annotated{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}}, 180 expectedDepActions: []string{ 181 "proprietary.meta_lic:gplLib.meta_lic:restricted", 182 "gplLib.meta_lic:gplLib.meta_lic:restricted", 183 }, 184 expectedTargetConditions: []string{}, 185 }, 186 { 187 name: "ronp", 188 edge: annotated{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}}, 189 expectedDepActions: []string{}, 190 expectedTargetConditions: []string{"gplBin.meta_lic:restricted"}, 191 }, 192 { 193 name: "noticeonb_e_o", 194 edge: annotated{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}}, 195 expectedDepActions: []string{}, 196 expectedTargetConditions: []string{}, 197 }, 198 { 199 name: "b_e_oonnotice", 200 edge: annotated{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}}, 201 expectedDepActions: []string{}, 202 expectedTargetConditions: []string{}, 203 }, 204 { 205 name: "noticeonrecip", 206 edge: annotated{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}}, 207 expectedDepActions: []string{}, 208 expectedTargetConditions: []string{}, 209 }, 210 { 211 name: "reciponnotice", 212 edge: annotated{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, 213 expectedDepActions: []string{}, 214 expectedTargetConditions: []string{}, 215 }, 216 } 217 for _, tt := range tests { 218 t.Run(tt.name, func(t *testing.T) { 219 fs := make(testFS) 220 stderr := &bytes.Buffer{} 221 target := meta[tt.edge.target] + fmt.Sprintf("deps: {\n file: \"%s\"\n", tt.edge.dep) 222 for _, ann := range tt.edge.annotations { 223 target += fmt.Sprintf(" annotations: \"%s\"\n", ann) 224 } 225 fs[tt.edge.target] = []byte(target + "}\n") 226 fs[tt.edge.dep] = []byte(meta[tt.edge.dep]) 227 lg, err := ReadLicenseGraph(&fs, stderr, []string{tt.edge.target}) 228 if err != nil { 229 t.Errorf("unexpected error reading graph: %s", err) 230 return 231 } 232 edge := lg.Edges()[0] 233 // simulate a condition inherited from another edge/dependency. 234 otherTarget := "" 235 otherCondition := "" 236 var otn *TargetNode 237 if len(tt.otherCondition) > 0 { 238 fields := strings.Split(tt.otherCondition, ":") 239 otherTarget = fields[0] 240 otherCondition = fields[1] 241 otn = &TargetNode{name: otherTarget} 242 // other target must exist in graph 243 lg.targets[otherTarget] = otn 244 otn.licenseConditions = LicenseConditionSet(RecognizedConditionNames[otherCondition]) 245 } 246 targets := make(map[string]*TargetNode) 247 targets[edge.target.name] = edge.target 248 targets[edge.dependency.name] = edge.dependency 249 if otn != nil { 250 targets[otn.name] = otn 251 } 252 if tt.expectedDepActions != nil { 253 t.Run("depConditionsPropagatingToTarget", func(t *testing.T) { 254 depConditions := edge.dependency.LicenseConditions() 255 if otherTarget != "" { 256 // simulate a sub-dependency's condition having already propagated up to dep and about to go to target 257 otherCs := otn.LicenseConditions() 258 depConditions |= otherCs 259 } 260 t.Logf("calculate target actions for edge=%s, dep conditions=%04x, treatAsAggregate=%v", edge.String(), depConditions, tt.treatAsAggregate) 261 csActual := depConditionsPropagatingToTarget(lg, edge, depConditions, tt.treatAsAggregate) 262 t.Logf("calculated target conditions as %04x{%s}", csActual, strings.Join(csActual.Names(), ", ")) 263 csExpected := NewLicenseConditionSet() 264 for _, triple := range tt.expectedDepActions { 265 fields := strings.Split(triple, ":") 266 expectedConditions := NewLicenseConditionSet() 267 for _, cname := range fields[2:] { 268 expectedConditions = expectedConditions.Plus(RecognizedConditionNames[cname]) 269 } 270 csExpected |= expectedConditions 271 } 272 t.Logf("expected target conditions as %04x{%s}", csExpected, strings.Join(csExpected.Names(), ", ")) 273 if csActual != csExpected { 274 t.Errorf("unexpected license conditions: got %04x, want %04x", csActual, csExpected) 275 } 276 }) 277 } 278 if tt.expectedTargetConditions != nil { 279 t.Run("targetConditionsPropagatingToDep", func(t *testing.T) { 280 targetConditions := edge.target.LicenseConditions() 281 if otherTarget != "" { 282 targetConditions = targetConditions.Union(otn.licenseConditions) 283 } 284 t.Logf("calculate dep conditions for edge=%s, target conditions=%v, treatAsAggregate=%v", edge.String(), targetConditions.Names(), tt.treatAsAggregate) 285 cs := targetConditionsPropagatingToDep(lg, edge, targetConditions, tt.treatAsAggregate, AllResolutions) 286 t.Logf("calculated dep conditions as %v", cs.Names()) 287 actual := cs.Names() 288 sort.Strings(actual) 289 expected := make([]string, 0) 290 for _, expectedDepCondition := range tt.expectedTargetConditions { 291 expected = append(expected, strings.Split(expectedDepCondition, ":")[1]) 292 } 293 sort.Strings(expected) 294 if len(actual) != len(expected) { 295 t.Errorf("unexpected number of target conditions: got %v with %d conditions, want %v with %d conditions", 296 actual, len(actual), expected, len(expected)) 297 } else { 298 for i := 0; i < len(actual); i++ { 299 if actual[i] != expected[i] { 300 t.Errorf("unexpected target condition at element %d: got %q, want %q", 301 i, actual[i], expected[i]) 302 } 303 } 304 } 305 }) 306 } 307 }) 308 } 309} 310