Skip to content

Commit

Permalink
feat(dsl): have dsl pass port for mock server instead of mock server
Browse files Browse the repository at this point in the history
In order to be friendly with Docker and Jenkins pipelines by exposing
specified ports the Pact struct will have an optional field for mock
service ports.

The default action is to use the exact same function to find a free
port. But if the field `AllowedMockServicePorts` is passed then it will
limit the ports to those specified

Breaking Changes:
- Changed signature of `daemon.NewService()`. It no longer returns a port as it is
now determined before `daemon.NewService()` is called.
- Changed signature of `client.StartServer()` to accept port. Previously it got the
port from the return of `daemon.NewService()` but the DSL desides the port now
  • Loading branch information
zbintliff committed Oct 6, 2017
1 parent e5ab27b commit 153b949
Show file tree
Hide file tree
Showing 13 changed files with 302 additions and 43 deletions.
5 changes: 2 additions & 3 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@ func (d Daemon) Shutdown() {
func (d Daemon) StartServer(request types.MockServer, reply *types.MockServer) error {
log.Println("[DEBUG] daemon - starting mock server with args:", request.Args)
server := &types.MockServer{}
port, svc := d.pactMockSvcManager.NewService(request.Args)
server.Port = port
svc := d.pactMockSvcManager.NewService(request.Args)
server.Status = -1
cmd := svc.Start()
server.Pid = cmd.Process.Pid
Expand All @@ -124,7 +123,7 @@ func (d Daemon) VerifyProvider(request types.VerifyRequest, reply *types.Command
}

var out bytes.Buffer
_, svc := d.verificationSvcManager.NewService(request.Args)
svc := d.verificationSvcManager.NewService(request.Args)
cmd, err := svc.Run(&out)

if err == nil && cmd.ProcessState != nil && cmd.ProcessState.Success() {
Expand Down
11 changes: 2 additions & 9 deletions daemon/mock_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package daemon

import (
"fmt"
"log"
"path/filepath"

"github.com/kardianos/osext"
"github.com/pact-foundation/pact-go/utils"
)

// MockService is a wrapper for the Pact Mock Service.
Expand All @@ -15,19 +13,14 @@ type MockService struct {
}

// NewService creates a new MockService with default settings.
func (m *MockService) NewService(args []string) (int, Service) {
port, _ := utils.GetFreePort()
log.Println("[DEBUG] starting mock service on port:", port)

func (m *MockService) NewService(args []string) Service {
m.Args = []string{
"service",
"--port",
fmt.Sprintf("%d", port),
}
m.Args = append(m.Args, args...)

m.Command = getMockServiceCommandPath()
return port, m
return m
}

func getMockServiceCommandPath() string {
Expand Down
8 changes: 2 additions & 6 deletions daemon/mock_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@ import "testing"

func TestMockService_NewService(t *testing.T) {
s := &MockService{}
port, svc := s.NewService([]string{"--foo"})

if port <= 0 {
t.Fatalf("Expected non-zero port but got: %d", port)
}
svc := s.NewService([]string{"--foo"})

if svc == nil {
t.Fatalf("Expected a non-nil object but got nil")
}

if s.Args[3] != "--foo" {
if s.Args[1] != "--foo" {
t.Fatalf("Expected '--foo' argument to be passed")
}
}
2 changes: 1 addition & 1 deletion daemon/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ type Service interface {
List() map[int]*exec.Cmd
Start() *exec.Cmd
Run(io.Writer) (*exec.Cmd, error)
NewService(args []string) (int, Service)
NewService(args []string) Service
}
4 changes: 2 additions & 2 deletions daemon/service_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,6 @@ func (s *ServiceMock) Start() *exec.Cmd {
}

// NewService creates a new MockService with default settings.
func (s *ServiceMock) NewService(args []string) (int, Service) {
return s.ServicePort, s
func (s *ServiceMock) NewService(args []string) Service {
return s
}
4 changes: 2 additions & 2 deletions daemon/verification_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ type VerificationService struct {
// --broker-password
// --publish_verification_results
// --provider_app_version
func (m *VerificationService) NewService(args []string) (int, Service) {
func (m *VerificationService) NewService(args []string) Service {
log.Printf("[DEBUG] starting verification service with args: %v\n", args)

m.Args = args
m.Command = getVerifierCommandPath()
return -1, m
return m
}

func getVerifierCommandPath() string {
Expand Down
6 changes: 1 addition & 5 deletions daemon/verification_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import "testing"

func TestVerificationService_NewService(t *testing.T) {
s := &VerificationService{}
port, svc := s.NewService([]string{"--foo bar"})

if port != -1 {
t.Fatalf("Expected port to be -1 but got: %d", port)
}
svc := s.NewService([]string{"--foo bar"})

if svc == nil {
t.Fatalf("Expected a non-nil object but got nil")
Expand Down
8 changes: 5 additions & 3 deletions dsl/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,22 @@ var waitForPort = func(port int, network string, address string, message string)
}

// StartServer starts a remote Pact Mock Server.
func (p *PactClient) StartServer(args []string) *types.MockServer {
func (p *PactClient) StartServer(args []string, port int) *types.MockServer {
log.Println("[DEBUG] client: starting a server")
var res types.MockServer
client, err := getHTTPClient(p.Port, p.getNetworkInterface(), p.Address)
if err == nil {
args = append(args, []string{"--port", strconv.Itoa(port)}...)
err = client.Call(commandStartServer, types.MockServer{Args: args}, &res)
res.Port = port
if err != nil {
log.Println("[ERROR] rpc:", err.Error())
}
}

if err == nil {
waitForPort(res.Port, p.getNetworkInterface(), p.Address, fmt.Sprintf(`Timed out waiting for Mock Server to
start on port %d - are you sure it's running?`, res.Port))
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 &res
Expand Down
9 changes: 5 additions & 4 deletions dsl/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func TestClient_List(t *testing.T) {
func TestClient_ListFail(t *testing.T) {
timeoutDuration = 50 * time.Millisecond
client := &PactClient{ /* don't supply port */ }
client.StartServer([]string{})
client.StartServer([]string{}, 0)
list := client.ListServers()

if len(list.Servers) != 0 {
Expand All @@ -146,7 +146,8 @@ func TestClient_StartServer(t *testing.T) {
defer waitForDaemonToShutdown(port, t)
client := &PactClient{Port: port}

client.StartServer([]string{})
mport, _ := utils.GetFreePort()
client.StartServer([]string{}, mport)
if svc.ServiceStartCount != 1 {
t.Fatalf("Expected 1 server to have been started, got %d", svc.ServiceStartCount)
}
Expand Down Expand Up @@ -189,7 +190,7 @@ func TestClient_RPCErrors(t *testing.T) {
return client.StopServer(&types.MockServer{})
},
&types.MockServer{}: func() interface{} {
return client.StartServer([]string{})
return client.StartServer([]string{}, 0)
},
&types.PactListResponse{}: func() interface{} {
return client.ListServers()
Expand Down Expand Up @@ -297,7 +298,7 @@ func TestClient_StartServerFail(t *testing.T) {
timeoutDuration = 50 * time.Millisecond

client := &PactClient{ /* don't supply port */ }
server := client.StartServer([]string{})
server := client.StartServer([]string{}, 0)
if server.Port != 0 {
t.Fatalf("Expected server to be empty %v", server)
}
Expand Down
20 changes: 19 additions & 1 deletion dsl/pact.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/hashicorp/logutils"
"github.com/pact-foundation/pact-go/types"
"github.com/pact-foundation/pact-go/utils"
)

// Pact is the container structure to run the Consumer Pact test cases.
Expand Down Expand Up @@ -69,6 +70,10 @@ type Pact struct {
// Examples include 'tcp', 'tcp4', 'tcp6'
// Defaults to 'tcp'
Network string

// Ports MockServer can be deployed to, can be CSV or Range with a dash
// Example "1234", "12324,5667", "1234-5667"
AllowedMockServerPorts string
}

// AddInteraction creates a new Pact interaction, initialising all
Expand Down Expand Up @@ -114,6 +119,19 @@ func (p *Pact) Setup(startMockServer bool) *Pact {
p.pactClient = client
}

// Need to predefine due to scoping
var port int
var perr error
if p.AllowedMockServerPorts != "" {
port, perr = utils.FindPortInRange(p.AllowedMockServerPorts)
} else {
port, perr = utils.GetFreePort()
}
if perr != nil {
log.Println("[ERROR] unable to find free port, mockserver will fail to start")
}
log.Println("[DEBUG] starting mock service on port:", port)

if p.Server == nil && startMockServer {
args := []string{
"--pact-specification-version",
Expand All @@ -128,7 +146,7 @@ func (p *Pact) Setup(startMockServer bool) *Pact {
p.Provider,
}

p.Server = p.pactClient.StartServer(args)
p.Server = p.pactClient.StartServer(args, port)
}

return p
Expand Down
66 changes: 61 additions & 5 deletions dsl/pact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ func TestPact_VerifyFail(t *testing.T) {
}

func TestPact_Setup(t *testing.T) {
t.Log("testing pact setup")
port, _ := utils.GetFreePort()
createDaemon(port, true)

Expand All @@ -176,17 +177,72 @@ func TestPact_Setup(t *testing.T) {
if pact.Server == nil {
t.Fatalf("Expected server to be created")
}

pact = &Pact{Port: port, LogLevel: "DEBUG"}
pact.Setup(false)
if pact.Server != nil {
pact2 := &Pact{Port: port, LogLevel: "DEBUG"}
pact2.Setup(false)
if pact2.Server != nil {
t.Fatalf("Expected server to be nil")
}
if pact.pactClient == nil {
if pact2.pactClient == nil {
t.Fatalf("Needed to still have a client")
}
}

func TestPact_SetupWithMockServerPort(t *testing.T) {
port, _ := utils.GetFreePort()
createDaemon(port, true)

pact := &Pact{Port: port, LogLevel: "DEBUG", AllowedMockServerPorts: "32768"}
pact.Setup(true)
if pact.Server == nil {
t.Fatalf("Expected server to be created")
}
if pact.Server.Port != 32768 {
t.Fatalf("Expected mock daemon to be started on specific port")
}
}

func TestPact_SetupWithMockServerPortCSV(t *testing.T) {
port, _ := utils.GetFreePort()
createDaemon(port, true)

pact := &Pact{Port: port, LogLevel: "DEBUG", AllowedMockServerPorts: "32768,32769"}
pact.Setup(true)
if pact.Server == nil {
t.Fatalf("Expected server to be created")
}
if pact.Server.Port != 32768 {
t.Fatalf("Expected mock daemon to be started on specific port")
}
}

func TestPact_SetupWithMockServerPortRange(t *testing.T) {
port, _ := utils.GetFreePort()
createDaemon(port, true)

pact := &Pact{Port: port, LogLevel: "DEBUG", AllowedMockServerPorts: "32768-32770"}
pact.Setup(true)
if pact.Server == nil {
t.Fatalf("Expected server to be created")
}
if pact.Server.Port != 32768 {
t.Fatalf("Expected mock daemon to be started on specific port")
}
}

func TestPact_Invalidrange(t *testing.T) {
port, _ := utils.GetFreePort()
createDaemon(port, true)

pact := &Pact{Port: port, LogLevel: "DEBUG", AllowedMockServerPorts: "abc-32770"}
pact.Setup(true)
if pact.Server == nil {
t.Fatalf("Expected server to be created")
}
if pact.Server.Port != 0 {
t.Fatalf("Expected mock daemon to be started on specific port")
}
}

func TestPact_Teardown(t *testing.T) {
old := waitForPort
defer func() { waitForPort = old }()
Expand Down
69 changes: 68 additions & 1 deletion utils/port.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
// Package utils contains a number of helper / utility functions.
package utils

import "net"
import (
"errors"
"fmt"
"net"
"strconv"
"strings"
)

// GetFreePort Gets an available port by asking the kernal for a random port
// ready and available for use.
Expand All @@ -18,3 +24,64 @@ func GetFreePort() (int, error) {
defer l.Close()
return l.Addr().(*net.TCPAddr).Port, nil
}

// FindPortInRange Iterate through CSV or Range of ports to find open port
// Valid inputs are "8081", "8081,8085", "8081-8085". Do not combine
// list and range
func FindPortInRange(s string) (int, error) {
// Take care of csv and single value
if !strings.Contains(s, "-") {
ports := strings.Split(strings.TrimSpace(s), ",")
for _, p := range ports {
i, err := strconv.Atoi(p)
if err != nil {
return 0, err
}
err = checkPort(i)
if err != nil {
continue
}
return i, nil
}
return 0, errors.New("all passed ports are unusable")
}
// Now take care of ranges
ports := strings.Split(strings.TrimSpace(s), "-")
if len(ports) != 2 {
return 0, errors.New("invalid range passed")
}
lower, err := strconv.Atoi(ports[0])
if err != nil {
return 0, err
}
upper, err := strconv.Atoi(ports[1])
if err != nil {
return 0, err
}
if upper < lower {
return 0, errors.New("invalid range passed")
}
for i := lower; i <= upper; i++ {
err = checkPort(i)
if err != nil {
continue
}
return i, nil
}
return 0, errors.New("all passed ports are unusable")
}

func checkPort(p int) error {
s := fmt.Sprintf("localhost:%d", p)
addr, err := net.ResolveTCPAddr("tcp", s)
if err != nil {
return err
}

l, err := net.ListenTCP("tcp", addr)
if err != nil {
return err
}
defer l.Close()
return nil
}
Loading

0 comments on commit 153b949

Please sign in to comment.