github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/testutil/containschecker.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2018 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package testutil 21 22 import ( 23 "fmt" 24 "reflect" 25 "strings" 26 27 "gopkg.in/check.v1" 28 ) 29 30 type containsChecker struct { 31 *check.CheckerInfo 32 } 33 34 // Contains is a Checker that looks for a elem in a container. 35 // The elem can be any object. The container can be an array, slice or string. 36 var Contains check.Checker = &containsChecker{ 37 &check.CheckerInfo{Name: "Contains", Params: []string{"container", "elem"}}, 38 } 39 40 func commonEquals(container, elem interface{}, result *bool, error *string) bool { 41 containerV := reflect.ValueOf(container) 42 elemV := reflect.ValueOf(elem) 43 switch containerV.Kind() { 44 case reflect.Slice, reflect.Array, reflect.Map: 45 containerElemType := containerV.Type().Elem() 46 if containerElemType.Kind() == reflect.Interface { 47 // Ensure that element implements the type of elements stored in the container. 48 if !elemV.Type().Implements(containerElemType) { 49 *result = false 50 *error = fmt.Sprintf(""+ 51 "container has items of interface type %s but expected"+ 52 " element does not implement it", containerElemType) 53 return true 54 } 55 } else { 56 // Ensure that type of elements in container is compatible with elem 57 if containerElemType != elemV.Type() { 58 *result = false 59 *error = fmt.Sprintf( 60 "container has items of type %s but expected element is a %s", 61 containerElemType, elemV.Type()) 62 return true 63 } 64 } 65 case reflect.String: 66 // When container is a string, we expect elem to be a string as well 67 if elemV.Kind() != reflect.String { 68 *result = false 69 *error = fmt.Sprintf("element is a %T but expected a string", elem) 70 } else { 71 *result = strings.Contains(containerV.String(), elemV.String()) 72 *error = "" 73 } 74 return true 75 } 76 return false 77 } 78 79 func (c *containsChecker) Check(params []interface{}, names []string) (result bool, error string) { 80 defer func() { 81 if v := recover(); v != nil { 82 result = false 83 error = fmt.Sprint(v) 84 } 85 }() 86 var container interface{} = params[0] 87 var elem interface{} = params[1] 88 if commonEquals(container, elem, &result, &error) { 89 return result, error 90 } 91 // Do the actual test using == 92 switch containerV := reflect.ValueOf(container); containerV.Kind() { 93 case reflect.Slice, reflect.Array: 94 for length, i := containerV.Len(), 0; i < length; i++ { 95 itemV := containerV.Index(i) 96 if itemV.Interface() == elem { 97 return true, "" 98 } 99 } 100 return false, "" 101 case reflect.Map: 102 for _, keyV := range containerV.MapKeys() { 103 itemV := containerV.MapIndex(keyV) 104 if itemV.Interface() == elem { 105 return true, "" 106 } 107 } 108 return false, "" 109 default: 110 return false, fmt.Sprintf("%T is not a supported container", container) 111 } 112 } 113 114 type deepContainsChecker struct { 115 *check.CheckerInfo 116 } 117 118 // DeepContains is a Checker that looks for a elem in a container using 119 // DeepEqual. The elem can be any object. The container can be an array, slice 120 // or string. 121 var DeepContains check.Checker = &deepContainsChecker{ 122 &check.CheckerInfo{Name: "DeepContains", Params: []string{"container", "elem"}}, 123 } 124 125 func (c *deepContainsChecker) Check(params []interface{}, names []string) (result bool, error string) { 126 var container interface{} = params[0] 127 var elem interface{} = params[1] 128 if commonEquals(container, elem, &result, &error) { 129 return result, error 130 } 131 // Do the actual test using reflect.DeepEqual 132 switch containerV := reflect.ValueOf(container); containerV.Kind() { 133 case reflect.Slice, reflect.Array: 134 for length, i := containerV.Len(), 0; i < length; i++ { 135 itemV := containerV.Index(i) 136 if reflect.DeepEqual(itemV.Interface(), elem) { 137 return true, "" 138 } 139 } 140 return false, "" 141 case reflect.Map: 142 for _, keyV := range containerV.MapKeys() { 143 itemV := containerV.MapIndex(keyV) 144 if reflect.DeepEqual(itemV.Interface(), elem) { 145 return true, "" 146 } 147 } 148 return false, "" 149 default: 150 return false, fmt.Sprintf("%T is not a supported container", container) 151 } 152 } 153 154 type deepUnsortedMatchChecker struct { 155 *check.CheckerInfo 156 } 157 158 // DeepUnsortedMatches checks if two containers contain the same elements in 159 // the same number (but possibly different order) using DeepEqual. The container 160 // can be an array, a slice or a map. 161 var DeepUnsortedMatches check.Checker = &deepUnsortedMatchChecker{ 162 &check.CheckerInfo{Name: "DeepUnsortedMatches", Params: []string{"container1", "container2"}}, 163 } 164 165 func (c *deepUnsortedMatchChecker) Check(params []interface{}, _ []string) (bool, string) { 166 container1 := reflect.ValueOf(params[0]) 167 container2 := reflect.ValueOf(params[1]) 168 169 // if both args are nil, return true 170 if container1.Kind() == reflect.Invalid && container2.Kind() == reflect.Invalid { 171 return true, "" 172 } 173 174 if container1.Kind() == reflect.Invalid || container2.Kind() == reflect.Invalid { 175 return false, "only one container was nil" 176 } 177 178 if container1.Kind() != container2.Kind() { 179 return false, fmt.Sprintf("containers are of different types: %s != %s", container1.Kind(), container2.Kind()) 180 } 181 182 if container1.Kind() != reflect.Map && container1.Kind() != reflect.Slice && container1.Kind() != reflect.Array { 183 return false, fmt.Sprintf("'%s' is not a supported type: must be slice, array, map or nil", container1.Kind().String()) 184 } 185 186 if container1.Type().Comparable() && params[0] == params[1] { 187 return true, "" 188 } 189 190 if container1.Len() != container2.Len() { 191 return false, fmt.Sprintf("containers have different lengths: %d != %d", container1.Len(), container2.Len()) 192 } 193 194 switch container1.Kind() { 195 case reflect.Array, reflect.Slice: 196 return deepSequenceMatch(container1, container2) 197 198 case reflect.Map: 199 map1 := container1.Interface() 200 map2 := container2.Interface() 201 if !reflect.DeepEqual(map1, map2) { 202 return false, "maps don't match" 203 } 204 205 return true, "" 206 207 default: 208 return false, fmt.Sprintf("%T is not a supported container. Must be a slice, an array or a map", container1) 209 } 210 } 211 212 func deepSequenceMatch(container1 reflect.Value, container2 reflect.Value) (bool, string) { 213 matched := make([]bool, container1.Len()) 214 215 out: 216 for i := 0; i < container1.Len(); i++ { 217 el1 := container1.Index(i).Interface() 218 219 for e := 0; e < container2.Len(); e++ { 220 el2 := container2.Index(e).Interface() 221 222 if !matched[e] && reflect.DeepEqual(el1, el2) { 223 // mark already matched elements, so that duplicate elements in 224 // one container can't be matched to the same element in the other. 225 matched[e] = true 226 continue out 227 } 228 } 229 230 return false, fmt.Sprintf("element [%d]=%s was unmatched in the second container", i, el1) 231 } 232 233 return true, "" 234 }