Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tfversion: Finish prerelease version semantic equality handling #317

Merged
merged 3 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/unreleased/BUG FIXES-20240328-180800.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUG FIXES
body: 'tfversion: Fixed `RequireBelow` ignoring equal versioning to fail a test'
time: 2024-03-28T18:08:00.236612-04:00
custom:
Issue: "303"
5 changes: 3 additions & 2 deletions .changes/unreleased/ENHANCEMENTS-20240327-171628.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
kind: ENHANCEMENTS
body: 'tfversion: Ensured semantically equivalent Terraform CLI prerelease versions
are considered equal to patch versions in `SkipBelow`'
body: 'tfversion: Ensured Terraform CLI prerelease versions are considered
semantically equal to patch versions in built-in checks to match the Terraform CLI
versioning policy'
time: 2024-03-27T17:16:28.49466-04:00
custom:
Issue: "303"
27 changes: 23 additions & 4 deletions tfversion/require_above.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,19 @@ import (
)

// RequireAbove will fail the test if the Terraform CLI
// version is below the given version. For example, if given
// version.Must(version.NewVersion("0.15.0")), then 0.14.x or
// any other prior minor versions will fail the test.
// version is exclusively below the given version. For example, if given
// version.Must(version.NewVersion("1.8.0")), then 1.7.x or
// any other prior versions will fail the test.
//
// Prereleases of Terraform CLI (whether alpha, beta, or rc) are considered
// equal to a given patch version. For example, if given
// version.Must(version.NewVersion("1.8.0")), then 1.8.0-rc1 will run, not fail,
// the test. Terraform prereleases are considered as potential candidates for
// the upcoming version and therefore are treated as semantically equal for
// testing. If failing prereleases of the same patch release is desired, give a
// higher prerelease version. For example, if given
// version.Must(version.NewVersion("1.8.0-rc2")), then 1.8.0-rc1 will fail the
// test.
func RequireAbove(minimumVersion *version.Version) TerraformVersionCheck {
return requireAboveCheck{
minimumVersion: minimumVersion,
Expand All @@ -27,8 +37,17 @@ type requireAboveCheck struct {

// CheckTerraformVersion satisfies the TerraformVersionCheck interface.
func (r requireAboveCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) {
var terraformVersion *version.Version

if req.TerraformVersion.LessThan(r.minimumVersion) {
// If given a prerelease version, check the Terraform CLI version directly,
// otherwise use the core version so that prereleases are treated as equal.
if r.minimumVersion.Prerelease() != "" {
terraformVersion = req.TerraformVersion
} else {
terraformVersion = req.TerraformVersion.Core()
}

if terraformVersion.LessThan(r.minimumVersion) {
resp.Error = fmt.Errorf("expected Terraform CLI version above %s but detected version is %s",
r.minimumVersion, req.TerraformVersion)
}
Expand Down
161 changes: 159 additions & 2 deletions tfversion/require_above_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
testinginterface "github.com/mitchellh/go-testing-interface"
)

func Test_RequireAbove(t *testing.T) { //nolint:paralleltest
func Test_RequireAbove_Equal(t *testing.T) { //nolint:paralleltest
t.Setenv("TF_ACC_TERRAFORM_PATH", "")
t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.1.0")

Expand All @@ -41,7 +41,30 @@ func Test_RequireAbove(t *testing.T) { //nolint:paralleltest
})
}

func Test_RequireAbove_Error(t *testing.T) { //nolint:paralleltest
func Test_RequireAbove_Higher(t *testing.T) { //nolint:paralleltest
t.Setenv("TF_ACC_TERRAFORM_PATH", "")
t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.1.1")

r.UnitTest(t, r.TestCase{
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"test": providerserver.NewProviderServer(testprovider.Provider{}),
},
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(version.Must(version.NewVersion("1.1.0"))),
},
Steps: []r.TestStep{
{
//nullable argument only available in TF v1.1.0+
Config: `variable "a" {
nullable = true
default = "hello"
}`,
},
},
})
}

func Test_RequireAbove_Lower(t *testing.T) { //nolint:paralleltest
t.Setenv("TF_ACC_TERRAFORM_PATH", "")
t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.0.7")

Expand All @@ -63,3 +86,137 @@ func Test_RequireAbove_Error(t *testing.T) { //nolint:paralleltest
})
})
}

func Test_RequireAbove_Prerelease_EqualCoreVersion(t *testing.T) { //nolint:paralleltest
t.Setenv("TF_ACC_TERRAFORM_PATH", "")
t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.8.0-rc1")

// Pragmatic compromise that 1.8.0-rc1 prerelease is considered to
// be equivalent to the 1.8.0 core version. This enables developers
// to assert that prerelease versions are ran with upcoming
// core versions.
//
// Reference: https://github.com/hashicorp/terraform-plugin-testing/issues/303
r.UnitTest(t, r.TestCase{
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"test": providerserver.NewProviderServer(testprovider.Provider{}),
},
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(version.Must(version.NewVersion("1.8.0"))),
},
Steps: []r.TestStep{
{
Config: `//non-empty config`,
},
},
})
}

func Test_RequireAbove_Prerelease_EqualPrerelease(t *testing.T) { //nolint:paralleltest
t.Setenv("TF_ACC_TERRAFORM_PATH", "")
t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.8.0-rc1")

r.UnitTest(t, r.TestCase{
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"test": providerserver.NewProviderServer(testprovider.Provider{}),
},
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(version.Must(version.NewVersion("1.8.0-rc1"))),
},
Steps: []r.TestStep{
{
Config: `//non-empty config`,
},
},
})
}

func Test_RequireAbove_Prerelease_HigherCoreVersion(t *testing.T) { //nolint:paralleltest
t.Setenv("TF_ACC_TERRAFORM_PATH", "")
t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.7.0-rc1")

// The 1.7.0-rc1 prerelease should always be considered to be below the
// 1.8.0 core version. This intentionally verifies that the logic does not
// ignore the core version of the prerelease version when compared against
// the core version of the check.
plugintest.TestExpectTFatal(t, func() {
r.UnitTest(&testinginterface.RuntimeT{}, r.TestCase{
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"test": providerserver.NewProviderServer(testprovider.Provider{}),
},
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(version.Must(version.NewVersion("1.8.0"))),
},
Steps: []r.TestStep{
{
Config: `//non-empty config`,
},
},
})
})
}

func Test_RequireAbove_Prerelease_HigherPrerelease(t *testing.T) { //nolint:paralleltest
t.Setenv("TF_ACC_TERRAFORM_PATH", "")
t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.7.0-rc1")

// The 1.7.0-rc1 prerelease should always be considered to be
// below the 1.7.0-rc2 prerelease.
plugintest.TestExpectTFatal(t, func() {
r.UnitTest(&testinginterface.RuntimeT{}, r.TestCase{
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"test": providerserver.NewProviderServer(testprovider.Provider{}),
},
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(version.Must(version.NewVersion("1.7.0-rc2"))),
},
Steps: []r.TestStep{
{
Config: `//non-empty config`,
},
},
})
})
}

func Test_RequireAbove_Prerelease_LowerCoreVersion(t *testing.T) { //nolint:paralleltest
t.Setenv("TF_ACC_TERRAFORM_PATH", "")
t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.8.0-rc1")

// The 1.8.0-rc1 prerelease should always be considered to be
// above the 1.7.0 core version.
r.UnitTest(t, r.TestCase{
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"test": providerserver.NewProviderServer(testprovider.Provider{}),
},
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(version.Must(version.NewVersion("1.7.0"))),
},
Steps: []r.TestStep{
{
Config: `//non-empty config`,
},
},
})
}

func Test_RequireAbove_Prerelease_LowerPrerelease(t *testing.T) { //nolint:paralleltest
t.Setenv("TF_ACC_TERRAFORM_PATH", "")
t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.8.0-rc1")

// The 1.8.0-rc1 prerelease should always be considered to be
// above the 1.8.0-beta1 prerelease.
r.UnitTest(t, r.TestCase{
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"test": providerserver.NewProviderServer(testprovider.Provider{}),
},
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(version.Must(version.NewVersion("1.8.0-beta1"))),
},
Steps: []r.TestStep{
{
Config: `//non-empty config`,
},
},
})
}
25 changes: 22 additions & 3 deletions tfversion/require_below.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,19 @@ import (
)

// RequireBelow will fail the test if the Terraform CLI
// version is above the given version. For example, if given
// version.Must(version.NewVersion("0.15.0")), then versions 0.15.x and
// version is inclusively above the given version. For example, if given
// version.Must(version.NewVersion("1.8.0")), then versions 1.8.x and
// above will fail the test.
//
// Prereleases of Terraform CLI (whether alpha, beta, or rc) are considered
// equal to a given patch version. For example, if given
// version.Must(version.NewVersion("1.8.0")), then 1.8.0-rc1 will fail, not run,
// the test. Terraform prereleases are considered as potential candidates for
// the upcoming version and therefore are treated as semantically equal for
// testing purposes. If failing prereleases of the same patch release is
// desired, give a lower prerelease version. For example, if given
// version.Must(version.NewVersion("1.8.0-rc1")), then 1.8.0-rc2 will fail the
// test.
func RequireBelow(maximumVersion *version.Version) TerraformVersionCheck {
return requireBelowCheck{
maximumVersion: maximumVersion,
Expand All @@ -27,8 +37,17 @@ type requireBelowCheck struct {

// CheckTerraformVersion satisfies the TerraformVersionCheck interface.
func (s requireBelowCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) {
var terraformVersion *version.Version

if req.TerraformVersion.GreaterThan(s.maximumVersion) {
// If given a prerelease version, check the Terraform CLI version directly,
// otherwise use the core version so that prereleases are treated as equal.
if s.maximumVersion.Prerelease() != "" {
terraformVersion = req.TerraformVersion
} else {
terraformVersion = req.TerraformVersion.Core()
}

if terraformVersion.GreaterThanOrEqual(s.maximumVersion) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! 🦅 👀

resp.Error = fmt.Errorf("expected Terraform CLI version below %s but detected version is %s",
s.maximumVersion, req.TerraformVersion)
}
Expand Down
Loading
Loading