Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ test-cover:
fuzz:
@echo "==> Running Fuzz Tests"
go env GOCACHE
go test -fuzz=FuzzNewVersion -fuzztime=15s .
go test -fuzz=FuzzStrictNewVersion -fuzztime=15s .
go test -fuzz=FuzzNewConstraint -fuzztime=15s .
# The -fuzz pattern is an unanchored regular expression and only one fuzz
# target may be run at a time, so each pattern is anchored.
go test -fuzz='^FuzzNewVersion$$' -fuzztime=15s .
go test -fuzz='^FuzzStrictNewVersion$$' -fuzztime=15s .
go test -fuzz='^FuzzNewConstraint$$' -fuzztime=15s .
go test -fuzz='^FuzzNewVersionDifferential$$' -fuzztime=15s .
go test -fuzz='^FuzzStrictNewVersionDifferential$$' -fuzztime=15s .
go test -fuzz='^FuzzNewConstraintDifferential$$' -fuzztime=15s .

$(GOLANGCI_LINT):
# Install golangci-lint. The configuration for it is in the .golangci.yml
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ for i, r := range raw {
sort.Sort(semver.Collection(vs))
```

The `Sort` function does the same thing without going through the `sort`
package, which saves an interface dispatch on each comparison.

```go
semver.Sort(vs)
```

## Checking Version Constraints

There are two methods for comparing versions. One uses comparison methods on
Expand Down
66 changes: 66 additions & 0 deletions benchmark_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package semver

import (
"sort"
"testing"
)

Expand Down Expand Up @@ -245,3 +246,68 @@ func BenchmarkStrictNewVersionMetaDash(b *testing.B) {
b.ResetTimer()
benchStrictNewVersion("1.0.0-alpha.1+meta.data", b)
}

/* Comparison benchmarks */

func benchCompare(v1, v2 string, b *testing.B) {
a, _ := NewVersion(v1)
c, _ := NewVersion(v2)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
a.Compare(c)
}
}

func BenchmarkCompareSimple(b *testing.B) {
benchCompare("1.2.3", "1.2.4", b)
}

func BenchmarkComparePrerelease(b *testing.B) {
benchCompare("1.2.3-alpha.1", "1.2.3-alpha.2", b)
}

func BenchmarkComparePrereleaseLong(b *testing.B) {
benchCompare("1.2.3-alpha.beta.11.rc.1", "1.2.3-alpha.beta.11.rc.2", b)
}

/* Sorting benchmarks */

// sortCorpus is a set of versions covering release and prerelease values
// across several major, minor, and patch segments.
var sortCorpus = []string{
"1.2.3", "0.4.2", "2.0.0", "1.0.0-alpha", "1.0.0-alpha.1",
"1.0.0-alpha.beta", "1.0.0-beta", "1.0.0-beta.2", "1.0.0-beta.11",
"1.0.0-rc.1", "1.0.0", "1.3.0", "1.2.0", "3.1.4", "2.7.18",
"0.0.1", "10.0.0", "1.2.3+meta", "4.5.6-rc.1+build.1", "2.0.0-rc.2",
}

func BenchmarkCollectionSort(b *testing.B) {
vs := make(Collection, len(sortCorpus))
for i, r := range sortCorpus {
vs[i] = MustParse(r)
}

work := make(Collection, len(vs))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
copy(work, vs)
sort.Sort(work)
}
}

func BenchmarkCollectionSortFunc(b *testing.B) {
vs := make(Collection, len(sortCorpus))
for i, r := range sortCorpus {
vs[i] = MustParse(r)
}

work := make(Collection, len(vs))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
copy(work, vs)
Sort(work)
}
}
11 changes: 11 additions & 0 deletions collection.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package semver

import "slices"

// Collection is a collection of Version instances and implements the sort
// interface. See the sort package for more details.
// https://golang.org/pkg/sort/
Expand All @@ -22,3 +24,12 @@ func (c Collection) Less(i, j int) bool {
func (c Collection) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}

// Sort sorts a Collection of versions from lowest to highest. It is equivalent
// to sort.Sort(c) but does not go through the sort.Interface methods, so each
// comparison avoids an interface dispatch.
func Sort(c Collection) {
slices.SortFunc(c, func(a, b *Version) int {
return a.Compare(b)
})
}
53 changes: 53 additions & 0 deletions collection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,56 @@ func TestCollection(t *testing.T) {
t.Error("Sorting Collection failed")
}
}

func TestSort(t *testing.T) {
raw := []string{
"1.2.3",
"1.0",
"1.3",
"2",
"0.4.2",
"1.0.0-alpha",
"1.0.0-beta.2",
"1.0.0-beta.11",
}

vs := make(Collection, len(raw))
for i, r := range raw {
v, err := NewVersion(r)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}

vs[i] = v
}

// Sorting through the sort interface and through the helper must agree.
expected := make(Collection, len(vs))
copy(expected, vs)
sort.Sort(expected)

Sort(vs)

e := []string{
"0.4.2",
"1.0.0-alpha",
"1.0.0-beta.2",
"1.0.0-beta.11",
"1.0.0",
"1.2.3",
"1.3.0",
"2.0.0",
}

a := make([]string, len(vs))
for i, v := range vs {
a[i] = v.String()
if !v.Equal(expected[i]) {
t.Errorf("Sort and sort.Sort disagree at %d: %s and %s", i, v, expected[i])
}
}

if !reflect.DeepEqual(a, e) {
t.Errorf("Sorting Collection failed, got %v", a)
}
}
Loading
Loading