From 9ee7d03e1c9e20316e2aaa8b4ea7b5ee60516bed Mon Sep 17 00:00:00 2001 From: Bob Badour Date: Mon, 25 Oct 2021 16:51:48 -0700 Subject: [PATCH] compliance package policy and resolves package to read, consume, and analyze license metadata and dependency graph. Bug: 68860345 Bug: 151177513 Bug: 151953481 Change-Id: Ic08406fa2250a08ad26f2167d934f841c95d9148 --- tools/compliance/Android.bp | 16 + tools/compliance/policy/policy.go | 238 ++++++ tools/compliance/policy/policy_test.go | 300 +++++++ tools/compliance/policy/resolve.go | 217 +++++ tools/compliance/policy/resolve_test.go | 755 ++++++++++++++++++ tools/compliance/policy/resolvenotices.go | 21 + .../compliance/policy/resolvenotices_test.go | 467 +++++++++++ tools/compliance/policy/resolveprivacy.go | 21 + .../compliance/policy/resolveprivacy_test.go | 87 ++ tools/compliance/policy/resolveshare.go | 21 + tools/compliance/policy/resolveshare_test.go | 295 +++++++ .../policy/shareprivacyconflicts.go | 91 +++ .../policy/shareprivacyconflicts_test.go | 129 +++ tools/compliance/policy/shipped.go | 54 ++ tools/compliance/policy/shipped_test.go | 130 +++ tools/compliance/policy/walk.go | 76 ++ tools/compliance/policy/walk_test.go | 629 +++++++++++++++ tools/compliance/resolutionset_test.go | 4 +- tools/compliance/test_util.go | 193 +++++ 19 files changed, 3742 insertions(+), 2 deletions(-) create mode 100644 tools/compliance/policy/policy.go create mode 100644 tools/compliance/policy/policy_test.go create mode 100644 tools/compliance/policy/resolve.go create mode 100644 tools/compliance/policy/resolve_test.go create mode 100644 tools/compliance/policy/resolvenotices.go create mode 100644 tools/compliance/policy/resolvenotices_test.go create mode 100644 tools/compliance/policy/resolveprivacy.go create mode 100644 tools/compliance/policy/resolveprivacy_test.go create mode 100644 tools/compliance/policy/resolveshare.go create mode 100644 tools/compliance/policy/resolveshare_test.go create mode 100644 tools/compliance/policy/shareprivacyconflicts.go create mode 100644 tools/compliance/policy/shareprivacyconflicts_test.go create mode 100644 tools/compliance/policy/shipped.go create mode 100644 tools/compliance/policy/shipped_test.go create mode 100644 tools/compliance/policy/walk.go create mode 100644 tools/compliance/policy/walk_test.go diff --git a/tools/compliance/Android.bp b/tools/compliance/Android.bp index 648bc9a731..e56a471eb5 100644 --- a/tools/compliance/Android.bp +++ b/tools/compliance/Android.bp @@ -24,6 +24,14 @@ bootstrap_go_package { "condition.go", "conditionset.go", "graph.go", + "policy/policy.go", + "policy/resolve.go", + "policy/resolvenotices.go", + "policy/resolveshare.go", + "policy/resolveprivacy.go", + "policy/shareprivacyconflicts.go", + "policy/shipped.go", + "policy/walk.go", "readgraph.go", "resolution.go", "resolutionset.go", @@ -32,6 +40,14 @@ bootstrap_go_package { "condition_test.go", "conditionset_test.go", "readgraph_test.go", + "policy/policy_test.go", + "policy/resolve_test.go", + "policy/resolvenotices_test.go", + "policy/resolveshare_test.go", + "policy/resolveprivacy_test.go", + "policy/shareprivacyconflicts_test.go", + "policy/shipped_test.go", + "policy/walk_test.go", "resolutionset_test.go", "test_util.go", ], diff --git a/tools/compliance/policy/policy.go b/tools/compliance/policy/policy.go new file mode 100644 index 0000000000..9dab05bbf0 --- /dev/null +++ b/tools/compliance/policy/policy.go @@ -0,0 +1,238 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +import ( + "regexp" + "strings" +) + +var ( + // ImpliesUnencumbered lists the condition names representing an author attempt to disclaim copyright. + ImpliesUnencumbered = ConditionNames{"unencumbered"} + + // ImpliesPermissive lists the condition names representing copyrighted but "licensed without policy requirements". + ImpliesPermissive = ConditionNames{"permissive"} + + // ImpliesNotice lists the condition names implying a notice or attribution policy. + ImpliesNotice = ConditionNames{"unencumbered", "permissive", "notice", "reciprocal", "restricted", "proprietary", "by_exception_only"} + + // ImpliesReciprocal lists the condition names implying a local source-sharing policy. + ImpliesReciprocal = ConditionNames{"reciprocal"} + + // Restricted lists the condition names implying an infectious source-sharing policy. + ImpliesRestricted = ConditionNames{"restricted"} + + // ImpliesProprietary lists the condition names implying a confidentiality policy. + ImpliesProprietary = ConditionNames{"proprietary"} + + // ImpliesByExceptionOnly lists the condition names implying a policy for "license review and approval before use". + ImpliesByExceptionOnly = ConditionNames{"proprietary", "by_exception_only"} + + // ImpliesPrivate lists the condition names implying a source-code privacy policy. + ImpliesPrivate = ConditionNames{"proprietary"} + + // ImpliesShared lists the condition names implying a source-code sharing policy. + ImpliesShared = ConditionNames{"reciprocal", "restricted"} +) + +var ( + anyLgpl = regexp.MustCompile(`^SPDX-license-identifier-LGPL.*`) + versionedGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL-\p{N}.*`) + genericGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL$`) + ccBySa = regexp.MustCompile(`^SPDX-license-identifier-CC-BY.*-SA.*`) +) + +// Resolution happens in two passes: +// +// 1. A bottom-up traversal propagates license conditions up to targets from +// dendencies as needed. +// +// 2. For each condition of interest, a top-down traversal adjusts the attached +// conditions pushing restricted down from targets into linked dependencies. +// +// The behavior of the 2 passes gets controlled by the 2 functions below. +// +// The first function controls what happens during the bottom-up traversal. In +// general conditions flow up through static links but not other dependencies; +// except, restricted sometimes flows up through dynamic links. +// +// In general, too, the originating target gets acted on to resolve the +// condition (e.g. providing notice), but again restricted is special in that +// it requires acting on (i.e. sharing source of) both the originating module +// and the target using the module. +// +// The latter function controls what happens during the top-down traversal. In +// general, only restricted conditions flow down at all, and only through +// static links. +// +// Not all restricted licenses are create equal. Some have special rules or +// exceptions. e.g. LGPL or "with classpath excption". + +// depActionsApplicableToTarget returns the actions which propagate up an +// edge from dependency to target. +// +// This function sets the policy for the bottom-up traversal and how conditions +// flow up the graph from dependencies to targets. +// +// If a pure aggregation is built into a derivative work that is not a pure +// aggregation, per policy it ceases to be a pure aggregation in the context of +// that derivative work. The `treatAsAggregate` parameter will be false for +// non-aggregates and for aggregates in non-aggregate contexts. +func depActionsApplicableToTarget(e TargetEdge, depActions actionSet, treatAsAggregate bool) actionSet { + result := make(actionSet) + if edgeIsDerivation(e) { + result.addSet(depActions) + for _, cs := range depActions.byName(ImpliesRestricted) { + result.add(e.Target(), cs) + } + return result + } + if !edgeIsDynamicLink(e) { + return result + } + + restricted := depActions.byName(ImpliesRestricted) + for actsOn, cs := range restricted { + for _, lc := range cs.AsList() { + hasGpl := false + hasLgpl := false + hasClasspath := false + hasGeneric := false + hasOther := false + for _, kind := range lc.origin.LicenseKinds() { + if strings.HasSuffix(kind, "-with-classpath-exception") { + hasClasspath = true + } else if anyLgpl.MatchString(kind) { + hasLgpl = true + } else if versionedGpl.MatchString(kind) { + hasGpl = true + } else if genericGpl.MatchString(kind) { + hasGeneric = true + } else if kind == "legacy_restricted" || ccBySa.MatchString(kind) { + hasOther = true + } + } + if hasOther || hasGpl { + result.addCondition(actsOn, lc) + result.addCondition(e.Target(), lc) + continue + } + if hasClasspath && !edgeNodesAreIndependentModules(e) { + result.addCondition(actsOn, lc) + result.addCondition(e.Target(), lc) + continue + } + if hasLgpl || hasClasspath { + continue + } + if !hasGeneric { + continue + } + result.addCondition(actsOn, lc) + result.addCondition(e.Target(), lc) + } + } + return result +} + +// targetConditionsApplicableToDep returns the conditions which propagate down +// an edge from target to dependency. +// +// This function sets the policy for the top-down traversal and how conditions +// flow down the graph from targets to dependencies. +// +// If a pure aggregation is built into a derivative work that is not a pure +// aggregation, per policy it ceases to be a pure aggregation in the context of +// that derivative work. The `treatAsAggregate` parameter will be false for +// non-aggregates and for aggregates in non-aggregate contexts. +func targetConditionsApplicableToDep(e TargetEdge, targetConditions *LicenseConditionSet, treatAsAggregate bool) *LicenseConditionSet { + result := targetConditions.Copy() + + // reverse direction -- none of these apply to things depended-on, only to targets depending-on. + result.RemoveAllByName(ConditionNames{"unencumbered", "permissive", "notice", "reciprocal", "proprietary", "by_exception_only"}) + + if !edgeIsDerivation(e) && !edgeIsDynamicLink(e) { + // target is not a derivative work of dependency and is not linked to dependency + result.RemoveAllByName(ImpliesRestricted) + return result + } + if treatAsAggregate { + // If the author of a pure aggregate licenses it restricted, apply restricted to immediate dependencies. + // Otherwise, restricted does not propagate back down to dependencies. + restricted := result.ByName(ImpliesRestricted).AsList() + for _, lc := range restricted { + if lc.origin.name != e.e.target { + result.Remove(lc) + } + } + return result + } + if edgeIsDerivation(e) { + return result + } + restricted := result.ByName(ImpliesRestricted).AsList() + for _, lc := range restricted { + hasGpl := false + hasLgpl := false + hasClasspath := false + hasGeneric := false + hasOther := false + for _, kind := range lc.origin.LicenseKinds() { + if strings.HasSuffix(kind, "-with-classpath-exception") { + hasClasspath = true + } else if anyLgpl.MatchString(kind) { + hasLgpl = true + } else if versionedGpl.MatchString(kind) { + hasGpl = true + } else if genericGpl.MatchString(kind) { + hasGeneric = true + } else if kind == "legacy_restricted" || ccBySa.MatchString(kind) { + hasOther = true + } + } + if hasOther || hasGpl { + continue + } + if hasClasspath && !edgeNodesAreIndependentModules(e) { + continue + } + if hasGeneric && !hasLgpl && !hasClasspath { + continue + } + result.Remove(lc) + } + return result +} + +// edgeIsDynamicLink returns true for edges representing shared libraries +// linked dynamically at runtime. +func edgeIsDynamicLink(e TargetEdge) bool { + return e.e.annotations.HasAnnotation("dynamic") +} + +// edgeIsDerivation returns true for edges where the target is a derivative +// work of dependency. +func edgeIsDerivation(e TargetEdge) bool { + isDynamic := e.e.annotations.HasAnnotation("dynamic") + isToolchain := e.e.annotations.HasAnnotation("toolchain") + return !isDynamic && !isToolchain +} + +// edgeNodesAreIndependentModules returns true for edges where the target and +// dependency are independent modules. +func edgeNodesAreIndependentModules(e TargetEdge) bool { + return e.Target().PackageName() != e.Dependency().PackageName() +} diff --git a/tools/compliance/policy/policy_test.go b/tools/compliance/policy/policy_test.go new file mode 100644 index 0000000000..aea307f87e --- /dev/null +++ b/tools/compliance/policy/policy_test.go @@ -0,0 +1,300 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +import ( + "bytes" + "fmt" + "sort" + "strings" + "testing" +) + +func TestPolicy_edgeConditions(t *testing.T) { + tests := []struct { + name string + edge annotated + treatAsAggregate bool + otherCondition string + expectedDepActions []string + expectedTargetConditions []string + }{ + { + name: "firstparty", + edge: annotated{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + expectedDepActions: []string{"apacheLib.meta_lic:apacheLib.meta_lic:notice"}, + expectedTargetConditions: []string{}, + }, + { + name: "notice", + edge: annotated{"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + expectedDepActions: []string{"mitLib.meta_lic:mitLib.meta_lic:notice"}, + expectedTargetConditions: []string{}, + }, + { + name: "fponlgpl", + edge: annotated{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}}, + expectedDepActions: []string{ + "apacheBin.meta_lic:lgplLib.meta_lic:restricted", + "lgplLib.meta_lic:lgplLib.meta_lic:restricted", + }, + expectedTargetConditions: []string{}, + }, + { + name: "fponlgpldynamic", + edge: annotated{"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + expectedDepActions: []string{}, + expectedTargetConditions: []string{}, + }, + { + name: "fpongpl", + edge: annotated{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}}, + expectedDepActions: []string{ + "apacheBin.meta_lic:gplLib.meta_lic:restricted", + "gplLib.meta_lic:gplLib.meta_lic:restricted", + }, + expectedTargetConditions: []string{}, + }, + { + name: "fpongpldynamic", + edge: annotated{"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + expectedDepActions: []string{ + "apacheBin.meta_lic:gplLib.meta_lic:restricted", + "gplLib.meta_lic:gplLib.meta_lic:restricted", + }, + expectedTargetConditions: []string{}, + }, + { + name: "independentmodule", + edge: annotated{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + expectedDepActions: []string{}, + expectedTargetConditions: []string{}, + }, + { + name: "independentmodulestatic", + edge: annotated{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}}, + expectedDepActions: []string{ + "apacheBin.meta_lic:gplWithClasspathException.meta_lic:restricted", + "gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted", + }, + expectedTargetConditions: []string{}, + }, + { + name: "dependentmodule", + edge: annotated{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + expectedDepActions: []string{ + "dependentModule.meta_lic:gplWithClasspathException.meta_lic:restricted", + "gplWithClasspathException.meta_lic:gplWithClasspathException.meta_lic:restricted", + }, + expectedTargetConditions: []string{}, + }, + + { + name: "lgplonfp", + edge: annotated{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + expectedDepActions: []string{"apacheLib.meta_lic:apacheLib.meta_lic:notice"}, + expectedTargetConditions: []string{"lgplBin.meta_lic:restricted"}, + }, + { + name: "lgplonfpdynamic", + edge: annotated{"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + expectedDepActions: []string{}, + expectedTargetConditions: []string{}, + }, + { + name: "gplonfp", + edge: annotated{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + expectedDepActions: []string{"apacheLib.meta_lic:apacheLib.meta_lic:notice"}, + expectedTargetConditions: []string{"gplBin.meta_lic:restricted"}, + }, + { + name: "gplcontainer", + edge: annotated{"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + treatAsAggregate: true, + expectedDepActions: []string{"apacheLib.meta_lic:apacheLib.meta_lic:notice"}, + expectedTargetConditions: []string{"gplContainer.meta_lic:restricted"}, + }, + { + name: "gploncontainer", + edge: annotated{"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + treatAsAggregate: true, + otherCondition: "gplLib.meta_lic:restricted", + expectedDepActions: []string{ + "apacheContainer.meta_lic:gplLib.meta_lic:restricted", + "apacheLib.meta_lic:apacheLib.meta_lic:notice", + "apacheLib.meta_lic:gplLib.meta_lic:restricted", + "gplLib.meta_lic:gplLib.meta_lic:restricted", + }, + expectedTargetConditions: []string{}, + }, + { + name: "gplonbin", + edge: annotated{"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + treatAsAggregate: false, + otherCondition: "gplLib.meta_lic:restricted", + expectedDepActions: []string{ + "apacheBin.meta_lic:gplLib.meta_lic:restricted", + "apacheLib.meta_lic:apacheLib.meta_lic:notice", + "apacheLib.meta_lic:gplLib.meta_lic:restricted", + "gplLib.meta_lic:gplLib.meta_lic:restricted", + }, + expectedTargetConditions: []string{"gplLib.meta_lic:restricted"}, + }, + { + name: "gplonfpdynamic", + edge: annotated{"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + expectedDepActions: []string{}, + expectedTargetConditions: []string{"gplBin.meta_lic:restricted"}, + }, + { + name: "independentmodulereverse", + edge: annotated{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}}, + expectedDepActions: []string{}, + expectedTargetConditions: []string{}, + }, + { + name: "independentmodulereversestatic", + edge: annotated{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + expectedDepActions: []string{"apacheBin.meta_lic:apacheBin.meta_lic:notice"}, + expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted"}, + }, + { + name: "dependentmodulereverse", + edge: annotated{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}}, + expectedDepActions: []string{}, + expectedTargetConditions: []string{"gplWithClasspathException.meta_lic:restricted"}, + }, + { + name: "ponr", + edge: annotated{"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}}, + expectedDepActions: []string{ + "proprietary.meta_lic:gplLib.meta_lic:restricted", + "gplLib.meta_lic:gplLib.meta_lic:restricted", + }, + expectedTargetConditions: []string{}, + }, + { + name: "ronp", + edge: annotated{"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}}, + expectedDepActions: []string{"proprietary.meta_lic:proprietary.meta_lic:proprietary"}, + expectedTargetConditions: []string{"gplBin.meta_lic:restricted"}, + }, + { + name: "noticeonb_e_o", + edge: annotated{"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}}, + expectedDepActions: []string{"by_exception.meta_lic:by_exception.meta_lic:by_exception_only"}, + expectedTargetConditions: []string{}, + }, + { + name: "b_e_oonnotice", + edge: annotated{"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}}, + expectedDepActions: []string{"mitLib.meta_lic:mitLib.meta_lic:notice"}, + expectedTargetConditions: []string{}, + }, + { + name: "noticeonrecip", + edge: annotated{"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}}, + expectedDepActions: []string{"mplLib.meta_lic:mplLib.meta_lic:reciprocal"}, + expectedTargetConditions: []string{}, + }, + { + name: "reciponnotice", + edge: annotated{"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + expectedDepActions: []string{"mitLib.meta_lic:mitLib.meta_lic:notice"}, + expectedTargetConditions: []string{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fs := make(testFS) + stderr := &bytes.Buffer{} + target := meta[tt.edge.target] + fmt.Sprintf("deps: {\n file: \"%s\"\n", tt.edge.dep) + for _, ann := range tt.edge.annotations { + target += fmt.Sprintf(" annotations: \"%s\"\n", ann) + } + fs[tt.edge.target] = []byte(target + "}\n") + fs[tt.edge.dep] = []byte(meta[tt.edge.dep]) + lg, err := ReadLicenseGraph(&fs, stderr, []string{tt.edge.target}) + if err != nil { + t.Errorf("unexpected error reading graph: %w", err) + return + } + // simulate a condition inherited from another edge/dependency. + otherTarget := "" + otherCondition := "" + if len(tt.otherCondition) > 0 { + fields := strings.Split(tt.otherCondition, ":") + otherTarget = fields[0] + otherCondition = fields[1] + // other target must exist in graph + lg.targets[otherTarget] = &TargetNode{name: otherTarget} + lg.targets[otherTarget].proto.LicenseConditions = append(lg.targets[otherTarget].proto.LicenseConditions, otherCondition) + } + if tt.expectedDepActions != nil { + depActions := make(actionSet) + depActions[lg.targets[tt.edge.dep]] = lg.targets[tt.edge.dep].LicenseConditions() + if otherTarget != "" { + // simulate a sub-dependency's condition having already propagated up to dep and about to go to target + otherCs := lg.targets[otherTarget].LicenseConditions() + depActions[lg.targets[tt.edge.dep]].AddSet(otherCs) + depActions[lg.targets[otherTarget]] = otherCs + } + asActual := depActionsApplicableToTarget(lg.Edges()[0], depActions, tt.treatAsAggregate) + asExpected := make(actionSet) + for _, triple := range tt.expectedDepActions { + fields := strings.Split(triple, ":") + actsOn := lg.targets[fields[0]] + origin := lg.targets[fields[1]] + expectedConditions := newLicenseConditionSet() + expectedConditions.add(origin, fields[2:]...) + if _, ok := asExpected[actsOn]; ok { + asExpected[actsOn].AddSet(expectedConditions) + } else { + asExpected[actsOn] = expectedConditions + } + } + + checkSameActions(lg, asActual, asExpected, t) + } + if tt.expectedTargetConditions != nil { + targetConditions := lg.TargetNode(tt.edge.target).LicenseConditions() + if otherTarget != "" { + targetConditions.add(lg.targets[otherTarget], otherCondition) + } + cs := targetConditionsApplicableToDep( + lg.Edges()[0], + targetConditions, + tt.treatAsAggregate) + actual := make([]string, 0, cs.Count()) + for _, lc := range cs.AsList() { + actual = append(actual, lc.asString(":")) + } + sort.Strings(actual) + sort.Strings(tt.expectedTargetConditions) + if len(actual) != len(tt.expectedTargetConditions) { + t.Errorf("unexpected number of target conditions: got %v with %d conditions, want %v with %d conditions", + actual, len(actual), tt.expectedTargetConditions, len(tt.expectedTargetConditions)) + } else { + for i := 0; i < len(actual); i++ { + if actual[i] != tt.expectedTargetConditions[i] { + t.Errorf("unexpected target condition at element %d: got %q, want %q", + i, actual[i], tt.expectedTargetConditions[i]) + } + } + } + } + }) + } +} diff --git a/tools/compliance/policy/resolve.go b/tools/compliance/policy/resolve.go new file mode 100644 index 0000000000..9962e68492 --- /dev/null +++ b/tools/compliance/policy/resolve.go @@ -0,0 +1,217 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +// ResolveBottomUpConditions performs a bottom-up walk of the LicenseGraph +// propagating conditions up the graph as necessary according to the properties +// of each edge and according to each license condition in question. +// +// Subsequent top-down walks of the graph will filter some resolutions and may +// introduce new resolutions. +// +// e.g. if a "restricted" condition applies to a binary, it also applies to all +// of the statically-linked libraries and the transitive closure of their static +// dependencies; even if neither they nor the transitive closure of their +// dependencies originate any "restricted" conditions. The bottom-up walk will +// not resolve the library and its transitive closure, but the later top-down +// walk will. +func ResolveBottomUpConditions(lg *LicenseGraph) *ResolutionSet { + + // short-cut if already walked and cached + lg.mu.Lock() + rs := lg.rsBU + lg.mu.Unlock() + + if rs != nil { + return rs + } + + // must be indexed for fast lookup + lg.indexForward() + + rs = newResolutionSet() + + // cmap contains an entry for every target that was previously walked as a pure aggregate only. + cmap := make(map[string]bool) + + var walk func(f string, treatAsAggregate bool) actionSet + + walk = func(f string, treatAsAggregate bool) actionSet { + target := lg.targets[f] + result := make(actionSet) + result[target] = newLicenseConditionSet() + result[target].add(target, target.proto.LicenseConditions...) + if preresolved, ok := rs.resolutions[target]; ok { + if treatAsAggregate { + result.addSet(preresolved) + return result + } + if _, asAggregate := cmap[f]; !asAggregate { + result.addSet(preresolved) + return result + } + // previously walked in a pure aggregate context, + // needs to walk again in non-aggregate context + delete(cmap, f) + } + if treatAsAggregate { + cmap[f] = true + } + + // add all the conditions from all the dependencies + for _, edge := range lg.index[f] { + // walk dependency to get its conditions + as := walk(edge.dependency, treatAsAggregate && lg.targets[edge.dependency].IsContainer()) + + // turn those into the conditions that apply to the target + as = depActionsApplicableToTarget(TargetEdge{lg, edge}, as, treatAsAggregate) + + // add them to the result + result.addSet(as) + } + + // record these conditions as applicable to the target + rs.addConditions(target, result) + rs.addSelf(target, result.byName(ImpliesRestricted)) + + // return this up the tree + return result + } + + // walk each of the roots + for _, r := range lg.rootFiles { + _ = walk(r, lg.targets[r].IsContainer()) + } + + // if not yet cached, save the result + lg.mu.Lock() + if lg.rsBU == nil { + lg.rsBU = rs + } else { + // if we end up with 2, release the later for garbage collection + rs = lg.rsBU + } + lg.mu.Unlock() + + return rs +} + +// ResolveTopDownCondtions performs a top-down walk of the LicenseGraph +// resolving all reachable nodes for `condition`. Policy establishes the rules +// for transforming and propagating resolutions down the graph. +// +// e.g. For current policy, none of the conditions propagate from target to +// dependency except restricted. For restricted, the policy is to share the +// source of any libraries linked to restricted code and to provide notice. +func ResolveTopDownConditions(lg *LicenseGraph) *ResolutionSet { + + // short-cut if already walked and cached + lg.mu.Lock() + rs := lg.rsTD + lg.mu.Unlock() + + if rs != nil { + return rs + } + + // start with the conditions propagated up the graph + rs = ResolveBottomUpConditions(lg) + + // rmap maps 'appliesTo' targets to their applicable conditions + // + // rmap is the resulting ResolutionSet + rmap := make(map[*TargetNode]actionSet) + for attachesTo, as := range rs.resolutions { + rmap[attachesTo] = as.copy() + } + + path := make([]*dependencyEdge, 0, 32) + + var walk func(f string, cs *LicenseConditionSet, treatAsAggregate bool) + + walk = func(f string, cs *LicenseConditionSet, treatAsAggregate bool) { + fnode := lg.targets[f] + if !cs.IsEmpty() { + parentsAllAggregate := true + for _, e := range path { + target := lg.targets[e.target] + if _, ok := rmap[target]; !ok { + rmap[target] = make(actionSet) + } + rmap[target].add(fnode, cs) + if !target.IsContainer() { + parentsAllAggregate = false + break + } + } + if parentsAllAggregate { + if _, ok := rmap[fnode]; !ok { + rmap[fnode] = make(actionSet) + } + rmap[fnode].add(fnode, cs) + } + } + // add conditions attached to `f` + cs = cs.Copy() + for _, fcs := range rs.resolutions[fnode] { + cs.AddSet(fcs) + } + // for each dependency + for _, edge := range lg.index[f] { + e := TargetEdge{lg, edge} + // dcs holds the dpendency conditions inherited from the target + dcs := targetConditionsApplicableToDep(e, cs, treatAsAggregate) + if dcs.IsEmpty() { + if !treatAsAggregate || (!edgeIsDerivation(e) && !edgeIsDynamicLink(e)) { + continue + } + } + path = append(path, edge) + // add the conditions to the dependency + walk(edge.dependency, dcs, treatAsAggregate && lg.targets[edge.dependency].IsContainer()) + path = path[:len(path)-1] + } + } + + // walk each of the roots + for _, r := range lg.rootFiles { + as, ok := rs.resolutions[lg.targets[r]] + if !ok { + // no conditions in root or transitive closure of dependencies + continue + } + if as.isEmpty() { + continue + } + + path = path[:0] + // add the conditions to the root and its transitive closure + walk(r, newLicenseConditionSet(), lg.targets[r].IsContainer()) + } + + rs = &ResolutionSet{rmap} + + // if not yet cached, save the result + lg.mu.Lock() + if lg.rsTD == nil { + lg.rsTD = rs + } else { + // if we end up with 2, release the later for garbage collection + rs = lg.rsTD + } + lg.mu.Unlock() + + return rs +} diff --git a/tools/compliance/policy/resolve_test.go b/tools/compliance/policy/resolve_test.go new file mode 100644 index 0000000000..aa5bb2a3c7 --- /dev/null +++ b/tools/compliance/policy/resolve_test.go @@ -0,0 +1,755 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +import ( + "bytes" + "testing" +) + +func TestResolveBottomUpConditions(t *testing.T) { + tests := []struct { + name string + roots []string + edges []annotated + expectedResolutions []res + }{ + { + name: "firstparty", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + }, + }, + { + name: "firstpartytool", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"toolchain"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + }, + }, + { + name: "firstpartydeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + }, + }, + { + name: "firstpartywide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + }, + }, + { + name: "firstpartydynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + }, + }, + { + name: "firstpartydynamicdeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + }, + }, + { + name: "firstpartydynamicwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + }, + }, + { + name: "restricted", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "restrictedtool", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"toolchain"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "restricteddeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "restrictedwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "restricteddynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "restricteddynamicdeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "restricteddynamicwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricted", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestrictedtool", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"toolchain"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricteddeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestrictedwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricteddynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricteddynamicdeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricteddynamicwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "classpath", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "classpathdependent", + roots: []string{"dependentModule.meta_lic"}, + edges: []annotated{ + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"}, + {"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "classpathdynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "classpathdependentdynamic", + roots: []string{"dependentModule.meta_lic"}, + edges: []annotated{ + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"}, + {"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stderr := &bytes.Buffer{} + lg, err := toGraph(stderr, tt.roots, tt.edges) + if err != nil { + t.Errorf("unexpected test data error: got %w, want no error", err) + return + } + expectedRs := toResolutionSet(lg, tt.expectedResolutions) + actualRs := ResolveBottomUpConditions(lg) + checkSame(actualRs, expectedRs, t) + }) + } +} + +func TestResolveTopDownConditions(t *testing.T) { + tests := []struct { + name string + roots []string + edges []annotated + expectedResolutions []res + }{ + { + name: "firstparty", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + }, + }, + { + name: "firstpartydynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + }, + }, + { + name: "restricted", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "restrictedtool", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplBin.meta_lic", []string{"toolchain"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "restricteddeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "mitBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "mplLib.meta_lic", []string{"static"}}, + {"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"}, + {"mitBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"mplLib.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"}, + }, + }, + { + name: "restrictedwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "restricteddynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "restricteddynamicdeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "mitBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + {"apacheBin.meta_lic", "mplLib.meta_lic", []string{"dynamic"}}, + {"mitBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"mplLib.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"}, + }, + }, + { + name: "restricteddynamicwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricted", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "weakrestrictedtool", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplBin.meta_lic", []string{"toolchain"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "weakrestricteddeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "weakrestrictedwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricteddynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "weakrestricteddynamicdeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricteddynamicwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "classpath", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "classpathdependent", + roots: []string{"dependentModule.meta_lic"}, + edges: []annotated{ + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}}, + {"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"}, + {"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "classpathdynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "classpathdependentdynamic", + roots: []string{"dependentModule.meta_lic"}, + edges: []annotated{ + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + {"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"}, + {"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stderr := &bytes.Buffer{} + lg, err := toGraph(stderr, tt.roots, tt.edges) + if err != nil { + t.Errorf("unexpected test data error: got %w, want no error", err) + return + } + expectedRs := toResolutionSet(lg, tt.expectedResolutions) + actualRs := ResolveTopDownConditions(lg) + checkSame(actualRs, expectedRs, t) + }) + } +} diff --git a/tools/compliance/policy/resolvenotices.go b/tools/compliance/policy/resolvenotices.go new file mode 100644 index 0000000000..80b5e028cb --- /dev/null +++ b/tools/compliance/policy/resolvenotices.go @@ -0,0 +1,21 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +// ResolveNotices implements the policy for notices. +func ResolveNotices(lg *LicenseGraph) *ResolutionSet { + rs := ResolveTopDownConditions(lg) + return WalkResolutionsForCondition(lg, rs, ImpliesNotice) +} diff --git a/tools/compliance/policy/resolvenotices_test.go b/tools/compliance/policy/resolvenotices_test.go new file mode 100644 index 0000000000..b428d5b549 --- /dev/null +++ b/tools/compliance/policy/resolvenotices_test.go @@ -0,0 +1,467 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +import ( + "bytes" + "testing" +) + +func TestResolveNotices(t *testing.T) { + tests := []struct { + name string + roots []string + edges []annotated + expectedResolutions []res + }{ + { + name: "firstparty", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + }, + }, + { + name: "firstpartydynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + }, + }, + { + name: "firstpartydynamicshipped", + roots: []string{"apacheBin.meta_lic", "apacheLib.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + }, + }, + { + name: "restricted", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "restrictedtool", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplBin.meta_lic", []string{"toolchain"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "restricteddeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "mitBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "mplLib.meta_lic", []string{"static"}}, + {"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"}, + {"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"}, + {"mitBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "restrictedwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "restricteddynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "restricteddynamicshipped", + roots: []string{"apacheBin.meta_lic", "mitLib.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"mitLib.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "restricteddynamicdeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "mitBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + {"apacheBin.meta_lic", "mplLib.meta_lic", []string{"dynamic"}}, + {"mitBin.meta_lic", "mitLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"}, + }, + }, + { + name: "restricteddynamicwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + }, + }, + { + name: "restricteddynamicwideshipped", + roots: []string{"apacheContainer.meta_lic", "gplLib.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricted", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestrictedtool", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplBin.meta_lic", []string{"toolchain"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "weakrestrictedtoolshipped", + roots: []string{"apacheBin.meta_lic", "lgplBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplBin.meta_lic", []string{"toolchain"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricteddeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestrictedwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricteddynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "weakrestricteddynamicshipped", + roots: []string{"apacheBin.meta_lic", "lgplLib.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricteddynamicdeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + }, + }, + { + name: "weakrestricteddynamicdeepshipped", + roots: []string{"apacheContainer.meta_lic", "lgplLib.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "weakrestricteddynamicwide", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + }, + }, + { + name: "weakrestricteddynamicwideshipped", + roots: []string{"apacheContainer.meta_lic", "lgplLib.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"lgplLib.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "classpath", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "classpathdependent", + roots: []string{"dependentModule.meta_lic"}, + edges: []annotated{ + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}}, + {"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"}, + {"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "classpathdynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "classpathdynamicshipped", + roots: []string{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + {"apacheBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "classpathdependentdynamic", + roots: []string{"dependentModule.meta_lic"}, + edges: []annotated{ + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + {"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"}, + {"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "classpathdependentdynamicshipped", + roots: []string{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + {"dependentModule.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"}, + {"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"dependentModule.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + {"dependentModule.meta_lic", "mitLib.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stderr := &bytes.Buffer{} + lg, err := toGraph(stderr, tt.roots, tt.edges) + if err != nil { + t.Errorf("unexpected test data error: got %w, want no error", err) + return + } + expectedRs := toResolutionSet(lg, tt.expectedResolutions) + actualRs := ResolveNotices(lg) + checkSame(actualRs, expectedRs, t) + }) + } +} diff --git a/tools/compliance/policy/resolveprivacy.go b/tools/compliance/policy/resolveprivacy.go new file mode 100644 index 0000000000..dabbc62c52 --- /dev/null +++ b/tools/compliance/policy/resolveprivacy.go @@ -0,0 +1,21 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +// ResolveSourcePrivacy implements the policy for source privacy. +func ResolveSourcePrivacy(lg *LicenseGraph) *ResolutionSet { + rs := ResolveTopDownConditions(lg) + return WalkResolutionsForCondition(lg, rs, ImpliesPrivate) +} diff --git a/tools/compliance/policy/resolveprivacy_test.go b/tools/compliance/policy/resolveprivacy_test.go new file mode 100644 index 0000000000..25772bbef4 --- /dev/null +++ b/tools/compliance/policy/resolveprivacy_test.go @@ -0,0 +1,87 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +import ( + "bytes" + "testing" +) + +func TestResolveSourcePrivacy(t *testing.T) { + tests := []struct { + name string + roots []string + edges []annotated + expectedResolutions []res + }{ + { + name: "firstparty", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{}, + }, + { + name: "notice", + roots: []string{"mitBin.meta_lic"}, + edges: []annotated{ + {"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{}, + }, + { + name: "lgpl", + roots: []string{"lgplBin.meta_lic"}, + edges: []annotated{ + {"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{}, + }, + { + name: "proprietaryonresricted", + roots: []string{"proprietary.meta_lic"}, + edges: []annotated{ + {"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"proprietary.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"}, + }, + }, + { + name: "restrictedonproprietary", + roots: []string{"gplBin.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stderr := &bytes.Buffer{} + lg, err := toGraph(stderr, tt.roots, tt.edges) + if err != nil { + t.Errorf("unexpected test data error: got %w, want no error", err) + return + } + expectedRs := toResolutionSet(lg, tt.expectedResolutions) + actualRs := ResolveSourcePrivacy(lg) + checkSame(actualRs, expectedRs, t) + }) + } +} diff --git a/tools/compliance/policy/resolveshare.go b/tools/compliance/policy/resolveshare.go new file mode 100644 index 0000000000..24efd2899a --- /dev/null +++ b/tools/compliance/policy/resolveshare.go @@ -0,0 +1,21 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +// ResolveSourceSharing implements the policy for source-sharing. +func ResolveSourceSharing(lg *LicenseGraph) *ResolutionSet { + rs := ResolveTopDownConditions(lg) + return WalkResolutionsForCondition(lg, rs, ImpliesShared) +} diff --git a/tools/compliance/policy/resolveshare_test.go b/tools/compliance/policy/resolveshare_test.go new file mode 100644 index 0000000000..7371ccf94b --- /dev/null +++ b/tools/compliance/policy/resolveshare_test.go @@ -0,0 +1,295 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +import ( + "bytes" + "testing" +) + +func TestResolveSourceSharing(t *testing.T) { + tests := []struct { + name string + roots []string + edges []annotated + expectedResolutions []res + }{ + { + name: "independentmodulerestricted", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{}, + }, + { + name: "independentmodulerestrictedshipped", + roots: []string{"apacheBin.meta_lic", "gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "independentmodulestaticrestricted", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "dependentmodulerestricted", + roots: []string{"dependentModule.meta_lic"}, + edges: []annotated{ + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "dependentmodulerestrictedshipclasspath", + roots: []string{"dependentModule.meta_lic", "gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "lgplonfprestricted", + roots: []string{"lgplBin.meta_lic"}, + edges: []annotated{ + {"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"}, + {"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"}, + }, + }, + { + name: "lgplonfpdynamicrestricted", + roots: []string{"lgplBin.meta_lic"}, + edges: []annotated{ + {"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"}, + }, + }, + { + name: "lgplonfpdynamicrestrictedshiplib", + roots: []string{"lgplBin.meta_lic", "apacheLib.meta_lic"}, + edges: []annotated{ + {"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"}, + }, + }, + { + name: "gplonfprestricted", + roots: []string{"gplBin.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"}, + {"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"}, + }, + }, + { + name: "gplcontainerrestricted", + roots: []string{"gplContainer.meta_lic"}, + edges: []annotated{ + {"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplContainer.meta_lic", "gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"}, + {"gplContainer.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"}, + }, + }, + { + name: "gploncontainerrestricted", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "gplonbinrestricted", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "gplonfpdynamicrestricted", + roots: []string{"gplBin.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"}, + }, + }, + { + name: "gplonfpdynamicrestrictedshiplib", + roots: []string{"gplBin.meta_lic", "apacheLib.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"}, + {"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"}, + }, + }, + { + name: "independentmodulereverserestricted", + roots: []string{"gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "independentmodulereversestaticrestricted", + roots: []string{"gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "dependentmodulereverserestricted", + roots: []string{"gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "dependentmodulereverserestrictedshipdependent", + roots: []string{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic"}, + edges: []annotated{ + {"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "ponrrestricted", + roots: []string{"proprietary.meta_lic"}, + edges: []annotated{ + {"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"proprietary.meta_lic", "proprietary.meta_lic", "gplLib.meta_lic", "restricted"}, + {"proprietary.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "ronprestricted", + roots: []string{"gplBin.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"}, + {"gplBin.meta_lic", "proprietary.meta_lic", "gplBin.meta_lic", "restricted"}, + }, + }, + { + name: "noticeonb_e_orestricted", + roots: []string{"mitBin.meta_lic"}, + edges: []annotated{ + {"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{}, + }, + { + name: "b_e_oonnoticerestricted", + roots: []string{"by_exception.meta_lic"}, + edges: []annotated{ + {"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{}, + }, + { + name: "noticeonreciprecip", + roots: []string{"mitBin.meta_lic"}, + edges: []annotated{ + {"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"mitBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"}, + }, + }, + { + name: "reciponnoticerecip", + roots: []string{"mplBin.meta_lic"}, + edges: []annotated{ + {"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"mplBin.meta_lic", "mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stderr := &bytes.Buffer{} + lg, err := toGraph(stderr, tt.roots, tt.edges) + if err != nil { + t.Errorf("unexpected test data error: got %w, want no error", err) + return + } + expectedRs := toResolutionSet(lg, tt.expectedResolutions) + actualRs := ResolveSourceSharing(lg) + checkSame(actualRs, expectedRs, t) + }) + } +} diff --git a/tools/compliance/policy/shareprivacyconflicts.go b/tools/compliance/policy/shareprivacyconflicts.go new file mode 100644 index 0000000000..dabdff5dce --- /dev/null +++ b/tools/compliance/policy/shareprivacyconflicts.go @@ -0,0 +1,91 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +import ( + "fmt" +) + +// SourceSharePrivacyConflict describes an individual conflict between a source-sharing +// condition and a source privacy condition +type SourceSharePrivacyConflict struct { + SourceNode *TargetNode + ShareCondition LicenseCondition + PrivacyCondition LicenseCondition +} + +// Error returns a string describing the conflict. +func (conflict SourceSharePrivacyConflict) Error() string { + return fmt.Sprintf("%s %s from %s and must share from %s %s\n", + conflict.SourceNode.name, + conflict.PrivacyCondition.name, conflict.PrivacyCondition.origin.name, + conflict.ShareCondition.name, conflict.ShareCondition.origin.name) +} + +// IsEqualTo returns true when `conflict` and `other` describe the same conflict. +func (conflict SourceSharePrivacyConflict) IsEqualTo(other SourceSharePrivacyConflict) bool { + return conflict.SourceNode.name == other.SourceNode.name && + conflict.ShareCondition.name == other.ShareCondition.name && + conflict.ShareCondition.origin.name == other.ShareCondition.origin.name && + conflict.PrivacyCondition.name == other.PrivacyCondition.name && + conflict.PrivacyCondition.origin.name == other.PrivacyCondition.origin.name +} + +// ConflictingSharedPrivateSource lists all of the targets where conflicting conditions to +// share the source and to keep the source private apply to the target. +func ConflictingSharedPrivateSource(lg *LicenseGraph) []SourceSharePrivacyConflict { + // shareSource is the set of all source-sharing resolutions. + shareSource := ResolveSourceSharing(lg) + if shareSource.IsEmpty() { + return []SourceSharePrivacyConflict{} + } + + // privateSource is the set of all source privacy resolutions. + privateSource := ResolveSourcePrivacy(lg) + if privateSource.IsEmpty() { + return []SourceSharePrivacyConflict{} + } + + // combined is the combination of source-sharing and source privacy. + combined := JoinResolutionSets(shareSource, privateSource) + + // size is the size of the result + size := 0 + for _, actsOn := range combined.ActsOn() { + rl := combined.ResolutionsByActsOn(actsOn) + size += rl.CountConditionsByName(ImpliesShared) * rl.CountConditionsByName(ImpliesPrivate) + } + if size == 0 { + return []SourceSharePrivacyConflict{} + } + result := make([]SourceSharePrivacyConflict, 0, size) + for _, actsOn := range combined.ActsOn() { + rl := combined.ResolutionsByActsOn(actsOn) + if len(rl) == 0 { + continue + } + + pconditions := rl.ByName(ImpliesPrivate).AllConditions().AsList() + ssconditions := rl.ByName(ImpliesShared).AllConditions().AsList() + + // report all conflicting condition combinations + for _, p := range pconditions { + for _, ss := range ssconditions { + result = append(result, SourceSharePrivacyConflict{actsOn, ss, p}) + } + } + } + return result +} diff --git a/tools/compliance/policy/shareprivacyconflicts_test.go b/tools/compliance/policy/shareprivacyconflicts_test.go new file mode 100644 index 0000000000..162c1fe3c9 --- /dev/null +++ b/tools/compliance/policy/shareprivacyconflicts_test.go @@ -0,0 +1,129 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +import ( + "bytes" + "sort" + "testing" +) + +// byConflict orders conflicts by target then share then privacy +type byConflict []SourceSharePrivacyConflict + +// Len returns the count of elements in the slice. +func (l byConflict) Len() int { return len(l) } + +// Swap rearranged 2 elements so that each occupies the other's former +// position. +func (l byConflict) Swap(i, j int) { l[i], l[j] = l[j], l[i] } + +// Less returns true when the `i`th element is lexicographically less than +// the `j`th element. +func (l byConflict) Less(i, j int) bool { + if l[i].SourceNode.name == l[j].SourceNode.name { + if l[i].ShareCondition.origin.name == l[j].ShareCondition.origin.name { + if l[i].ShareCondition.name == l[j].ShareCondition.name { + if l[i].PrivacyCondition.origin.name == l[j].PrivacyCondition.origin.name { + return l[i].PrivacyCondition.name < l[j].PrivacyCondition.name + } + return l[i].PrivacyCondition.origin.name < l[j].PrivacyCondition.origin.name + } + return l[i].ShareCondition.name < l[j].ShareCondition.name + } + return l[i].ShareCondition.origin.name < l[j].ShareCondition.origin.name + } + return l[i].SourceNode.name < l[j].SourceNode.name +} + +func TestConflictingSharedPrivateSource(t *testing.T) { + tests := []struct { + name string + roots []string + edges []annotated + expectedConflicts []confl + }{ + { + name: "firstparty", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedConflicts: []confl{}, + }, + { + name: "notice", + roots: []string{"mitBin.meta_lic"}, + edges: []annotated{ + {"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedConflicts: []confl{}, + }, + { + name: "lgpl", + roots: []string{"lgplBin.meta_lic"}, + edges: []annotated{ + {"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedConflicts: []confl{}, + }, + { + name: "proprietaryonrestricted", + roots: []string{"proprietary.meta_lic"}, + edges: []annotated{ + {"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedConflicts: []confl{ + {"proprietary.meta_lic", "gplLib.meta_lic:restricted", "proprietary.meta_lic:proprietary"}, + }, + }, + { + name: "restrictedonproprietary", + roots: []string{"gplBin.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}}, + }, + expectedConflicts: []confl{ + {"proprietary.meta_lic", "gplBin.meta_lic:restricted", "proprietary.meta_lic:proprietary"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stderr := &bytes.Buffer{} + lg, err := toGraph(stderr, tt.roots, tt.edges) + if err != nil { + t.Errorf("unexpected test data error: got %w, want no error", err) + return + } + expectedConflicts := toConflictList(lg, tt.expectedConflicts) + actualConflicts := ConflictingSharedPrivateSource(lg) + sort.Sort(byConflict(expectedConflicts)) + sort.Sort(byConflict(actualConflicts)) + if len(expectedConflicts) != len(actualConflicts) { + t.Errorf("unexpected number of share/privacy conflicts: got %v with %d conflicts, want %v with %d conflicts", + actualConflicts, len(actualConflicts), expectedConflicts, len(expectedConflicts)) + } else { + for i := 0; i < len(actualConflicts); i++ { + if !actualConflicts[i].IsEqualTo(expectedConflicts[i]) { + t.Errorf("unexpected share/privacy conflict at element %d: got %q, want %q", + i, actualConflicts[i].Error(), expectedConflicts[i].Error()) + } + } + } + + }) + } +} diff --git a/tools/compliance/policy/shipped.go b/tools/compliance/policy/shipped.go new file mode 100644 index 0000000000..74eb343865 --- /dev/null +++ b/tools/compliance/policy/shipped.go @@ -0,0 +1,54 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +// ShippedNodes returns the set of nodes in a license graph where the target or +// a derivative work gets distributed. (caches result) +func ShippedNodes(lg *LicenseGraph) *TargetNodeSet { + lg.mu.Lock() + shipped := lg.shippedNodes + lg.mu.Unlock() + if shipped != nil { + return shipped + } + + tset := make(map[*TargetNode]bool) + + WalkTopDown(lg, func(lg *LicenseGraph, tn *TargetNode, path TargetEdgePath) bool { + if _, alreadyWalked := tset[tn]; alreadyWalked { + return false + } + if len(path) > 0 { + if !edgeIsDerivation(path[len(path)-1]) { + return false + } + } + tset[tn] = true + return true + }) + + shipped = &TargetNodeSet{tset} + + lg.mu.Lock() + if lg.shippedNodes == nil { + lg.shippedNodes = shipped + } else { + // if we end up with 2, release the later for garbage collection. + shipped = lg.shippedNodes + } + lg.mu.Unlock() + + return shipped +} diff --git a/tools/compliance/policy/shipped_test.go b/tools/compliance/policy/shipped_test.go new file mode 100644 index 0000000000..53a846928c --- /dev/null +++ b/tools/compliance/policy/shipped_test.go @@ -0,0 +1,130 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +import ( + "bytes" + "sort" + "testing" +) + +func TestShippedNodes(t *testing.T) { + tests := []struct { + name string + roots []string + edges []annotated + expectedNodes []string + }{ + { + name: "singleton", + roots: []string{"apacheLib.meta_lic"}, + edges: []annotated{}, + expectedNodes: []string{"apacheLib.meta_lic"}, + }, + { + name: "simplebinary", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedNodes: []string{"apacheBin.meta_lic", "apacheLib.meta_lic"}, + }, + { + name: "simpledynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + }, + expectedNodes: []string{"apacheBin.meta_lic"}, + }, + { + name: "container", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedNodes: []string{ + "apacheContainer.meta_lic", + "apacheLib.meta_lic", + "gplLib.meta_lic", + }, + }, + { + name: "binary", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedNodes: []string{ + "apacheBin.meta_lic", + "apacheLib.meta_lic", + "gplLib.meta_lic", + }, + }, + { + name: "binarydynamic", + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + }, + expectedNodes: []string{ + "apacheBin.meta_lic", + "apacheLib.meta_lic", + }, + }, + { + name: "containerdeep", + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + {"apacheLib.meta_lic", "gplLib.meta_lic", []string{"dynamic"}}, + }, + expectedNodes: []string{ + "apacheContainer.meta_lic", + "apacheBin.meta_lic", + "apacheLib.meta_lic", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stderr := &bytes.Buffer{} + lg, err := toGraph(stderr, tt.roots, tt.edges) + if err != nil { + t.Errorf("unexpected test data error: got %w, want no error", err) + return + } + expectedNodes := append([]string{}, tt.expectedNodes...) + actualNodes := ShippedNodes(lg).Names() + sort.Strings(expectedNodes) + sort.Strings(actualNodes) + if len(expectedNodes) != len(actualNodes) { + t.Errorf("unexpected number of shipped nodes: got %v with %d nodes, want %v with %d nodes", + actualNodes, len(actualNodes), expectedNodes, len(expectedNodes)) + return + } + for i := 0; i < len(actualNodes); i++ { + if expectedNodes[i] != actualNodes[i] { + t.Errorf("unexpected node at index %d: got %q in %v, want %q in %v", + i, actualNodes[i], actualNodes, expectedNodes[i], expectedNodes) + } + } + }) + } +} diff --git a/tools/compliance/policy/walk.go b/tools/compliance/policy/walk.go new file mode 100644 index 0000000000..8b6737d6c5 --- /dev/null +++ b/tools/compliance/policy/walk.go @@ -0,0 +1,76 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +// VisitNode is called for each root and for each walked dependency node by +// WalkTopDown. When VisitNode returns true, WalkTopDown will proceed to walk +// down the dependences of the node +type VisitNode func(*LicenseGraph, *TargetNode, TargetEdgePath) bool + +// WalkTopDown does a top-down walk of `lg` calling `visit` and descending +// into depenencies when `visit` returns true. +func WalkTopDown(lg *LicenseGraph, visit VisitNode) { + path := NewTargetEdgePath(32) + + // must be indexed for fast lookup + lg.indexForward() + + var walk func(f string) + walk = func(f string) { + visitChildren := visit(lg, lg.targets[f], *path) + if !visitChildren { + return + } + for _, edge := range lg.index[f] { + path.Push(TargetEdge{lg, edge}) + walk(edge.dependency) + path.Pop() + } + } + + for _, r := range lg.rootFiles { + path.Clear() + walk(r) + } +} + +// WalkResolutionsForCondition performs a top-down walk of the LicenseGraph +// resolving all distributed works for condition `names`. +func WalkResolutionsForCondition(lg *LicenseGraph, rs *ResolutionSet, names ConditionNames) *ResolutionSet { + shipped := ShippedNodes(lg) + + // rmap maps 'attachesTo' targets to the `actsOn` targets and applicable conditions + // + // rmap is the resulting ResolutionSet + rmap := make(map[*TargetNode]actionSet) + + WalkTopDown(lg, func(lg *LicenseGraph, tn *TargetNode, _ TargetEdgePath) bool { + if _, ok := rmap[tn]; ok { + return false + } + if !shipped.Contains(tn) { + return false + } + if as, ok := rs.resolutions[tn]; ok { + fas := as.byActsOn(shipped).byName(names) + if !fas.isEmpty() { + rmap[tn] = fas + } + } + return tn.IsContainer() // descend into containers + }) + + return &ResolutionSet{rmap} +} diff --git a/tools/compliance/policy/walk_test.go b/tools/compliance/policy/walk_test.go new file mode 100644 index 0000000000..2eef702ef6 --- /dev/null +++ b/tools/compliance/policy/walk_test.go @@ -0,0 +1,629 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compliance + +import ( + "bytes" + "testing" +) + +func TestWalkResolutionsForCondition(t *testing.T) { + tests := []struct { + name string + condition ConditionNames + roots []string + edges []annotated + expectedResolutions []res + }{ + { + name: "firstparty", + condition: ImpliesNotice, + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + }, + }, + { + name: "notice", + condition: ImpliesNotice, + roots: []string{"mitBin.meta_lic"}, + edges: []annotated{ + {"mitBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"}, + {"mitBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "fponlgplnotice", + condition: ImpliesNotice, + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "lgplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "lgplLib.meta_lic", "lgplLib.meta_lic", "restricted"}, + }, + }, + { + name: "fponlgpldynamicnotice", + condition: ImpliesNotice, + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "lgplLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + }, + }, + { + name: "independentmodulenotice", + condition: ImpliesNotice, + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + }, + }, + { + name: "independentmodulerestricted", + condition: ImpliesRestricted, + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{}, + }, + { + name: "independentmodulestaticnotice", + condition: ImpliesNotice, + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "independentmodulestaticrestricted", + condition: ImpliesRestricted, + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "dependentmodulenotice", + condition: ImpliesNotice, + roots: []string{"dependentModule.meta_lic"}, + edges: []annotated{ + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"dependentModule.meta_lic", "dependentModule.meta_lic", "dependentModule.meta_lic", "notice"}, + {"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "dependentmodulerestricted", + condition: ImpliesRestricted, + roots: []string{"dependentModule.meta_lic"}, + edges: []annotated{ + {"dependentModule.meta_lic", "gplWithClasspathException.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"dependentModule.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "lgplonfpnotice", + condition: ImpliesNotice, + roots: []string{"lgplBin.meta_lic"}, + edges: []annotated{ + {"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"}, + {"lgplBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + {"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"}, + }, + }, + { + name: "lgplonfprestricted", + condition: ImpliesRestricted, + roots: []string{"lgplBin.meta_lic"}, + edges: []annotated{ + {"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"}, + {"lgplBin.meta_lic", "apacheLib.meta_lic", "lgplBin.meta_lic", "restricted"}, + }, + }, + { + name: "lgplonfpdynamicnotice", + condition: ImpliesNotice, + roots: []string{"lgplBin.meta_lic"}, + edges: []annotated{ + {"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"}, + }, + }, + { + name: "lgplonfpdynamicrestricted", + condition: ImpliesRestricted, + roots: []string{"lgplBin.meta_lic"}, + edges: []annotated{ + {"lgplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"lgplBin.meta_lic", "lgplBin.meta_lic", "lgplBin.meta_lic", "restricted"}, + }, + }, + { + name: "gplonfpnotice", + condition: ImpliesNotice, + roots: []string{"gplBin.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"}, + {"gplBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + {"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"}, + }, + }, + { + name: "gplonfprestricted", + condition: ImpliesRestricted, + roots: []string{"gplBin.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"}, + {"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"}, + }, + }, + { + name: "gplcontainernotice", + condition: ImpliesNotice, + roots: []string{"gplContainer.meta_lic"}, + edges: []annotated{ + {"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplContainer.meta_lic", "gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"}, + {"gplContainer.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + {"gplContainer.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"}, + }, + }, + { + name: "gplcontainerrestricted", + condition: ImpliesRestricted, + roots: []string{"gplContainer.meta_lic"}, + edges: []annotated{ + {"gplContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplContainer.meta_lic", "gplContainer.meta_lic", "gplContainer.meta_lic", "restricted"}, + {"gplContainer.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "gplContainer.meta_lic", "restricted"}, + }, + }, + { + name: "gploncontainernotice", + condition: ImpliesNotice, + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "apacheContainer.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheLib.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "gploncontainerrestricted", + condition: ImpliesRestricted, + roots: []string{"apacheContainer.meta_lic"}, + edges: []annotated{ + {"apacheContainer.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheContainer.meta_lic", "apacheContainer.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheContainer.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"gplLib.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "gplonbinnotice", + condition: ImpliesNotice, + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheLib.meta_lic", "apacheLib.meta_lic", "notice"}, + {"apacheBin.meta_lic", "apacheLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "gplonbinrestricted", + condition: ImpliesRestricted, + roots: []string{"apacheBin.meta_lic"}, + edges: []annotated{ + {"apacheBin.meta_lic", "apacheLib.meta_lic", []string{"static"}}, + {"apacheBin.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"apacheBin.meta_lic", "apacheBin.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "apacheLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"apacheBin.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "gplonfpdynamicnotice", + condition: ImpliesNotice, + roots: []string{"gplBin.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"}, + }, + }, + { + name: "gplonfpdynamicrestricted", + condition: ImpliesRestricted, + roots: []string{"gplBin.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"}, + }, + }, + { + name: "gplonfpdynamicrestrictedshipped", + condition: ImpliesRestricted, + roots: []string{"gplBin.meta_lic", "apacheLib.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "apacheLib.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"}, + {"gplBin.meta_lic", "apacheLib.meta_lic", "gplBin.meta_lic", "restricted"}, + }, + }, + { + name: "independentmodulereversenotice", + condition: ImpliesNotice, + roots: []string{"gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "independentmodulereverserestricted", + condition: ImpliesRestricted, + roots: []string{"gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "independentmodulereverserestrictedshipped", + condition: ImpliesRestricted, + roots: []string{"gplWithClasspathException.meta_lic", "apacheBin.meta_lic"}, + edges: []annotated{ + {"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "independentmodulereversestaticnotice", + condition: ImpliesNotice, + roots: []string{"gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", "apacheBin.meta_lic", "notice"}, + {"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "independentmodulereversestaticrestricted", + condition: ImpliesRestricted, + roots: []string{"gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "apacheBin.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "dependentmodulereversenotice", + condition: ImpliesNotice, + roots: []string{"gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "dependentmodulereverserestricted", + condition: ImpliesRestricted, + roots: []string{"gplWithClasspathException.meta_lic"}, + edges: []annotated{ + {"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "dependentmodulereverserestrictedshipped", + condition: ImpliesRestricted, + roots: []string{"gplWithClasspathException.meta_lic", "dependentModule.meta_lic"}, + edges: []annotated{ + {"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", []string{"dynamic"}}, + }, + expectedResolutions: []res{ + {"gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + {"gplWithClasspathException.meta_lic", "dependentModule.meta_lic", "gplWithClasspathException.meta_lic", "restricted"}, + }, + }, + { + name: "ponrnotice", + condition: ImpliesNotice, + roots: []string{"proprietary.meta_lic"}, + edges: []annotated{ + {"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"proprietary.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"}, + {"proprietary.meta_lic", "proprietary.meta_lic", "gplLib.meta_lic", "restricted"}, + {"proprietary.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "ponrrestricted", + condition: ImpliesRestricted, + roots: []string{"proprietary.meta_lic"}, + edges: []annotated{ + {"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"proprietary.meta_lic", "gplLib.meta_lic", "gplLib.meta_lic", "restricted"}, + {"proprietary.meta_lic", "proprietary.meta_lic", "gplLib.meta_lic", "restricted"}, + }, + }, + { + name: "ponrproprietary", + condition: ImpliesProprietary, + roots: []string{"proprietary.meta_lic"}, + edges: []annotated{ + {"proprietary.meta_lic", "gplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"proprietary.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"}, + }, + }, + { + name: "ronpnotice", + condition: ImpliesNotice, + roots: []string{"gplBin.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"}, + {"gplBin.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"}, + {"gplBin.meta_lic", "proprietary.meta_lic", "gplBin.meta_lic", "restricted"}, + }, + }, + { + name: "ronprestricted", + condition: ImpliesRestricted, + roots: []string{"gplBin.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "gplBin.meta_lic", "gplBin.meta_lic", "restricted"}, + {"gplBin.meta_lic", "proprietary.meta_lic", "gplBin.meta_lic", "restricted"}, + }, + }, + { + name: "ronpproprietary", + condition: ImpliesProprietary, + roots: []string{"gplBin.meta_lic"}, + edges: []annotated{ + {"gplBin.meta_lic", "proprietary.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"gplBin.meta_lic", "proprietary.meta_lic", "proprietary.meta_lic", "proprietary"}, + }, + }, + { + name: "noticeonb_e_onotice", + condition: ImpliesNotice, + roots: []string{"mitBin.meta_lic"}, + edges: []annotated{ + {"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"}, + {"mitBin.meta_lic", "by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"}, + }, + }, + { + name: "noticeonb_e_orestricted", + condition: ImpliesRestricted, + roots: []string{"mitBin.meta_lic"}, + edges: []annotated{ + {"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{}, + }, + { + name: "noticeonb_e_ob_e_o", + condition: ImpliesByExceptionOnly, + roots: []string{"mitBin.meta_lic"}, + edges: []annotated{ + {"mitBin.meta_lic", "by_exception.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"mitBin.meta_lic", "by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"}, + }, + }, + { + name: "b_e_oonnoticenotice", + condition: ImpliesNotice, + roots: []string{"by_exception.meta_lic"}, + edges: []annotated{ + {"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"by_exception.meta_lic", "by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"}, + {"by_exception.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "b_e_oonnoticerestricted", + condition: ImpliesRestricted, + roots: []string{"by_exception.meta_lic"}, + edges: []annotated{ + {"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{}, + }, + { + name: "b_e_oonnoticeb_e_o", + condition: ImpliesByExceptionOnly, + roots: []string{"by_exception.meta_lic"}, + edges: []annotated{ + {"by_exception.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"by_exception.meta_lic", "by_exception.meta_lic", "by_exception.meta_lic", "by_exception_only"}, + }, + }, + { + name: "noticeonrecipnotice", + condition: ImpliesNotice, + roots: []string{"mitBin.meta_lic"}, + edges: []annotated{ + {"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"mitBin.meta_lic", "mitBin.meta_lic", "mitBin.meta_lic", "notice"}, + {"mitBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"}, + }, + }, + { + name: "noticeonreciprecip", + condition: ImpliesReciprocal, + roots: []string{"mitBin.meta_lic"}, + edges: []annotated{ + {"mitBin.meta_lic", "mplLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"mitBin.meta_lic", "mplLib.meta_lic", "mplLib.meta_lic", "reciprocal"}, + }, + }, + { + name: "reciponnoticenotice", + condition: ImpliesNotice, + roots: []string{"mplBin.meta_lic"}, + edges: []annotated{ + {"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"mplBin.meta_lic", "mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"}, + {"mplBin.meta_lic", "mitLib.meta_lic", "mitLib.meta_lic", "notice"}, + }, + }, + { + name: "reciponnoticerecip", + condition: ImpliesReciprocal, + roots: []string{"mplBin.meta_lic"}, + edges: []annotated{ + {"mplBin.meta_lic", "mitLib.meta_lic", []string{"static"}}, + }, + expectedResolutions: []res{ + {"mplBin.meta_lic", "mplBin.meta_lic", "mplBin.meta_lic", "reciprocal"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stderr := &bytes.Buffer{} + lg, err := toGraph(stderr, tt.roots, tt.edges) + if err != nil { + t.Errorf("unexpected test data error: got %w, want no error", err) + return + } + expectedRs := toResolutionSet(lg, tt.expectedResolutions) + actualRs := WalkResolutionsForCondition(lg, ResolveTopDownConditions(lg), tt.condition) + checkSame(actualRs, expectedRs, t) + }) + } +} diff --git a/tools/compliance/resolutionset_test.go b/tools/compliance/resolutionset_test.go index 265357abb3..e50e8230a7 100644 --- a/tools/compliance/resolutionset_test.go +++ b/tools/compliance/resolutionset_test.go @@ -125,10 +125,10 @@ func TestResolutionSet_AttachedToTarget(t *testing.T) { rsShare := toResolutionSet(lg, share) - if rsShare.AttachedToTarget(newTestNode(lg, "binc")) { + if rsShare.AttachesToTarget(newTestNode(lg, "binc")) { t.Errorf("unexpected AttachedToTarget(\"binc\"): got true, want false") } - if !rsShare.AttachedToTarget(newTestNode(lg, "image")) { + if !rsShare.AttachesToTarget(newTestNode(lg, "image")) { t.Errorf("unexpected AttachedToTarget(\"image\"): got false want true") } } diff --git a/tools/compliance/test_util.go b/tools/compliance/test_util.go index 46df45e00f..a183b9014f 100644 --- a/tools/compliance/test_util.go +++ b/tools/compliance/test_util.go @@ -30,6 +30,85 @@ const ( license_kinds: "SPDX-license-identifier-Apache-2.0" license_conditions: "notice" ` + + // GPL starts a test metadata file for GPL 2.0 licensing. + GPL = `` + +`package_name: "Free Software" +license_kinds: "SPDX-license-identifier-GPL-2.0" +license_conditions: "restricted" +` + + // Classpath starts a test metadata file for GPL 2.0 with classpath exception licensing. + Classpath = `` + +`package_name: "Free Software" +license_kinds: "SPDX-license-identifier-GPL-2.0-with-classpath-exception" +license_conditions: "restricted" +` + + // DependentModule starts a test metadata file for a module in the same package as `Classpath`. + DependentModule = `` + +`package_name: "Free Software" +license_kinds: "SPDX-license-identifier-MIT" +license_conditions: "notice" +` + + // LGPL starts a test metadata file for a module with LGPL 2.0 licensing. + LGPL = `` + +`package_name: "Free Library" +license_kinds: "SPDX-license-identifier-LGPL-2.0" +license_conditions: "restricted" +` + + // MPL starts a test metadata file for a module with MPL 2.0 reciprical licensing. + MPL = `` + +`package_name: "Reciprocal" +license_kinds: "SPDX-license-identifier-MPL-2.0" +license_conditions: "reciprocal" +` + + // MIT starts a test metadata file for a module with generic notice (MIT) licensing. + MIT = `` + +`package_name: "Android" +license_kinds: "SPDX-license-identifier-MIT" +license_conditions: "notice" +` + + // Proprietary starts a test metadata file for a module with proprietary licensing. + Proprietary = `` + +`package_name: "Android" +license_kinds: "legacy_proprietary" +license_conditions: "proprietary" +` + + // ByException starts a test metadata file for a module with by_exception_only licensing. + ByException = `` + +`package_name: "Special" +license_kinds: "legacy_by_exception_only" +license_conditions: "by_exception_only" +` + +) + +var ( + // meta maps test file names to metadata file content without dependencies. + meta = map[string]string{ + "apacheBin.meta_lic": AOSP, + "apacheLib.meta_lic": AOSP, + "apacheContainer.meta_lic": AOSP + "is_container: true\n", + "dependentModule.meta_lic": DependentModule, + "gplWithClasspathException.meta_lic": Classpath, + "gplBin.meta_lic": GPL, + "gplLib.meta_lic": GPL, + "gplContainer.meta_lic": GPL + "is_container: true\n", + "lgplBin.meta_lic": LGPL, + "lgplLib.meta_lic": LGPL, + "mitBin.meta_lic": MIT, + "mitLib.meta_lic": MIT, + "mplBin.meta_lic": MPL, + "mplLib.meta_lic": MPL, + "proprietary.meta_lic": Proprietary, + "by_exception.meta_lic": ByException, + } ) // toConditionList converts a test data map of condition name to origin names into a ConditionList. @@ -125,6 +204,98 @@ func (l byEdge) Less(i, j int) bool { return l[i].target < l[j].target } + +// annotated describes annotated test data edges to define test graphs. +type annotated struct { + target, dep string + annotations []string +} + +func (e annotated) String() string { + if e.annotations != nil { + return e.target + " -> " + e.dep + " [" + strings.Join(e.annotations, ", ") + "]" + } + return e.target + " -> " + e.dep +} + +func (e annotated) IsEqualTo(other annotated) bool { + if e.target != other.target { + return false + } + if e.dep != other.dep { + return false + } + if len(e.annotations) != len(other.annotations) { + return false + } + a1 := append([]string{}, e.annotations...) + a2 := append([]string{}, other.annotations...) + for i := 0; i < len(a1); i++ { + if a1[i] != a2[i] { + return false + } + } + return true +} + +// toGraph converts a list of roots and a list of annotated edges into a test license graph. +func toGraph(stderr io.Writer, roots []string, edges []annotated) (*LicenseGraph, error) { + deps := make(map[string][]annotated) + for _, root := range roots { + deps[root] = []annotated{} + } + for _, edge := range edges { + if prev, ok := deps[edge.target]; ok { + deps[edge.target] = append(prev, edge) + } else { + deps[edge.target] = []annotated{edge} + } + if _, ok := deps[edge.dep]; !ok { + deps[edge.dep] = []annotated{} + } + } + fs := make(testFS) + for file, edges := range deps { + body := meta[file] + for _, edge := range edges { + body += fmt.Sprintf("deps: {\n file: %q\n", edge.dep) + for _, ann := range edge.annotations { + body += fmt.Sprintf(" annotations: %q\n", ann) + } + body += "}\n" + } + fs[file] = []byte(body) + } + + return ReadLicenseGraph(&fs, stderr, roots) +} + + +// byAnnotatedEdge orders edges by target then dep name then annotations. +type byAnnotatedEdge []annotated + +func (l byAnnotatedEdge) Len() int { return len(l) } +func (l byAnnotatedEdge) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l byAnnotatedEdge) Less(i, j int) bool { + if l[i].target == l[j].target { + if l[i].dep == l[j].dep { + ai := append([]string{}, l[i].annotations...) + aj := append([]string{}, l[j].annotations...) + sort.Strings(ai) + sort.Strings(aj) + for k := 0; k < len(ai) && k < len(aj); k++ { + if ai[k] == aj[k] { + continue + } + return ai[k] < aj[k] + } + return len(ai) < len(aj) + } + return l[i].dep < l[j].dep + } + return l[i].target < l[j].target +} + // res describes test data resolutions to define test resolution sets. type res struct { attachesTo, actsOn, origin, condition string @@ -148,6 +319,28 @@ func toResolutionSet(lg *LicenseGraph, data []res) *ResolutionSet { return &ResolutionSet{rmap} } +type confl struct { + sourceNode, share, privacy string +} + +func toConflictList(lg *LicenseGraph, data []confl) []SourceSharePrivacyConflict { + result := make([]SourceSharePrivacyConflict, 0, len(data)) + for _, c := range data { + fields := strings.Split(c.share, ":") + oshare := fields[0] + cshare := fields[1] + fields = strings.Split(c.privacy, ":") + oprivacy := fields[0] + cprivacy := fields[1] + result = append(result, SourceSharePrivacyConflict{ + newTestNode(lg, c.sourceNode), + LicenseCondition{cshare, newTestNode(lg, oshare)}, + LicenseCondition{cprivacy, newTestNode(lg, oprivacy)}, + }) + } + return result +} + // checkSameActions compares an actual action set to an expected action set for a test. func checkSameActions(lg *LicenseGraph, asActual, asExpected actionSet, t *testing.T) { rsActual := ResolutionSet{make(map[*TargetNode]actionSet)}