Skip to content

Commit

Permalink
feat(daemonless): refactor out daemon code
Browse files Browse the repository at this point in the history
- Removes 'daemon' command from CLI
- No longer creates binary for a release
- Expects CLI tools to be on the path (currently no checks for version compatibility etc.)
- Updated examples and all tests passing

BREAKING CHANGE
  • Loading branch information
mefellows committed Mar 19, 2018
1 parent b330414 commit 5a7529a
Show file tree
Hide file tree
Showing 1,596 changed files with 402,142 additions and 577 deletions.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ any new packages are added to `vendor.yml` prior to patching.
## Integration Tests

Before releasing a new version, in addition to the standard (isolated) tests
we smoke test the key features against a running Daemon and Broker.
we smoke test the key features against the latest code and Broker.

Run `make pact` to start the daemon and run integration tests.
Run `make pact` to run the integration tests.
6 changes: 1 addition & 5 deletions client/mock_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package client

import (
"fmt"
"path/filepath"

"github.com/kardianos/osext"
)

// MockService is a wrapper for the Pact Mock Service.
Expand All @@ -24,6 +21,5 @@ func (m *MockService) NewService(args []string) Service {
}

func getMockServiceCommandPath() string {
dir, _ := osext.ExecutableFolder()
return fmt.Sprintf(filepath.Join(dir, "pact", "bin", "pact-mock-service"))
return fmt.Sprintf("pact-mock-service")
}
12 changes: 10 additions & 2 deletions client/service.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
/*
Package client implements the raw interface to the Pact CLI tools: The Pact Mock Service and Provider Verification
"binaries."
See https://github.com/pact-foundation/pact-provider-verifier and
https://github.com/bethesque/pact-mock_service for more on the Ruby "binaries".
NOTE: The ultimate goal here is to replace the Ruby dependencies with a shared
library (Pact Reference - (https://github.com/pact-foundation/pact-reference/).
*/
package client

import (
"io"
"os/exec"
)

Expand All @@ -13,6 +22,5 @@ type Service interface {
List() map[int]*exec.Cmd
Command() *exec.Cmd
Start() *exec.Cmd
Run(io.Writer) (*exec.Cmd, error)
NewService(args []string) Service
}
15 changes: 0 additions & 15 deletions client/service_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package client

import (
"bufio"
"io"
"log"
"os"
"os/exec"
"strings"
"time"
)

Expand Down Expand Up @@ -100,19 +98,6 @@ func (s *ServiceManager) List() map[int]*exec.Cmd {
return s.processes
}

// Run runs a service synchronously and log its output to the given Pipe.
func (s *ServiceManager) Run(w io.Writer) (*exec.Cmd, error) {
log.Println("[DEBUG] starting service")
log.Printf("[DEBUG] %s %s\n", s.Cmd, strings.Join(s.Args, " "))
cmd := exec.Command(s.Cmd, s.Args...)
cmd.Env = s.Env
cmd.Stdout = w
cmd.Stderr = w
err := cmd.Run()

return cmd, err
}

func (s *ServiceManager) Command() *exec.Cmd {
cmd := exec.Command(s.Cmd, s.Args...)
cmd.Env = s.Env
Expand Down
6 changes: 1 addition & 5 deletions client/verification_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ package client
import (
"fmt"
"log"
"path/filepath"

"github.com/kardianos/osext"
)

// VerificationService is a wrapper for the Pact Provider Verifier Service.
Expand Down Expand Up @@ -35,6 +32,5 @@ func (v *VerificationService) NewService(args []string) Service {
}

func getVerifierCommandPath() string {
dir, _ := osext.ExecutableFolder()
return fmt.Sprintf(filepath.Join(dir, "pact", "bin", "pact-provider-verifier"))
return fmt.Sprintf("pact-provider-verifier")
}
2 changes: 1 addition & 1 deletion command/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ var installCmd = &cobra.Command{
}

func init() {
installCmd.Flags().StringVarP(&path, "path", "p", "/opt/pact", "Local daemon port to listen on")
installCmd.Flags().StringVarP(&path, "path", "p", "/opt/pact", "Location to install the Pact CLI tools")
RootCmd.AddCommand(installCmd)
}
2 changes: 1 addition & 1 deletion command/root.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package command contains the basic CLI commands to run Pact Go as a daemon.
// Package command contains the basic CLI commands to run Pact Go.
package command

import (
Expand Down
11 changes: 3 additions & 8 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ A typical consumer-side test would look something like this:
func TestLogin(t *testing.T) {
// Create Pact, connecting to local Daemon
// Ensure the port matches the daemon port!
// Create Pact client
pact := Pact{
Port: 6666,
Consumer: "My Consumer",
Provider: "My Provider",
}
Expand Down Expand Up @@ -123,11 +121,8 @@ Provider side Pact testing, involves verifying that the contract - the Pact file
A typical Provider side test would like something like:
func TestProvider_PactContract(t *testing.T) {
// Create Pact, connecting to local Daemon
// Ensure the port matches the daemon port!
pact := Pact{
Port: 6666,
}
// Create Pact
pact := Pact{}
go startMyAPI("http://localhost:8000")
pact.VerifyProvider(types.VerifyRequest{
Expand Down
154 changes: 127 additions & 27 deletions dsl/client.go
Original file line number Diff line number Diff line change
@@ -1,68 +1,169 @@
package dsl

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net"
"net/rpc"
"net/url"
"regexp"
"strconv"
"strings"
"time"

"github.com/pact-foundation/pact-go/client"
"github.com/pact-foundation/pact-go/types"
)

var (
timeoutDuration = 10 * time.Second
)

// PactClient is the default implementation of the Client interface.
// PactClient is the main interface into starting/stopping
// the underlying Pact CLI subsystem
type PactClient struct {
// Address the Daemon is listening on
Address string
pactMockSvcManager client.Service
verificationSvcManager client.Service

// Track mock servers
Servers []MockService

// Track stub servers
// Stubs []StubService
// Network Daemon is listening on
Network string

// Address the Daemon is listening on
Address string
}

// StartServer starts a remote Pact Mock Server.
func (p *PactClient) StartServer(args []string, port int) *MockService {
log.Println("[DEBUG] client: starting a server")
// NewClient creates a new Pact client manager
func NewClient(MockServiceManager client.Service, verificationServiceManager client.Service) *PactClient {
MockServiceManager.Setup()
verificationServiceManager.Setup()

return nil
return &PactClient{
pactMockSvcManager: MockServiceManager,
verificationSvcManager: verificationServiceManager,
}
}

// StartServer starts a remote Pact Mock Server.
func (p *PactClient) StartServer(args []string, port int) *types.MockServer {
log.Println("[DEBUG] client: starting a server with args:", args, "port:", port)
args = append(args, []string{"--port", strconv.Itoa(port)}...)
svc := p.pactMockSvcManager.NewService(args)
cmd := svc.Start()

waitForPort(port, p.getNetworkInterface(), p.Address, fmt.Sprintf(`Timed out waiting for Mock Server to
start on port %d - are you sure it's running?`, port))

return &types.MockServer{
Pid: cmd.Process.Pid,
Port: port,
}
}

// ListServers list all available Mock Servers
func (p *PactClient) ListServers(args []string, port int) []*types.MockServer {
// ListServers lists all known Mock Servers
func (p *PactClient) ListServers() []*types.MockServer {
log.Println("[DEBUG] client: starting a server")

return nil
var servers []*types.MockServer

for port, s := range p.pactMockSvcManager.List() {
servers = append(servers, &types.MockServer{
Pid: s.Process.Pid,
Port: port,
})
}

return servers
}

// StopServer stops a remote Pact Mock Server.
func (p *PactClient) StopServer(server *types.MockServer) *types.MockServer {
func (p *PactClient) StopServer(server *types.MockServer) (*types.MockServer, error) {
log.Println("[DEBUG] client: stop server")

return nil
// TODO: Need to be able to get a non-zero exit code here!
_, server.Error = p.pactMockSvcManager.Stop(server.Pid)
return server, server.Error
}

// RemoveAllServers stops all remote Pact Mock Servers.
func (p *PactClient) RemoveAllServers(server *types.MockServer) *[]types.MockServer {
log.Println("[DEBUG] client: stop server")

for _, s := range p.verificationSvcManager.List() {
if s != nil {
p.pactMockSvcManager.Stop(s.Process.Pid)
}
}
return nil
}

// VerifyProvider runs the verification process against a running Provider.
func (p *PactClient) VerifyProvider(request types.VerifyRequest) (types.ProviderVerifierResponse, error) {
log.Println("[DEBUG] client: verifying a provider")
var response types.ProviderVerifierResponse

// Convert request into flags, and validate request
err := request.Validate()
if err != nil {
return response, err
}

port := getPort(request.ProviderBaseURL)

waitForPort(port, p.getNetworkInterface(), p.Address, fmt.Sprintf(`Timed out waiting for Provider API to start
on port %d - are you sure it's running?`, port))

// Run command, splitting out stderr and stdout. The command can fail for
// several reasons:
// 1. Command is unable to run at all.
// 2. Command runs, but fails for unknown reason.
// 3. Command runs, and returns exit status 1 because the tests fail.
//
// First, attempt to decode the response of the stdout.
// If that is successful, we are at case 3. Return stdout as message, no error.
// Else, return an error, include stderr and stdout in both the error and message.
svc := p.verificationSvcManager.NewService(request.Args)
cmd := svc.Command()

return types.ProviderVerifierResponse{}, nil
stdOutPipe, err := cmd.StdoutPipe()
if err != nil {
return response, err
}
stdErrPipe, err := cmd.StderrPipe()
if err != nil {
return response, err
}
err = cmd.Start()
if err != nil {
return response, err
}
stdOut, err := ioutil.ReadAll(stdOutPipe)
if err != nil {
return response, err
}
stdErr, err := ioutil.ReadAll(stdErrPipe)
if err != nil {
return response, err
}

err = cmd.Wait()

decoder := json.NewDecoder(bytes.NewReader(stdOut))

dErr := decoder.Decode(&response)
if dErr == nil {
return response, err
}

if err == nil {
err = dErr
}

return response, fmt.Errorf("error verifying provider: %s\n\nSTDERR:\n%s\n\nSTDOUT:\n%s", err, stdErr, stdOut)
}

// Get a port given a URL
Expand All @@ -84,17 +185,7 @@ func getPort(rawURL string) int {
return -1
}

func getHTTPClient(port int, network string, address string) (*rpc.Client, error) {
log.Println("[DEBUG] creating an HTTP client")
err := waitForPort(port, network, address, fmt.Sprintf(`Timed out waiting for Daemon on port %d - are you
sure it's running?`, port))
if err != nil {
return nil, err
}
return rpc.DialHTTP(network, fmt.Sprintf("%s:%d", address, port))
}

// Use this to wait for a daemon to be running prior
// Use this to wait for a port to be running prior
// to running tests.
var waitForPort = func(port int, network string, address string, message string) error {
log.Println("[DEBUG] waiting for port", port, "to become available")
Expand Down Expand Up @@ -130,3 +221,12 @@ func sanitiseRubyResponse(response string) string {

return s
}

// getNetworkInterface returns a default interface to communicate to the Daemon
// if none specified
func (p *PactClient) getNetworkInterface() string {
if p.Network == "" {
return "tcp"
}
return p.Network
}
Loading

0 comments on commit 5a7529a

Please sign in to comment.