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