1
2
3
4 package tomltest
5
6 import (
7 "strconv"
8 "strings"
9 "time"
10 )
11
12
13
14
15
16
17
18
19
20 func (r Test) CompareJSON(want, have interface{}) Test {
21 switch w := want.(type) {
22 case map[string]interface{}:
23 return r.cmpJSONMaps(w, have)
24 case []interface{}:
25 return r.cmpJSONArrays(w, have)
26 default:
27 return r.fail(
28 "Key '%s' in expected output should be a map or a list of maps, but it's a %T",
29 r.Key, want)
30 }
31 }
32
33 func (r Test) cmpJSONMaps(want map[string]interface{}, have interface{}) Test {
34 haveMap, ok := have.(map[string]interface{})
35 if !ok {
36 return r.mismatch("table", want, haveMap)
37 }
38
39
40 if isValue(want) && !isValue(haveMap) {
41 return r.fail(
42 "Key '%s' is supposed to be a value, but the parser reports it as a table",
43 r.Key)
44 }
45 if !isValue(want) && isValue(haveMap) {
46 return r.fail(
47 "Key '%s' is supposed to be a table, but the parser reports it as a value",
48 r.Key)
49 }
50 if isValue(want) && isValue(haveMap) {
51 return r.cmpJSONValues(want, haveMap)
52 }
53
54
55 for k := range want {
56 if _, ok := haveMap[k]; !ok {
57 bunk := r.kjoin(k)
58 return bunk.fail("Could not find key '%s' in parser output.",
59 bunk.Key)
60 }
61 }
62 for k := range haveMap {
63 if _, ok := want[k]; !ok {
64 bunk := r.kjoin(k)
65 return bunk.fail("Could not find key '%s' in expected output.",
66 bunk.Key)
67 }
68 }
69
70
71 for k := range want {
72 if sub := r.kjoin(k).CompareJSON(want[k], haveMap[k]); sub.Failed() {
73 return sub
74 }
75 }
76 return r
77 }
78
79 func (r Test) cmpJSONArrays(want, have interface{}) Test {
80 wantSlice, ok := want.([]interface{})
81 if !ok {
82 return r.bug("'value' should be a JSON array when 'type=array', but it is a %T", want)
83 }
84
85 haveSlice, ok := have.([]interface{})
86 if !ok {
87 return r.fail(
88 "Malformed output from your encoder: 'value' is not a JSON array: %T", have)
89 }
90
91 if len(wantSlice) != len(haveSlice) {
92 return r.fail("Array lengths differ for key '%s':\n"+
93 " Expected: %d\n"+
94 " Your encoder: %d",
95 r.Key, len(wantSlice), len(haveSlice))
96 }
97 for i := 0; i < len(wantSlice); i++ {
98 if sub := r.CompareJSON(wantSlice[i], haveSlice[i]); sub.Failed() {
99 return sub
100 }
101 }
102 return r
103 }
104
105 func (r Test) cmpJSONValues(want, have map[string]interface{}) Test {
106 wantType, ok := want["type"].(string)
107 if !ok {
108 return r.bug("'type' should be a string, but it is a %T", want["type"])
109 }
110
111 haveType, ok := have["type"].(string)
112 if !ok {
113 return r.fail("Malformed output from your encoder: 'type' is not a string: %T", have["type"])
114 }
115
116 if wantType != haveType {
117 return r.valMismatch(wantType, haveType, want, have)
118 }
119
120
121 if wantType == "array" {
122 return r.cmpJSONArrays(want, have)
123 }
124
125
126 wantVal, ok := want["value"].(string)
127 if !ok {
128 return r.bug("'value' %v should be a string, but it is a %[1]T", want["value"])
129 }
130
131 haveVal, ok := have["value"].(string)
132 if !ok {
133 return r.fail("Malformed output from your encoder: %T is not a string", have["value"])
134 }
135
136
137 switch wantType {
138 case "float":
139 return r.cmpFloats(wantVal, haveVal)
140 case "datetime", "datetime-local", "date-local", "time-local":
141 return r.cmpAsDatetimes(wantType, wantVal, haveVal)
142 default:
143 return r.cmpAsStrings(wantVal, haveVal)
144 }
145 }
146
147 func (r Test) cmpAsStrings(want, have string) Test {
148 if want != have {
149 return r.fail("Values for key '%s' don't match:\n"+
150 " Expected: %s\n"+
151 " Your encoder: %s",
152 r.Key, want, have)
153 }
154 return r
155 }
156
157 func (r Test) cmpFloats(want, have string) Test {
158
159 if strings.HasSuffix(want, "nan") || strings.HasSuffix(have, "nan") {
160 if want != have {
161 return r.fail("Values for key '%s' don't match:\n"+
162 " Expected: %v\n"+
163 " Your encoder: %v",
164 r.Key, want, have)
165 }
166 return r
167 }
168
169 wantF, err := strconv.ParseFloat(want, 64)
170 if err != nil {
171 return r.bug("Could not read '%s' as a float value for key '%s'", want, r.Key)
172 }
173
174 haveF, err := strconv.ParseFloat(have, 64)
175 if err != nil {
176 return r.fail("Malformed output from your encoder: key '%s' is not a float: '%s'", r.Key, have)
177 }
178
179 if wantF != haveF {
180 return r.fail("Values for key '%s' don't match:\n"+
181 " Expected: %v\n"+
182 " Your encoder: %v",
183 r.Key, wantF, haveF)
184 }
185 return r
186 }
187
188 var datetimeRepl = strings.NewReplacer(
189 " ", "T",
190 "t", "T",
191 "z", "Z")
192
193 var layouts = map[string]string{
194 "datetime": time.RFC3339Nano,
195 "datetime-local": "2006-01-02T15:04:05.999999999",
196 "date-local": "2006-01-02",
197 "time-local": "15:04:05",
198 }
199
200 func (r Test) cmpAsDatetimes(kind, want, have string) Test {
201 layout, ok := layouts[kind]
202 if !ok {
203 panic("should never happen")
204 }
205
206 wantT, err := time.Parse(layout, datetimeRepl.Replace(want))
207 if err != nil {
208 return r.bug("Could not read '%s' as a datetime value for key '%s'", want, r.Key)
209 }
210
211 haveT, err := time.Parse(layout, datetimeRepl.Replace(want))
212 if err != nil {
213 return r.fail("Malformed output from your encoder: key '%s' is not a datetime: '%s'", r.Key, have)
214 }
215 if !wantT.Equal(haveT) {
216 return r.fail("Values for key '%s' don't match:\n"+
217 " Expected: %v\n"+
218 " Your encoder: %v",
219 r.Key, wantT, haveT)
220 }
221 return r
222 }
223
224 func (r Test) kjoin(key string) Test {
225 if len(r.Key) == 0 {
226 r.Key = key
227 } else {
228 r.Key += "." + key
229 }
230 return r
231 }
232
233 func isValue(m map[string]interface{}) bool {
234 if len(m) != 2 {
235 return false
236 }
237 if _, ok := m["type"]; !ok {
238 return false
239 }
240 if _, ok := m["value"]; !ok {
241 return false
242 }
243 return true
244 }
245
246 func (r Test) mismatch(wantType string, want, have interface{}) Test {
247 return r.fail("Key '%s' is not an %s but %[4]T:\n"+
248 " Expected: %#[3]v\n"+
249 " Your encoder: %#[4]v",
250 r.Key, wantType, want, have)
251 }
252
253 func (r Test) valMismatch(wantType, haveType string, want, have interface{}) Test {
254 return r.fail("Key '%s' is not an %s but %s:\n"+
255 " Expected: %#[3]v\n"+
256 " Your encoder: %#[4]v",
257 r.Key, wantType, want, have)
258 }
259
View as plain text