commit
3f2c51b56e
@ -1,25 +0,0 @@ |
||||
# cache/memcache |
||||
|
||||
##### 项目简介 |
||||
1. 提供protobuf,gob,json序列化方式,gzip的memcache接口 |
||||
|
||||
#### 使用方式 |
||||
```golang |
||||
// 初始化 注意这里只是示例 展示用法 不能每次都New 只需要初始化一次 |
||||
mc := memcache.New(&memcache.Config{}) |
||||
// 程序关闭的时候调用close方法 |
||||
defer mc.Close() |
||||
// 增加 key |
||||
err = mc.Set(c, &memcache.Item{}) |
||||
// 删除key |
||||
err := mc.Delete(c,key) |
||||
// 获得某个key的内容 |
||||
err := mc.Get(c,key).Scan(&v) |
||||
// 获取多个key的内容 |
||||
replies, err := mc.GetMulti(c, keys) |
||||
for _, key := range replies.Keys() { |
||||
if err = replies.Scan(key, &v); err != nil { |
||||
return |
||||
} |
||||
} |
||||
``` |
@ -0,0 +1,261 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"context" |
||||
"fmt" |
||||
"io" |
||||
"net" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
|
||||
pkgerr "github.com/pkg/errors" |
||||
) |
||||
|
||||
var ( |
||||
crlf = []byte("\r\n") |
||||
space = []byte(" ") |
||||
replyOK = []byte("OK\r\n") |
||||
replyStored = []byte("STORED\r\n") |
||||
replyNotStored = []byte("NOT_STORED\r\n") |
||||
replyExists = []byte("EXISTS\r\n") |
||||
replyNotFound = []byte("NOT_FOUND\r\n") |
||||
replyDeleted = []byte("DELETED\r\n") |
||||
replyEnd = []byte("END\r\n") |
||||
replyTouched = []byte("TOUCHED\r\n") |
||||
replyClientErrorPrefix = []byte("CLIENT_ERROR ") |
||||
replyServerErrorPrefix = []byte("SERVER_ERROR ") |
||||
) |
||||
|
||||
var _ protocolConn = &asiiConn{} |
||||
|
||||
// asiiConn is the low-level implementation of Conn
|
||||
type asiiConn struct { |
||||
err error |
||||
conn net.Conn |
||||
// Read & Write
|
||||
readTimeout time.Duration |
||||
writeTimeout time.Duration |
||||
rw *bufio.ReadWriter |
||||
} |
||||
|
||||
func replyToError(line []byte) error { |
||||
switch { |
||||
case bytes.Equal(line, replyStored): |
||||
return nil |
||||
case bytes.Equal(line, replyOK): |
||||
return nil |
||||
case bytes.Equal(line, replyDeleted): |
||||
return nil |
||||
case bytes.Equal(line, replyTouched): |
||||
return nil |
||||
case bytes.Equal(line, replyNotStored): |
||||
return ErrNotStored |
||||
case bytes.Equal(line, replyExists): |
||||
return ErrCASConflict |
||||
case bytes.Equal(line, replyNotFound): |
||||
return ErrNotFound |
||||
case bytes.Equal(line, replyNotStored): |
||||
return ErrNotStored |
||||
case bytes.Equal(line, replyExists): |
||||
return ErrCASConflict |
||||
} |
||||
return pkgerr.WithStack(protocolError(string(line))) |
||||
} |
||||
|
||||
func (c *asiiConn) Populate(ctx context.Context, cmd string, key string, flags uint32, expiration int32, cas uint64, data []byte) error { |
||||
c.conn.SetWriteDeadline(shrinkDeadline(ctx, c.writeTimeout)) |
||||
// <command name> <key> <flags> <exptime> <bytes> [noreply]\r\n
|
||||
var err error |
||||
if cmd == "cas" { |
||||
_, err = fmt.Fprintf(c.rw, "%s %s %d %d %d %d\r\n", cmd, key, flags, expiration, len(data), cas) |
||||
} else { |
||||
_, err = fmt.Fprintf(c.rw, "%s %s %d %d %d\r\n", cmd, key, flags, expiration, len(data)) |
||||
} |
||||
if err != nil { |
||||
return c.fatal(err) |
||||
} |
||||
c.rw.Write(data) |
||||
c.rw.Write(crlf) |
||||
if err = c.rw.Flush(); err != nil { |
||||
return c.fatal(err) |
||||
} |
||||
c.conn.SetReadDeadline(shrinkDeadline(ctx, c.readTimeout)) |
||||
line, err := c.rw.ReadSlice('\n') |
||||
if err != nil { |
||||
return c.fatal(err) |
||||
} |
||||
return replyToError(line) |
||||
} |
||||
|
||||
// newConn returns a new memcache connection for the given net connection.
|
||||
func newASCIIConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) (protocolConn, error) { |
||||
if writeTimeout <= 0 || readTimeout <= 0 { |
||||
return nil, pkgerr.Errorf("readTimeout writeTimeout can't be zero") |
||||
} |
||||
c := &asiiConn{ |
||||
conn: netConn, |
||||
rw: bufio.NewReadWriter(bufio.NewReader(netConn), |
||||
bufio.NewWriter(netConn)), |
||||
readTimeout: readTimeout, |
||||
writeTimeout: writeTimeout, |
||||
} |
||||
return c, nil |
||||
} |
||||
|
||||
func (c *asiiConn) Close() error { |
||||
if c.err == nil { |
||||
c.err = pkgerr.New("memcache: closed") |
||||
} |
||||
return c.conn.Close() |
||||
} |
||||
|
||||
func (c *asiiConn) fatal(err error) error { |
||||
if c.err == nil { |
||||
c.err = pkgerr.WithStack(err) |
||||
// Close connection to force errors on subsequent calls and to unblock
|
||||
// other reader or writer.
|
||||
c.conn.Close() |
||||
} |
||||
return c.err |
||||
} |
||||
|
||||
func (c *asiiConn) Err() error { |
||||
return c.err |
||||
} |
||||
|
||||
func (c *asiiConn) Get(ctx context.Context, key string) (result *Item, err error) { |
||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) |
||||
if _, err = fmt.Fprintf(c.rw, "gets %s\r\n", key); err != nil { |
||||
return nil, c.fatal(err) |
||||
} |
||||
if err = c.rw.Flush(); err != nil { |
||||
return nil, c.fatal(err) |
||||
} |
||||
if err = c.parseGetReply(func(it *Item) { |
||||
result = it |
||||
}); err != nil { |
||||
return |
||||
} |
||||
if result == nil { |
||||
return nil, ErrNotFound |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (c *asiiConn) GetMulti(ctx context.Context, keys ...string) (map[string]*Item, error) { |
||||
var err error |
||||
c.conn.SetWriteDeadline(shrinkDeadline(ctx, c.writeTimeout)) |
||||
if _, err = fmt.Fprintf(c.rw, "gets %s\r\n", strings.Join(keys, " ")); err != nil { |
||||
return nil, c.fatal(err) |
||||
} |
||||
if err = c.rw.Flush(); err != nil { |
||||
return nil, c.fatal(err) |
||||
} |
||||
results := make(map[string]*Item, len(keys)) |
||||
if err = c.parseGetReply(func(it *Item) { |
||||
results[it.Key] = it |
||||
}); err != nil { |
||||
return nil, err |
||||
} |
||||
return results, nil |
||||
} |
||||
|
||||
func (c *asiiConn) parseGetReply(f func(*Item)) error { |
||||
c.conn.SetReadDeadline(shrinkDeadline(context.TODO(), c.readTimeout)) |
||||
for { |
||||
line, err := c.rw.ReadSlice('\n') |
||||
if err != nil { |
||||
return c.fatal(err) |
||||
} |
||||
if bytes.Equal(line, replyEnd) { |
||||
return nil |
||||
} |
||||
if bytes.HasPrefix(line, replyServerErrorPrefix) { |
||||
errMsg := line[len(replyServerErrorPrefix):] |
||||
return c.fatal(protocolError(errMsg)) |
||||
} |
||||
it := new(Item) |
||||
size, err := scanGetReply(line, it) |
||||
if err != nil { |
||||
return c.fatal(err) |
||||
} |
||||
it.Value = make([]byte, size+2) |
||||
if _, err = io.ReadFull(c.rw, it.Value); err != nil { |
||||
return c.fatal(err) |
||||
} |
||||
if !bytes.HasSuffix(it.Value, crlf) { |
||||
return c.fatal(protocolError("corrupt get reply, no except CRLF")) |
||||
} |
||||
it.Value = it.Value[:size] |
||||
f(it) |
||||
} |
||||
} |
||||
|
||||
func scanGetReply(line []byte, item *Item) (size int, err error) { |
||||
pattern := "VALUE %s %d %d %d\r\n" |
||||
dest := []interface{}{&item.Key, &item.Flags, &size, &item.cas} |
||||
if bytes.Count(line, space) == 3 { |
||||
pattern = "VALUE %s %d %d\r\n" |
||||
dest = dest[:3] |
||||
} |
||||
n, err := fmt.Sscanf(string(line), pattern, dest...) |
||||
if err != nil || n != len(dest) { |
||||
return -1, fmt.Errorf("memcache: unexpected line in get response: %q", line) |
||||
} |
||||
return size, nil |
||||
} |
||||
|
||||
func (c *asiiConn) Touch(ctx context.Context, key string, expire int32) error { |
||||
line, err := c.writeReadLine("touch %s %d\r\n", key, expire) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return replyToError(line) |
||||
} |
||||
|
||||
func (c *asiiConn) IncrDecr(ctx context.Context, cmd, key string, delta uint64) (uint64, error) { |
||||
line, err := c.writeReadLine("%s %s %d\r\n", cmd, key, delta) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
switch { |
||||
case bytes.Equal(line, replyNotFound): |
||||
return 0, ErrNotFound |
||||
case bytes.HasPrefix(line, replyClientErrorPrefix): |
||||
errMsg := line[len(replyClientErrorPrefix):] |
||||
return 0, pkgerr.WithStack(protocolError(errMsg)) |
||||
} |
||||
val, err := strconv.ParseUint(string(line[:len(line)-2]), 10, 64) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
return val, nil |
||||
} |
||||
|
||||
func (c *asiiConn) Delete(ctx context.Context, key string) error { |
||||
line, err := c.writeReadLine("delete %s\r\n", key) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return replyToError(line) |
||||
} |
||||
|
||||
func (c *asiiConn) writeReadLine(format string, args ...interface{}) ([]byte, error) { |
||||
c.conn.SetWriteDeadline(shrinkDeadline(context.TODO(), c.writeTimeout)) |
||||
_, err := fmt.Fprintf(c.rw, format, args...) |
||||
if err != nil { |
||||
return nil, c.fatal(pkgerr.WithStack(err)) |
||||
} |
||||
if err = c.rw.Flush(); err != nil { |
||||
return nil, c.fatal(pkgerr.WithStack(err)) |
||||
} |
||||
c.conn.SetReadDeadline(shrinkDeadline(context.TODO(), c.readTimeout)) |
||||
line, err := c.rw.ReadSlice('\n') |
||||
if err != nil { |
||||
return line, c.fatal(pkgerr.WithStack(err)) |
||||
} |
||||
return line, nil |
||||
} |
@ -0,0 +1,569 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"bytes" |
||||
"strconv" |
||||
"strings" |
||||
|
||||
"testing" |
||||
) |
||||
|
||||
func TestASCIIConnAdd(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
a *Item |
||||
e error |
||||
}{ |
||||
{ |
||||
"Add", |
||||
&Item{ |
||||
Key: "test_add", |
||||
Value: []byte("0"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"Add_Large", |
||||
&Item{ |
||||
Key: "test_add_large", |
||||
Value: bytes.Repeat(space, _largeValue+1), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"Add_Exist", |
||||
&Item{ |
||||
Key: "test_add", |
||||
Value: []byte("0"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
ErrNotStored, |
||||
}, |
||||
} |
||||
for _, test := range tests { |
||||
t.Run(test.name, func(t *testing.T) { |
||||
if err := testConnASCII.Add(test.a); err != test.e { |
||||
t.Fatal(err) |
||||
} |
||||
if b, err := testConnASCII.Get(test.a.Key); err != nil { |
||||
t.Fatal(err) |
||||
} else { |
||||
compareItem(t, test.a, b) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestASCIIConnGet(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
a *Item |
||||
k string |
||||
e error |
||||
}{ |
||||
{ |
||||
"Get", |
||||
&Item{ |
||||
Key: "test_get", |
||||
Value: []byte("0"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
"test_get", |
||||
nil, |
||||
}, |
||||
{ |
||||
"Get_NotExist", |
||||
&Item{ |
||||
Key: "test_get_not_exist", |
||||
Value: []byte("0"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
"test_get_not_exist!", |
||||
ErrNotFound, |
||||
}, |
||||
} |
||||
for _, test := range tests { |
||||
t.Run(test.name, func(t *testing.T) { |
||||
if err := testConnASCII.Add(test.a); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if b, err := testConnASCII.Get(test.a.Key); err != nil { |
||||
t.Fatal(err) |
||||
} else { |
||||
compareItem(t, test.a, b) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
//func TestGetHasErr(t *testing.T) {
|
||||
// prepareEnv(t)
|
||||
//
|
||||
// st := &TestItem{Name: "json", Age: 10}
|
||||
// itemx := &Item{Key: "test", Object: st, Flags: FlagJSON}
|
||||
// c.Set(itemx)
|
||||
//
|
||||
// expected := errors.New("some error")
|
||||
// monkey.Patch(scanGetReply, func(line []byte, item *Item) (size int, err error) {
|
||||
// return 0, expected
|
||||
// })
|
||||
//
|
||||
// if _, err := c.Get("test"); err.Error() != expected.Error() {
|
||||
// t.Errorf("conn.Get() unexpected error(%v)", err)
|
||||
// }
|
||||
// if err := c.(*asciiConn).err; err.Error() != expected.Error() {
|
||||
// t.Errorf("unexpected error(%v)", err)
|
||||
// }
|
||||
//}
|
||||
|
||||
func TestASCIIConnGetMulti(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
a []*Item |
||||
k []string |
||||
e error |
||||
}{ |
||||
{"getMulti_Add", |
||||
[]*Item{ |
||||
{ |
||||
Key: "get_multi_1", |
||||
Value: []byte("test"), |
||||
Flags: FlagRAW, |
||||
Expiration: 60, |
||||
cas: 0, |
||||
}, |
||||
{ |
||||
Key: "get_multi_2", |
||||
Value: []byte("test2"), |
||||
Flags: FlagRAW, |
||||
Expiration: 60, |
||||
cas: 0, |
||||
}, |
||||
}, |
||||
[]string{"get_multi_1", "get_multi_2"}, |
||||
nil, |
||||
}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
t.Run(test.name, func(t *testing.T) { |
||||
for _, i := range test.a { |
||||
if err := testConnASCII.Set(i); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
if r, err := testConnASCII.GetMulti(test.k); err != nil { |
||||
t.Fatal(err) |
||||
} else { |
||||
reply := r["get_multi_1"] |
||||
compareItem(t, reply, test.a[0]) |
||||
reply = r["get_multi_2"] |
||||
compareItem(t, reply, test.a[1]) |
||||
} |
||||
|
||||
}) |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestASCIIConnSet(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
a *Item |
||||
e error |
||||
}{ |
||||
{ |
||||
"SetLowerBound", |
||||
&Item{ |
||||
Key: strings.Repeat("a", 1), |
||||
Value: []byte("4"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"SetUpperBound", |
||||
&Item{ |
||||
Key: strings.Repeat("a", 250), |
||||
Value: []byte("3"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"SetIllegalKeyZeroLength", |
||||
&Item{ |
||||
Key: "", |
||||
Value: []byte("2"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
ErrMalformedKey, |
||||
}, |
||||
{ |
||||
"SetIllegalKeyLengthExceededLimit", |
||||
&Item{ |
||||
Key: " ", |
||||
Value: []byte("1"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
ErrMalformedKey, |
||||
}, |
||||
{ |
||||
"SeJsonItem", |
||||
&Item{ |
||||
Key: "set_obj", |
||||
Object: &struct { |
||||
Name string |
||||
Age int |
||||
}{"json", 10}, |
||||
Expiration: 60, |
||||
Flags: FlagJSON, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"SeErrItemJSONGzip", |
||||
&Item{ |
||||
Key: "set_err_item", |
||||
Expiration: 60, |
||||
Flags: FlagJSON | FlagGzip, |
||||
}, |
||||
ErrItem, |
||||
}, |
||||
{ |
||||
"SeErrItemBytesValueWrongFlag", |
||||
&Item{ |
||||
Key: "set_err_item", |
||||
Value: []byte("2"), |
||||
Expiration: 60, |
||||
Flags: FlagJSON, |
||||
}, |
||||
ErrItem, |
||||
}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
t.Run(test.name, func(t *testing.T) { |
||||
if err := testConnASCII.Set(test.a); err != test.e { |
||||
t.Fatal(err) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestASCIIConnCompareAndSwap(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
a *Item |
||||
b *Item |
||||
c *Item |
||||
k string |
||||
e error |
||||
}{ |
||||
{ |
||||
"CompareAndSwap", |
||||
&Item{ |
||||
Key: "test_cas", |
||||
Value: []byte("2"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
nil, |
||||
&Item{ |
||||
Key: "test_cas", |
||||
Value: []byte("3"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
"test_cas", |
||||
nil, |
||||
}, |
||||
{ |
||||
"CompareAndSwapErrCASConflict", |
||||
&Item{ |
||||
Key: "test_cas_conflict", |
||||
Value: []byte("2"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
&Item{ |
||||
Key: "test_cas_conflict", |
||||
Value: []byte("1"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
&Item{ |
||||
Key: "test_cas_conflict", |
||||
Value: []byte("3"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
"test_cas_conflict", |
||||
ErrCASConflict, |
||||
}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
t.Run(test.name, func(t *testing.T) { |
||||
if err := testConnASCII.Set(test.a); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
r, err := testConnASCII.Get(test.k) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
if test.b != nil { |
||||
if err := testConnASCII.Set(test.b); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
r.Value = test.c.Value |
||||
if err := testConnASCII.CompareAndSwap(r); err != nil { |
||||
if err != test.e { |
||||
t.Fatal(err) |
||||
} |
||||
} else { |
||||
if fr, err := testConnASCII.Get(test.k); err != nil { |
||||
t.Fatal(err) |
||||
} else { |
||||
compareItem(t, fr, test.c) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
t.Run("TestCompareAndSwapErrNotFound", func(t *testing.T) { |
||||
ti := &Item{ |
||||
Key: "test_cas_notfound", |
||||
Value: []byte("2"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
} |
||||
if err := testConnASCII.Set(ti); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
r, err := testConnASCII.Get(ti.Key) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
r.Key = "test_cas_notfound_boom" |
||||
r.Value = []byte("3") |
||||
if err := testConnASCII.CompareAndSwap(r); err != nil { |
||||
if err != ErrNotFound { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
func TestASCIIConnReplace(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
a *Item |
||||
b *Item |
||||
e error |
||||
}{ |
||||
{ |
||||
"TestReplace", |
||||
&Item{ |
||||
Key: "test_replace", |
||||
Value: []byte("2"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
&Item{ |
||||
Key: "test_replace", |
||||
Value: []byte("3"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"TestReplaceErrNotStored", |
||||
&Item{ |
||||
Key: "test_replace_not_stored", |
||||
Value: []byte("2"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
&Item{ |
||||
Key: "test_replace_not_stored_boom", |
||||
Value: []byte("3"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
}, |
||||
ErrNotStored, |
||||
}, |
||||
} |
||||
|
||||
for _, test := range tests { |
||||
t.Run(test.name, func(t *testing.T) { |
||||
if err := testConnASCII.Set(test.a); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if err := testConnASCII.Replace(test.b); err != nil { |
||||
if err == test.e { |
||||
return |
||||
} |
||||
t.Fatal(err) |
||||
} |
||||
if r, err := testConnASCII.Get(test.b.Key); err != nil { |
||||
t.Fatal(err) |
||||
} else { |
||||
compareItem(t, r, test.b) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestASCIIConnIncrDecr(t *testing.T) { |
||||
tests := []struct { |
||||
fn func(key string, delta uint64) (uint64, error) |
||||
name string |
||||
k string |
||||
v uint64 |
||||
w uint64 |
||||
}{ |
||||
{ |
||||
testConnASCII.Increment, |
||||
"Incr_10", |
||||
"test_incr", |
||||
10, |
||||
10, |
||||
}, |
||||
{ |
||||
testConnASCII.Increment, |
||||
"Incr_10(2)", |
||||
"test_incr", |
||||
10, |
||||
20, |
||||
}, |
||||
{ |
||||
testConnASCII.Decrement, |
||||
"Decr_10", |
||||
"test_incr", |
||||
10, |
||||
10, |
||||
}, |
||||
} |
||||
if err := testConnASCII.Add(&Item{ |
||||
Key: "test_incr", |
||||
Value: []byte("0"), |
||||
}); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
for _, test := range tests { |
||||
t.Run(test.name, func(t *testing.T) { |
||||
if a, err := test.fn(test.k, test.v); err != nil { |
||||
t.Fatal(err) |
||||
} else { |
||||
if a != test.w { |
||||
t.Fatalf("want %d, got %d", test.w, a) |
||||
} |
||||
} |
||||
if b, err := testConnASCII.Get(test.k); err != nil { |
||||
t.Fatal(err) |
||||
} else { |
||||
if string(b.Value) != strconv.FormatUint(test.w, 10) { |
||||
t.Fatalf("want %s, got %d", b.Value, test.w) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestASCIIConnTouch(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
k string |
||||
a *Item |
||||
e error |
||||
}{ |
||||
{ |
||||
"Touch", |
||||
"test_touch", |
||||
&Item{ |
||||
Key: "test_touch", |
||||
Value: []byte("0"), |
||||
Expiration: 60, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"Touch_NotExist", |
||||
"test_touch_not_exist", |
||||
nil, |
||||
ErrNotFound, |
||||
}, |
||||
} |
||||
for _, test := range tests { |
||||
if test.a != nil { |
||||
if err := testConnASCII.Add(test.a); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if err := testConnASCII.Touch(test.k, 1); err != test.e { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestASCIIConnDelete(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
k string |
||||
a *Item |
||||
e error |
||||
}{ |
||||
{ |
||||
"Delete", |
||||
"test_delete", |
||||
&Item{ |
||||
Key: "test_delete", |
||||
Value: []byte("0"), |
||||
Expiration: 60, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"Delete_NotExist", |
||||
"test_delete_not_exist", |
||||
nil, |
||||
ErrNotFound, |
||||
}, |
||||
} |
||||
for _, test := range tests { |
||||
if test.a != nil { |
||||
if err := testConnASCII.Add(test.a); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if err := testConnASCII.Delete(test.k); err != test.e { |
||||
t.Fatal(err) |
||||
} |
||||
if _, err := testConnASCII.Get(test.k); err != ErrNotFound { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func compareItem(t *testing.T, a, b *Item) { |
||||
if a.Key != b.Key || !bytes.Equal(a.Value, b.Value) || a.Flags != b.Flags { |
||||
t.Fatalf("compareItem: a(%s, %d, %d) : b(%s, %d, %d)", a.Key, len(a.Value), a.Flags, b.Key, len(b.Value), b.Flags) |
||||
} |
||||
} |
@ -1,187 +0,0 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"context" |
||||
) |
||||
|
||||
// Memcache memcache client
|
||||
type Memcache struct { |
||||
pool *Pool |
||||
} |
||||
|
||||
// Reply is the result of Get
|
||||
type Reply struct { |
||||
err error |
||||
item *Item |
||||
conn Conn |
||||
closed bool |
||||
} |
||||
|
||||
// Replies is the result of GetMulti
|
||||
type Replies struct { |
||||
err error |
||||
items map[string]*Item |
||||
usedItems map[string]struct{} |
||||
conn Conn |
||||
closed bool |
||||
} |
||||
|
||||
// New get a memcache client
|
||||
func New(c *Config) *Memcache { |
||||
return &Memcache{pool: NewPool(c)} |
||||
} |
||||
|
||||
// Close close connection pool
|
||||
func (mc *Memcache) Close() error { |
||||
return mc.pool.Close() |
||||
} |
||||
|
||||
// Conn direct get a connection
|
||||
func (mc *Memcache) Conn(c context.Context) Conn { |
||||
return mc.pool.Get(c) |
||||
} |
||||
|
||||
// Set writes the given item, unconditionally.
|
||||
func (mc *Memcache) Set(c context.Context, item *Item) (err error) { |
||||
conn := mc.pool.Get(c) |
||||
err = conn.Set(item) |
||||
conn.Close() |
||||
return |
||||
} |
||||
|
||||
// Add writes the given item, if no value already exists for its key.
|
||||
// ErrNotStored is returned if that condition is not met.
|
||||
func (mc *Memcache) Add(c context.Context, item *Item) (err error) { |
||||
conn := mc.pool.Get(c) |
||||
err = conn.Add(item) |
||||
conn.Close() |
||||
return |
||||
} |
||||
|
||||
// Replace writes the given item, but only if the server *does* already hold data for this key.
|
||||
func (mc *Memcache) Replace(c context.Context, item *Item) (err error) { |
||||
conn := mc.pool.Get(c) |
||||
err = conn.Replace(item) |
||||
conn.Close() |
||||
return |
||||
} |
||||
|
||||
// CompareAndSwap writes the given item that was previously returned by Get
|
||||
func (mc *Memcache) CompareAndSwap(c context.Context, item *Item) (err error) { |
||||
conn := mc.pool.Get(c) |
||||
err = conn.CompareAndSwap(item) |
||||
conn.Close() |
||||
return |
||||
} |
||||
|
||||
// Get sends a command to the server for gets data.
|
||||
func (mc *Memcache) Get(c context.Context, key string) *Reply { |
||||
conn := mc.pool.Get(c) |
||||
item, err := conn.Get(key) |
||||
if err != nil { |
||||
conn.Close() |
||||
} |
||||
return &Reply{err: err, item: item, conn: conn} |
||||
} |
||||
|
||||
// Item get raw Item
|
||||
func (r *Reply) Item() *Item { |
||||
return r.item |
||||
} |
||||
|
||||
// Scan converts value, read from the memcache
|
||||
func (r *Reply) Scan(v interface{}) (err error) { |
||||
if r.err != nil { |
||||
return r.err |
||||
} |
||||
err = r.conn.Scan(r.item, v) |
||||
if !r.closed { |
||||
r.conn.Close() |
||||
r.closed = true |
||||
} |
||||
return |
||||
} |
||||
|
||||
// GetMulti is a batch version of Get
|
||||
func (mc *Memcache) GetMulti(c context.Context, keys []string) (*Replies, error) { |
||||
conn := mc.pool.Get(c) |
||||
items, err := conn.GetMulti(keys) |
||||
rs := &Replies{err: err, items: items, conn: conn, usedItems: make(map[string]struct{}, len(keys))} |
||||
if (err != nil) || (len(items) == 0) { |
||||
rs.Close() |
||||
} |
||||
return rs, err |
||||
} |
||||
|
||||
// Close close rows.
|
||||
func (rs *Replies) Close() (err error) { |
||||
if !rs.closed { |
||||
err = rs.conn.Close() |
||||
rs.closed = true |
||||
} |
||||
return |
||||
} |
||||
|
||||
// Item get Item from rows
|
||||
func (rs *Replies) Item(key string) *Item { |
||||
return rs.items[key] |
||||
} |
||||
|
||||
// Scan converts value, read from key in rows
|
||||
func (rs *Replies) Scan(key string, v interface{}) (err error) { |
||||
if rs.err != nil { |
||||
return rs.err |
||||
} |
||||
item, ok := rs.items[key] |
||||
if !ok { |
||||
rs.Close() |
||||
return ErrNotFound |
||||
} |
||||
rs.usedItems[key] = struct{}{} |
||||
err = rs.conn.Scan(item, v) |
||||
if (err != nil) || (len(rs.items) == len(rs.usedItems)) { |
||||
rs.Close() |
||||
} |
||||
return |
||||
} |
||||
|
||||
// Keys keys of result
|
||||
func (rs *Replies) Keys() (keys []string) { |
||||
keys = make([]string, 0, len(rs.items)) |
||||
for key := range rs.items { |
||||
keys = append(keys, key) |
||||
} |
||||
return |
||||
} |
||||
|
||||
// Touch updates the expiry for the given key.
|
||||
func (mc *Memcache) Touch(c context.Context, key string, timeout int32) (err error) { |
||||
conn := mc.pool.Get(c) |
||||
err = conn.Touch(key, timeout) |
||||
conn.Close() |
||||
return |
||||
} |
||||
|
||||
// Delete deletes the item with the provided key.
|
||||
func (mc *Memcache) Delete(c context.Context, key string) (err error) { |
||||
conn := mc.pool.Get(c) |
||||
err = conn.Delete(key) |
||||
conn.Close() |
||||
return |
||||
} |
||||
|
||||
// Increment atomically increments key by delta.
|
||||
func (mc *Memcache) Increment(c context.Context, key string, delta uint64) (newValue uint64, err error) { |
||||
conn := mc.pool.Get(c) |
||||
newValue, err = conn.Increment(key, delta) |
||||
conn.Close() |
||||
return |
||||
} |
||||
|
||||
// Decrement atomically decrements key by delta.
|
||||
func (mc *Memcache) Decrement(c context.Context, key string, delta uint64) (newValue uint64, err error) { |
||||
conn := mc.pool.Get(c) |
||||
newValue, err = conn.Decrement(key, delta) |
||||
conn.Close() |
||||
return |
||||
} |
@ -0,0 +1,185 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"testing" |
||||
|
||||
test "github.com/bilibili/kratos/pkg/cache/memcache/test" |
||||
"github.com/gogo/protobuf/proto" |
||||
) |
||||
|
||||
func TestConnRaw(t *testing.T) { |
||||
item := &Item{ |
||||
Key: "test", |
||||
Value: []byte("test"), |
||||
Flags: FlagRAW, |
||||
Expiration: 60, |
||||
cas: 0, |
||||
} |
||||
if err := testConnASCII.Set(item); err != nil { |
||||
t.Errorf("conn.Store() error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestConnSerialization(t *testing.T) { |
||||
type TestObj struct { |
||||
Name string |
||||
Age int32 |
||||
} |
||||
|
||||
tests := []struct { |
||||
name string |
||||
a *Item |
||||
e error |
||||
}{ |
||||
|
||||
{ |
||||
"JSON", |
||||
&Item{ |
||||
Key: "test_json", |
||||
Object: &TestObj{"json", 1}, |
||||
Expiration: 60, |
||||
Flags: FlagJSON, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"JSONGzip", |
||||
&Item{ |
||||
Key: "test_json_gzip", |
||||
Object: &TestObj{"jsongzip", 2}, |
||||
Expiration: 60, |
||||
Flags: FlagJSON | FlagGzip, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"GOB", |
||||
&Item{ |
||||
Key: "test_gob", |
||||
Object: &TestObj{"gob", 3}, |
||||
Expiration: 60, |
||||
Flags: FlagGOB, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"GOBGzip", |
||||
&Item{ |
||||
Key: "test_gob_gzip", |
||||
Object: &TestObj{"gobgzip", 4}, |
||||
Expiration: 60, |
||||
Flags: FlagGOB | FlagGzip, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"Protobuf", |
||||
&Item{ |
||||
Key: "test_protobuf", |
||||
Object: &test.TestItem{Name: "protobuf", Age: 6}, |
||||
Expiration: 60, |
||||
Flags: FlagProtobuf, |
||||
}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"ProtobufGzip", |
||||
&Item{ |
||||
Key: "test_protobuf_gzip", |
||||
Object: &test.TestItem{Name: "protobufgzip", Age: 7}, |
||||
Expiration: 60, |
||||
Flags: FlagProtobuf | FlagGzip, |
||||
}, |
||||
nil, |
||||
}, |
||||
} |
||||
for _, tc := range tests { |
||||
t.Run(tc.name, func(t *testing.T) { |
||||
if err := testConnASCII.Set(tc.a); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if r, err := testConnASCII.Get(tc.a.Key); err != tc.e { |
||||
t.Fatal(err) |
||||
} else { |
||||
if (tc.a.Flags & FlagProtobuf) > 0 { |
||||
var no test.TestItem |
||||
if err := testConnASCII.Scan(r, &no); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if (tc.a.Object.(*test.TestItem).Name != no.Name) || (tc.a.Object.(*test.TestItem).Age != no.Age) { |
||||
t.Fatalf("compare failed error, %v %v", tc.a.Object.(*test.TestItem), no) |
||||
} |
||||
} else { |
||||
var no TestObj |
||||
if err := testConnASCII.Scan(r, &no); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if (tc.a.Object.(*TestObj).Name != no.Name) || (tc.a.Object.(*TestObj).Age != no.Age) { |
||||
t.Fatalf("compare failed error, %v %v", tc.a.Object.(*TestObj), no) |
||||
} |
||||
} |
||||
|
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkConnJSON(b *testing.B) { |
||||
st := &struct { |
||||
Name string |
||||
Age int |
||||
}{"json", 10} |
||||
itemx := &Item{Key: "json", Object: st, Flags: FlagJSON} |
||||
var ( |
||||
eb bytes.Buffer |
||||
je *json.Encoder |
||||
ir bytes.Reader |
||||
jd *json.Decoder |
||||
jr reader |
||||
nst test.TestItem |
||||
) |
||||
jd = json.NewDecoder(&jr) |
||||
je = json.NewEncoder(&eb) |
||||
eb.Grow(_encodeBuf) |
||||
// NOTE reuse bytes.Buffer internal buf
|
||||
// DON'T concurrency call Scan
|
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
eb.Reset() |
||||
if err := je.Encode(itemx.Object); err != nil { |
||||
return |
||||
} |
||||
data := eb.Bytes() |
||||
ir.Reset(data) |
||||
jr.Reset(&ir) |
||||
jd.Decode(&nst) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkConnProtobuf(b *testing.B) { |
||||
st := &test.TestItem{Name: "protobuf", Age: 10} |
||||
itemx := &Item{Key: "protobuf", Object: st, Flags: FlagJSON} |
||||
var ( |
||||
eb bytes.Buffer |
||||
nst test.TestItem |
||||
ped *proto.Buffer |
||||
) |
||||
ped = proto.NewBuffer(eb.Bytes()) |
||||
eb.Grow(_encodeBuf) |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
ped.Reset() |
||||
pb, ok := itemx.Object.(proto.Message) |
||||
if !ok { |
||||
return |
||||
} |
||||
if err := ped.Marshal(pb); err != nil { |
||||
return |
||||
} |
||||
data := ped.Bytes() |
||||
ped.SetBuf(data) |
||||
ped.Unmarshal(&nst) |
||||
} |
||||
} |
@ -0,0 +1,162 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"bytes" |
||||
"compress/gzip" |
||||
"encoding/gob" |
||||
"encoding/json" |
||||
"io" |
||||
|
||||
"github.com/gogo/protobuf/proto" |
||||
) |
||||
|
||||
type reader struct { |
||||
io.Reader |
||||
} |
||||
|
||||
func (r *reader) Reset(rd io.Reader) { |
||||
r.Reader = rd |
||||
} |
||||
|
||||
const _encodeBuf = 4096 // 4kb
|
||||
|
||||
type encodeDecode struct { |
||||
// Item Reader
|
||||
ir bytes.Reader |
||||
// Compress
|
||||
gr gzip.Reader |
||||
gw *gzip.Writer |
||||
cb bytes.Buffer |
||||
// Encoding
|
||||
edb bytes.Buffer |
||||
// json
|
||||
jr reader |
||||
jd *json.Decoder |
||||
je *json.Encoder |
||||
// protobuffer
|
||||
ped *proto.Buffer |
||||
} |
||||
|
||||
func newEncodeDecoder() *encodeDecode { |
||||
ed := &encodeDecode{} |
||||
ed.jd = json.NewDecoder(&ed.jr) |
||||
ed.je = json.NewEncoder(&ed.edb) |
||||
ed.gw = gzip.NewWriter(&ed.cb) |
||||
ed.edb.Grow(_encodeBuf) |
||||
// NOTE reuse bytes.Buffer internal buf
|
||||
// DON'T concurrency call Scan
|
||||
ed.ped = proto.NewBuffer(ed.edb.Bytes()) |
||||
return ed |
||||
} |
||||
|
||||
func (ed *encodeDecode) encode(item *Item) (data []byte, err error) { |
||||
if (item.Flags | _flagEncoding) == _flagEncoding { |
||||
if item.Value == nil { |
||||
return nil, ErrItem |
||||
} |
||||
} else if item.Object == nil { |
||||
return nil, ErrItem |
||||
} |
||||
// encoding
|
||||
switch { |
||||
case item.Flags&FlagGOB == FlagGOB: |
||||
ed.edb.Reset() |
||||
if err = gob.NewEncoder(&ed.edb).Encode(item.Object); err != nil { |
||||
return |
||||
} |
||||
data = ed.edb.Bytes() |
||||
case item.Flags&FlagProtobuf == FlagProtobuf: |
||||
ed.edb.Reset() |
||||
ed.ped.SetBuf(ed.edb.Bytes()) |
||||
pb, ok := item.Object.(proto.Message) |
||||
if !ok { |
||||
err = ErrItemObject |
||||
return |
||||
} |
||||
if err = ed.ped.Marshal(pb); err != nil { |
||||
return |
||||
} |
||||
data = ed.ped.Bytes() |
||||
case item.Flags&FlagJSON == FlagJSON: |
||||
ed.edb.Reset() |
||||
if err = ed.je.Encode(item.Object); err != nil { |
||||
return |
||||
} |
||||
data = ed.edb.Bytes() |
||||
default: |
||||
data = item.Value |
||||
} |
||||
// compress
|
||||
if item.Flags&FlagGzip == FlagGzip { |
||||
ed.cb.Reset() |
||||
ed.gw.Reset(&ed.cb) |
||||
if _, err = ed.gw.Write(data); err != nil { |
||||
return |
||||
} |
||||
if err = ed.gw.Close(); err != nil { |
||||
return |
||||
} |
||||
data = ed.cb.Bytes() |
||||
} |
||||
if len(data) > 8000000 { |
||||
err = ErrValueSize |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (ed *encodeDecode) decode(item *Item, v interface{}) (err error) { |
||||
var ( |
||||
data []byte |
||||
rd io.Reader |
||||
) |
||||
ed.ir.Reset(item.Value) |
||||
rd = &ed.ir |
||||
if item.Flags&FlagGzip == FlagGzip { |
||||
rd = &ed.gr |
||||
if err = ed.gr.Reset(&ed.ir); err != nil { |
||||
return |
||||
} |
||||
defer func() { |
||||
if e := ed.gr.Close(); e != nil { |
||||
err = e |
||||
} |
||||
}() |
||||
} |
||||
switch { |
||||
case item.Flags&FlagGOB == FlagGOB: |
||||
err = gob.NewDecoder(rd).Decode(v) |
||||
case item.Flags&FlagJSON == FlagJSON: |
||||
ed.jr.Reset(rd) |
||||
err = ed.jd.Decode(v) |
||||
default: |
||||
data = item.Value |
||||
if item.Flags&FlagGzip == FlagGzip { |
||||
ed.edb.Reset() |
||||
if _, err = io.Copy(&ed.edb, rd); err != nil { |
||||
return |
||||
} |
||||
data = ed.edb.Bytes() |
||||
} |
||||
if item.Flags&FlagProtobuf == FlagProtobuf { |
||||
m, ok := v.(proto.Message) |
||||
if !ok { |
||||
err = ErrItemObject |
||||
return |
||||
} |
||||
ed.ped.SetBuf(data) |
||||
err = ed.ped.Unmarshal(m) |
||||
} else { |
||||
switch v.(type) { |
||||
case *[]byte: |
||||
d := v.(*[]byte) |
||||
*d = data |
||||
case *string: |
||||
d := v.(*string) |
||||
*d = string(data) |
||||
case interface{}: |
||||
err = json.Unmarshal(data, v) |
||||
} |
||||
} |
||||
} |
||||
return |
||||
} |
@ -0,0 +1,220 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"bytes" |
||||
"testing" |
||||
|
||||
mt "github.com/bilibili/kratos/pkg/cache/memcache/test" |
||||
) |
||||
|
||||
func TestEncode(t *testing.T) { |
||||
type TestObj struct { |
||||
Name string |
||||
Age int32 |
||||
} |
||||
testObj := TestObj{"abc", 1} |
||||
|
||||
ed := newEncodeDecoder() |
||||
tests := []struct { |
||||
name string |
||||
a *Item |
||||
r []byte |
||||
e error |
||||
}{ |
||||
{ |
||||
"EncodeRawFlagErrItem", |
||||
&Item{ |
||||
Object: &TestObj{"abc", 1}, |
||||
Flags: FlagRAW, |
||||
}, |
||||
[]byte{}, |
||||
ErrItem, |
||||
}, |
||||
{ |
||||
"EncodeEncodeFlagErrItem", |
||||
&Item{ |
||||
Value: []byte("test"), |
||||
Flags: FlagJSON, |
||||
}, |
||||
[]byte{}, |
||||
ErrItem, |
||||
}, |
||||
{ |
||||
"EncodeEmpty", |
||||
&Item{ |
||||
Value: []byte(""), |
||||
Flags: FlagRAW, |
||||
}, |
||||
[]byte(""), |
||||
nil, |
||||
}, |
||||
{ |
||||
"EncodeMaxSize", |
||||
&Item{ |
||||
Value: bytes.Repeat([]byte("A"), 8000000), |
||||
Flags: FlagRAW, |
||||
}, |
||||
bytes.Repeat([]byte("A"), 8000000), |
||||
nil, |
||||
}, |
||||
{ |
||||
"EncodeExceededMaxSize", |
||||
&Item{ |
||||
Value: bytes.Repeat([]byte("A"), 8000000+1), |
||||
Flags: FlagRAW, |
||||
}, |
||||
nil, |
||||
ErrValueSize, |
||||
}, |
||||
{ |
||||
"EncodeGOB", |
||||
&Item{ |
||||
Object: testObj, |
||||
Flags: FlagGOB, |
||||
}, |
||||
[]byte{38, 255, 131, 3, 1, 1, 7, 84, 101, 115, 116, 79, 98, 106, 1, 255, 132, 0, 1, 2, 1, 4, 78, 97, 109, 101, 1, 12, 0, 1, 3, 65, 103, 101, 1, 4, 0, 0, 0, 10, 255, 132, 1, 3, 97, 98, 99, 1, 2, 0}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"EncodeJSON", |
||||
&Item{ |
||||
Object: testObj, |
||||
Flags: FlagJSON, |
||||
}, |
||||
[]byte{123, 34, 78, 97, 109, 101, 34, 58, 34, 97, 98, 99, 34, 44, 34, 65, 103, 101, 34, 58, 49, 125, 10}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"EncodeProtobuf", |
||||
&Item{ |
||||
Object: &mt.TestItem{Name: "abc", Age: 1}, |
||||
Flags: FlagProtobuf, |
||||
}, |
||||
[]byte{10, 3, 97, 98, 99, 16, 1}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"EncodeGzip", |
||||
&Item{ |
||||
Value: bytes.Repeat([]byte("B"), 50), |
||||
Flags: FlagGzip, |
||||
}, |
||||
[]byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 114, 34, 25, 0, 2, 0, 0, 255, 255, 252, 253, 67, 209, 50, 0, 0, 0}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"EncodeGOBGzip", |
||||
&Item{ |
||||
Object: testObj, |
||||
Flags: FlagGOB | FlagGzip, |
||||
}, |
||||
[]byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 82, 251, 223, 204, 204, 200, 200, 30, 146, 90, 92, 226, 159, 148, 197, 248, 191, 133, 129, 145, 137, 145, 197, 47, 49, 55, 149, 145, 135, 129, 145, 217, 49, 61, 149, 145, 133, 129, 129, 129, 235, 127, 11, 35, 115, 98, 82, 50, 35, 19, 3, 32, 0, 0, 255, 255, 211, 249, 1, 154, 50, 0, 0, 0}, |
||||
nil, |
||||
}, |
||||
} |
||||
for _, test := range tests { |
||||
t.Run(test.name, func(t *testing.T) { |
||||
if r, err := ed.encode(test.a); err != test.e { |
||||
t.Fatal(err) |
||||
} else { |
||||
if err == nil { |
||||
if !bytes.Equal(r, test.r) { |
||||
t.Fatalf("not equal, expect %v\n got %v", test.r, r) |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestDecode(t *testing.T) { |
||||
type TestObj struct { |
||||
Name string |
||||
Age int32 |
||||
} |
||||
testObj := &TestObj{"abc", 1} |
||||
|
||||
ed := newEncodeDecoder() |
||||
tests := []struct { |
||||
name string |
||||
a *Item |
||||
r interface{} |
||||
e error |
||||
}{ |
||||
{ |
||||
"DecodeGOB", |
||||
&Item{ |
||||
Flags: FlagGOB, |
||||
Value: []byte{38, 255, 131, 3, 1, 1, 7, 84, 101, 115, 116, 79, 98, 106, 1, 255, 132, 0, 1, 2, 1, 4, 78, 97, 109, 101, 1, 12, 0, 1, 3, 65, 103, 101, 1, 4, 0, 0, 0, 10, 255, 132, 1, 3, 97, 98, 99, 1, 2, 0}, |
||||
}, |
||||
testObj, |
||||
nil, |
||||
}, |
||||
{ |
||||
"DecodeJSON", |
||||
&Item{ |
||||
Value: []byte{123, 34, 78, 97, 109, 101, 34, 58, 34, 97, 98, 99, 34, 44, 34, 65, 103, 101, 34, 58, 49, 125, 10}, |
||||
Flags: FlagJSON, |
||||
}, |
||||
testObj, |
||||
nil, |
||||
}, |
||||
{ |
||||
"DecodeProtobuf", |
||||
&Item{ |
||||
Value: []byte{10, 3, 97, 98, 99, 16, 1}, |
||||
|
||||
Flags: FlagProtobuf, |
||||
}, |
||||
&mt.TestItem{Name: "abc", Age: 1}, |
||||
nil, |
||||
}, |
||||
{ |
||||
"DecodeGzip", |
||||
&Item{ |
||||
Value: []byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 114, 34, 25, 0, 2, 0, 0, 255, 255, 252, 253, 67, 209, 50, 0, 0, 0}, |
||||
Flags: FlagGzip, |
||||
}, |
||||
bytes.Repeat([]byte("B"), 50), |
||||
nil, |
||||
}, |
||||
{ |
||||
"DecodeGOBGzip", |
||||
&Item{ |
||||
Value: []byte{31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 82, 251, 223, 204, 204, 200, 200, 30, 146, 90, 92, 226, 159, 148, 197, 248, 191, 133, 129, 145, 137, 145, 197, 47, 49, 55, 149, 145, 135, 129, 145, 217, 49, 61, 149, 145, 133, 129, 129, 129, 235, 127, 11, 35, 115, 98, 82, 50, 35, 19, 3, 32, 0, 0, 255, 255, 211, 249, 1, 154, 50, 0, 0, 0}, |
||||
Flags: FlagGOB | FlagGzip, |
||||
}, |
||||
testObj, |
||||
nil, |
||||
}, |
||||
} |
||||
for _, test := range tests { |
||||
t.Run(test.name, func(t *testing.T) { |
||||
if (test.a.Flags & FlagProtobuf) > 0 { |
||||
var dd mt.TestItem |
||||
if err := ed.decode(test.a, &dd); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if (test.r.(*mt.TestItem).Name != dd.Name) || (test.r.(*mt.TestItem).Age != dd.Age) { |
||||
t.Fatalf("compare failed error, expect %v\n got %v", test.r.(*mt.TestItem), dd) |
||||
} |
||||
} else if test.a.Flags == FlagGzip { |
||||
var dd []byte |
||||
if err := ed.decode(test.a, &dd); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if !bytes.Equal(dd, test.r.([]byte)) { |
||||
t.Fatalf("compare failed error, expect %v\n got %v", test.r, dd) |
||||
} |
||||
} else { |
||||
var dd TestObj |
||||
if err := ed.decode(test.a, &dd); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if (test.r.(*TestObj).Name != dd.Name) || (test.r.(*TestObj).Age != dd.Age) { |
||||
t.Fatalf("compare failed error, expect %v\n got %v", test.r.(*TestObj), dd) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,177 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"time" |
||||
) |
||||
|
||||
var testExampleAddr string |
||||
|
||||
func ExampleConn_set() { |
||||
var ( |
||||
err error |
||||
value []byte |
||||
conn Conn |
||||
expire int32 = 100 |
||||
p = struct { |
||||
Name string |
||||
Age int64 |
||||
}{"golang", 10} |
||||
) |
||||
cnop := DialConnectTimeout(time.Duration(time.Second)) |
||||
rdop := DialReadTimeout(time.Duration(time.Second)) |
||||
wrop := DialWriteTimeout(time.Duration(time.Second)) |
||||
if value, err = json.Marshal(p); err != nil { |
||||
fmt.Println(err) |
||||
return |
||||
} |
||||
if conn, err = Dial("tcp", testExampleAddr, cnop, rdop, wrop); err != nil { |
||||
fmt.Println(err) |
||||
return |
||||
} |
||||
// FlagRAW test
|
||||
itemRaw := &Item{ |
||||
Key: "test_raw", |
||||
Value: value, |
||||
Expiration: expire, |
||||
} |
||||
if err = conn.Set(itemRaw); err != nil { |
||||
fmt.Println(err) |
||||
return |
||||
} |
||||
// FlagGzip
|
||||
itemGZip := &Item{ |
||||
Key: "test_gzip", |
||||
Value: value, |
||||
Flags: FlagGzip, |
||||
Expiration: expire, |
||||
} |
||||
if err = conn.Set(itemGZip); err != nil { |
||||
fmt.Println(err) |
||||
return |
||||
} |
||||
// FlagGOB
|
||||
itemGOB := &Item{ |
||||
Key: "test_gob", |
||||
Object: p, |
||||
Flags: FlagGOB, |
||||
Expiration: expire, |
||||
} |
||||
if err = conn.Set(itemGOB); err != nil { |
||||
fmt.Println(err) |
||||
return |
||||
} |
||||
// FlagJSON
|
||||
itemJSON := &Item{ |
||||
Key: "test_json", |
||||
Object: p, |
||||
Flags: FlagJSON, |
||||
Expiration: expire, |
||||
} |
||||
if err = conn.Set(itemJSON); err != nil { |
||||
fmt.Println(err) |
||||
return |
||||
} |
||||
// FlagJSON | FlagGzip
|
||||
itemJSONGzip := &Item{ |
||||
Key: "test_jsonGzip", |
||||
Object: p, |
||||
Flags: FlagJSON | FlagGzip, |
||||
Expiration: expire, |
||||
} |
||||
if err = conn.Set(itemJSONGzip); err != nil { |
||||
fmt.Println(err) |
||||
return |
||||
} |
||||
// Output:
|
||||
} |
||||
|
||||
func ExampleConn_get() { |
||||
var ( |
||||
err error |
||||
item2 *Item |
||||
conn Conn |
||||
p struct { |
||||
Name string |
||||
Age int64 |
||||
} |
||||
) |
||||
cnop := DialConnectTimeout(time.Duration(time.Second)) |
||||
rdop := DialReadTimeout(time.Duration(time.Second)) |
||||
wrop := DialWriteTimeout(time.Duration(time.Second)) |
||||
if conn, err = Dial("tcp", testExampleAddr, cnop, rdop, wrop); err != nil { |
||||
fmt.Println(err) |
||||
return |
||||
} |
||||
if item2, err = conn.Get("test_raw"); err != nil { |
||||
fmt.Println(err) |
||||
} else { |
||||
if err = conn.Scan(item2, &p); err != nil { |
||||
fmt.Printf("FlagRAW conn.Scan error(%v)\n", err) |
||||
return |
||||
} |
||||
} |
||||
// FlagGZip
|
||||
if item2, err = conn.Get("test_gzip"); err != nil { |
||||
fmt.Println(err) |
||||
} else { |
||||
if err = conn.Scan(item2, &p); err != nil { |
||||
fmt.Printf("FlagGZip conn.Scan error(%v)\n", err) |
||||
return |
||||
} |
||||
} |
||||
// FlagGOB
|
||||
if item2, err = conn.Get("test_gob"); err != nil { |
||||
fmt.Println(err) |
||||
} else { |
||||
if err = conn.Scan(item2, &p); err != nil { |
||||
fmt.Printf("FlagGOB conn.Scan error(%v)\n", err) |
||||
return |
||||
} |
||||
} |
||||
// FlagJSON
|
||||
if item2, err = conn.Get("test_json"); err != nil { |
||||
fmt.Println(err) |
||||
} else { |
||||
if err = conn.Scan(item2, &p); err != nil { |
||||
fmt.Printf("FlagJSON conn.Scan error(%v)\n", err) |
||||
return |
||||
} |
||||
} |
||||
// Output:
|
||||
} |
||||
|
||||
func ExampleConn_getMulti() { |
||||
var ( |
||||
err error |
||||
conn Conn |
||||
res map[string]*Item |
||||
keys = []string{"test_raw", "test_gzip"} |
||||
p struct { |
||||
Name string |
||||
Age int64 |
||||
} |
||||
) |
||||
cnop := DialConnectTimeout(time.Duration(time.Second)) |
||||
rdop := DialReadTimeout(time.Duration(time.Second)) |
||||
wrop := DialWriteTimeout(time.Duration(time.Second)) |
||||
if conn, err = Dial("tcp", testExampleAddr, cnop, rdop, wrop); err != nil { |
||||
fmt.Println(err) |
||||
return |
||||
} |
||||
if res, err = conn.GetMulti(keys); err != nil { |
||||
fmt.Printf("conn.GetMulti(%v) error(%v)", keys, err) |
||||
return |
||||
} |
||||
for _, v := range res { |
||||
if err = conn.Scan(v, &p); err != nil { |
||||
fmt.Printf("conn.Scan error(%v)\n", err) |
||||
return |
||||
} |
||||
fmt.Println(p) |
||||
} |
||||
// Output:
|
||||
//{golang 10}
|
||||
//{golang 10}
|
||||
} |
@ -0,0 +1,85 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"log" |
||||
"os" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/bilibili/kratos/pkg/container/pool" |
||||
xtime "github.com/bilibili/kratos/pkg/time" |
||||
) |
||||
|
||||
var testConnASCII Conn |
||||
var testMemcache *Memcache |
||||
var testPool *Pool |
||||
var testMemcacheAddr string |
||||
|
||||
func setupTestConnASCII(addr string) { |
||||
var err error |
||||
cnop := DialConnectTimeout(time.Duration(2 * time.Second)) |
||||
rdop := DialReadTimeout(time.Duration(2 * time.Second)) |
||||
wrop := DialWriteTimeout(time.Duration(2 * time.Second)) |
||||
testConnASCII, err = Dial("tcp", addr, cnop, rdop, wrop) |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
testConnASCII.Delete("test") |
||||
testConnASCII.Delete("test1") |
||||
testConnASCII.Delete("test2") |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
func setupTestMemcache(addr string) { |
||||
testConfig := &Config{ |
||||
Config: &pool.Config{ |
||||
Active: 10, |
||||
Idle: 10, |
||||
IdleTimeout: xtime.Duration(time.Second), |
||||
WaitTimeout: xtime.Duration(time.Second), |
||||
Wait: false, |
||||
}, |
||||
Addr: addr, |
||||
Proto: "tcp", |
||||
DialTimeout: xtime.Duration(time.Second), |
||||
ReadTimeout: xtime.Duration(time.Second), |
||||
WriteTimeout: xtime.Duration(time.Second), |
||||
} |
||||
testMemcache = New(testConfig) |
||||
} |
||||
|
||||
func setupTestPool(addr string) { |
||||
config := &Config{ |
||||
Name: "test", |
||||
Proto: "tcp", |
||||
Addr: addr, |
||||
DialTimeout: xtime.Duration(time.Second), |
||||
ReadTimeout: xtime.Duration(time.Second), |
||||
WriteTimeout: xtime.Duration(time.Second), |
||||
} |
||||
config.Config = &pool.Config{ |
||||
Active: 10, |
||||
Idle: 5, |
||||
IdleTimeout: xtime.Duration(90 * time.Second), |
||||
} |
||||
testPool = NewPool(config) |
||||
} |
||||
|
||||
func TestMain(m *testing.M) { |
||||
testMemcacheAddr = os.Getenv("TEST_MEMCACHE_ADDR") |
||||
if testExampleAddr == "" { |
||||
log.Print("TEST_MEMCACHE_ADDR not provide skip test.") |
||||
// ignored test.
|
||||
os.Exit(0) |
||||
} |
||||
setupTestConnASCII(testMemcacheAddr) |
||||
setupTestMemcache(testMemcacheAddr) |
||||
setupTestPool(testMemcacheAddr) |
||||
// TODO: add setupexample?
|
||||
testExampleAddr = testMemcacheAddr |
||||
|
||||
ret := m.Run() |
||||
os.Exit(ret) |
||||
} |
@ -0,0 +1,300 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"reflect" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
func Test_client_Set(t *testing.T) { |
||||
type args struct { |
||||
c context.Context |
||||
item *Item |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
wantErr bool |
||||
}{ |
||||
{name: "set value", args: args{c: context.Background(), item: &Item{Key: "Test_client_Set", Value: []byte("abc")}}, wantErr: false}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if err := testMemcache.Set(tt.args.c, tt.args.item); (err != nil) != tt.wantErr { |
||||
t.Errorf("client.Set() error = %v, wantErr %v", err, tt.wantErr) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_client_Add(t *testing.T) { |
||||
type args struct { |
||||
c context.Context |
||||
item *Item |
||||
} |
||||
key := fmt.Sprintf("Test_client_Add_%d", time.Now().Unix()) |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
wantErr bool |
||||
}{ |
||||
{name: "add not exist value", args: args{c: context.Background(), item: &Item{Key: key, Value: []byte("abc")}}, wantErr: false}, |
||||
{name: "add exist value", args: args{c: context.Background(), item: &Item{Key: key, Value: []byte("abc")}}, wantErr: true}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if err := testMemcache.Add(tt.args.c, tt.args.item); (err != nil) != tt.wantErr { |
||||
t.Errorf("client.Add() error = %v, wantErr %v", err, tt.wantErr) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_client_Replace(t *testing.T) { |
||||
key := fmt.Sprintf("Test_client_Replace_%d", time.Now().Unix()) |
||||
ekey := "Test_client_Replace_exist" |
||||
testMemcache.Set(context.Background(), &Item{Key: ekey, Value: []byte("ok")}) |
||||
type args struct { |
||||
c context.Context |
||||
item *Item |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
wantErr bool |
||||
}{ |
||||
{name: "not exist value", args: args{c: context.Background(), item: &Item{Key: key, Value: []byte("abc")}}, wantErr: true}, |
||||
{name: "exist value", args: args{c: context.Background(), item: &Item{Key: ekey, Value: []byte("abc")}}, wantErr: false}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if err := testMemcache.Replace(tt.args.c, tt.args.item); (err != nil) != tt.wantErr { |
||||
t.Errorf("client.Replace() error = %v, wantErr %v", err, tt.wantErr) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_client_CompareAndSwap(t *testing.T) { |
||||
key := fmt.Sprintf("Test_client_CompareAndSwap_%d", time.Now().Unix()) |
||||
ekey := "Test_client_CompareAndSwap_k" |
||||
testMemcache.Set(context.Background(), &Item{Key: ekey, Value: []byte("old")}) |
||||
cas := testMemcache.Get(context.Background(), ekey).Item().cas |
||||
type args struct { |
||||
c context.Context |
||||
item *Item |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
wantErr bool |
||||
}{ |
||||
{name: "not exist value", args: args{c: context.Background(), item: &Item{Key: key, Value: []byte("abc")}}, wantErr: true}, |
||||
{name: "exist value", args: args{c: context.Background(), item: &Item{Key: ekey, cas: cas, Value: []byte("abc")}}, wantErr: false}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if err := testMemcache.CompareAndSwap(tt.args.c, tt.args.item); (err != nil) != tt.wantErr { |
||||
t.Errorf("client.CompareAndSwap() error = %v, wantErr %v", err, tt.wantErr) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_client_Get(t *testing.T) { |
||||
key := fmt.Sprintf("Test_client_Get_%d", time.Now().Unix()) |
||||
ekey := "Test_client_Get_k" |
||||
testMemcache.Set(context.Background(), &Item{Key: ekey, Value: []byte("old")}) |
||||
type args struct { |
||||
c context.Context |
||||
key string |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want string |
||||
wantErr bool |
||||
}{ |
||||
{name: "not exist value", args: args{c: context.Background(), key: key}, wantErr: true}, |
||||
{name: "exist value", args: args{c: context.Background(), key: ekey}, wantErr: false, want: "old"}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
var res string |
||||
if err := testMemcache.Get(tt.args.c, tt.args.key).Scan(&res); (err != nil) != tt.wantErr || res != tt.want { |
||||
t.Errorf("client.Get() = %v, want %v, got err: %v, want err: %v", err, tt.want, err, tt.wantErr) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_client_Touch(t *testing.T) { |
||||
key := fmt.Sprintf("Test_client_Touch_%d", time.Now().Unix()) |
||||
ekey := "Test_client_Touch_k" |
||||
testMemcache.Set(context.Background(), &Item{Key: ekey, Value: []byte("old")}) |
||||
type args struct { |
||||
c context.Context |
||||
key string |
||||
timeout int32 |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
wantErr bool |
||||
}{ |
||||
{name: "not exist value", args: args{c: context.Background(), key: key, timeout: 100000}, wantErr: true}, |
||||
{name: "exist value", args: args{c: context.Background(), key: ekey, timeout: 100000}, wantErr: false}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if err := testMemcache.Touch(tt.args.c, tt.args.key, tt.args.timeout); (err != nil) != tt.wantErr { |
||||
t.Errorf("client.Touch() error = %v, wantErr %v", err, tt.wantErr) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_client_Delete(t *testing.T) { |
||||
key := fmt.Sprintf("Test_client_Delete_%d", time.Now().Unix()) |
||||
ekey := "Test_client_Delete_k" |
||||
testMemcache.Set(context.Background(), &Item{Key: ekey, Value: []byte("old")}) |
||||
type args struct { |
||||
c context.Context |
||||
key string |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
wantErr bool |
||||
}{ |
||||
{name: "not exist value", args: args{c: context.Background(), key: key}, wantErr: true}, |
||||
{name: "exist value", args: args{c: context.Background(), key: ekey}, wantErr: false}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if err := testMemcache.Delete(tt.args.c, tt.args.key); (err != nil) != tt.wantErr { |
||||
t.Errorf("client.Delete() error = %v, wantErr %v", err, tt.wantErr) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_client_Increment(t *testing.T) { |
||||
key := fmt.Sprintf("Test_client_Increment_%d", time.Now().Unix()) |
||||
ekey := "Test_client_Increment_k" |
||||
testMemcache.Set(context.Background(), &Item{Key: ekey, Value: []byte("1")}) |
||||
type args struct { |
||||
c context.Context |
||||
key string |
||||
delta uint64 |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
wantNewValue uint64 |
||||
wantErr bool |
||||
}{ |
||||
{name: "not exist value", args: args{c: context.Background(), key: key, delta: 10}, wantErr: true}, |
||||
{name: "exist value", args: args{c: context.Background(), key: ekey, delta: 10}, wantErr: false, wantNewValue: 11}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
gotNewValue, err := testMemcache.Increment(tt.args.c, tt.args.key, tt.args.delta) |
||||
if (err != nil) != tt.wantErr { |
||||
t.Errorf("client.Increment() error = %v, wantErr %v", err, tt.wantErr) |
||||
return |
||||
} |
||||
if gotNewValue != tt.wantNewValue { |
||||
t.Errorf("client.Increment() = %v, want %v", gotNewValue, tt.wantNewValue) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_client_Decrement(t *testing.T) { |
||||
key := fmt.Sprintf("Test_client_Decrement_%d", time.Now().Unix()) |
||||
ekey := "Test_client_Decrement_k" |
||||
testMemcache.Set(context.Background(), &Item{Key: ekey, Value: []byte("100")}) |
||||
type args struct { |
||||
c context.Context |
||||
key string |
||||
delta uint64 |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
wantNewValue uint64 |
||||
wantErr bool |
||||
}{ |
||||
{name: "not exist value", args: args{c: context.Background(), key: key, delta: 10}, wantErr: true}, |
||||
{name: "exist value", args: args{c: context.Background(), key: ekey, delta: 10}, wantErr: false, wantNewValue: 90}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
gotNewValue, err := testMemcache.Decrement(tt.args.c, tt.args.key, tt.args.delta) |
||||
if (err != nil) != tt.wantErr { |
||||
t.Errorf("client.Decrement() error = %v, wantErr %v", err, tt.wantErr) |
||||
return |
||||
} |
||||
if gotNewValue != tt.wantNewValue { |
||||
t.Errorf("client.Decrement() = %v, want %v", gotNewValue, tt.wantNewValue) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func Test_client_GetMulti(t *testing.T) { |
||||
key := fmt.Sprintf("Test_client_GetMulti_%d", time.Now().Unix()) |
||||
ekey1 := "Test_client_GetMulti_k1" |
||||
ekey2 := "Test_client_GetMulti_k2" |
||||
testMemcache.Set(context.Background(), &Item{Key: ekey1, Value: []byte("1")}) |
||||
testMemcache.Set(context.Background(), &Item{Key: ekey2, Value: []byte("2")}) |
||||
keys := []string{key, ekey1, ekey2} |
||||
rows, err := testMemcache.GetMulti(context.Background(), keys) |
||||
if err != nil { |
||||
t.Errorf("client.GetMulti() error = %v, wantErr %v", err, nil) |
||||
} |
||||
tests := []struct { |
||||
key string |
||||
wantNewValue string |
||||
wantErr bool |
||||
nilItem bool |
||||
}{ |
||||
{key: ekey1, wantErr: false, wantNewValue: "1", nilItem: false}, |
||||
{key: ekey2, wantErr: false, wantNewValue: "2", nilItem: false}, |
||||
{key: key, wantErr: true, nilItem: true}, |
||||
} |
||||
if reflect.DeepEqual(keys, rows.Keys()) { |
||||
t.Errorf("got %v, expect: %v", rows.Keys(), keys) |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.key, func(t *testing.T) { |
||||
var gotNewValue string |
||||
err = rows.Scan(tt.key, &gotNewValue) |
||||
if (err != nil) != tt.wantErr { |
||||
t.Errorf("rows.Scan() error = %v, wantErr %v", err, tt.wantErr) |
||||
return |
||||
} |
||||
if gotNewValue != tt.wantNewValue { |
||||
t.Errorf("rows.Value() = %v, want %v", gotNewValue, tt.wantNewValue) |
||||
} |
||||
if (rows.Item(tt.key) == nil) != tt.nilItem { |
||||
t.Errorf("rows.Item() = %v, want %v", rows.Item(tt.key) == nil, tt.nilItem) |
||||
} |
||||
}) |
||||
} |
||||
err = rows.Close() |
||||
if err != nil { |
||||
t.Errorf("client.Replies.Close() error = %v, wantErr %v", err, nil) |
||||
} |
||||
} |
||||
|
||||
func Test_client_Conn(t *testing.T) { |
||||
conn := testMemcache.Conn(context.Background()) |
||||
defer conn.Close() |
||||
if conn == nil { |
||||
t.Errorf("expect get conn, get nil") |
||||
} |
||||
} |
@ -1,59 +0,0 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"context" |
||||
) |
||||
|
||||
// MockErr for unit test.
|
||||
type MockErr struct { |
||||
Error error |
||||
} |
||||
|
||||
var _ Conn = MockErr{} |
||||
|
||||
// MockWith return a mock conn.
|
||||
func MockWith(err error) MockErr { |
||||
return MockErr{Error: err} |
||||
} |
||||
|
||||
// Err .
|
||||
func (m MockErr) Err() error { return m.Error } |
||||
|
||||
// Close .
|
||||
func (m MockErr) Close() error { return m.Error } |
||||
|
||||
// Add .
|
||||
func (m MockErr) Add(item *Item) error { return m.Error } |
||||
|
||||
// Set .
|
||||
func (m MockErr) Set(item *Item) error { return m.Error } |
||||
|
||||
// Replace .
|
||||
func (m MockErr) Replace(item *Item) error { return m.Error } |
||||
|
||||
// CompareAndSwap .
|
||||
func (m MockErr) CompareAndSwap(item *Item) error { return m.Error } |
||||
|
||||
// Get .
|
||||
func (m MockErr) Get(key string) (*Item, error) { return nil, m.Error } |
||||
|
||||
// GetMulti .
|
||||
func (m MockErr) GetMulti(keys []string) (map[string]*Item, error) { return nil, m.Error } |
||||
|
||||
// Touch .
|
||||
func (m MockErr) Touch(key string, timeout int32) error { return m.Error } |
||||
|
||||
// Delete .
|
||||
func (m MockErr) Delete(key string) error { return m.Error } |
||||
|
||||
// Increment .
|
||||
func (m MockErr) Increment(key string, delta uint64) (uint64, error) { return 0, m.Error } |
||||
|
||||
// Decrement .
|
||||
func (m MockErr) Decrement(key string, delta uint64) (uint64, error) { return 0, m.Error } |
||||
|
||||
// Scan .
|
||||
func (m MockErr) Scan(item *Item, v interface{}) error { return m.Error } |
||||
|
||||
// WithContext .
|
||||
func (m MockErr) WithContext(ctx context.Context) Conn { return m } |
@ -1,197 +0,0 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"context" |
||||
"io" |
||||
"time" |
||||
|
||||
"github.com/bilibili/kratos/pkg/container/pool" |
||||
"github.com/bilibili/kratos/pkg/stat" |
||||
xtime "github.com/bilibili/kratos/pkg/time" |
||||
) |
||||
|
||||
var stats = stat.Cache |
||||
|
||||
// Config memcache config.
|
||||
type Config struct { |
||||
*pool.Config |
||||
|
||||
Name string // memcache name, for trace
|
||||
Proto string |
||||
Addr string |
||||
DialTimeout xtime.Duration |
||||
ReadTimeout xtime.Duration |
||||
WriteTimeout xtime.Duration |
||||
} |
||||
|
||||
// Pool memcache connection pool struct.
|
||||
type Pool struct { |
||||
p pool.Pool |
||||
c *Config |
||||
} |
||||
|
||||
// NewPool new a memcache conn pool.
|
||||
func NewPool(c *Config) (p *Pool) { |
||||
if c.DialTimeout <= 0 || c.ReadTimeout <= 0 || c.WriteTimeout <= 0 { |
||||
panic("must config memcache timeout") |
||||
} |
||||
p1 := pool.NewList(c.Config) |
||||
cnop := DialConnectTimeout(time.Duration(c.DialTimeout)) |
||||
rdop := DialReadTimeout(time.Duration(c.ReadTimeout)) |
||||
wrop := DialWriteTimeout(time.Duration(c.WriteTimeout)) |
||||
p1.New = func(ctx context.Context) (io.Closer, error) { |
||||
conn, err := Dial(c.Proto, c.Addr, cnop, rdop, wrop) |
||||
return &traceConn{Conn: conn, address: c.Addr}, err |
||||
} |
||||
p = &Pool{p: p1, c: c} |
||||
return |
||||
} |
||||
|
||||
// Get gets a connection. The application must close the returned connection.
|
||||
// This method always returns a valid connection so that applications can defer
|
||||
// error handling to the first use of the connection. If there is an error
|
||||
// getting an underlying connection, then the connection Err, Do, Send, Flush
|
||||
// and Receive methods return that error.
|
||||
func (p *Pool) Get(ctx context.Context) Conn { |
||||
c, err := p.p.Get(ctx) |
||||
if err != nil { |
||||
return errorConnection{err} |
||||
} |
||||
c1, _ := c.(Conn) |
||||
return &pooledConnection{p: p, c: c1.WithContext(ctx), ctx: ctx} |
||||
} |
||||
|
||||
// Close release the resources used by the pool.
|
||||
func (p *Pool) Close() error { |
||||
return p.p.Close() |
||||
} |
||||
|
||||
type pooledConnection struct { |
||||
p *Pool |
||||
c Conn |
||||
ctx context.Context |
||||
} |
||||
|
||||
func pstat(key string, t time.Time, err error) { |
||||
stats.Timing(key, int64(time.Since(t)/time.Millisecond)) |
||||
if err != nil { |
||||
if msg := formatErr(err); msg != "" { |
||||
stats.Incr("memcache", msg) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (pc *pooledConnection) Close() error { |
||||
c := pc.c |
||||
if _, ok := c.(errorConnection); ok { |
||||
return nil |
||||
} |
||||
pc.c = errorConnection{ErrConnClosed} |
||||
pc.p.p.Put(context.Background(), c, c.Err() != nil) |
||||
return nil |
||||
} |
||||
|
||||
func (pc *pooledConnection) Err() error { |
||||
return pc.c.Err() |
||||
} |
||||
|
||||
func (pc *pooledConnection) Set(item *Item) (err error) { |
||||
now := time.Now() |
||||
err = pc.c.Set(item) |
||||
pstat("memcache:set", now, err) |
||||
return |
||||
} |
||||
|
||||
func (pc *pooledConnection) Add(item *Item) (err error) { |
||||
now := time.Now() |
||||
err = pc.c.Add(item) |
||||
pstat("memcache:add", now, err) |
||||
return |
||||
} |
||||
|
||||
func (pc *pooledConnection) Replace(item *Item) (err error) { |
||||
now := time.Now() |
||||
err = pc.c.Replace(item) |
||||
pstat("memcache:replace", now, err) |
||||
return |
||||
} |
||||
|
||||
func (pc *pooledConnection) CompareAndSwap(item *Item) (err error) { |
||||
now := time.Now() |
||||
err = pc.c.CompareAndSwap(item) |
||||
pstat("memcache:cas", now, err) |
||||
return |
||||
} |
||||
|
||||
func (pc *pooledConnection) Get(key string) (r *Item, err error) { |
||||
now := time.Now() |
||||
r, err = pc.c.Get(key) |
||||
pstat("memcache:get", now, err) |
||||
return |
||||
} |
||||
|
||||
func (pc *pooledConnection) GetMulti(keys []string) (res map[string]*Item, err error) { |
||||
// if keys is empty slice returns empty map direct
|
||||
if len(keys) == 0 { |
||||
return make(map[string]*Item), nil |
||||
} |
||||
now := time.Now() |
||||
res, err = pc.c.GetMulti(keys) |
||||
pstat("memcache:gets", now, err) |
||||
return |
||||
} |
||||
|
||||
func (pc *pooledConnection) Touch(key string, timeout int32) (err error) { |
||||
now := time.Now() |
||||
err = pc.c.Touch(key, timeout) |
||||
pstat("memcache:touch", now, err) |
||||
return |
||||
} |
||||
|
||||
func (pc *pooledConnection) Scan(item *Item, v interface{}) error { |
||||
return pc.c.Scan(item, v) |
||||
} |
||||
|
||||
func (pc *pooledConnection) WithContext(ctx context.Context) Conn { |
||||
// TODO: set context
|
||||
pc.ctx = ctx |
||||
return pc |
||||
} |
||||
|
||||
func (pc *pooledConnection) Delete(key string) (err error) { |
||||
now := time.Now() |
||||
err = pc.c.Delete(key) |
||||
pstat("memcache:delete", now, err) |
||||
return |
||||
} |
||||
|
||||
func (pc *pooledConnection) Increment(key string, delta uint64) (newValue uint64, err error) { |
||||
now := time.Now() |
||||
newValue, err = pc.c.Increment(key, delta) |
||||
pstat("memcache:increment", now, err) |
||||
return |
||||
} |
||||
|
||||
func (pc *pooledConnection) Decrement(key string, delta uint64) (newValue uint64, err error) { |
||||
now := time.Now() |
||||
newValue, err = pc.c.Decrement(key, delta) |
||||
pstat("memcache:decrement", now, err) |
||||
return |
||||
} |
||||
|
||||
type errorConnection struct{ err error } |
||||
|
||||
func (ec errorConnection) Err() error { return ec.err } |
||||
func (ec errorConnection) Close() error { return ec.err } |
||||
func (ec errorConnection) Add(item *Item) error { return ec.err } |
||||
func (ec errorConnection) Set(item *Item) error { return ec.err } |
||||
func (ec errorConnection) Replace(item *Item) error { return ec.err } |
||||
func (ec errorConnection) CompareAndSwap(item *Item) error { return ec.err } |
||||
func (ec errorConnection) Get(key string) (*Item, error) { return nil, ec.err } |
||||
func (ec errorConnection) GetMulti(keys []string) (map[string]*Item, error) { return nil, ec.err } |
||||
func (ec errorConnection) Touch(key string, timeout int32) error { return ec.err } |
||||
func (ec errorConnection) Delete(key string) error { return ec.err } |
||||
func (ec errorConnection) Increment(key string, delta uint64) (uint64, error) { return 0, ec.err } |
||||
func (ec errorConnection) Decrement(key string, delta uint64) (uint64, error) { return 0, ec.err } |
||||
func (ec errorConnection) Scan(item *Item, v interface{}) error { return ec.err } |
||||
func (ec errorConnection) WithContext(ctx context.Context) Conn { return ec } |
@ -0,0 +1,204 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"io" |
||||
"time" |
||||
|
||||
"github.com/bilibili/kratos/pkg/container/pool" |
||||
"github.com/bilibili/kratos/pkg/stat" |
||||
) |
||||
|
||||
var stats = stat.Cache |
||||
|
||||
// Pool memcache connection pool struct.
|
||||
// Deprecated: Use Memcache instead
|
||||
type Pool struct { |
||||
p pool.Pool |
||||
c *Config |
||||
} |
||||
|
||||
// NewPool new a memcache conn pool.
|
||||
// Deprecated: Use New instead
|
||||
func NewPool(cfg *Config) (p *Pool) { |
||||
if cfg.DialTimeout <= 0 || cfg.ReadTimeout <= 0 || cfg.WriteTimeout <= 0 { |
||||
panic("must config memcache timeout") |
||||
} |
||||
p1 := pool.NewList(cfg.Config) |
||||
cnop := DialConnectTimeout(time.Duration(cfg.DialTimeout)) |
||||
rdop := DialReadTimeout(time.Duration(cfg.ReadTimeout)) |
||||
wrop := DialWriteTimeout(time.Duration(cfg.WriteTimeout)) |
||||
p1.New = func(ctx context.Context) (io.Closer, error) { |
||||
conn, err := Dial(cfg.Proto, cfg.Addr, cnop, rdop, wrop) |
||||
return newTraceConn(conn, fmt.Sprintf("%s://%s", cfg.Proto, cfg.Addr)), err |
||||
} |
||||
p = &Pool{p: p1, c: cfg} |
||||
return |
||||
} |
||||
|
||||
// Get gets a connection. The application must close the returned connection.
|
||||
// This method always returns a valid connection so that applications can defer
|
||||
// error handling to the first use of the connection. If there is an error
|
||||
// getting an underlying connection, then the connection Err, Do, Send, Flush
|
||||
// and Receive methods return that error.
|
||||
func (p *Pool) Get(ctx context.Context) Conn { |
||||
c, err := p.p.Get(ctx) |
||||
if err != nil { |
||||
return errConn{err} |
||||
} |
||||
c1, _ := c.(Conn) |
||||
return &poolConn{p: p, c: c1, ctx: ctx} |
||||
} |
||||
|
||||
// Close release the resources used by the pool.
|
||||
func (p *Pool) Close() error { |
||||
return p.p.Close() |
||||
} |
||||
|
||||
type poolConn struct { |
||||
c Conn |
||||
p *Pool |
||||
ctx context.Context |
||||
} |
||||
|
||||
func pstat(key string, t time.Time, err error) { |
||||
stats.Timing(key, int64(time.Since(t)/time.Millisecond)) |
||||
if err != nil { |
||||
if msg := formatErr(err); msg != "" { |
||||
stats.Incr("memcache", msg) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (pc *poolConn) Close() error { |
||||
c := pc.c |
||||
if _, ok := c.(errConn); ok { |
||||
return nil |
||||
} |
||||
pc.c = errConn{ErrConnClosed} |
||||
pc.p.p.Put(context.Background(), c, c.Err() != nil) |
||||
return nil |
||||
} |
||||
|
||||
func (pc *poolConn) Err() error { |
||||
return pc.c.Err() |
||||
} |
||||
|
||||
func (pc *poolConn) Set(item *Item) (err error) { |
||||
return pc.c.SetContext(pc.ctx, item) |
||||
} |
||||
|
||||
func (pc *poolConn) Add(item *Item) (err error) { |
||||
return pc.AddContext(pc.ctx, item) |
||||
} |
||||
|
||||
func (pc *poolConn) Replace(item *Item) (err error) { |
||||
return pc.ReplaceContext(pc.ctx, item) |
||||
} |
||||
|
||||
func (pc *poolConn) CompareAndSwap(item *Item) (err error) { |
||||
return pc.CompareAndSwapContext(pc.ctx, item) |
||||
} |
||||
|
||||
func (pc *poolConn) Get(key string) (r *Item, err error) { |
||||
return pc.c.GetContext(pc.ctx, key) |
||||
} |
||||
|
||||
func (pc *poolConn) GetMulti(keys []string) (res map[string]*Item, err error) { |
||||
return pc.c.GetMultiContext(pc.ctx, keys) |
||||
} |
||||
|
||||
func (pc *poolConn) Touch(key string, timeout int32) (err error) { |
||||
return pc.c.TouchContext(pc.ctx, key, timeout) |
||||
} |
||||
|
||||
func (pc *poolConn) Scan(item *Item, v interface{}) error { |
||||
return pc.c.Scan(item, v) |
||||
} |
||||
|
||||
func (pc *poolConn) Delete(key string) (err error) { |
||||
return pc.c.DeleteContext(pc.ctx, key) |
||||
} |
||||
|
||||
func (pc *poolConn) Increment(key string, delta uint64) (newValue uint64, err error) { |
||||
return pc.c.IncrementContext(pc.ctx, key, delta) |
||||
} |
||||
|
||||
func (pc *poolConn) Decrement(key string, delta uint64) (newValue uint64, err error) { |
||||
return pc.c.DecrementContext(pc.ctx, key, delta) |
||||
} |
||||
|
||||
func (pc *poolConn) AddContext(ctx context.Context, item *Item) error { |
||||
now := time.Now() |
||||
err := pc.c.AddContext(ctx, item) |
||||
pstat("memcache:add", now, err) |
||||
return err |
||||
} |
||||
|
||||
func (pc *poolConn) SetContext(ctx context.Context, item *Item) error { |
||||
now := time.Now() |
||||
err := pc.c.SetContext(ctx, item) |
||||
pstat("memcache:set", now, err) |
||||
return err |
||||
} |
||||
|
||||
func (pc *poolConn) ReplaceContext(ctx context.Context, item *Item) error { |
||||
now := time.Now() |
||||
err := pc.c.ReplaceContext(ctx, item) |
||||
pstat("memcache:replace", now, err) |
||||
return err |
||||
} |
||||
|
||||
func (pc *poolConn) GetContext(ctx context.Context, key string) (*Item, error) { |
||||
now := time.Now() |
||||
item, err := pc.c.Get(key) |
||||
pstat("memcache:get", now, err) |
||||
return item, err |
||||
} |
||||
|
||||
func (pc *poolConn) GetMultiContext(ctx context.Context, keys []string) (map[string]*Item, error) { |
||||
// if keys is empty slice returns empty map direct
|
||||
if len(keys) == 0 { |
||||
return make(map[string]*Item), nil |
||||
} |
||||
now := time.Now() |
||||
items, err := pc.c.GetMulti(keys) |
||||
pstat("memcache:gets", now, err) |
||||
return items, err |
||||
} |
||||
|
||||
func (pc *poolConn) DeleteContext(ctx context.Context, key string) error { |
||||
now := time.Now() |
||||
err := pc.c.Delete(key) |
||||
pstat("memcache:delete", now, err) |
||||
return err |
||||
} |
||||
|
||||
func (pc *poolConn) IncrementContext(ctx context.Context, key string, delta uint64) (uint64, error) { |
||||
now := time.Now() |
||||
newValue, err := pc.c.IncrementContext(ctx, key, delta) |
||||
pstat("memcache:increment", now, err) |
||||
return newValue, err |
||||
} |
||||
|
||||
func (pc *poolConn) DecrementContext(ctx context.Context, key string, delta uint64) (uint64, error) { |
||||
now := time.Now() |
||||
newValue, err := pc.c.DecrementContext(ctx, key, delta) |
||||
pstat("memcache:decrement", now, err) |
||||
return newValue, err |
||||
} |
||||
|
||||
func (pc *poolConn) CompareAndSwapContext(ctx context.Context, item *Item) error { |
||||
now := time.Now() |
||||
err := pc.c.CompareAndSwap(item) |
||||
pstat("memcache:cas", now, err) |
||||
return err |
||||
} |
||||
|
||||
func (pc *poolConn) TouchContext(ctx context.Context, key string, seconds int32) error { |
||||
now := time.Now() |
||||
err := pc.c.Touch(key, seconds) |
||||
pstat("memcache:touch", now, err) |
||||
return err |
||||
} |
@ -0,0 +1,545 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"bytes" |
||||
"context" |
||||
"reflect" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/bilibili/kratos/pkg/container/pool" |
||||
xtime "github.com/bilibili/kratos/pkg/time" |
||||
) |
||||
|
||||
var itempool = &Item{ |
||||
Key: "testpool", |
||||
Value: []byte("testpool"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
cas: 0, |
||||
} |
||||
var itempool2 = &Item{ |
||||
Key: "test_count", |
||||
Value: []byte("0"), |
||||
Flags: 0, |
||||
Expiration: 1000, |
||||
cas: 0, |
||||
} |
||||
|
||||
type testObject struct { |
||||
Mid int64 |
||||
Value []byte |
||||
} |
||||
|
||||
var largeValue = &Item{ |
||||
Key: "large_value", |
||||
Flags: FlagGOB | FlagGzip, |
||||
Expiration: 1000, |
||||
cas: 0, |
||||
} |
||||
|
||||
var largeValueBoundary = &Item{ |
||||
Key: "large_value", |
||||
Flags: FlagGOB | FlagGzip, |
||||
Expiration: 1000, |
||||
cas: 0, |
||||
} |
||||
|
||||
func TestPoolSet(t *testing.T) { |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
// set
|
||||
if err := conn.Set(itempool); err != nil { |
||||
t.Errorf("memcache: set error(%v)", err) |
||||
} else { |
||||
t.Logf("memcache: set value: %s", itempool.Value) |
||||
} |
||||
if err := conn.Close(); err != nil { |
||||
t.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestPoolGet(t *testing.T) { |
||||
key := "testpool" |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
// get
|
||||
if res, err := conn.Get(key); err != nil { |
||||
t.Errorf("memcache: get error(%v)", err) |
||||
} else { |
||||
t.Logf("memcache: get value: %s", res.Value) |
||||
} |
||||
if _, err := conn.Get("not_found"); err != ErrNotFound { |
||||
t.Errorf("memcache: expceted err is not found but got: %v", err) |
||||
} |
||||
if err := conn.Close(); err != nil { |
||||
t.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestPoolGetMulti(t *testing.T) { |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
s := []string{"testpool", "test1"} |
||||
// get
|
||||
if res, err := conn.GetMulti(s); err != nil { |
||||
t.Errorf("memcache: gets error(%v)", err) |
||||
} else { |
||||
t.Logf("memcache: gets value: %d", len(res)) |
||||
} |
||||
if err := conn.Close(); err != nil { |
||||
t.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestPoolTouch(t *testing.T) { |
||||
key := "testpool" |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
// touch
|
||||
if err := conn.Touch(key, 10); err != nil { |
||||
t.Errorf("memcache: touch error(%v)", err) |
||||
} |
||||
if err := conn.Close(); err != nil { |
||||
t.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestPoolIncrement(t *testing.T) { |
||||
key := "test_count" |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
// set
|
||||
if err := conn.Set(itempool2); err != nil { |
||||
t.Errorf("memcache: set error(%v)", err) |
||||
} else { |
||||
t.Logf("memcache: set value: 0") |
||||
} |
||||
// incr
|
||||
if res, err := conn.Increment(key, 1); err != nil { |
||||
t.Errorf("memcache: incr error(%v)", err) |
||||
} else { |
||||
t.Logf("memcache: incr n: %d", res) |
||||
if res != 1 { |
||||
t.Errorf("memcache: expected res=1 but got %d", res) |
||||
} |
||||
} |
||||
// decr
|
||||
if res, err := conn.Decrement(key, 1); err != nil { |
||||
t.Errorf("memcache: decr error(%v)", err) |
||||
} else { |
||||
t.Logf("memcache: decr n: %d", res) |
||||
if res != 0 { |
||||
t.Errorf("memcache: expected res=0 but got %d", res) |
||||
} |
||||
} |
||||
if err := conn.Close(); err != nil { |
||||
t.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestPoolErr(t *testing.T) { |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
if err := conn.Close(); err != nil { |
||||
t.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
if err := conn.Err(); err == nil { |
||||
t.Errorf("memcache: err not nil") |
||||
} else { |
||||
t.Logf("memcache: err: %v", err) |
||||
} |
||||
} |
||||
|
||||
func TestPoolCompareAndSwap(t *testing.T) { |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
key := "testpool" |
||||
//cas
|
||||
if r, err := conn.Get(key); err != nil { |
||||
t.Errorf("conn.Get() error(%v)", err) |
||||
} else { |
||||
r.Value = []byte("shit") |
||||
if err := conn.CompareAndSwap(r); err != nil { |
||||
t.Errorf("conn.Get() error(%v)", err) |
||||
} |
||||
r, _ := conn.Get("testpool") |
||||
if r.Key != "testpool" || !bytes.Equal(r.Value, []byte("shit")) || r.Flags != 0 { |
||||
t.Error("conn.Get() error, value") |
||||
} |
||||
if err := conn.Close(); err != nil { |
||||
t.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestPoolDel(t *testing.T) { |
||||
key := "testpool" |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
// delete
|
||||
if err := conn.Delete(key); err != nil { |
||||
t.Errorf("memcache: delete error(%v)", err) |
||||
} else { |
||||
t.Logf("memcache: delete key: %s", key) |
||||
} |
||||
if err := conn.Close(); err != nil { |
||||
t.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkMemcache(b *testing.B) { |
||||
c := &Config{ |
||||
Name: "test", |
||||
Proto: "tcp", |
||||
Addr: testMemcacheAddr, |
||||
DialTimeout: xtime.Duration(time.Second), |
||||
ReadTimeout: xtime.Duration(time.Second), |
||||
WriteTimeout: xtime.Duration(time.Second), |
||||
} |
||||
c.Config = &pool.Config{ |
||||
Active: 10, |
||||
Idle: 5, |
||||
IdleTimeout: xtime.Duration(90 * time.Second), |
||||
} |
||||
testPool = NewPool(c) |
||||
b.ResetTimer() |
||||
b.RunParallel(func(pb *testing.PB) { |
||||
for pb.Next() { |
||||
conn := testPool.Get(context.Background()) |
||||
if err := conn.Close(); err != nil { |
||||
b.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
} |
||||
}) |
||||
if err := testPool.Close(); err != nil { |
||||
b.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestPoolSetLargeValue(t *testing.T) { |
||||
var b bytes.Buffer |
||||
for i := 0; i < 4000000; i++ { |
||||
b.WriteByte(1) |
||||
} |
||||
obj := &testObject{} |
||||
obj.Mid = 1000 |
||||
obj.Value = b.Bytes() |
||||
largeValue.Object = obj |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
// set
|
||||
if err := conn.Set(largeValue); err != nil { |
||||
t.Errorf("memcache: set error(%v)", err) |
||||
} |
||||
if err := conn.Close(); err != nil { |
||||
t.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestPoolGetLargeValue(t *testing.T) { |
||||
key := largeValue.Key |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
// get
|
||||
var err error |
||||
if _, err = conn.Get(key); err != nil { |
||||
t.Errorf("memcache: large get error(%+v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestPoolGetMultiLargeValue(t *testing.T) { |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
s := []string{largeValue.Key, largeValue.Key} |
||||
// get
|
||||
if res, err := conn.GetMulti(s); err != nil { |
||||
t.Errorf("memcache: gets error(%v)", err) |
||||
} else { |
||||
t.Logf("memcache: gets value: %d", len(res)) |
||||
} |
||||
if err := conn.Close(); err != nil { |
||||
t.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestPoolSetLargeValueBoundary(t *testing.T) { |
||||
var b bytes.Buffer |
||||
for i := 0; i < _largeValue; i++ { |
||||
b.WriteByte(1) |
||||
} |
||||
obj := &testObject{} |
||||
obj.Mid = 1000 |
||||
obj.Value = b.Bytes() |
||||
largeValueBoundary.Object = obj |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
// set
|
||||
if err := conn.Set(largeValueBoundary); err != nil { |
||||
t.Errorf("memcache: set error(%v)", err) |
||||
} |
||||
if err := conn.Close(); err != nil { |
||||
t.Errorf("memcache: close error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestPoolGetLargeValueBoundary(t *testing.T) { |
||||
key := largeValueBoundary.Key |
||||
conn := testPool.Get(context.Background()) |
||||
defer conn.Close() |
||||
// get
|
||||
var err error |
||||
if _, err = conn.Get(key); err != nil { |
||||
t.Errorf("memcache: large get error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestPoolAdd(t *testing.T) { |
||||
var ( |
||||
key = "test_add" |
||||
item = &Item{ |
||||
Key: key, |
||||
Value: []byte("0"), |
||||
Flags: 0, |
||||
Expiration: 60, |
||||
cas: 0, |
||||
} |
||||
conn = testPool.Get(context.Background()) |
||||
) |
||||
defer conn.Close() |
||||
conn.Delete(key) |
||||
if err := conn.Add(item); err != nil { |
||||
t.Errorf("memcache: add error(%v)", err) |
||||
} |
||||
if err := conn.Add(item); err != ErrNotStored { |
||||
t.Errorf("memcache: add error(%v)", err) |
||||
} |
||||
} |
||||
|
||||
func TestNewPool(t *testing.T) { |
||||
type args struct { |
||||
cfg *Config |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
wantErr error |
||||
wantPanic bool |
||||
}{ |
||||
{ |
||||
"NewPoolIllegalDialTimeout", |
||||
args{ |
||||
&Config{ |
||||
Name: "test_illegal_dial_timeout", |
||||
Proto: "tcp", |
||||
Addr: testMemcacheAddr, |
||||
DialTimeout: xtime.Duration(-time.Second), |
||||
ReadTimeout: xtime.Duration(time.Second), |
||||
WriteTimeout: xtime.Duration(time.Second), |
||||
}, |
||||
}, |
||||
nil, |
||||
true, |
||||
}, |
||||
{ |
||||
"NewPoolIllegalReadTimeout", |
||||
args{ |
||||
&Config{ |
||||
Name: "test_illegal_read_timeout", |
||||
Proto: "tcp", |
||||
Addr: testMemcacheAddr, |
||||
DialTimeout: xtime.Duration(time.Second), |
||||
ReadTimeout: xtime.Duration(-time.Second), |
||||
WriteTimeout: xtime.Duration(time.Second), |
||||
}, |
||||
}, |
||||
nil, |
||||
true, |
||||
}, |
||||
{ |
||||
"NewPoolIllegalWriteTimeout", |
||||
args{ |
||||
&Config{ |
||||
Name: "test_illegal_write_timeout", |
||||
Proto: "tcp", |
||||
Addr: testMemcacheAddr, |
||||
DialTimeout: xtime.Duration(time.Second), |
||||
ReadTimeout: xtime.Duration(time.Second), |
||||
WriteTimeout: xtime.Duration(-time.Second), |
||||
}, |
||||
}, |
||||
nil, |
||||
true, |
||||
}, |
||||
{ |
||||
"NewPool", |
||||
args{ |
||||
&Config{ |
||||
Name: "test_new", |
||||
Proto: "tcp", |
||||
Addr: testMemcacheAddr, |
||||
DialTimeout: xtime.Duration(time.Second), |
||||
ReadTimeout: xtime.Duration(time.Second), |
||||
WriteTimeout: xtime.Duration(time.Second), |
||||
}, |
||||
}, |
||||
nil, |
||||
true, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
defer func() { |
||||
r := recover() |
||||
if (r != nil) != tt.wantPanic { |
||||
t.Errorf("wantPanic recover = %v, wantPanic = %v", r, tt.wantPanic) |
||||
} |
||||
}() |
||||
|
||||
if gotP := NewPool(tt.args.cfg); gotP == nil { |
||||
t.Error("NewPool() failed, got nil") |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestPool_Get(t *testing.T) { |
||||
|
||||
type args struct { |
||||
ctx context.Context |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
p *Pool |
||||
args args |
||||
wantErr bool |
||||
n int |
||||
}{ |
||||
{ |
||||
"Get", |
||||
NewPool(&Config{ |
||||
Config: &pool.Config{ |
||||
Active: 3, |
||||
Idle: 2, |
||||
}, |
||||
Name: "test_get", |
||||
Proto: "tcp", |
||||
Addr: testMemcacheAddr, |
||||
DialTimeout: xtime.Duration(time.Second), |
||||
ReadTimeout: xtime.Duration(time.Second), |
||||
WriteTimeout: xtime.Duration(time.Second), |
||||
}), |
||||
args{context.TODO()}, |
||||
false, |
||||
3, |
||||
}, |
||||
{ |
||||
"GetExceededPoolSize", |
||||
NewPool(&Config{ |
||||
Config: &pool.Config{ |
||||
Active: 3, |
||||
Idle: 2, |
||||
}, |
||||
Name: "test_get_out", |
||||
Proto: "tcp", |
||||
Addr: testMemcacheAddr, |
||||
DialTimeout: xtime.Duration(time.Second), |
||||
ReadTimeout: xtime.Duration(time.Second), |
||||
WriteTimeout: xtime.Duration(time.Second), |
||||
}), |
||||
args{context.TODO()}, |
||||
true, |
||||
6, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
for i := 1; i <= tt.n; i++ { |
||||
got := tt.p.Get(tt.args.ctx) |
||||
if reflect.TypeOf(got) == reflect.TypeOf(errConn{}) { |
||||
if !tt.wantErr { |
||||
t.Errorf("got errConn, export Conn") |
||||
} |
||||
return |
||||
} else { |
||||
if tt.wantErr { |
||||
if i > tt.p.c.Active { |
||||
t.Errorf("got Conn, export errConn") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestPool_Close(t *testing.T) { |
||||
|
||||
type args struct { |
||||
ctx context.Context |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
p *Pool |
||||
args args |
||||
wantErr bool |
||||
g int |
||||
c int |
||||
}{ |
||||
{ |
||||
"Close", |
||||
NewPool(&Config{ |
||||
Config: &pool.Config{ |
||||
Active: 1, |
||||
Idle: 1, |
||||
}, |
||||
Name: "test_get", |
||||
Proto: "tcp", |
||||
Addr: testMemcacheAddr, |
||||
DialTimeout: xtime.Duration(time.Second), |
||||
ReadTimeout: xtime.Duration(time.Second), |
||||
WriteTimeout: xtime.Duration(time.Second), |
||||
}), |
||||
args{context.TODO()}, |
||||
false, |
||||
3, |
||||
3, |
||||
}, |
||||
{ |
||||
"CloseExceededPoolSize", |
||||
NewPool(&Config{ |
||||
Config: &pool.Config{ |
||||
Active: 1, |
||||
Idle: 1, |
||||
}, |
||||
Name: "test_get_out", |
||||
Proto: "tcp", |
||||
Addr: testMemcacheAddr, |
||||
DialTimeout: xtime.Duration(time.Second), |
||||
ReadTimeout: xtime.Duration(time.Second), |
||||
WriteTimeout: xtime.Duration(time.Second), |
||||
}), |
||||
args{context.TODO()}, |
||||
true, |
||||
5, |
||||
3, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
for i := 1; i <= tt.g; i++ { |
||||
got := tt.p.Get(tt.args.ctx) |
||||
if err := got.Close(); err != nil { |
||||
if !tt.wantErr { |
||||
t.Error(err) |
||||
} |
||||
} |
||||
if i <= tt.c { |
||||
if err := got.Close(); err != nil { |
||||
t.Error(err) |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,48 @@ |
||||
load( |
||||
"@io_bazel_rules_go//go:def.bzl", |
||||
"go_library", |
||||
) |
||||
load( |
||||
"@io_bazel_rules_go//proto:def.bzl", |
||||
"go_proto_library", |
||||
) |
||||
|
||||
go_library( |
||||
name = "go_default_library", |
||||
srcs = [], |
||||
embed = [":proto_go_proto"], |
||||
importpath = "go-common/library/cache/memcache/test", |
||||
tags = ["automanaged"], |
||||
visibility = ["//visibility:public"], |
||||
deps = ["@com_github_golang_protobuf//proto:go_default_library"], |
||||
) |
||||
|
||||
filegroup( |
||||
name = "package-srcs", |
||||
srcs = glob(["**"]), |
||||
tags = ["automanaged"], |
||||
visibility = ["//visibility:private"], |
||||
) |
||||
|
||||
filegroup( |
||||
name = "all-srcs", |
||||
srcs = [":package-srcs"], |
||||
tags = ["automanaged"], |
||||
visibility = ["//visibility:public"], |
||||
) |
||||
|
||||
proto_library( |
||||
name = "test_proto", |
||||
srcs = ["test.proto"], |
||||
import_prefix = "go-common/library/cache/memcache/test", |
||||
strip_import_prefix = "", |
||||
tags = ["automanaged"], |
||||
) |
||||
|
||||
go_proto_library( |
||||
name = "proto_go_proto", |
||||
compilers = ["@io_bazel_rules_go//proto:go_proto"], |
||||
importpath = "go-common/library/cache/memcache/test", |
||||
proto = ":test_proto", |
||||
tags = ["automanaged"], |
||||
) |
@ -0,0 +1,375 @@ |
||||
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||
// source: test.proto
|
||||
|
||||
/* |
||||
Package proto is a generated protocol buffer package. |
||||
|
||||
It is generated from these files: |
||||
test.proto |
||||
|
||||
It has these top-level messages: |
||||
TestItem |
||||
*/ |
||||
package proto |
||||
|
||||
import proto1 "github.com/golang/protobuf/proto" |
||||
import fmt "fmt" |
||||
import math "math" |
||||
|
||||
import io "io" |
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto1.Marshal |
||||
var _ = fmt.Errorf |
||||
var _ = math.Inf |
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto1.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type FOO int32 |
||||
|
||||
const ( |
||||
FOO_X FOO = 0 |
||||
) |
||||
|
||||
var FOO_name = map[int32]string{ |
||||
0: "X", |
||||
} |
||||
var FOO_value = map[string]int32{ |
||||
"X": 0, |
||||
} |
||||
|
||||
func (x FOO) String() string { |
||||
return proto1.EnumName(FOO_name, int32(x)) |
||||
} |
||||
func (FOO) EnumDescriptor() ([]byte, []int) { return fileDescriptorTest, []int{0} } |
||||
|
||||
type TestItem struct { |
||||
Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"` |
||||
Age int32 `protobuf:"varint,2,opt,name=Age,proto3" json:"Age,omitempty"` |
||||
} |
||||
|
||||
func (m *TestItem) Reset() { *m = TestItem{} } |
||||
func (m *TestItem) String() string { return proto1.CompactTextString(m) } |
||||
func (*TestItem) ProtoMessage() {} |
||||
func (*TestItem) Descriptor() ([]byte, []int) { return fileDescriptorTest, []int{0} } |
||||
|
||||
func (m *TestItem) GetName() string { |
||||
if m != nil { |
||||
return m.Name |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
func (m *TestItem) GetAge() int32 { |
||||
if m != nil { |
||||
return m.Age |
||||
} |
||||
return 0 |
||||
} |
||||
|
||||
func init() { |
||||
proto1.RegisterType((*TestItem)(nil), "proto.TestItem") |
||||
proto1.RegisterEnum("proto.FOO", FOO_name, FOO_value) |
||||
} |
||||
func (m *TestItem) Marshal() (dAtA []byte, err error) { |
||||
size := m.Size() |
||||
dAtA = make([]byte, size) |
||||
n, err := m.MarshalTo(dAtA) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return dAtA[:n], nil |
||||
} |
||||
|
||||
func (m *TestItem) MarshalTo(dAtA []byte) (int, error) { |
||||
var i int |
||||
_ = i |
||||
var l int |
||||
_ = l |
||||
if len(m.Name) > 0 { |
||||
dAtA[i] = 0xa |
||||
i++ |
||||
i = encodeVarintTest(dAtA, i, uint64(len(m.Name))) |
||||
i += copy(dAtA[i:], m.Name) |
||||
} |
||||
if m.Age != 0 { |
||||
dAtA[i] = 0x10 |
||||
i++ |
||||
i = encodeVarintTest(dAtA, i, uint64(m.Age)) |
||||
} |
||||
return i, nil |
||||
} |
||||
|
||||
func encodeFixed64Test(dAtA []byte, offset int, v uint64) int { |
||||
dAtA[offset] = uint8(v) |
||||
dAtA[offset+1] = uint8(v >> 8) |
||||
dAtA[offset+2] = uint8(v >> 16) |
||||
dAtA[offset+3] = uint8(v >> 24) |
||||
dAtA[offset+4] = uint8(v >> 32) |
||||
dAtA[offset+5] = uint8(v >> 40) |
||||
dAtA[offset+6] = uint8(v >> 48) |
||||
dAtA[offset+7] = uint8(v >> 56) |
||||
return offset + 8 |
||||
} |
||||
func encodeFixed32Test(dAtA []byte, offset int, v uint32) int { |
||||
dAtA[offset] = uint8(v) |
||||
dAtA[offset+1] = uint8(v >> 8) |
||||
dAtA[offset+2] = uint8(v >> 16) |
||||
dAtA[offset+3] = uint8(v >> 24) |
||||
return offset + 4 |
||||
} |
||||
func encodeVarintTest(dAtA []byte, offset int, v uint64) int { |
||||
for v >= 1<<7 { |
||||
dAtA[offset] = uint8(v&0x7f | 0x80) |
||||
v >>= 7 |
||||
offset++ |
||||
} |
||||
dAtA[offset] = uint8(v) |
||||
return offset + 1 |
||||
} |
||||
func (m *TestItem) Size() (n int) { |
||||
var l int |
||||
_ = l |
||||
l = len(m.Name) |
||||
if l > 0 { |
||||
n += 1 + l + sovTest(uint64(l)) |
||||
} |
||||
if m.Age != 0 { |
||||
n += 1 + sovTest(uint64(m.Age)) |
||||
} |
||||
return n |
||||
} |
||||
|
||||
func sovTest(x uint64) (n int) { |
||||
for { |
||||
n++ |
||||
x >>= 7 |
||||
if x == 0 { |
||||
break |
||||
} |
||||
} |
||||
return n |
||||
} |
||||
func sozTest(x uint64) (n int) { |
||||
return sovTest(uint64((x << 1) ^ uint64((int64(x) >> 63)))) |
||||
} |
||||
func (m *TestItem) Unmarshal(dAtA []byte) error { |
||||
l := len(dAtA) |
||||
iNdEx := 0 |
||||
for iNdEx < l { |
||||
preIndex := iNdEx |
||||
var wire uint64 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowTest |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
wire |= (uint64(b) & 0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
fieldNum := int32(wire >> 3) |
||||
wireType := int(wire & 0x7) |
||||
if wireType == 4 { |
||||
return fmt.Errorf("proto: TestItem: wiretype end group for non-group") |
||||
} |
||||
if fieldNum <= 0 { |
||||
return fmt.Errorf("proto: TestItem: illegal tag %d (wire type %d)", fieldNum, wire) |
||||
} |
||||
switch fieldNum { |
||||
case 1: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) |
||||
} |
||||
var stringLen uint64 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowTest |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
stringLen |= (uint64(b) & 0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
intStringLen := int(stringLen) |
||||
if intStringLen < 0 { |
||||
return ErrInvalidLengthTest |
||||
} |
||||
postIndex := iNdEx + intStringLen |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
m.Name = string(dAtA[iNdEx:postIndex]) |
||||
iNdEx = postIndex |
||||
case 2: |
||||
if wireType != 0 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Age", wireType) |
||||
} |
||||
m.Age = 0 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowTest |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
m.Age |= (int32(b) & 0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
default: |
||||
iNdEx = preIndex |
||||
skippy, err := skipTest(dAtA[iNdEx:]) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if skippy < 0 { |
||||
return ErrInvalidLengthTest |
||||
} |
||||
if (iNdEx + skippy) > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
iNdEx += skippy |
||||
} |
||||
} |
||||
|
||||
if iNdEx > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
return nil |
||||
} |
||||
func skipTest(dAtA []byte) (n int, err error) { |
||||
l := len(dAtA) |
||||
iNdEx := 0 |
||||
for iNdEx < l { |
||||
var wire uint64 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return 0, ErrIntOverflowTest |
||||
} |
||||
if iNdEx >= l { |
||||
return 0, io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
wire |= (uint64(b) & 0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
wireType := int(wire & 0x7) |
||||
switch wireType { |
||||
case 0: |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return 0, ErrIntOverflowTest |
||||
} |
||||
if iNdEx >= l { |
||||
return 0, io.ErrUnexpectedEOF |
||||
} |
||||
iNdEx++ |
||||
if dAtA[iNdEx-1] < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
return iNdEx, nil |
||||
case 1: |
||||
iNdEx += 8 |
||||
return iNdEx, nil |
||||
case 2: |
||||
var length int |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return 0, ErrIntOverflowTest |
||||
} |
||||
if iNdEx >= l { |
||||
return 0, io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
length |= (int(b) & 0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
iNdEx += length |
||||
if length < 0 { |
||||
return 0, ErrInvalidLengthTest |
||||
} |
||||
return iNdEx, nil |
||||
case 3: |
||||
for { |
||||
var innerWire uint64 |
||||
var start int = iNdEx |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return 0, ErrIntOverflowTest |
||||
} |
||||
if iNdEx >= l { |
||||
return 0, io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
innerWire |= (uint64(b) & 0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
innerWireType := int(innerWire & 0x7) |
||||
if innerWireType == 4 { |
||||
break |
||||
} |
||||
next, err := skipTest(dAtA[start:]) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
iNdEx = start + next |
||||
} |
||||
return iNdEx, nil |
||||
case 4: |
||||
return iNdEx, nil |
||||
case 5: |
||||
iNdEx += 4 |
||||
return iNdEx, nil |
||||
default: |
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType) |
||||
} |
||||
} |
||||
panic("unreachable") |
||||
} |
||||
|
||||
var ( |
||||
ErrInvalidLengthTest = fmt.Errorf("proto: negative length found during unmarshaling") |
||||
ErrIntOverflowTest = fmt.Errorf("proto: integer overflow") |
||||
) |
||||
|
||||
func init() { proto1.RegisterFile("test.proto", fileDescriptorTest) } |
||||
|
||||
var fileDescriptorTest = []byte{ |
||||
// 122 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e, |
||||
0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0x06, 0x5c, 0x1c, 0x21, 0xa9, |
||||
0xc5, 0x25, 0x9e, 0x25, 0xa9, 0xb9, 0x42, 0x42, 0x5c, 0x2c, 0x7e, 0x89, 0xb9, 0xa9, 0x12, 0x8c, |
||||
0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x90, 0x00, 0x17, 0xb3, 0x63, 0x7a, 0xaa, 0x04, 0x93, |
||||
0x02, 0xa3, 0x06, 0x6b, 0x10, 0x88, 0xa9, 0xc5, 0xc3, 0xc5, 0xec, 0xe6, 0xef, 0x2f, 0xc4, 0xca, |
||||
0xc5, 0x18, 0x21, 0xc0, 0xe0, 0x24, 0x70, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, |
||||
0x1e, 0xc9, 0x31, 0xce, 0x78, 0x2c, 0xc7, 0x90, 0xc4, 0x06, 0x36, 0xd8, 0x18, 0x10, 0x00, 0x00, |
||||
0xff, 0xff, 0x16, 0x80, 0x60, 0x15, 0x6d, 0x00, 0x00, 0x00, |
||||
} |
@ -0,0 +1,12 @@ |
||||
syntax = "proto3"; |
||||
package proto; |
||||
|
||||
enum FOO |
||||
{ |
||||
X = 0; |
||||
}; |
||||
|
||||
message TestItem{ |
||||
string Name = 1; |
||||
int32 Age = 2; |
||||
} |
@ -1,109 +0,0 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"context" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/bilibili/kratos/pkg/log" |
||||
"github.com/bilibili/kratos/pkg/net/trace" |
||||
) |
||||
|
||||
const ( |
||||
_traceFamily = "memcache" |
||||
_traceSpanKind = "client" |
||||
_traceComponentName = "library/cache/memcache" |
||||
_tracePeerService = "memcache" |
||||
_slowLogDuration = time.Millisecond * 250 |
||||
) |
||||
|
||||
type traceConn struct { |
||||
Conn |
||||
ctx context.Context |
||||
address string |
||||
} |
||||
|
||||
func (t *traceConn) setTrace(action, statement string) func(error) error { |
||||
now := time.Now() |
||||
parent, ok := trace.FromContext(t.ctx) |
||||
if !ok { |
||||
return func(err error) error { return err } |
||||
} |
||||
span := parent.Fork(_traceFamily, "Memcache:"+action) |
||||
span.SetTag( |
||||
trace.String(trace.TagSpanKind, _traceSpanKind), |
||||
trace.String(trace.TagComponent, _traceComponentName), |
||||
trace.String(trace.TagPeerService, _tracePeerService), |
||||
trace.String(trace.TagPeerAddress, t.address), |
||||
trace.String(trace.TagDBStatement, action+" "+statement), |
||||
) |
||||
return func(err error) error { |
||||
span.Finish(&err) |
||||
t := time.Since(now) |
||||
if t > _slowLogDuration { |
||||
log.Warn("%s slow log action: %s key: %s time: %v", _traceFamily, action, statement, t) |
||||
} |
||||
return err |
||||
} |
||||
} |
||||
|
||||
func (t *traceConn) WithContext(ctx context.Context) Conn { |
||||
t.ctx = ctx |
||||
t.Conn = t.Conn.WithContext(ctx) |
||||
return t |
||||
} |
||||
|
||||
func (t *traceConn) Add(item *Item) error { |
||||
finishFn := t.setTrace("Add", item.Key) |
||||
return finishFn(t.Conn.Add(item)) |
||||
} |
||||
|
||||
func (t *traceConn) Set(item *Item) error { |
||||
finishFn := t.setTrace("Set", item.Key) |
||||
return finishFn(t.Conn.Set(item)) |
||||
} |
||||
|
||||
func (t *traceConn) Replace(item *Item) error { |
||||
finishFn := t.setTrace("Replace", item.Key) |
||||
return finishFn(t.Conn.Replace(item)) |
||||
} |
||||
|
||||
func (t *traceConn) Get(key string) (*Item, error) { |
||||
finishFn := t.setTrace("Get", key) |
||||
item, err := t.Conn.Get(key) |
||||
return item, finishFn(err) |
||||
} |
||||
|
||||
func (t *traceConn) GetMulti(keys []string) (map[string]*Item, error) { |
||||
finishFn := t.setTrace("GetMulti", strings.Join(keys, " ")) |
||||
items, err := t.Conn.GetMulti(keys) |
||||
return items, finishFn(err) |
||||
} |
||||
|
||||
func (t *traceConn) Delete(key string) error { |
||||
finishFn := t.setTrace("Delete", key) |
||||
return finishFn(t.Conn.Delete(key)) |
||||
} |
||||
|
||||
func (t *traceConn) Increment(key string, delta uint64) (newValue uint64, err error) { |
||||
finishFn := t.setTrace("Increment", key+" "+strconv.FormatUint(delta, 10)) |
||||
newValue, err = t.Conn.Increment(key, delta) |
||||
return newValue, finishFn(err) |
||||
} |
||||
|
||||
func (t *traceConn) Decrement(key string, delta uint64) (newValue uint64, err error) { |
||||
finishFn := t.setTrace("Decrement", key+" "+strconv.FormatUint(delta, 10)) |
||||
newValue, err = t.Conn.Decrement(key, delta) |
||||
return newValue, finishFn(err) |
||||
} |
||||
|
||||
func (t *traceConn) CompareAndSwap(item *Item) error { |
||||
finishFn := t.setTrace("CompareAndSwap", item.Key) |
||||
return finishFn(t.Conn.CompareAndSwap(item)) |
||||
} |
||||
|
||||
func (t *traceConn) Touch(key string, seconds int32) (err error) { |
||||
finishFn := t.setTrace("Touch", key+" "+strconv.Itoa(int(seconds))) |
||||
return finishFn(t.Conn.Touch(key, seconds)) |
||||
} |
@ -0,0 +1,103 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"context" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/bilibili/kratos/pkg/log" |
||||
"github.com/bilibili/kratos/pkg/net/trace" |
||||
) |
||||
|
||||
const ( |
||||
_slowLogDuration = time.Millisecond * 250 |
||||
) |
||||
|
||||
func newTraceConn(conn Conn, address string) Conn { |
||||
tags := []trace.Tag{ |
||||
trace.String(trace.TagSpanKind, "client"), |
||||
trace.String(trace.TagComponent, "cache/memcache"), |
||||
trace.String(trace.TagPeerService, "memcache"), |
||||
trace.String(trace.TagPeerAddress, address), |
||||
} |
||||
return &traceConn{Conn: conn, tags: tags} |
||||
} |
||||
|
||||
type traceConn struct { |
||||
Conn |
||||
tags []trace.Tag |
||||
} |
||||
|
||||
func (t *traceConn) setTrace(ctx context.Context, action, statement string) func(error) error { |
||||
now := time.Now() |
||||
parent, ok := trace.FromContext(ctx) |
||||
if !ok { |
||||
return func(err error) error { return err } |
||||
} |
||||
span := parent.Fork("", "Memcache:"+action) |
||||
span.SetTag(t.tags...) |
||||
span.SetTag(trace.String(trace.TagDBStatement, action+" "+statement)) |
||||
return func(err error) error { |
||||
span.Finish(&err) |
||||
t := time.Since(now) |
||||
if t > _slowLogDuration { |
||||
log.Warn("memcache slow log action: %s key: %s time: %v", action, statement, t) |
||||
} |
||||
return err |
||||
} |
||||
} |
||||
|
||||
func (t *traceConn) AddContext(ctx context.Context, item *Item) error { |
||||
finishFn := t.setTrace(ctx, "Add", item.Key) |
||||
return finishFn(t.Conn.Add(item)) |
||||
} |
||||
|
||||
func (t *traceConn) SetContext(ctx context.Context, item *Item) error { |
||||
finishFn := t.setTrace(ctx, "Set", item.Key) |
||||
return finishFn(t.Conn.Set(item)) |
||||
} |
||||
|
||||
func (t *traceConn) ReplaceContext(ctx context.Context, item *Item) error { |
||||
finishFn := t.setTrace(ctx, "Replace", item.Key) |
||||
return finishFn(t.Conn.Replace(item)) |
||||
} |
||||
|
||||
func (t *traceConn) GetContext(ctx context.Context, key string) (*Item, error) { |
||||
finishFn := t.setTrace(ctx, "Get", key) |
||||
item, err := t.Conn.Get(key) |
||||
return item, finishFn(err) |
||||
} |
||||
|
||||
func (t *traceConn) GetMultiContext(ctx context.Context, keys []string) (map[string]*Item, error) { |
||||
finishFn := t.setTrace(ctx, "GetMulti", strings.Join(keys, " ")) |
||||
items, err := t.Conn.GetMulti(keys) |
||||
return items, finishFn(err) |
||||
} |
||||
|
||||
func (t *traceConn) DeleteContext(ctx context.Context, key string) error { |
||||
finishFn := t.setTrace(ctx, "Delete", key) |
||||
return finishFn(t.Conn.Delete(key)) |
||||
} |
||||
|
||||
func (t *traceConn) IncrementContext(ctx context.Context, key string, delta uint64) (newValue uint64, err error) { |
||||
finishFn := t.setTrace(ctx, "Increment", key+" "+strconv.FormatUint(delta, 10)) |
||||
newValue, err = t.Conn.Increment(key, delta) |
||||
return newValue, finishFn(err) |
||||
} |
||||
|
||||
func (t *traceConn) DecrementContext(ctx context.Context, key string, delta uint64) (newValue uint64, err error) { |
||||
finishFn := t.setTrace(ctx, "Decrement", key+" "+strconv.FormatUint(delta, 10)) |
||||
newValue, err = t.Conn.Decrement(key, delta) |
||||
return newValue, finishFn(err) |
||||
} |
||||
|
||||
func (t *traceConn) CompareAndSwapContext(ctx context.Context, item *Item) error { |
||||
finishFn := t.setTrace(ctx, "CompareAndSwap", item.Key) |
||||
return finishFn(t.Conn.CompareAndSwap(item)) |
||||
} |
||||
|
||||
func (t *traceConn) TouchContext(ctx context.Context, key string, seconds int32) (err error) { |
||||
finishFn := t.setTrace(ctx, "Touch", key+" "+strconv.Itoa(int(seconds))) |
||||
return finishFn(t.Conn.Touch(key, seconds)) |
||||
} |
@ -0,0 +1,75 @@ |
||||
package memcache |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
pb "github.com/bilibili/kratos/pkg/cache/memcache/test" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestItemUtil(t *testing.T) { |
||||
item1 := RawItem("test", []byte("hh"), 0, 0) |
||||
assert.Equal(t, "test", item1.Key) |
||||
assert.Equal(t, []byte("hh"), item1.Value) |
||||
assert.Equal(t, FlagRAW, FlagRAW&item1.Flags) |
||||
|
||||
item1 = JSONItem("test", &Item{}, 0, 0) |
||||
assert.Equal(t, "test", item1.Key) |
||||
assert.NotNil(t, item1.Object) |
||||
assert.Equal(t, FlagJSON, FlagJSON&item1.Flags) |
||||
|
||||
item1 = ProtobufItem("test", &pb.TestItem{}, 0, 0) |
||||
assert.Equal(t, "test", item1.Key) |
||||
assert.NotNil(t, item1.Object) |
||||
assert.Equal(t, FlagProtobuf, FlagProtobuf&item1.Flags) |
||||
} |
||||
|
||||
func TestLegalKey(t *testing.T) { |
||||
type args struct { |
||||
key string |
||||
} |
||||
tests := []struct { |
||||
name string |
||||
args args |
||||
want bool |
||||
}{ |
||||
{ |
||||
name: "test empty key", |
||||
want: false, |
||||
}, |
||||
{ |
||||
name: "test too large key", |
||||
args: args{func() string { |
||||
var data []byte |
||||
for i := 0; i < 255; i++ { |
||||
data = append(data, 'k') |
||||
} |
||||
return string(data) |
||||
}()}, |
||||
want: false, |
||||
}, |
||||
{ |
||||
name: "test invalid char", |
||||
args: args{"hello world"}, |
||||
want: false, |
||||
}, |
||||
{ |
||||
name: "test invalid char", |
||||
args: args{string([]byte{0x7f})}, |
||||
want: false, |
||||
}, |
||||
{ |
||||
name: "test normal key", |
||||
args: args{"hello"}, |
||||
want: true, |
||||
}, |
||||
} |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
if got := legalKey(tt.args.key); got != tt.want { |
||||
t.Errorf("legalKey() = %v, want %v", got, tt.want) |
||||
} |
||||
}) |
||||
} |
||||
} |
Loading…
Reference in new issue