From fa2795a341fde4d2257d0118b4fbaaec2c50a3fb Mon Sep 17 00:00:00 2001 From: magodo Date: Mon, 11 Apr 2022 14:35:44 +0800 Subject: [PATCH 1/8] `fs.AnyVersion` supports optional version constraint Allows users to optionally specify one or more (comma separated) version constraints for `fs.AnyVersion` source. --- README.md | 4 +++ fs/any_version.go | 48 +++++++++++++++++++++++++++++++-- fs/any_version_test.go | 22 +++++++++++++++ fs/fs_unix_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 87c06a2..5a9b05a 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,10 @@ execPath, err := i.Ensure(context.Background(), []src.Source{ Product: product.Terraform, Version: v0_14_0, }, + &fs.AnyVersion{ + Product: product.Terraform, + Constraint: "~> 0.14.0", + }, &releases.ExactVersion{ Product: product.Terraform, Version: v0_14_0, diff --git a/fs/any_version.go b/fs/any_version.go index fc1f946..6724c0b 100644 --- a/fs/any_version.go +++ b/fs/any_version.go @@ -6,6 +6,7 @@ import ( "log" "path/filepath" + "github.com/hashicorp/go-version" "github.com/hashicorp/hc-install/errors" "github.com/hashicorp/hc-install/internal/src" "github.com/hashicorp/hc-install/internal/validators" @@ -22,6 +23,9 @@ import ( // and any declared ExtraPaths (which are *appended* to // any directories in $PATH). Source is skipped if no binary // is found or accessible/executable. +// +// When Constraint is used, find the first binary that meets the specified +// version constraint. type AnyVersion struct { // Product represents the product (its binary name to look up), // conflicts with ExactBinPath @@ -31,8 +35,12 @@ type AnyVersion struct { // the default system $PATH, conflicts with ExactBinPath ExtraPaths []string + // Constraint represents one or more (comma separated) version constraints + // that should be met by the binary, conflicts with ExactBinPath + Constraint string + // ExactBinPath represents exact path to the binary, - // conflicts with Product and ExtraPaths + // conflicts with Product, ExtraPaths and Constraints ExactBinPath string logger *log.Logger @@ -55,6 +63,14 @@ func (av *AnyVersion) Validate() error { if av.Product != nil && !validators.IsBinaryNameValid(av.Product.BinaryName()) { return fmt.Errorf("invalid binary name: %q", av.Product.BinaryName()) } + if av.Constraint != "" { + if av.Product.GetVersion == nil { + return fmt.Errorf("undeclared version getter") + } + if _, err := version.NewConstraint(av.Constraint); err != nil { + return err + } + } return nil } @@ -79,7 +95,35 @@ func (av *AnyVersion) Find(ctx context.Context) (string, error) { return av.ExactBinPath, nil } - execPath, err := findFile(lookupDirs(av.ExtraPaths), av.Product.BinaryName(), checkExecutable) + var constraints version.Constraints + if av.Constraint != "" { + // The constraint spec is validated in Validate(). + constraints, _ = version.NewConstraint(av.Constraint) + } + + execPath, err := findFile(lookupDirs(av.ExtraPaths), av.Product.BinaryName(), func(file string) error { + err := checkExecutable(file) + if err != nil { + return err + } + + if av.Constraint == "" { + return nil + } + + v, err := av.Product.GetVersion(ctx, file) + if err != nil { + return err + } + + for _, vc := range constraints { + if !vc.Check(v) { + return fmt.Errorf("version (%s) doesn't meet constraint %s", v, vc.String()) + } + } + + return nil + }) if err != nil { return "", errors.SkippableErr(err) } diff --git a/fs/any_version_test.go b/fs/any_version_test.go index 9f75956..534d134 100644 --- a/fs/any_version_test.go +++ b/fs/any_version_test.go @@ -52,11 +52,33 @@ func TestAnyVersionValidate(t *testing.T) { }, expectedErr: fmt.Errorf("invalid binary name: \"invalid!\""), }, + "Product-with-constraint-missing-get-version": { + av: AnyVersion{ + Product: &product.Product{ + BinaryName: product.Terraform.BinaryName, + }, + Constraint: ">= 1.0", + }, + expectedErr: fmt.Errorf("undeclared version getter"), + }, + "Product-incorrect-constraint": { + av: AnyVersion{ + Product: &product.Terraform, + Constraint: "invalid!", + }, + expectedErr: fmt.Errorf(`Malformed constraint: invalid!`), + }, "Product-valid": { av: AnyVersion{ Product: &product.Terraform, }, }, + "Product-with-constraint-valid": { + av: AnyVersion{ + Product: &product.Terraform, + Constraint: ">= 1.0", + }, + }, } for name, testCase := range testCases { diff --git a/fs/fs_unix_test.go b/fs/fs_unix_test.go index 08d9efd..2f07ef4 100644 --- a/fs/fs_unix_test.go +++ b/fs/fs_unix_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "testing" + "github.com/hashicorp/go-version" "github.com/hashicorp/hc-install/errors" "github.com/hashicorp/hc-install/internal/testutil" "github.com/hashicorp/hc-install/product" @@ -56,6 +57,66 @@ func TestAnyVersion_executable(t *testing.T) { } } +func TestAnyVersion_constraint(t *testing.T) { + testutil.EndToEndTest(t) + + dirPath, fileName := testutil.CreateTempFile(t, "") + t.Setenv("PATH", dirPath) + + fullPath := filepath.Join(dirPath, fileName) + err := os.Chmod(fullPath, 0700) + if err != nil { + t.Fatal(err) + } + + av := &AnyVersion{ + Product: &product.Product{ + BinaryName: func() string { return fileName }, + GetVersion: func(ctx context.Context, execPath string) (*version.Version, error) { + return version.NewVersion("1.2.0") + }, + }, + Constraint: "~> 1.0", + } + av.SetLogger(testutil.TestLogger()) + _, err = av.Find(context.Background()) + if err != nil { + t.Fatal(err) + } +} + +func TestAnyVersion_constraintNotMet(t *testing.T) { + testutil.EndToEndTest(t) + + dirPath, fileName := testutil.CreateTempFile(t, "") + t.Setenv("PATH", dirPath) + + fullPath := filepath.Join(dirPath, fileName) + err := os.Chmod(fullPath, 0700) + if err != nil { + t.Fatal(err) + } + + av := &AnyVersion{ + Product: &product.Product{ + BinaryName: func() string { return fileName }, + GetVersion: func(ctx context.Context, execPath string) (*version.Version, error) { + return version.NewVersion("2.0.0") + }, + }, + Constraint: "~> 1.0", + } + av.SetLogger(testutil.TestLogger()) + _, err = av.Find(context.Background()) + if err == nil { + t.Fatal("expected error for non-executable file") + } + + if !errors.IsErrorSkippable(err) { + t.Fatalf("expected a skippable error, got: %#v", err) + } +} + func TestAnyVersion_exactBinPath(t *testing.T) { testutil.EndToEndTest(t) From 22eeb9b91a3a764151f9476e5c24eac84e0d1ec6 Mon Sep 17 00:00:00 2001 From: magodo Date: Mon, 11 Apr 2022 15:07:14 +0800 Subject: [PATCH 2/8] Refine readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a9b05a..c9d7eb6 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ execPath, err := i.Ensure(context.Background(), []src.Source{ Version: v0_14_0, }, &fs.AnyVersion{ - Product: product.Terraform, + Product: &product.Terraform, Constraint: "~> 0.14.0", }, &releases.ExactVersion{ From 84486ab39b7281437620f62c55292cdf9d9aa04b Mon Sep 17 00:00:00 2001 From: magodo Date: Mon, 11 Apr 2022 15:19:35 +0800 Subject: [PATCH 3/8] Modify `Constraint` type and name to align with `releases.Versions` --- README.md | 2 +- fs/any_version.go | 23 +++++++---------------- fs/any_version_test.go | 14 ++++---------- fs/fs_unix_test.go | 4 ++-- 4 files changed, 14 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index c9d7eb6..6fb83b9 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ execPath, err := i.Ensure(context.Background(), []src.Source{ }, &fs.AnyVersion{ Product: &product.Terraform, - Constraint: "~> 0.14.0", + Constraint: version.MustConstraints(version.NewConstraint("~> 0.14.0")), }, &releases.ExactVersion{ Product: product.Terraform, diff --git a/fs/any_version.go b/fs/any_version.go index 6724c0b..e21b30d 100644 --- a/fs/any_version.go +++ b/fs/any_version.go @@ -24,7 +24,7 @@ import ( // any directories in $PATH). Source is skipped if no binary // is found or accessible/executable. // -// When Constraint is used, find the first binary that meets the specified +// When Constraints is used, find the first binary that meets the specified // version constraint. type AnyVersion struct { // Product represents the product (its binary name to look up), @@ -35,9 +35,9 @@ type AnyVersion struct { // the default system $PATH, conflicts with ExactBinPath ExtraPaths []string - // Constraint represents one or more (comma separated) version constraints - // that should be met by the binary, conflicts with ExactBinPath - Constraint string + // Constraints represents a set of version constraints that should + // be met by the binary, conflicts with ExactBinPath + Constraints version.Constraints // ExactBinPath represents exact path to the binary, // conflicts with Product, ExtraPaths and Constraints @@ -63,13 +63,10 @@ func (av *AnyVersion) Validate() error { if av.Product != nil && !validators.IsBinaryNameValid(av.Product.BinaryName()) { return fmt.Errorf("invalid binary name: %q", av.Product.BinaryName()) } - if av.Constraint != "" { + if av.Constraints != nil { if av.Product.GetVersion == nil { return fmt.Errorf("undeclared version getter") } - if _, err := version.NewConstraint(av.Constraint); err != nil { - return err - } } return nil } @@ -95,19 +92,13 @@ func (av *AnyVersion) Find(ctx context.Context) (string, error) { return av.ExactBinPath, nil } - var constraints version.Constraints - if av.Constraint != "" { - // The constraint spec is validated in Validate(). - constraints, _ = version.NewConstraint(av.Constraint) - } - execPath, err := findFile(lookupDirs(av.ExtraPaths), av.Product.BinaryName(), func(file string) error { err := checkExecutable(file) if err != nil { return err } - if av.Constraint == "" { + if av.Constraints == nil { return nil } @@ -116,7 +107,7 @@ func (av *AnyVersion) Find(ctx context.Context) (string, error) { return err } - for _, vc := range constraints { + for _, vc := range av.Constraints { if !vc.Check(v) { return fmt.Errorf("version (%s) doesn't meet constraint %s", v, vc.String()) } diff --git a/fs/any_version_test.go b/fs/any_version_test.go index 534d134..4a7ea01 100644 --- a/fs/any_version_test.go +++ b/fs/any_version_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "testing" + "github.com/hashicorp/go-version" "github.com/hashicorp/hc-install/product" ) @@ -57,17 +58,10 @@ func TestAnyVersionValidate(t *testing.T) { Product: &product.Product{ BinaryName: product.Terraform.BinaryName, }, - Constraint: ">= 1.0", + Constraints: version.MustConstraints(version.NewConstraint(">= 1.0")), }, expectedErr: fmt.Errorf("undeclared version getter"), }, - "Product-incorrect-constraint": { - av: AnyVersion{ - Product: &product.Terraform, - Constraint: "invalid!", - }, - expectedErr: fmt.Errorf(`Malformed constraint: invalid!`), - }, "Product-valid": { av: AnyVersion{ Product: &product.Terraform, @@ -75,8 +69,8 @@ func TestAnyVersionValidate(t *testing.T) { }, "Product-with-constraint-valid": { av: AnyVersion{ - Product: &product.Terraform, - Constraint: ">= 1.0", + Product: &product.Terraform, + Constraints: version.MustConstraints(version.NewConstraint(">= 1.0")), }, }, } diff --git a/fs/fs_unix_test.go b/fs/fs_unix_test.go index 2f07ef4..7bc4798 100644 --- a/fs/fs_unix_test.go +++ b/fs/fs_unix_test.go @@ -76,7 +76,7 @@ func TestAnyVersion_constraint(t *testing.T) { return version.NewVersion("1.2.0") }, }, - Constraint: "~> 1.0", + Constraints: version.MustConstraints(version.NewConstraint("~> 1.0")), } av.SetLogger(testutil.TestLogger()) _, err = av.Find(context.Background()) @@ -104,7 +104,7 @@ func TestAnyVersion_constraintNotMet(t *testing.T) { return version.NewVersion("2.0.0") }, }, - Constraint: "~> 1.0", + Constraints: version.MustConstraints(version.NewConstraint("~> 1.0")), } av.SetLogger(testutil.TestLogger()) _, err = av.Find(context.Background()) From 75dc530e8a2d62ccdb0ae96d774ab41defe0714e Mon Sep 17 00:00:00 2001 From: magodo Date: Wed, 4 May 2022 19:31:14 +0800 Subject: [PATCH 4/8] revert everything --- README.md | 4 --- fs/any_version.go | 39 ++------------------------- fs/any_version_test.go | 16 ----------- fs/fs_unix_test.go | 61 ------------------------------------------ 4 files changed, 2 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index 6fb83b9..87c06a2 100644 --- a/README.md +++ b/README.md @@ -79,10 +79,6 @@ execPath, err := i.Ensure(context.Background(), []src.Source{ Product: product.Terraform, Version: v0_14_0, }, - &fs.AnyVersion{ - Product: &product.Terraform, - Constraint: version.MustConstraints(version.NewConstraint("~> 0.14.0")), - }, &releases.ExactVersion{ Product: product.Terraform, Version: v0_14_0, diff --git a/fs/any_version.go b/fs/any_version.go index e21b30d..fc1f946 100644 --- a/fs/any_version.go +++ b/fs/any_version.go @@ -6,7 +6,6 @@ import ( "log" "path/filepath" - "github.com/hashicorp/go-version" "github.com/hashicorp/hc-install/errors" "github.com/hashicorp/hc-install/internal/src" "github.com/hashicorp/hc-install/internal/validators" @@ -23,9 +22,6 @@ import ( // and any declared ExtraPaths (which are *appended* to // any directories in $PATH). Source is skipped if no binary // is found or accessible/executable. -// -// When Constraints is used, find the first binary that meets the specified -// version constraint. type AnyVersion struct { // Product represents the product (its binary name to look up), // conflicts with ExactBinPath @@ -35,12 +31,8 @@ type AnyVersion struct { // the default system $PATH, conflicts with ExactBinPath ExtraPaths []string - // Constraints represents a set of version constraints that should - // be met by the binary, conflicts with ExactBinPath - Constraints version.Constraints - // ExactBinPath represents exact path to the binary, - // conflicts with Product, ExtraPaths and Constraints + // conflicts with Product and ExtraPaths ExactBinPath string logger *log.Logger @@ -63,11 +55,6 @@ func (av *AnyVersion) Validate() error { if av.Product != nil && !validators.IsBinaryNameValid(av.Product.BinaryName()) { return fmt.Errorf("invalid binary name: %q", av.Product.BinaryName()) } - if av.Constraints != nil { - if av.Product.GetVersion == nil { - return fmt.Errorf("undeclared version getter") - } - } return nil } @@ -92,29 +79,7 @@ func (av *AnyVersion) Find(ctx context.Context) (string, error) { return av.ExactBinPath, nil } - execPath, err := findFile(lookupDirs(av.ExtraPaths), av.Product.BinaryName(), func(file string) error { - err := checkExecutable(file) - if err != nil { - return err - } - - if av.Constraints == nil { - return nil - } - - v, err := av.Product.GetVersion(ctx, file) - if err != nil { - return err - } - - for _, vc := range av.Constraints { - if !vc.Check(v) { - return fmt.Errorf("version (%s) doesn't meet constraint %s", v, vc.String()) - } - } - - return nil - }) + execPath, err := findFile(lookupDirs(av.ExtraPaths), av.Product.BinaryName(), checkExecutable) if err != nil { return "", errors.SkippableErr(err) } diff --git a/fs/any_version_test.go b/fs/any_version_test.go index 4a7ea01..9f75956 100644 --- a/fs/any_version_test.go +++ b/fs/any_version_test.go @@ -5,7 +5,6 @@ import ( "path/filepath" "testing" - "github.com/hashicorp/go-version" "github.com/hashicorp/hc-install/product" ) @@ -53,26 +52,11 @@ func TestAnyVersionValidate(t *testing.T) { }, expectedErr: fmt.Errorf("invalid binary name: \"invalid!\""), }, - "Product-with-constraint-missing-get-version": { - av: AnyVersion{ - Product: &product.Product{ - BinaryName: product.Terraform.BinaryName, - }, - Constraints: version.MustConstraints(version.NewConstraint(">= 1.0")), - }, - expectedErr: fmt.Errorf("undeclared version getter"), - }, "Product-valid": { av: AnyVersion{ Product: &product.Terraform, }, }, - "Product-with-constraint-valid": { - av: AnyVersion{ - Product: &product.Terraform, - Constraints: version.MustConstraints(version.NewConstraint(">= 1.0")), - }, - }, } for name, testCase := range testCases { diff --git a/fs/fs_unix_test.go b/fs/fs_unix_test.go index 7bc4798..08d9efd 100644 --- a/fs/fs_unix_test.go +++ b/fs/fs_unix_test.go @@ -9,7 +9,6 @@ import ( "path/filepath" "testing" - "github.com/hashicorp/go-version" "github.com/hashicorp/hc-install/errors" "github.com/hashicorp/hc-install/internal/testutil" "github.com/hashicorp/hc-install/product" @@ -57,66 +56,6 @@ func TestAnyVersion_executable(t *testing.T) { } } -func TestAnyVersion_constraint(t *testing.T) { - testutil.EndToEndTest(t) - - dirPath, fileName := testutil.CreateTempFile(t, "") - t.Setenv("PATH", dirPath) - - fullPath := filepath.Join(dirPath, fileName) - err := os.Chmod(fullPath, 0700) - if err != nil { - t.Fatal(err) - } - - av := &AnyVersion{ - Product: &product.Product{ - BinaryName: func() string { return fileName }, - GetVersion: func(ctx context.Context, execPath string) (*version.Version, error) { - return version.NewVersion("1.2.0") - }, - }, - Constraints: version.MustConstraints(version.NewConstraint("~> 1.0")), - } - av.SetLogger(testutil.TestLogger()) - _, err = av.Find(context.Background()) - if err != nil { - t.Fatal(err) - } -} - -func TestAnyVersion_constraintNotMet(t *testing.T) { - testutil.EndToEndTest(t) - - dirPath, fileName := testutil.CreateTempFile(t, "") - t.Setenv("PATH", dirPath) - - fullPath := filepath.Join(dirPath, fileName) - err := os.Chmod(fullPath, 0700) - if err != nil { - t.Fatal(err) - } - - av := &AnyVersion{ - Product: &product.Product{ - BinaryName: func() string { return fileName }, - GetVersion: func(ctx context.Context, execPath string) (*version.Version, error) { - return version.NewVersion("2.0.0") - }, - }, - Constraints: version.MustConstraints(version.NewConstraint("~> 1.0")), - } - av.SetLogger(testutil.TestLogger()) - _, err = av.Find(context.Background()) - if err == nil { - t.Fatal("expected error for non-executable file") - } - - if !errors.IsErrorSkippable(err) { - t.Fatalf("expected a skippable error, got: %#v", err) - } -} - func TestAnyVersion_exactBinPath(t *testing.T) { testutil.EndToEndTest(t) From cc6bc4fec345215e577bc0fd90298d4786617371 Mon Sep 17 00:00:00 2001 From: magodo Date: Wed, 4 May 2022 19:28:35 +0800 Subject: [PATCH 5/8] New source - `fs.Version`: allows specifying version constraints --- README.md | 2 +- fs/fs_test.go | 22 +++++++++++ fs/version.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++ fs/version_test.go | 70 +++++++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 fs/version.go create mode 100644 fs/version_test.go diff --git a/README.md b/README.md index 87c06a2..eb287ff 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ The `Installer` offers a few high-level methods: The `Installer` methods accept number of different `Source` types. Each comes with different trade-offs described below. - - `fs.{AnyVersion,ExactVersion}` - Finds a binary in `$PATH` (or additional paths) + - `fs.{AnyVersion,ExactVersion,Version}` - Finds a binary in `$PATH` (or additional paths) - **Pros:** - This is most convenient when you already have the product installed on your system which you already manage. diff --git a/fs/fs_test.go b/fs/fs_test.go index 9d81127..d043022 100644 --- a/fs/fs_test.go +++ b/fs/fs_test.go @@ -16,6 +16,9 @@ var ( _ src.Findable = &ExactVersion{} _ src.LoggerSettable = &ExactVersion{} + + _ src.Findable = &Version{} + _ src.LoggerSettable = &Version{} ) func TestExactVersion(t *testing.T) { @@ -36,3 +39,22 @@ func TestExactVersion(t *testing.T) { t.Fatal(err) } } + +func TestVersion(t *testing.T) { + t.Skip("TODO") + testutil.EndToEndTest(t) + + // TODO: mock out command execution? + + t.Setenv("PATH", "") + + v := &Version{ + Product: product.Terraform, + Constraints: version.MustConstraints(version.NewConstraint(">= 1.0")), + } + v.SetLogger(testutil.TestLogger()) + _, err := v.Find(context.Background()) + if err != nil { + t.Fatal(err) + } +} diff --git a/fs/version.go b/fs/version.go new file mode 100644 index 0000000..26633b8 --- /dev/null +++ b/fs/version.go @@ -0,0 +1,97 @@ +package fs + +import ( + "context" + "fmt" + "log" + "path/filepath" + "time" + + "github.com/hashicorp/go-version" + "github.com/hashicorp/hc-install/errors" + "github.com/hashicorp/hc-install/internal/src" + "github.com/hashicorp/hc-install/internal/validators" + "github.com/hashicorp/hc-install/product" +) + +// Version finds the first executable binary of the product name +// which matches the version constraint within system $PATH and any declared ExtraPaths +// (which are *appended* to any directories in $PATH) +type Version struct { + Product product.Product + Constraints version.Constraints + ExtraPaths []string + Timeout time.Duration + + logger *log.Logger +} + +func (*Version) IsSourceImpl() src.InstallSrcSigil { + return src.InstallSrcSigil{} +} + +func (v *Version) SetLogger(logger *log.Logger) { + v.logger = logger +} + +func (v *Version) log() *log.Logger { + if v.logger == nil { + return discardLogger + } + return v.logger +} + +func (v *Version) Validate() error { + if !validators.IsBinaryNameValid(v.Product.BinaryName()) { + return fmt.Errorf("invalid binary name: %q", v.Product.BinaryName()) + } + if len(v.Constraints) == 0 { + return fmt.Errorf("undeclared version constraints") + } + if v.Product.GetVersion == nil { + return fmt.Errorf("undeclared version getter") + } + return nil +} + +func (v *Version) Find(ctx context.Context) (string, error) { + timeout := defaultTimeout + if v.Timeout > 0 { + timeout = v.Timeout + } + ctx, cancelFunc := context.WithTimeout(ctx, timeout) + defer cancelFunc() + + execPath, err := findFile(lookupDirs(v.ExtraPaths), v.Product.BinaryName(), func(file string) error { + err := checkExecutable(file) + if err != nil { + return err + } + + ver, err := v.Product.GetVersion(ctx, file) + if err != nil { + return err + } + + for _, vc := range v.Constraints { + if !vc.Check(ver) { + return fmt.Errorf("version (%s) doesn't meet constraints %s", ver, vc.String()) + } + } + + return nil + }) + if err != nil { + return "", errors.SkippableErr(err) + } + + if !filepath.IsAbs(execPath) { + var err error + execPath, err = filepath.Abs(execPath) + if err != nil { + return "", errors.SkippableErr(err) + } + } + + return execPath, nil +} diff --git a/fs/version_test.go b/fs/version_test.go new file mode 100644 index 0000000..721041d --- /dev/null +++ b/fs/version_test.go @@ -0,0 +1,70 @@ +package fs + +import ( + "fmt" + "testing" + + "github.com/hashicorp/go-version" + "github.com/hashicorp/hc-install/product" +) + +func TestVersionValidate(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + v Version + expectedErr error + }{ + "Product-incorrect-binary-name": { + v: Version{ + Product: product.Product{ + BinaryName: func() string { return "invalid!" }, + }, + }, + expectedErr: fmt.Errorf("invalid binary name: \"invalid!\""), + }, + "Product-missing-get-version": { + v: Version{ + Product: product.Product{ + BinaryName: product.Terraform.BinaryName, + }, + Constraints: version.MustConstraints(version.NewConstraint(">= 1.0")), + }, + expectedErr: fmt.Errorf("undeclared version getter"), + }, + "Product-missing-version-constraint": { + v: Version{ + Product: product.Terraform, + }, + expectedErr: fmt.Errorf("undeclared version constraints"), + }, + "Product-and-version-constraint": { + v: Version{ + Product: product.Terraform, + Constraints: version.MustConstraints(version.NewConstraint(">= 1.0")), + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + err := testCase.v.Validate() + + if err == nil && testCase.expectedErr != nil { + t.Fatalf("expected error: %s, got no error", testCase.expectedErr) + } + + if err != nil && testCase.expectedErr == nil { + t.Fatalf("expected no error, got error: %s", err) + } + + if err != nil && testCase.expectedErr != nil && err.Error() != testCase.expectedErr.Error() { + t.Fatalf("expected error: %s, got error: %s", testCase.expectedErr, err) + } + }) + } +} From b23b1737f73460ace81697c377c6e0d19aed5c7a Mon Sep 17 00:00:00 2001 From: magodo Date: Tue, 10 May 2022 09:57:29 +0800 Subject: [PATCH 6/8] Modify e2e test --- fs/fs_test.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/fs/fs_test.go b/fs/fs_test.go index d043022..31a7bb8 100644 --- a/fs/fs_test.go +++ b/fs/fs_test.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/go-version" "github.com/hashicorp/hc-install/internal/testutil" "github.com/hashicorp/hc-install/product" + "github.com/hashicorp/hc-install/releases" "github.com/hashicorp/hc-install/src" ) @@ -45,16 +46,34 @@ func TestVersion(t *testing.T) { testutil.EndToEndTest(t) // TODO: mock out command execution? - t.Setenv("PATH", "") + ctx := context.Background() + + p := t.TempDir() + ev := releases.ExactVersion{ + Product: product.Terraform, + Version: version.Must(version.NewVersion("1.0.0")), + InstallDir: p, + } + + if _, err := ev.Install(ctx); err != nil { + t.Fatalf("installing release version failed: %v", err) + } + + // Version matches constraint v := &Version{ Product: product.Terraform, Constraints: version.MustConstraints(version.NewConstraint(">= 1.0")), } v.SetLogger(testutil.TestLogger()) - _, err := v.Find(context.Background()) - if err != nil { + if _, err := v.Find(ctx); err != nil { t.Fatal(err) } + + // Version mismatches constraint + v.Constraints = version.MustConstraints(version.NewConstraint("> 1.0")) + if _, err := v.Find(ctx); err == nil { + t.Fatal("expecting error") + } } From 320d563d6926de72d3aab337d5d9394ea113b4c1 Mon Sep 17 00:00:00 2001 From: magodo Date: Tue, 10 May 2022 19:58:50 +0800 Subject: [PATCH 7/8] enable test --- fs/fs_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fs/fs_test.go b/fs/fs_test.go index 31a7bb8..0349ef6 100644 --- a/fs/fs_test.go +++ b/fs/fs_test.go @@ -42,10 +42,6 @@ func TestExactVersion(t *testing.T) { } func TestVersion(t *testing.T) { - t.Skip("TODO") - testutil.EndToEndTest(t) - - // TODO: mock out command execution? t.Setenv("PATH", "") ctx := context.Background() From 546f8c63602748634c0fc6914b28c1c909ee1d54 Mon Sep 17 00:00:00 2001 From: magodo Date: Tue, 10 May 2022 20:06:10 +0800 Subject: [PATCH 8/8] modify and pass test --- fs/fs_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/fs_test.go b/fs/fs_test.go index 0349ef6..dafa2ca 100644 --- a/fs/fs_test.go +++ b/fs/fs_test.go @@ -42,11 +42,12 @@ func TestExactVersion(t *testing.T) { } func TestVersion(t *testing.T) { - t.Setenv("PATH", "") + testutil.EndToEndTest(t) ctx := context.Background() p := t.TempDir() + t.Setenv("PATH", p) ev := releases.ExactVersion{ Product: product.Terraform, Version: version.Must(version.NewVersion("1.0.0")), @@ -64,7 +65,7 @@ func TestVersion(t *testing.T) { } v.SetLogger(testutil.TestLogger()) if _, err := v.Find(ctx); err != nil { - t.Fatal(err) + t.Fatalf("finding: %v", err) } // Version mismatches constraint