提交 8f62ac82 作者: Brian Tiger Chow

vendor: fzzy/radix/redis

上级 de34261b
......@@ -109,6 +109,11 @@
"Rev": "50e7633d5f27d81490026a13e5b92d2e42d8c6bb"
},
{
"ImportPath": "github.com/fzzy/radix/redis",
"Comment": "v0.5.1",
"Rev": "27a863cdffdb0998d13e1e11992b18489aeeaa25"
},
{
"ImportPath": "github.com/gorilla/context",
"Rev": "14f550f51af52180c2eefed15e5fd18d63c0a64a"
},
......
package redis
import (
"bufio"
"errors"
"net"
"strings"
"time"
"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/fzzy/radix/redis/resp"
)
const (
bufSize int = 4096
)
//* Common errors
var LoadingError error = errors.New("server is busy loading dataset in memory")
var PipelineQueueEmptyError error = errors.New("pipeline queue empty")
//* Client
// Client describes a Redis client.
type Client struct {
// The connection the client talks to redis over. Don't touch this unless
// you know what you're doing.
Conn net.Conn
timeout time.Duration
reader *bufio.Reader
pending []*request
completed []*Reply
}
// request describes a client's request to the redis server
type request struct {
cmd string
args []interface{}
}
// Dial connects to the given Redis server with the given timeout, which will be
// used as the read/write timeout when communicating with redis
func DialTimeout(network, addr string, timeout time.Duration) (*Client, error) {
// establish a connection
conn, err := net.Dial(network, addr)
if err != nil {
return nil, err
}
c := new(Client)
c.Conn = conn
c.timeout = timeout
c.reader = bufio.NewReaderSize(conn, bufSize)
return c, nil
}
// Dial connects to the given Redis server.
func Dial(network, addr string) (*Client, error) {
return DialTimeout(network, addr, time.Duration(0))
}
//* Public methods
// Close closes the connection.
func (c *Client) Close() error {
return c.Conn.Close()
}
// Cmd calls the given Redis command.
func (c *Client) Cmd(cmd string, args ...interface{}) *Reply {
err := c.writeRequest(&request{cmd, args})
if err != nil {
return &Reply{Type: ErrorReply, Err: err}
}
return c.ReadReply()
}
// Append adds the given call to the pipeline queue.
// Use GetReply() to read the reply.
func (c *Client) Append(cmd string, args ...interface{}) {
c.pending = append(c.pending, &request{cmd, args})
}
// GetReply returns the reply for the next request in the pipeline queue.
// Error reply with PipelineQueueEmptyError is returned,
// if the pipeline queue is empty.
func (c *Client) GetReply() *Reply {
if len(c.completed) > 0 {
r := c.completed[0]
c.completed = c.completed[1:]
return r
}
c.completed = nil
if len(c.pending) == 0 {
return &Reply{Type: ErrorReply, Err: PipelineQueueEmptyError}
}
nreqs := len(c.pending)
err := c.writeRequest(c.pending...)
c.pending = nil
if err != nil {
return &Reply{Type: ErrorReply, Err: err}
}
r := c.ReadReply()
c.completed = make([]*Reply, nreqs-1)
for i := 0; i < nreqs-1; i++ {
c.completed[i] = c.ReadReply()
}
return r
}
//* Private methods
func (c *Client) setReadTimeout() {
if c.timeout != 0 {
c.Conn.SetReadDeadline(time.Now().Add(c.timeout))
}
}
func (c *Client) setWriteTimeout() {
if c.timeout != 0 {
c.Conn.SetWriteDeadline(time.Now().Add(c.timeout))
}
}
// This will read a redis reply off of the connection without sending anything
// first (useful after you've sent a SUSBSCRIBE command). This will block until
// a reply is received or the timeout is reached. On timeout an ErrorReply will
// be returned, you can check if it's a timeout like so:
//
// r := conn.ReadReply()
// if r.Err != nil {
// if t, ok := r.Err.(*net.OpError); ok && t.Timeout() {
// // Is timeout
// } else {
// // Not timeout
// }
// }
//
// Note: this is a more low-level function, you really shouldn't have to
// actually use it unless you're writing your own pub/sub code
func (c *Client) ReadReply() *Reply {
c.setReadTimeout()
return c.parse()
}
func (c *Client) writeRequest(requests ...*request) error {
c.setWriteTimeout()
for i := range requests {
req := make([]interface{}, 0, len(requests[i].args)+1)
req = append(req, requests[i].cmd)
req = append(req, requests[i].args...)
err := resp.WriteArbitraryAsFlattenedStrings(c.Conn, req)
if err != nil {
c.Close()
return err
}
}
return nil
}
func (c *Client) parse() *Reply {
m, err := resp.ReadMessage(c.reader)
if err != nil {
if t, ok := err.(*net.OpError); !ok || !t.Timeout() {
// close connection except timeout
c.Close()
}
return &Reply{Type: ErrorReply, Err: err}
}
r, err := messageToReply(m)
if err != nil {
return &Reply{Type: ErrorReply, Err: err}
}
return r
}
// The error return parameter is for bubbling up parse errors and the like, if
// the error is sent by redis itself as an Err message type, then it will be
// sent back as an actual Reply (wrapped in a CmdError)
func messageToReply(m *resp.Message) (*Reply, error) {
r := &Reply{}
switch m.Type {
case resp.Err:
errMsg, err := m.Err()
if err != nil {
return nil, err
}
if strings.HasPrefix(errMsg.Error(), "LOADING") {
err = LoadingError
} else {
err = &CmdError{errMsg}
}
r.Type = ErrorReply
r.Err = err
case resp.SimpleStr:
status, err := m.Bytes()
if err != nil {
return nil, err
}
r.Type = StatusReply
r.buf = status
case resp.Int:
i, err := m.Int()
if err != nil {
return nil, err
}
r.Type = IntegerReply
r.int = i
case resp.BulkStr:
b, err := m.Bytes()
if err != nil {
return nil, err
}
r.Type = BulkReply
r.buf = b
case resp.Nil:
r.Type = NilReply
case resp.Array:
ms, err := m.Array()
if err != nil {
return nil, err
}
r.Type = MultiReply
r.Elems = make([]*Reply, len(ms))
for i := range ms {
r.Elems[i], err = messageToReply(ms[i])
if err != nil {
return nil, err
}
}
}
return r, nil
}
package redis
import (
"bufio"
"bytes"
"github.com/stretchr/testify/assert"
. "testing"
"time"
)
func dial(t *T) *Client {
client, err := DialTimeout("tcp", "127.0.0.1:6379", 10*time.Second)
assert.Nil(t, err)
return client
}
func TestCmd(t *T) {
c := dial(t)
v, _ := c.Cmd("echo", "Hello, World!").Str()
assert.Equal(t, "Hello, World!", v)
// Test that a bad command properly returns a *CmdError
err := c.Cmd("non-existant-cmd").Err
assert.NotEqual(t, "", err.(*CmdError).Error())
// Test that application level errors propagate correctly
c.Cmd("sadd", "foo", "bar")
_, err = c.Cmd("get", "foo").Str()
assert.NotEqual(t, "", err.(*CmdError).Error())
}
func TestPipeline(t *T) {
c := dial(t)
c.Append("echo", "foo")
c.Append("echo", "bar")
c.Append("echo", "zot")
v, _ := c.GetReply().Str()
assert.Equal(t, "foo", v)
v, _ = c.GetReply().Str()
assert.Equal(t, "bar", v)
v, _ = c.GetReply().Str()
assert.Equal(t, "zot", v)
r := c.GetReply()
assert.Equal(t, ErrorReply, r.Type)
assert.Equal(t, PipelineQueueEmptyError, r.Err)
}
func TestParse(t *T) {
c := dial(t)
parseString := func(b string) *Reply {
c.reader = bufio.NewReader(bytes.NewBufferString(b))
return c.parse()
}
// missing \n trailing
r := parseString("foo")
assert.Equal(t, ErrorReply, r.Type)
assert.NotNil(t, r.Err)
// error reply
r = parseString("-ERR unknown command 'foobar'\r\n")
assert.Equal(t, ErrorReply, r.Type)
assert.Equal(t, "ERR unknown command 'foobar'", r.Err.Error())
// LOADING error
r = parseString("-LOADING Redis is loading the dataset in memory\r\n")
assert.Equal(t, ErrorReply, r.Type)
assert.Equal(t, LoadingError, r.Err)
// status reply
r = parseString("+OK\r\n")
assert.Equal(t, StatusReply, r.Type)
assert.Equal(t, []byte("OK"), r.buf)
// integer reply
r = parseString(":1337\r\n")
assert.Equal(t, IntegerReply, r.Type)
assert.Equal(t, int64(1337), r.int)
// null bulk reply
r = parseString("$-1\r\n")
assert.Equal(t, NilReply, r.Type)
// bulk reply
r = parseString("$6\r\nfoobar\r\n")
assert.Equal(t, BulkReply, r.Type)
assert.Equal(t, []byte("foobar"), r.buf)
// null multi bulk reply
r = parseString("*-1\r\n")
assert.Equal(t, NilReply, r.Type)
// multi bulk reply
r = parseString("*5\r\n:0\r\n:1\r\n:2\r\n:3\r\n$6\r\nfoobar\r\n")
assert.Equal(t, MultiReply, r.Type)
assert.Equal(t, 5, len(r.Elems))
for i := 0; i < 4; i++ {
assert.Equal(t, int64(i), r.Elems[i].int)
}
assert.Equal(t, []byte("foobar"), r.Elems[4].buf)
}
// A simple client for connecting and interacting with redis.
//
// To import inside your package do:
//
// import "github.com/fzzy/radix/redis"
//
// Connecting
//
// Use either Dial or DialTimeout:
//
// client, err := redis.Dial("tcp", "localhost:6379")
// if err != nil {
// // handle err
// }
//
// Make sure to call Close on the client if you want to clean it up before the
// end of the program.
//
// Cmd and Reply
//
// The Cmd method returns a Reply, which has methods for converting to various
// types. Each of these methods returns an error which can either be a
// connection error (e.g. timeout), an application error (e.g. key is wrong
// type), or a conversion error (e.g. cannot convert to integer). You can also
// directly check the error using the Err field:
//
// foo, err := client.Cmd("GET", "foo").Str()
// if err != nil {
// // handle err
// }
//
// // Checking Err field directly
//
// err = client.Cmd("PING").Err
// if err != nil {
// // handle err
// }
//
// Multi Replies
//
// The elements to Multi replies can be accessed as strings using List or
// ListBytes, or you can use the Elems field for more low-level access:
//
// r := client.Cmd("MGET", "foo", "bar", "baz")
//
// // This:
// for _, elemStr := range r.List() {
// fmt.Println(elemStr)
// }
//
// // is equivalent to this:
// for i := range r.Elems {
// elemStr, _ := r.Elems[i].Str()
// fmt.Println(elemStr)
// }
//
// Pipelining
//
// Pipelining is when the client sends a bunch of commands to the server at
// once, and only once all the commands have been sent does it start reading the
// replies off the socket. This is supported using the Append and GetReply
// methods. Append will simply append the command to a buffer without sending
// it, the first time GetReply is called it will send all the commands in the
// buffer and return the Reply for the first command that was sent. Subsequent
// calls to GetReply return Replys for subsequent commands:
//
// client.Append("GET", "foo")
// client.Append("SET", "bar", "foo")
// client.Append("DEL", "baz")
//
// // Read GET foo reply
// foo, err := client.GetReply().Str()
// if err != nil {
// // handle err
// }
//
// // Read SET bar foo reply
// if err := client.GetReply().Err; err != nil {
// // handle err
// }
//
// // Read DEL baz reply
// if err := client.GetReply().Err; err != nil {
// // handle err
// }
//
package redis
package redis
import (
"errors"
"strconv"
"strings"
)
// A CmdError implements the error interface and is what is returned when the
// server returns an error on the application level (e.g. key doesn't exist or
// is the wrong type), as opposed to a connection/transport error.
//
// You can test if a reply is a CmdError like so:
//
// r := conn.Cmd("GET", "key-which-isnt-a-string")
// if r.Err != nil {
// if cerr, ok := r.Err.(*redis.CmdError); ok {
// // Is CmdError
// } else {
// // Is other error
// }
// }
type CmdError struct {
Err error
}
func (cerr *CmdError) Error() string {
return cerr.Err.Error()
}
// Returns true if error returned was due to the redis server being read only
func (cerr *CmdError) Readonly() bool {
return strings.HasPrefix(cerr.Err.Error(), "READONLY")
}
//* Reply
/*
ReplyType describes type of a reply.
Possible values are:
StatusReply -- status reply
ErrorReply -- error reply
IntegerReply -- integer reply
NilReply -- nil reply
BulkReply -- bulk reply
MultiReply -- multi bulk reply
*/
type ReplyType uint8
const (
StatusReply ReplyType = iota
ErrorReply
IntegerReply
NilReply
BulkReply
MultiReply
)
// Reply holds a Redis reply.
type Reply struct {
Type ReplyType // Reply type
Elems []*Reply // Sub-replies
Err error // Reply error
buf []byte
int int64
}
// Bytes returns the reply value as a byte string or
// an error, if the reply type is not StatusReply or BulkReply.
func (r *Reply) Bytes() ([]byte, error) {
if r.Type == ErrorReply {
return nil, r.Err
}
if !(r.Type == StatusReply || r.Type == BulkReply) {
return nil, errors.New("string value is not available for this reply type")
}
return r.buf, nil
}
// Str is a convenience method for calling Reply.Bytes() and converting it to string
func (r *Reply) Str() (string, error) {
b, err := r.Bytes()
if err != nil {
return "", err
}
return string(b), nil
}
// Int64 returns the reply value as a int64 or an error,
// if the reply type is not IntegerReply or the reply type
// BulkReply could not be parsed to an int64.
func (r *Reply) Int64() (int64, error) {
if r.Type == ErrorReply {
return 0, r.Err
}
if r.Type != IntegerReply {
s, err := r.Str()
if err == nil {
i64, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return 0, errors.New("failed to parse integer value from string value")
} else {
return i64, nil
}
}
return 0, errors.New("integer value is not available for this reply type")
}
return r.int, nil
}
// Int is a convenience method for calling Reply.Int64() and converting it to int.
func (r *Reply) Int() (int, error) {
i64, err := r.Int64()
if err != nil {
return 0, err
}
return int(i64), nil
}
// Bool returns false, if the reply value equals to 0 or "0", otherwise true; or
// an error, if the reply type is not IntegerReply or BulkReply.
func (r *Reply) Bool() (bool, error) {
if r.Type == ErrorReply {
return false, r.Err
}
i, err := r.Int()
if err == nil {
if i == 0 {
return false, nil
}
return true, nil
}
s, err := r.Str()
if err == nil {
if s == "0" {
return false, nil
}
return true, nil
}
return false, errors.New("boolean value is not available for this reply type")
}
// List returns a multi bulk reply as a slice of strings or an error.
// The reply type must be MultiReply and its elements' types must all be either BulkReply or NilReply.
// Nil elements are returned as empty strings.
// Useful for list commands.
func (r *Reply) List() ([]string, error) {
// Doing all this in two places instead of just calling ListBytes() so we don't have
// to iterate twice
if r.Type == ErrorReply {
return nil, r.Err
}
if r.Type != MultiReply {
return nil, errors.New("reply type is not MultiReply")
}
strings := make([]string, len(r.Elems))
for i, v := range r.Elems {
if v.Type == BulkReply {
strings[i] = string(v.buf)
} else if v.Type == NilReply {
strings[i] = ""
} else {
return nil, errors.New("element type is not BulkReply or NilReply")
}
}
return strings, nil
}
// ListBytes returns a multi bulk reply as a slice of bytes slices or an error.
// The reply type must be MultiReply and its elements' types must all be either BulkReply or NilReply.
// Nil elements are returned as nil.
// Useful for list commands.
func (r *Reply) ListBytes() ([][]byte, error) {
if r.Type == ErrorReply {
return nil, r.Err
}
if r.Type != MultiReply {
return nil, errors.New("reply type is not MultiReply")
}
bufs := make([][]byte, len(r.Elems))
for i, v := range r.Elems {
if v.Type == BulkReply {
bufs[i] = v.buf
} else if v.Type == NilReply {
bufs[i] = nil
} else {
return nil, errors.New("element type is not BulkReply or NilReply")
}
}
return bufs, nil
}
// Hash returns a multi bulk reply as a map[string]string or an error.
// The reply type must be MultiReply,
// it must have an even number of elements,
// they must be in a "key value key value..." order and
// values must all be either BulkReply or NilReply.
// Nil values are returned as empty strings.
// Useful for hash commands.
func (r *Reply) Hash() (map[string]string, error) {
if r.Type == ErrorReply {
return nil, r.Err
}
rmap := map[string]string{}
if r.Type != MultiReply {
return nil, errors.New("reply type is not MultiReply")
}
if len(r.Elems)%2 != 0 {
return nil, errors.New("reply has odd number of elements")
}
for i := 0; i < len(r.Elems)/2; i++ {
var val string
key, err := r.Elems[i*2].Str()
if err != nil {
return nil, errors.New("key element has no string reply")
}
v := r.Elems[i*2+1]
if v.Type == BulkReply {
val = string(v.buf)
rmap[key] = val
} else if v.Type == NilReply {
} else {
return nil, errors.New("value element type is not BulkReply or NilReply")
}
}
return rmap, nil
}
// String returns a string representation of the reply and its sub-replies.
// This method is for debugging.
// Use method Reply.Str() for reading string reply.
func (r *Reply) String() string {
switch r.Type {
case ErrorReply:
return r.Err.Error()
case StatusReply:
fallthrough
case BulkReply:
return string(r.buf)
case IntegerReply:
return strconv.FormatInt(r.int, 10)
case NilReply:
return "<nil>"
case MultiReply:
s := "[ "
for _, e := range r.Elems {
s = s + e.String() + " "
}
return s + "]"
}
// This should never execute
return ""
}
package redis
import (
"github.com/stretchr/testify/assert"
. "testing"
)
func TestStr(t *T) {
r := &Reply{Type: ErrorReply, Err: LoadingError}
_, err := r.Str()
assert.Equal(t, LoadingError, err)
r = &Reply{Type: IntegerReply}
_, err = r.Str()
assert.NotNil(t, err)
r = &Reply{Type: StatusReply, buf: []byte("foo")}
b, err := r.Str()
assert.Nil(t, err)
assert.Equal(t, "foo", b)
r = &Reply{Type: BulkReply, buf: []byte("foo")}
b, err = r.Str()
assert.Nil(t, err)
assert.Equal(t, "foo", b)
}
func TestBytes(t *T) {
r := &Reply{Type: BulkReply, buf: []byte("foo")}
b, err := r.Bytes()
assert.Nil(t, err)
assert.Equal(t, []byte("foo"), b)
}
func TestInt64(t *T) {
r := &Reply{Type: ErrorReply, Err: LoadingError}
_, err := r.Int64()
assert.Equal(t, LoadingError, err)
r = &Reply{Type: IntegerReply, int: 5}
b, err := r.Int64()
assert.Nil(t, err)
assert.Equal(t, int64(5), b)
r = &Reply{Type: BulkReply, buf: []byte("5")}
b, err = r.Int64()
assert.Nil(t, err)
assert.Equal(t, int64(5), b)
r = &Reply{Type: BulkReply, buf: []byte("foo")}
_, err = r.Int64()
assert.NotNil(t, err)
}
func TestInt(t *T) {
r := &Reply{Type: IntegerReply, int: 5}
b, err := r.Int()
assert.Nil(t, err)
assert.Equal(t, 5, b)
}
func TestBool(t *T) {
r := &Reply{Type: IntegerReply, int: 0}
b, err := r.Bool()
assert.Nil(t, err)
assert.Equal(t, false, b)
r = &Reply{Type: StatusReply, buf: []byte("0")}
b, err = r.Bool()
assert.Nil(t, err)
assert.Equal(t, false, b)
r = &Reply{Type: IntegerReply, int: 2}
b, err = r.Bool()
assert.Nil(t, err)
assert.Equal(t, true, b)
r = &Reply{Type: NilReply}
_, err = r.Bool()
assert.NotNil(t, err)
}
func TestList(t *T) {
r := &Reply{Type: MultiReply}
r.Elems = make([]*Reply, 3)
r.Elems[0] = &Reply{Type: BulkReply, buf: []byte("0")}
r.Elems[1] = &Reply{Type: NilReply}
r.Elems[2] = &Reply{Type: BulkReply, buf: []byte("2")}
l, err := r.List()
assert.Nil(t, err)
assert.Equal(t, 3, len(l))
assert.Equal(t, "0", l[0])
assert.Equal(t, "", l[1])
assert.Equal(t, "2", l[2])
}
func TestBytesList(t *T) {
r := &Reply{Type: MultiReply}
r.Elems = make([]*Reply, 3)
r.Elems[0] = &Reply{Type: BulkReply, buf: []byte("0")}
r.Elems[1] = &Reply{Type: NilReply}
r.Elems[2] = &Reply{Type: BulkReply, buf: []byte("2")}
l, err := r.ListBytes()
assert.Nil(t, err)
assert.Equal(t, 3, len(l))
assert.Equal(t, []byte("0"), l[0])
assert.Nil(t, l[1])
assert.Equal(t, []byte("2"), l[2])
}
func TestHash(t *T) {
r := &Reply{Type: MultiReply}
r.Elems = make([]*Reply, 6)
r.Elems[0] = &Reply{Type: BulkReply, buf: []byte("a")}
r.Elems[1] = &Reply{Type: BulkReply, buf: []byte("0")}
r.Elems[2] = &Reply{Type: BulkReply, buf: []byte("b")}
r.Elems[3] = &Reply{Type: NilReply}
r.Elems[4] = &Reply{Type: BulkReply, buf: []byte("c")}
r.Elems[5] = &Reply{Type: BulkReply, buf: []byte("2")}
h, err := r.Hash()
assert.Nil(t, err)
assert.Equal(t, "0", h["a"])
assert.Equal(t, "", h["b"])
assert.Equal(t, "2", h["c"])
}
package resp
import (
"bytes"
"errors"
"github.com/stretchr/testify/assert"
. "testing"
)
func TestRead(t *T) {
var m *Message
var err error
_, err = NewMessage(nil)
assert.NotNil(t, err)
_, err = NewMessage([]byte{})
assert.NotNil(t, err)
// Simple string
m, _ = NewMessage([]byte("+ohey\r\n"))
assert.Equal(t, SimpleStr, m.Type)
assert.Equal(t, []byte("ohey"), m.val)
// Empty simple string
m, _ = NewMessage([]byte("+\r\n"))
assert.Equal(t, SimpleStr, m.Type)
assert.Equal(t, []byte(""), m.val.([]byte))
// Error
m, _ = NewMessage([]byte("-ohey\r\n"))
assert.Equal(t, Err, m.Type)
assert.Equal(t, []byte("ohey"), m.val.([]byte))
// Empty error
m, _ = NewMessage([]byte("-\r\n"))
assert.Equal(t, Err, m.Type)
assert.Equal(t, []byte(""), m.val.([]byte))
// Int
m, _ = NewMessage([]byte(":1024\r\n"))
assert.Equal(t, Int, m.Type)
assert.Equal(t, int64(1024), m.val.(int64))
// Bulk string
m, _ = NewMessage([]byte("$3\r\nfoo\r\n"))
assert.Equal(t, BulkStr, m.Type)
assert.Equal(t, []byte("foo"), m.val.([]byte))
// Empty bulk string
m, _ = NewMessage([]byte("$0\r\n\r\n"))
assert.Equal(t, BulkStr, m.Type)
assert.Equal(t, []byte(""), m.val.([]byte))
// Nil bulk string
m, _ = NewMessage([]byte("$-1\r\n"))
assert.Equal(t, Nil, m.Type)
// Array
m, _ = NewMessage([]byte("*2\r\n+foo\r\n+bar\r\n"))
assert.Equal(t, Array, m.Type)
assert.Equal(t, 2, len(m.val.([]*Message)))
assert.Equal(t, SimpleStr, m.val.([]*Message)[0].Type)
assert.Equal(t, []byte("foo"), m.val.([]*Message)[0].val.([]byte))
assert.Equal(t, SimpleStr, m.val.([]*Message)[1].Type)
assert.Equal(t, []byte("bar"), m.val.([]*Message)[1].val.([]byte))
// Empty array
m, _ = NewMessage([]byte("*0\r\n"))
assert.Equal(t, Array, m.Type)
assert.Equal(t, 0, len(m.val.([]*Message)))
// Nil Array
m, _ = NewMessage([]byte("*-1\r\n"))
assert.Equal(t, Nil, m.Type)
// Embedded Array
m, _ = NewMessage([]byte("*3\r\n+foo\r\n+bar\r\n*2\r\n+foo\r\n+bar\r\n"))
assert.Equal(t, Array, m.Type)
assert.Equal(t, 3, len(m.val.([]*Message)))
assert.Equal(t, SimpleStr, m.val.([]*Message)[0].Type)
assert.Equal(t, []byte("foo"), m.val.([]*Message)[0].val.([]byte))
assert.Equal(t, SimpleStr, m.val.([]*Message)[1].Type)
assert.Equal(t, []byte("bar"), m.val.([]*Message)[1].val.([]byte))
m = m.val.([]*Message)[2]
assert.Equal(t, 2, len(m.val.([]*Message)))
assert.Equal(t, SimpleStr, m.val.([]*Message)[0].Type)
assert.Equal(t, []byte("foo"), m.val.([]*Message)[0].val.([]byte))
assert.Equal(t, SimpleStr, m.val.([]*Message)[1].Type)
assert.Equal(t, []byte("bar"), m.val.([]*Message)[1].val.([]byte))
// Test that two bulks in a row read correctly
m, _ = NewMessage([]byte("*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"))
assert.Equal(t, Array, m.Type)
assert.Equal(t, 2, len(m.val.([]*Message)))
assert.Equal(t, BulkStr, m.val.([]*Message)[0].Type)
assert.Equal(t, []byte("foo"), m.val.([]*Message)[0].val.([]byte))
assert.Equal(t, BulkStr, m.val.([]*Message)[1].Type)
assert.Equal(t, []byte("bar"), m.val.([]*Message)[1].val.([]byte))
}
type arbitraryTest struct {
val interface{}
expect []byte
}
var nilMessage, _ = NewMessage([]byte("$-1\r\n"))
var arbitraryTests = []arbitraryTest{
{[]byte("OHAI"), []byte("$4\r\nOHAI\r\n")},
{"OHAI", []byte("$4\r\nOHAI\r\n")},
{true, []byte("$1\r\n1\r\n")},
{false, []byte("$1\r\n0\r\n")},
{nil, []byte("$-1\r\n")},
{80, []byte(":80\r\n")},
{int64(-80), []byte(":-80\r\n")},
{uint64(80), []byte(":80\r\n")},
{float32(0.1234), []byte("$6\r\n0.1234\r\n")},
{float64(0.1234), []byte("$6\r\n0.1234\r\n")},
{errors.New("hi"), []byte("-hi\r\n")},
{nilMessage, []byte("$-1\r\n")},
{[]int{1, 2, 3}, []byte("*3\r\n:1\r\n:2\r\n:3\r\n")},
{map[int]int{1: 2}, []byte("*2\r\n:1\r\n:2\r\n")},
{NewSimpleString("OK"), []byte("+OK\r\n")},
}
var arbitraryAsStringTests = []arbitraryTest{
{[]byte("OHAI"), []byte("$4\r\nOHAI\r\n")},
{"OHAI", []byte("$4\r\nOHAI\r\n")},
{true, []byte("$1\r\n1\r\n")},
{false, []byte("$1\r\n0\r\n")},
{nil, []byte("$0\r\n\r\n")},
{80, []byte("$2\r\n80\r\n")},
{int64(-80), []byte("$3\r\n-80\r\n")},
{uint64(80), []byte("$2\r\n80\r\n")},
{float32(0.1234), []byte("$6\r\n0.1234\r\n")},
{float64(0.1234), []byte("$6\r\n0.1234\r\n")},
{errors.New("hi"), []byte("$2\r\nhi\r\n")},
{nilMessage, []byte("$-1\r\n")},
{[]int{1, 2, 3}, []byte("*3\r\n$1\r\n1\r\n$1\r\n2\r\n$1\r\n3\r\n")},
{map[int]int{1: 2}, []byte("*2\r\n$1\r\n1\r\n$1\r\n2\r\n")},
{NewSimpleString("OK"), []byte("+OK\r\n")},
}
var arbitraryAsFlattenedStringsTests = []arbitraryTest{
{
[]interface{}{"wat", map[string]interface{}{
"foo": 1,
}},
[]byte("*3\r\n$3\r\nwat\r\n$3\r\nfoo\r\n$1\r\n1\r\n"),
},
}
func TestWriteArbitrary(t *T) {
var err error
buf := bytes.NewBuffer([]byte{})
for _, test := range arbitraryTests {
t.Logf("Checking test %v", test)
buf.Reset()
err = WriteArbitrary(buf, test.val)
assert.Nil(t, err)
assert.Equal(t, test.expect, buf.Bytes())
}
}
func TestWriteArbitraryAsString(t *T) {
var err error
buf := bytes.NewBuffer([]byte{})
for _, test := range arbitraryAsStringTests {
t.Logf("Checking test %v", test)
buf.Reset()
err = WriteArbitraryAsString(buf, test.val)
assert.Nil(t, err)
assert.Equal(t, test.expect, buf.Bytes())
}
}
func TestWriteArbitraryAsFlattenedStrings(t *T) {
var err error
buf := bytes.NewBuffer([]byte{})
for _, test := range arbitraryAsFlattenedStringsTests {
t.Logf("Checking test %v", test)
buf.Reset()
err = WriteArbitraryAsFlattenedStrings(buf, test.val)
assert.Nil(t, err)
assert.Equal(t, test.expect, buf.Bytes())
}
}
func TestMessageWrite(t *T) {
var err error
var m *Message
buf := bytes.NewBuffer([]byte{})
for _, test := range arbitraryTests {
t.Logf("Checking test; %v", test)
buf.Reset()
m, err = NewMessage(test.expect)
assert.Nil(t, err)
err = WriteMessage(buf, m)
assert.Nil(t, err)
assert.Equal(t, test.expect, buf.Bytes())
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论