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