Codebase list golang-github-stvp-tempredis / upstream/latest
New upstream version 0.0~git20160122.0.83f7aae Sascha Steinbiss 6 years ago
6 changed file(s) with 276 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 The MIT License (MIT)
1
2 Copyright (c) 2015 Stovepipe Studios, Inc.
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in all
12 copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 SOFTWARE.
21
0 tempredis
1 =========
2
3 `tempredis` is a Go package that makes it easy to start and stop temporary
4 `redis-server` processes.
5
6 [API documentation](http://godoc.org/github.com/stvp/tempredis)
0 package tempredis
1
2 // Config is a key-value map of Redis config settings.
3 type Config map[string]string
4
5 func (c Config) Socket() string {
6 return c["unixsocket"]
7 }
0 package tempredis
1
2 import (
3 "github.com/garyburd/redigo/redis"
4 )
5
6 func ExampleUsage() {
7 server, err := Start(Config{"databases": "8"})
8 if err != nil {
9 panic(err)
10 }
11 defer server.Term()
12
13 conn, err := redis.Dial("unix", server.Socket())
14 defer conn.Close()
15 if err != nil {
16 panic(err)
17 }
18
19 conn.Do("SET", "foo", "bar")
20 }
0 package tempredis
1
2 import (
3 "bufio"
4 "bytes"
5 "fmt"
6 "io"
7 "io/ioutil"
8 "os"
9 "os/exec"
10 "strings"
11 "syscall"
12 )
13
14 const (
15 // ready is the string redis-server prints to stdout after starting
16 // successfully.
17 ready = "The server is now ready to accept connections"
18 )
19
20 // Server encapsulates the configuration, starting, and stopping of a single
21 // redis-server process that is reachable via a local Unix socket.
22 type Server struct {
23 dir string
24 config Config
25 cmd *exec.Cmd
26 stdout io.Reader
27 stdoutBuf bytes.Buffer
28 stderr io.Reader
29 }
30
31 // Start initiates a new redis-server process configured with the given
32 // configuration. redis-server will listen on a temporary local Unix socket. An
33 // error is returned if redis-server is unable to successfully start for any
34 // reason.
35 func Start(config Config) (server *Server, err error) {
36 if config == nil {
37 config = Config{}
38 }
39
40 dir, err := ioutil.TempDir(os.TempDir(), "tempredis")
41 if err != nil {
42 return nil, err
43 }
44
45 if _, ok := config["unixsocket"]; !ok {
46 config["unixsocket"] = fmt.Sprintf("%s/%s", dir, "redis.sock")
47 }
48 if _, ok := config["port"]; !ok {
49 config["port"] = "0"
50 }
51
52 server = &Server{
53 dir: dir,
54 config: config,
55 }
56 err = server.start()
57 if err != nil {
58 return server, err
59 }
60
61 // Block until Redis is ready to accept connections.
62 err = server.waitFor(ready)
63
64 return server, err
65 }
66
67 func (s *Server) start() (err error) {
68 if s.cmd != nil {
69 return fmt.Errorf("redis-server has already been started")
70 }
71
72 s.cmd = exec.Command("redis-server", "-")
73
74 stdin, _ := s.cmd.StdinPipe()
75 s.stdout, _ = s.cmd.StdoutPipe()
76 s.stderr, _ = s.cmd.StderrPipe()
77
78 err = s.cmd.Start()
79 if err == nil {
80 err = writeConfig(s.config, stdin)
81 }
82
83 return err
84 }
85
86 func writeConfig(config Config, w io.WriteCloser) (err error) {
87 for key, value := range config {
88 if value == "" {
89 value = "\"\""
90 }
91 _, err = fmt.Fprintf(w, "%s %s\n", key, value)
92 if err != nil {
93 return err
94 }
95 }
96 return w.Close()
97 }
98
99 // waitFor blocks until redis-server prints the given string to stdout.
100 func (s *Server) waitFor(search string) (err error) {
101 var line string
102
103 scanner := bufio.NewScanner(s.stdout)
104 for scanner.Scan() {
105 line = scanner.Text()
106 fmt.Fprintf(&s.stdoutBuf, "%s\n", line)
107 if strings.Contains(line, search) {
108 return nil
109 }
110 }
111 err = scanner.Err()
112 if err == nil {
113 err = io.EOF
114 }
115 return err
116 }
117
118 // Socket returns the full path to the local redis-server Unix socket.
119 func (s *Server) Socket() string {
120 return s.config.Socket()
121 }
122
123 // Stdout blocks until redis-server returns and then returns the full stdout
124 // output.
125 func (s *Server) Stdout() string {
126 io.Copy(&s.stdoutBuf, s.stdout)
127 return s.stdoutBuf.String()
128 }
129
130 // Stderr blocks until redis-server returns and then returns the full stdout
131 // output.
132 func (s *Server) Stderr() string {
133 bytes, _ := ioutil.ReadAll(s.stderr)
134 return string(bytes)
135 }
136
137 // Term gracefully shuts down redis-server. It returns an error if redis-server
138 // fails to terminate.
139 func (s *Server) Term() (err error) {
140 return s.signalAndCleanup(syscall.SIGTERM)
141 }
142
143 // Kill forcefully shuts down redis-server. It returns an error if redis-server
144 // fails to die.
145 func (s *Server) Kill() (err error) {
146 return s.signalAndCleanup(syscall.SIGKILL)
147 }
148
149 func (s *Server) signalAndCleanup(sig syscall.Signal) error {
150 s.cmd.Process.Signal(sig)
151 _, err := s.cmd.Process.Wait()
152 os.RemoveAll(s.dir)
153 return err
154 }
0 package tempredis
1
2 import (
3 "testing"
4
5 "github.com/garyburd/redigo/redis"
6 )
7
8 func TestServer(t *testing.T) {
9 server, err := Start(Config{"databases": "3"})
10 if err != nil {
11 t.Fatal(err)
12 }
13 defer server.Kill()
14
15 r, err := redis.Dial("unix", server.Socket())
16 if err != nil {
17 t.Fatal(err)
18 }
19 defer r.Close()
20
21 databases, err := redis.Strings(r.Do("CONFIG", "GET", "databases"))
22 if err != nil {
23 t.Fatal(err)
24 }
25 if databases[1] != "3" {
26 t.Fatalf("databases config should be 3, but got %s", databases)
27 }
28
29 if err := server.Term(); err != nil {
30 t.Fatal(err)
31 }
32 if err := server.Term(); err == nil {
33 t.Fatal("stopping an already stopped server should fail")
34 }
35 }
36
37 func TestStartWithDefaultConfig(t *testing.T) {
38 server, err := Start(nil)
39 if err != nil {
40 t.Fatal(err)
41 }
42 defer server.Kill()
43
44 r, err := redis.Dial("unix", server.Socket())
45 if err != nil {
46 t.Fatal(err)
47 }
48 defer r.Close()
49
50 _, err = r.Do("PING")
51 if err != nil {
52 t.Fatal(err)
53 }
54 }
55
56 func TestStartFail(t *testing.T) {
57 server, err := Start(Config{"oops": "borked"})
58 if err == nil {
59 t.Fatal("expected error, got nil")
60 }
61 defer server.Kill()
62 }