From 4d995334b654df6e3fe9fd96b785d3ca58a2a38b 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 --- versions/versions.go | 28 +++++++++++++++++-- versions/versions_test.go | 59 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/versions/versions.go b/versions/versions.go index 3f5b5af8b8..5002c826fc 100644 --- a/versions/versions.go +++ b/versions/versions.go @@ -46,8 +46,32 @@ 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: + // + // If the constraint does not specify a prerelease, then it will never match + + 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 fce0919f1c..3fa37326c3 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,11 +320,54 @@ 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, + }, + // 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) t.Run(name, func(t *testing.T) { - t.Parallel() err := versions.Check(tc.version, tc.constraint, tc.prereleases) errtest.Assert(t, err, tc.want, "error mismatch") })