// 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 backend_test import ( "fmt" "log" "os" "os/exec" "path/filepath" "strings" "testing" "github.com/golang/protobuf/proto" "golang.org/x/net/context" "google.golang.org/grpc" pb "tokkee.org/go-talk/grpc/proto/backend" ) var ( server string client string // TODO: probe for an unused port port = "50051" ) func init() { gopath := os.Getenv("GOPATH") if gopath == "" { panic("GOPATH not set") } server = filepath.Join(gopath, "bin", "server") client = filepath.Join(gopath, "bin", "client") for _, path := range []string{server, client} { 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/client'", server, client)) } } } // setup sets up a Backend test instance and returns a client connected to it // and a cleanup function to be called when done. func setup() (pb.BackendClient, func(), error) { srv := exec.Command(server, "--listen=:"+port) if err := srv.Start(); err != nil { return nil, nil, fmt.Errorf("failed to start server: %v", err) } conn, err := grpc.Dial("localhost:"+port, grpc.WithInsecure()) if err != nil { srv.Process.Kill() return nil, nil, fmt.Errorf("failed to connect to server: %v", err) } return pb.NewBackendClient(conn), func() { conn.Close() if err := srv.Process.Kill(); err != nil { log.Printf("Failed to kill server process: %v", err) } srv.Wait() }, nil } // TestServer runs sample queries against the backend server using the RPC // interface and checks the results. func TestServer(t *testing.T) { ctx := context.Background() c, cleanup, err := setup() if err != nil { t.Fatalf("Setup failed: %v", err) } defer cleanup() for _, test := range []struct { query string wantErr bool expected *pb.QueryReply }{ { query: "CounT 123456", expected: &pb.QueryReply{ Type: "COUNT", N: 6, }, }, { query: "count abc", expected: &pb.QueryReply{ Type: "COUNT", N: 3, }, }, { query: "count multiple words are supported as well", expected: &pb.QueryReply{ Type: "COUNT", N: 36, }, }, { query: "RANDOM 7", expected: &pb.QueryReply{ Type: "RANDOM", N: 4, }, }, { query: "RANDOM 4", expected: &pb.QueryReply{ Type: "RANDOM", N: 0, }, }, { query: "RANDOM NAN", wantErr: true, }, { query: "COUNT", wantErr: true, }, { query: "INVALID COMMAND", wantErr: true, }, } { req := &pb.QueryRequest{Query: test.query} res, err := c.Query(ctx, req) if (err != nil) != test.wantErr || !proto.Equal(res, test.expected) { e := "" if test.wantErr { e = "" } t.Errorf("c.Query(%v) = %v, %v; want %v, %s", req, res, err, test.expected, e) } } } // TestClient runs sample queries against the backend server using the client // program and checks the results. func TestClient(t *testing.T) { _, cleanup, err := setup() if err != nil { t.Fatalf("Setup failed: %v", err) } defer cleanup() for _, test := range []struct { query string wantErr bool expected string }{ { query: "count 123456", expected: "COUNT: 6", }, { query: "count abc", expected: "COUNT: 3", }, { query: "count multiple words are supported as well", expected: "COUNT: 36", }, { query: "RANDOM 7", expected: "RANDOM: 4", }, { query: "RANDOM 4", expected: "RANDOM: 0", }, { query: "RANDOM NAN", wantErr: true, }, { query: "COUNT", wantErr: true, }, { query: "INVALID COMMAND", wantErr: true, }, } { out, err := exec.Command(client, "--server=localhost:"+port, test.query).CombinedOutput() if (err != nil) != test.wantErr || !strings.HasSuffix(string(out), test.expected+"\n") { e := "" if test.wantErr { e = "" } t.Errorf("%s %s returned %q, %v; want %q, %s", client, test.query, string(out), err, test.expected, e) } } }