From e137e7ce22752da57cc5dfd39ae2ed7b5b040c5f Mon Sep 17 00:00:00 2001 From: Matt Fellows Date: Sun, 25 Mar 2018 17:36:26 +1100 Subject: [PATCH] feat(install): commands check version currency before running Now that we have departed from packaging the CLI tools as part of pact-go, we needed a mechanism to ensure the tools matched the library. The only sensible time for this is at runtime, prior to executing the tests. --- client/service_manager.go | 1 + command/install.go | 18 ++++-- dsl/mock_service.go | 1 - dsl/pact.go | 28 ++++++++- dsl/pact_test.go | 5 ++ .../consumer/goconsumer/user_service_test.go | 10 +-- install/install_darwin.go | 3 - install/install_linux.go | 3 - install/install_windows.go | 3 - install/installer.go | 25 ++++++-- install/installer_test.go | 62 ++++++++++++++++--- 11 files changed, 125 insertions(+), 34 deletions(-) delete mode 100644 install/install_darwin.go delete mode 100644 install/install_linux.go delete mode 100644 install/install_windows.go diff --git a/client/service_manager.go b/client/service_manager.go index 41a226989..5e5191ace 100644 --- a/client/service_manager.go +++ b/client/service_manager.go @@ -21,6 +21,7 @@ type ServiceManager struct { // Setup the Management services. func (s *ServiceManager) Setup() { log.Println("[DEBUG] setting up a service manager") + s.commandCreatedChan = make(chan *exec.Cmd) s.commandCompleteChan = make(chan *exec.Cmd) s.processes = make(map[int]*exec.Cmd) diff --git a/command/install.go b/command/install.go index 1157af7ce..253abc065 100644 --- a/command/install.go +++ b/command/install.go @@ -1,7 +1,10 @@ package command import ( - "fmt" + "log" + "os" + + "github.com/pact-foundation/pact-go/install" "github.com/spf13/cobra" ) @@ -9,15 +12,18 @@ import ( var path string var installCmd = &cobra.Command{ Use: "install", - Short: "Install required tools", - Long: "Installs underlying Pact standalone tools for use by the library", + Short: "Check required tools", + Long: "Checks versions of required Pact CLI tools for used by the library", Run: func(cmd *cobra.Command, args []string) { setLogLevel(verbose, logLevel) // Run the installer - fmt.Println("[INFO] Installing required tools...", path) - fmt.Println("[INFO] Checking installation succeeded...") - fmt.Println("[INFO] Tooling installed and up to date!") + i := install.NewInstaller() + var err error + if err = i.CheckInstallation(); err != nil { + log.Println("[ERROR] Your Pact CLI installation is out of date, please update to the latest version. Error:", err) + os.Exit(1) + } }, } diff --git a/dsl/mock_service.go b/dsl/mock_service.go index d3d86bcd0..76eb8ab80 100644 --- a/dsl/mock_service.go +++ b/dsl/mock_service.go @@ -61,7 +61,6 @@ func (m *MockService) call(method string, url string, content interface{}) error responseBody, err := ioutil.ReadAll(res.Body) res.Body.Close() - log.Printf("[DEBUG] mock service response Body: %s\n", responseBody) if res.StatusCode < 200 || res.StatusCode >= 300 { return errors.New(string(responseBody)) } diff --git a/dsl/pact.go b/dsl/pact.go index 1dc0cbc93..571f19e82 100644 --- a/dsl/pact.go +++ b/dsl/pact.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/logutils" "github.com/pact-foundation/pact-go/client" + "github.com/pact-foundation/pact-go/install" "github.com/pact-foundation/pact-go/types" "github.com/pact-foundation/pact-go/utils" ) @@ -73,6 +74,14 @@ type Pact struct { // Ports MockServer can be deployed to, can be CSV or Range with a dash // Example "1234", "12324,5667", "1234-5667" AllowedMockServerPorts string + + // DisableToolValidityCheck prevents CLI version checking - use this carefully! + // The ideal situation is to check the tool installation with before running + // the tests, which should speed up large test suites significantly + DisableToolValidityCheck bool + + // Check if CLI tools are up to date + toolValidityCheck bool } // AddInteraction creates a new Pact interaction, initialising all @@ -97,6 +106,11 @@ func (p *Pact) Setup(startMockServer bool) *Pact { p.Network = "tcp" } + if !p.toolValidityCheck && !p.DisableToolValidityCheck { + checkCliCompatibility() + p.toolValidityCheck = true + } + if p.Host == "" { p.Host = "localhost" } @@ -163,7 +177,7 @@ func (p *Pact) setupLogging() { p.LogLevel = "INFO" } p.logFilter = &logutils.LevelFilter{ - Levels: []logutils.LogLevel{"DEBUG", "WARN", "ERROR"}, + Levels: []logutils.LogLevel{"DEBUG", "INFO", "WARN", "ERROR"}, MinLevel: logutils.LogLevel(p.LogLevel), Writer: os.Stderr, } @@ -279,3 +293,15 @@ func (p *Pact) VerifyProvider(t *testing.T, request types.VerifyRequest) (types. return res, err } + +var installer = install.NewInstaller() + +var checkCliCompatibility = func() { + log.Println("[DEBUG] checking CLI compatability") + err := installer.CheckInstallation() + + if err != nil { + log.Println("[DEBUG] asoetusaoteuasoet") + log.Fatal("[ERROR] CLI tools are out of date, please upgrade before continuing.") + } +} diff --git a/dsl/pact_test.go b/dsl/pact_test.go index b42f79fa6..879ddb85f 100644 --- a/dsl/pact_test.go +++ b/dsl/pact_test.go @@ -10,6 +10,11 @@ import ( "github.com/pact-foundation/pact-go/types" ) +func init() { + // mock out this function + checkCliCompatibility = func() {} +} + func TestPact_setupLogging(t *testing.T) { res := captureOutput(func() { (&Pact{LogLevel: "DEBUG"}).setupLogging() diff --git a/examples/consumer/goconsumer/user_service_test.go b/examples/consumer/goconsumer/user_service_test.go index 2b6cb9b7c..436079b15 100644 --- a/examples/consumer/goconsumer/user_service_test.go +++ b/examples/consumer/goconsumer/user_service_test.go @@ -97,10 +97,12 @@ func setup() { // Create Pact connecting to local Daemon func createPact() dsl.Pact { return dsl.Pact{ - Consumer: "billy", - Provider: "bobby", - LogDir: logDir, - PactDir: pactDir, + Consumer: "billy", + Provider: "bobby", + LogDir: logDir, + PactDir: pactDir, + LogLevel: "DEBUG", + DisableToolValidityCheck: true, } } diff --git a/install/install_darwin.go b/install/install_darwin.go deleted file mode 100644 index 4f6947c84..000000000 --- a/install/install_darwin.go +++ /dev/null @@ -1,3 +0,0 @@ -package install - -var defaultPath = "c:/bin" diff --git a/install/install_linux.go b/install/install_linux.go deleted file mode 100644 index 4f6947c84..000000000 --- a/install/install_linux.go +++ /dev/null @@ -1,3 +0,0 @@ -package install - -var defaultPath = "c:/bin" diff --git a/install/install_windows.go b/install/install_windows.go deleted file mode 100644 index 2ef4b714e..000000000 --- a/install/install_windows.go +++ /dev/null @@ -1,3 +0,0 @@ -package install - -var defaultPath = "/opt" diff --git a/install/installer.go b/install/installer.go index 11bb559f0..d05c0759f 100644 --- a/install/installer.go +++ b/install/installer.go @@ -13,11 +13,12 @@ import ( // Installer manages the underlying Ruby installation type Installer struct { + commander commander } const ( mockServiceRange = ">= 2.6.4, < 3.0.0" - verifierRange = ">= 1.11.0, < 2.0.0" + verifierRange = ">= 1.12.0, < 3.0.0" brokerRange = ">= 1.14.0, < 2.0.0" ) @@ -27,6 +28,11 @@ var versionMap = map[string]string{ "pact-broker": brokerRange, } +// NewInstaller creates a new initialised Installer +func NewInstaller() *Installer { + return &Installer{commander: realCommander{}} +} + // CheckInstallation checks installation of all of the tools func (i *Installer) CheckInstallation() error { @@ -68,7 +74,7 @@ func (i *Installer) CheckVersion(binary, version string) error { return nil } - return fmt.Errorf("version %s does not match constraint %s", version, versionRange) + return fmt.Errorf("version %s of %s does not match constraint %s", version, binary, versionRange) } // InstallTools installs the CLI tools onto the host system @@ -82,9 +88,20 @@ func (i *Installer) InstallTools() error { func (i *Installer) GetVersionForBinary(binary string) (version string, err error) { log.Println("[DEBUG] running binary", binary) - cmd := exec.Command(binary, "version") - content, err := cmd.Output() + content, err := i.commander.Output(binary, "version") version = string(content) return strings.TrimSpace(version), err } + +// commander wraps the exec package, allowing us +// properly test the file system +type commander interface { + Output(command string, args ...string) ([]byte, error) +} + +type realCommander struct{} + +func (c realCommander) Output(command string, args ...string) ([]byte, error) { + return exec.Command(command, args...).CombinedOutput() +} diff --git a/install/installer_test.go b/install/installer_test.go index b74a0983a..24466da3e 100644 --- a/install/installer_test.go +++ b/install/installer_test.go @@ -1,14 +1,39 @@ package install import ( - "fmt" + "errors" "testing" ) -// TODO: mock out the file system +type testCommander struct { + // Version to return + version string + + // Error to return + err error +} + +// set version range to be the same for all binaries +func initVersionRange() { + versionMap = map[string]string{ + "pact-mock-service": ">= 1.0.0, < 2.0.0", + "pact-provider-verifier": ">= 1.0.0, < 2.0.0", + "pact-broker": ">= 1.0.0, < 2.0.0", + } +} + +func (c testCommander) Output(command string, args ...string) ([]byte, error) { + return []byte(c.version), c.err +} + +func getInstaller(version string, err error) *Installer { + initVersionRange() + return &Installer{testCommander{version, err}} +} + func TestInstaller_CheckVersion(t *testing.T) { - i := Installer{} - err := i.CheckVersion("pact-mock-service", "2.7.3") + i := getInstaller("1.5.0", nil) + err := i.CheckVersion("pact-mock-service", "1.5.0") if err != nil { t.Fatal("error:", err) @@ -16,8 +41,8 @@ func TestInstaller_CheckVersion(t *testing.T) { } func TestInstaller_CheckVersionFail(t *testing.T) { - i := Installer{} - err := i.CheckVersion("pact-mock-service", "3.7.3") + i := getInstaller("2.0.0", nil) + err := i.CheckVersion("pact-mock-service", "2.0.0") if err == nil { t.Fatal("want error, got none") @@ -25,21 +50,40 @@ func TestInstaller_CheckVersionFail(t *testing.T) { } func TestInstaller_getVersionForBinary(t *testing.T) { - i := Installer{} + version := "1.5.0" + i := getInstaller(version, nil) v, err := i.GetVersionForBinary("pact-mock-service") if err != nil { t.Fatal("error:", err) } + if v != version { + t.Fatal("Want", version, "got", v) + } +} + +func TestInstaller_getVersionForBinaryError(t *testing.T) { + i := getInstaller("", errors.New("test error")) + _, err := i.GetVersionForBinary("pact-mock-service") - fmt.Println("version: ", v) + if err == nil { + t.Fatal("Want error, got nil") + } } func TestInstaller_CheckInstallation(t *testing.T) { - i := Installer{} + i := getInstaller("1.0.0", nil) err := i.CheckInstallation() if err != nil { t.Fatal("error:", err) } } +func TestInstaller_CheckInstallationError(t *testing.T) { + i := getInstaller("2.0.0", nil) + err := i.CheckInstallation() + + if err == nil { + t.Fatal("Want error, got nil") + } +}