v0.314.0: a restore puts the setup gate back as it was (R-1057); a test build moves with the floor (R-1053); Prowlarr links itself (R-1058); the box claims Plex by address (R-1060)
gates / gates (push) Successful in 47s
gates / gates (push) Successful in 47s
MinAgent 0.155.0 (unchanged). Tests red-proved by mutation (A: 10, B: 7, C: 1, C2: 2). Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -18,6 +18,9 @@ type Version struct {
|
||||
Minor int
|
||||
Patch int
|
||||
Raw string
|
||||
// Pre (v0.314.0, R-1053) is a pre-release suffix — „rc.4" in 0.313.0-rc.4 — set ONLY by ParseVersionPre.
|
||||
// ParseVersion refuses a suffix, so every caller that lists or offers tags still never sees a test build.
|
||||
Pre string
|
||||
}
|
||||
|
||||
// ParseVersion parses "X.Y.Z" or "vX.Y.Z". Returns error for "dev", "latest", or invalid formats.
|
||||
@@ -45,6 +48,76 @@ func ParseVersion(s string) (Version, error) {
|
||||
return Version{Major: major, Minor: minor, Patch: patch, Raw: s}, nil
|
||||
}
|
||||
|
||||
// ParseVersionPre is ParseVersion that also accepts a pre-release suffix (semver §9): „0.313.0-rc.4" is 0.313.0 with
|
||||
// Pre „rc.4", which Compare orders BELOW 0.313.0. A build suffix („+abc") is dropped. Used where the question is „is
|
||||
// this box below the floor?" (R-1053: a box on a test build was never moved by the floor) — never to pick a tag to offer.
|
||||
func ParseVersionPre(s string) (Version, error) {
|
||||
s = strings.TrimPrefix(s, "v")
|
||||
if i := strings.IndexByte(s, '+'); i >= 0 {
|
||||
s = s[:i]
|
||||
}
|
||||
base, pre, hasPre := strings.Cut(s, "-")
|
||||
v, err := ParseVersion(base)
|
||||
if err != nil {
|
||||
return Version{}, err
|
||||
}
|
||||
if hasPre {
|
||||
if pre == "" {
|
||||
return Version{}, fmt.Errorf("invalid version: %q (empty pre-release)", s)
|
||||
}
|
||||
for _, id := range strings.Split(pre, ".") {
|
||||
if id == "" || strings.Trim(id, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-") != "" {
|
||||
return Version{}, fmt.Errorf("invalid pre-release in %q", s)
|
||||
}
|
||||
}
|
||||
v.Pre = pre
|
||||
}
|
||||
v.Raw = s
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// comparePre orders pre-release suffixes per semver §11: none > any; identifiers left to right, numeric ones
|
||||
// numerically and below alphanumeric ones; a shorter list below a longer one with the same prefix.
|
||||
func comparePre(a, b string) int {
|
||||
switch {
|
||||
case a == b:
|
||||
return 0
|
||||
case a == "":
|
||||
return 1
|
||||
case b == "":
|
||||
return -1
|
||||
}
|
||||
as, bs := strings.Split(a, "."), strings.Split(b, ".")
|
||||
for i := 0; i < len(as) && i < len(bs); i++ {
|
||||
an, aerr := strconv.Atoi(as[i])
|
||||
bn, berr := strconv.Atoi(bs[i])
|
||||
switch {
|
||||
case aerr == nil && berr == nil:
|
||||
if an != bn {
|
||||
if an < bn {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
case aerr == nil:
|
||||
return -1
|
||||
case berr == nil:
|
||||
return 1
|
||||
default:
|
||||
if c := strings.Compare(as[i], bs[i]); c != 0 {
|
||||
return c
|
||||
}
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case len(as) < len(bs):
|
||||
return -1
|
||||
case len(as) > len(bs):
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Compare returns -1 if a < b, 0 if a == b, 1 if a > b.
|
||||
func (a Version) Compare(b Version) int {
|
||||
if a.Major != b.Major {
|
||||
@@ -65,10 +138,13 @@ func (a Version) Compare(b Version) int {
|
||||
}
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
return comparePre(a.Pre, b.Pre)
|
||||
}
|
||||
|
||||
// String returns the version as "X.Y.Z".
|
||||
// String returns the version as "X.Y.Z" (or "X.Y.Z-pre" for a ParseVersionPre result).
|
||||
func (v Version) String() string {
|
||||
if v.Pre != "" {
|
||||
return fmt.Sprintf("%d.%d.%d-%s", v.Major, v.Minor, v.Patch, v.Pre)
|
||||
}
|
||||
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package util
|
||||
|
||||
import "testing"
|
||||
|
||||
// R-1053 (v0.314.0): a test build orders BELOW its release, so the floor moves a box off it; ParseVersion itself still
|
||||
// refuses a suffix, so nothing that lists or offers tags ever sees a test build.
|
||||
// COMPANION RED-PROOF: make comparePre return 0 → „0.313.0-rc.4 < 0.313.0" fails.
|
||||
func TestR1053_PreReleaseOrdersBelowItsRelease(t *testing.T) {
|
||||
order := []string{"0.312.9", "0.313.0-rc.1", "0.313.0-rc.4", "0.313.0-rc.10", "0.313.0", "0.313.1-rc.1", "0.313.1"}
|
||||
for i := 0; i+1 < len(order); i++ {
|
||||
a, err := ParseVersionPre(order[i])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, err := ParseVersionPre(order[i+1])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a.Compare(b) >= 0 || b.Compare(a) <= 0 {
|
||||
t.Errorf("%s must be below %s", order[i], order[i+1])
|
||||
}
|
||||
}
|
||||
v, err := ParseVersionPre("v0.313.0-rc.4+build7")
|
||||
if err != nil || v.Pre != "rc.4" || v.String() != "0.313.0-rc.4" {
|
||||
t.Fatalf("got %+v %v", v, err)
|
||||
}
|
||||
for _, bad := range []string{"0.313.0-", "0.313.0-rc..4", "0.313.0-r c", "dev", "0.313"} {
|
||||
if _, err := ParseVersionPre(bad); err == nil {
|
||||
t.Errorf("%q must be refused", bad)
|
||||
}
|
||||
}
|
||||
if _, err := ParseVersion("0.313.0-rc.4"); err == nil {
|
||||
t.Fatal("ParseVersion must still refuse a test build (it picks the tags the household is offered)")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user