mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
New custom SemVer implementation (#8010)
This was something I originally intended to use in another project, but since this turned out to be a better implementation (subject of course to review!) in terms of both performance and simplicity, I figured we might as well use it here too. Some changes include: - `MustParse` to parse known valid versions - Allocates nothing in any operations other than 1 alloc in `String()` - Ignores empty `PreRelease` and `Metadata` fields in serialization - `encoding.TextAppender` implementation to serialize without allocating - A whole bunch of benchmarks Signed-off-by: Anders Eknert <anders@eknert.com>
This commit is contained in:
@@ -22,10 +22,7 @@ func minVersionIndex() ast.VersionIndex {
|
||||
}
|
||||
|
||||
for _, v := range versions {
|
||||
var sv semver.Version
|
||||
if err := sv.Set(v[1:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sv := semver.MustParse(v[1:])
|
||||
|
||||
c, err := ast.LoadCapabilitiesVersion(v)
|
||||
if err != nil {
|
||||
|
||||
@@ -81,8 +81,6 @@ type GHResponse struct {
|
||||
|
||||
// New returns an instance of the Reporter
|
||||
func New(opts Options) (Reporter, error) {
|
||||
r := GHVersionCollector{}
|
||||
|
||||
url := cmp.Or(os.Getenv("OPA_TELEMETRY_SERVICE_URL"), ExternalServiceURL)
|
||||
|
||||
restConfig := fmt.Appendf(nil, `{
|
||||
@@ -93,7 +91,7 @@ func New(opts Options) (Reporter, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.client = client
|
||||
r := GHVersionCollector{client: client}
|
||||
|
||||
// heap_usage_bytes is always present, so register it unconditionally
|
||||
r.RegisterGatherer("heap_usage_bytes", readRuntimeMemStats)
|
||||
@@ -135,19 +133,17 @@ func createDataResponse(ghResp GHResponse) (*DataResponse, error) {
|
||||
return nil, errors.New("server response does not contain tag_name")
|
||||
}
|
||||
|
||||
v := strings.TrimPrefix(version.Version, "v")
|
||||
sv, err := semver.NewVersion(v)
|
||||
sv, err := semver.Parse(version.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse current version %q: %w", v, err)
|
||||
return nil, fmt.Errorf("failed to parse current version %q: %w", version.Version, err)
|
||||
}
|
||||
|
||||
latestV := strings.TrimPrefix(ghResp.TagName, "v")
|
||||
latestSV, err := semver.NewVersion(latestV)
|
||||
latestSV, err := semver.Parse(ghResp.TagName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse latest version %q: %w", latestV, err)
|
||||
return nil, fmt.Errorf("failed to parse latest version %q: %w", ghResp.TagName, err)
|
||||
}
|
||||
|
||||
isLatest := sv.Compare(*latestSV) >= 0
|
||||
isLatest := sv.Compare(latestSV) >= 0
|
||||
|
||||
// Note: alternatively, we could look through the assets in the GH API response to find a matching asset,
|
||||
// and use its URL. However, this is not guaranteed to be more robust, and wouldn't use the 'openpolicyagent.org' domain.
|
||||
|
||||
+194
-197
@@ -14,237 +14,234 @@
|
||||
|
||||
// Semantic Versions http://semver.org
|
||||
|
||||
// Package semver has been vendored from:
|
||||
// This file was originally vendored from:
|
||||
// https://github.com/coreos/go-semver/tree/e214231b295a8ea9479f11b70b35d5acf3556d9b/semver
|
||||
// A number of the original functions of the package have been removed since
|
||||
// they are not required for our built-ins.
|
||||
// There isn't a single line left from the original source today, but being generous about
|
||||
// attribution won't hurt.
|
||||
package semver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/open-policy-agent/opa/v1/util"
|
||||
)
|
||||
|
||||
// reMetaIdentifier matches pre-release and metadata identifiers against the spec requirements
|
||||
var reMetaIdentifier = regexp.MustCompile(`^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$`)
|
||||
|
||||
// Version represents a parsed SemVer
|
||||
type Version struct {
|
||||
Major int64
|
||||
Minor int64
|
||||
Patch int64
|
||||
PreRelease PreRelease
|
||||
Metadata string
|
||||
PreRelease string `json:"PreRelease,omitempty"`
|
||||
Metadata string `json:"Metadata,omitempty"`
|
||||
}
|
||||
|
||||
// PreRelease represents a pre-release suffix string
|
||||
type PreRelease string
|
||||
// Parse constructs new semver Version from version string.
|
||||
func Parse(version string) (v Version, err error) {
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
|
||||
func splitOff(input *string, delim string) (val string) {
|
||||
parts := strings.SplitN(*input, delim, 2)
|
||||
|
||||
if len(parts) == 2 {
|
||||
*input = parts[0]
|
||||
val = parts[1]
|
||||
version, v.Metadata = cut(version, '+')
|
||||
if v.Metadata != "" && !reMetaIdentifier.MatchString(v.Metadata) {
|
||||
return v, fmt.Errorf("invalid metadata identifier: %s", v.Metadata)
|
||||
}
|
||||
|
||||
return val
|
||||
version, v.PreRelease = cut(version, '-')
|
||||
if v.PreRelease != "" && !reMetaIdentifier.MatchString(v.PreRelease) {
|
||||
return v, fmt.Errorf("invalid pre-release identifier: %s", v.PreRelease)
|
||||
}
|
||||
|
||||
if strings.Count(version, ".") != 2 {
|
||||
return v, fmt.Errorf("%s should contain major, minor, and patch versions", version)
|
||||
}
|
||||
|
||||
major, after := cut(version, '.')
|
||||
if v.Major, err = strconv.ParseInt(major, 10, 64); err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
minor, after := cut(after, '.')
|
||||
if v.Minor, err = strconv.ParseInt(minor, 10, 64); err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
if v.Patch, err = strconv.ParseInt(after, 10, 64); err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// NewVersion constructs new SemVers from strings
|
||||
func NewVersion(version string) (*Version, error) {
|
||||
v := Version{}
|
||||
|
||||
if err := v.Set(version); err != nil {
|
||||
return nil, err
|
||||
// MustParse is like Parse but panics if the version string is invalid instead of returning an error.
|
||||
func MustParse(version string) Version {
|
||||
v, err := Parse(version)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &v, nil
|
||||
return v
|
||||
}
|
||||
|
||||
// Set parses and updates v from the given version string. Implements flag.Value
|
||||
func (v *Version) Set(version string) error {
|
||||
metadata := splitOff(&version, "+")
|
||||
preRelease := PreRelease(splitOff(&version, "-"))
|
||||
dotParts := strings.SplitN(version, ".", 3)
|
||||
|
||||
if len(dotParts) != 3 {
|
||||
return fmt.Errorf("%s is not in dotted-tri format", version)
|
||||
}
|
||||
|
||||
if err := validateIdentifier(string(preRelease)); err != nil {
|
||||
return fmt.Errorf("failed to validate pre-release: %v", err)
|
||||
}
|
||||
|
||||
if err := validateIdentifier(metadata); err != nil {
|
||||
return fmt.Errorf("failed to validate metadata: %v", err)
|
||||
}
|
||||
|
||||
parsed := make([]int64, 3)
|
||||
|
||||
for i, v := range dotParts[:3] {
|
||||
val, err := strconv.ParseInt(v, 10, 64)
|
||||
parsed[i] = val
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
v.Metadata = metadata
|
||||
v.PreRelease = preRelease
|
||||
v.Major = parsed[0]
|
||||
v.Minor = parsed[1]
|
||||
v.Patch = parsed[2]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
|
||||
if v.PreRelease != "" {
|
||||
fmt.Fprintf(&buffer, "-%s", v.PreRelease)
|
||||
}
|
||||
|
||||
if v.Metadata != "" {
|
||||
fmt.Fprintf(&buffer, "+%s", v.Metadata)
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Compare tests if v is less than, equal to, or greater than versionB,
|
||||
// returning -1, 0, or +1 respectively.
|
||||
func (v Version) Compare(versionB Version) int {
|
||||
if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
return preReleaseCompare(v, versionB)
|
||||
}
|
||||
|
||||
// Slice converts the comparable parts of the semver into a slice of integers.
|
||||
func (v Version) Slice() []int64 {
|
||||
return []int64{v.Major, v.Minor, v.Patch}
|
||||
}
|
||||
|
||||
// Slice splits the pre-release suffix string
|
||||
func (p PreRelease) Slice() []string {
|
||||
preRelease := string(p)
|
||||
return strings.Split(preRelease, ".")
|
||||
}
|
||||
|
||||
func preReleaseCompare(versionA Version, versionB Version) int {
|
||||
a := versionA.PreRelease
|
||||
b := versionB.PreRelease
|
||||
|
||||
/* Handle the case where if two versions are otherwise equal it is the
|
||||
* one without a PreRelease that is greater */
|
||||
if len(a) == 0 && (len(b) > 0) {
|
||||
return 1
|
||||
} else if len(b) == 0 && (len(a) > 0) {
|
||||
return -1
|
||||
}
|
||||
|
||||
// If there is a prerelease, check and compare each part.
|
||||
return recursivePreReleaseCompare(a.Slice(), b.Slice())
|
||||
}
|
||||
|
||||
func recursiveCompare(versionA []int64, versionB []int64) int {
|
||||
if len(versionA) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
a := versionA[0]
|
||||
b := versionB[0]
|
||||
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
|
||||
return recursiveCompare(versionA[1:], versionB[1:])
|
||||
}
|
||||
|
||||
func recursivePreReleaseCompare(versionA []string, versionB []string) int {
|
||||
// A larger set of pre-release fields has a higher precedence than a smaller set,
|
||||
// if all of the preceding identifiers are equal.
|
||||
if len(versionA) == 0 {
|
||||
if len(versionB) > 0 {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
} else if len(versionB) == 0 {
|
||||
// We're longer than versionB so return 1.
|
||||
return 1
|
||||
}
|
||||
|
||||
a := versionA[0]
|
||||
b := versionB[0]
|
||||
|
||||
aInt := false
|
||||
bInt := false
|
||||
|
||||
aI, err := strconv.Atoi(versionA[0])
|
||||
if err == nil {
|
||||
aInt = true
|
||||
}
|
||||
|
||||
bI, err := strconv.Atoi(versionB[0])
|
||||
if err == nil {
|
||||
bInt = true
|
||||
}
|
||||
|
||||
// Numeric identifiers always have lower precedence than non-numeric identifiers.
|
||||
if aInt && !bInt {
|
||||
return -1
|
||||
} else if !aInt && bInt {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Handle Integer Comparison
|
||||
if aInt && bInt {
|
||||
if aI > bI {
|
||||
return 1
|
||||
} else if aI < bI {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Handle String Comparison
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
|
||||
return recursivePreReleaseCompare(versionA[1:], versionB[1:])
|
||||
}
|
||||
|
||||
// validateIdentifier makes sure the provided identifier satisfies semver spec
|
||||
func validateIdentifier(id string) error {
|
||||
if id != "" && !reIdentifier.MatchString(id) {
|
||||
return fmt.Errorf("%s is not a valid semver identifier", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// reIdentifier is a regular expression used to check that pre-release and metadata
|
||||
// identifiers satisfy the spec requirements
|
||||
var reIdentifier = regexp.MustCompile(`^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$`)
|
||||
|
||||
// Compare compares two semver strings.
|
||||
func Compare(a, b string) int {
|
||||
aV, err := NewVersion(strings.TrimPrefix(a, "v"))
|
||||
aV, err := Parse(a)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
bV, err := NewVersion(strings.TrimPrefix(b, "v"))
|
||||
bV, err := Parse(b)
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
return aV.Compare(*bV)
|
||||
return aV.Compare(bV)
|
||||
}
|
||||
|
||||
// AppendText appends the textual representation of the version to b and returns the extended buffer.
|
||||
// This method conforms to the encoding.TextAppender interface, and is useful for serializing the Version
|
||||
// without allocating, provided the caller has pre-allocated sufficient space in b.
|
||||
func (v Version) AppendText(b []byte) ([]byte, error) {
|
||||
if b == nil {
|
||||
b = make([]byte, 0, length(v))
|
||||
}
|
||||
|
||||
b = append(strconv.AppendInt(b, v.Major, 10), '.')
|
||||
b = append(strconv.AppendInt(b, v.Minor, 10), '.')
|
||||
b = strconv.AppendInt(b, v.Patch, 10)
|
||||
|
||||
if v.PreRelease != "" {
|
||||
b = append(append(b, '-'), v.PreRelease...)
|
||||
}
|
||||
if v.Metadata != "" {
|
||||
b = append(append(b, '+'), v.Metadata...)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// String returns the string representation of the version.
|
||||
func (v Version) String() string {
|
||||
bs := make([]byte, 0, length(v))
|
||||
bs, _ = v.AppendText(bs)
|
||||
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// Compare tests if v is less than, equal to, or greater than other, returning -1, 0, or +1 respectively.
|
||||
// Comparison is based on the SemVer specification (https://semver.org/#spec-item-11).
|
||||
func (v Version) Compare(other Version) int {
|
||||
if v.Major > other.Major {
|
||||
return 1
|
||||
} else if v.Major < other.Major {
|
||||
return -1
|
||||
}
|
||||
|
||||
if v.Minor > other.Minor {
|
||||
return 1
|
||||
} else if v.Minor < other.Minor {
|
||||
return -1
|
||||
}
|
||||
|
||||
if v.Patch > other.Patch {
|
||||
return 1
|
||||
} else if v.Patch < other.Patch {
|
||||
return -1
|
||||
}
|
||||
|
||||
if v.PreRelease == other.PreRelease {
|
||||
return 0
|
||||
}
|
||||
|
||||
// if two versions are otherwise equal it is the one without a pre-release that is greater
|
||||
if v.PreRelease == "" && other.PreRelease != "" {
|
||||
return 1
|
||||
}
|
||||
if other.PreRelease == "" && v.PreRelease != "" {
|
||||
return -1
|
||||
}
|
||||
|
||||
a, afterA := cut(v.PreRelease, '.')
|
||||
b, afterB := cut(other.PreRelease, '.')
|
||||
|
||||
for {
|
||||
if a == "" && b != "" {
|
||||
return -1
|
||||
}
|
||||
if a != "" && b == "" {
|
||||
return 1
|
||||
}
|
||||
|
||||
aIsInt := isAllDecimals(a)
|
||||
bIsInt := isAllDecimals(b)
|
||||
|
||||
// numeric identifiers have lower precedence than non-numeric
|
||||
if aIsInt && !bIsInt {
|
||||
return -1
|
||||
} else if !aIsInt && bIsInt {
|
||||
return 1
|
||||
}
|
||||
|
||||
if aIsInt && bIsInt {
|
||||
aInt, _ := strconv.Atoi(a)
|
||||
bInt, _ := strconv.Atoi(b)
|
||||
|
||||
if aInt > bInt {
|
||||
return 1
|
||||
} else if aInt < bInt {
|
||||
return -1
|
||||
}
|
||||
} else {
|
||||
// string comparison
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// a larger set of pre-release fields has a higher precedence than a
|
||||
// smaller set, if all of the preceding identifiers are equal.
|
||||
if afterA != "" && afterB == "" {
|
||||
return 1
|
||||
} else if afterA == "" && afterB != "" {
|
||||
return -1
|
||||
}
|
||||
|
||||
a, afterA = cut(afterA, '.')
|
||||
b, afterB = cut(afterB, '.')
|
||||
}
|
||||
}
|
||||
|
||||
func isAllDecimals(s string) bool {
|
||||
for _, r := range s {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return s != ""
|
||||
}
|
||||
|
||||
// length allows calculating the length of the version for pre-allocation.
|
||||
func length(v Version) int {
|
||||
n := util.NumDigitsInt64(v.Major) + util.NumDigitsInt64(v.Minor) + util.NumDigitsInt64(v.Patch) + 2
|
||||
if v.PreRelease != "" {
|
||||
n += len(v.PreRelease) + 1
|
||||
}
|
||||
if v.Metadata != "" {
|
||||
n += len(v.Metadata) + 1
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// cut is a *slightly* faster version of strings.Cut only accepting
|
||||
// single byte separators, and skipping the boolean return value.
|
||||
func cut(s string, sep byte) (before, after string) {
|
||||
if i := strings.IndexByte(s, sep); i >= 0 {
|
||||
return s[:i], s[i+1:]
|
||||
}
|
||||
return s, ""
|
||||
}
|
||||
|
||||
+168
-57
@@ -12,76 +12,98 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// vendored from: https://github.com/coreos/go-semver/tree/e214231b295a8ea9479f11b70b35d5acf3556d9b/semver
|
||||
// originally vendored from: https://github.com/coreos/go-semver/tree/e214231b295a8ea9479f11b70b35d5acf3556d9b/semver
|
||||
package semver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type fixture struct {
|
||||
GreaterVersion string
|
||||
LesserVersion string
|
||||
}
|
||||
type (
|
||||
fixture struct {
|
||||
greater string
|
||||
lesser string
|
||||
}
|
||||
|
||||
var fixtures = []fixture{
|
||||
{"0.0.0", "0.0.0-foo"},
|
||||
{"0.0.1", "0.0.0"},
|
||||
{"1.0.0", "0.9.9"},
|
||||
{"0.10.0", "0.9.0"},
|
||||
{"0.99.0", "0.10.0"},
|
||||
{"2.0.0", "1.2.3"},
|
||||
{"0.0.0", "0.0.0-foo"},
|
||||
{"0.0.1", "0.0.0"},
|
||||
{"1.0.0", "0.9.9"},
|
||||
{"0.10.0", "0.9.0"},
|
||||
{"0.99.0", "0.10.0"},
|
||||
{"2.0.0", "1.2.3"},
|
||||
{"0.0.0", "0.0.0-foo"},
|
||||
{"0.0.1", "0.0.0"},
|
||||
{"1.0.0", "0.9.9"},
|
||||
{"0.10.0", "0.9.0"},
|
||||
{"0.99.0", "0.10.0"},
|
||||
{"2.0.0", "1.2.3"},
|
||||
{"1.2.3", "1.2.3-asdf"},
|
||||
{"1.2.3", "1.2.3-4"},
|
||||
{"1.2.3", "1.2.3-4-foo"},
|
||||
{"1.2.3-5-foo", "1.2.3-5"},
|
||||
{"1.2.3-5", "1.2.3-4"},
|
||||
{"1.2.3-5-foo", "1.2.3-5-Foo"},
|
||||
{"3.0.0", "2.7.2+asdf"},
|
||||
{"3.0.0+foobar", "2.7.2"},
|
||||
{"1.2.3-a.10", "1.2.3-a.5"},
|
||||
{"1.2.3-a.b", "1.2.3-a.5"},
|
||||
{"1.2.3-a.b", "1.2.3-a"},
|
||||
{"1.2.3-a.b.c.10.d.5", "1.2.3-a.b.c.5.d.100"},
|
||||
{"1.0.0", "1.0.0-rc.1"},
|
||||
{"1.0.0-rc.2", "1.0.0-rc.1"},
|
||||
{"1.0.0-rc.1", "1.0.0-beta.11"},
|
||||
{"1.0.0-beta.11", "1.0.0-beta.2"},
|
||||
{"1.0.0-beta.2", "1.0.0-beta"},
|
||||
{"1.0.0-beta", "1.0.0-alpha.beta"},
|
||||
{"1.0.0-alpha.beta", "1.0.0-alpha.1"},
|
||||
{"1.0.0-alpha.1", "1.0.0-alpha"},
|
||||
{"1.2.3-rc.1-1-1hash", "1.2.3-rc.2"},
|
||||
compareFixture struct {
|
||||
greater Version
|
||||
lesser Version
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
fixtures = []fixture{
|
||||
{"0.0.0", "0.0.0-foo"},
|
||||
{"0.0.1", "0.0.0"},
|
||||
{"1.0.0", "0.9.9"},
|
||||
{"0.10.0", "0.9.0"},
|
||||
{"0.99.0", "0.10.0"},
|
||||
{"2.0.0", "1.2.3"},
|
||||
{"0.0.0", "0.0.0-foo"},
|
||||
{"0.0.1", "0.0.0"},
|
||||
{"1.0.0", "0.9.9"},
|
||||
{"0.10.0", "0.9.0"},
|
||||
{"0.99.0", "0.10.0"},
|
||||
{"2.0.0", "1.2.3"},
|
||||
{"0.0.0", "0.0.0-foo"},
|
||||
{"0.0.1", "0.0.0"},
|
||||
{"1.0.0", "0.9.9"},
|
||||
{"0.10.0", "0.9.0"},
|
||||
{"0.99.0", "0.10.0"},
|
||||
{"2.0.0", "1.2.3"},
|
||||
{"1.2.3", "1.2.3-asdf"},
|
||||
{"1.2.3", "1.2.3-4"},
|
||||
{"1.2.3", "1.2.3-4-foo"},
|
||||
{"1.2.3-5-foo", "1.2.3-5"},
|
||||
{"1.2.3-5", "1.2.3-4"},
|
||||
{"1.2.3-5-foo", "1.2.3-5-Foo"},
|
||||
{"3.0.0", "2.7.2+asdf"},
|
||||
{"3.0.0+foobar", "2.7.2"},
|
||||
{"1.2.3-a.10", "1.2.3-a.5"},
|
||||
{"1.2.3-a.b", "1.2.3-a.5"},
|
||||
{"1.2.3-a.b", "1.2.3-a"},
|
||||
{"1.2.3-a.b.c.10.d.5", "1.2.3-a.b.c.5.d.100"},
|
||||
{"1.0.0", "1.0.0-rc.1"},
|
||||
{"1.0.0-rc.2", "1.0.0-rc.1"},
|
||||
{"1.0.0-rc.1", "1.0.0-beta.11"},
|
||||
{"1.0.0-beta.11", "1.0.0-beta.2"},
|
||||
{"1.0.0-beta.2", "1.0.0-beta"},
|
||||
{"1.0.0-beta", "1.0.0-alpha.beta"},
|
||||
{"1.0.0-alpha.beta", "1.0.0-alpha.1"},
|
||||
{"1.0.0-alpha.1", "1.0.0-alpha"},
|
||||
{"1.2.3-rc.1-1-1hash", "1.2.3-rc.2"},
|
||||
}
|
||||
|
||||
equalFixtures = []string{
|
||||
"0.0.0",
|
||||
"1.2.3",
|
||||
"1.2.3-rc.1",
|
||||
"1.2.3+build.123",
|
||||
"1.2.3-rc.1+build.123",
|
||||
"1.2.3-rc.1+build.123.444",
|
||||
}
|
||||
)
|
||||
|
||||
func TestCompareEqual(t *testing.T) {
|
||||
for _, v := range equalFixtures {
|
||||
a := MustParse(v)
|
||||
o := a
|
||||
|
||||
if a.Compare(o) != 0 {
|
||||
t.Errorf("Expected %s to be equal to %s", a, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompare(t *testing.T) {
|
||||
for _, v := range fixtures {
|
||||
gt, err := NewVersion(v.GreaterVersion)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
gt := MustParse(v.greater)
|
||||
lt := MustParse(v.lesser)
|
||||
|
||||
lt, err := NewVersion(v.LesserVersion)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if gt.Compare(*lt) <= 0 {
|
||||
if gt.Compare(lt) <= 0 {
|
||||
t.Errorf("%s should be greater than %s", gt, lt)
|
||||
}
|
||||
if lt.Compare(*gt) > 0 {
|
||||
if lt.Compare(gt) > 0 {
|
||||
t.Errorf("%s should not be greater than %s", lt, gt)
|
||||
}
|
||||
}
|
||||
@@ -98,8 +120,97 @@ func TestBadInput(t *testing.T) {
|
||||
"0.88.0+11_e4e5dcabb",
|
||||
}
|
||||
for _, b := range bad {
|
||||
if _, err := NewVersion(b); err == nil {
|
||||
if _, err := Parse(b); err == nil {
|
||||
t.Error("Improperly accepted value: ", b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCompare(b *testing.B) {
|
||||
versionFixtures := make([]compareFixture, 0, len(fixtures))
|
||||
for _, v := range fixtures {
|
||||
versionFixtures = append(versionFixtures, compareFixture{
|
||||
greater: MustParse(v.greater),
|
||||
lesser: MustParse(v.lesser),
|
||||
})
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
for _, v := range versionFixtures {
|
||||
if v.greater.Compare(v.lesser) <= 0 {
|
||||
b.Fatalf("%s should be greater than %s", v.greater, v.lesser)
|
||||
}
|
||||
if v.lesser.Compare(v.greater) > 0 {
|
||||
b.Fatalf("%s should not be greater than %s", v.lesser, v.greater)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCompareEqual(b *testing.B) {
|
||||
versionFixtures := make([]Version, 0, len(equalFixtures))
|
||||
for _, v := range equalFixtures {
|
||||
versionFixtures = append(versionFixtures, MustParse(v))
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
for _, v := range versionFixtures {
|
||||
o := v
|
||||
if v.Compare(o) != 0 {
|
||||
b.Fatalf("Expected %s to be equal to %s", v, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkString(b *testing.B) {
|
||||
v := MustParse("1.2.3-alpha.1+build.123")
|
||||
|
||||
for b.Loop() {
|
||||
if s := v.String(); s != "1.2.3-alpha.1+build.123" {
|
||||
b.Fatalf("unexpected version string: %s", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendText(b *testing.B) {
|
||||
v := MustParse("1.2.3-alpha.1+build.123")
|
||||
|
||||
for b.Loop() {
|
||||
_, err := v.AppendText(nil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendTextPreAllocated(b *testing.B) {
|
||||
v, err := Parse("1.2.3-alpha.1+build.123")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, 32)
|
||||
|
||||
for b.Loop() {
|
||||
if buf, err = v.AppendText(buf); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if string(buf) != "1.2.3-alpha.1+build.123" {
|
||||
b.Fatal("unexpected version string")
|
||||
}
|
||||
buf = buf[:0]
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParseSimple(b *testing.B) {
|
||||
for b.Loop() {
|
||||
MustParse("1.2.3")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParsePrereleaseAndMetadata(b *testing.B) {
|
||||
for b.Loop() {
|
||||
MustParse("1.2.3-alpha.1+build.123")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,13 +228,8 @@ func LoadCapabilitiesVersions() ([]string, error) {
|
||||
// MinimumCompatibleVersion returns the minimum compatible OPA version based on
|
||||
// the built-ins, features, and keywords in c.
|
||||
func (c *Capabilities) MinimumCompatibleVersion() (string, bool) {
|
||||
var maxVersion semver.Version
|
||||
|
||||
// this is the oldest OPA release that includes capabilities
|
||||
if err := maxVersion.Set("0.17.0"); err != nil {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
maxVersion := semver.MustParse("0.17.0")
|
||||
minVersionIndex := minVersionIndexOnce()
|
||||
|
||||
for _, bi := range c.Builtins {
|
||||
|
||||
+210
-624
File diff suppressed because it is too large
Load Diff
@@ -1268,11 +1268,12 @@ func compile(c *ast.Capabilities, b *bundle.Bundle, dbg debug.Debug, enablePrint
|
||||
return nil, compiler.Errors
|
||||
}
|
||||
|
||||
minVersion, ok := compiler.Required.MinimumCompatibleVersion()
|
||||
if !ok {
|
||||
dbg.Printf("could not determine minimum compatible version!")
|
||||
} else {
|
||||
dbg.Printf("minimum compatible version: %v", minVersion)
|
||||
if dbg.Writer() != io.Discard {
|
||||
if minVersion, ok := compiler.Required.MinimumCompatibleVersion(); !ok {
|
||||
dbg.Printf("could not determine minimum compatible version!")
|
||||
} else {
|
||||
dbg.Printf("minimum compatible version: %v", minVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return compiler, nil
|
||||
|
||||
+6
-15
@@ -23,34 +23,25 @@ func builtinSemVerCompare(_ BuiltinContext, operands []*ast.Term, iter func(*ast
|
||||
return err
|
||||
}
|
||||
|
||||
versionA, err := semver.NewVersion(string(versionStringA))
|
||||
versionA, err := semver.Parse(string(versionStringA))
|
||||
if err != nil {
|
||||
return fmt.Errorf("operand 1: string %s is not a valid SemVer", versionStringA)
|
||||
}
|
||||
versionB, err := semver.NewVersion(string(versionStringB))
|
||||
versionB, err := semver.Parse(string(versionStringB))
|
||||
if err != nil {
|
||||
return fmt.Errorf("operand 2: string %s is not a valid SemVer", versionStringB)
|
||||
}
|
||||
|
||||
result := versionA.Compare(*versionB)
|
||||
|
||||
return iter(ast.InternedTerm(result))
|
||||
return iter(ast.InternedTerm(versionA.Compare(versionB)))
|
||||
}
|
||||
|
||||
func builtinSemVerIsValid(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
versionString, err := builtins.StringOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return iter(ast.InternedTerm(false))
|
||||
if err == nil {
|
||||
_, err = semver.Parse(string(versionString))
|
||||
}
|
||||
|
||||
result := true
|
||||
|
||||
_, err = semver.NewVersion(string(versionString))
|
||||
if err != nil {
|
||||
result = false
|
||||
}
|
||||
|
||||
return iter(ast.InternedTerm(result))
|
||||
return iter(ast.InternedTerm(err == nil))
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -44,6 +44,12 @@ func StringToByteSlice[T ~string](s T) []byte {
|
||||
// NumDigitsInt returns the number of digits in n.
|
||||
// This is useful for pre-allocating buffers for string conversion.
|
||||
func NumDigitsInt(n int) int {
|
||||
return NumDigitsInt64(int64(n))
|
||||
}
|
||||
|
||||
// NumDigitsInt64 returns the number of digits in n.
|
||||
// This is useful for pre-allocating buffers for string conversion.
|
||||
func NumDigitsInt64(n int64) int {
|
||||
if n == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user