blob: 46df45e00f4cc7d9ee7dce3b99d84066b34fb33e [file] [log] [blame]
Bob Badoura99ac622021-10-25 16:21:00 -07001// 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 "io/fs"
21 "sort"
22 "strings"
23 "testing"
24)
25
26const (
27 // AOSP starts a test metadata file for Android Apache-2.0 licensing.
28 AOSP = `` +
29 `package_name: "Android"
30license_kinds: "SPDX-license-identifier-Apache-2.0"
31license_conditions: "notice"
32`
33)
34
35// toConditionList converts a test data map of condition name to origin names into a ConditionList.
36func toConditionList(lg *LicenseGraph, conditions map[string][]string) ConditionList {
37 cl := make(ConditionList, 0)
38 for name, origins := range conditions {
39 for _, origin := range origins {
40 cl = append(cl, LicenseCondition{name, newTestNode(lg, origin)})
41 }
42 }
43 return cl
44}
45
46// newTestNode constructs a test node in the license graph.
47func newTestNode(lg *LicenseGraph, targetName string) *TargetNode {
48 if _, ok := lg.targets[targetName]; !ok {
49 lg.targets[targetName] = &TargetNode{name: targetName}
50 }
51 return lg.targets[targetName]
52}
53
54// testFS implements a test file system (fs.FS) simulated by a map from filename to []byte content.
55type testFS map[string][]byte
56
57// Open implements fs.FS.Open() to open a file based on the filename.
58func (fs *testFS) Open(name string) (fs.File, error) {
59 if _, ok := (*fs)[name]; !ok {
60 return nil, fmt.Errorf("unknown file %q", name)
61 }
62 return &testFile{fs, name, 0}, nil
63}
64
65// testFile implements a test file (fs.File) based on testFS above.
66type testFile struct {
67 fs *testFS
68 name string
69 posn int
70}
71
72// Stat not implemented to obviate implementing fs.FileInfo.
73func (f *testFile) Stat() (fs.FileInfo, error) {
74 return nil, fmt.Errorf("unimplemented")
75}
76
77// Read copies bytes from the testFS map.
78func (f *testFile) Read(b []byte) (int, error) {
79 if f.posn < 0 {
80 return 0, fmt.Errorf("file not open: %q", f.name)
81 }
82 if f.posn >= len((*f.fs)[f.name]) {
83 return 0, io.EOF
84 }
85 n := copy(b, (*f.fs)[f.name][f.posn:])
86 f.posn += n
87 return n, nil
88}
89
90// Close marks the testFile as no longer in use.
91func (f *testFile) Close() error {
92 if f.posn < 0 {
93 return fmt.Errorf("file already closed: %q", f.name)
94 }
95 f.posn = -1
96 return nil
97}
98
99// edge describes test data edges to define test graphs.
100type edge struct {
101 target, dep string
102}
103
104// String returns a string representation of the edge.
105func (e edge) String() string {
106 return e.target + " -> " + e.dep
107}
108
109// byEdge orders edges by target then dep name then annotations.
110type byEdge []edge
111
112// Len returns the count of elements in the slice.
113func (l byEdge) Len() int { return len(l) }
114
115// Swap rearranges 2 elements of the slice so that each occupies the other's
116// former position.
117func (l byEdge) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
118
119// Less returns true when the `i`th element is lexicographically less than
120// the `j`th element.
121func (l byEdge) Less(i, j int) bool {
122 if l[i].target == l[j].target {
123 return l[i].dep < l[j].dep
124 }
125 return l[i].target < l[j].target
126}
127
128// res describes test data resolutions to define test resolution sets.
129type res struct {
130 attachesTo, actsOn, origin, condition string
131}
132
133// toResolutionSet converts a list of res test data into a test resolution set.
134func toResolutionSet(lg *LicenseGraph, data []res) *ResolutionSet {
135 rmap := make(map[*TargetNode]actionSet)
136 for _, r := range data {
137 attachesTo := newTestNode(lg, r.attachesTo)
138 actsOn := newTestNode(lg, r.actsOn)
139 origin := newTestNode(lg, r.origin)
140 if _, ok := rmap[attachesTo]; !ok {
141 rmap[attachesTo] = make(actionSet)
142 }
143 if _, ok := rmap[attachesTo][actsOn]; !ok {
144 rmap[attachesTo][actsOn] = newLicenseConditionSet()
145 }
146 rmap[attachesTo][actsOn].add(origin, r.condition)
147 }
148 return &ResolutionSet{rmap}
149}
150
151// checkSameActions compares an actual action set to an expected action set for a test.
152func checkSameActions(lg *LicenseGraph, asActual, asExpected actionSet, t *testing.T) {
153 rsActual := ResolutionSet{make(map[*TargetNode]actionSet)}
154 rsExpected := ResolutionSet{make(map[*TargetNode]actionSet)}
155 testNode := newTestNode(lg, "test")
156 rsActual.resolutions[testNode] = asActual
157 rsExpected.resolutions[testNode] = asExpected
158 checkSame(&rsActual, &rsExpected, t)
159}
160
161// checkSame compares an actual resolution set to an expected resolution set for a test.
162func checkSame(rsActual, rsExpected *ResolutionSet, t *testing.T) {
163 expectedTargets := rsExpected.AttachesTo()
164 sort.Sort(expectedTargets)
165 for _, target := range expectedTargets {
166 if !rsActual.AttachesToTarget(target) {
167 t.Errorf("unexpected missing target: got AttachesToTarget(%q) is false in %s, want true in %s", target.name, rsActual, rsExpected)
168 continue
169 }
170 expectedRl := rsExpected.Resolutions(target)
171 sort.Sort(expectedRl)
172 actualRl := rsActual.Resolutions(target)
173 sort.Sort(actualRl)
174 if len(expectedRl) != len(actualRl) {
175 t.Errorf("unexpected number of resolutions attach to %q: got %s with %d elements, want %s with %d elements",
176 target.name, actualRl, len(actualRl), expectedRl, len(expectedRl))
177 continue
178 }
179 for i := 0; i < len(expectedRl); i++ {
180 if expectedRl[i].attachesTo.name != actualRl[i].attachesTo.name || expectedRl[i].actsOn.name != actualRl[i].actsOn.name {
181 t.Errorf("unexpected resolution attaches to %q at index %d: got %s, want %s",
182 target.name, i, actualRl[i].asString(), expectedRl[i].asString())
183 continue
184 }
185 expectedConditions := expectedRl[i].Resolves().AsList()
186 actualConditions := actualRl[i].Resolves().AsList()
187 sort.Sort(expectedConditions)
188 sort.Sort(actualConditions)
189 if len(expectedConditions) != len(actualConditions) {
190 t.Errorf("unexpected number of conditions apply to %q acting on %q: got %s with %d elements, want %s with %d elements",
191 target.name, expectedRl[i].actsOn.name,
192 actualConditions, len(actualConditions),
193 expectedConditions, len(expectedConditions))
194 continue
195 }
196 for j := 0; j < len(expectedConditions); j++ {
197 if expectedConditions[j] != actualConditions[j] {
198 t.Errorf("unexpected condition attached to %q acting on %q at index %d: got %s at index %d in %s, want %s in %s",
199 target.name, expectedRl[i].actsOn.name, i,
200 actualConditions[j].asString(":"), j, actualConditions,
201 expectedConditions[j].asString(":"), expectedConditions)
202 }
203 }
204 }
205
206 }
207 actualTargets := rsActual.AttachesTo()
208 sort.Sort(actualTargets)
209 for i, target := range actualTargets {
210 if !rsExpected.AttachesToTarget(target) {
211 t.Errorf("unexpected target: got %q element %d in AttachesTo() %s with %d elements in %s, want %s with %d elements in %s",
212 target.name, i, actualTargets, len(actualTargets), rsActual, expectedTargets, len(expectedTargets), rsExpected)
213 }
214 }
215}