checkers.go (3640B)
1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package hqt 15 16 import ( 17 "errors" 18 "fmt" 19 "reflect" 20 "strings" 21 22 qt "github.com/frankban/quicktest" 23 "github.com/gohugoio/hugo/htesting" 24 "github.com/google/go-cmp/cmp" 25 "github.com/spf13/cast" 26 ) 27 28 // IsSameString asserts that two strings are equal. The two strings 29 // are normalized (whitespace removed) before doing a ==. 30 // Also note that two strings can be the same even if they're of different 31 // types. 32 var IsSameString qt.Checker = &stringChecker{ 33 argNames: []string{"got", "want"}, 34 } 35 36 // IsSameType asserts that got is the same type as want. 37 var IsSameType qt.Checker = &typeChecker{ 38 argNames: []string{"got", "want"}, 39 } 40 41 type argNames []string 42 43 func (a argNames) ArgNames() []string { 44 return a 45 } 46 47 type typeChecker struct { 48 argNames 49 } 50 51 // Check implements Checker.Check by checking that got and args[0] is of the same type. 52 func (c *typeChecker) Check(got any, args []any, note func(key string, value any)) (err error) { 53 if want := args[0]; reflect.TypeOf(got) != reflect.TypeOf(want) { 54 if _, ok := got.(error); ok && want == nil { 55 return errors.New("got non-nil error") 56 } 57 return errors.New("values are not of same type") 58 } 59 return nil 60 } 61 62 type stringChecker struct { 63 argNames 64 } 65 66 // Check implements Checker.Check by checking that got and args[0] represents the same normalized text (whitespace etc. removed). 67 func (c *stringChecker) Check(got any, args []any, note func(key string, value any)) (err error) { 68 s1, s2 := cast.ToString(got), cast.ToString(args[0]) 69 70 if s1 == s2 { 71 return nil 72 } 73 74 s1, s2 = normalizeString(s1), normalizeString(s2) 75 76 if s1 == s2 { 77 return nil 78 } 79 80 return fmt.Errorf("values are not the same text: %s", strings.Join(htesting.DiffStrings(s1, s2), " | ")) 81 } 82 83 func normalizeString(s string) string { 84 s = strings.ReplaceAll(s, "\r\n", "\n") 85 86 lines := strings.Split(strings.TrimSpace(s), "\n") 87 for i, line := range lines { 88 lines[i] = strings.Join(strings.Fields(strings.TrimSpace(line)), "") 89 } 90 return strings.Join(lines, "\n") 91 } 92 93 // DeepAllowUnexported creates an option to allow compare of unexported types 94 // in the given list of types. 95 // see https://github.com/google/go-cmp/issues/40#issuecomment-328615283 96 func DeepAllowUnexported(vs ...any) cmp.Option { 97 m := make(map[reflect.Type]struct{}) 98 for _, v := range vs { 99 structTypes(reflect.ValueOf(v), m) 100 } 101 var typs []any 102 for t := range m { 103 typs = append(typs, reflect.New(t).Elem().Interface()) 104 } 105 return cmp.AllowUnexported(typs...) 106 } 107 108 func structTypes(v reflect.Value, m map[reflect.Type]struct{}) { 109 if !v.IsValid() { 110 return 111 } 112 switch v.Kind() { 113 case reflect.Ptr: 114 if !v.IsNil() { 115 structTypes(v.Elem(), m) 116 } 117 case reflect.Interface: 118 if !v.IsNil() { 119 structTypes(v.Elem(), m) 120 } 121 case reflect.Slice, reflect.Array: 122 for i := 0; i < v.Len(); i++ { 123 structTypes(v.Index(i), m) 124 } 125 case reflect.Map: 126 for _, k := range v.MapKeys() { 127 structTypes(v.MapIndex(k), m) 128 } 129 case reflect.Struct: 130 m[v.Type()] = struct{}{} 131 for i := 0; i < v.NumField(); i++ { 132 structTypes(v.Field(i), m) 133 } 134 } 135 }