From 83c75c2332b11c4461b30a5aec94f6a63bcee258 Mon Sep 17 00:00:00 2001 From: Matt Fellows Date: Thu, 2 Jun 2016 21:17:28 +1000 Subject: [PATCH] test(daemon): RPC client testing --- command/pact_mock_service_cmd_test.go | 16 ++ command/root_test.go | 2 +- command/version_test.go | 20 +++ daemon/daemon.go | 29 ++-- daemon/daemon_test.go | 12 +- daemon/pact_mock_service.go | 16 +- daemon/pact_mock_service_test.go | 4 - daemon/service_test.go | 31 ++-- dsl/client.go | 81 ++++++++++ dsl/client_test.go | 213 ++++++++++++++++++++++++++ dsl/pact.go | 45 +++++- dsl/pact_test.go | 15 ++ dsl/request_test.go | 1 + main.go | 7 +- scripts/dev.sh | 1 + 15 files changed, 439 insertions(+), 54 deletions(-) create mode 100644 command/version_test.go create mode 100644 dsl/client.go create mode 100644 dsl/client_test.go create mode 100644 dsl/request_test.go diff --git a/command/pact_mock_service_cmd_test.go b/command/pact_mock_service_cmd_test.go index d47dcf0d9..a45b9d3cc 100644 --- a/command/pact_mock_service_cmd_test.go +++ b/command/pact_mock_service_cmd_test.go @@ -1 +1,17 @@ package command + +import "testing" + +func init() { + // os.Args = append(os.Args, "mock") + // os.Args = append(os.Args, "--help") +} + +func Test_PactMockServiceCommand(t *testing.T) { + err := mockServiceCmd.Help() + if err != nil { + t.Fatalf("Error: %v", err) + } + + // mockServiceCmd.Run(nil, os.Args) +} diff --git a/command/root_test.go b/command/root_test.go index 4b6e434c8..65e8fed49 100644 --- a/command/root_test.go +++ b/command/root_test.go @@ -7,7 +7,7 @@ import ( func init() { // Set CLI flags to simulate real - os.Args[1] = "version" + os.Args = []string{"version"} } func Test_RootCommand(t *testing.T) { diff --git a/command/version_test.go b/command/version_test.go new file mode 100644 index 000000000..f07c3ae2b --- /dev/null +++ b/command/version_test.go @@ -0,0 +1,20 @@ +package command + +import ( + "os" + "testing" +) + +func init() { + // Set CLI flags to simulate real + // os.Args = append(os.Args, "version") + os.Args = []string{"version"} +} + +func Test_VersionCommand(t *testing.T) { + err := versionCmd.Execute() + if err != nil { + t.Fatalf("Error: %v", err) + } + versionCmd.Run(nil, os.Args) +} diff --git a/daemon/daemon.go b/daemon/daemon.go index 0474bb0a7..94d004e99 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -70,17 +70,6 @@ func NewDaemon(pactMockServiceManager Service) *Daemon { // StartDaemon starts the daemon RPC server. func (d *Daemon) StartDaemon(port int) { fmt.Println("Starting daemon on port", port) - // rpc.Register(d) - // rpc.HandleHTTP() - // - // // Start daemon in background - // go func() { - // l, e := net.Listen("tcp", fmt.Sprintf(":%d", port)) - // if e != nil { - // log.Fatal("listen error:", e) - // } - // http.Serve(l, nil) - // }() serv := rpc.NewServer() serv.Register(d) @@ -102,8 +91,7 @@ func (d *Daemon) StartDaemon(port int) { panic(err) } go http.Serve(l, mux) - - // d.pactMockSvcManager.Start() + fmt.Println("Server started, waiting for stuff!") // Wait for sigterm signal.Notify(d.signalChan, os.Interrupt, os.Kill) @@ -114,6 +102,13 @@ func (d *Daemon) StartDaemon(port int) { fmt.Println("done") } +// StopDaemon allows clients to programmatically shuts down the running Daemon +// via RPC. +func (d *Daemon) StopDaemon(request string, reply *string) error { + d.signalChan <- os.Interrupt + return nil +} + // Shutdown ensures all services are cleanly destroyed. func (d *Daemon) Shutdown() { for _, s := range d.pactMockSvcManager.List() { @@ -126,12 +121,12 @@ func (d *Daemon) Shutdown() { // StartServer starts a mock server and returns a pointer to aPactMockServer // struct. func (d *Daemon) StartServer(request *PactMockServer, reply *PactMockServer) error { - reply = &PactMockServer{} + server := &PactMockServer{} port, svc := d.pactMockSvcManager.NewService() - reply.Port = port + server.Port = port cmd := svc.Start() - reply.Pid = cmd.Process.Pid - + server.Pid = cmd.Process.Pid + *reply = *server return nil } diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go index e4b9b23e8..9a3c52998 100644 --- a/daemon/daemon_test.go +++ b/daemon/daemon_test.go @@ -57,6 +57,14 @@ func TestNewDaemon(t *testing.T) { } } +func TestStopDaemon(t *testing.T) { + d, _ := createMockedDaemon() + port, _ := utils.GetFreePort() + go d.StartDaemon(port) + connectToDaemon(port, t) + d.Shutdown() +} + // Use this to wait for a daemon to be running prior // to running tests func connectToDaemon(port int, t *testing.T) { @@ -125,7 +133,7 @@ func TestStartServer(t *testing.T) { t.Fatalf("Error: %v", err) } - if res.Pid != 0 { + if res.Pid == 0 { t.Fatalf("Expected non-zero Pid but got: %d", res.Pid) } @@ -253,7 +261,7 @@ func TestRPCClient_StartServer(t *testing.T) { log.Fatal("rpc error:", err) } - if res.Pid != 0 { + if res.Pid == 0 { t.Fatalf("Expected non-zero Pid but got: %d", res.Pid) } diff --git a/daemon/pact_mock_service.go b/daemon/pact_mock_service.go index 25bbe4a7a..b0593bf75 100644 --- a/daemon/pact_mock_service.go +++ b/daemon/pact_mock_service.go @@ -17,18 +17,18 @@ type PactMockService struct { // NewService creates a new PactMockService with default settings. func (m *PactMockService) NewService() (int, Service) { port, _ := utils.GetFreePort() - version := 2 - dir, _ := os.Getwd() - dir = fmt.Sprintf(filepath.Join(dir, "../", "pacts")) - logDir := fmt.Sprintf(filepath.Join(dir, "../", "logs")) + // version := 2 + // dir, _ := os.Getwd() + // dir = fmt.Sprintf(filepath.Join(dir, "../", "pacts")) + // logDir := fmt.Sprintf(filepath.Join(dir, "../", "logs")) log.Println("Starting mock service on port:", port) m.Args = []string{ fmt.Sprintf("--port %d", port), - fmt.Sprintf("--pact-specification-version %d", version), - fmt.Sprintf("--pact-dir %s", dir), - fmt.Sprintf("--log-dir %s", logDir), - fmt.Sprintf("--ssl"), + // fmt.Sprintf("--pact-specification-version %d", version), + // fmt.Sprintf("--pact-dir %s", dir), + // fmt.Sprintf("--log-dir %s", logDir), + // fmt.Sprintf("--ssl"), } m.Command = getCommandPath() return port, m diff --git a/daemon/pact_mock_service_test.go b/daemon/pact_mock_service_test.go index b88f26de6..54ccbe4ad 100644 --- a/daemon/pact_mock_service_test.go +++ b/daemon/pact_mock_service_test.go @@ -13,8 +13,4 @@ func TestNewService(t *testing.T) { if svc == nil { t.Fatalf("Expected a non-nil object but got nil") } - - // if len(svc.Args) != 1 { - // t.Fatalf("Expected 1 argument (--port) but got: %d", len(svc.Args)) - // } } diff --git a/daemon/service_test.go b/daemon/service_test.go index 180e98aa1..f81ffd207 100644 --- a/daemon/service_test.go +++ b/daemon/service_test.go @@ -9,7 +9,7 @@ import ( "time" ) -var channelTimeout = time.After(50 * time.Millisecond) +var channelTimeout = 50 * time.Millisecond func createServiceManager() *ServiceManager { cs := []string{"-test.run=TestHelperProcess", "--", os.Args[0]} @@ -53,17 +53,17 @@ func TestServiceManager_removeServiceMonitor(t *testing.T) { } mgr.commandCompleteChan <- cmd - + var timeout = time.After(channelTimeout) for { select { case <-time.After(10 * time.Millisecond): if len(mgr.processes) == 0 { return } - case <-channelTimeout: + case <-timeout: if len(mgr.processes) != 0 { - t.Fatalf(`Expected 1 command to be removed from the queue. - Timed out after 500millis`) + t.Fatalf(`Expected 1 command to be removed from the queue. Have %d + Timed out after 500millis`, len(mgr.processes)) } } } @@ -74,6 +74,7 @@ func TestServiceManager_addServiceMonitor(t *testing.T) { cmd := fakeExecCommand("", true, "") cmd.Start() mgr.commandCreatedChan <- cmd + var timeout = time.After(channelTimeout) for { select { @@ -81,7 +82,7 @@ func TestServiceManager_addServiceMonitor(t *testing.T) { if len(mgr.processes) == 1 { return } - case <-channelTimeout: + case <-timeout: if len(mgr.processes) != 1 { t.Fatalf(`Expected 1 command to be added to the queue, but got: %d. Timed out after 500millis`, len(mgr.processes)) @@ -95,8 +96,8 @@ func TestServiceManager_addServiceMonitorWithDeadJob(t *testing.T) { mgr := createServiceManager() cmd := fakeExecCommand("", true, "") mgr.commandCreatedChan <- cmd + var timeout = time.After(channelTimeout) - attempts := 0 for { select { case <-time.After(10 * time.Millisecond): @@ -104,10 +105,12 @@ func TestServiceManager_addServiceMonitorWithDeadJob(t *testing.T) { t.Fatalf(`Expected 0 command to be added to the queue, but got: %d. Timed out after 5 attempts`, len(mgr.processes)) } - attempts++ - if attempts == 5 { - return + case <-timeout: + if len(mgr.processes) != 0 { + t.Fatalf(`Expected 0 command to be added to the queue, but got: %d. + Timed out after 50millis`, len(mgr.processes)) } + return } } } @@ -121,14 +124,14 @@ func TestServiceManager_Stop(t *testing.T) { } mgr.Stop(cmd.Process.Pid) - + var timeout = time.After(channelTimeout) for { select { case <-time.After(10 * time.Millisecond): if len(mgr.processes) == 0 { return } - case <-channelTimeout: + case <-timeout: if len(mgr.processes) != 0 { t.Fatalf(`Expected 1 command to be removed from the queue. Timed out after 500millis`) @@ -155,6 +158,7 @@ func TestServiceManager_List(t *testing.T) { func TestServiceManager_Start(t *testing.T) { mgr := createServiceManager() mgr.Start() + var timeout = time.After(channelTimeout) for { select { @@ -162,7 +166,7 @@ func TestServiceManager_Start(t *testing.T) { if len(mgr.processes) == 1 { return } - case <-channelTimeout: + case <-timeout: if len(mgr.processes) != 1 { t.Fatalf(`Expected 1 command to be added to the queue, but got: %d. Timed out after 500millis`, len(mgr.processes)) @@ -170,5 +174,4 @@ func TestServiceManager_Start(t *testing.T) { return } } - } diff --git a/dsl/client.go b/dsl/client.go new file mode 100644 index 000000000..8d0b9ea58 --- /dev/null +++ b/dsl/client.go @@ -0,0 +1,81 @@ +package dsl + +import ( + "fmt" + "log" + "net" + "net/rpc" + "time" + + "github.com/mefellows/pact-go/daemon" +) + +// Client is the simplified remote interface to the Pact Daemon +type Client interface { + StartServer() *daemon.PactMockServer +} + +// PactClient is the default implementation of the Client interface. +type PactClient struct { + port int +} + +func getHTTPClient(port int) (*rpc.Client, error) { + waitForPort(port) + return rpc.DialHTTP("tcp", fmt.Sprintf(":%d", port)) +} + +// Use this to wait for a daemon to be running prior +// to running tests +func waitForPort(port int) { + fmt.Printf("client - Waiting for daemon port: %d", port) + timeout := time.After(10 * time.Second) + + for { + select { + case <-timeout: + log.Fatalf("Expected server to start < 1s.") + case <-time.After(50 * time.Millisecond): + _, err := net.Dial("tcp", fmt.Sprintf(":%d", port)) + if err == nil { + return + } + } + } +} + +// StartServer starts a remote Pact Mock Server. +func (p *PactClient) StartServer() *daemon.PactMockServer { + var res daemon.PactMockServer + client, err := getHTTPClient(p.port) + if err != nil { + log.Fatal("rpc error:", err) + } + err = client.Call("Daemon.StartServer", daemon.PactMockServer{}, &res) + if err != nil { + log.Fatal("rpc error:", err) + } + return &res +} + +// ListServers starts a remote Pact Mock Server. +func (p *PactClient) ListServers() *daemon.PactListResponse { + var res daemon.PactListResponse + client, err := getHTTPClient(p.port) + err = client.Call("Daemon.ListServers", daemon.PactMockServer{}, &res) + if err != nil { + log.Fatal("rpc error:", err) + } + return &res +} + +// StopServer stops a remote Pact Mock Server. +func (p *PactClient) StopServer(server *daemon.PactMockServer) *daemon.PactMockServer { + client, err := getHTTPClient(p.port) + var res daemon.PactMockServer + err = client.Call("Daemon.StopServer", server, &res) + if err != nil { + log.Fatal("rpc error:", err) + } + return &res +} diff --git a/dsl/client_test.go b/dsl/client_test.go new file mode 100644 index 000000000..4a6761da2 --- /dev/null +++ b/dsl/client_test.go @@ -0,0 +1,213 @@ +package dsl + +import ( + "fmt" + "log" + "net" + "net/rpc" + "testing" + "time" + + "github.com/mefellows/pact-go/daemon" + "github.com/mefellows/pact-go/utils" +) + +// Use this to wait for a daemon to be running prior +// to running tests +func waitForPortInTest(port int, t *testing.T) { + timeout := time.After(1 * time.Second) + for { + select { + case <-timeout: + t.Fatalf("Expected server to start < 1s.") + case <-time.After(50 * time.Millisecond): + _, err := net.Dial("tcp", fmt.Sprintf(":%d", port)) + if err == nil { + return + } + } + } +} + +// Use this to wait for a daemon to stop after running a test. +func waitForDaemonToShutdown(port int, t *testing.T) { + req := "" + res := "" + // var req interface{} + + waitForPortInTest(port, t) + + fmt.Println("Sending remote shutdown signal...") + client, err := rpc.DialHTTP("tcp", fmt.Sprintf(":%d", port)) + + err = client.Call("Daemon.StopDaemon", &req, &res) + // err = client.Call("Daemon.StopDaemon", req, &res) + if err != nil { + log.Fatal("rpc error:", err) + } + fmt.Println(res) + + t.Logf("Waiting for deamon to shutdown before next test") + timeout := time.After(1 * time.Second) + for { + select { + case <-timeout: + t.Fatalf("Expected server to shutdown < 1s.") + case <-time.After(50 * time.Millisecond): + conn, err := net.Dial("tcp", fmt.Sprintf(":%d", port)) + conn.SetReadDeadline(time.Now()) + defer conn.Close() + if err != nil { + return + } + buffer := make([]byte, 8) + _, err = conn.Read(buffer) + if err != nil { + return + } + } + } +} + +func createDaemon(port int) *daemon.Daemon { + s := &daemon.PactMockService{} + _, svc := s.NewService() + d := daemon.NewDaemon(svc) + go d.StartDaemon(port) + return d +} + +// func TestRPCClient_ListFail(t *testing.T) { +// client := &PactClient{ /* don't supply port */ } +// +// } + +// Integration style test: Can a client hit each endpoint? +func TestRPCClient_List(t *testing.T) { + port, _ := utils.GetFreePort() + createDaemon(port) + waitForPortInTest(port, t) + defer waitForDaemonToShutdown(port, t) + client := &PactClient{port: port} + server := client.StartServer() + + waitForPortInTest(server.Port, t) + + s := client.ListServers() + + if len(s.Servers) != 1 { + t.Fatalf("Expected 1 server to be running, got %d", len(s.Servers)) + } + + // client, err := rpc.DialHTTP("tcp", fmt.Sprintf(":%d", port)) + // var res daemon.PactMockServer + // err = client.Call("Daemon.StartServer", daemon.PactMockServer{}, &res) + // if err != nil { + // log.Fatal("rpc error:", err) + // } + // + // waitForPortInTest(res.Port, t) + // + // client, err = rpc.DialHTTP("tcp", fmt.Sprintf(":%d", port)) + // var res2 daemon.PactListResponse + // err = client.Call("Daemon.ListServers", daemon.PactMockServer{}, &res2) + // if err != nil { + // log.Fatal("rpc error:", err) + // } +} + +// Integration style test: Can a client hit each endpoint? +func TestRPCClient_StartServer(t *testing.T) { + port, _ := utils.GetFreePort() + createDaemon(port) + waitForPortInTest(port, t) + + client, err := rpc.DialHTTP("tcp", fmt.Sprintf(":%d", port)) + var res daemon.PactMockServer + err = client.Call("Daemon.StartServer", daemon.PactMockServer{}, &res) + if err != nil { + log.Fatal("rpc error:", err) + } + + <-time.After(10 * time.Second) + waitForDaemonToShutdown(port, t) +} + +/* +// Integration style test: Can a client hit each endpoint? +func TestRPCClient_StopServer(t *testing.T) { + port, _ := utils.GetFreePort() + // defer waitForDaemonToShutdown(port, daemon, t) + d := createDaemon(port) + waitForPortInTest(port, t) + + var cmd *exec.Cmd + for _, s := range manager.List() { + cmd = s + } + request := daemon.PactMockServer{ + Pid: cmd.Process.Pid, + } + + client, err := rpc.DialHTTP("tcp", fmt.Sprintf(":%d", port)) + var res *daemon.PactMockServer + err = client.Call("Daemon.StopServer", request, &res) + if err != nil { + log.Fatal("rpc error:", err) + } + + if res.Pid != cmd.Process.Pid { + t.Fatalf("Expected PID to match request %d but got: %d", cmd.Process.Pid, res.Pid) + } + + if res.Port != 0 { + t.Fatalf("Expected non-zero port but got: %d", res.Port) + } +} + +// Integration style test: Can a client hit each endpoint? +func TestRPCClient_Verify(t *testing.T) { + port, _ := utils.GetFreePort() + d := createDaemon(port) + // defer waitForDaemonToShutdown(port, daemon, t) + waitForPortInTest(port, t) + + client, err := rpc.DialHTTP("tcp", fmt.Sprintf(":%d", port)) + var res daemon.PactResponse + err = client.Call("Daemon.Verify", &daemon.VerifyRequest{}, &res) + if err != nil { + log.Fatal("rpc error:", err) + } + + if res.ExitCode != 0 { + t.Fatalf("Expected exit code to be 0, got: %d", res.ExitCode) + } + if res.Message != "" { + t.Fatalf("Expected message to be blank but got: %s", res.Message) + } +} + +// Integration style test: Can a client hit each endpoint? +func TestRPCClient_Publish(t *testing.T) { + port, _ := utils.GetFreePort() + d := createDaemon(port) + // defer waitForDaemonToShutdown(port, daemon, t) + go d.StartDaemon(port) + waitForPortInTest(port, t) + + client, err := rpc.DialHTTP("tcp", fmt.Sprintf(":%d", port)) + var res PactResponse + err = client.Call("Daemon.Publish", &PublishRequest{}, &res) + if err != nil { + log.Fatal("rpc error:", err) + } + + if res.ExitCode != 0 { + t.Fatalf("Expected exit code to be 0, got: %d", res.ExitCode) + } + + if res.Message != "" { + t.Fatalf("Expected message to be blank but got: %s", res.Message) + } +} +*/ diff --git a/dsl/pact.go b/dsl/pact.go index 308362ced..c7b1a8d5d 100644 --- a/dsl/pact.go +++ b/dsl/pact.go @@ -1,6 +1,12 @@ package dsl -import "fmt" +import ( + "fmt" + "log" + "net/rpc" + + "github.com/mefellows/pact-go/daemon" +) // Pact is the container structure to run the Consumer Pact test cases. type Pact interface { @@ -24,7 +30,42 @@ type Pact interface { } // PactConsumer is the main implementation of the Pact interface. -type PactConsumer struct{} +type PactConsumer struct { + server *daemon.PactMockServer +} + +// Before starts the Pact Mock Server before each test suite. + +// TODO: Turn these calls into a client library not exposing the RPC junk. +// Move the daemon stuff into another package? + +func (p *PactConsumer) Before() Pact { + client, err := rpc.DialHTTP("tcp", fmt.Sprintf(":%d", 6666)) + var res daemon.PactMockServer + err = client.Call("Daemon.StartServer", daemon.PactMockServer{}, &res) + if err != nil { + log.Fatal("rpc error:", err) + } + + log.Println("Have pact mock:", &res) + p.server = &res + return p +} + +// After stops the Pact Mock Server after each test suite. +func (p *PactConsumer) After() Pact { + client, err := rpc.DialHTTP("tcp", fmt.Sprintf(":%d", 6666)) + var res daemon.PactMockServer + + fmt.Println("CLIENT Stopping:", p.server.Pid) + err = client.Call("Daemon.StopServer", p.server, &res) + if err != nil { + log.Println("ERror!!", err) + } + + log.Println("Have pact mock stop response:", res) + return p +} // Given specifies a provider state. Optional. func (p *PactConsumer) Given(state string) Pact { diff --git a/dsl/pact_test.go b/dsl/pact_test.go index 1cec3f0de..1ee506a91 100644 --- a/dsl/pact_test.go +++ b/dsl/pact_test.go @@ -14,6 +14,21 @@ func simplePact() (pact *PactMock) { return } +// +// func TestPact_Before(t *testing.T) { +// pact := &PactConsumer{} +// pact.Before() +// <-time.After(1 * time.Second) +// pact.After() +// +// // Can I hit stuff? +// +// // +// +// } + +// + func providerStatesPact() (pact *PactMock) { pact = &PactMock{} pact. diff --git a/dsl/request_test.go b/dsl/request_test.go new file mode 100644 index 000000000..f17392d4a --- /dev/null +++ b/dsl/request_test.go @@ -0,0 +1 @@ +package dsl diff --git a/main.go b/main.go index 70aef47ae..46a7c5e0c 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,7 @@ package main -import ( - "fmt" - - "github.com/mefellows/pact-go/command" -) +import "github.com/mefellows/pact-go/command" func main() { command.Execute() - fmt.Printf("Testing") } diff --git a/scripts/dev.sh b/scripts/dev.sh index f44ef72ab..d20f3d1bb 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -2,6 +2,7 @@ set -e +./scripts/build.sh ./scripts/package.sh # Setup dev