1 // Copyright (C) 2016 Sebastian 'tokkee' Harl <sh@tokkee.org>
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions
6 // are met:
7 // 1. Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 // 2. Redistributions in binary form must reproduce the above copyright
10 // notice, this list of conditions and the following disclaimer in the
11 // documentation and/or other materials provided with the distribution.
12 //
13 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14 // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
15 // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
17 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
20 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
21 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
22 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
23 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 package backend_test
27 import (
28 "fmt"
29 "log"
30 "os"
31 "os/exec"
32 "path/filepath"
33 "strings"
34 "testing"
36 "github.com/golang/protobuf/proto"
37 "golang.org/x/net/context"
38 "google.golang.org/grpc"
40 pb "tokkee.org/go-talk/grpc/proto/backend"
41 )
43 var (
44 server string
45 client string
47 // TODO: probe for an unused port
48 port = "50051"
49 )
51 func init() {
52 gopath := os.Getenv("GOPATH")
53 if gopath == "" {
54 panic("GOPATH not set")
55 }
57 server = filepath.Join(gopath, "bin", "server")
58 client = filepath.Join(gopath, "bin", "client")
60 for _, path := range []string{server, client} {
61 if _, err := os.Stat(path); err != nil {
62 if !os.IsNotExist(err) {
63 panic(err)
64 }
65 panic(fmt.Sprintf("%s and/or %s not found; run 'go install tokkee.org/go-talk/grpc/server tokkee.org/go-talk/grpc/client'", server, client))
66 }
67 }
68 }
70 // setup sets up a Backend test instance and returns a client connected to it
71 // and a cleanup function to be called when done.
72 func setup() (pb.BackendClient, func(), error) {
73 srv := exec.Command(server, "--listen=:"+port)
74 if err := srv.Start(); err != nil {
75 return nil, nil, fmt.Errorf("failed to start server: %v", err)
76 }
78 conn, err := grpc.Dial("localhost:"+port, grpc.WithInsecure())
79 if err != nil {
80 srv.Process.Kill()
81 return nil, nil, fmt.Errorf("failed to connect to server: %v", err)
82 }
84 return pb.NewBackendClient(conn), func() {
85 conn.Close()
87 if err := srv.Process.Kill(); err != nil {
88 log.Printf("Failed to kill server process: %v", err)
89 }
90 srv.Wait()
91 }, nil
92 }
94 // TestServer runs sample queries against the backend server using the RPC
95 // interface and checks the results.
96 func TestServer(t *testing.T) {
97 ctx := context.Background()
98 c, cleanup, err := setup()
99 if err != nil {
100 t.Fatalf("Setup failed: %v", err)
101 }
102 defer cleanup()
104 for _, test := range []struct {
105 query string
106 wantErr bool
107 expected *pb.QueryReply
108 }{
109 {
110 query: "CounT 123456",
111 expected: &pb.QueryReply{
112 Type: "COUNT",
113 N: 6,
114 },
115 },
116 {
117 query: "count abc",
118 expected: &pb.QueryReply{
119 Type: "COUNT",
120 N: 3,
121 },
122 },
123 {
124 query: "count multiple words are supported as well",
125 expected: &pb.QueryReply{
126 Type: "COUNT",
127 N: 36,
128 },
129 },
130 {
131 query: "RANDOM 7",
132 expected: &pb.QueryReply{
133 Type: "RANDOM",
134 N: 4,
135 },
136 },
137 {
138 query: "RANDOM 4",
139 expected: &pb.QueryReply{
140 Type: "RANDOM",
141 N: 0,
142 },
143 },
144 {
145 query: "RANDOM NAN",
146 wantErr: true,
147 },
148 {
149 query: "COUNT",
150 wantErr: true,
151 },
152 {
153 query: "INVALID COMMAND",
154 wantErr: true,
155 },
156 } {
157 req := &pb.QueryRequest{Query: test.query}
158 res, err := c.Query(ctx, req)
159 if (err != nil) != test.wantErr || !proto.Equal(res, test.expected) {
160 e := "<nil>"
161 if test.wantErr {
162 e = "<ERR>"
163 }
164 t.Errorf("c.Query(%v) = %v, %v; want %v, %s", req, res, err, test.expected, e)
165 }
166 }
167 }
169 // TestClient runs sample queries against the backend server using the client
170 // program and checks the results.
171 func TestClient(t *testing.T) {
172 _, cleanup, err := setup()
173 if err != nil {
174 t.Fatalf("Setup failed: %v", err)
175 }
176 defer cleanup()
178 for _, test := range []struct {
179 query string
180 wantErr bool
181 expected string
182 }{
183 {
184 query: "count 123456",
185 expected: "COUNT: 6",
186 },
187 {
188 query: "count abc",
189 expected: "COUNT: 3",
190 },
191 {
192 query: "count multiple words are supported as well",
193 expected: "COUNT: 36",
194 },
195 {
196 query: "RANDOM 7",
197 expected: "RANDOM: 4",
198 },
199 {
200 query: "RANDOM 4",
201 expected: "RANDOM: 0",
202 },
203 {
204 query: "RANDOM NAN",
205 wantErr: true,
206 },
207 {
208 query: "COUNT",
209 wantErr: true,
210 },
211 {
212 query: "INVALID COMMAND",
213 wantErr: true,
214 },
215 } {
216 out, err := exec.Command(client, "--server=localhost:"+port, test.query).CombinedOutput()
217 if (err != nil) != test.wantErr || !strings.HasSuffix(string(out), test.expected+"\n") {
218 e := "<nil>"
219 if test.wantErr {
220 e = "<ERR>"
221 }
222 t.Errorf("%s %s returned %q, %v; want %q, %s", client, test.query, string(out), err, test.expected, e)
223 }
224 }
225 }