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 2 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
115 changes: 115 additions & 0 deletions tfversion/require_above_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,118 @@ 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_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
119 changes: 119 additions & 0 deletions tfversion/require_below_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (

r "github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/internal/plugintest"
"github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider"
"github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver"
"github.com/hashicorp/terraform-plugin-testing/tfversion"

testinginterface "github.com/mitchellh/go-testing-interface"
Expand Down Expand Up @@ -64,3 +66,120 @@ func Test_RequireBelow_Error(t *testing.T) { //nolint:paralleltest
})
})
}

austinvalle marked this conversation as resolved.
Show resolved Hide resolved
func Test_RequireBelow_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
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.RequireBelow(version.Must(version.NewVersion("1.8.0"))),
},
Steps: []r.TestStep{
{
Config: `//non-empty config`,
},
},
})
})
}

func Test_RequireBelow_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.
r.UnitTest(t, r.TestCase{
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"test": providerserver.NewProviderServer(testprovider.Provider{}),
},
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireBelow(version.Must(version.NewVersion("1.8.0"))),
},
Steps: []r.TestStep{
{
Config: `//non-empty config`,
},
},
})
}

func Test_RequireBelow_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.
r.UnitTest(t, r.TestCase{
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"test": providerserver.NewProviderServer(testprovider.Provider{}),
},
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireBelow(version.Must(version.NewVersion("1.7.0-rc2"))),
},
Steps: []r.TestStep{
{
Config: `//non-empty config`,
},
},
})
}

func Test_RequireBelow_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.
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.RequireBelow(version.Must(version.NewVersion("1.7.0"))),
},
Steps: []r.TestStep{
{
Config: `//non-empty config`,
},
},
})
})
}

func Test_RequireBelow_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.
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.RequireBelow(version.Must(version.NewVersion("1.8.0-beta1"))),
},
Steps: []r.TestStep{
{
Config: `//non-empty config`,
},
},
})
})
}
37 changes: 33 additions & 4 deletions tfversion/require_between.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,19 @@ import (

// RequireBetween will fail the test if the Terraform CLI
// version is outside the given minimum (exclusive) and maximum (inclusive).
// For example, if given a minimum version of version.Must(version.NewVersion("0.15.0"))
// and a maximum version of version.Must(version.NewVersion("1.0.0")), then 0.15.x or
// any other prior versions and versions greater than 1.0.0 will fail the test.
// For example, if given a minimum version of version.Must(version.NewVersion("1.7.0"))
// and a maximum version of version.Must(version.NewVersion("1.8.0")), then 1.6.x or
// any other prior versions and versions greater than or equal to 1.8.0 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 a minimum version of
// 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 purposes. If failing prereleases of the same patch release is
// desired, give a higher prerelease version. For example, if given a minimum
// version of version.Must(version.NewVersion("1.8.0-rc2")), then 1.8.0-rc1 will
// fail the test.
func RequireBetween(minimumVersion, maximumVersion *version.Version) TerraformVersionCheck {
return requireBetweenCheck{
minimumVersion: minimumVersion,
Expand All @@ -30,8 +40,27 @@ type requireBetweenCheck struct {

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

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

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

if minTerraformVersion.LessThan(s.minimumVersion) || maxTerraformVersion.GreaterThanOrEqual(s.maximumVersion) {
resp.Error = fmt.Errorf("expected Terraform CLI version between %s and %s but detected version is %s",
s.minimumVersion, s.maximumVersion, req.TerraformVersion)
}
Expand Down
Loading
Loading