diff options
Diffstat (limited to 'Godeps/_workspace/src/github.com/garyburd/redigo/redis')
12 files changed, 677 insertions, 186 deletions
diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go index ac0e971c4..6a3819f1d 100644 --- a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go @@ -21,6 +21,8 @@ import ( "fmt" "io" "net" + "net/url" + "regexp" "strconv" "sync" "time" @@ -51,56 +53,164 @@ type conn struct { numScratch [40]byte } -// Dial connects to the Redis server at the given network and address. -func Dial(network, address string) (Conn, error) { - dialer := xDialer{} - return dialer.Dial(network, address) -} - // DialTimeout acts like Dial but takes timeouts for establishing the // connection to the server, writing a command and reading a reply. +// +// Deprecated: Use Dial with options instead. func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { - netDialer := net.Dialer{Timeout: connectTimeout} - dialer := xDialer{ - NetDial: netDialer.Dial, - ReadTimeout: readTimeout, - WriteTimeout: writeTimeout, - } - return dialer.Dial(network, address) + return Dial(network, address, + DialConnectTimeout(connectTimeout), + DialReadTimeout(readTimeout), + DialWriteTimeout(writeTimeout)) +} + +// DialOption specifies an option for dialing a Redis server. +type DialOption struct { + f func(*dialOptions) +} + +type dialOptions struct { + readTimeout time.Duration + writeTimeout time.Duration + dial func(network, addr string) (net.Conn, error) + db int + password string +} + +// DialReadTimeout specifies the timeout for reading a single command reply. +func DialReadTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.readTimeout = d + }} +} + +// DialWriteTimeout specifies the timeout for writing a single command. +func DialWriteTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.writeTimeout = d + }} } -// A Dialer specifies options for connecting to a Redis server. -type xDialer struct { - // NetDial specifies the dial function for creating TCP connections. If - // NetDial is nil, then net.Dial is used. - NetDial func(network, addr string) (net.Conn, error) +// DialConnectTimeout specifies the timeout for connecting to the Redis server. +func DialConnectTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + dialer := net.Dialer{Timeout: d} + do.dial = dialer.Dial + }} +} + +// DialNetDial specifies a custom dial function for creating TCP +// connections. If this option is left out, then net.Dial is +// used. DialNetDial overrides DialConnectTimeout. +func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { + return DialOption{func(do *dialOptions) { + do.dial = dial + }} +} - // ReadTimeout specifies the timeout for reading a single command - // reply. If ReadTimeout is zero, then no timeout is used. - ReadTimeout time.Duration +// DialDatabase specifies the database to select when dialing a connection. +func DialDatabase(db int) DialOption { + return DialOption{func(do *dialOptions) { + do.db = db + }} +} - // WriteTimeout specifies the timeout for writing a single command. If - // WriteTimeout is zero, then no timeout is used. - WriteTimeout time.Duration +// DialPassword specifies the password to use when connecting to +// the Redis server. +func DialPassword(password string) DialOption { + return DialOption{func(do *dialOptions) { + do.password = password + }} } -// Dial connects to the Redis server at address on the named network. -func (d *xDialer) Dial(network, address string) (Conn, error) { - dial := d.NetDial - if dial == nil { - dial = net.Dial +// Dial connects to the Redis server at the given network and +// address using the specified options. +func Dial(network, address string, options ...DialOption) (Conn, error) { + do := dialOptions{ + dial: net.Dial, } - netConn, err := dial(network, address) + for _, option := range options { + option.f(&do) + } + + netConn, err := do.dial(network, address) if err != nil { return nil, err } - return &conn{ + c := &conn{ conn: netConn, bw: bufio.NewWriter(netConn), br: bufio.NewReader(netConn), - readTimeout: d.ReadTimeout, - writeTimeout: d.WriteTimeout, - }, nil + readTimeout: do.readTimeout, + writeTimeout: do.writeTimeout, + } + + if do.password != "" { + if _, err := c.Do("AUTH", do.password); err != nil { + netConn.Close() + return nil, err + } + } + + if do.db != 0 { + if _, err := c.Do("SELECT", do.db); err != nil { + netConn.Close() + return nil, err + } + } + + return c, nil +} + +var pathDBRegexp = regexp.MustCompile(`/(\d+)\z`) + +// DialURL connects to a Redis server at the given URL using the Redis +// URI scheme. URLs should follow the draft IANA specification for the +// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). +func DialURL(rawurl string, options ...DialOption) (Conn, error) { + u, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + + if u.Scheme != "redis" { + return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) + } + + // As per the IANA draft spec, the host defaults to localhost and + // the port defaults to 6379. + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + // assume port is missing + host = u.Host + port = "6379" + } + if host == "" { + host = "localhost" + } + address := net.JoinHostPort(host, port) + + if u.User != nil { + password, isSet := u.User.Password() + if isSet { + options = append(options, DialPassword(password)) + } + } + + match := pathDBRegexp.FindStringSubmatch(u.Path) + if len(match) == 2 { + db, err := strconv.Atoi(match[1]) + if err != nil { + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) + } + if db != 0 { + options = append(options, DialDatabase(db)) + } + } else if u.Path != "" { + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) + } + + return Dial("tcp", address, options...) } // NewConn returns a new Redigo connection for the given net connection. @@ -417,7 +527,9 @@ func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { } if cmd != "" { - c.writeCommand(cmd, args) + if err := c.writeCommand(cmd, args); err != nil { + return nil, c.fatal(err) + } } if err := c.bw.Flush(); err != nil { diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go index 800370136..06a2c8fa7 100644 --- a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go @@ -15,19 +15,37 @@ package redis_test import ( - "bufio" "bytes" + "io" "math" "net" + "os" "reflect" "strings" "testing" "time" - "github.com/garyburd/redigo/internal/redistest" - "github.com/garyburd/redigo/redis" + "github.com/mattermost/platform/Godeps/_workspace/src/github.com/garyburd/redigo/redis" ) +type testConn struct { + io.Reader + io.Writer +} + +func (*testConn) Close() error { return nil } +func (*testConn) LocalAddr() net.Addr { return nil } +func (*testConn) RemoteAddr() net.Addr { return nil } +func (*testConn) SetDeadline(t time.Time) error { return nil } +func (*testConn) SetReadDeadline(t time.Time) error { return nil } +func (*testConn) SetWriteDeadline(t time.Time) error { return nil } + +func dialTestConn(r io.Reader, w io.Writer) redis.DialOption { + return redis.DialNetDial(func(net, addr string) (net.Conn, error) { + return &testConn{Reader: r, Writer: w}, nil + }) +} + var writeTests = []struct { args []interface{} expected string @@ -73,14 +91,13 @@ var writeTests = []struct { func TestWrite(t *testing.T) { for _, tt := range writeTests { var buf bytes.Buffer - rw := bufio.ReadWriter{Writer: bufio.NewWriter(&buf)} - c := redis.NewConnBufio(rw) + c, _ := redis.Dial("", "", dialTestConn(nil, &buf)) err := c.Send(tt.args[0].(string), tt.args[1:]...) if err != nil { t.Errorf("Send(%v) returned error %v", tt.args, err) continue } - rw.Flush() + c.Flush() actual := buf.String() if actual != tt.expected { t.Errorf("Send(%v) = %q, want %q", tt.args, actual, tt.expected) @@ -173,11 +190,7 @@ var readTests = []struct { func TestRead(t *testing.T) { for _, tt := range readTests { - rw := bufio.ReadWriter{ - Reader: bufio.NewReader(strings.NewReader(tt.reply)), - Writer: bufio.NewWriter(nil), // writer need to support Flush - } - c := redis.NewConnBufio(rw) + c, _ := redis.Dial("", "", dialTestConn(strings.NewReader(tt.reply), nil)) actual, err := c.Receive() if tt.expected == errorSentinel { if err == nil { @@ -257,7 +270,7 @@ var testCommands = []struct { } func TestDoCommands(t *testing.T) { - c, err := redistest.Dial() + c, err := redis.DialDefaultServer() if err != nil { t.Fatalf("error connection to database, %v", err) } @@ -276,7 +289,7 @@ func TestDoCommands(t *testing.T) { } func TestPipelineCommands(t *testing.T) { - c, err := redistest.Dial() + c, err := redis.DialDefaultServer() if err != nil { t.Fatalf("error connection to database, %v", err) } @@ -302,7 +315,7 @@ func TestPipelineCommands(t *testing.T) { } func TestBlankCommmand(t *testing.T) { - c, err := redistest.Dial() + c, err := redis.DialDefaultServer() if err != nil { t.Fatalf("error connection to database, %v", err) } @@ -329,7 +342,7 @@ func TestBlankCommmand(t *testing.T) { } func TestRecvBeforeSend(t *testing.T) { - c, err := redistest.Dial() + c, err := redis.DialDefaultServer() if err != nil { t.Fatalf("error connection to database, %v", err) } @@ -350,7 +363,7 @@ func TestRecvBeforeSend(t *testing.T) { } func TestError(t *testing.T) { - c, err := redistest.Dial() + c, err := redis.DialDefaultServer() if err != nil { t.Fatalf("error connection to database, %v", err) } @@ -370,7 +383,7 @@ func TestError(t *testing.T) { } } -func TestReadDeadline(t *testing.T) { +func TestReadTimeout(t *testing.T) { l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("net.Listen returned %v", err) @@ -391,7 +404,9 @@ func TestReadDeadline(t *testing.T) { } }() - c1, err := redis.DialTimeout(l.Addr().Network(), l.Addr().String(), 0, time.Millisecond, 0) + // Do + + c1, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond)) if err != nil { t.Fatalf("redis.Dial returned %v", err) } @@ -405,7 +420,9 @@ func TestReadDeadline(t *testing.T) { t.Fatalf("c1.Err() = nil, expect error") } - c2, err := redis.DialTimeout(l.Addr().Network(), l.Addr().String(), 0, time.Millisecond, 0) + // Send/Flush/Receive + + c2, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond)) if err != nil { t.Fatalf("redis.Dial returned %v", err) } @@ -422,6 +439,95 @@ func TestReadDeadline(t *testing.T) { } } +var dialErrors = []struct { + rawurl string + expectedError string +}{ + { + "localhost", + "invalid redis URL scheme", + }, + // The error message for invalid hosts is diffferent in different + // versions of Go, so just check that there is an error message. + { + "redis://weird url", + "", + }, + { + "redis://foo:bar:baz", + "", + }, + { + "http://www.google.com", + "invalid redis URL scheme: http", + }, + { + "redis://localhost:6379/abc123", + "invalid database: abc123", + }, +} + +func TestDialURLErrors(t *testing.T) { + for _, d := range dialErrors { + _, err := redis.DialURL(d.rawurl) + if err == nil || !strings.Contains(err.Error(), d.expectedError) { + t.Errorf("DialURL did not return expected error (expected %v to contain %s)", err, d.expectedError) + } + } +} + +func TestDialURLPort(t *testing.T) { + checkPort := func(network, address string) (net.Conn, error) { + if address != "localhost:6379" { + t.Errorf("DialURL did not set port to 6379 by default (got %v)", address) + } + return nil, nil + } + _, err := redis.DialURL("redis://localhost", redis.DialNetDial(checkPort)) + if err != nil { + t.Error("dial error:", err) + } +} + +func TestDialURLHost(t *testing.T) { + checkHost := func(network, address string) (net.Conn, error) { + if address != "localhost:6379" { + t.Errorf("DialURL did not set host to localhost by default (got %v)", address) + } + return nil, nil + } + _, err := redis.DialURL("redis://:6379", redis.DialNetDial(checkHost)) + if err != nil { + t.Error("dial error:", err) + } +} + +func TestDialURLPassword(t *testing.T) { + var buf bytes.Buffer + _, err := redis.DialURL("redis://x:abc123@localhost", dialTestConn(strings.NewReader("+OK\r\n"), &buf)) + if err != nil { + t.Error("dial error:", err) + } + expected := "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n" + actual := buf.String() + if actual != expected { + t.Errorf("commands = %q, want %q", actual, expected) + } +} + +func TestDialURLDatabase(t *testing.T) { + var buf bytes.Buffer + _, err := redis.DialURL("redis://localhost/3", dialTestConn(strings.NewReader("+OK\r\n"), &buf)) + if err != nil { + t.Error("dial error:", err) + } + expected := "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n" + actual := buf.String() + if actual != expected { + t.Errorf("commands = %q, want %q", actual, expected) + } +} + // Connect to local instance of Redis running on the default port. func ExampleDial(x int) { c, err := redis.Dial("tcp", ":6379") @@ -431,11 +537,20 @@ func ExampleDial(x int) { defer c.Close() } +// Connect to remote instance of Redis using a URL. +func ExampleDialURL() { + c, err := redis.DialURL(os.Getenv("REDIS_URL")) + if err != nil { + // handle connection error + } + defer c.Close() +} + // TextExecError tests handling of errors in a transaction. See // http://redis.io/topics/transactions for information on how Redis handles // errors in a transaction. func TestExecError(t *testing.T) { - c, err := redistest.Dial() + c, err := redis.DialDefaultServer() if err != nil { t.Fatalf("error connection to database, %v", err) } @@ -443,6 +558,7 @@ func TestExecError(t *testing.T) { // Execute commands that fail before EXEC is called. + c.Do("DEL", "k0") c.Do("ZADD", "k0", 0, 0) c.Send("MULTI") c.Send("NOTACOMMAND", "k0", 0, 0) @@ -455,6 +571,7 @@ func TestExecError(t *testing.T) { // Execute commands that fail after EXEC is called. The first command // returns an error. + c.Do("DEL", "k1") c.Do("ZADD", "k1", 0, 0) c.Send("MULTI") c.Send("HSET", "k1", 0, 0) @@ -478,7 +595,7 @@ func TestExecError(t *testing.T) { } if _, ok := vs[1].([]byte); !ok { - t.Fatalf("second result is type %T, expected []byte", vs[2]) + t.Fatalf("second result is type %T, expected []byte", vs[1]) } // Execute commands that fail after EXEC is called. The second command @@ -513,7 +630,7 @@ func TestExecError(t *testing.T) { func BenchmarkDoEmpty(b *testing.B) { b.StopTimer() - c, err := redistest.Dial() + c, err := redis.DialDefaultServer() if err != nil { b.Fatal(err) } @@ -528,7 +645,7 @@ func BenchmarkDoEmpty(b *testing.B) { func BenchmarkDoPing(b *testing.B) { b.StopTimer() - c, err := redistest.Dial() + c, err := redis.DialDefaultServer() if err != nil { b.Fatal(err) } diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go index 9daf2e33f..c29b35f81 100644 --- a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go @@ -25,7 +25,7 @@ import ( "sync" "time" - "github.com/garyburd/redigo/internal" + "github.com/mattermost/platform/Godeps/_workspace/src/github.com/garyburd/redigo/internal" ) var nowFunc = time.Now // for testing @@ -94,7 +94,10 @@ var ( type Pool struct { // Dial is an application supplied function for creating and configuring a - // connection + // connection. + // + // The connection returned from Dial must not be in a special state + // (subscribed to pubsub channel, transaction started, ...). Dial func() (Conn, error) // TestOnBorrow is an optional application supplied function for checking @@ -116,7 +119,7 @@ type Pool struct { // the timeout to a value less than the server's timeout. IdleTimeout time.Duration - // If Wait is true and the pool is at the MaxIdle limit, then Get() waits + // If Wait is true and the pool is at the MaxActive limit, then Get() waits // for a connection to be returned to the pool before returning. Wait bool @@ -135,8 +138,9 @@ type idleConn struct { t time.Time } -// NewPool creates a new pool. This function is deprecated. Applications should -// initialize the Pool fields directly as shown in example. +// NewPool creates a new pool. +// +// Deprecated: Initialize the Pool directory as shown in the example. func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { return &Pool{Dial: newFn, MaxIdle: maxIdle} } diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go index 1fe305f16..89e46e08d 100644 --- a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go @@ -22,8 +22,7 @@ import ( "testing" "time" - "github.com/garyburd/redigo/internal/redistest" - "github.com/garyburd/redigo/redis" + "github.com/mattermost/platform/Godeps/_workspace/src/github.com/garyburd/redigo/redis" ) type poolTestConn struct { @@ -32,10 +31,16 @@ type poolTestConn struct { redis.Conn } -func (c *poolTestConn) Close() error { c.d.open -= 1; return nil } -func (c *poolTestConn) Err() error { return c.err } +func (c *poolTestConn) Close() error { + c.d.mu.Lock() + c.d.open -= 1 + c.d.mu.Unlock() + return c.Conn.Close() +} + +func (c *poolTestConn) Err() error { return c.err } -func (c *poolTestConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { +func (c *poolTestConn) Do(commandName string, args ...interface{}) (interface{}, error) { if commandName == "ERR" { c.err = args[0].(error) commandName = "PING" @@ -52,6 +57,7 @@ func (c *poolTestConn) Send(commandName string, args ...interface{}) error { } type poolDialer struct { + mu sync.Mutex t *testing.T dialed int open int @@ -60,19 +66,25 @@ type poolDialer struct { } func (d *poolDialer) dial() (redis.Conn, error) { + d.mu.Lock() d.dialed += 1 - if d.dialErr != nil { + dialErr := d.dialErr + d.mu.Unlock() + if dialErr != nil { return nil, d.dialErr } - c, err := redistest.Dial() + c, err := redis.DialDefaultServer() if err != nil { return nil, err } + d.mu.Lock() d.open += 1 + d.mu.Unlock() return &poolTestConn{d: d, Conn: c}, nil } func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) { + d.mu.Lock() if d.dialed != dialed { d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed) } @@ -82,6 +94,7 @@ func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) { if active := p.ActiveCount(); active != open { d.t.Errorf("%s: active=%d, want %d", message, active, open) } + d.mu.Unlock() } func TestPoolReuse(t *testing.T) { @@ -111,6 +124,8 @@ func TestPoolMaxIdle(t *testing.T) { MaxIdle: 2, Dial: d.dial, } + defer p.Close() + for i := 0; i < 10; i++ { c1 := p.Get() c1.Do("PING") @@ -133,6 +148,7 @@ func TestPoolError(t *testing.T) { MaxIdle: 2, Dial: d.dial, } + defer p.Close() c := p.Get() c.Do("ERR", io.EOF) @@ -154,6 +170,7 @@ func TestPoolClose(t *testing.T) { MaxIdle: 2, Dial: d.dial, } + defer p.Close() c1 := p.Get() c1.Do("PING") @@ -195,6 +212,7 @@ func TestPoolTimeout(t *testing.T) { IdleTimeout: 300 * time.Second, Dial: d.dial, } + defer p.Close() now := time.Now() redis.SetNowFunc(func() time.Time { return now }) @@ -213,14 +231,14 @@ func TestPoolTimeout(t *testing.T) { c.Close() d.check("2", p, 2, 1) - - p.Close() } func TestPoolConcurrenSendReceive(t *testing.T) { p := &redis.Pool{ - Dial: redistest.Dial, + Dial: redis.DialDefaultServer, } + defer p.Close() + c := p.Get() done := make(chan error, 1) go func() { @@ -238,7 +256,6 @@ func TestPoolConcurrenSendReceive(t *testing.T) { t.Fatalf("Do() returned error %v", err) } c.Close() - p.Close() } func TestPoolBorrowCheck(t *testing.T) { @@ -248,6 +265,7 @@ func TestPoolBorrowCheck(t *testing.T) { Dial: d.dial, TestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error("BLAH") }, } + defer p.Close() for i := 0; i < 10; i++ { c := p.Get() @@ -255,7 +273,6 @@ func TestPoolBorrowCheck(t *testing.T) { c.Close() } d.check("1", p, 10, 1) - p.Close() } func TestPoolMaxActive(t *testing.T) { @@ -265,6 +282,8 @@ func TestPoolMaxActive(t *testing.T) { MaxActive: 2, Dial: d.dial, } + defer p.Close() + c1 := p.Get() c1.Do("PING") c2 := p.Get() @@ -289,7 +308,6 @@ func TestPoolMaxActive(t *testing.T) { c3.Close() d.check("4", p, 2, 2) - p.Close() } func TestPoolMonitorCleanup(t *testing.T) { @@ -299,12 +317,13 @@ func TestPoolMonitorCleanup(t *testing.T) { MaxActive: 2, Dial: d.dial, } + defer p.Close() + c := p.Get() c.Send("MONITOR") c.Close() d.check("", p, 1, 0) - p.Close() } func TestPoolPubSubCleanup(t *testing.T) { @@ -314,6 +333,7 @@ func TestPoolPubSubCleanup(t *testing.T) { MaxActive: 2, Dial: d.dial, } + defer p.Close() c := p.Get() c.Send("SUBSCRIBE", "x") @@ -334,8 +354,6 @@ func TestPoolPubSubCleanup(t *testing.T) { t.Errorf("got commands %v, want %v", d.commands, want) } d.commands = nil - - p.Close() } func TestPoolTransactionCleanup(t *testing.T) { @@ -345,6 +363,7 @@ func TestPoolTransactionCleanup(t *testing.T) { MaxActive: 2, Dial: d.dial, } + defer p.Close() c := p.Get() c.Do("WATCH", "key") @@ -406,8 +425,6 @@ func TestPoolTransactionCleanup(t *testing.T) { t.Errorf("got commands %v, want %v", d.commands, want) } d.commands = nil - - p.Close() } func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error { @@ -436,6 +453,7 @@ func TestWaitPool(t *testing.T) { Wait: true, } defer p.Close() + c := p.Get() errs := startGoroutines(p, "PING") d.check("before close", p, 1, 1) @@ -462,6 +480,8 @@ func TestWaitPoolClose(t *testing.T) { Dial: d.dial, Wait: true, } + defer p.Close() + c := p.Get() if _, err := c.Do("PING"); err != nil { t.Fatal(err) @@ -497,6 +517,7 @@ func TestWaitPoolCommandError(t *testing.T) { Wait: true, } defer p.Close() + c := p.Get() errs := startGoroutines(p, "ERR", testErr) d.check("before close", p, 1, 1) @@ -525,6 +546,7 @@ func TestWaitPoolDialError(t *testing.T) { Wait: true, } defer p.Close() + c := p.Get() errs := startGoroutines(p, "ERR", testErr) d.check("before close", p, 1, 1) @@ -565,7 +587,7 @@ func TestWaitPoolDialError(t *testing.T) { // test ensures that iteration will work correctly if multiple threads are // iterating simultaneously. func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) { - count := 100 + const count = 100 // First we'll Create a pool where the pilfering of idle connections fails. d := poolDialer{t: t} @@ -580,29 +602,17 @@ func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) { defer p.Close() // Fill the pool with idle connections. - b1 := sync.WaitGroup{} - b1.Add(count) - b2 := sync.WaitGroup{} - b2.Add(count) - for i := 0; i < count; i++ { - go func() { - c := p.Get() - if c.Err() != nil { - t.Errorf("pool get failed: %v", c.Err()) - } - b1.Done() - b1.Wait() - c.Close() - b2.Done() - }() + conns := make([]redis.Conn, count) + for i := range conns { + conns[i] = p.Get() } - b2.Wait() - if d.dialed != count { - t.Errorf("Expected %d dials, got %d", count, d.dialed) + for i := range conns { + conns[i].Close() } // Spawn a bunch of goroutines to thrash the pool. - b2.Add(count) + var wg sync.WaitGroup + wg.Add(count) for i := 0; i < count; i++ { go func() { c := p.Get() @@ -610,10 +620,10 @@ func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) { t.Errorf("pool get failed: %v", c.Err()) } c.Close() - b2.Done() + wg.Done() }() } - b2.Wait() + wg.Wait() if d.dialed != count*2 { t.Errorf("Expected %d dials, got %d", count*2, d.dialed) } @@ -621,7 +631,7 @@ func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) { func BenchmarkPoolGet(b *testing.B) { b.StopTimer() - p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2} + p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2} c := p.Get() if err := c.Err(); err != nil { b.Fatal(err) @@ -637,7 +647,7 @@ func BenchmarkPoolGet(b *testing.B) { func BenchmarkPoolGetErr(b *testing.B) { b.StopTimer() - p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2} + p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2} c := p.Get() if err := c.Err(); err != nil { b.Fatal(err) @@ -656,7 +666,7 @@ func BenchmarkPoolGetErr(b *testing.B) { func BenchmarkPoolGetPing(b *testing.B) { b.StopTimer() - p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2} + p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2} c := p.Get() if err := c.Err(); err != nil { b.Fatal(err) diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go index 365a58821..05e60763c 100644 --- a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go @@ -16,20 +16,18 @@ package redis_test import ( "fmt" - "net" "reflect" "sync" "testing" - "time" - "github.com/garyburd/redigo/internal/redistest" - "github.com/garyburd/redigo/redis" + "github.com/mattermost/platform/Godeps/_workspace/src/github.com/garyburd/redigo/redis" ) func publish(channel, value interface{}) { c, err := dial() if err != nil { - panic(err) + fmt.Println(err) + return } defer c.Close() c.Do("PUBLISH", channel, value) @@ -39,7 +37,8 @@ func publish(channel, value interface{}) { func ExamplePubSubConn() { c, err := dial() if err != nil { - panic(err) + fmt.Println(err) + return } defer c.Close() var wg sync.WaitGroup @@ -111,20 +110,19 @@ func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected int } func TestPushed(t *testing.T) { - pc, err := redistest.Dial() + pc, err := redis.DialDefaultServer() if err != nil { t.Fatalf("error connection to database, %v", err) } defer pc.Close() - nc, err := net.Dial("tcp", ":6379") + sc, err := redis.DialDefaultServer() if err != nil { - t.Fatal(err) + t.Fatalf("error connection to database, %v", err) } - defer nc.Close() - nc.SetReadDeadline(time.Now().Add(4 * time.Second)) + defer sc.Close() - c := redis.PubSubConn{Conn: redis.NewConn(nc, 0, 0)} + c := redis.PubSubConn{Conn: sc} c.Subscribe("c1") expectPushed(t, c, "Subscribe(c1)", redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1}) diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go index 5af29bf51..57896147f 100644 --- a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go @@ -215,7 +215,9 @@ func Bool(reply interface{}, err error) (bool, error) { return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) } -// MultiBulk is deprecated. Use Values. +// MultiBulk is a helper that converts an array command reply to a []interface{}. +// +// Deprecated: Use Values instead. func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } // Values is a helper that converts an array command reply to a []interface{}. @@ -271,13 +273,40 @@ func Strings(reply interface{}, err error) ([]string, error) { return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply) } +// ByteSlices is a helper that converts an array command reply to a [][]byte. +// If err is not equal to nil, then ByteSlices returns nil, err. Nil array +// items are stay nil. ByteSlices returns an error if an array item is not a +// bulk string or nil. +func ByteSlices(reply interface{}, err error) ([][]byte, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + result := make([][]byte, len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + p, ok := reply[i].([]byte) + if !ok { + return nil, fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", reply[i]) + } + result[i] = p + } + return result, nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply) +} + // Ints is a helper that converts an array command reply to a []int. If // err is not equal to nil, then Ints returns nil, err. func Ints(reply interface{}, err error) ([]int, error) { var ints []int - if reply == nil { - return ints, ErrNil - } values, err := Values(reply, err) if err != nil { return ints, err diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go index 92744c590..83c0c7b98 100644 --- a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go @@ -19,8 +19,7 @@ import ( "reflect" "testing" - "github.com/garyburd/redigo/internal/redistest" - "github.com/garyburd/redigo/redis" + "github.com/mattermost/platform/Godeps/_workspace/src/github.com/garyburd/redigo/redis" ) type valueError struct { @@ -58,6 +57,16 @@ var replyTests = []struct { ve([]string(nil), redis.ErrNil), }, { + "byteslices([v1, v2])", + ve(redis.ByteSlices([]interface{}{[]byte("v1"), []byte("v2")}, nil)), + ve([][]byte{[]byte("v1"), []byte("v2")}, nil), + }, + { + "byteslices(nil)", + ve(redis.ByteSlices(nil, nil)), + ve([][]byte(nil), redis.ErrNil), + }, + { "values([v1, v2])", ve(redis.Values([]interface{}{[]byte("v1"), []byte("v2")}, nil)), ve([]interface{}{[]byte("v1"), []byte("v2")}, nil), @@ -101,15 +110,16 @@ func TestReply(t *testing.T) { } } -// dial wraps DialTestDB() with a more suitable function name for examples. +// dial wraps DialDefaultServer() with a more suitable function name for examples. func dial() (redis.Conn, error) { - return redistest.Dial() + return redis.DialDefaultServer() } func ExampleBool() { c, err := dial() if err != nil { - panic(err) + fmt.Println(err) + return } defer c.Close() @@ -123,7 +133,8 @@ func ExampleBool() { func ExampleInt() { c, err := dial() if err != nil { - panic(err) + fmt.Println(err) + return } defer c.Close() @@ -140,7 +151,8 @@ func ExampleInt() { func ExampleInts() { c, err := dial() if err != nil { - panic(err) + fmt.Println(err) + return } defer c.Close() @@ -154,7 +166,8 @@ func ExampleInts() { func ExampleString() { c, err := dial() if err != nil { - panic(err) + fmt.Println(err) + return } defer c.Close() diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go index 8c9cfa18d..962e94bcc 100644 --- a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go @@ -32,11 +32,25 @@ func ensureLen(d reflect.Value, n int) { } func cannotConvert(d reflect.Value, s interface{}) error { - return fmt.Errorf("redigo: Scan cannot convert from %s to %s", - reflect.TypeOf(s), d.Type()) + var sname string + switch s.(type) { + case string: + sname = "Redis simple string" + case Error: + sname = "Redis error" + case int64: + sname = "Redis integer" + case []byte: + sname = "Redis bulk string" + case []interface{}: + sname = "Redis array" + default: + sname = reflect.TypeOf(s).String() + } + return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) } -func convertAssignBytes(d reflect.Value, s []byte) (err error) { +func convertAssignBulkString(d reflect.Value, s []byte) (err error) { switch d.Type().Kind() { case reflect.Float32, reflect.Float64: var x float64 @@ -98,7 +112,7 @@ func convertAssignInt(d reflect.Value, s int64) (err error) { func convertAssignValue(d reflect.Value, s interface{}) (err error) { switch s := s.(type) { case []byte: - err = convertAssignBytes(d, s) + err = convertAssignBulkString(d, s) case int64: err = convertAssignInt(d, s) default: @@ -107,7 +121,7 @@ func convertAssignValue(d reflect.Value, s interface{}) (err error) { return err } -func convertAssignValues(d reflect.Value, s []interface{}) error { +func convertAssignArray(d reflect.Value, s []interface{}) error { if d.Type().Kind() != reflect.Slice { return cannotConvert(d, s) } @@ -144,7 +158,7 @@ func convertAssign(d interface{}, s interface{}) (err error) { if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { err = cannotConvert(d, s) } else { - err = convertAssignBytes(d.Elem(), s) + err = convertAssignBulkString(d.Elem(), s) } } case int64: @@ -169,6 +183,13 @@ func convertAssign(d interface{}, s interface{}) (err error) { err = convertAssignInt(d.Elem(), s) } } + case string: + switch d := d.(type) { + case *string: + *d = string(s) + default: + err = cannotConvert(reflect.ValueOf(d), s) + } case []interface{}: switch d := d.(type) { case *[]interface{}: @@ -181,7 +202,7 @@ func convertAssign(d interface{}, s interface{}) (err error) { if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { err = cannotConvert(d, s) } else { - err = convertAssignValues(d.Elem(), s) + err = convertAssignArray(d.Elem(), s) } } case Error: @@ -206,12 +227,13 @@ func convertAssign(d interface{}, s interface{}) (err error) { // following the copied values. func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { if len(src) < len(dest) { - return nil, errors.New("redigo: Scan array short") + return nil, errors.New("redigo.Scan: array short") } var err error for i, d := range dest { err = convertAssign(d, src[i]) if err != nil { + err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) break } } @@ -219,9 +241,9 @@ func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { } type fieldSpec struct { - name string - index []int - //omitEmpty bool + name string + index []int + omitEmpty bool } type structSpec struct { @@ -237,7 +259,7 @@ func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *st for i := 0; i < t.NumField(); i++ { f := t.Field(i) switch { - case f.PkgPath != "": + case f.PkgPath != "" && !f.Anonymous: // Ignore unexported fields. case f.Anonymous: // TODO: Handle pointers. Requires change to decoder and @@ -258,10 +280,10 @@ func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *st } for _, s := range p[1:] { switch s { - //case "omitempty": - // fs.omitempty = true + case "omitempty": + fs.omitEmpty = true default: - panic(errors.New("redigo: unknown field flag " + s + " for type " + t.Name())) + panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) } } } @@ -321,7 +343,7 @@ func structSpecForType(t reflect.Type) *structSpec { return ss } -var errScanStructValue = errors.New("redigo: ScanStruct value must be non-nil pointer to a struct") +var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") // ScanStruct scans alternating names and values from src to a struct. The // HGETALL and CONFIG GET commands return replies in this format. @@ -350,7 +372,7 @@ func ScanStruct(src []interface{}, dest interface{}) error { ss := structSpecForType(d.Type()) if len(src)%2 != 0 { - return errors.New("redigo: ScanStruct expects even number of values in values") + return errors.New("redigo.ScanStruct: number of values not a multiple of 2") } for i := 0; i < len(src); i += 2 { @@ -360,21 +382,21 @@ func ScanStruct(src []interface{}, dest interface{}) error { } name, ok := src[i].([]byte) if !ok { - return errors.New("redigo: ScanStruct key not a bulk string value") + return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) } fs := ss.fieldSpec(name) if fs == nil { continue } if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { - return err + return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) } } return nil } var ( - errScanSliceValue = errors.New("redigo: ScanSlice dest must be non-nil pointer to a struct") + errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") ) // ScanSlice scans src to the slice pointed to by dest. The elements the dest @@ -407,7 +429,7 @@ func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error continue } if err := convertAssignValue(d.Index(i), s); err != nil { - return err + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) } } return nil @@ -420,18 +442,18 @@ func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error for i, name := range fieldNames { fss[i] = ss.m[name] if fss[i] == nil { - return errors.New("redigo: ScanSlice bad field name " + name) + return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) } } } if len(fss) == 0 { - return errors.New("redigo: ScanSlice no struct fields") + return errors.New("redigo.ScanSlice: no struct fields") } n := len(src) / len(fss) if n*len(fss) != len(src) { - return errors.New("redigo: ScanSlice length not a multiple of struct field count") + return errors.New("redigo.ScanSlice: length not a multiple of struct field count") } ensureLen(d, n) @@ -449,7 +471,7 @@ func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error continue } if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { - return err + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) } } } @@ -507,6 +529,26 @@ func flattenStruct(args Args, v reflect.Value) Args { ss := structSpecForType(v.Type()) for _, fs := range ss.l { fv := v.FieldByIndex(fs.index) + if fs.omitEmpty { + var empty = false + switch fv.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + empty = fv.Len() == 0 + case reflect.Bool: + empty = !fv.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + empty = fv.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + empty = fv.Uint() == 0 + case reflect.Float32, reflect.Float64: + empty = fv.Float() == 0 + case reflect.Interface, reflect.Ptr: + empty = fv.IsNil() + } + if empty { + continue + } + } args = append(args, fs.name, fv.Interface()) } return args diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go index b57dd8969..af1f2c2da 100644 --- a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go @@ -16,10 +16,11 @@ package redis_test import ( "fmt" - "github.com/garyburd/redigo/redis" "math" "reflect" "testing" + + "github.com/mattermost/platform/Godeps/_workspace/src/github.com/garyburd/redigo/redis" ) var scanConversionTests = []struct { @@ -46,6 +47,7 @@ var scanConversionTests = []struct { {[]byte("1"), true}, {int64(1), true}, {[]byte("t"), true}, + {"hello", "hello"}, {[]byte("hello"), "hello"}, {[]byte("world"), []byte("world")}, {[]interface{}{[]byte("foo")}, []interface{}{[]byte("foo")}}, @@ -100,7 +102,8 @@ func TestScanConversionError(t *testing.T) { func ExampleScan() { c, err := dial() if err != nil { - panic(err) + fmt.Println(err) + return } defer c.Close() @@ -115,7 +118,8 @@ func ExampleScan() { "GET", "album:*->title", "GET", "album:*->rating")) if err != nil { - panic(err) + fmt.Println(err) + return } for len(values) > 0 { @@ -123,7 +127,8 @@ func ExampleScan() { rating := -1 // initialize to illegal value to detect nil. values, err = redis.Scan(values, &title, &rating) if err != nil { - panic(err) + fmt.Println(err) + return } if rating == -1 { fmt.Println(title, "not-rated") @@ -295,7 +300,8 @@ func TestScanSlice(t *testing.T) { func ExampleScanSlice() { c, err := dial() if err != nil { - panic(err) + fmt.Println(err) + return } defer c.Close() @@ -310,7 +316,8 @@ func ExampleScanSlice() { "GET", "album:*->title", "GET", "album:*->rating")) if err != nil { - panic(err) + fmt.Println(err) + return } var albums []struct { @@ -318,7 +325,8 @@ func ExampleScanSlice() { Rating int } if err := redis.ScanSlice(values, &albums); err != nil { - panic(err) + fmt.Println(err) + return } fmt.Printf("%v\n", albums) // Output: @@ -332,16 +340,17 @@ var argsTests = []struct { }{ {"struct ptr", redis.Args{}.AddFlat(&struct { - I int `redis:"i"` - U uint `redis:"u"` - S string `redis:"s"` - P []byte `redis:"p"` + I int `redis:"i"` + U uint `redis:"u"` + S string `redis:"s"` + P []byte `redis:"p"` + M map[string]string `redis:"m"` Bt bool Bf bool }{ - -1234, 5678, "hello", []byte("world"), true, false, + -1234, 5678, "hello", []byte("world"), map[string]string{"hello": "world"}, true, false, }), - redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "Bt", true, "Bf", false}, + redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "m", map[string]string{"hello": "world"}, "Bt", true, "Bf", false}, }, {"struct", redis.Args{}.AddFlat(struct{ I int }{123}), @@ -351,6 +360,20 @@ var argsTests = []struct { redis.Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2), redis.Args{1, "a", "b", "c", 2}, }, + {"struct omitempty", + redis.Args{}.AddFlat(&struct { + I int `redis:"i,omitempty"` + U uint `redis:"u,omitempty"` + S string `redis:"s,omitempty"` + P []byte `redis:"p,omitempty"` + M map[string]string `redis:"m,omitempty"` + Bt bool `redis:"Bt,omitempty"` + Bf bool `redis:"Bf,omitempty"` + }{ + 0, 0, "", []byte{}, map[string]string{}, true, false, + }), + redis.Args{"Bt", true}, + }, } func TestArgs(t *testing.T) { @@ -364,7 +387,8 @@ func TestArgs(t *testing.T) { func ExampleArgs() { c, err := dial() if err != nil { - panic(err) + fmt.Println(err) + return } defer c.Close() @@ -379,7 +403,8 @@ func ExampleArgs() { p1.Body = "Hello" if _, err := c.Do("HMSET", redis.Args{}.Add("id1").AddFlat(&p1)...); err != nil { - panic(err) + fmt.Println(err) + return } m := map[string]string{ @@ -389,18 +414,21 @@ func ExampleArgs() { } if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil { - panic(err) + fmt.Println(err) + return } for _, id := range []string{"id1", "id2"} { v, err := redis.Values(c.Do("HGETALL", id)) if err != nil { - panic(err) + fmt.Println(err) + return } if err := redis.ScanStruct(v, &p2); err != nil { - panic(err) + fmt.Println(err) + return } fmt.Printf("%+v\n", p2) diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go index c9635bf08..5d414ea59 100644 --- a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go @@ -20,8 +20,7 @@ import ( "testing" "time" - "github.com/garyburd/redigo/internal/redistest" - "github.com/garyburd/redigo/redis" + "github.com/mattermost/platform/Godeps/_workspace/src/github.com/garyburd/redigo/redis" ) func ExampleScript(c redis.Conn, reply interface{}, err error) { @@ -35,7 +34,7 @@ func ExampleScript(c redis.Conn, reply interface{}, err error) { } func TestScript(t *testing.T) { - c, err := redistest.Dial() + c, err := redis.DialDefaultServer() if err != nil { t.Fatalf("error connection to database, %v", err) } diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go index b959a11f4..7240fa1f3 100644 --- a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go @@ -16,7 +16,17 @@ package redis import ( "bufio" - "net" + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "strconv" + "strings" + "sync" + "testing" "time" ) @@ -24,15 +34,144 @@ func SetNowFunc(f func() time.Time) { nowFunc = f } -type nopCloser struct{ net.Conn } +var ( + ErrNegativeInt = errNegativeInt -func (nopCloser) Close() error { return nil } + serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary") + serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers") + serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`") + serverLog = ioutil.Discard + + defaultServerMu sync.Mutex + defaultServer *Server + defaultServerErr error +) -// NewConnBufio is a hook for tests. -func NewConnBufio(rw bufio.ReadWriter) Conn { - return &conn{br: rw.Reader, bw: rw.Writer, conn: nopCloser{}} +type Server struct { + name string + cmd *exec.Cmd + done chan struct{} } -var ( - ErrNegativeInt = errNegativeInt -) +func NewServer(name string, args ...string) (*Server, error) { + s := &Server{ + name: name, + cmd: exec.Command(*serverPath, args...), + done: make(chan struct{}), + } + + r, err := s.cmd.StdoutPipe() + if err != nil { + return nil, err + } + + err = s.cmd.Start() + if err != nil { + return nil, err + } + + ready := make(chan error, 1) + go s.watch(r, ready) + + select { + case err = <-ready: + case <-time.After(time.Second * 10): + err = errors.New("timeout waiting for server to start") + } + + if err != nil { + s.Stop() + return nil, err + } + + return s, nil +} + +func (s *Server) watch(r io.Reader, ready chan error) { + fmt.Fprintf(serverLog, "%d START %s \n", s.cmd.Process.Pid, s.name) + var listening bool + var text string + scn := bufio.NewScanner(r) + for scn.Scan() { + text = scn.Text() + fmt.Fprintf(serverLog, "%s\n", text) + if !listening { + if strings.Contains(text, "The server is now ready to accept connections on port") { + listening = true + ready <- nil + } + } + } + if !listening { + ready <- fmt.Errorf("server exited: %s", text) + } + s.cmd.Wait() + fmt.Fprintf(serverLog, "%d STOP %s \n", s.cmd.Process.Pid, s.name) + close(s.done) +} + +func (s *Server) Stop() { + s.cmd.Process.Signal(os.Interrupt) + <-s.done +} + +// stopDefaultServer stops the server created by DialDefaultServer. +func stopDefaultServer() { + defaultServerMu.Lock() + defer defaultServerMu.Unlock() + if defaultServer != nil { + defaultServer.Stop() + defaultServer = nil + } +} + +// startDefaultServer starts the default server if not already running. +func startDefaultServer() error { + defaultServerMu.Lock() + defer defaultServerMu.Unlock() + if defaultServer != nil || defaultServerErr != nil { + return defaultServerErr + } + defaultServer, defaultServerErr = NewServer( + "default", + "--port", strconv.Itoa(*serverBasePort), + "--save", "", + "--appendonly", "no") + return defaultServerErr +} + +// DialDefaultServer starts the test server if not already started and dials a +// connection to the server. +func DialDefaultServer() (Conn, error) { + if err := startDefaultServer(); err != nil { + return nil, err + } + c, err := Dial("tcp", fmt.Sprintf(":%d", *serverBasePort), DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second)) + if err != nil { + return nil, err + } + c.Do("FLUSHDB") + return c, nil +} + +func TestMain(m *testing.M) { + os.Exit(func() int { + flag.Parse() + + var f *os.File + if *serverLogName != "" { + var err error + f, err = os.OpenFile(*serverLogName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) + if err != nil { + fmt.Fprintf(os.Stderr, "Error opening redis-log: %v\n", err) + return 1 + } + defer f.Close() + serverLog = f + } + + defer stopDefaultServer() + + return m.Run() + }()) +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go index 1d86ee6ce..3e4bca418 100644 --- a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go @@ -16,7 +16,7 @@ package redis_test import ( "fmt" - "github.com/garyburd/redigo/redis" + "github.com/mattermost/platform/Godeps/_workspace/src/github.com/garyburd/redigo/redis" ) // zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands. |