• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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