1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package redis_test
16
17 import (
18 "fmt"
19 "math"
20 "reflect"
21 "testing"
22 "time"
23
24 "github.com/gomodule/redigo/redis"
25 )
26
27 type durationScan struct {
28 time.Duration `redis:"sd"`
29 }
30
31 func (t *durationScan) RedisScan(src interface{}) (err error) {
32 if t == nil {
33 return fmt.Errorf("nil pointer")
34 }
35 switch src := src.(type) {
36 case string:
37 t.Duration, err = time.ParseDuration(src)
38 case []byte:
39 t.Duration, err = time.ParseDuration(string(src))
40 case int64:
41 t.Duration = time.Duration(src)
42 default:
43 err = fmt.Errorf("cannot convert from %T to %T", src, t)
44 }
45 return err
46 }
47
48 var scanConversionTests = []struct {
49 src interface{}
50 dest interface{}
51 }{
52 {[]byte("-inf"), math.Inf(-1)},
53 {[]byte("+inf"), math.Inf(1)},
54 {[]byte("0"), float64(0)},
55 {[]byte("3.14159"), float64(3.14159)},
56 {[]byte("3.14"), float32(3.14)},
57 {[]byte("-100"), int(-100)},
58 {[]byte("101"), int(101)},
59 {int64(102), int(102)},
60 {[]byte("103"), uint(103)},
61 {int64(104), uint(104)},
62 {[]byte("105"), int8(105)},
63 {int64(106), int8(106)},
64 {[]byte("107"), uint8(107)},
65 {int64(108), uint8(108)},
66 {[]byte("0"), false},
67 {int64(0), false},
68 {[]byte("f"), false},
69 {[]byte("1"), true},
70 {int64(1), true},
71 {[]byte("t"), true},
72 {"hello", "hello"},
73 {[]byte("hello"), "hello"},
74 {[]byte("world"), []byte("world")},
75 {[]interface{}{[]byte("foo")}, []interface{}{[]byte("foo")}},
76 {[]interface{}{[]byte("foo")}, []string{"foo"}},
77 {[]interface{}{[]byte("hello"), []byte("world")}, []string{"hello", "world"}},
78 {[]interface{}{[]byte("bar")}, [][]byte{[]byte("bar")}},
79 {[]interface{}{[]byte("1")}, []int{1}},
80 {[]interface{}{[]byte("1"), []byte("2")}, []int{1, 2}},
81 {[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}},
82 {[]interface{}{[]byte("1")}, []byte{1}},
83 {[]interface{}{[]byte("1")}, []bool{true}},
84 {"1m", durationScan{Duration: time.Minute}},
85 {[]byte("1m"), durationScan{Duration: time.Minute}},
86 {time.Minute.Nanoseconds(), durationScan{Duration: time.Minute}},
87 {[]interface{}{[]byte("1m")}, []durationScan{{Duration: time.Minute}}},
88 {[]interface{}{[]byte("1m")}, []*durationScan{{Duration: time.Minute}}},
89 }
90
91 func TestScanConversion(t *testing.T) {
92 for _, tt := range scanConversionTests {
93 values := []interface{}{tt.src}
94 dest := reflect.New(reflect.TypeOf(tt.dest))
95 values, err := redis.Scan(values, dest.Interface())
96 if err != nil {
97 t.Errorf("Scan(%v) returned error %v", tt, err)
98 continue
99 }
100 if !reflect.DeepEqual(tt.dest, dest.Elem().Interface()) {
101 t.Errorf("Scan(%v) returned %v, want %v", tt, dest.Elem().Interface(), tt.dest)
102 }
103 }
104 }
105
106 var scanConversionErrorTests = []struct {
107 src interface{}
108 dest interface{}
109 }{
110 {[]byte("1234"), byte(0)},
111 {int64(1234), byte(0)},
112 {[]byte("-1"), byte(0)},
113 {int64(-1), byte(0)},
114 {[]byte("junk"), false},
115 {redis.Error("blah"), false},
116 {redis.Error("blah"), durationScan{Duration: time.Minute}},
117 {"invalid", durationScan{Duration: time.Minute}},
118 }
119
120 func TestScanConversionError(t *testing.T) {
121 for _, tt := range scanConversionErrorTests {
122 values := []interface{}{tt.src}
123 dest := reflect.New(reflect.TypeOf(tt.dest))
124 values, err := redis.Scan(values, dest.Interface())
125 if err == nil {
126 t.Errorf("Scan(%v) did not return error", tt)
127 }
128 }
129 }
130
131 func ExampleScan() {
132 c, err := dial()
133 if err != nil {
134 fmt.Println(err)
135 return
136 }
137 defer c.Close()
138
139 c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
140 c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
141 c.Send("HMSET", "album:3", "title", "Beat")
142 c.Send("LPUSH", "albums", "1")
143 c.Send("LPUSH", "albums", "2")
144 c.Send("LPUSH", "albums", "3")
145 values, err := redis.Values(c.Do("SORT", "albums",
146 "BY", "album:*->rating",
147 "GET", "album:*->title",
148 "GET", "album:*->rating"))
149 if err != nil {
150 fmt.Println(err)
151 return
152 }
153
154 for len(values) > 0 {
155 var title string
156 rating := -1
157 values, err = redis.Scan(values, &title, &rating)
158 if err != nil {
159 fmt.Println(err)
160 return
161 }
162 if rating == -1 {
163 fmt.Println(title, "not-rated")
164 } else {
165 fmt.Println(title, rating)
166 }
167 }
168
169
170
171
172 }
173
174 type s0 struct {
175 X int
176 Y int `redis:"y"`
177 Bt bool
178 }
179
180 type s1 struct {
181 X int `redis:"-"`
182 I int `redis:"i"`
183 U uint `redis:"u"`
184 S string `redis:"s"`
185 P []byte `redis:"p"`
186 B bool `redis:"b"`
187 Bt bool
188 Bf bool
189 s0
190 Sd durationScan `redis:"sd"`
191 Sdp *durationScan `redis:"sdp"`
192 }
193
194 var scanStructTests = []struct {
195 title string
196 reply []string
197 value interface{}
198 }{
199 {"basic",
200 []string{
201 "i", "-1234",
202 "u", "5678",
203 "s", "hello",
204 "p", "world",
205 "b", "t",
206 "Bt", "1",
207 "Bf", "0",
208 "X", "123",
209 "y", "456",
210 "sd", "1m",
211 "sdp", "1m",
212 },
213 &s1{
214 I: -1234,
215 U: 5678,
216 S: "hello",
217 P: []byte("world"),
218 B: true,
219 Bt: true,
220 Bf: false,
221 s0: s0{X: 123, Y: 456},
222 Sd: durationScan{Duration: time.Minute},
223 Sdp: &durationScan{Duration: time.Minute},
224 },
225 },
226 }
227
228 func TestScanStruct(t *testing.T) {
229 for _, tt := range scanStructTests {
230
231 var reply []interface{}
232 for _, v := range tt.reply {
233 reply = append(reply, []byte(v))
234 }
235
236 value := reflect.New(reflect.ValueOf(tt.value).Type().Elem())
237
238 if err := redis.ScanStruct(reply, value.Interface()); err != nil {
239 t.Fatalf("ScanStruct(%s) returned error %v", tt.title, err)
240 }
241
242 if !reflect.DeepEqual(value.Interface(), tt.value) {
243 t.Fatalf("ScanStruct(%s) returned %v, want %v", tt.title, value.Interface(), tt.value)
244 }
245 }
246 }
247
248 func TestBadScanStructArgs(t *testing.T) {
249 x := []interface{}{"A", "b"}
250 test := func(v interface{}) {
251 if err := redis.ScanStruct(x, v); err == nil {
252 t.Errorf("Expect error for ScanStruct(%T, %T)", x, v)
253 }
254 }
255
256 test(nil)
257
258 var v0 *struct{}
259 test(v0)
260
261 var v1 int
262 test(&v1)
263
264 x = x[:1]
265 v2 := struct{ A string }{}
266 test(&v2)
267 }
268
269 var scanSliceTests = []struct {
270 src []interface{}
271 fieldNames []string
272 ok bool
273 dest interface{}
274 }{
275 {
276 []interface{}{[]byte("1"), nil, []byte("-1")},
277 nil,
278 true,
279 []int{1, 0, -1},
280 },
281 {
282 []interface{}{[]byte("1"), nil, []byte("2")},
283 nil,
284 true,
285 []uint{1, 0, 2},
286 },
287 {
288 []interface{}{[]byte("-1")},
289 nil,
290 false,
291 []uint{1},
292 },
293 {
294 []interface{}{[]byte("hello"), nil, []byte("world")},
295 nil,
296 true,
297 [][]byte{[]byte("hello"), nil, []byte("world")},
298 },
299 {
300 []interface{}{[]byte("hello"), nil, []byte("world")},
301 nil,
302 true,
303 []string{"hello", "", "world"},
304 },
305 {
306 []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
307 nil,
308 true,
309 []struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
310 },
311 {
312 []interface{}{[]byte("a1"), []byte("b1")},
313 nil,
314 false,
315 []struct{ A, B, C string }{{"a1", "b1", ""}},
316 },
317 {
318 []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
319 nil,
320 true,
321 []*struct{ A, B string }{{A: "a1", B: "b1"}, {A: "a2", B: "b2"}},
322 },
323 {
324 []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
325 []string{"A", "B"},
326 true,
327 []struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}},
328 },
329 {
330 []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
331 nil,
332 false,
333 []struct{}{},
334 },
335 }
336
337 func TestScanSlice(t *testing.T) {
338 for _, tt := range scanSliceTests {
339
340 typ := reflect.ValueOf(tt.dest).Type()
341 dest := reflect.New(typ)
342
343 err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...)
344 if tt.ok != (err == nil) {
345 t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err)
346 continue
347 }
348 if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) {
349 t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest)
350 }
351 }
352 }
353
354 func ExampleScanSlice() {
355 c, err := dial()
356 if err != nil {
357 fmt.Println(err)
358 return
359 }
360 defer c.Close()
361
362 c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
363 c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
364 c.Send("HMSET", "album:3", "title", "Beat", "rating", 4)
365 c.Send("LPUSH", "albums", "1")
366 c.Send("LPUSH", "albums", "2")
367 c.Send("LPUSH", "albums", "3")
368 values, err := redis.Values(c.Do("SORT", "albums",
369 "BY", "album:*->rating",
370 "GET", "album:*->title",
371 "GET", "album:*->rating"))
372 if err != nil {
373 fmt.Println(err)
374 return
375 }
376
377 var albums []struct {
378 Title string
379 Rating int
380 }
381 if err := redis.ScanSlice(values, &albums); err != nil {
382 fmt.Println(err)
383 return
384 }
385 fmt.Printf("%v\n", albums)
386
387
388 }
389
390 var argsTests = []struct {
391 title string
392 actual redis.Args
393 expected redis.Args
394 }{
395 {"struct ptr",
396 redis.Args{}.AddFlat(&struct {
397 I int `redis:"i"`
398 U uint `redis:"u"`
399 S string `redis:"s"`
400 P []byte `redis:"p"`
401 M map[string]string `redis:"m"`
402 Bt bool
403 Bf bool
404 }{
405 -1234, 5678, "hello", []byte("world"), map[string]string{"hello": "world"}, true, false,
406 }),
407 redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "m", map[string]string{"hello": "world"}, "Bt", true, "Bf", false},
408 },
409 {"struct",
410 redis.Args{}.AddFlat(struct{ I int }{123}),
411 redis.Args{"I", 123},
412 },
413 {"slice",
414 redis.Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2),
415 redis.Args{1, "a", "b", "c", 2},
416 },
417 {"struct omitempty",
418 redis.Args{}.AddFlat(&struct {
419 I int `redis:"i,omitempty"`
420 U uint `redis:"u,omitempty"`
421 S string `redis:"s,omitempty"`
422 P []byte `redis:"p,omitempty"`
423 M map[string]string `redis:"m,omitempty"`
424 Bt bool `redis:"Bt,omitempty"`
425 Bf bool `redis:"Bf,omitempty"`
426 }{
427 0, 0, "", []byte{}, map[string]string{}, true, false,
428 }),
429 redis.Args{"Bt", true},
430 },
431 }
432
433 func TestArgs(t *testing.T) {
434 for _, tt := range argsTests {
435 if !reflect.DeepEqual(tt.actual, tt.expected) {
436 t.Fatalf("%s is %v, want %v", tt.title, tt.actual, tt.expected)
437 }
438 }
439 }
440
441 func ExampleArgs() {
442 c, err := dial()
443 if err != nil {
444 fmt.Println(err)
445 return
446 }
447 defer c.Close()
448
449 var p1, p2 struct {
450 Title string `redis:"title"`
451 Author string `redis:"author"`
452 Body string `redis:"body"`
453 }
454
455 p1.Title = "Example"
456 p1.Author = "Gary"
457 p1.Body = "Hello"
458
459 if _, err := c.Do("HMSET", redis.Args{}.Add("id1").AddFlat(&p1)...); err != nil {
460 fmt.Println(err)
461 return
462 }
463
464 m := map[string]string{
465 "title": "Example2",
466 "author": "Steve",
467 "body": "Map",
468 }
469
470 if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil {
471 fmt.Println(err)
472 return
473 }
474
475 for _, id := range []string{"id1", "id2"} {
476
477 v, err := redis.Values(c.Do("HGETALL", id))
478 if err != nil {
479 fmt.Println(err)
480 return
481 }
482
483 if err := redis.ScanStruct(v, &p2); err != nil {
484 fmt.Println(err)
485 return
486 }
487
488 fmt.Printf("%+v\n", p2)
489 }
490
491
492
493
494 }
495
View as plain text