forked from BurntSushi/toml
/
toml.go
136 lines (122 loc) · 3.22 KB
/
toml.go
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
}