439 lines
9.9 KiB
Go
439 lines
9.9 KiB
Go
// Copyright 2012 Gary Burd
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
// not use this file except in compliance with the License. You may obtain
|
|
// a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
// License for the specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package redis
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
var scanConversionTests = []struct {
|
|
src interface{}
|
|
dest interface{}
|
|
}{
|
|
{[]byte("-inf"), math.Inf(-1)},
|
|
{[]byte("+inf"), math.Inf(1)},
|
|
{[]byte("0"), float64(0)},
|
|
{[]byte("3.14159"), float64(3.14159)},
|
|
{[]byte("3.14"), float32(3.14)},
|
|
{[]byte("-100"), int(-100)},
|
|
{[]byte("101"), int(101)},
|
|
{int64(102), int(102)},
|
|
{[]byte("103"), uint(103)},
|
|
{int64(104), uint(104)},
|
|
{[]byte("105"), int8(105)},
|
|
{int64(106), int8(106)},
|
|
{[]byte("107"), uint8(107)},
|
|
{int64(108), uint8(108)},
|
|
{[]byte("0"), false},
|
|
{int64(0), false},
|
|
{[]byte("f"), false},
|
|
{[]byte("1"), true},
|
|
{int64(1), true},
|
|
{[]byte("t"), true},
|
|
{"hello", "hello"},
|
|
{[]byte("hello"), "hello"},
|
|
{[]byte("world"), []byte("world")},
|
|
{[]interface{}{[]byte("foo")}, []interface{}{[]byte("foo")}},
|
|
{[]interface{}{[]byte("foo")}, []string{"foo"}},
|
|
{[]interface{}{[]byte("hello"), []byte("world")}, []string{"hello", "world"}},
|
|
{[]interface{}{[]byte("bar")}, [][]byte{[]byte("bar")}},
|
|
{[]interface{}{[]byte("1")}, []int{1}},
|
|
{[]interface{}{[]byte("1"), []byte("2")}, []int{1, 2}},
|
|
{[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}},
|
|
{[]interface{}{[]byte("1")}, []byte{1}},
|
|
{[]interface{}{[]byte("1")}, []bool{true}},
|
|
}
|
|
|
|
func TestScanConversion(t *testing.T) {
|
|
for _, tt := range scanConversionTests {
|
|
values := []interface{}{tt.src}
|
|
dest := reflect.New(reflect.TypeOf(tt.dest))
|
|
values, err := Scan(values, dest.Interface())
|
|
if err != nil {
|
|
t.Errorf("Scan(%v) returned error %v", tt, err)
|
|
continue
|
|
}
|
|
if !reflect.DeepEqual(tt.dest, dest.Elem().Interface()) {
|
|
t.Errorf("Scan(%v) returned %v, want %v", tt, dest.Elem().Interface(), tt.dest)
|
|
}
|
|
}
|
|
}
|
|
|
|
var scanConversionErrorTests = []struct {
|
|
src interface{}
|
|
dest interface{}
|
|
}{
|
|
{[]byte("1234"), byte(0)},
|
|
{int64(1234), byte(0)},
|
|
{[]byte("-1"), byte(0)},
|
|
{int64(-1), byte(0)},
|
|
{[]byte("junk"), false},
|
|
{Error("blah"), false},
|
|
}
|
|
|
|
func TestScanConversionError(t *testing.T) {
|
|
for _, tt := range scanConversionErrorTests {
|
|
values := []interface{}{tt.src}
|
|
dest := reflect.New(reflect.TypeOf(tt.dest))
|
|
values, err := Scan(values, dest.Interface())
|
|
if err == nil {
|
|
t.Errorf("Scan(%v) did not return error", tt)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ExampleScan() {
|
|
c, err := dial()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
defer c.Close()
|
|
|
|
c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
|
|
c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
|
|
c.Send("HMSET", "album:3", "title", "Beat")
|
|
c.Send("LPUSH", "albums", "1")
|
|
c.Send("LPUSH", "albums", "2")
|
|
c.Send("LPUSH", "albums", "3")
|
|
values, err := Values(c.Do("SORT", "albums",
|
|
"BY", "album:*->rating",
|
|
"GET", "album:*->title",
|
|
"GET", "album:*->rating"))
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
for len(values) > 0 {
|
|
var title string
|
|
rating := -1 // initialize to illegal value to detect nil.
|
|
values, err = Scan(values, &title, &rating)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
if rating == -1 {
|
|
fmt.Println(title, "not-rated")
|
|
} else {
|
|
fmt.Println(title, rating)
|
|
}
|
|
}
|
|
// Output:
|
|
// Beat not-rated
|
|
// Earthbound 1
|
|
// Red 5
|
|
}
|
|
|
|
type s0 struct {
|
|
X int
|
|
Y int `redis:"y"`
|
|
Bt bool
|
|
}
|
|
|
|
type s1 struct {
|
|
X int `redis:"-"`
|
|
I int `redis:"i"`
|
|
U uint `redis:"u"`
|
|
S string `redis:"s"`
|
|
P []byte `redis:"p"`
|
|
B bool `redis:"b"`
|
|
Bt bool
|
|
Bf bool
|
|
s0
|
|
}
|
|
|
|
var scanStructTests = []struct {
|
|
title string
|
|
reply []string
|
|
value interface{}
|
|
}{
|
|
{"basic",
|
|
[]string{"i", "-1234", "u", "5678", "s", "hello", "p", "world", "b", "t", "Bt", "1", "Bf", "0", "X", "123", "y", "456"},
|
|
&s1{I: -1234, U: 5678, S: "hello", P: []byte("world"), B: true, Bt: true, Bf: false, s0: s0{X: 123, Y: 456}},
|
|
},
|
|
}
|
|
|
|
func TestScanStruct(t *testing.T) {
|
|
for _, tt := range scanStructTests {
|
|
|
|
var reply []interface{}
|
|
for _, v := range tt.reply {
|
|
reply = append(reply, []byte(v))
|
|
}
|
|
|
|
value := reflect.New(reflect.ValueOf(tt.value).Type().Elem())
|
|
|
|
if err := ScanStruct(reply, value.Interface()); err != nil {
|
|
t.Fatalf("ScanStruct(%s) returned error %v", tt.title, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(value.Interface(), tt.value) {
|
|
t.Fatalf("ScanStruct(%s) returned %v, want %v", tt.title, value.Interface(), tt.value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBadScanStructArgs(t *testing.T) {
|
|
x := []interface{}{"A", "b"}
|
|
test := func(v interface{}) {
|
|
if err := ScanStruct(x, v); err == nil {
|
|
t.Errorf("Expect error for ScanStruct(%T, %T)", x, v)
|
|
}
|
|
}
|
|
|
|
test(nil)
|
|
|
|
var v0 *struct{}
|
|
test(v0)
|
|
|
|
var v1 int
|
|
test(&v1)
|
|
|
|
x = x[:1]
|
|
v2 := struct{ A string }{}
|
|
test(&v2)
|
|
}
|
|
|
|
var scanSliceTests = []struct {
|
|
src []interface{}
|
|
fieldNames []string
|
|
ok bool
|
|
dest interface{}
|
|
}{
|
|
{
|
|
[]interface{}{[]byte("1"), nil, []byte("-1")},
|
|
nil,
|
|
true,
|
|
[]int{1, 0, -1},
|
|
},
|
|
{
|
|
[]interface{}{[]byte("1"), nil, []byte("2")},
|
|
nil,
|
|
true,
|
|
[]uint{1, 0, 2},
|
|
},
|
|
{
|
|
[]interface{}{[]byte("-1")},
|
|
nil,
|
|
false,
|
|
[]uint{1},
|
|
},
|
|
{
|
|
[]interface{}{[]byte("hello"), nil, []byte("world")},
|
|
nil,
|
|
true,
|
|
[][]byte{[]byte("hello"), nil, []byte("world")},
|
|
},
|
|
{
|
|
[]interface{}{[]byte("hello"), nil, []byte("world")},
|
|
nil,
|
|
true,
|
|
[]string{"hello", "", "world"},
|
|
},
|
|
{
|
|
[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
|
|
nil,
|
|
true,
|
|
[]struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
|
|
},
|
|
{
|
|
[]interface{}{[]byte("a1"), []byte("b1")},
|
|
nil,
|
|
false,
|
|
[]struct{ A, B, C string }{{"a1", "b1", ""}},
|
|
},
|
|
{
|
|
[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
|
|
nil,
|
|
true,
|
|
[]*struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
|
|
},
|
|
{
|
|
[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
|
|
[]string{"A", "B"},
|
|
true,
|
|
[]struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}},
|
|
},
|
|
{
|
|
[]interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
|
|
nil,
|
|
false,
|
|
[]struct{}{},
|
|
},
|
|
}
|
|
|
|
func TestScanSlice(t *testing.T) {
|
|
for _, tt := range scanSliceTests {
|
|
|
|
typ := reflect.ValueOf(tt.dest).Type()
|
|
dest := reflect.New(typ)
|
|
|
|
err := ScanSlice(tt.src, dest.Interface(), tt.fieldNames...)
|
|
if tt.ok != (err == nil) {
|
|
t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err)
|
|
continue
|
|
}
|
|
if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) {
|
|
t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ExampleScanSlice() {
|
|
c, err := dial()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
defer c.Close()
|
|
|
|
c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
|
|
c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
|
|
c.Send("HMSET", "album:3", "title", "Beat", "rating", 4)
|
|
c.Send("LPUSH", "albums", "1")
|
|
c.Send("LPUSH", "albums", "2")
|
|
c.Send("LPUSH", "albums", "3")
|
|
values, err := Values(c.Do("SORT", "albums",
|
|
"BY", "album:*->rating",
|
|
"GET", "album:*->title",
|
|
"GET", "album:*->rating"))
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
var albums []struct {
|
|
Title string
|
|
Rating int
|
|
}
|
|
if err := ScanSlice(values, &albums); err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
fmt.Printf("%v\n", albums)
|
|
// Output:
|
|
// [{Earthbound 1} {Beat 4} {Red 5}]
|
|
}
|
|
|
|
var argsTests = []struct {
|
|
title string
|
|
actual Args
|
|
expected Args
|
|
}{
|
|
{"struct ptr",
|
|
Args{}.AddFlat(&struct {
|
|
I int `redis:"i"`
|
|
U uint `redis:"u"`
|
|
S string `redis:"s"`
|
|
P []byte `redis:"p"`
|
|
M map[string]string `redis:"m"`
|
|
Bt bool
|
|
Bf bool
|
|
}{
|
|
-1234, 5678, "hello", []byte("world"), map[string]string{"hello": "world"}, true, false,
|
|
}),
|
|
Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "m", map[string]string{"hello": "world"}, "Bt", true, "Bf", false},
|
|
},
|
|
{"struct",
|
|
Args{}.AddFlat(struct{ I int }{123}),
|
|
Args{"I", 123},
|
|
},
|
|
{"slice",
|
|
Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2),
|
|
Args{1, "a", "b", "c", 2},
|
|
},
|
|
{"struct omitempty",
|
|
Args{}.AddFlat(&struct {
|
|
I int `redis:"i,omitempty"`
|
|
U uint `redis:"u,omitempty"`
|
|
S string `redis:"s,omitempty"`
|
|
P []byte `redis:"p,omitempty"`
|
|
M map[string]string `redis:"m,omitempty"`
|
|
Bt bool `redis:"Bt,omitempty"`
|
|
Bf bool `redis:"Bf,omitempty"`
|
|
}{
|
|
0, 0, "", []byte{}, map[string]string{}, true, false,
|
|
}),
|
|
Args{"Bt", true},
|
|
},
|
|
}
|
|
|
|
func TestArgs(t *testing.T) {
|
|
for _, tt := range argsTests {
|
|
if !reflect.DeepEqual(tt.actual, tt.expected) {
|
|
t.Fatalf("%s is %v, want %v", tt.title, tt.actual, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ExampleArgs() {
|
|
c, err := dial()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
defer c.Close()
|
|
|
|
var p1, p2 struct {
|
|
Title string `redis:"title"`
|
|
Author string `redis:"author"`
|
|
Body string `redis:"body"`
|
|
}
|
|
|
|
p1.Title = "Example"
|
|
p1.Author = "Gary"
|
|
p1.Body = "Hello"
|
|
|
|
if _, err := c.Do("HMSET", Args{}.Add("id1").AddFlat(&p1)...); err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
m := map[string]string{
|
|
"title": "Example2",
|
|
"author": "Steve",
|
|
"body": "Map",
|
|
}
|
|
|
|
if _, err := c.Do("HMSET", Args{}.Add("id2").AddFlat(m)...); err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
for _, id := range []string{"id1", "id2"} {
|
|
|
|
v, err := Values(c.Do("HGETALL", id))
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
if err := ScanStruct(v, &p2); err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("%+v\n", p2)
|
|
}
|
|
|
|
// Output:
|
|
// {Title:Example Author:Gary Body:Hello}
|
|
// {Title:Example2 Author:Steve Body:Map}
|
|
}
|