Code

Add an initial version of sample code for my Go talk.
[go-talk.git] / grpc / integration / ui / ui_test.go
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 ui_test
27 import (
28         "fmt"
29         "io/ioutil"
30         "log"
31         "net/http"
32         "net/url"
33         "os"
34         "os/exec"
35         "path/filepath"
36         "strings"
37         "testing"
38         "time"
40         "google.golang.org/grpc"
41 )
43 var (
44         server string
45         ui     string
47         // TODO: probe for unused ports
48         serverPort = "50051"
49         uiPort     = "9999"
50 )
52 func init() {
53         gopath := os.Getenv("GOPATH")
54         if gopath == "" {
55                 panic("GOPATH not set")
56         }
58         server = filepath.Join(gopath, "bin", "server")
59         ui = filepath.Join(gopath, "bin", "ui")
61         for _, path := range []string{server, ui} {
62                 if _, err := os.Stat(path); err != nil {
63                         if !os.IsNotExist(err) {
64                                 panic(err)
65                         }
66                         panic(fmt.Sprintf("%s and/or %s not found; run 'go install tokkee.org/go-talk/grpc/server tokkee.org/go-talk/grpc/ui'", server, ui))
67                 }
68         }
69 }
71 // setup sets up a Backend test instance and UI and returns the UI address and
72 // a cleanup function to be called when done.
73 func setup() (string, func(), error) {
74         srv := exec.Command(server, "--listen=:"+serverPort)
75         if err := srv.Start(); err != nil {
76                 return "", nil, fmt.Errorf("failed to start server: %v", err)
77         }
78         // Wait for the server to be ready.
79         conn, err := grpc.Dial("localhost:"+serverPort, grpc.WithInsecure())
80         if err != nil {
81                 srv.Process.Kill()
82                 return "", nil, fmt.Errorf("failed to connect to server: %v", err)
83         }
84         conn.Close()
86         u := exec.Command(ui, "--listen=:"+uiPort, "--backend=localhost:"+serverPort)
87         if err := u.Start(); err != nil {
88                 srv.Process.Kill()
89                 return "", nil, fmt.Errorf("failed to start UI: %v", err)
90         }
91         // Wait for the UI to be ready.
92         for {
93                 if _, err := http.Get("http://localhost:" + uiPort + "/query"); err == nil {
94                         break
95                 }
96                 time.Sleep(10 * time.Millisecond)
97         }
99         return "http://localhost:" + uiPort, func() {
100                 if err := u.Process.Kill(); err != nil {
101                         log.Printf("Failed to kill UI process: %v", err)
102                 }
103                 u.Wait()
105                 if err := srv.Process.Kill(); err != nil {
106                         log.Printf("Failed to kill server process: %v", err)
107                 }
108                 srv.Wait()
109         }, nil
112 // TestQuery runs sample queries against the /query endpoint of the UI and
113 // checks the results.
114 func TestQuery(t *testing.T) {
115         addr, cleanup, err := setup()
116         if err != nil {
117                 t.Fatalf("Setup failed: %v", err)
118         }
119         defer cleanup()
121         for _, test := range []struct {
122                 query    string
123                 status   int
124                 expected []string
125         }{
126                 {
127                         query:  "CounT 123456",
128                         status: 200,
129                         expected: []string{
130                                 "<b>CounT 123456 =></b> COUNT: 6",
131                         },
132                 },
133                 {
134                         query:  "count abc",
135                         status: 200,
136                         expected: []string{
137                                 "<b>count abc =></b> COUNT: 3",
138                         },
139                 },
140                 {
141                         query:  "count multiple words are supported as well; RANDOM 7; RANDOM 4",
142                         status: 200,
143                         expected: []string{
144                                 "<b>count multiple words are supported as well =></b> COUNT: 36",
145                                 "<b>RANDOM 7 =></b> RANDOM: 4",
146                                 "<b>RANDOM 4 =></b> RANDOM: 0",
147                         },
148                 },
149                 {
150                         query:  "RANDOM NAN",
151                         status: 400,
152                 },
153                 {
154                         query:  "COUNT",
155                         status: 400,
156                 },
157                 {
158                         query:  "INVALID COMMAND",
159                         status: 400,
160                 },
161         } {
162                 params := make(url.Values)
163                 params.Add("q", test.query)
164                 res, err := http.PostForm(addr+"/query", params)
165                 if err != nil {
166                         t.Errorf("PostForm(%q, %v) = %v", addr+"/query", params, err)
167                         continue
168                 }
169                 defer res.Body.Close()
171                 raw, err := ioutil.ReadAll(res.Body)
172                 if err != nil {
173                         t.Errorf("Failed to read response body: %v", err)
174                         continue
175                 }
176                 body := string(raw)
178                 ok := true
179                 for _, expected := range test.expected {
180                         if !strings.Contains(body, expected) {
181                                 ok = false
182                                 break
183                         }
184                 }
185                 if res.StatusCode != test.status || !ok {
186                         t.Errorf("POST(%q, %v) = %v:\n%s\nwant status=%d; matches=%v", addr+"/query", params, res, body, test.status, test.expected)
187                 }
188         }