feat: add release candidate (RC) support to release tooling (#23600)
This adds full RC release support to the release scripts and GitHub Actions workflow. Previously, the tooling only supported stable and mainline releases with strict vMAJOR.MINOR.PATCH semver tags. Changes: - scripts/releaser/version.go: Add Pre field to version struct for prerelease suffixes (e.g. "rc.0"), update regex, parsing, String(), comparison methods, and add IsRC()/rcNumber() helpers. - scripts/releaser/release.go: Detect RC branches (release/X.Y-rc.N), suggest RC version numbers, auto-set "rc" channel (skipping stable/mainline prompt), add RC advisory to release notes, skip docs update for RC releases. - .github/workflows/release.yaml: Add "rc" channel option, fix branch derivation for RC tags (v2.32.0-rc.0 -> release/2.32-rc.0 instead of broken release/2.32.0-rc), skip homebrew/winget/package publishing for RC releases. - scripts/release/publish.sh: Add --rc flag, pass --prerelease to gh release create for RC releases. - scripts/releaser/version_test.go: Add comprehensive unit tests for version parsing, string formatting, IsRC, rcNumber, GreaterThan, and Equal with RC versions. <!-- If you have used AI to produce some or all of this PR, please ensure you have read our [AI Contribution guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING) before submitting. -->
This commit is contained in:
@@ -9,6 +9,7 @@ on:
|
||||
options:
|
||||
- mainline
|
||||
- stable
|
||||
- rc
|
||||
release_notes:
|
||||
description: Release notes for the publishing the release. This is required to create a release.
|
||||
dry_run:
|
||||
@@ -119,9 +120,19 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2.10.2 -> release/2.10
|
||||
# Derive the release branch from the version tag.
|
||||
# Standard: 2.10.2 -> release/2.10
|
||||
# RC: 2.32.0-rc.0 -> release/2.32-rc.0
|
||||
version="$(./scripts/version.sh)"
|
||||
release_branch=release/${version%.*}
|
||||
if [[ "$version" == *-rc.* ]]; then
|
||||
# Extract major.minor and rc suffix from e.g. 2.32.0-rc.0
|
||||
base_version="${version%%-rc.*}" # 2.32.0
|
||||
major_minor="${base_version%.*}" # 2.32
|
||||
rc_suffix="${version##*-rc.}" # 0
|
||||
release_branch="release/${major_minor}-rc.${rc_suffix}"
|
||||
else
|
||||
release_branch=release/${version%.*}
|
||||
fi
|
||||
branch_contains_tag=$(git branch --remotes --contains "${GITHUB_REF}" --list "*/${release_branch}" --format='%(refname)')
|
||||
if [[ -z "${branch_contains_tag}" ]]; then
|
||||
echo "Ref tag must exist in a branch named ${release_branch} when creating a release, did you use scripts/release.sh?"
|
||||
@@ -531,6 +542,9 @@ jobs:
|
||||
if [[ $CODER_RELEASE_CHANNEL == "stable" ]]; then
|
||||
publish_args+=(--stable)
|
||||
fi
|
||||
if [[ $CODER_RELEASE_CHANNEL == "rc" ]]; then
|
||||
publish_args+=(--rc)
|
||||
fi
|
||||
if [[ $CODER_DRY_RUN == *t* ]]; then
|
||||
publish_args+=(--dry-run)
|
||||
fi
|
||||
@@ -643,7 +657,7 @@ jobs:
|
||||
retention-days: 7
|
||||
|
||||
- name: Send repository-dispatch event
|
||||
if: ${{ !inputs.dry_run }}
|
||||
if: ${{ !inputs.dry_run && inputs.release_channel != 'rc' }}
|
||||
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
|
||||
with:
|
||||
token: ${{ secrets.CDRCI_GITHUB_TOKEN }}
|
||||
@@ -731,7 +745,7 @@ jobs:
|
||||
name: Publish to winget-pkgs
|
||||
runs-on: windows-latest
|
||||
needs: release
|
||||
if: ${{ !inputs.dry_run }}
|
||||
if: ${{ !inputs.dry_run && inputs.release_channel != 'rc' }}
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
|
||||
@@ -34,11 +34,12 @@ if [[ "${CI:-}" == "" ]]; then
|
||||
fi
|
||||
|
||||
stable=0
|
||||
rc=0
|
||||
version=""
|
||||
release_notes_file=""
|
||||
dry_run=0
|
||||
|
||||
args="$(getopt -o "" -l stable,version:,release-notes-file:,dry-run -- "$@")"
|
||||
args="$(getopt -o "" -l stable,rc,version:,release-notes-file:,dry-run -- "$@")"
|
||||
eval set -- "$args"
|
||||
while true; do
|
||||
case "$1" in
|
||||
@@ -46,6 +47,10 @@ while true; do
|
||||
stable=1
|
||||
shift
|
||||
;;
|
||||
--rc)
|
||||
rc=1
|
||||
shift
|
||||
;;
|
||||
--version)
|
||||
version="$2"
|
||||
shift 2
|
||||
@@ -68,6 +73,10 @@ while true; do
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$stable" == 1 ]] && [[ "$rc" == 1 ]]; then
|
||||
error "Cannot specify both --stable and --rc"
|
||||
fi
|
||||
|
||||
# Check dependencies
|
||||
dependencies gh
|
||||
|
||||
@@ -162,6 +171,11 @@ if [[ "$stable" == 1 ]]; then
|
||||
latest=true
|
||||
fi
|
||||
|
||||
prerelease_flag=()
|
||||
if [[ "$rc" == 1 ]]; then
|
||||
prerelease_flag=(--prerelease)
|
||||
fi
|
||||
|
||||
target_commitish=main # This is the default.
|
||||
# Skip during dry-runs
|
||||
if [[ "$dry_run" == 0 ]]; then
|
||||
@@ -176,6 +190,7 @@ fi
|
||||
true |
|
||||
maybedryrun "$dry_run" gh release create \
|
||||
--latest="$latest" \
|
||||
"${prerelease_flag[@]}" \
|
||||
--title "$new_tag" \
|
||||
--target "$target_commitish" \
|
||||
--notes-file "$release_notes_file" \
|
||||
|
||||
+78
-34
@@ -30,9 +30,11 @@ func runRelease(ctx context.Context, inv *serpent.Invocation, executor ReleaseEx
|
||||
}
|
||||
|
||||
var latestMainline *version
|
||||
if len(allTags) > 0 {
|
||||
v := allTags[0]
|
||||
latestMainline = &v
|
||||
for _, t := range allTags {
|
||||
if t.Pre == "" {
|
||||
latestMainline = &t
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
stableMinor := -1
|
||||
@@ -41,7 +43,7 @@ func runRelease(ctx context.Context, inv *serpent.Invocation, executor ReleaseEx
|
||||
stableMinor = latestMainline.Minor - 1
|
||||
// Find highest tag in the stable minor series.
|
||||
for _, t := range allTags {
|
||||
if t.Major == latestMainline.Major && t.Minor == stableMinor {
|
||||
if t.Major == latestMainline.Major && t.Minor == stableMinor && t.Pre == "" {
|
||||
latestStableStr = t.String()
|
||||
break
|
||||
}
|
||||
@@ -66,15 +68,17 @@ func runRelease(ctx context.Context, inv *serpent.Invocation, executor ReleaseEx
|
||||
return xerrors.Errorf("detecting branch: %w", err)
|
||||
}
|
||||
|
||||
branchRe := regexp.MustCompile(`^release/(\d+)\.(\d+)$`)
|
||||
// Match standard release branches (release/2.32) and RC
|
||||
// branches (release/2.32-rc.0).
|
||||
branchRe := regexp.MustCompile(`^release/(\d+)\.(\d+)(?:-rc\.(\d+))?$`)
|
||||
m := branchRe.FindStringSubmatch(currentBranch)
|
||||
if m == nil {
|
||||
warnf(w, "Current branch %q is not a release branch (release/X.Y).", currentBranch)
|
||||
warnf(w, "Current branch %q is not a release branch (release/X.Y or release/X.Y-rc.N).", currentBranch)
|
||||
branchInput, err := cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: "Enter the release branch to use (e.g. release/2.21)",
|
||||
Text: "Enter the release branch to use (e.g. release/2.21 or release/2.21-rc.0)",
|
||||
Validate: func(s string) error {
|
||||
if !branchRe.MatchString(s) {
|
||||
return xerrors.New("must be in format release/X.Y (e.g. release/2.21)")
|
||||
return xerrors.New("must be in format release/X.Y or release/X.Y-rc.N (e.g. release/2.21 or release/2.21-rc.0)")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@@ -87,6 +91,10 @@ func runRelease(ctx context.Context, inv *serpent.Invocation, executor ReleaseEx
|
||||
}
|
||||
branchMajor, _ := strconv.Atoi(m[1])
|
||||
branchMinor, _ := strconv.Atoi(m[2])
|
||||
branchRC := -1 // -1 means not an RC branch.
|
||||
if m[3] != "" {
|
||||
branchRC, _ = strconv.Atoi(m[3])
|
||||
}
|
||||
successf(w, "Using release branch: %s", currentBranch)
|
||||
|
||||
// --- Fetch & sync check ---
|
||||
@@ -134,9 +142,27 @@ func runRelease(ctx context.Context, inv *serpent.Invocation, executor ReleaseEx
|
||||
if prevVersion == nil {
|
||||
infof(w, "No previous release tag found on this branch.")
|
||||
suggested = version{Major: branchMajor, Minor: branchMinor, Patch: 0}
|
||||
if branchRC >= 0 {
|
||||
suggested.Pre = fmt.Sprintf("rc.%d", branchRC)
|
||||
}
|
||||
} else {
|
||||
infof(w, "Previous release tag: %s", prevVersion.String())
|
||||
suggested = version{Major: prevVersion.Major, Minor: prevVersion.Minor, Patch: prevVersion.Patch + 1}
|
||||
if branchRC >= 0 {
|
||||
// On an RC branch, suggest the next RC for
|
||||
// the same base version.
|
||||
nextRC := 0
|
||||
if prevVersion.IsRC() {
|
||||
nextRC = prevVersion.rcNumber() + 1
|
||||
}
|
||||
suggested = version{
|
||||
Major: prevVersion.Major,
|
||||
Minor: prevVersion.Minor,
|
||||
Patch: prevVersion.Patch,
|
||||
Pre: fmt.Sprintf("rc.%d", nextRC),
|
||||
}
|
||||
} else {
|
||||
suggested = version{Major: prevVersion.Major, Minor: prevVersion.Minor, Patch: prevVersion.Patch + 1}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(w)
|
||||
@@ -147,7 +173,7 @@ func runRelease(ctx context.Context, inv *serpent.Invocation, executor ReleaseEx
|
||||
Default: suggested.String(),
|
||||
Validate: func(s string) error {
|
||||
if _, ok := parseVersion(s); !ok {
|
||||
return xerrors.New("must be in format vMAJOR.MINOR.PATCH (e.g. v2.31.1)")
|
||||
return xerrors.New("must be in format vMAJOR.MINOR.PATCH or vMAJOR.MINOR.PATCH-rc.N (e.g. v2.31.1 or v2.31.0-rc.0)")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@@ -303,29 +329,36 @@ func runRelease(ctx context.Context, inv *serpent.Invocation, executor ReleaseEx
|
||||
// --- Channel selection ---
|
||||
// This is done before release notes generation because the
|
||||
// notes format differs between mainline and stable channels.
|
||||
channelDefault := cliui.ConfirmNo
|
||||
channelHint := ""
|
||||
if newVersion.Minor == stableMinor {
|
||||
channelDefault = cliui.ConfirmYes
|
||||
channelHint = " (this looks like a stable release)"
|
||||
}
|
||||
|
||||
// RC releases are always on the "rc" channel and skip the
|
||||
// stable/mainline prompt.
|
||||
channel := "mainline"
|
||||
_, err = cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: fmt.Sprintf("Mark this as the latest stable release on GitHub?%s", channelHint),
|
||||
Default: channelDefault,
|
||||
IsConfirm: true,
|
||||
})
|
||||
if err == nil {
|
||||
channel = "stable"
|
||||
} else if !errors.Is(err, cliui.ErrCanceled) {
|
||||
return err
|
||||
}
|
||||
|
||||
if channel == "stable" {
|
||||
infof(w, "Channel: stable (will be marked as GitHub Latest).")
|
||||
if newVersion.IsRC() {
|
||||
channel = "rc"
|
||||
infof(w, "Channel: rc (release candidate, will be marked as prerelease on GitHub).")
|
||||
} else {
|
||||
infof(w, "Channel: mainline (will be marked as prerelease).")
|
||||
channelDefault := cliui.ConfirmNo
|
||||
channelHint := ""
|
||||
if newVersion.Minor == stableMinor {
|
||||
channelDefault = cliui.ConfirmYes
|
||||
channelHint = " (this looks like a stable release)"
|
||||
}
|
||||
|
||||
_, err = cliui.Prompt(inv, cliui.PromptOptions{
|
||||
Text: fmt.Sprintf("Mark this as the latest stable release on GitHub?%s", channelHint),
|
||||
Default: channelDefault,
|
||||
IsConfirm: true,
|
||||
})
|
||||
if err == nil {
|
||||
channel = "stable"
|
||||
} else if !errors.Is(err, cliui.ErrCanceled) {
|
||||
return err
|
||||
}
|
||||
|
||||
if channel == "stable" {
|
||||
infof(w, "Channel: stable (will be marked as GitHub Latest).")
|
||||
} else {
|
||||
infof(w, "Channel: mainline (will be marked as prerelease).")
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
|
||||
@@ -408,12 +441,17 @@ func runRelease(ctx context.Context, inv *serpent.Invocation, executor ReleaseEx
|
||||
// scripts/release/generate_release_notes.sh.
|
||||
var notes strings.Builder
|
||||
|
||||
// Stable since header or mainline blurb.
|
||||
// Stable since header, mainline blurb, or RC advisory.
|
||||
if channel == "stable" {
|
||||
fmt.Fprintf(¬es, "> ## Stable (since %s)\n\n", time.Now().Format("January 02, 2006"))
|
||||
}
|
||||
fmt.Fprintln(¬es, "## Changelog")
|
||||
if channel == "mainline" {
|
||||
switch channel {
|
||||
case "rc":
|
||||
fmt.Fprintln(¬es)
|
||||
fmt.Fprintln(¬es, "> [!NOTE]")
|
||||
fmt.Fprintln(¬es, "> This is a **release candidate** (RC) for testing purposes. It is not recommended for production use. Please report any issues you encounter. Learn more about our [Release Schedule](https://coder.com/docs/install/releases).")
|
||||
case "mainline":
|
||||
fmt.Fprintln(¬es)
|
||||
fmt.Fprintln(¬es, "> [!NOTE]")
|
||||
fmt.Fprintln(¬es, "> This is a mainline Coder release. We advise enterprise customers without a staging environment to install our [latest stable release](https://github.com/coder/coder/releases/latest) while we refine this version. Learn more about our [Release Schedule](https://coder.com/docs/install/releases).")
|
||||
@@ -576,7 +614,13 @@ func runRelease(ctx context.Context, inv *serpent.Invocation, executor ReleaseEx
|
||||
successf(w, "Release workflow triggered!")
|
||||
|
||||
// --- Update release docs ---
|
||||
promptAndUpdateDocs(inv, newVersion, channel, dryRun)
|
||||
// RC releases skip docs updates (calendar, helm versions, etc.)
|
||||
// since they are not production releases.
|
||||
if newVersion.IsRC() {
|
||||
infof(w, "Skipping docs update for release candidate.")
|
||||
} else {
|
||||
promptAndUpdateDocs(inv, newVersion, channel, dryRun)
|
||||
}
|
||||
|
||||
fmt.Fprintln(w)
|
||||
successf(w, "Done! 🎉")
|
||||
|
||||
@@ -7,14 +7,16 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// version holds a parsed semver version.
|
||||
// version holds a parsed semver version with optional prerelease
|
||||
// suffix (e.g. "rc.0").
|
||||
type version struct {
|
||||
Major int
|
||||
Minor int
|
||||
Patch int
|
||||
Pre string // e.g. "rc.0", "" for stable releases.
|
||||
}
|
||||
|
||||
var semverRe = regexp.MustCompile(`^v(\d+)\.(\d+)\.(\d+)$`)
|
||||
var semverRe = regexp.MustCompile(`^v(\d+)\.(\d+)\.(\d+)(-(.+))?$`)
|
||||
|
||||
func parseVersion(s string) (version, bool) {
|
||||
m := semverRe.FindStringSubmatch(s)
|
||||
@@ -24,13 +26,35 @@ func parseVersion(s string) (version, bool) {
|
||||
maj, _ := strconv.Atoi(m[1])
|
||||
mnr, _ := strconv.Atoi(m[2])
|
||||
pat, _ := strconv.Atoi(m[3])
|
||||
return version{Major: maj, Minor: mnr, Patch: pat}, true
|
||||
return version{Major: maj, Minor: mnr, Patch: pat, Pre: m[5]}, true
|
||||
}
|
||||
|
||||
func (v version) String() string {
|
||||
if v.Pre != "" {
|
||||
return fmt.Sprintf("v%d.%d.%d-%s", v.Major, v.Minor, v.Patch, v.Pre)
|
||||
}
|
||||
return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
}
|
||||
|
||||
// IsRC returns true when the version has a prerelease suffix starting
|
||||
// with "rc." (e.g. "rc.0", "rc.1").
|
||||
func (v version) IsRC() bool {
|
||||
return strings.HasPrefix(v.Pre, "rc.")
|
||||
}
|
||||
|
||||
// rcNumber returns the numeric RC identifier (e.g. 0 for "rc.0").
|
||||
// It returns -1 when the version is not an RC.
|
||||
func (v version) rcNumber() int {
|
||||
if !v.IsRC() {
|
||||
return -1
|
||||
}
|
||||
n, err := strconv.Atoi(strings.TrimPrefix(v.Pre, "rc."))
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (v version) GreaterThan(b version) bool {
|
||||
if v.Major != b.Major {
|
||||
return v.Major > b.Major
|
||||
@@ -38,11 +62,27 @@ func (v version) GreaterThan(b version) bool {
|
||||
if v.Minor != b.Minor {
|
||||
return v.Minor > b.Minor
|
||||
}
|
||||
return v.Patch > b.Patch
|
||||
if v.Patch != b.Patch {
|
||||
return v.Patch > b.Patch
|
||||
}
|
||||
// A release without prerelease suffix is greater than one
|
||||
// with a prerelease suffix (v2.32.0 > v2.32.0-rc.0).
|
||||
if v.Pre == "" && b.Pre != "" {
|
||||
return true
|
||||
}
|
||||
if v.Pre != "" && b.Pre == "" {
|
||||
return false
|
||||
}
|
||||
// Both have prerelease: compare numerically for RC versions.
|
||||
if v.IsRC() && b.IsRC() {
|
||||
return v.rcNumber() > b.rcNumber()
|
||||
}
|
||||
// Fallback for non-RC prerelease strings.
|
||||
return v.Pre > b.Pre
|
||||
}
|
||||
|
||||
func (v version) Equal(b version) bool {
|
||||
return v.Major == b.Major && v.Minor == b.Minor && v.Patch == b.Patch
|
||||
return v.Major == b.Major && v.Minor == b.Minor && v.Patch == b.Patch && v.Pre == b.Pre
|
||||
}
|
||||
|
||||
// allSemverTags returns all semver tags sorted descending.
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
input string
|
||||
ok bool
|
||||
want version
|
||||
}{
|
||||
{"v2.32.0", true, version{2, 32, 0, ""}},
|
||||
{"v1.0.0", true, version{1, 0, 0, ""}},
|
||||
{"v2.32.0-rc.0", true, version{2, 32, 0, "rc.0"}},
|
||||
{"v2.32.0-rc.1", true, version{2, 32, 0, "rc.1"}},
|
||||
{"v2.32.1-beta.3", true, version{2, 32, 1, "beta.3"}},
|
||||
{"2.32.0", false, version{}},
|
||||
{"v2.32", false, version{}},
|
||||
{"vx.y.z", false, version{}},
|
||||
{"", false, version{}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got, ok := parseVersion(tt.input)
|
||||
if ok != tt.ok {
|
||||
t.Fatalf("parseVersion(%q) ok = %v, want %v", tt.input, ok, tt.ok)
|
||||
}
|
||||
if ok && got != tt.want {
|
||||
t.Fatalf("parseVersion(%q) = %+v, want %+v", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
v version
|
||||
want string
|
||||
}{
|
||||
{version{2, 32, 0, ""}, "v2.32.0"},
|
||||
{version{2, 32, 0, "rc.0"}, "v2.32.0-rc.0"},
|
||||
{version{1, 0, 0, "beta.1"}, "v1.0.0-beta.1"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.want, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tt.v.String(); got != tt.want {
|
||||
t.Fatalf("String() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionIsRC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
v version
|
||||
want bool
|
||||
}{
|
||||
{version{2, 32, 0, "rc.0"}, true},
|
||||
{version{2, 32, 0, "rc.1"}, true},
|
||||
{version{2, 32, 0, ""}, false},
|
||||
{version{2, 32, 0, "beta.1"}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.v.String(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tt.v.IsRC(); got != tt.want {
|
||||
t.Fatalf("IsRC() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionRCNumber(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
v version
|
||||
want int
|
||||
}{
|
||||
{version{2, 32, 0, "rc.0"}, 0},
|
||||
{version{2, 32, 0, "rc.5"}, 5},
|
||||
{version{2, 32, 0, ""}, -1},
|
||||
{version{2, 32, 0, "beta.1"}, -1},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.v.String(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tt.v.rcNumber(); got != tt.want {
|
||||
t.Fatalf("rcNumber() = %d, want %d", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionGreaterThan(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
a, b version
|
||||
want bool
|
||||
}{
|
||||
// Standard comparisons.
|
||||
{version{2, 32, 1, ""}, version{2, 32, 0, ""}, true},
|
||||
{version{2, 32, 0, ""}, version{2, 32, 1, ""}, false},
|
||||
{version{2, 33, 0, ""}, version{2, 32, 0, ""}, true},
|
||||
{version{3, 0, 0, ""}, version{2, 99, 99, ""}, true},
|
||||
|
||||
// Release > RC with same base version.
|
||||
{version{2, 32, 0, ""}, version{2, 32, 0, "rc.0"}, true},
|
||||
{version{2, 32, 0, "rc.0"}, version{2, 32, 0, ""}, false},
|
||||
|
||||
// RC ordering.
|
||||
{version{2, 32, 0, "rc.1"}, version{2, 32, 0, "rc.0"}, true},
|
||||
{version{2, 32, 0, "rc.0"}, version{2, 32, 0, "rc.1"}, false},
|
||||
{version{2, 32, 0, "rc.10"}, version{2, 32, 0, "rc.9"}, true},
|
||||
{version{2, 32, 0, "rc.9"}, version{2, 32, 0, "rc.10"}, false},
|
||||
|
||||
// Equal.
|
||||
{version{2, 32, 0, ""}, version{2, 32, 0, ""}, false},
|
||||
{version{2, 32, 0, "rc.0"}, version{2, 32, 0, "rc.0"}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.a.String()+"_gt_"+tt.b.String(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tt.a.GreaterThan(tt.b); got != tt.want {
|
||||
t.Fatalf("%s.GreaterThan(%s) = %v, want %v", tt.a, tt.b, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionEqual(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
a, b version
|
||||
want bool
|
||||
}{
|
||||
{version{2, 32, 0, ""}, version{2, 32, 0, ""}, true},
|
||||
{version{2, 32, 0, "rc.0"}, version{2, 32, 0, "rc.0"}, true},
|
||||
{version{2, 32, 0, ""}, version{2, 32, 0, "rc.0"}, false},
|
||||
{version{2, 32, 0, "rc.0"}, version{2, 32, 0, "rc.1"}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.a.String()+"_eq_"+tt.b.String(), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := tt.a.Equal(tt.b); got != tt.want {
|
||||
t.Fatalf("%s.Equal(%s) = %v, want %v", tt.a, tt.b, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user