github.com/Aoi-hosizora/ahlib@v1.5.1-0.20230404072829-241b93cf91c7/xtesting/xtesting_internal.go (about) 1 package xtesting 2 3 import ( 4 "errors" 5 "fmt" 6 "github.com/Aoi-hosizora/ahlib/xreflect" 7 "math" 8 "reflect" 9 "regexp" 10 "strings" 11 ) 12 13 // ================== 14 // internal functions 15 // ================== 16 17 // validateArgsAreNotFunc checks whether provided arguments are not function type, and can be safely used in Equal, NotEqual, 18 // EqualValue, NotEqualValue functions. 19 func validateArgsAreNotFunc(give, want interface{}) error { 20 if give == nil || want == nil { 21 return nil 22 } 23 24 giveKind := reflect.TypeOf(give).Kind() 25 wantKind := reflect.TypeOf(want).Kind() 26 if giveKind == reflect.Func || wantKind == reflect.Func { 27 return errors.New("cannot take func type as argument") 28 } 29 30 return nil 31 } 32 33 // validateArgsAreSameKind checks whether provided arguments are all non-nil, and have the same reflect.Kind. 34 func validateArgsAreSameKind(give, want interface{}, kind reflect.Kind) error { 35 if give == nil || want == nil { 36 return errors.New("cannot take nil value as argument") 37 } 38 39 giveType := reflect.TypeOf(give) 40 wantType := reflect.TypeOf(want) 41 if giveType.Kind() != kind { 42 return fmt.Errorf("cannot take non-%s type (%T) as argument", kind.String(), give) 43 } 44 if wantType.Kind() != kind { 45 return fmt.Errorf("cannot take non-%s type (%T) as argument", kind.String(), want) 46 } 47 48 return nil 49 } 50 51 // matchRegexp returns true if a specified regexp matches a string. 52 func matchRegexp(rx interface{}, str string) (matched bool, re *regexp.Regexp, err error) { 53 var r *regexp.Regexp 54 if rr, ok := rx.(*regexp.Regexp); ok { 55 r = rr 56 } else if s, ok := rx.(string); ok { 57 r, err = regexp.Compile(s) 58 if err != nil { 59 return false, nil, errors.New("invalid regexp") 60 } 61 } else { 62 return false, nil, errors.New("must be *regexp.Regexp or string") 63 } 64 65 return r.FindStringIndex(str) != nil, r, nil 66 } 67 68 // calcDiffInDelta calculates the different between given values. 69 func calcDiffInDelta(give, want interface{}, delta float64) (inDelta bool, actualDiff float64, err error) { 70 giveFloat, ok1 := xreflect.Float64Value(give) 71 wantFloat, ok2 := xreflect.Float64Value(want) 72 73 if !ok1 || !ok2 { 74 return false, 0, errors.New("parameters must be numerical") 75 } 76 if math.IsNaN(giveFloat) || math.IsNaN(wantFloat) { 77 return false, 0, errors.New("numbers must not be NaN") 78 } 79 if math.IsNaN(delta) { 80 return false, 0, errors.New("delta must not be NaN") 81 } 82 83 actualDiff = math.Abs(giveFloat - wantFloat) 84 return actualDiff <= math.Abs(delta), actualDiff, nil 85 } 86 87 // calcRelativeError calculates the relative error between given values. 88 func calcRelativeError(give, want interface{}, epsilon float64) (inEps bool, actualRee float64, err error) { 89 giveFloat, ok1 := xreflect.Float64Value(give) 90 wantFloat, ok2 := xreflect.Float64Value(want) 91 92 if !ok1 || !ok2 { 93 return false, 0, errors.New("parameters must be numerical") 94 } 95 if math.IsNaN(giveFloat) || math.IsNaN(wantFloat) { 96 return false, 0, errors.New("numbers must not be NaN") 97 } 98 if math.IsNaN(epsilon) { 99 return false, 0, errors.New("epsilon must not be NaN") 100 } 101 if wantFloat == 0 { 102 return false, 0, fmt.Errorf("wanted value must not be zero") 103 } 104 105 actualRee = math.Abs(giveFloat-wantFloat) / math.Abs(wantFloat) 106 return actualRee <= math.Abs(epsilon), actualRee, nil 107 } 108 109 // containElement try loop over the list check if the list includes the element. 110 func containElement(list interface{}, element interface{}) (found bool, err error) { 111 if list == nil || element == nil { 112 return false, errors.New("cannot take nil as argument") 113 } 114 115 listValue := reflect.ValueOf(list) 116 elemType := reflect.TypeOf(element) 117 listType := listValue.Type() 118 listKind := listValue.Kind() 119 120 if listKind == reflect.String && elemType.Kind() != reflect.String { 121 return false, fmt.Errorf("cannot take incompatible element type `%T` as argument", element) 122 } 123 if listKind == reflect.Array || listKind == reflect.Slice || listKind == reflect.Map { 124 listElemType := listType.Elem() // allow interface{} values compare with all other values 125 if listElemType.Kind() != reflect.Interface && !reflect.DeepEqual(elemType, listElemType) { 126 return false, fmt.Errorf("cannot take incompatible element type `%T` as argument", element) 127 } 128 } 129 130 switch listKind { 131 case reflect.String: 132 elementValue := reflect.ValueOf(element) 133 return strings.Contains(listValue.String(), elementValue.String()), nil 134 135 case reflect.Map: 136 mapKeys := listValue.MapKeys() 137 for i := 0; i < len(mapKeys); i++ { 138 if reflect.DeepEqual(mapKeys[i].Interface(), element) { 139 return true, nil 140 } 141 } 142 return false, nil 143 144 case reflect.Array, reflect.Slice: 145 for i := 0; i < listValue.Len(); i++ { 146 if reflect.DeepEqual(listValue.Index(i).Interface(), element) { 147 return true, nil 148 } 149 } 150 return false, nil 151 152 default: 153 return false, fmt.Errorf("cannot take a non-list type `%T` as argument", list) 154 } 155 } 156 157 // validateArgsAreSameList checks that the provided value is array or slice, and their types are the same. 158 func validateArgsAreSameList(listA, listB interface{}) error { 159 if listA == nil || listB == nil { 160 return errors.New("cannot take nil as argument") 161 } 162 163 typeA, typeB := reflect.TypeOf(listA), reflect.TypeOf(listB) 164 kindA, kindB := typeA.Kind(), typeB.Kind() 165 166 if kindA != reflect.Array && kindA != reflect.Slice { 167 return fmt.Errorf("cannot take a non-list type `%T` as argument", listA) 168 } 169 if kindB != reflect.Array && kindB != reflect.Slice { 170 return fmt.Errorf("cannot take a non-list type `%T` as argument", listB) 171 } 172 if !reflect.DeepEqual(typeA, typeB) { 173 return fmt.Errorf("cannot take two lists in different-types `%T` and `%T` as argument", listA, listB) 174 } 175 176 return nil 177 } 178 179 // containAllElements checks the specified list contains all elements given in the specified subset. 180 func containAllElements(list, subset interface{}) (allFound bool, element interface{}) { 181 if xreflect.IsEmptyCollection(list) && xreflect.IsEmptyCollection(subset) { 182 return true, nil 183 } 184 185 subsetValue := reflect.ValueOf(subset) 186 for i := 0; i < subsetValue.Len(); i++ { 187 el := subsetValue.Index(i).Interface() 188 found, err := containElement(list, el) 189 190 if err != nil || !found { 191 return false, el 192 } 193 } 194 195 return true, nil 196 } 197 198 // diffLists diffs two arrays/slices and returns slices of elements that are only in A and only in B. If some element is 199 // present multiple times, each instance is counted separately. The order of items in both lists is ignored. 200 func diffLists(listA, listB interface{}) (extraA []interface{}, extraB []interface{}) { 201 if (xreflect.IsEmptyCollection(listA)) && (xreflect.IsEmptyCollection(listB)) { 202 return nil, nil 203 } 204 205 valueA, valueB := reflect.ValueOf(listA), reflect.ValueOf(listB) 206 lenA, lenB := valueA.Len(), valueB.Len() 207 visited := make([]bool, lenB) // Mark indexes in valueB that we already used 208 209 for i := 0; i < lenA; i++ { 210 element := valueA.Index(i).Interface() 211 found := false 212 for j := 0; j < lenB; j++ { 213 if visited[j] { 214 continue 215 } 216 if reflect.DeepEqual(valueB.Index(j).Interface(), element) { 217 visited[j] = true 218 found = true 219 break 220 } 221 } 222 if !found { 223 extraA = append(extraA, element) 224 } 225 } 226 227 for j := 0; j < lenB; j++ { 228 if visited[j] { 229 continue 230 } 231 extraB = append(extraB, valueB.Index(j).Interface()) 232 } 233 234 return extraA, extraB 235 } 236 237 // validateArgsForImplement checks the value of object is not nil, and checks the type of interfacePtr is *SomeInterface. 238 func validateArgsForImplement(value, interfacePtr interface{}) (interfaceType reflect.Type, err error) { 239 interfaceType = reflect.TypeOf(interfacePtr) 240 if interfaceType == nil { 241 return nil, errors.New("cannot take nil as interfacePtr argument") 242 } 243 if interfaceType.Kind() != reflect.Ptr { 244 return nil, errors.New("cannot take non-interface-pointer type as interfacePtr argument") 245 } 246 interfaceType = interfaceType.Elem() 247 if interfaceType.Kind() != reflect.Interface { 248 return nil, errors.New("cannot take non-interface-pointer type as interfacePtr argument") 249 } 250 251 if value == nil { 252 return nil, fmt.Errorf("cannot check whether nil value implements `%s` or not", interfaceType.String()) 253 } 254 255 return interfaceType, nil 256 } 257 258 // checkPanic returns true if the function passed to it panics. Otherwise, it returns false. 259 func checkPanic(f func()) (didPanic bool, message interface{}) { 260 // set to true first, used to detect panic(nil) 261 didPanic = true 262 263 defer func() { 264 message = recover() 265 }() 266 267 // call the target function 268 f() 269 didPanic = false 270 271 return 272 }