Browse Source

Add validation, further tests, compare helpers

Benedikt Lang 12 years ago
parent
commit
6bee9cf8c9
2 changed files with 194 additions and 13 deletions
  1. +82
    -5
      semver.go
  2. +112
    -8
      semver_test.go

+ 82
- 5
semver.go View File

@ -8,7 +8,11 @@ import (
"strings"
)
var SEMVER_SPEC_VERSION = Version{
const NUMBERS = "0123456789"
const ALPHAS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
// Latest fully supported spec version
var SPEC_VERSION = Version{
Major: 2,
Minor: 0,
Patch: 0,
@ -22,6 +26,7 @@ type Version struct {
Build []string //No Precendence
}
// Version to string
func (v *Version) String() string {
var buf bytes.Buffer
var DOT = []byte(".")
@ -53,6 +58,30 @@ func (v *Version) String() string {
return buf.String()
}
// Checks if v is greater than o.
func (v *Version) GT(o *Version) bool {
return (v.Compare(o) == 1)
}
// Checks if v is greater than or equal to o.
func (v *Version) GTE(o *Version) bool {
return (v.Compare(o) >= 0)
}
// Checks if v is less than o.
func (v *Version) LT(o *Version) bool {
return (v.Compare(o) == -1)
}
// Checks if v is less than or equal to o.
func (v *Version) LTE(o *Version) bool {
return (v.Compare(o) <= 0)
}
// Compares Versions v to o:
// -1 == v is less than o
// 0 == v is equal to o
// 1 == v is greater than o
func (v *Version) Compare(o *Version) int {
if v.Major != o.Major {
if v.Major > o.Major {
@ -108,16 +137,50 @@ func (v *Version) Compare(o *Version) int {
}
}
// Validates v and returns error in case
func (v *Version) Validate() error {
// Major, Minor, Patch already validated using uint64
if len(v.Pre) > 0 {
for _, pre := range v.Pre {
if !pre.IsNum { //Numeric prerelease versions already uint64
if len(pre.VersionStr) == 0 {
return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
}
if !containsOnly(pre.VersionStr, NUMBERS+ALPHAS) {
return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
}
}
}
}
if len(v.Build) > 0 {
for _, build := range v.Build {
if len(build) == 0 {
return fmt.Errorf("Build meta data can not be empty %q", build)
}
if !containsOnly(build, ALPHAS+NUMBERS) {
return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
}
}
}
return nil
}
// Parses a string to version
func Parse(s string) (*Version, error) {
if len(s) == 0 {
return nil, errors.New("Version string empty")
}
// Split into major.minor.(patch+pr+meta)
parts := strings.SplitN(s, ".", 3)
if len(parts) != 3 {
return nil, errors.New("No Major.Minor.Patch elements found")
}
// Major
if !containsOnly(parts[0], NUMBERS) {
return nil, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
}
@ -126,6 +189,7 @@ func Parse(s string) (*Version, error) {
return nil, err
}
// Minor
if !containsOnly(parts[1], NUMBERS) {
return nil, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
}
@ -137,6 +201,7 @@ func Parse(s string) (*Version, error) {
preIndex := strings.Index(parts[2], "-")
buildIndex := strings.Index(parts[2], "+")
// Determine last index of patch version (first of pre or build versions)
var subVersionIndex int
if preIndex != -1 && buildIndex == -1 {
subVersionIndex = preIndex
@ -145,7 +210,7 @@ func Parse(s string) (*Version, error) {
} else if preIndex == -1 && buildIndex == -1 {
subVersionIndex = len(parts[2])
} else {
// if there is no actual preIndex but a hyphen inside the build meta data
// if there is no actual prversion but a hyphen inside the build meta data
if buildIndex < preIndex {
subVersionIndex = buildIndex
preIndex = -1 // Build meta data before preIndex found implicates there are no prerelease versions
@ -189,6 +254,9 @@ func Parse(s string) (*Version, error) {
buildStr := parts[2][buildIndex+1:]
buildParts := strings.Split(buildStr, ".")
for _, str := range buildParts {
if len(str) == 0 {
return nil, errors.New("Build meta data is empty")
}
if !containsOnly(str, ALPHAS+NUMBERS) {
return nil, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
}
@ -206,10 +274,16 @@ type PRVersion struct {
IsNum bool
}
// Creates a new valid prerelease version
func NewPRVersion(s string) (*PRVersion, error) {
if len(s) == 0 {
return nil, errors.New("Prerelease is empty")
}
v := &PRVersion{}
if containsOnly(s, NUMBERS) {
num, err := strconv.ParseUint(s, 10, 64)
// Might never be hit, but just in case
if err != nil {
return nil, err
}
@ -224,10 +298,15 @@ func NewPRVersion(s string) (*PRVersion, error) {
return v, nil
}
// Is pre release version numeric?
func (v *PRVersion) IsNumeric() bool {
return v.IsNum
}
// Compares PreRelease Versions v to o:
// -1 == v is less than o
// 0 == v is equal to o
// 1 == v is greater than o
func (v *PRVersion) Compare(o *PRVersion) int {
if v.IsNum && !o.IsNum {
return -1
@ -252,6 +331,7 @@ func (v *PRVersion) Compare(o *PRVersion) int {
}
}
// PreRelease version to string
func (v *PRVersion) String() string {
if v.IsNum {
return strconv.FormatUint(v.VersionNum, 10)
@ -259,9 +339,6 @@ func (v *PRVersion) String() string {
return v.VersionStr
}
const NUMBERS = "0123456789"
const ALPHAS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
func containsOnly(s string, set string) bool {
return strings.IndexFunc(s, func(r rune) bool {
return !strings.ContainsRune(set, r)


+ 112
- 8
semver_test.go View File

@ -6,6 +6,14 @@ import (
"testing"
)
func prstr(s string) *PRVersion {
return &PRVersion{s, 0, false}
}
func prnum(i uint64) *PRVersion {
return &PRVersion{"", i, true}
}
type formatTest struct {
v Version
result string
@ -24,14 +32,6 @@ var formatTests = []formatTest{
{Version{1, 2, 3, []*PRVersion{prstr("alpha"), prstr("b-eta")}, nil}, "1.2.3-alpha.b-eta"},
}
func prstr(s string) *PRVersion {
return &PRVersion{s, 0, false}
}
func prnum(i uint64) *PRVersion {
return &PRVersion{"", i, true}
}
func TestStringer(t *testing.T) {
for _, test := range formatTests {
if res := test.v.String(); res != test.result {
@ -46,6 +46,16 @@ func TestParse(t *testing.T) {
t.Errorf("Error parsing %q: %q", test.result, err)
} else if comp := v.Compare(&test.v); comp != 0 {
t.Errorf("Parsing, expected %q but got %q, comp: %d ", test.v, v, comp)
} else if err := v.Validate(); err != nil {
t.Errorf("Error validating parsed version %q: %q", test.v, err)
}
}
}
func TestValidate(t *testing.T) {
for _, test := range formatTests {
if err := test.v.Validate(); err != nil {
t.Errorf("Error validating %q: %q", test.v, err)
}
}
}
@ -96,3 +106,97 @@ func TestCompare(t *testing.T) {
}
}
}
type wrongformatTest struct {
v *Version
str string
}
var wrongformatTests = []wrongformatTest{
{nil, ""},
{nil, "."},
{nil, "1."},
{nil, ".1"},
{nil, "a.b.c"},
{nil, "1.a.b"},
{nil, "1.1.a"},
{nil, "1.a.1"},
{nil, "a.1.1"},
{nil, ".."},
{nil, "1.."},
{nil, "1.1."},
{nil, "1..1"},
{nil, "1.1.+123"},
{nil, "1.1.-beta"},
{nil, "-1.1.1"},
{nil, "1.-1.1"},
{nil, "1.1.-1"},
{&Version{0, 0, 0, []*PRVersion{prstr("!")}, nil}, "0.0.0-!"},
{&Version{0, 0, 0, nil, []string{"!"}}, "0.0.0+!"},
// empty prversion
{&Version{0, 0, 0, []*PRVersion{prstr(""), prstr("alpha")}, nil}, "0.0.0-.alpha"},
// empty build meta data
{&Version{0, 0, 0, []*PRVersion{prstr("alpha")}, []string{""}}, "0.0.0-alpha+"},
{&Version{0, 0, 0, []*PRVersion{prstr("alpha")}, []string{"test", ""}}, "0.0.0-alpha+test."},
}
func TestWrongFormat(t *testing.T) {
for _, test := range wrongformatTests {
if res, err := Parse(test.str); err == nil {
t.Errorf("Parsing wrong format version %q, expected error but got %q", test.str, res)
}
if test.v != nil {
if err := test.v.Validate(); err == nil {
t.Errorf("Validating wrong format version %q (%q), expected error", test.v, test.str)
}
}
}
}
func TestCompareHelper(t *testing.T) {
v := &Version{1, 0, 0, []*PRVersion{prstr("alpha")}, nil}
v1 := &Version{1, 0, 0, nil, nil}
if !v.GTE(v) {
t.Errorf("%q should be greater than or equal to %q", v, v)
}
if !v.LTE(v) {
t.Errorf("%q should be greater than or equal to %q", v, v)
}
if !v.LT(v1) {
t.Errorf("%q should be less than %q", v, v1)
}
if !v.LTE(v1) {
t.Errorf("%q should be less than or equal %q", v, v1)
}
if !v1.GT(v) {
t.Errorf("%q should be less than %q", v1, v)
}
if !v1.GTE(v) {
t.Errorf("%q should be less than or equal %q", v1, v)
}
}
func TestPreReleaseVersions(t *testing.T) {
p1, err := NewPRVersion("123")
if !p1.IsNumeric() {
t.Errorf("Expected numeric prversion, got %q", p1)
}
if p1.VersionNum != 123 {
t.Error("Wrong prversion number")
}
if err != nil {
t.Errorf("Not expected error %q", err)
}
p2, err := NewPRVersion("alpha")
if p2.IsNumeric() {
t.Errorf("Expected non-numeric prversion, got %q", p2)
}
if p2.VersionStr != "alpha" {
t.Error("Wrong prversion string")
}
if err != nil {
t.Errorf("Not expected error %q", err)
}
}

Loading…
Cancel
Save