From 36eba48150d220fd4913f26dd0bf747acf2221af Mon Sep 17 00:00:00 2001 From: i4k Date: Mon, 20 May 2024 18:13:18 +0100 Subject: [PATCH] fix!: prereleases should not match constraints for previous versions. Signed-off-by: i4k --- stdlib/funcs_test.go | 5 ++-- versions/versions.go | 31 +++++++++++++++++-- versions/versions_test.go | 63 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 93 insertions(+), 6 deletions(-) diff --git a/stdlib/funcs_test.go b/stdlib/funcs_test.go index b9222021f..9de173e36 100644 --- a/stdlib/funcs_test.go +++ b/stdlib/funcs_test.go @@ -266,12 +266,11 @@ func TestStdlibTmVersionMatch(t *testing.T) { }, { expr: `tm_version_match("2.0.0-dev", "~> 1", {allow_prereleases = true})`, - want: true, + want: false, }, { expr: `tm_version_match("2.0.0-dev", "~> 1.0", {allow_prereleases = true})`, - // 2.0.0-dev < 2.0.0 - want: true, + want: false, }, { expr: `tm_version_match("2.0.0-dev", "~> 1.0", {allow_prereleases = false})`, diff --git a/versions/versions.go b/versions/versions.go index 3f5b5af8b..f4b2261e2 100644 --- a/versions/versions.go +++ b/versions/versions.go @@ -46,8 +46,35 @@ func Match(version, constraint string, allowPrereleases bool) (bool, error) { return false, errors.E(ErrCheck, "invalid constraint", err) } - allowed := versions.MeetingConstraintsExact(spec) - return allowed.Has(semver), nil + // Prereleases for an upcoming breaking change MUST NOT match the previous release. + // In other words, Semantic Version defines the order below: + // 0.9.9 < 1.0.0-alpha < 1.0.0 < 1.0.1 + // + // But we want the behavior below: + // + // The constraint `~> 0.5.0` must not match `0.6.0-rc1` release. + // The reasoning is that v0.6.0-rc1 could already introduce some (or all) of + // the v0.6.0 release and then the loose `~> 0.5.0` could put users at risk. + + var plainConstraints, rcConstraints constraints.IntersectionSpec + for _, sel := range spec { + if sel.Boundary.Prerelease != "" { + rcConstraints = append(rcConstraints, sel) + } else { + plainConstraints = append(plainConstraints, sel) + } + } + + plainAllowed := versions.MeetingConstraintsExact(plainConstraints) + copied := semver + copied.Prerelease = "" + has := plainAllowed.Has(copied) + if !has { + return false, nil + } + + rcAllowed := versions.MeetingConstraintsExact(rcConstraints) + return rcAllowed.Has(semver), nil } spec, err := hclversion.NewConstraint(constraint) diff --git a/versions/versions_test.go b/versions/versions_test.go index fce0919f1..dd7405cb9 100644 --- a/versions/versions_test.go +++ b/versions/versions_test.go @@ -192,6 +192,18 @@ func TestTerramateVersionConstraints(t *testing.T) { constraint: "> 1.2.2, < 1.2.3", want: errors.E(versions.ErrCheck), }, + { + version: "1.2.3-alpha", + constraint: "> 1.2.2, < 1.2.3", + prereleases: true, + want: errors.E(versions.ErrCheck), + }, + { + version: "1.2.3-alpha", + constraint: "~> 1.2.2", + prereleases: true, + //want: errors.E(versions.ErrCheck), + }, { version: "1.2.3-dev", constraint: ">= 1.2.3", @@ -201,12 +213,12 @@ func TestTerramateVersionConstraints(t *testing.T) { version: "1.2.3-dev", constraint: ">= 1.2.3", prereleases: true, - want: errors.E(versions.ErrCheck), }, { version: "1.2.3-dev", constraint: "< 1.2.3", prereleases: true, + want: errors.E(versions.ErrCheck), }, { version: "1.2.3-dev", @@ -308,6 +320,55 @@ func TestTerramateVersionConstraints(t *testing.T) { constraint: "< 1.2.3-dev2", prereleases: true, }, + { + version: "0.6.0-rc1", + constraint: "~> 0.5.0", + want: errors.E(versions.ErrCheck), + }, + { + version: "0.6.0-rc1", + constraint: "~> 0.5.0", + prereleases: true, + want: errors.E(versions.ErrCheck), + }, + { + version: "0.6.0-rc1", + constraint: "~> 0.6.0-rc1", + }, + { + version: "0.6.0-rc1", + constraint: "~> 0.6.0-rc1", + prereleases: true, + }, + { + version: "0.6.0-rc1", + constraint: "~> 0.5.0", + want: errors.E(versions.ErrCheck), + }, + { + version: "2.0.0-alpha", + constraint: "~> 1", + prereleases: true, + want: errors.E(versions.ErrCheck), + }, + { + version: "1.0.0-alpha", + constraint: "< 2", + prereleases: true, + }, + { + version: "0.6.0-rc1", + constraint: "> 0.5, ~> 0.5.0", + want: errors.E(versions.ErrCheck), + }, + // TODO(i4k): review this with Marius. + // looks broken. + { + version: "1.0.0-alpha", + constraint: "< 1.0.0", + prereleases: true, + want: errors.E(versions.ErrCheck), + }, } { tc := tc name := fmt.Sprintf("CheckVersionFor(%q,%q, %t)", tc.version, tc.constraint, tc.prereleases)