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
110 }
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 }
189 }