blob: 3f1705ba89108e714df4ea5676fa6bc25b4a95e1 [file] [edit]
// Copyright 2012 Gary Burd
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package redis_test
import (
"fmt"
"math"
"reflect"
"strconv"
"testing"
"time"
"github.com/gomodule/redigo/redis"
"github.com/stretchr/testify/require"
)
var (
maxUint64Str = strconv.FormatUint(math.MaxUint64, 10)
)
type valueError struct {
v interface{}
err error
}
func ve(v interface{}, err error) valueError {
return valueError{v, err}
}
var replyTests = []struct {
name interface{}
actual valueError
expected valueError
}{
{
"ints([[]byte, []byte])",
ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)),
ve([]int{4, 5}, nil),
},
{
"ints([nt64, int64])",
ve(redis.Ints([]interface{}{int64(4), int64(5)}, nil)),
ve([]int{4, 5}, nil),
},
{
"ints([[]byte, nil, []byte])",
ve(redis.Ints([]interface{}{[]byte("4"), nil, []byte("5")}, nil)),
ve([]int{4, 0, 5}, nil),
},
{
"ints(nil)",
ve(redis.Ints(nil, nil)),
ve([]int(nil), redis.ErrNil),
},
{
"int64s([[]byte, []byte])",
ve(redis.Int64s([]interface{}{[]byte("4"), []byte("5")}, nil)),
ve([]int64{4, 5}, nil),
},
{
"int64s([int64, int64])",
ve(redis.Int64s([]interface{}{int64(4), int64(5)}, nil)),
ve([]int64{4, 5}, nil),
},
{
"uint64s([[]byte, []byte])",
ve(redis.Uint64s([]interface{}{[]byte(maxUint64Str), []byte("5")}, nil)),
ve([]uint64{math.MaxUint64, 5}, nil),
},
{
"Uint64Map([[]byte, []byte])",
ve(redis.Uint64Map([]interface{}{[]byte("key1"), []byte(maxUint64Str), []byte("key2"), []byte("5")}, nil)),
ve(map[string]uint64{"key1": math.MaxUint64, "key2": 5}, nil),
},
{
"strings([[]byte, []byte])",
ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
ve([]string{"v1", "v2"}, nil),
},
{
"strings([string, string])",
ve(redis.Strings([]interface{}{"v1", "v2"}, nil)),
ve([]string{"v1", "v2"}, nil),
},
{
"byteslices([v1, v2])",
ve(redis.ByteSlices([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
ve([][]byte{[]byte("v1"), []byte("v2")}, nil),
},
{
"float64s([v1, v2])",
ve(redis.Float64s([]interface{}{[]byte("1.234"), []byte("5.678")}, nil)),
ve([]float64{1.234, 5.678}, nil),
},
{
"values([v1, v2])",
ve(redis.Values([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
ve([]interface{}{[]byte("v1"), []byte("v2")}, nil),
},
{
"values(nil)",
ve(redis.Values(nil, nil)),
ve([]interface{}(nil), redis.ErrNil),
},
{
"float64(1.0)",
ve(redis.Float64([]byte("1.0"), nil)),
ve(float64(1.0), nil),
},
{
"float64(nil)",
ve(redis.Float64(nil, nil)),
ve(float64(0.0), redis.ErrNil),
},
{
"float64Map([[]byte, []byte])",
ve(redis.Float64Map([]interface{}{[]byte("key1"), []byte("1.234"), []byte("key2"), []byte("5.678")}, nil)),
ve(map[string]float64{"key1": 1.234, "key2": 5.678}, nil),
},
{
"uint64(1)",
ve(redis.Uint64(int64(1), nil)),
ve(uint64(1), nil),
},
{
"uint64(-1)",
ve(redis.Uint64(int64(-1), nil)),
ve(uint64(0), redis.ErrNegativeInt(-1)),
},
{
"positions([[1, 2], nil, [3, 4]])",
ve(redis.Positions([]interface{}{[]interface{}{[]byte("1"), []byte("2")}, nil, []interface{}{[]byte("3"), []byte("4")}}, nil)),
ve([]*[2]float64{{1.0, 2.0}, nil, {3.0, 4.0}}, nil),
},
{
"SlowLogs(1, 1579625870, 3, {set, x, y}, localhost:1234, testClient",
ve(getSlowLog()),
ve(redis.SlowLog{ID: 1, Time: time.Unix(1579625870, 0), ExecutionTime: time.Duration(3) * time.Microsecond, Args: []string{"set", "x", "y"}, ClientAddr: "localhost:1234", ClientName: "testClient"}, nil),
},
}
func getSlowLog() (redis.SlowLog, error) {
slowLogs, _ := redis.SlowLogs([]interface{}{[]interface{}{int64(1), int64(1579625870), int64(3), []interface{}{"set", "x", "y"}, "localhost:1234", "testClient"}}, nil)
if err != nil {
return redis.SlowLog{}, err
}
return slowLogs[0], nil
}
func TestReply(t *testing.T) {
for _, rt := range replyTests {
if rt.actual.err != rt.expected.err && rt.actual.err.Error() != rt.expected.err.Error() {
t.Errorf("%s returned err %v, want %v", rt.name, rt.actual.err, rt.expected.err)
continue
}
if !reflect.DeepEqual(rt.actual.v, rt.expected.v) {
t.Errorf("%s=%+v, want %+v", rt.name, rt.actual.v, rt.expected.v)
}
}
}
func TestSlowLog(t *testing.T) {
c, err := dial()
if err != nil {
t.Errorf("TestSlowLog failed during dial with error " + err.Error())
return
}
defer c.Close()
resultStr, err := redis.Strings(c.Do("CONFIG", "GET", "slowlog-log-slower-than"))
if err != nil {
t.Errorf("TestSlowLog failed during CONFIG GET slowlog-log-slower-than with error " + err.Error())
return
}
// in case of older verion < 2.2.12 where SLOWLOG command is not supported
// don't run the test
if len(resultStr) == 0 {
return
}
slowLogSlowerThanOldCfg, err := strconv.Atoi(resultStr[1])
if err != nil {
t.Errorf("TestSlowLog failed during strconv.Atoi with error " + err.Error())
return
}
result, err := c.Do("CONFIG", "SET", "slowlog-log-slower-than", "0")
if err != nil && result != "OK" {
t.Errorf("TestSlowLog failed during CONFIG SET with error " + err.Error())
return
}
result, err = c.Do("SLOWLOG", "GET")
if err != nil {
t.Errorf("TestSlowLog failed during SLOWLOG GET with error " + err.Error())
return
}
slowLogs, err := redis.SlowLogs(result, err)
if err != nil {
t.Errorf("TestSlowLog failed during redis.SlowLogs with error " + err.Error())
return
}
slowLog := slowLogs[0]
if slowLog.Args[0] != "CONFIG" ||
slowLog.Args[1] != "SET" ||
slowLog.Args[2] != "slowlog-log-slower-than" ||
slowLog.Args[3] != "0" {
t.Errorf("%s=%+v, want %+v", "TestSlowLog test failed : ",
slowLog.Args[0]+" "+slowLog.Args[1]+" "+slowLog.Args[2]+" "+
slowLog.Args[3], "CONFIG SET slowlog-log-slower-than 0")
}
// reset the old configuration after test
result, err = c.Do("CONFIG", "SET", "slowlog-log-slower-than", slowLogSlowerThanOldCfg)
if err != nil && result != "OK" {
t.Errorf("TestSlowLog failed during CONFIG SET with error " + err.Error())
return
}
}
func TestLatency(t *testing.T) {
c, err := dial()
require.NoError(t, err)
defer c.Close()
resultStr, err := redis.Strings(c.Do("CONFIG", "GET", "latency-monitor-threshold"))
require.NoError(t, err)
// LATENCY commands were added in 2.8.13 so might not be supported.
if len(resultStr) == 0 {
t.Skip("Latency commands not supported")
}
latencyMonitorThresholdOldCfg, err := strconv.Atoi(resultStr[1])
require.NoError(t, err)
// Enable latency monitoring for events that take 1ms or longer.
result, err := c.Do("CONFIG", "SET", "latency-monitor-threshold", "1")
// reset the old configuration after test.
defer func() {
res, err := c.Do("CONFIG", "SET", "latency-monitor-threshold", latencyMonitorThresholdOldCfg)
require.NoError(t, err)
require.Equal(t, "OK", res)
}()
require.NoError(t, err)
require.Equal(t, "OK", result)
// Sleep for 1ms to register a slow event.
_, err = c.Do("DEBUG", "SLEEP", 0.001)
require.NoError(t, err)
result, err = c.Do("LATENCY", "LATEST")
require.NoError(t, err)
latestLatencies, err := redis.Latencies(result, err)
require.NoError(t, err)
require.Equal(t, 1, len(latestLatencies))
latencyEvent := latestLatencies[0]
// The actual latency might be longer than 1ms
require.GreaterOrEqual(t, latencyEvent.Latest, time.Millisecond)
require.GreaterOrEqual(t, latencyEvent.Max, time.Millisecond)
expected := redis.Latency{
Name: "command",
Latest: latencyEvent.Latest,
Max: latencyEvent.Max,
Time: latencyEvent.Time,
}
require.Equal(t, expected, latencyEvent)
}
func TestLatencyHistories(t *testing.T) {
c, err := dial()
require.NoError(t, err)
defer c.Close()
res, err := redis.Strings(c.Do("CONFIG", "GET", "latency-monitor-threshold"))
require.NoError(t, err)
// LATENCY commands were added in 2.8.13 so might not be supported.
if len(res) == 0 {
t.Skip("Latency commands not supported")
}
latencyMonitorThresholdOldCfg, err := strconv.Atoi(res[1])
require.NoError(t, err)
// Reset so we're compatible with -count=X
_, err = c.Do("LATENCY", "RESET", "command")
require.NoError(t, err)
// Enable latency monitoring for events that take 1ms or longer
result, err := c.Do("CONFIG", "SET", "latency-monitor-threshold", "1")
// reset the old configuration after test.
defer func() {
res, err := c.Do("CONFIG", "SET", "latency-monitor-threshold", latencyMonitorThresholdOldCfg)
require.NoError(t, err)
require.Equal(t, "OK", res)
}()
require.NoError(t, err)
require.Equal(t, "OK", result)
// Sleep for 1ms to register a slow event
_, err = c.Do("DEBUG", "SLEEP", 0.001)
require.NoError(t, err)
result, err = c.Do("LATENCY", "HISTORY", "command")
require.NoError(t, err)
latencyHistory, err := redis.LatencyHistories(result, err)
require.NoError(t, err)
require.Len(t, latencyHistory, 1)
latencyEvent := latencyHistory[0]
// The actual latency might be longer than 1ms
require.GreaterOrEqual(t, latencyEvent.ExecutionTime, time.Millisecond)
}
// dial wraps DialDefaultServer() with a more suitable function name for examples.
func dial() (redis.Conn, error) {
return redis.DialDefaultServer()
}
// serverAddr wraps DefaultServerAddr() with a more suitable function name for examples.
func serverAddr() (string, error) {
return redis.DefaultServerAddr()
}
func ExampleBool() {
c, err := dial()
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
if _, err = c.Do("SET", "foo", 1); err != nil {
fmt.Println(err)
return
}
exists, err := redis.Bool(c.Do("EXISTS", "foo"))
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%#v\n", exists)
// Output:
// true
}
func ExampleInt() {
c, err := dial()
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
_, err = c.Do("SET", "k1", 1)
if err != nil {
fmt.Println(err)
return
}
n, err := redis.Int(c.Do("GET", "k1"))
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%#v\n", n)
n, err = redis.Int(c.Do("INCR", "k1"))
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%#v\n", n)
// Output:
// 1
// 2
}
func ExampleInts() {
c, err := dial()
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
_, err = c.Do("SADD", "set_with_integers", 4, 5, 6)
if err != nil {
fmt.Println(err)
return
}
ints, err := redis.Ints(c.Do("SMEMBERS", "set_with_integers"))
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%#v\n", ints)
// Output:
// []int{4, 5, 6}
}
func ExampleString() {
c, err := dial()
if err != nil {
fmt.Println(err)
return
}
defer c.Close()
_, err = c.Do("SET", "hello", "world")
if err != nil {
fmt.Println(err)
return
}
s, err := redis.String(c.Do("GET", "hello"))
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%#v\n", s)
// Output:
// "world"
}