Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8b078073 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "compliance package structures for license metadata" am: f645c504

Original change: https://android-review.googlesource.com/c/platform/build/+/1906558

Change-Id: I1e75ad6a44936f66c5d997590ffaf7d162b2fa1d
parents 7939f4bc f645c504
Loading
Loading
Loading
Loading
+44 −0
Original line number Diff line number Diff line
//
// Copyright (C) 2021 The Android Open Source Project
//
// 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 {
    default_applicable_licenses: ["Android-Apache-2.0"],
}

bootstrap_go_package {
    name: "compliance-module",
    srcs: [
        "actionset.go",
        "condition.go",
        "conditionset.go",
        "graph.go",
        "readgraph.go",
        "resolution.go",
        "resolutionset.go",
    ],
    testSrcs: [
        "condition_test.go",
        "conditionset_test.go",
        "readgraph_test.go",
        "resolutionset_test.go",
        "test_util.go",
    ],
    deps: [
        "golang-protobuf-proto",
        "golang-protobuf-encoding-prototext",
        "license_metadata_proto",
    ],
    pkgPath: "compliance",
}
+110 −0
Original line number Diff line number Diff line
// 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"
	"sort"
	"strings"
)

// actionSet maps `actOn` target nodes to the license conditions the actions resolve.
type actionSet map[*TargetNode]*LicenseConditionSet

// String returns a string representation of the set.
func (as actionSet) String() string {
	var sb strings.Builder
	fmt.Fprintf(&sb, "{")
	osep := ""
	for actsOn, cs := range as {
		cl := cs.AsList()
		sort.Sort(cl)
		fmt.Fprintf(&sb, "%s%s -> %s", osep, actsOn.name, cl.String())
		osep = ", "
	}
	fmt.Fprintf(&sb, "}")
	return sb.String()
}

// byName returns the subset of `as` actions where the condition name is in `names`.
func (as actionSet) byName(names ConditionNames) actionSet {
	result := make(actionSet)
	for actsOn, cs := range as {
		bn := cs.ByName(names)
		if bn.IsEmpty() {
			continue
		}
		result[actsOn] = bn
	}
	return result
}

// byActsOn returns the subset of `as` where `actsOn` is in the `reachable` target node set.
func (as actionSet) byActsOn(reachable *TargetNodeSet) actionSet {
	result := make(actionSet)
	for actsOn, cs := range as {
		if !reachable.Contains(actsOn) || cs.IsEmpty() {
			continue
		}
		result[actsOn] = cs.Copy()
	}
	return result
}

// copy returns another actionSet with the same value as `as`
func (as actionSet) copy() actionSet {
	result := make(actionSet)
	for actsOn, cs := range as {
		if cs.IsEmpty() {
			continue
		}
		result[actsOn] = cs.Copy()
	}
	return result
}

// addSet adds all of the actions of `other` if not already present.
func (as actionSet) addSet(other actionSet) {
	for actsOn, cs := range other {
		as.add(actsOn, cs)
	}
}

// add makes the action on `actsOn` to resolve the conditions in `cs` a member of the set.
func (as actionSet) add(actsOn *TargetNode, cs *LicenseConditionSet) {
	if acs, ok := as[actsOn]; ok {
		acs.AddSet(cs)
	} else {
		as[actsOn] = cs.Copy()
	}
}

// addCondition makes the action on `actsOn` to resolve `lc` a member of the set.
func (as actionSet) addCondition(actsOn *TargetNode, lc LicenseCondition) {
	if _, ok := as[actsOn]; !ok {
		as[actsOn] = newLicenseConditionSet()
	}
	as[actsOn].Add(lc)
}

// isEmpty returns true if no action to resolve a condition exists.
func (as actionSet) isEmpty() bool {
	for _, cs := range as {
		if !cs.IsEmpty() {
			return false
		}
	}
	return true
}
+156 −0
Original line number Diff line number Diff line
// 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"
	"strings"
)

// LicenseCondition describes an individual license condition or requirement
// originating at a specific target node. (immutable)
//
// e.g. A module licensed under GPL terms would originate a `restricted` condition.
type LicenseCondition struct {
	name   string
	origin *TargetNode
}

// Name returns the name of the condition. e.g. "restricted" or "notice"
func (lc LicenseCondition) Name() string {
	return lc.name
}

// Origin identifies the TargetNode where the condition originates.
func (lc LicenseCondition) Origin() *TargetNode {
	return lc.origin
}

// asString returns a string representation of a license condition:
// origin+separator+condition.
func (lc LicenseCondition) asString(separator string) string {
	return lc.origin.name + separator + lc.name
}

// ConditionList implements introspection methods to arrays of LicenseCondition.
type ConditionList []LicenseCondition


// ConditionList orders arrays of LicenseCondition by Origin and Name.

// Len returns the length of the list.
func (l ConditionList) Len() int      { return len(l) }

// Swap rearranges 2 elements in the list so each occupies the other's former position.
func (l ConditionList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }

// Less returns true when the `i`th element is lexicographically less than tht `j`th element.
func (l ConditionList) Less(i, j int) bool {
	if l[i].origin.name == l[j].origin.name {
		return l[i].name < l[j].name
	}
	return l[i].origin.name < l[j].origin.name
}

// String returns a string representation of the set.
func (cl ConditionList) String() string {
	var sb strings.Builder
	fmt.Fprintf(&sb, "[")
	sep := ""
	for _, lc := range cl {
		fmt.Fprintf(&sb, "%s%s:%s", sep, lc.origin.name, lc.name)
		sep = ", "
	}
	fmt.Fprintf(&sb, "]")
	return sb.String()
}

// HasByName returns true if the list contains any condition matching `name`.
func (cl ConditionList) HasByName(name ConditionNames) bool {
	for _, lc := range cl {
		if name.Contains(lc.name) {
			return true
		}
	}
	return false
}

// ByName returns the sublist of conditions that match `name`.
func (cl ConditionList) ByName(name ConditionNames) ConditionList {
	result := make(ConditionList, 0, cl.CountByName(name))
	for _, lc := range cl {
		if name.Contains(lc.name) {
			result = append(result, lc)
		}
	}
	return result
}

// CountByName returns the size of the sublist of conditions that match `name`.
func (cl ConditionList) CountByName(name ConditionNames) int {
	size := 0
	for _, lc := range cl {
		if name.Contains(lc.name) {
			size++
		}
	}
	return size
}

// HasByOrigin returns true if the list contains any condition originating at `origin`.
func (cl ConditionList) HasByOrigin(origin *TargetNode) bool {
	for _, lc := range cl {
		if lc.origin.name == origin.name {
			return true
		}
	}
	return false
}

// ByOrigin returns the sublist of conditions that originate at `origin`.
func (cl ConditionList) ByOrigin(origin *TargetNode) ConditionList {
	result := make(ConditionList, 0, cl.CountByOrigin(origin))
	for _, lc := range cl {
		if lc.origin.name == origin.name {
			result = append(result, lc)
		}
	}
	return result
}

// CountByOrigin returns the size of the sublist of conditions that originate at `origin`.
func (cl ConditionList) CountByOrigin(origin *TargetNode) int {
	size := 0
	for _, lc := range cl {
		if lc.origin.name == origin.name {
			size++
		}
	}
	return size
}

// ConditionNames implements the Contains predicate for slices of condition
// name strings.
type ConditionNames []string

// Contains returns true if the name matches one of the ConditionNames.
func (cn ConditionNames) Contains(name string) bool {
	for _, cname := range cn {
		if cname == name {
			return true
		}
	}
	return false
}
+218 −0
Original line number Diff line number Diff line
// 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 (
	"sort"
	"strings"
	"testing"
)

func TestConditionNames(t *testing.T) {
	impliesShare := ConditionNames([]string{"restricted", "reciprocal"})

	if impliesShare.Contains("notice") {
		t.Errorf("impliesShare.Contains(\"notice\") got true, want false")
	}

	if !impliesShare.Contains("restricted") {
		t.Errorf("impliesShare.Contains(\"restricted\") got false, want true")
	}

	if !impliesShare.Contains("reciprocal") {
		t.Errorf("impliesShare.Contains(\"reciprocal\") got false, want true")
	}

	if impliesShare.Contains("") {
		t.Errorf("impliesShare.Contains(\"\") got true, want false")
	}
}

func TestConditionList(t *testing.T) {
	tests := []struct {
		name       string
		conditions map[string][]string
		byName     map[string][]string
		byOrigin   map[string][]string
	}{
		{
			name: "noticeonly",
			conditions: map[string][]string{
				"notice": []string{"bin1", "lib1"},
			},
			byName: map[string][]string{
				"notice":     []string{"bin1", "lib1"},
				"restricted": []string{},
			},
			byOrigin: map[string][]string{
				"bin1": []string{"notice"},
				"lib1": []string{"notice"},
				"bin2": []string{},
				"lib2": []string{},
			},
		},
		{
			name:       "empty",
			conditions: map[string][]string{},
			byName: map[string][]string{
				"notice":     []string{},
				"restricted": []string{},
			},
			byOrigin: map[string][]string{
				"bin1": []string{},
				"lib1": []string{},
				"bin2": []string{},
				"lib2": []string{},
			},
		},
		{
			name: "everything",
			conditions: map[string][]string{
				"notice":            []string{"bin1", "bin2", "lib1", "lib2"},
				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
				"restricted":        []string{"bin1", "bin2", "lib1", "lib2"},
				"by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
			},
			byName: map[string][]string{
				"permissive":        []string{},
				"notice":            []string{"bin1", "bin2", "lib1", "lib2"},
				"reciprocal":        []string{"bin1", "bin2", "lib1", "lib2"},
				"restricted":        []string{"bin1", "bin2", "lib1", "lib2"},
				"by_exception_only": []string{"bin1", "bin2", "lib1", "lib2"},
			},
			byOrigin: map[string][]string{
				"bin1":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
				"bin2":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
				"lib1":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
				"lib2":  []string{"notice", "reciprocal", "restricted", "by_exception_only"},
				"other": []string{},
			},
		},
		{
			name: "allbutoneeach",
			conditions: map[string][]string{
				"notice":            []string{"bin2", "lib1", "lib2"},
				"reciprocal":        []string{"bin1", "lib1", "lib2"},
				"restricted":        []string{"bin1", "bin2", "lib2"},
				"by_exception_only": []string{"bin1", "bin2", "lib1"},
			},
			byName: map[string][]string{
				"permissive":        []string{},
				"notice":            []string{"bin2", "lib1", "lib2"},
				"reciprocal":        []string{"bin1", "lib1", "lib2"},
				"restricted":        []string{"bin1", "bin2", "lib2"},
				"by_exception_only": []string{"bin1", "bin2", "lib1"},
			},
			byOrigin: map[string][]string{
				"bin1":  []string{"reciprocal", "restricted", "by_exception_only"},
				"bin2":  []string{"notice", "restricted", "by_exception_only"},
				"lib1":  []string{"notice", "reciprocal", "by_exception_only"},
				"lib2":  []string{"notice", "reciprocal", "restricted"},
				"other": []string{},
			},
		},
		{
			name: "oneeach",
			conditions: map[string][]string{
				"notice":            []string{"bin1"},
				"reciprocal":        []string{"bin2"},
				"restricted":        []string{"lib1"},
				"by_exception_only": []string{"lib2"},
			},
			byName: map[string][]string{
				"permissive":        []string{},
				"notice":            []string{"bin1"},
				"reciprocal":        []string{"bin2"},
				"restricted":        []string{"lib1"},
				"by_exception_only": []string{"lib2"},
			},
			byOrigin: map[string][]string{
				"bin1":  []string{"notice"},
				"bin2":  []string{"reciprocal"},
				"lib1":  []string{"restricted"},
				"lib2":  []string{"by_exception_only"},
				"other": []string{},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lg := newLicenseGraph()
			cl := toConditionList(lg, tt.conditions)
			for names, expected := range tt.byName {
				name := ConditionNames(strings.Split(names, ":"))
				if cl.HasByName(name) {
					if len(expected) == 0 {
						t.Errorf("unexpected ConditionList.HasByName(%q): got true, want false", name)
					}
				} else {
					if len(expected) != 0 {
						t.Errorf("unexpected ConditionList.HasByName(%q): got false, want true", name)
					}
				}
				if len(expected) != cl.CountByName(name) {
					t.Errorf("unexpected ConditionList.CountByName(%q): got %d, want %d", name, cl.CountByName(name), len(expected))
				}
				byName := cl.ByName(name)
				if len(expected) != len(byName) {
					t.Errorf("unexpected ConditionList.ByName(%q): got %v, want %v", name, byName, expected)
				} else {
					sort.Strings(expected)
					actual := make([]string, 0, len(byName))
					for _, lc := range byName {
						actual = append(actual, lc.Origin().Name())
					}
					sort.Strings(actual)
					for i := 0; i < len(expected); i++ {
						if expected[i] != actual[i] {
							t.Errorf("unexpected ConditionList.ByName(%q) index %d in %v: got %s, want %s", name, i, actual, actual[i], expected[i])
						}
					}
				}
			}
			for origin, expected := range tt.byOrigin {
				onode := newTestNode(lg, origin)
				if cl.HasByOrigin(onode) {
					if len(expected) == 0 {
						t.Errorf("unexpected ConditionList.HasByOrigin(%q): got true, want false", origin)
					}
				} else {
					if len(expected) != 0 {
						t.Errorf("unexpected ConditionList.HasByOrigin(%q): got false, want true", origin)
					}
				}
				if len(expected) != cl.CountByOrigin(onode) {
					t.Errorf("unexpected ConditionList.CountByOrigin(%q): got %d, want %d", origin, cl.CountByOrigin(onode), len(expected))
				}
				byOrigin := cl.ByOrigin(onode)
				if len(expected) != len(byOrigin) {
					t.Errorf("unexpected ConditionList.ByOrigin(%q): got %v, want %v", origin, byOrigin, expected)
				} else {
					sort.Strings(expected)
					actual := make([]string, 0, len(byOrigin))
					for _, lc := range byOrigin {
						actual = append(actual, lc.Name())
					}
					sort.Strings(actual)
					for i := 0; i < len(expected); i++ {
						if expected[i] != actual[i] {
							t.Errorf("unexpected ConditionList.ByOrigin(%q) index %d in %v: got %s, want %s", origin, i, actual, actual[i], expected[i])
						}
					}
				}
			}
		})
	}
}
+269 −0
Original line number Diff line number Diff line
// 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"
)

// NewLicenseConditionSet creates a new instance or variable of *LicenseConditionSet.
func NewLicenseConditionSet(conditions ...LicenseCondition) *LicenseConditionSet {
	cs := newLicenseConditionSet()
	cs.Add(conditions...)
	return cs
}

// LicenseConditionSet describes a mutable set of immutable license conditions.
type LicenseConditionSet struct {
	// conditions describes the set of license conditions i.e. (condition name, origin target) pairs
	// by mapping condition name -> origin target -> true.
	conditions map[string]map[*TargetNode]bool
}

// Add makes all `conditions` members of the set if they were not previously.
func (cs *LicenseConditionSet) Add(conditions ...LicenseCondition) {
	if len(conditions) == 0 {
		return
	}
	for _, lc := range conditions {
		if _, ok := cs.conditions[lc.name]; !ok {
			cs.conditions[lc.name] = make(map[*TargetNode]bool)
		}
		cs.conditions[lc.name][lc.origin] = true
	}
}

// AddSet makes all elements of `conditions` members of the set if they were not previously.
func (cs *LicenseConditionSet) AddSet(other *LicenseConditionSet) {
	if len(other.conditions) == 0 {
		return
	}
	for name, origins := range other.conditions {
		if len(origins) == 0 {
			continue
		}
		if _, ok := cs.conditions[name]; !ok {
			cs.conditions[name] = make(map[*TargetNode]bool)
		}
		for origin := range origins {
			cs.conditions[name][origin] = other.conditions[name][origin]
		}
	}
}

// ByName returns a list of the conditions in the set matching `names`.
func (cs *LicenseConditionSet) ByName(names ...ConditionNames) *LicenseConditionSet {
	other := newLicenseConditionSet()
	for _, cn := range names {
		for _, name := range cn {
			if origins, ok := cs.conditions[name]; ok {
				other.conditions[name] = make(map[*TargetNode]bool)
				for origin := range origins {
					other.conditions[name][origin] = true
				}
			}
		}
	}
	return other
}

// HasAnyByName returns true if the set contains any conditions matching `names` originating at any target.
func (cs *LicenseConditionSet) HasAnyByName(names ...ConditionNames) bool {
	for _, cn := range names {
		for _, name := range cn {
			if origins, ok := cs.conditions[name]; ok {
				if len(origins) > 0 {
					return true
				}
			}
		}
	}
	return false
}

// CountByName returns the number of conditions matching `names` originating at any target.
func (cs *LicenseConditionSet) CountByName(names ...ConditionNames) int {
	size := 0
	for _, cn := range names {
		for _, name := range cn {
			if origins, ok := cs.conditions[name]; ok {
				size += len(origins)
			}
		}
	}
	return size
}

// ByOrigin returns all of the conditions that originate at `origin` regardless of name.
func (cs *LicenseConditionSet) ByOrigin(origin *TargetNode) *LicenseConditionSet {
	other := newLicenseConditionSet()
	for name, origins := range cs.conditions {
		if _, ok := origins[origin]; ok {
			other.conditions[name] = make(map[*TargetNode]bool)
			other.conditions[name][origin] = true
		}
	}
	return other
}

// HasAnyByOrigin returns true if the set contains any conditions originating at `origin` regardless of condition name.
func (cs *LicenseConditionSet) HasAnyByOrigin(origin *TargetNode) bool {
	for _, origins := range cs.conditions {
		if _, ok := origins[origin]; ok {
			return true
		}
	}
	return false
}

// CountByOrigin returns the number of conditions originating at `origin` regardless of condition name.
func (cs *LicenseConditionSet) CountByOrigin(origin *TargetNode) int {
	size := 0
	for _, origins := range cs.conditions {
		if _, ok := origins[origin]; ok {
			size++
		}
	}
	return size
}

// AsList returns a list of all the conditions in the set.
func (cs *LicenseConditionSet) AsList() ConditionList {
	result := make(ConditionList, 0, cs.Count())
	for name, origins := range cs.conditions {
		for origin := range origins {
			result = append(result, LicenseCondition{name, origin})
		}
	}
	return result
}

// Count returns the number of conditions in the set.
func (cs *LicenseConditionSet) Count() int {
	size := 0
	for _, origins := range cs.conditions {
		size += len(origins)
	}
	return size
}

// Copy creates a new LicenseCondition variable with the same value.
func (cs *LicenseConditionSet) Copy() *LicenseConditionSet {
	other := newLicenseConditionSet()
	for name := range cs.conditions {
		other.conditions[name] = make(map[*TargetNode]bool)
		for origin := range cs.conditions[name] {
			other.conditions[name][origin] = cs.conditions[name][origin]
		}
	}
	return other
}

// HasCondition returns true if the set contains any condition matching both `names` and `origin`.
func (cs *LicenseConditionSet) HasCondition(names ConditionNames, origin *TargetNode) bool {
	for _, name := range names {
		if origins, ok := cs.conditions[name]; ok {
			_, isPresent := origins[origin]
			if isPresent {
				return true
			}
		}
	}
	return false
}

// IsEmpty returns true when the set of conditions contains zero elements.
func (cs *LicenseConditionSet) IsEmpty() bool {
	for _, origins := range cs.conditions {
		if 0 < len(origins) {
			return false
		}
	}
	return true
}

// RemoveAllByName changes the set to delete all conditions matching `names`.
func (cs *LicenseConditionSet) RemoveAllByName(names ...ConditionNames) {
	for _, cn := range names {
		for _, name := range cn {
			delete(cs.conditions, name)
		}
	}
}

// Remove changes the set to delete `conditions`.
func (cs *LicenseConditionSet) Remove(conditions ...LicenseCondition) {
	for _, lc := range conditions {
		if _, isPresent := cs.conditions[lc.name]; !isPresent {
			panic(fmt.Errorf("attempt to remove non-existent condition: %q", lc.asString(":")))
		}
		if _, isPresent := cs.conditions[lc.name][lc.origin]; !isPresent {
			panic(fmt.Errorf("attempt to remove non-existent origin: %q", lc.asString(":")))
		}
		delete(cs.conditions[lc.name], lc.origin)
	}
}

// removeSet changes the set to delete all conditions also present in `other`.
func (cs *LicenseConditionSet) RemoveSet(other *LicenseConditionSet) {
	for name, origins := range other.conditions {
		if _, isPresent := cs.conditions[name]; !isPresent {
			continue
		}
		for origin := range origins {
			delete(cs.conditions[name], origin)
		}
	}
}

// compliance-only LicenseConditionSet methods

// newLicenseConditionSet constructs a set of `conditions`.
func newLicenseConditionSet() *LicenseConditionSet {
	return &LicenseConditionSet{make(map[string]map[*TargetNode]bool)}
}

// add changes the set to include each element of `conditions` originating at `origin`.
func (cs *LicenseConditionSet) add(origin *TargetNode, conditions ...string) {
	for _, name := range conditions {
		if _, ok := cs.conditions[name]; !ok {
			cs.conditions[name] = make(map[*TargetNode]bool)
		}
		cs.conditions[name][origin] = true
	}
}

// asStringList returns the conditions in the set as `separator`-separated (origin, condition-name) pair strings.
func (cs *LicenseConditionSet) asStringList(separator string) []string {
	result := make([]string, 0, cs.Count())
	for name, origins := range cs.conditions {
		for origin := range origins {
			result = append(result, origin.name+separator+name)
		}
	}
	return result
}

// conditionNamesArray implements a `contains` predicate for arrays of ConditionNames
type conditionNamesArray []ConditionNames

func (cn conditionNamesArray) contains(name string) bool {
	for _, names := range cn {
		if names.Contains(name) {
			return true
		}
	}
	return false
}
Loading