git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/toml/internal/toml-test/toml.go (about) 1 //go:build go1.16 2 // +build go1.16 3 4 package tomltest 5 6 import ( 7 "math" 8 "reflect" 9 ) 10 11 // CompareTOML compares the given arguments. 12 // 13 // The returned value is a copy of Test with Failure set to a (human-readable) 14 // description of the first element that is unequal. If both arguments are equal 15 // Test is returned unchanged. 16 // 17 // Reflect.DeepEqual could work here, but it won't tell us how the two 18 // structures are different. 19 func (r Test) CompareTOML(want, have interface{}) Test { 20 if isTomlValue(want) { 21 if !isTomlValue(have) { 22 return r.fail("Type for key '%s' differs:\n"+ 23 " Expected: %[2]v (%[2]T)\n"+ 24 " Your encoder: %[3]v (%[3]T)", 25 r.Key, want, have) 26 } 27 28 if !deepEqual(want, have) { 29 return r.fail("Values for key '%s' differ:\n"+ 30 " Expected: %[2]v (%[2]T)\n"+ 31 " Your encoder: %[3]v (%[3]T)", 32 r.Key, want, have) 33 } 34 return r 35 } 36 37 switch w := want.(type) { 38 case map[string]interface{}: 39 return r.cmpTOMLMap(w, have) 40 case []interface{}: 41 return r.cmpTOMLArrays(w, have) 42 default: 43 return r.fail("Unrecognized TOML structure: %T", want) 44 } 45 } 46 47 func (r Test) cmpTOMLMap(want map[string]interface{}, have interface{}) Test { 48 haveMap, ok := have.(map[string]interface{}) 49 if !ok { 50 return r.mismatch("table", want, haveMap) 51 } 52 53 // Check that the keys of each map are equivalent. 54 for k := range want { 55 if _, ok := haveMap[k]; !ok { 56 bunk := r.kjoin(k) 57 return bunk.fail("Could not find key '%s' in encoder output", bunk.Key) 58 } 59 } 60 for k := range haveMap { 61 if _, ok := want[k]; !ok { 62 bunk := r.kjoin(k) 63 return bunk.fail("Could not find key '%s' in expected output", bunk.Key) 64 } 65 } 66 67 // Okay, now make sure that each value is equivalent. 68 for k := range want { 69 if sub := r.kjoin(k).CompareTOML(want[k], haveMap[k]); sub.Failed() { 70 return sub 71 } 72 } 73 return r 74 } 75 76 func (r Test) cmpTOMLArrays(want []interface{}, have interface{}) Test { 77 // Slice can be decoded to []interface{} for an array of primitives, or 78 // []map[string]interface{} for an array of tables. 79 // 80 // TODO: it would be nicer if it could always decode to []interface{}? 81 haveSlice, ok := have.([]interface{}) 82 if !ok { 83 tblArray, ok := have.([]map[string]interface{}) 84 if !ok { 85 return r.mismatch("array", want, have) 86 } 87 88 haveSlice = make([]interface{}, len(tblArray)) 89 for i := range tblArray { 90 haveSlice[i] = tblArray[i] 91 } 92 } 93 94 if len(want) != len(haveSlice) { 95 return r.fail("Array lengths differ for key '%s'"+ 96 " Expected: %[2]v (len=%[4]d)\n"+ 97 " Your encoder: %[3]v (len=%[5]d)", 98 r.Key, want, haveSlice, len(want), len(haveSlice)) 99 } 100 for i := 0; i < len(want); i++ { 101 if sub := r.CompareTOML(want[i], haveSlice[i]); sub.Failed() { 102 return sub 103 } 104 } 105 return r 106 } 107 108 // reflect.DeepEqual() that deals with NaN != NaN 109 func deepEqual(want, have interface{}) bool { 110 var wantF, haveF float64 111 switch f := want.(type) { 112 case float32: 113 wantF = float64(f) 114 case float64: 115 wantF = f 116 } 117 switch f := have.(type) { 118 case float32: 119 haveF = float64(f) 120 case float64: 121 haveF = f 122 } 123 if math.IsNaN(wantF) && math.IsNaN(haveF) { 124 return true 125 } 126 127 return reflect.DeepEqual(want, have) 128 } 129 130 func isTomlValue(v interface{}) bool { 131 switch v.(type) { 132 case map[string]interface{}, []interface{}: 133 return false 134 } 135 return true 136 }