// Copyright (C) 2016 Sebastian 'tokkee' Harl // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package ui_test import ( "fmt" "io/ioutil" "log" "net/http" "net/url" "os" "os/exec" "path/filepath" "strings" "testing" "time" "google.golang.org/grpc" ) var ( server string ui string // TODO: probe for unused ports serverPort = "50051" uiPort = "9999" ) func init() { gopath := os.Getenv("GOPATH") if gopath == "" { panic("GOPATH not set") } server = filepath.Join(gopath, "bin", "server") ui = filepath.Join(gopath, "bin", "ui") for _, path := range []string{server, ui} { if _, err := os.Stat(path); err != nil { if !os.IsNotExist(err) { panic(err) } 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)) } } } // setup sets up a Backend test instance and UI and returns the UI address and // a cleanup function to be called when done. func setup() (string, func(), error) { srv := exec.Command(server, "--listen=:"+serverPort) if err := srv.Start(); err != nil { return "", nil, fmt.Errorf("failed to start server: %v", err) } // Wait for the server to be ready. conn, err := grpc.Dial("localhost:"+serverPort, grpc.WithInsecure()) if err != nil { srv.Process.Kill() return "", nil, fmt.Errorf("failed to connect to server: %v", err) } conn.Close() u := exec.Command(ui, "--listen=:"+uiPort, "--backend=localhost:"+serverPort) if err := u.Start(); err != nil { srv.Process.Kill() return "", nil, fmt.Errorf("failed to start UI: %v", err) } // Wait for the UI to be ready. for { if _, err := http.Get("http://localhost:" + uiPort + "/query"); err == nil { break } time.Sleep(10 * time.Millisecond) } return "http://localhost:" + uiPort, func() { if err := u.Process.Kill(); err != nil { log.Printf("Failed to kill UI process: %v", err) } u.Wait() if err := srv.Process.Kill(); err != nil { log.Printf("Failed to kill server process: %v", err) } srv.Wait() }, nil } // TestQuery runs sample queries against the /query endpoint of the UI and // checks the results. func TestQuery(t *testing.T) { addr, cleanup, err := setup() if err != nil { t.Fatalf("Setup failed: %v", err) } defer cleanup() for _, test := range []struct { query string status int expected []string }{ { query: "CounT 123456", status: 200, expected: []string{ "CounT 123456 => COUNT: 6", }, }, { query: "count abc", status: 200, expected: []string{ "count abc => COUNT: 3", }, }, { query: "count multiple words are supported as well; RANDOM 7; RANDOM 4", status: 200, expected: []string{ "count multiple words are supported as well => COUNT: 36", "RANDOM 7 => RANDOM: 4", "RANDOM 4 => RANDOM: 0", }, }, { query: "RANDOM NAN", status: 400, }, { query: "COUNT", status: 400, }, { query: "INVALID COMMAND", status: 400, }, } { params := make(url.Values) params.Add("q", test.query) res, err := http.PostForm(addr+"/query", params) if err != nil { t.Errorf("PostForm(%q, %v) = %v", addr+"/query", params, err) continue } defer res.Body.Close() raw, err := ioutil.ReadAll(res.Body) if err != nil { t.Errorf("Failed to read response body: %v", err) continue } body := string(raw) ok := true for _, expected := range test.expected { if !strings.Contains(body, expected) { ok = false break } } if res.StatusCode != test.status || !ok { t.Errorf("POST(%q, %v) = %v:\n%s\nwant status=%d; matches=%v", addr+"/query", params, res, body, test.status, test.expected) } } }