diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 48b251f..7126370 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -25,44 +25,14 @@ jobs: only-new-issues: true args: --verbose - diff: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version-file: 'go.mod' - - run: go mod tidy -diff - - run: go mod download - - run: go mod verify - - run: go generate ./... - - name: Detect uncommitted changes - run: | - changes=$(git status --porcelain) - if [[ -n "$changes" ]]; then - { - echo "## :construction: Uncommitted changes" - echo "\`\`\`console" - echo "\$ git status --porcelain" - echo "$changes" - echo "\`\`\`" - } >> "$GITHUB_STEP_SUMMARY" - - echo "::group::Uncommitted changes" - echo "$changes" - echo "::endgroup::" - - exit 1 - fi - test: - needs: diff runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: 'go.mod' + - run: go mod tidy -diff - run: go mod download - run: go mod verify - run: go test -v -count=1 -race -shuffle=on -coverprofile=coverage.out -covermode=atomic ./... @@ -83,3 +53,34 @@ jobs: - uses: codecov/codecov-action@v4 with: use_oidc: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) }} + + generate-diff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + - run: go mod tidy -diff + - run: go mod download + - run: go mod verify + - run: go generate ./... + - name: Detect uncommitted changes + run: | + changes=$(git status --porcelain) + if [[ -n "$changes" ]]; then + { + echo '## :construction: Uncommitted changes' + echo 'Run `$ go generate ./...` to re-generate the files.' + echo '```console' + echo '$ git status --porcelain' + echo "$changes" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + echo "::group::Uncommitted changes" + echo "$changes" + echo "::endgroup::" + + exit 1 + fi diff --git a/.golangci.yml b/.golangci.yml index 3734bf8..63cc5d4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,17 +16,18 @@ linters: disable: - dupl - - depguard - testpackage - varnamelen - wsl - - nlreturn - revive issues: exclude-rules: - path: '(.+)_test\.go' linters: - - dupl - - lll + - depguard - funlen + - maintidx + - path: 'version_test\.go' + linters: + - lll diff --git a/README.md b/README.md index 81012d3..4b33bfc 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@
+[![Go Reference](https://pkg.go.dev/badge/github.com/typisttech/comver.svg)](https://pkg.go.dev/github.com/typisttech/comver) +[![GitHub Release](https://img.shields.io/github/v/release/typisttech/comver?style=flat-square&)](https://github.com/typisttech/comver/releases/latest) [![Go](https://github.com/typisttech/comver/actions/workflows/go.yml/badge.svg)](https://github.com/typisttech/comver/actions/workflows/go.yml) [![codecov](https://codecov.io/gh/typisttech/comver/graph/badge.svg?token=GVO7RV80TJ)](https://codecov.io/gh/typisttech/comver) [![Go Report Card](https://goreportcard.com/badge/github.com/typisttech/comver)](https://goreportcard.com/report/github.com/typisttech/comver) -[![GitHub Release](https://img.shields.io/github/v/release/typisttech/comver?style=flat-square&)](https://github.com/typisttech/comver/releases/latest) -[![Go Reference](https://pkg.go.dev/badge/github.com/typisttech/comver.svg)](https://pkg.go.dev/github.com/typisttech/comver) [![license](https://img.shields.io/github/license/typisttech/comver.svg?style=flat-square)](https://github.com/typisttech/comver/blob/master/LICENSE) [![X Follow @TangRufus](https://img.shields.io/badge/Follow-%40TangRufus-black?style=flat-square&logo=x&logoColor=white)](https://x.com/tangrufus) [![Hire Typist Tech](https://img.shields.io/badge/Hire-Typist%20Tech-ff69b4.svg?style=flat-square)](https://typist.tech/contact/) @@ -29,138 +29,15 @@ ## Usage > [!NOTE] -> See full API documentation at [pkg.go.dev](https://pkg.go.dev/github.com/typisttech/comver). - -### `Version` - -[`NewVersion`](https://pkg.go.dev/github.com/typisttech/comver#NewVersion) parses a given version string, attempts to coerce a version string into a [`Version`](https://pkg.go.dev/github.com/typisttech/comver#Version) object or return an error if unable to parse the version string. - -If there is a leading **v** or a version listed without all parts (e.g. **v1.2.p5+foo**) it will attempt to coerce it into a valid composer version (e.g. **1.2.0.0-patch5**). In both cases a [`Version`](https://pkg.go.dev/github.com/typisttech/comver#Version) object is returned that can be sorted, compared, and used in constraints. +> +> See full API documentation on [pkg.go.dev](https://pkg.go.dev/github.com/typisttech/comver). +## Known Issues -> [!WARNING] -> Due to implementation complexity, it only supports a subset of [composer versioning](https://github.com/composer/semver/). +> [!CAUTION] > -> Refer to the [`version_test.go`](version_test.go) for examples. - - -```go -ss := []string{ - "1.2.3", - "v1.2.p5+foo", - "v1.2.3.4.p5+foo", - "2010-01-02", - "2010-01-02.5", - "not a version", - "1.0.0-meh", - "20100102.0.3.4", - "1.0.0-alpha.beta", -} - -for _, s := range ss { - v, err := comver.NewVersion(s) - if err != nil { - fmt.Println(s, " => ", err) - continue - } - fmt.Println(s, " => ", v) -} - -// Output: -// 1.2.3 => 1.2.3.0 -// v1.2.p5+foo => 1.2.0.0-patch5 -// v1.2.3.4.p5+foo => 1.2.3.4-patch5 -// 2010-01-02 => 2010.1.2.0 -// 2010-01-02.5 => 2010.1.2.5 -// not a version => error parsing version string "not a version" -// 1.0.0-meh => error parsing version string "1.0.0-meh" -// 20100102.0.3.4 => error parsing version string "20100102.0.3.4" -// 1.0.0-alpha.beta => error parsing version string "1.0.0-alpha.beta" -``` - -### `constraint` - -```go -v1, _ := comver.NewVersion("1") -v2, _ := comver.NewVersion("2") -v3, _ := comver.NewVersion("3") -v4, _ := comver.NewVersion("4") - -cs := []any{ - comver.NewGreaterThanConstraint(v1), - comver.NewGreaterThanOrEqualToConstraint(v2), - comver.NewLessThanOrEqualToConstraint(v3), - comver.NewLessThanConstraint(v4), -} - -for _, c := range cs { - fmt.Println(c) -} - -// Output: -// >1 -// >=2 -// <=3 -// <4 -``` - -### `interval` - -`interval` represents the intersection (logical AND) of two constraints. - -```go -v1, _ := comver.NewVersion("1") -v2, _ := comver.NewVersion("2") -v3, _ := comver.NewVersion("3") - -g1l3, _ := comver.NewInterval( - comver.NewGreaterThanConstraint(v1), - comver.NewLessThanConstraint(v3), -) - -if g1l3.Check(v2) { - fmt.Println(v2.Short(), "satisfies", g1l3) -} - -if !g1l3.Check(v3) { - fmt.Println(v2.Short(), "doesn't satisfy", g1l3) -} - -// Output: -// 2 satisfies >1 <3 -// 2 doesn't satisfy >1 <3 -``` - -### `Intervals` - -[`Intervals`](https://pkg.go.dev/github.com/typisttech/comver#Intervals) represent the union (logical OR) of multiple intervals. - -```go -v1, _ := comver.NewVersion("1") -v2, _ := comver.NewVersion("2") -v3, _ := comver.NewVersion("3") -v4, _ := comver.NewVersion("4") - -g1l3, _ := comver.NewInterval( -comver.NewGreaterThanConstraint(v1), -comver.NewLessThanConstraint(v3), -) - -ge2le4, _ := comver.NewInterval( -comver.NewGreaterThanOrEqualToConstraint(v2), -comver.NewLessThanOrEqualToConstraint(v4), -) - -is := comver.Intervals{g1l3, ge2le4} -fmt.Println(is) - -is = comver.Compact(is) -fmt.Println(is) - -// Output: -// >1 <3 || >=2 <=4 -// >1 <=4 -``` +> Due to implementation complexity, it only supports a subset of [composer versioning](https://github.com/composer/semver/). +> Refer to the [version_test.go](https://github.com/typisttech/comver/blob/main/version_test.go) for examples. ## Credits diff --git a/and.go b/and.go new file mode 100644 index 0000000..64803a0 --- /dev/null +++ b/and.go @@ -0,0 +1,109 @@ +package comver + +import "slices" + +const ( + errNoEndlessGiven stringError = "no endless given" + errUnexpectedAndLogic stringError = "unexpected and logic" + errImpossibleInterval stringError = "impossible interval" +) + +// And returns a [CeilingFloorConstrainter] instance representing the logical AND of +// the given [Endless] instances; or return an error if the given [Endless] instances +// could never be satisfied at the same time. +func And(es ...Endless) (CeilingFloorConstrainter, error) { //nolint:cyclop,ireturn + var nilC CeilingFloorConstrainter + + if len(es) == 0 { + return nilC, errNoEndlessGiven + } + + es = slices.Clone(es) + es = slices.DeleteFunc(es, Endless.wildcard) + + if len(es) == 0 { + return NewWildcard(), nil + } + if len(es) == 1 { + return es[0], nil + } + + ceiling, ceilingOk := minBoundedCeiling(es...) + floor, floorOk := maxBoundedFloor(es...) + + if !ceilingOk && !floorOk { + // logic error! This should never happen + return nilC, errUnexpectedAndLogic + } + if ceilingOk && !floorOk { + return ceiling, nil + } + if !ceilingOk { // floorOk is always true here + return floor, nil + } + + vCmp := floor.floor().versionCompare(ceiling.ceiling().version) + + if vCmp > 0 { + return nilC, errImpossibleInterval + } + + if vCmp == 0 { + if !floor.floor().inclusive() || !ceiling.ceiling().inclusive() { + return nilC, errImpossibleInterval + } + + return NewExactConstraint(*floor.floor().version), nil + } + + return interval{ + upper: ceiling, + lower: floor, + }, nil +} + +// MustAnd is like [And] but panics if an error occurs. +func MustAnd(es ...Endless) CeilingFloorConstrainter { //nolint:ireturn + c, err := And(es...) + if err != nil { + panic(err) + } + + return c +} + +func minBoundedCeiling(es ...Endless) (Endless, bool) { + es = slices.Clone(es) + + bcs := slices.DeleteFunc(es, func(b Endless) bool { + return b.ceiling().version == nil + }) + + if len(bcs) == 0 { + var nilF Endless + + return nilF, false + } + + return slices.MinFunc(bcs, func(a, b Endless) int { + return a.ceiling().compare(b.ceiling()) + }), true +} + +func maxBoundedFloor(es ...Endless) (Endless, bool) { + es = slices.Clone(es) + + bfs := slices.DeleteFunc(es, func(c Endless) bool { + return c.floor().wildcard() + }) + + if len(bfs) == 0 { + var nilF Endless + + return nilF, false + } + + return slices.MaxFunc(bfs, func(a, b Endless) int { + return a.floor().compare(b.floor()) + }), true +} diff --git a/and_example_test.go b/and_example_test.go new file mode 100644 index 0000000..956a14e --- /dev/null +++ b/and_example_test.go @@ -0,0 +1,48 @@ +package comver_test + +import ( + "fmt" + + "github.com/typisttech/comver" +) + +func ExampleAnd() { + a, _ := comver.And( + comver.NewGreaterThanOrEqualTo(comver.MustParse("2")), + comver.NewLessThan(comver.MustParse("3")), + ) + + fmt.Println(a) + // Output: >=2 <3 +} + +func ExampleAnd_wildcard() { + a, _ := comver.And( + comver.NewGreaterThanOrEqualTo(comver.MustParse("2")), + comver.NewLessThan(comver.MustParse("3")), + comver.NewWildcard(), + ) + + fmt.Println(a) + // Output: >=2 <3 +} + +func ExampleAnd_exactConstraint() { + a, _ := comver.And( + comver.NewGreaterThanOrEqualTo(comver.MustParse("2")), + comver.NewLessThanOrEqualTo(comver.MustParse("2")), + ) + + fmt.Println(a) + // Output: 2 +} + +func ExampleAnd_impossibleInterval() { + _, err := comver.And( + comver.NewGreaterThanOrEqualTo(comver.MustParse("3")), + comver.NewLessThan(comver.MustParse("2")), + ) + + fmt.Println(err) + // Output: impossible interval +} diff --git a/and_test.go b/and_test.go new file mode 100644 index 0000000..2347de2 --- /dev/null +++ b/and_test.go @@ -0,0 +1,792 @@ +package comver + +import ( + "errors" + "math/rand/v2" + "reflect" + "slices" + "testing" +) + +func TestAnd(t *testing.T) { + t.Parallel() + + var nilC CeilingFloorConstrainter + + tests := []struct { + name string + es []Endless + want CeilingFloorConstrainter + wantErr error + }{ + { + name: "empty", + es: []Endless{}, + want: nilC, + wantErr: errNoEndlessGiven, + }, + { + name: "single_lessThan", + es: []Endless{NewLessThan(MustParse("1"))}, + want: NewLessThan(MustParse("1")), + wantErr: nil, + }, + { + name: "single_lessThanOrEqualTo", + es: []Endless{NewLessThanOrEqualTo(MustParse("2"))}, + want: NewLessThanOrEqualTo(MustParse("2")), + wantErr: nil, + }, + { + name: "single_greaterThan", + es: []Endless{NewGreaterThan(MustParse("3"))}, + want: NewGreaterThan(MustParse("3")), + wantErr: nil, + }, + { + name: "single_greaterThanOrEqualTo", + es: []Endless{NewGreaterThanOrEqualTo(MustParse("4"))}, + want: NewGreaterThanOrEqualTo(MustParse("4")), + wantErr: nil, + }, + { + name: "single_wildcard", + es: []Endless{NewWildcard()}, + want: NewWildcard(), + wantErr: nil, + }, + { + name: "multiple_wildcards", + es: []Endless{NewWildcard(), NewWildcard()}, + want: NewWildcard(), + wantErr: nil, + }, + { + name: "multiple_ceiling_inclusive", + es: []Endless{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + }, + want: NewLessThan(MustParse("1")), + wantErr: nil, + }, + { + name: "multiple_ceiling_non_inclusive", + es: []Endless{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + }, + want: NewLessThan(MustParse("1")), + wantErr: nil, + }, + { + name: "multiple_floors_inclusive", + es: []Endless{ + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + }, + want: NewGreaterThanOrEqualTo(MustParse("4")), + wantErr: nil, + }, + { + name: "multiple_floors_non_inclusive", + es: []Endless{ + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + NewGreaterThan(MustParse("4")), + }, + want: NewGreaterThan(MustParse("4")), + wantErr: nil, + }, + { + name: "impossible_interval", + es: []Endless{ + NewLessThan(MustParse("1")), + NewGreaterThan(MustParse("4")), + }, + want: nilC, + wantErr: errImpossibleInterval, + }, + { + name: "impossible_interval_wildcard", + es: []Endless{ + NewLessThan(MustParse("1")), + NewGreaterThan(MustParse("4")), + NewWildcard(), + }, + want: nilC, + wantErr: errImpossibleInterval, + }, + { + name: "multiple_wildcards_multiple_endlesses", + es: []Endless{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + NewGreaterThan(MustParse("4")), + NewWildcard(), + NewWildcard(), + }, + want: nilC, + wantErr: errImpossibleInterval, + }, + { + name: "impossible_interval_same_version", + es: []Endless{ + NewLessThan(MustParse("1")), + NewGreaterThan(MustParse("1")), + }, + want: nilC, + wantErr: errImpossibleInterval, + }, + + { + name: "exact_version_ceiling_inclusive", + es: []Endless{ + NewLessThanOrEqualTo(MustParse("1")), + NewGreaterThan(MustParse("1")), + }, + want: nilC, + wantErr: errImpossibleInterval, + }, + { + name: "exact_version_floor_inclusive", + es: []Endless{ + NewLessThan(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + }, + want: nilC, + wantErr: errImpossibleInterval, + }, + { + name: "exact_version_ceiling_inclusive_floor_inclusive", + es: []Endless{ + NewLessThanOrEqualTo(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + }, + want: NewExactConstraint(MustParse("1")), + wantErr: nil, + }, + { + name: "exact_version_wildcard", + es: []Endless{ + NewLessThanOrEqualTo(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + NewWildcard(), + }, + want: NewExactConstraint(MustParse("1")), + wantErr: nil, + }, + { + name: "impossible_interval_same_version", + es: []Endless{ + NewLessThanOrEqualTo(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("1")), + NewLessThan(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + NewGreaterThan(MustParse("1")), + NewGreaterThan(MustParse("1")), + NewWildcard(), + NewWildcard(), + }, + want: nilC, + wantErr: errImpossibleInterval, + }, + { + name: "impossible_interval_same_version", + es: []Endless{ + NewLessThan(MustParse("1")), + NewLessThan(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + NewGreaterThan(MustParse("1")), + NewGreaterThan(MustParse("1")), + NewWildcard(), + NewWildcard(), + }, + want: nilC, + wantErr: errImpossibleInterval, + }, + { + name: "exact_version_multiple", + es: []Endless{ + NewLessThanOrEqualTo(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + NewWildcard(), + NewWildcard(), + }, + want: NewExactConstraint(MustParse("1")), + wantErr: nil, + }, + { + name: "impossible_interval_same_version", + es: []Endless{ + NewLessThanOrEqualTo(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("1")), + NewLessThan(MustParse("1")), + NewWildcard(), + NewWildcard(), + NewGreaterThan(MustParse("1")), + }, + want: nilC, + wantErr: errImpossibleInterval, + }, + { + name: "impossible_interval_same_version", + es: []Endless{ + NewGreaterThanOrEqualTo(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + NewGreaterThan(MustParse("1")), + NewGreaterThan(MustParse("1")), + NewWildcard(), + NewWildcard(), + NewLessThan(MustParse("1")), + }, + want: nilC, + wantErr: errImpossibleInterval, + }, + { + name: "impossible_interval_same_version", + es: []Endless{ + NewLessThanOrEqualTo(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("1")), + NewLessThan(MustParse("1")), + NewWildcard(), + NewWildcard(), + NewGreaterThanOrEqualTo(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + NewGreaterThan(MustParse("1")), + NewGreaterThan(MustParse("1")), + }, + want: nilC, + wantErr: errImpossibleInterval, + }, + { + name: "impossible_interval_same_version", + es: []Endless{ + NewLessThanOrEqualTo(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("1")), + NewLessThan(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + NewGreaterThan(MustParse("1")), + NewGreaterThan(MustParse("1")), + }, + want: nilC, + wantErr: errImpossibleInterval, + }, + { + name: "simple", + es: []Endless{ + NewLessThan(MustParse("4")), + NewGreaterThan(MustParse("1")), + }, + want: interval{ + upper: NewLessThan(MustParse("4")), + lower: NewGreaterThan(MustParse("1")), + }, + wantErr: nil, + }, + { + name: "compact_ceiling", + es: []Endless{ + NewLessThanOrEqualTo(MustParse("4")), + NewLessThan(MustParse("4")), + NewLessThanOrEqualTo(MustParse("3")), + NewLessThan(MustParse("3")), + NewGreaterThan(MustParse("1")), + }, + want: interval{ + upper: NewLessThan(MustParse("3")), + lower: NewGreaterThan(MustParse("1")), + }, + wantErr: nil, + }, + { + name: "compact_ceiling_inclusive", + es: []Endless{ + NewLessThanOrEqualTo(MustParse("4")), + NewLessThan(MustParse("4")), + NewLessThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("1")), + }, + want: interval{ + upper: NewLessThanOrEqualTo(MustParse("3")), + lower: NewGreaterThan(MustParse("1")), + }, + wantErr: nil, + }, + { + name: "compact_floor", + es: []Endless{ + NewLessThan(MustParse("4")), + NewGreaterThan(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("2")), + NewGreaterThan(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + }, + want: interval{ + upper: NewLessThan(MustParse("4")), + lower: NewGreaterThan(MustParse("2")), + }, + wantErr: nil, + }, + { + name: "compact_floor_non_inclusive", + es: []Endless{ + NewLessThan(MustParse("4")), + NewGreaterThanOrEqualTo(MustParse("2")), + NewGreaterThan(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + }, + want: interval{ + upper: NewLessThan(MustParse("4")), + lower: NewGreaterThanOrEqualTo(MustParse("2")), + }, + wantErr: nil, + }, + { + name: "compact_ceiling_floor", + es: []Endless{ + NewLessThanOrEqualTo(MustParse("4")), + NewLessThan(MustParse("4")), + NewLessThanOrEqualTo(MustParse("3")), + NewLessThan(MustParse("3")), + NewGreaterThan(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("2")), + NewGreaterThan(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + }, + want: interval{ + upper: NewLessThan(MustParse("3")), + lower: NewGreaterThan(MustParse("2")), + }, + wantErr: nil, + }, + { + name: "compact_ceiling_floor_wildcard", + es: []Endless{ + NewLessThanOrEqualTo(MustParse("4")), + NewLessThan(MustParse("4")), + NewLessThanOrEqualTo(MustParse("3")), + NewLessThan(MustParse("3")), + NewGreaterThan(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("2")), + NewGreaterThan(MustParse("1")), + NewGreaterThanOrEqualTo(MustParse("1")), + NewWildcard(), + }, + want: interval{ + upper: NewLessThan(MustParse("3")), + lower: NewGreaterThan(MustParse("2")), + }, + wantErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + rand.Shuffle(len(tt.es), func(i, j int) { + tt.es[i], tt.es[j] = tt.es[j], tt.es[i] + }) + + got, err := And(tt.es...) + + if !errors.Is(err, tt.wantErr) { + t.Fatalf("And() error = %#v, wantErr %#v", err, tt.wantErr) + } + + if err != nil { + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("And() got = %v, want %v", got, tt.want) + } + }) + } + + t.Run("no_arg", func(t *testing.T) { + t.Parallel() + + wantErr := errNoEndlessGiven + + _, err := And() + + if !errors.Is(err, wantErr) { + t.Errorf("And() error = %#v, wantErr %#v", err, wantErr) + } + }) +} + +func Test_minBoundedCeiling(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + cs []Endless + want Endless + wantOk bool + } + tests := []testCase{ + { + name: "empty", + cs: []Endless{}, + want: NewWildcard(), + wantOk: false, + }, + { + name: "nil", + cs: nil, + want: NewWildcard(), + wantOk: false, + }, + { + name: "single_lessThan", + cs: []Endless{NewLessThan(MustParse("1"))}, + want: NewLessThan(MustParse("1")), + wantOk: true, + }, + { + name: "single_lessThanOrEqualTo", + cs: []Endless{NewLessThanOrEqualTo(MustParse("2"))}, + want: NewLessThanOrEqualTo(MustParse("2")), + wantOk: true, + }, + { + name: "single_greaterThan", + cs: []Endless{NewGreaterThan(MustParse("3"))}, + want: NewWildcard(), + wantOk: false, + }, + { + name: "single_greaterThanOrEqualTo", + cs: []Endless{NewGreaterThanOrEqualTo(MustParse("4"))}, + want: NewWildcard(), + wantOk: false, + }, + { + name: "single_wildcard", + cs: []Endless{NewWildcard()}, + want: NewWildcard(), + wantOk: false, + }, + { + name: "multiple_no_ceiling", + cs: []Endless{ + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + NewGreaterThan(MustParse("4")), + }, + want: NewWildcard(), + wantOk: false, + }, + { + name: "multiple_no_ceiling_wildcard", + cs: []Endless{ + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + NewGreaterThan(MustParse("4")), + NewWildcard(), + }, + want: NewWildcard(), + wantOk: false, + }, + { + name: "multiple_ceilings_non_inclusive", + cs: []Endless{ + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + NewGreaterThan(MustParse("4")), + }, + want: NewLessThanOrEqualTo(MustParse("1")), + wantOk: true, + }, + { + name: "multiple_ceilings_inclusive", + cs: []Endless{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + NewGreaterThan(MustParse("4")), + }, + want: NewLessThan(MustParse("1")), + wantOk: true, + }, + { + name: "multiple_ceilings_wildcard", + cs: []Endless{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + NewGreaterThan(MustParse("4")), + NewWildcard(), + }, + want: NewLessThan(MustParse("1")), + wantOk: true, + }, + { + name: "single_ceiling", + cs: []Endless{ + NewLessThanOrEqualTo(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + NewGreaterThan(MustParse("4")), + }, + want: NewLessThanOrEqualTo(MustParse("2")), + wantOk: true, + }, + { + name: "single_ceiling_wildcard", + cs: []Endless{ + NewLessThanOrEqualTo(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + NewGreaterThan(MustParse("4")), + NewWildcard(), + }, + want: NewLessThanOrEqualTo(MustParse("2")), + wantOk: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + rand.Shuffle(len(tt.cs), func(i, j int) { + tt.cs[i], tt.cs[j] = tt.cs[j], tt.cs[i] + }) + + original := slices.Clone(tt.cs) + + got, gotOk := minBoundedCeiling(tt.cs...) + + if gotOk != tt.wantOk { + t.Fatalf("minBoundedCeiling() gotOk = %v, want %v", gotOk, tt.wantOk) + } + + if !tt.wantOk { + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("minBoundedCeiling() got = %v, want %v", got, tt.want) + } + + if !reflect.DeepEqual(tt.cs, original) { + t.Errorf("minBoundedCeiling() changed the original slice got = %v, want %v", tt.cs, original) + } + }) + } +} + +func Test_maxBoundedFloor(t *testing.T) { + t.Parallel() + + type testCase struct { + name string + fs []Endless + want Endless + wantOk bool + } + tests := []testCase{ + { + name: "empty", + fs: []Endless{}, + want: NewWildcard(), + wantOk: false, + }, + { + name: "nil", + fs: nil, + want: NewWildcard(), + wantOk: false, + }, + { + name: "single_lessThan", + fs: []Endless{NewLessThan(MustParse("1"))}, + want: NewWildcard(), + wantOk: false, + }, + { + name: "single_lessThanOrEqualTo", + fs: []Endless{NewLessThanOrEqualTo(MustParse("2"))}, + want: NewWildcard(), + wantOk: false, + }, + { + name: "single_greaterThan", + fs: []Endless{NewGreaterThan(MustParse("3"))}, + want: NewGreaterThan(MustParse("3")), + wantOk: true, + }, + { + name: "single_greaterThanOrEqualTo", + fs: []Endless{NewGreaterThanOrEqualTo(MustParse("4"))}, + want: NewGreaterThanOrEqualTo(MustParse("4")), + wantOk: true, + }, + { + name: "single_wildcard", + fs: []Endless{NewWildcard()}, + want: NewWildcard(), + wantOk: false, + }, + { + name: "multiple_no_floor", + fs: []Endless{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + }, + want: NewWildcard(), + wantOk: false, + }, + { + name: "multiple_no_floor_wildcard", + fs: []Endless{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + NewWildcard(), + }, + want: NewWildcard(), + wantOk: false, + }, + { + name: "multiple_floors_non_inclusive", + fs: []Endless{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThan(MustParse("4")), + }, + want: NewGreaterThan(MustParse("4")), + wantOk: true, + }, + { + name: "multiple_floors_inclusive", + fs: []Endless{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + NewGreaterThan(MustParse("4")), + }, + want: NewGreaterThan(MustParse("4")), + wantOk: true, + }, + { + name: "multiple_floors_wildcard", + fs: []Endless{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + NewGreaterThan(MustParse("4")), + NewWildcard(), + }, + want: NewGreaterThan(MustParse("4")), + wantOk: true, + }, + { + name: "single_floor", + fs: []Endless{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + NewGreaterThan(MustParse("3")), + }, + want: NewGreaterThan(MustParse("3")), + wantOk: true, + }, + { + name: "single_floor_wildcard", + fs: []Endless{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + NewGreaterThan(MustParse("3")), + NewWildcard(), + }, + want: NewGreaterThan(MustParse("3")), + wantOk: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + rand.Shuffle(len(tt.fs), func(i, j int) { + tt.fs[i], tt.fs[j] = tt.fs[j], tt.fs[i] + }) + + original := slices.Clone(tt.fs) + + got, gotOk := maxBoundedFloor(tt.fs...) + + if gotOk != tt.wantOk { + t.Fatalf("maxBoundedFloor() gotOk = %v, want %v", gotOk, tt.wantOk) + } + + if !tt.wantOk { + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("maxBoundedFloor() got = %v, want %v", got, tt.want) + } + + if !reflect.DeepEqual(tt.fs, original) { + t.Errorf("maxBoundedFloor() changed the original slice got = %v, want %v", tt.fs, original) + } + }) + } +} diff --git a/compact.go b/compact.go new file mode 100644 index 0000000..2936c33 --- /dev/null +++ b/compact.go @@ -0,0 +1,249 @@ +package comver + +import ( + "slices" +) + +// Compact returns a new [Constrainter] that is logically equivalent to the input [Or]. +// The returned [Constrainter] may or may be not be an [Or] instance. +// When it is, Compact tries to return the shortest [Or] possible. +func Compact(o Or) Constrainter { //nolint:cyclop,ireturn + if len(o) == 0 { + return Or{} + } + if len(o) == 1 { + return o[0] + } + if slices.ContainsFunc(o, wildcard) { + return NewWildcard() + } + + o = slices.Clone(o) + + ceiling, ceilingOk := maxFloorlessCeiling(o...) + floor, floorOk := minCeilinglessFloor(o...) + + // short circuit if we have a wildcard + if ceilingOk && floorOk && matchAll(ceiling, floor) { + return NewWildcard() + } + + o = slices.DeleteFunc(o, func(c CeilingFloorConstrainter) bool { + return c.ceiling().wildcard() || c.floor().wildcard() || + (ceilingOk && ceiling.compare(c.ceiling()) >= 0) || + (floorOk && floor.compare(c.floor()) <= 0) + }) + + // important to sort before compacting + slices.SortFunc(o, compare) + o = slices.CompactFunc(o, func(a, b CeilingFloorConstrainter) bool { + return compare(a, b) == 0 + }) + + r := compactMultiple(o) + + if floorOk { + r = append(r, floor) + } + if ceilingOk { + r = append(r, ceiling) + } + + if len(r) == 1 { + return r[0] + } + + slices.SortFunc(r, compare) + + return slices.Clip(r) +} + +func wildcard(c CeilingFloorConstrainter) bool { + return c.floor().wildcard() && c.ceiling().wildcard() +} + +func minCeilinglessFloor(cs ...CeilingFloorConstrainter) (Endless, bool) { + cs = slices.Clone(cs) + o := slices.Clone(cs) + + cs = slices.DeleteFunc(cs, func(c CeilingFloorConstrainter) bool { + return !c.ceiling().wildcard() + }) + + if len(cs) == 0 { + var nilE Endless + + return nilE, false + } + + m := slices.MinFunc(cs, func(a, b CeilingFloorConstrainter) int { + return a.floor().compare(b.floor()) + }).floor() + + o = slices.DeleteFunc(o, func(c CeilingFloorConstrainter) bool { + return c.ceiling().wildcard() || + c.floor().wildcard() + }) + + for i := range o { + if o[i].floor().compare(m) < 0 { + if o[i].Check(*m.version) || matchAll(o[i].ceiling(), m) { + m = o[i].floor() + } + + continue + } + + if m.versionCompare(o[i].floor().version) == 0 { + if o[i].floor().inclusive() { + m = o[i].floor() + } + + continue + } + } + + return m.floor(), true +} + +func maxFloorlessCeiling(cs ...CeilingFloorConstrainter) (Endless, bool) { + cs = slices.Clone(cs) + o := slices.Clone(cs) + + cs = slices.DeleteFunc(cs, func(c CeilingFloorConstrainter) bool { + return !c.floor().wildcard() + }) + + if len(cs) == 0 { + var nilE Endless + + return nilE, false + } + + m := slices.MaxFunc(cs, func(a, b CeilingFloorConstrainter) int { + return a.ceiling().compare(b.ceiling()) + }).ceiling() + + o = slices.DeleteFunc(o, func(c CeilingFloorConstrainter) bool { + return c.ceiling().wildcard() || c.floor().wildcard() + }) + + for i := range o { + if o[i].ceiling().compare(m) > 0 { + if o[i].Check(*m.version) || matchAll(o[i].floor(), m) { + m = o[i].ceiling() + } + + continue + } + + if m.versionCompare(o[i].ceiling().version) == 0 { + if o[i].ceiling().inclusive() { + m = o[i].ceiling() + } + + continue + } + } + + return m, true +} + +func matchAll(e, f Endless) bool { + if e.ceilingBounded() && f.ceilingBounded() { + return false + } + + if e.floorBounded() && f.floorBounded() { + return false + } + + cmp := e.compare(f) + + if cmp == 0 { + return false + } + + if cmp > 0 { + e, f = f, e + } + + if e.versionCompare(f.version) == 0 { + return e.inclusive() || f.inclusive() + } + + return !e.ceilingBounded() && !f.floorBounded() +} + +func compare(a, b CeilingFloorConstrainter) int { + cmp := a.floor().compare(b.floor()) + + if cmp != 0 { + return cmp + } + + return a.ceiling().compare(b.ceiling()) +} + +func compactTwo(a, b CeilingFloorConstrainter) (CeilingFloorConstrainter, bool) { //nolint:ireturn + cmp := compare(a, b) + if cmp == 0 { + return a, true + } + + if cmp > 0 { + a, b = b, a + } + + if !overlap(a, b) && !continuous(a, b) { + return a, false + } + + if a.ceiling().compare(b.ceiling()) > 0 { + return a, true + } + + return interval{ + upper: b.ceiling(), + lower: a.floor(), + }, true +} + +func compactMultiple(o []CeilingFloorConstrainter) Or { + r := make(Or, 0, len(o)+2) //nolint:mnd + + if len(o) != 0 { + p := o[0] + for i := range o { + q, ok := compactTwo(p, o[i]) + + if ok { + p = q + } else { + r = append(r, p) + p = o[i] + } + + // always append the last r + if i == len(o)-1 { + r = append(r, p) + } + } + } + + return r +} + +func overlap(a, b CeilingFloorConstrainter) bool { + return a.Check(*b.floor().version) || + b.Check(*a.floor().version) +} + +func continuous(a, b CeilingFloorConstrainter) bool { + f := func(a, b CeilingFloorConstrainter) bool { + return (a.ceiling().inclusive() || b.floor().inclusive()) && + a.ceiling().version.Compare(*b.floor().version) == 0 + } + + return f(a, b) || f(b, a) +} diff --git a/compact_example_test.go b/compact_example_test.go new file mode 100644 index 0000000..496d20e --- /dev/null +++ b/compact_example_test.go @@ -0,0 +1,87 @@ +package comver_test + +import ( + "fmt" + + "github.com/typisttech/comver" +) + +func ExampleCompact() { + o := comver.Or{ + comver.MustAnd( + comver.NewLessThan(comver.MustParse("2")), + comver.NewGreaterThan(comver.MustParse("1")), + ), + comver.MustAnd( + comver.NewLessThan(comver.MustParse("5")), + comver.NewGreaterThan(comver.MustParse("3")), + ), + comver.MustAnd( + comver.NewLessThan(comver.MustParse("6")), + comver.NewGreaterThan(comver.MustParse("4")), + ), + } + + c := comver.Compact(o) + + fmt.Println("Before:", o) + fmt.Println("After:", c) + + // Output: + // Before: >1 <2 || >3 <5 || >4 <6 + // After: >1 <2 || >3 <6 +} + +func ExampleCompact_endless() { + o := comver.Or{ + comver.MustAnd( + comver.NewLessThan(comver.MustParse("5")), + comver.NewGreaterThan(comver.MustParse("3")), + ), + comver.NewGreaterThan(comver.MustParse("4")), + } + + c := comver.Compact(o) + + fmt.Println("Before:", o) + fmt.Println("After:", c) + + // Output: + // Before: >3 <5 || >4 + // After: >3 +} + +func ExampleCompact_wildcard() { + o := comver.Or{ + comver.NewLessThan(comver.MustParse("3")), + comver.NewGreaterThan(comver.MustParse("2")), + } + + c := comver.Compact(o) + + fmt.Println("Before:", o) + fmt.Println("After:", c) + + // Output: + // Before: <3 || >2 + // After: * +} + +func ExampleCompact_wildcardTrumps() { + o := comver.Or{ + comver.MustAnd( + comver.NewLessThan(comver.MustParse("2")), + comver.NewGreaterThan(comver.MustParse("1")), + ), + comver.NewWildcard(), + } + + c := comver.Compact(o) + + fmt.Println("Before:", o) + fmt.Println("After:", c) + + // Output: + // Before: >1 <2 || * + // After: * +} diff --git a/compact_test.go b/compact_test.go new file mode 100644 index 0000000..260d3aa --- /dev/null +++ b/compact_test.go @@ -0,0 +1,1583 @@ +package comver + +import ( + "math/rand/v2" + "reflect" + "slices" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestCompact(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + o Or + want Constrainter + }{ + { + name: "empty", + o: Or{}, + want: Or{}, + }, + { + name: "nil", + o: nil, + want: Or{}, + }, + { + name: "single_lessThan", + o: Or{NewLessThan(MustParse("1"))}, + want: NewLessThan(MustParse("1")), + }, + { + name: "single_lessThanOrEqualTo", + o: Or{NewLessThanOrEqualTo(MustParse("2"))}, + want: NewLessThanOrEqualTo(MustParse("2")), + }, + { + name: "single_greaterThan", + o: Or{NewGreaterThan(MustParse("3"))}, + want: NewGreaterThan(MustParse("3")), + }, + { + name: "single_greaterThanOrEqualTo", + o: Or{NewGreaterThanOrEqualTo(MustParse("4"))}, + want: NewGreaterThanOrEqualTo(MustParse("4")), + }, + { + name: "single_exact", + o: Or{NewExactConstraint(MustParse("5"))}, + want: NewExactConstraint(MustParse("5")), + }, + { + name: "single_interval", + o: Or{ + interval{ + upper: NewLessThan(MustParse("7")), + lower: NewGreaterThan(MustParse("6")), + }, + }, + want: interval{ + upper: NewLessThan(MustParse("7")), + lower: NewGreaterThan(MustParse("6")), + }, + }, + { + name: "single_wildcard", + o: Or{NewWildcard()}, + want: NewWildcard(), + }, + { + name: "multiple_wildcards", + o: Or{NewWildcard(), NewWildcard()}, + want: NewWildcard(), + }, + { + name: "wildcard_trumps_everything_else", + o: Or{ + NewLessThan(MustParse("1")), + NewLessThanOrEqualTo(MustParse("1")), + NewLessThan(MustParse("2")), + NewLessThanOrEqualTo(MustParse("2")), + NewGreaterThanOrEqualTo(MustParse("3")), + NewGreaterThan(MustParse("3")), + NewGreaterThanOrEqualTo(MustParse("4")), + NewGreaterThan(MustParse("4")), + NewExactConstraint(MustParse("5")), + interval{ + upper: NewLessThan(MustParse("7")), + lower: NewGreaterThan(MustParse("6")), + }, + NewWildcard(), + }, + want: NewWildcard(), + }, + { + name: "match_all", + o: Or{ + NewLessThan(MustParse("10")), + NewGreaterThan(MustParse("9")), + }, + want: NewWildcard(), + }, + { + name: "match_all_same_version_ceiling_inclusive_floor_inclusive", + o: Or{ + NewLessThanOrEqualTo(MustParse("10")), + NewGreaterThanOrEqualTo(MustParse("10")), + }, + want: NewWildcard(), + }, + { + name: "match_all_same_version_ceiling_non_inclusive_floor_inclusive", + o: Or{ + NewLessThan(MustParse("10")), + NewGreaterThanOrEqualTo(MustParse("10")), + }, + want: NewWildcard(), + }, + { + name: "match_all_same_version_ceiling_inclusive_floor_non_inclusive", + o: Or{ + NewLessThanOrEqualTo(MustParse("10")), + NewGreaterThan(MustParse("10")), + }, + want: NewWildcard(), + }, + { + name: "same_version_not_match_all", + o: Or{ + NewGreaterThan(MustParse("10")), + NewLessThan(MustParse("10")), + }, + want: Or{ + NewLessThan(MustParse("10")), + NewGreaterThan(MustParse("10")), + }, + }, + { + name: "unrelated_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + }, + { + name: "overlapping_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("18")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + }, + { + name: "overlapping_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThan(MustParse("13")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + }, + { + name: "overlapping_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("24")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("26")), + }, + }, + }, + { + name: "overlapping_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("19")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("26")), + }, + }, + }, + { + name: "overlapping_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("25")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("19")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("26")), + }, + }, + { + name: "overlapping_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("12")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("26")), + }, + }, + { + name: "overlapping_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("19")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("26")), + }, + }, + { + name: "continuous_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThanOrEqualTo(MustParse("17")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + }, + { + name: "continuous_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("17")), + }, + interval{ + lower: NewGreaterThanOrEqualTo(MustParse("17")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("20")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + }, + { + name: "continuous_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThanOrEqualTo(MustParse("23")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("26")), + }, + }, + }, + { + name: "continuous_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThanOrEqualTo(MustParse("23")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThan(MustParse("17")), + upper: NewLessThan(MustParse("26")), + }, + }, + }, + { + name: "continuous_intervals", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("14")), + }, + interval{ + lower: NewGreaterThanOrEqualTo(MustParse("14")), + upper: NewLessThanOrEqualTo(MustParse("23")), + }, + interval{ + lower: NewGreaterThan(MustParse("23")), + upper: NewLessThan(MustParse("26")), + }, + }, + want: interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("26")), + }, + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("18")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("18")), + }, + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("17")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + NewGreaterThan(MustParse("15")), + }, + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThanOrEqualTo(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("17")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + NewGreaterThan(MustParse("15")), + }, + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("16")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + NewGreaterThan(MustParse("15")), + }, + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("15")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + NewGreaterThanOrEqualTo(MustParse("15")), + }, + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThanOrEqualTo(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("15")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + NewGreaterThanOrEqualTo(MustParse("15")), + }, + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("14")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + NewGreaterThanOrEqualTo(MustParse("14")), + }, + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("13")), + }, + want: NewGreaterThan(MustParse("11")), + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThanOrEqualTo(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("13")), + }, + want: NewGreaterThan(MustParse("11")), + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("12")), + }, + want: NewGreaterThan(MustParse("11")), + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("11")), + }, + want: NewGreaterThanOrEqualTo(MustParse("11")), + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThanOrEqualTo(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("11")), + }, + want: NewGreaterThanOrEqualTo(MustParse("11")), + }, + { + name: "tail_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThanOrEqualTo(MustParse("10")), + }, + want: NewGreaterThanOrEqualTo(MustParse("10")), + }, + + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("18")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("18")), + }, + }, + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("17")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("17")), + }, + }, + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThanOrEqualTo(MustParse("17")), + }, + NewGreaterThan(MustParse("17")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + NewGreaterThan(MustParse("15")), + }, + }, + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("16")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + NewGreaterThan(MustParse("15")), + }, + }, + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("15")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + NewGreaterThan(MustParse("15")), + }, + }, + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThanOrEqualTo(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("15")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + NewGreaterThanOrEqualTo(MustParse("15")), + }, + }, + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("14")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + NewGreaterThan(MustParse("14")), + }, + }, + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("13")), + }, + want: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + NewGreaterThan(MustParse("13")), + }, + }, + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThanOrEqualTo(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("13")), + }, + want: NewGreaterThan(MustParse("11")), + }, + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("12")), + }, + want: NewGreaterThan(MustParse("11")), + }, + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("11")), + }, + want: NewGreaterThan(MustParse("11")), + }, + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThanOrEqualTo(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("11")), + }, + want: NewGreaterThanOrEqualTo(MustParse("11")), + }, + { + name: "tail_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewGreaterThan(MustParse("10")), + }, + want: NewGreaterThan(MustParse("10")), + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThan(MustParse("18")), + }, + want: NewLessThan(MustParse("18")), + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThan(MustParse("17")), + }, + want: NewLessThan(MustParse("17")), + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThanOrEqualTo(MustParse("17")), + }, + NewLessThan(MustParse("17")), + }, + want: NewLessThanOrEqualTo(MustParse("17")), + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThan(MustParse("16")), + }, + want: NewLessThan(MustParse("17")), + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThan(MustParse("15")), + }, + want: Or{ + NewLessThan(MustParse("15")), + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThanOrEqualTo(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThan(MustParse("15")), + }, + want: NewLessThan(MustParse("17")), + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThan(MustParse("14")), + }, + want: Or{ + NewLessThan(MustParse("14")), + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThan(MustParse("13")), + }, + want: Or{ + NewLessThan(MustParse("13")), + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThanOrEqualTo(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThan(MustParse("13")), + }, + want: Or{ + NewLessThanOrEqualTo(MustParse("13")), + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThan(MustParse("12")), + }, + want: Or{ + NewLessThan(MustParse("13")), + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThan(MustParse("11")), + }, + want: Or{ + NewLessThan(MustParse("11")), + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThanOrEqualTo(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThan(MustParse("11")), + }, + want: Or{ + NewLessThan(MustParse("13")), + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_non_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThan(MustParse("10")), + }, + want: Or{ + NewLessThan(MustParse("10")), + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("18")), + }, + want: NewLessThanOrEqualTo(MustParse("18")), + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("17")), + }, + want: NewLessThanOrEqualTo(MustParse("17")), + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThanOrEqualTo(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("17")), + }, + want: NewLessThanOrEqualTo(MustParse("17")), + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("16")), + }, + want: NewLessThan(MustParse("17")), + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("15")), + }, + want: NewLessThan(MustParse("17")), + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThanOrEqualTo(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("15")), + }, + want: NewLessThan(MustParse("17")), + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("14")), + }, + want: Or{ + NewLessThanOrEqualTo(MustParse("14")), + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("13")), + }, + want: Or{ + NewLessThanOrEqualTo(MustParse("13")), + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThanOrEqualTo(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("13")), + }, + want: Or{ + NewLessThanOrEqualTo(MustParse("13")), + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("12")), + }, + want: Or{ + NewLessThan(MustParse("13")), + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("11")), + }, + want: Or{ + NewLessThan(MustParse("13")), + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThanOrEqualTo(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("11")), + }, + want: Or{ + NewLessThan(MustParse("13")), + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "head_inclusive", + o: Or{ + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + NewLessThanOrEqualTo(MustParse("10")), + }, + want: Or{ + NewLessThanOrEqualTo(MustParse("10")), + interval{ + lower: NewGreaterThan(MustParse("11")), + upper: NewLessThan(MustParse("13")), + }, + interval{ + lower: NewGreaterThan(MustParse("15")), + upper: NewLessThan(MustParse("17")), + }, + }, + }, + { + name: "match_all_with_interval", + o: Or{ + NewGreaterThan(MustParse("1")), + interval{ + lower: NewGreaterThan(MustParse("2")), + upper: NewLessThan(MustParse("3")), + }, + NewLessThan(MustParse("4")), + }, + want: NewWildcard(), + }, + { + name: "match_all_within_interval", + o: Or{ + NewGreaterThan(MustParse("2")), + interval{ + lower: NewGreaterThan(MustParse("1")), + upper: NewLessThan(MustParse("4")), + }, + NewLessThan(MustParse("3")), + }, + want: NewWildcard(), + }, + { + name: "match_all_within_intervals", + o: Or{ + NewLessThan(MustParse("3")), + interval{ + lower: NewGreaterThan(MustParse("2")), + upper: NewLessThan(MustParse("6")), + }, + interval{ + lower: NewGreaterThan(MustParse("5")), + upper: NewLessThan(MustParse("8")), + }, + NewGreaterThan(MustParse("7")), + }, + want: NewWildcard(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + rand.Shuffle(len(tt.o), func(i, j int) { + tt.o[i], tt.o[j] = tt.o[j], tt.o[i] + }) + + original := slices.Clone(tt.o) + + got := Compact(tt.o) + + opts := cmp.Options{ + cmp.Comparer(func(a, b Constrainter) bool { + return a.String() == b.String() + }), + } + + if diff := cmp.Diff(tt.want, got, opts); diff != "" { + t.Errorf("Compact(%q) mismatch (-want +got):\n%s", original, diff) + } + + if !reflect.DeepEqual(tt.o, original) { + t.Errorf("Compact() changed the original slice got = %v, want %v", tt.o, original) + } + }) + } +} + +func Test_wildcard(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + c CeilingFloorConstrainter + want bool + }{ + { + name: "lessThan", + c: NewLessThan(MustParse("1")), + want: false, + }, + { + name: "lessThanOrEqualTo", + c: NewLessThanOrEqualTo(MustParse("2")), + want: false, + }, + { + name: "greaterThan", + c: NewGreaterThan(MustParse("3")), + want: false, + }, + { + name: "greaterThanOrEqualTo", + c: NewGreaterThanOrEqualTo(MustParse("4")), + want: false, + }, + { + name: "exact", + c: NewExactConstraint(MustParse("5")), + want: false, + }, + { + name: "interval", + c: interval{ + upper: NewLessThan(MustParse("7")), + lower: NewGreaterThan(MustParse("6")), + }, + want: false, + }, + { + name: "wildcard", + c: NewWildcard(), + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if got := wildcard(tt.c); got != tt.want { + t.Errorf("wildcard() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/constraint.go b/constraint.go index 9f3eacb..43228fe 100644 --- a/constraint.go +++ b/constraint.go @@ -1,140 +1,14 @@ package comver -const ( - greaterThanOrEqualTo constraintOp = iota - greaterThan - lessThan - lessThanOrEqualTo - - ErrUnexpectedConstraintOp stringError = "unexpected constraintOp" -) - -type constraintOp int8 - -func (op constraintOp) compare(other constraintOp) int { - return int(op) - int(other) -} - -func (op constraintOp) ceillingless() bool { - return op == greaterThan || op == greaterThanOrEqualTo -} - -func (op constraintOp) floorless() bool { - return op == lessThanOrEqualTo || op == lessThan -} - -func (op constraintOp) String() string { - switch op { - case lessThanOrEqualTo: - return "<=" - case lessThan: - return "<" - case greaterThan: - return ">" - case greaterThanOrEqualTo: - return ">=" - default: - return ErrUnexpectedConstraintOp.Error() - } -} - -type constraint struct { - // The Version used in the constraint check, e.g.: the Version representing 1.2.3 in '<=1.2.3'. - version Version - op constraintOp -} - -func NewLessThanOrEqualToConstraint(v Version) *constraint { - return &constraint{ - version: v, - op: lessThanOrEqualTo, - } -} - -func NewLessThanConstraint(v Version) *constraint { - return &constraint{ - version: v, - op: lessThan, - } -} - -func NewGreaterThanConstraint(v Version) *constraint { - return &constraint{ - version: v, - op: greaterThan, - } -} - -func NewGreaterThanOrEqualToConstraint(v Version) *constraint { - return &constraint{ - version: v, - op: greaterThanOrEqualTo, - } -} - -func (c *constraint) lowerbounded() bool { - return c.op == greaterThan || c.op == greaterThanOrEqualTo -} - -func (c *constraint) upperbounded() bool { - return c.op == lessThanOrEqualTo || c.op == lessThan -} - -// Check reports whether a [Version] satisfies the constraint. -func (c *constraint) Check(v Version) bool { - if c == nil { - // this should never happen - return true - } - - cmp := v.Compare(c.version) - - switch c.op { - case lessThan: - return cmp < 0 - case lessThanOrEqualTo: - return cmp <= 0 - case greaterThanOrEqualTo: - return cmp >= 0 - case greaterThan: - return cmp > 0 - default: - // this should never happen - return true - } -} - -func (c *constraint) String() string { - return c.op.String() + c.version.Short() +type Constrainter interface { + // Check reports whether a [Version] satisfies the constraint. + Check(v Version) bool + String() string } -// compare returns an integer comparing two constraints. -// -// The comparison is done by comparing the version first, then the operator. -// - Versions are compared according to their semantic precedence -// - Operators are compared in the following order (lowest to highest): >=, >, <, <= -// - nil is considered to be the higher than upperbounded constraints -// - nil is considered to be the lower than lowerbounded constraints -// -// The result is 0 when c == d, -1 when c < d, or +1 when c > d. -func (c *constraint) compare(d *constraint) int { - switch { - case c == nil && d == nil: - return 0 - case c == nil: - if d.lowerbounded() { - return -1 - } - return +1 - case d == nil: - if c.lowerbounded() { - return +1 - } - return -1 - } +type CeilingFloorConstrainter interface { + ceiling() Endless + floor() Endless - if cmp := c.version.Compare(d.version); cmp != 0 { - return cmp - } - return c.op.compare(d.op) + Constrainter } diff --git a/constraint_test.go b/constraint_test.go deleted file mode 100644 index d2fcc21..0000000 --- a/constraint_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package comver - -import "testing" - -func Test_constraint_Check(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - constraint *constraint - version Version - want bool - }{ - { - name: "lessThan satisfied", - constraint: &constraint{version: Version{major: 2}, op: lessThan}, - version: Version{major: 1}, - want: true, - }, - { - name: "lessThan just satisfied", - constraint: &constraint{version: Version{major: 2}, op: lessThan}, - version: Version{major: 2, modifier: modifierRC}, - want: true, - }, - { - name: "lessThan just not satisfied", - constraint: &constraint{version: Version{major: 2}, op: lessThan}, - version: Version{major: 2}, - want: false, - }, - { - name: "lessThan not satisfied", - constraint: &constraint{version: Version{major: 2}, op: lessThan}, - version: Version{major: 3}, - want: false, - }, - - { - name: "lessThanOrEqualTo satisfied", - constraint: &constraint{version: Version{major: 2}, op: lessThanOrEqualTo}, - version: Version{major: 1}, - want: true, - }, - { - name: "lessThanOrEqualTo just satisfied", - constraint: &constraint{version: Version{major: 2}, op: lessThanOrEqualTo}, - version: Version{major: 2}, - want: true, - }, - { - name: "lessThanOrEqualTo just not satisfied", - constraint: &constraint{version: Version{major: 2}, op: lessThanOrEqualTo}, - version: Version{major: 2, modifier: modifierPatch}, - want: false, - }, - { - name: "lessThanOrEqualTo not satisfied", - constraint: &constraint{version: Version{major: 2}, op: lessThanOrEqualTo}, - version: Version{major: 3}, - want: false, - }, - - { - name: "greaterThan satisfied", - constraint: &constraint{version: Version{major: 2}, op: greaterThan}, - version: Version{major: 3}, - want: true, - }, - { - name: "greaterThan just satisfied", - constraint: &constraint{version: Version{major: 2}, op: greaterThan}, - version: Version{major: 2, modifier: modifierPatch}, - want: true, - }, - { - name: "greaterThan just not satisfied", - constraint: &constraint{version: Version{major: 2}, op: greaterThan}, - version: Version{major: 2}, - want: false, - }, - { - name: "greaterThan not satisfied", - constraint: &constraint{version: Version{major: 2}, op: greaterThan}, - version: Version{major: 1}, - want: false, - }, - - { - name: "greaterThanOrEqualTo satisfied", - constraint: &constraint{version: Version{major: 2}, op: greaterThanOrEqualTo}, - version: Version{major: 3}, - want: true, - }, - { - name: "greaterThanOrEqualTo just satisfied", - constraint: &constraint{version: Version{major: 2}, op: greaterThanOrEqualTo}, - version: Version{major: 2}, - want: true, - }, - { - name: "greaterThanOrEqualTo just not satisfied", - constraint: &constraint{version: Version{major: 2}, op: greaterThanOrEqualTo}, - version: Version{major: 2, modifier: modifierRC}, - want: false, - }, - { - name: "greaterThanOrEqualTo not satisfied", - constraint: &constraint{version: Version{major: 2}, op: greaterThanOrEqualTo}, - version: Version{major: 1}, - want: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - if got := tt.constraint.Check(tt.version); got != tt.want { - t.Errorf("Check() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/doc_examle_test.go b/doc_examle_test.go deleted file mode 100644 index e265409..0000000 --- a/doc_examle_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package comver_test - -import ( - "fmt" - - "github.com/typisttech/comver" -) - -func Example_version() { - ss := []string{ - "1.2.3", - "v1.2.p5+foo", - "v1.2.3.4.p5+foo", - "2010-01-02", - "2010-01-02.5", - "not a version", - "1.0.0-meh", - "20100102.0.3.4", - "1.0.0-alpha.beta", - } - - for _, s := range ss { - v, err := comver.NewVersion(s) - if err != nil { - fmt.Println(s, " => ", err) - continue - } - fmt.Println(s, " => ", v) - } - - // Output: - // 1.2.3 => 1.2.3.0 - // v1.2.p5+foo => 1.2.0.0-patch5 - // v1.2.3.4.p5+foo => 1.2.3.4-patch5 - // 2010-01-02 => 2010.1.2.0 - // 2010-01-02.5 => 2010.1.2.5 - // not a version => error parsing version string "not a version" - // 1.0.0-meh => error parsing version string "1.0.0-meh" - // 20100102.0.3.4 => error parsing version string "20100102.0.3.4" - // 1.0.0-alpha.beta => error parsing version string "1.0.0-alpha.beta" -} - -func Example_constraint() { - v1, _ := comver.NewVersion("1") - v2, _ := comver.NewVersion("2") - v3, _ := comver.NewVersion("3") - v4, _ := comver.NewVersion("4") - - cs := []any{ - comver.NewGreaterThanConstraint(v1), - comver.NewGreaterThanOrEqualToConstraint(v2), - comver.NewLessThanOrEqualToConstraint(v3), - comver.NewLessThanConstraint(v4), - } - - for _, c := range cs { - fmt.Println(c) - } - - // Output: - // >1 - // >=2 - // <=3 - // <4 -} - -func Example_interval() { - v1, _ := comver.NewVersion("1") - v2, _ := comver.NewVersion("2") - v3, _ := comver.NewVersion("3") - - g1l3, _ := comver.NewInterval( - comver.NewGreaterThanConstraint(v1), - comver.NewLessThanConstraint(v3), - ) - - if g1l3.Check(v2) { - fmt.Println(v2.Short(), "satisfies", g1l3) - } - - if !g1l3.Check(v3) { - fmt.Println(v2.Short(), "doesn't satisfy", g1l3) - } - - // Output: - // 2 satisfies >1 <3 - // 2 doesn't satisfy >1 <3 -} - -func Example_intervals() { - v1, _ := comver.NewVersion("1") - v2, _ := comver.NewVersion("2") - v3, _ := comver.NewVersion("3") - v4, _ := comver.NewVersion("4") - - g1l3, _ := comver.NewInterval( - comver.NewGreaterThanConstraint(v1), - comver.NewLessThanConstraint(v3), - ) - - ge2le4, _ := comver.NewInterval( - comver.NewGreaterThanOrEqualToConstraint(v2), - comver.NewLessThanOrEqualToConstraint(v4), - ) - - is := comver.Intervals{g1l3, ge2le4} - fmt.Println(is) - - is = comver.Compact(is) - fmt.Println(is) - - // Output: - // >1 <3 || >=2 <=4 - // >1 <=4 -} diff --git a/doc_example_test.go b/doc_example_test.go new file mode 100644 index 0000000..6679738 --- /dev/null +++ b/doc_example_test.go @@ -0,0 +1,60 @@ +package comver_test + +import ( + "fmt" + + "github.com/typisttech/comver" +) + +func Example_version() { + ss := []string{ + "1.2.3", + "1.2", + "1", + + " 1.0.0", + "00.01.03.04", + + "2010-01-02.5", + "2010-01-02", + + "v1.2.3.4-beta.5+foo", + "v1.2.3.4.p5+foo", + "v1.2.3", + "v1.2.p5+foo", + + "not a version", + "1.0.0-alpha.beta", + "1.0.0-meh", + "1.0.0.0.0", + "20100102.0.3.4", + } + + for _, s := range ss { + v, err := comver.Parse(s) + if err != nil { + fmt.Printf("%-21q => %v\n", s, err) + + continue + } + fmt.Printf("%-21q => %v\n", s, v) + } + + // Output: + // "1.2.3" => 1.2.3.0 + // "1.2" => 1.2.0.0 + // "1" => 1.0.0.0 + // " 1.0.0" => 1.0.0.0 + // "00.01.03.04" => 0.1.3.4 + // "2010-01-02.5" => 2010.1.2.5 + // "2010-01-02" => 2010.1.2.0 + // "v1.2.3.4-beta.5+foo" => 1.2.3.4-beta5 + // "v1.2.3.4.p5+foo" => 1.2.3.4-patch5 + // "v1.2.3" => 1.2.3.0 + // "v1.2.p5+foo" => 1.2.0.0-patch5 + // "not a version" => error parsing version string "not a version" + // "1.0.0-alpha.beta" => error parsing version string "1.0.0-alpha.beta" + // "1.0.0-meh" => error parsing version string "1.0.0-meh" + // "1.0.0.0.0" => error parsing version string "1.0.0.0.0" + // "20100102.0.3.4" => error parsing version string "20100102.0.3.4" +} diff --git a/endless.go b/endless.go new file mode 100644 index 0000000..afcfa9e --- /dev/null +++ b/endless.go @@ -0,0 +1,148 @@ +package comver + +// Endless represents a constraint that is either floor bounded, ceiling bounded, +// or wildcard (satisfied by any version). +// The zero value for Endless is a wildcard constraint (satisfied by any version). +type Endless struct { + // The version used in the constraint check, + // e.g.: the version representing 1.2.3 in '<=1.2.3'. + // If nil, the Endless is a wildcard satisfied by any version. + version *Version + op op +} + +func NewLessThanOrEqualTo(v Version) Endless { + return Endless{ + version: &v, + op: lessThanOrEqualTo, + } +} + +func NewLessThan(v Version) Endless { + return Endless{ + version: &v, + op: lessThan, + } +} + +func NewGreaterThan(v Version) Endless { + return Endless{ + version: &v, + op: greaterThan, + } +} + +func NewGreaterThanOrEqualTo(v Version) Endless { + return Endless{ + version: &v, + op: greaterThanOrEqualTo, + } +} + +func NewWildcard() Endless { + return Endless{ //nolint:exhaustruct + version: nil, + } +} + +// Check reports whether a [Version] satisfies the constraint. +func (b Endless) Check(v Version) bool { + if b.version == nil { + // this is wildcard, match all versions + return true + } + + cmp := b.version.Compare(v) + + switch b.op { + case lessThan: + return cmp > 0 + case lessThanOrEqualTo: + return cmp >= 0 + case greaterThanOrEqualTo: + return cmp <= 0 + case greaterThan: + return cmp < 0 + default: + // this should never happen + panic("unexpected constraint operator") + } +} + +func (b Endless) ceiling() Endless { + if !b.ceilingBounded() { + return NewWildcard() + } + + return b +} + +func (b Endless) floor() Endless { + if !b.floorBounded() { + return NewWildcard() + } + + return b +} + +func (b Endless) String() string { + if b.wildcard() { + return "*" + } + + return b.op.String() + b.version.Short() +} + +func (b Endless) wildcard() bool { + return b.version == nil +} + +// compare returns an integer comparing two [Endless] instances. +// +// The comparison is done by comparing the version first, then the operator. +// - Versions are compared according to their semantic precedence +// - Operators are compared in the following order (lowest to highest): >=, >, <, <= +// - wildcard [Endless] is considered to be higher than ceiling bounded [Endless] while +// floor than floor bounded [Endless] +// +// The result is 0 when b == d, -1 when b < d, or +1 when b > d. +func (b Endless) compare(d Endless) int { + switch { + case b.wildcard() && d.wildcard(): + return 0 + case b.wildcard(): + if d.floorBounded() { + return -1 + } + + return +1 + case d.wildcard(): + if b.floorBounded() { + return +1 + } + + return -1 + } + + if cmp := b.versionCompare(d.version); cmp != 0 { + return cmp + } + + return b.op.compare(d.op) +} + +func (b Endless) ceilingBounded() bool { + return b.op.ceilingBounded() +} + +func (b Endless) floorBounded() bool { + return b.op.floorBounded() +} + +func (b Endless) inclusive() bool { + return b.op.inclusive() +} + +func (b Endless) versionCompare(v *Version) int { + return b.version.Compare(*v) +} diff --git a/endless_test.go b/endless_test.go new file mode 100644 index 0000000..32ff838 --- /dev/null +++ b/endless_test.go @@ -0,0 +1,309 @@ +package comver + +import ( + "reflect" + "testing" +) + +func TestEndless_Check(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + endless Endless + version Version + want bool + }{ + { + name: "lessThan_satisfied", + endless: NewLessThan(MustParse("2")), + version: MustParse("1"), + want: true, + }, + { + name: "lessThan_just_satisfied", + endless: NewLessThan(MustParse("2")), + version: MustParse("2.rc"), + want: true, + }, + { + name: "lessThan_just_not_satisfied", + endless: NewLessThan(MustParse("2")), + version: MustParse("2"), + want: false, + }, + { + name: "lessThan_not_satisfied", + endless: NewLessThan(MustParse("2")), + version: MustParse("3"), + want: false, + }, + + { + name: "lessThanOrEqualTo_satisfied", + endless: NewLessThanOrEqualTo(MustParse("2")), + version: MustParse("1"), + want: true, + }, + { + name: "lessThanOrEqualTo_just_satisfied", + endless: NewLessThanOrEqualTo(MustParse("2")), + version: MustParse("2"), + want: true, + }, + { + name: "lessThanOrEqualTo_just_not_satisfied", + endless: NewLessThanOrEqualTo(MustParse("2")), + version: MustParse("2.patch"), + want: false, + }, + { + name: "lessThanOrEqualTo_not_satisfied", + endless: NewLessThanOrEqualTo(MustParse("2")), + version: MustParse("3"), + want: false, + }, + + { + name: "greaterThan_satisfied", + endless: NewGreaterThan(MustParse("2")), + version: MustParse("3"), + want: true, + }, + { + name: "greaterThan_just_satisfied", + endless: NewGreaterThan(MustParse("2")), + version: MustParse("2.patch"), + want: true, + }, + { + name: "greaterThan_just_not_satisfied", + endless: NewGreaterThan(MustParse("2")), + version: MustParse("2"), + want: false, + }, + { + name: "greaterThan_not_satisfied", + endless: NewGreaterThan(MustParse("2")), + version: MustParse("1"), + want: false, + }, + + { + name: "greaterThanOrEqualTo_satisfied", + endless: NewGreaterThanOrEqualTo(MustParse("2")), + version: MustParse("3"), + want: true, + }, + { + name: "greaterThanOrEqualTo_just_satisfied", + endless: NewGreaterThanOrEqualTo(MustParse("2")), + version: MustParse("2"), + want: true, + }, + { + name: "greaterThanOrEqualTo_just_not_satisfied", + endless: NewGreaterThanOrEqualTo(MustParse("2")), + version: MustParse("2.rc"), + want: false, + }, + { + name: "greaterThanOrEqualTo_not_satisfied", + endless: NewGreaterThanOrEqualTo(MustParse("2")), + version: MustParse("1"), + want: false, + }, + { + name: "wildcard", + endless: NewWildcard(), + version: MustParse("1"), + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if got := tt.endless.Check(tt.version); got != tt.want { + t.Errorf("Check() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEndless_Ceiling(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + endless Endless + want Endless + }{ + { + name: "lessThan", + endless: NewLessThan(MustParse("1")), + want: NewLessThan(MustParse("1")), + }, + { + name: "lessThanOrEqualTo", + endless: NewLessThanOrEqualTo(MustParse("2")), + want: NewLessThanOrEqualTo(MustParse("2")), + }, + { + name: "greaterThanOrEqualTo", + endless: NewGreaterThanOrEqualTo(MustParse("3")), + want: NewWildcard(), + }, + { + name: "greaterThan", + endless: NewGreaterThan(MustParse("4")), + want: NewWildcard(), + }, + { + name: "wildcard", + endless: NewWildcard(), + want: NewWildcard(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if got := tt.endless.ceiling(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ceiling() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEndless_Floor(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + endless Endless + want Endless + }{ + { + name: "lessThan", + endless: NewLessThan(MustParse("1")), + want: NewWildcard(), + }, + { + name: "lessThanOrEqualTo", + endless: NewLessThanOrEqualTo(MustParse("2")), + want: NewWildcard(), + }, + { + name: "greaterThanOrEqualTo", + endless: NewGreaterThanOrEqualTo(MustParse("3")), + want: NewGreaterThanOrEqualTo(MustParse("3")), + }, + { + name: "greaterThan", + endless: NewGreaterThan(MustParse("4")), + want: NewGreaterThan(MustParse("4")), + }, + { + name: "wildcard", + endless: NewWildcard(), + want: NewWildcard(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if got := tt.endless.floor(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("floor() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEndless_wildcard(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + endless Endless + want bool + }{ + { + name: "lessThan", + endless: NewLessThan(MustParse("1")), + want: false, + }, + { + name: "lessThanOrEqualTo", + endless: NewLessThanOrEqualTo(MustParse("2")), + want: false, + }, + { + name: "greaterThan", + endless: NewGreaterThan(MustParse("3")), + want: false, + }, + { + name: "greaterThanOrEqualTo", + endless: NewGreaterThanOrEqualTo(MustParse("4")), + want: false, + }, + { + name: "wildcard", + endless: NewWildcard(), + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if got := tt.endless.wildcard(); got != tt.want { + t.Errorf("wildcard() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEndless_String(t *testing.T) { + t.Parallel() + + tests := []struct { + endless Endless + want string + }{ + { + endless: NewLessThanOrEqualTo(MustParse("2")), + want: "<=2", + }, + { + endless: NewLessThan(MustParse("2")), + want: "<2", + }, + { + endless: NewGreaterThan(MustParse("2")), + want: ">2", + }, + { + endless: NewGreaterThanOrEqualTo(MustParse("2")), + want: ">=2", + }, + { + endless: NewWildcard(), + want: "*", + }, + } + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + t.Parallel() + + if got := tt.endless.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/exact.go b/exact.go new file mode 100644 index 0000000..a45ed9f --- /dev/null +++ b/exact.go @@ -0,0 +1,28 @@ +package comver + +type ExactConstraint struct { + version Version +} + +func NewExactConstraint(v Version) ExactConstraint { + return ExactConstraint{ + version: v, + } +} + +func (e ExactConstraint) ceiling() Endless { + return NewLessThanOrEqualTo(e.version) +} + +func (e ExactConstraint) floor() Endless { + return NewGreaterThanOrEqualTo(e.version) +} + +// Check reports whether a [Version] satisfies the constraint. +func (e ExactConstraint) Check(v Version) bool { + return e.version.Compare(v) == 0 +} + +func (e ExactConstraint) String() string { + return e.version.Short() +} diff --git a/go.mod b/go.mod index 1d73b81..39201ba 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/typisttech/comver go 1.23 + +require github.com/google/go-cmp v0.6.0 diff --git a/go.sum b/go.sum index e69de29..5a8d551 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= diff --git a/internal/wordfacetest/version_test.go b/internal/wordfacetest/version_test.go index c433c93..fbb986c 100644 --- a/internal/wordfacetest/version_test.go +++ b/internal/wordfacetest/version_test.go @@ -72,18 +72,18 @@ var invalidWordFenceVersions = []string{ //nolint:gochecknoglobals } //go:generate go run gen_wordfence.go gen.go -func TestNewVersion_Wordfence(t *testing.T) { +func TestParse_Wordfence(t *testing.T) { t.Parallel() for _, v := range wordFenceVersions { t.Run(v, func(t *testing.T) { t.Parallel() - _, err := comver.NewVersion(v) + _, err := comver.Parse(v) wantErr := slices.Contains(invalidWordFenceVersions, v) if (err != nil) != wantErr { - t.Fatalf("NewVersion(%q) error = %v, wantErr %v", v, err, wantErr) + t.Fatalf("Parse(%q) error = %v, wantErr %v", v, err, wantErr) } }) } diff --git a/internal/wordfacetest/wordfence_test.go b/internal/wordfacetest/wordfence_test.go index fea167a..ad7eda8 100644 --- a/internal/wordfacetest/wordfence_test.go +++ b/internal/wordfacetest/wordfence_test.go @@ -84,6 +84,7 @@ var wordFenceVersions = []string{ "0.16.7", "0.17.0", "0.18.3", + "0.18.6.2", "0.18.9", "0.2", "0.2.0", @@ -196,6 +197,7 @@ var wordFenceVersions = []string{ "0.5.70", "0.5.71", "0.5.72", + "0.5.76", "0.5.8.1", "0.5.8.2", "0.58", @@ -327,6 +329,7 @@ var wordFenceVersions = []string{ "0.90", "0.91", "0.92", + "0.92.1", "0.94", "0.95", "0.97", @@ -390,6 +393,7 @@ var wordFenceVersions = []string{ "1.0.227", "1.0.228", "1.0.23", + "1.0.231", "1.0.239", "1.0.24", "1.0.240", @@ -434,6 +438,7 @@ var wordFenceVersions = []string{ "1.0.47", "1.0.48", "1.0.49", + "1.0.4b", "1.0.5", "1.0.5.2", "1.0.52", @@ -465,6 +470,7 @@ var wordFenceVersions = []string{ "1.0.8", "1.0.8.1", "1.0.8.2", + "1.0.8.3", "1.0.8.4", "1.0.8.7", "1.0.81", @@ -607,6 +613,7 @@ var wordFenceVersions = []string{ "1.10.25", "1.10.27", "1.10.28.2", + "1.10.29", "1.10.3", "1.10.31", "1.10.4", @@ -636,6 +643,7 @@ var wordFenceVersions = []string{ "1.11.8", "1.11.9", "1.110", + "1.112.0", "1.12", "1.12.0", "1.12.03", @@ -725,10 +733,12 @@ var wordFenceVersions = []string{ "1.16.1", "1.16.10", "1.16.2", + "1.16.3", "1.16.3.5", "1.16.4", "1.16.44", "1.16.56", + "1.16.6", "1.16.66", "1.16.68", "1.16.7", @@ -1000,7 +1010,6 @@ var wordFenceVersions = []string{ "1.3.33", "1.3.34", "1.3.35", - "1.3.36", "1.3.37", "1.3.38", "1.3.39", @@ -1030,6 +1039,7 @@ var wordFenceVersions = []string{ "1.3.6", "1.3.6.1", "1.3.6.2", + "1.3.6.3", "1.3.6.4", "1.3.6.5", "1.3.6.7", @@ -1369,6 +1379,7 @@ var wordFenceVersions = []string{ "1.6.56.1", "1.6.57", "1.6.57.4", + "1.6.58", "1.6.58.2", "1.6.58.3", "1.6.59", @@ -1419,6 +1430,7 @@ var wordFenceVersions = []string{ "1.64", "1.66", "1.66.0", + "1.66.2", "1.67", "1.68", "1.68.232", @@ -1443,6 +1455,7 @@ var wordFenceVersions = []string{ "1.7.1.4", "1.7.10", "1.7.1001", + "1.7.1003", "1.7.11", "1.7.12", "1.7.13", @@ -1587,6 +1600,7 @@ var wordFenceVersions = []string{ "1.9.13", "1.9.136", "1.9.14", + "1.9.15", "1.9.15.11", "1.9.15.2", "1.9.16", @@ -1596,6 +1610,7 @@ var wordFenceVersions = []string{ "1.9.18", "1.9.19", "1.9.2", + "1.9.2.1", "1.9.20", "1.9.217", "1.9.22", @@ -1690,6 +1705,7 @@ var wordFenceVersions = []string{ "10.5.5", "10.6", "10.6.1", + "10.6.2", "10.6.5", "10.6.6", "10.7", @@ -1700,6 +1716,7 @@ var wordFenceVersions = []string{ "10.9", "10.9.1", "10.9.2", + "10.9.3", "11.0", "11.0.1", "11.0.12", @@ -1781,6 +1798,7 @@ var wordFenceVersions = []string{ "12.3.15", "12.3.16", "12.3.19", + "12.3.20", "12.4", "12.4.0", "12.4.4", @@ -2160,6 +2178,7 @@ var wordFenceVersions = []string{ "2.14.3", "2.14.4", "2.14.5", + "2.145", "2.15", "2.15.0", "2.15.14", @@ -2184,6 +2203,7 @@ var wordFenceVersions = []string{ "2.17.1", "2.17.2", "2.17.3", + "2.18", "2.18.0", "2.18.1", "2.18.16", @@ -2253,6 +2273,7 @@ var wordFenceVersions = []string{ "2.2.8", "2.2.8.3", "2.2.80", + "2.2.82", "2.2.84", "2.2.85", "2.2.86", @@ -2281,6 +2302,7 @@ var wordFenceVersions = []string{ "2.21.2", "2.21.3", "2.210", + "2.216", "2.22", "2.22.1", "2.22.14", @@ -2297,6 +2319,7 @@ var wordFenceVersions = []string{ "2.24.13", "2.24.14", "2.24.3", + "2.243", "2.25", "2.25.0", "2.25.1", @@ -2369,6 +2392,8 @@ var wordFenceVersions = []string{ "2.35.0", "2.35.1.2.3", "2.35.1.3.0", + "2.35.17", + "2.35.19", "2.35.7", "2.36.1", "2.37", @@ -2496,6 +2521,7 @@ var wordFenceVersions = []string{ "2.6.11", "2.6.12", "2.6.13", + "2.6.14", "2.6.15", "2.6.16", "2.6.18", @@ -2780,7 +2806,7 @@ var wordFenceVersions = []string{ "2024.04.30", "2024.08.26", "2024.1", - "2024.11.10", + "2024.11.20", "2024.2", "2024.3", "2024.4", @@ -2825,11 +2851,12 @@ var wordFenceVersions = []string{ "2311.17.01", "234", "24.0.3", + "24.0.7", "24.0128", "24.0422", "24.0902", "240315", - "2408", + "2411", "25.2.0", "26.0", "26.5", @@ -2908,6 +2935,7 @@ var wordFenceVersions = []string{ "3.05.0", "3.05.4", "3.05.8", + "3.06", "3.09", "3.1", "3.1.0", @@ -2923,6 +2951,7 @@ var wordFenceVersions = []string{ "3.1.13", "3.1.14", "3.1.15", + "3.1.15.1", "3.1.16", "3.1.18", "3.1.19", @@ -2954,6 +2983,7 @@ var wordFenceVersions = []string{ "3.1.43", "3.1.44", "3.1.45", + "3.1.46", "3.1.5", "3.1.6", "3.1.60", @@ -3088,6 +3118,7 @@ var wordFenceVersions = []string{ "3.2.38", "3.2.39", "3.2.4", + "3.2.4.2", "3.2.40", "3.2.41", "3.2.42", @@ -3148,6 +3179,7 @@ var wordFenceVersions = []string{ "3.24.5", "3.25", "3.25.1", + "3.25.7", "3.26.2", "3.27.0", "3.27.8", @@ -3231,6 +3263,7 @@ var wordFenceVersions = []string{ "3.37.15", "3.37.18", "3.38", + "3.39.4", "3.4", "3.4.0", "3.4.06", @@ -3460,6 +3493,7 @@ var wordFenceVersions = []string{ "3.8.29", "3.8.3", "3.8.3.2", + "3.8.3.3", "3.8.3.4", "3.8.30", "3.8.31", @@ -3612,6 +3646,7 @@ var wordFenceVersions = []string{ "4.1.13", "4.1.14", "4.1.15", + "4.1.16", "4.1.17", "4.1.18", "4.1.19", @@ -3710,11 +3745,13 @@ var wordFenceVersions = []string{ "4.15.1", "4.15.12", "4.15.17", + "4.15.18", "4.15.2", "4.15.23", "4.15.3", "4.15.4", "4.15.5", + "4.15.7", "4.15.8", "4.16", "4.16.1", @@ -4278,6 +4315,7 @@ var wordFenceVersions = []string{ "5.3.15", "5.3.16", "5.3.17", + "5.3.18", "5.3.2", "5.3.2.0", "5.3.3", @@ -4363,6 +4401,7 @@ var wordFenceVersions = []string{ "5.6.7", "5.6.8", "5.6.9", + "5.61.0", "5.7", "5.7.0", "5.7.0.1", @@ -4439,6 +4478,7 @@ var wordFenceVersions = []string{ "5.9.27", "5.9.3", "5.9.3.2", + "5.9.3.6", "5.9.4", "5.9.5", "5.9.6", @@ -4517,6 +4557,7 @@ var wordFenceVersions = []string{ "6.15.13.1", "6.15.20", "6.15.21", + "6.16.1.2", "6.17.4", "6.18", "6.2", @@ -4588,6 +4629,7 @@ var wordFenceVersions = []string{ "6.4.6", "6.4.6.0", "6.4.6.1", + "6.4.6.2", "6.4.6.4", "6.4.7", "6.4.7.1", @@ -4596,6 +4638,8 @@ var wordFenceVersions = []string{ "6.4.9", "6.4.9.4", "6.4.9.6", + "6.43.2", + "6.44", "6.45", "6.46", "6.5", @@ -4672,6 +4716,7 @@ var wordFenceVersions = []string{ "6.9.8", "6.9.9", "6.930", + "67.1.0", "7.0", "7.0.0", "7.0.06", @@ -4741,6 +4786,7 @@ var wordFenceVersions = []string{ "7.2.8", "7.2.9", "7.23", + "7.25", "7.26", "7.28", "7.3", @@ -4817,6 +4863,7 @@ var wordFenceVersions = []string{ "7.6.24", "7.6.3", "7.6.4", + "7.6.5", "7.6.6", "7.6.8", "7.62", @@ -4834,6 +4881,7 @@ var wordFenceVersions = []string{ "7.8.2", "7.8.3", "7.8.4", + "7.8.5", "7.8.6", "7.8.7", "7.8.9", @@ -4859,7 +4907,6 @@ var wordFenceVersions = []string{ "8.0.18", "8.0.2", "8.0.26", - "8.0.3", "8.0.3.1", "8.0.33", "8.0.4", @@ -5063,5 +5110,6 @@ var wordFenceVersions = []string{ "9.95.0.1", "9.96.0.1", "9.99.1.0", + "91.5.0", "p1.2.5", } diff --git a/interval.go b/interval.go index 36e77d2..865c135 100644 --- a/interval.go +++ b/interval.go @@ -1,200 +1,26 @@ package comver -// interval represents the intersection (logical AND) of two constraints. -type interval [2]*constraint - -const ( - ErrImpossibleInterval stringError = "impossible interval" -) - -// NewInterval creates a new interval representing the intersection (logical AND) of two constraints. -// -// If either c1 or c2 is nil, it represents a boundless range. -// If both c1 and c2 are nil, it matches all versions (wildcard). -func NewInterval(c1, c2 *constraint) (interval, error) { //nolint:cyclop - cmp := c1.compare(c2) - // ensure c1 is the lower than c2 - if cmp > 0 { - c1, c2 = c2, c1 - } - - switch { - case c1 == nil && c2 == nil: - return interval{}, nil - case c1 == nil: - return interval{c2}, nil - case c2 == nil: - return interval{c1}, nil - case cmp == 0: // exactly the same - return interval{c1}, nil - case c1.op.ceillingless() && c2.op.ceillingless(): - // same direction - return interval{c2}, nil - case c1.op.floorless() && c2.op.floorless(): - // same direction - return interval{c1}, nil - case c1.version.Compare(c2.version) == 0 && c1.Check(c1.version) && c2.Check(c2.version): - // same version & different directions & overlapping - return interval{c1, c2}, nil - case c1.Check(c2.version) && c2.Check(c1.version): - return interval{c1, c2}, nil - default: - // different directions & no overlap - return interval{}, ErrImpossibleInterval - } +// interval represents a constraint that is both floor bounded and ceiling bounded. +// It must be initialized via [And]. +// The zero value for interval is a constraint could never be satisfied. +type interval struct { + upper Endless + lower Endless } -// Check reports whether a [Version] satisfies the interval. +// Check reports whether a [Version] satisfies the constraint. func (i interval) Check(v Version) bool { - for _, c := range i { - if c != nil && !c.Check(v) { - return false - } - } - return true -} - -func (i interval) String() string { - switch { - case i[0] == nil && i[1] == nil: - return "*" - case i[0] == nil: - return i[1].String() - case i[1] == nil: - return i[0].String() - } - - if i.exactVersionOnly() { - return i[0].version.Short() - } - - cmp := i[0].compare(i[1]) - switch { - case cmp < 0: - return i[0].String() + " " + i[1].String() - case cmp > 0: - return i[1].String() + " " + i[0].String() - default: - return i[0].String() - } + return i.ceiling().Check(v) && i.floor().Check(v) } -func (i interval) wildcard() bool { - return i[0] == nil && i[1] == nil +func (i interval) ceiling() Endless { + return i.upper } -func (i interval) floorless() bool { - return i.floor() == nil -} - -func (i interval) floor() *constraint { //nolint:cyclop - if i.wildcard() { - return nil - } - - if i[0] != nil && i[1] == nil { - if i[0].lowerbounded() { - return i[0] - } - return nil - } - - if i[0] == nil && i[1] != nil { - if i[1].lowerbounded() { - return i[1] - } - return nil - } - - // both i[0] and i[1] are not nil - - if !i[0].lowerbounded() && !i[1].lowerbounded() { - return nil - } - - if i[0].lowerbounded() && i[1].lowerbounded() { - cmp := i[0].compare(i[1]) - switch { - case cmp < 0: - return i[0] - case cmp > 0: - return i[1] - default: - return i[0] - } - } - - // exactly one of them is lower bounded - if i[0].lowerbounded() { - return i[0] - } - return i[1] +func (i interval) floor() Endless { + return i.lower } -func (i interval) ceilingless() bool { - return i.ceiling() == nil -} - -func (i interval) ceiling() *constraint { //nolint:cyclop - if i.wildcard() { - return nil - } - - if i[0] != nil && i[1] == nil { - if i[0].upperbounded() { - return i[0] - } - return nil - } - - if i[0] == nil && i[1] != nil { - if i[1].upperbounded() { - return i[1] - } - return nil - } - - // both i[0] and i[1] are not nil - - if !i[0].upperbounded() && !i[1].upperbounded() { - return nil - } - - if i[0].upperbounded() && i[1].upperbounded() { - cmp := i[0].compare(i[1]) - switch { - case cmp < 0: - return i[1] - case cmp > 0: - return i[0] - default: - return i[0] - } - } - - // exactly one of them is upper bounded - if i[0].upperbounded() { - return i[0] - } - return i[1] -} - -func (i interval) exactVersionOnly() bool { - if i[0] == nil || i[1] == nil { - return false - } - - if i[0].version.Compare(i[1].version) != 0 { - return false - } - - return (i[0].lowerbounded() && i[1].upperbounded()) || (i[0].upperbounded() && i[1].lowerbounded()) -} - -func (i interval) compare(j interval) int { - cmp := i.floor().compare(j.floor()) - if cmp != 0 { - return cmp - } - return j.ceiling().compare(j.ceiling()) +func (i interval) String() string { + return i.floor().String() + " " + i.ceiling().String() } diff --git a/interval_example_test.go b/interval_example_test.go deleted file mode 100644 index ff59c58..0000000 --- a/interval_example_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package comver_test - -import ( - "fmt" - - "github.com/typisttech/comver" -) - -func ExampleNewInterval() { - v1, _ := comver.NewVersion("1") - v2, _ := comver.NewVersion("2") - - c1 := comver.NewGreaterThanConstraint(v1) - c2 := comver.NewLessThanConstraint(v2) - - i, _ := comver.NewInterval(c1, c2) - - fmt.Println(i) - // Output: >1 <2 -} - -func ExampleNewInterval_error() { - v1, _ := comver.NewVersion("1") - v2, _ := comver.NewVersion("2") - - c1 := comver.NewLessThanConstraint(v1) - c2 := comver.NewGreaterThanConstraint(v2) - - _, err := comver.NewInterval(c1, c2) - - fmt.Println(err) - // Output: impossible interval -} - -func ExampleNewInterval_ceillingless() { - v1, _ := comver.NewVersion("1") - - c1 := comver.NewGreaterThanOrEqualToConstraint(v1) - - i, _ := comver.NewInterval(c1, nil) - - fmt.Println(i) - // Output: >=1 -} - -func ExampleNewInterval_floorless() { - v1, _ := comver.NewVersion("1") - - c1 := comver.NewLessThanOrEqualToConstraint(v1) - - i, _ := comver.NewInterval(c1, nil) - - fmt.Println(i) - // Output: <=1 -} - -func ExampleNewInterval_exactVersion() { - v1, _ := comver.NewVersion("1") - - c1 := comver.NewLessThanOrEqualToConstraint(v1) - c2 := comver.NewGreaterThanOrEqualToConstraint(v1) - - i, _ := comver.NewInterval(c1, c2) - - fmt.Println(i) - // Output: 1 -} - -func ExampleNewInterval_wildcard() { - i, _ := comver.NewInterval(nil, nil) - - fmt.Println(i) - // Output: * -} - -func ExampleNewInterval_compact() { - v1, _ := comver.NewVersion("1") - v2, _ := comver.NewVersion("2") - - c1 := comver.NewLessThanConstraint(v1) - c2 := comver.NewLessThanConstraint(v2) - - i, _ := comver.NewInterval(c1, c2) - - fmt.Println(i) - // Output: <1 -} - -func ExampleNewInterval_compactWildcard() { - v, _ := comver.NewVersion("1") - - c1 := comver.NewLessThanConstraint(v) - c2 := comver.NewGreaterThanOrEqualToConstraint(v) - - i, _ := comver.NewInterval(c1, c2) - - fmt.Println(i) - // Output: * -} diff --git a/interval_test.go b/interval_test.go deleted file mode 100644 index 5872fa7..0000000 --- a/interval_test.go +++ /dev/null @@ -1,581 +0,0 @@ -package comver - -import ( - "fmt" - "reflect" - "strconv" - "testing" -) - -func newIntervalTestCases() []struct { - c1 *constraint - c2 *constraint - want interval - wantErr bool -} { - return []struct { - c1 *constraint - c2 *constraint - want interval - wantErr bool - }{ - { - c1: nil, - c2: nil, - want: interval{}, - wantErr: false, - }, - // With single nil. - { - c1: &constraint{Version{major: 9}, lessThanOrEqualTo}, - c2: nil, - want: interval{&constraint{Version{major: 9}, lessThanOrEqualTo}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 9}, lessThan}, - c2: nil, - want: interval{&constraint{Version{major: 9}, lessThan}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 9}, greaterThan}, - c2: nil, - want: interval{&constraint{Version{major: 9}, greaterThan}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 9}, greaterThanOrEqualTo}, - c2: nil, - want: interval{&constraint{Version{major: 9}, greaterThanOrEqualTo}}, - wantErr: false, - }, - - // Same constraint. - { - c1: &constraint{Version{major: 8}, lessThanOrEqualTo}, - c2: &constraint{Version{major: 8}, lessThanOrEqualTo}, - want: interval{&constraint{Version{major: 8}, lessThanOrEqualTo}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 8}, lessThan}, - c2: &constraint{Version{major: 8}, lessThan}, - want: interval{&constraint{Version{major: 8}, lessThan}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 8}, greaterThan}, - c2: &constraint{Version{major: 8}, greaterThan}, - want: interval{&constraint{Version{major: 8}, greaterThan}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 8}, greaterThanOrEqualTo}, - c2: &constraint{Version{major: 8}, greaterThanOrEqualTo}, - want: interval{&constraint{Version{major: 8}, greaterThanOrEqualTo}}, - wantErr: false, - }, - - // Same direction. Different versions. Same op. - { - c1: &constraint{Version{major: 7}, lessThanOrEqualTo}, - c2: &constraint{Version{major: 6}, lessThanOrEqualTo}, - want: interval{&constraint{Version{major: 6}, lessThanOrEqualTo}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 7}, lessThan}, - c2: &constraint{Version{major: 6}, lessThan}, - want: interval{&constraint{Version{major: 6}, lessThan}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 7}, greaterThan}, - c2: &constraint{Version{major: 6}, greaterThan}, - want: interval{&constraint{Version{major: 7}, greaterThan}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 7}, greaterThanOrEqualTo}, - c2: &constraint{Version{major: 6}, greaterThanOrEqualTo}, - want: interval{&constraint{Version{major: 7}, greaterThanOrEqualTo}}, - wantErr: false, - }, - - // Same direction. Same version. Different ops. - { - c1: &constraint{Version{major: 5}, lessThanOrEqualTo}, - c2: &constraint{Version{major: 5}, lessThan}, - want: interval{&constraint{Version{major: 5}, lessThan}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 5}, greaterThan}, - c2: &constraint{Version{major: 5}, greaterThanOrEqualTo}, - want: interval{&constraint{Version{major: 5}, greaterThan}}, - wantErr: false, - }, - - // Different directions. Same version. Different ops. - { - c1: &constraint{Version{major: 5}, lessThanOrEqualTo}, - c2: &constraint{Version{major: 5}, greaterThan}, - want: interval{}, - wantErr: true, - }, - { - c1: &constraint{Version{major: 5}, lessThan}, - c2: &constraint{Version{major: 5}, greaterThan}, - want: interval{}, - wantErr: true, - }, - { - c1: &constraint{Version{major: 5}, lessThan}, - c2: &constraint{Version{major: 5}, greaterThanOrEqualTo}, - want: interval{}, - wantErr: true, - }, - { - c1: &constraint{Version{major: 5}, lessThanOrEqualTo}, - c2: &constraint{Version{major: 5}, greaterThanOrEqualTo}, - want: interval{&constraint{Version{major: 5}, greaterThanOrEqualTo}, &constraint{Version{major: 5}, lessThanOrEqualTo}}, - wantErr: false, - }, - - // Different directions. Different versions. Different ops. - { - c1: &constraint{Version{major: 4}, lessThanOrEqualTo}, - c2: &constraint{Version{major: 3}, greaterThan}, - want: interval{&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 4}, lessThanOrEqualTo}, - c2: &constraint{Version{major: 3}, greaterThanOrEqualTo}, - want: interval{&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 4}, lessThan}, - c2: &constraint{Version{major: 3}, greaterThan}, - want: interval{&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 4}, lessThan}, - c2: &constraint{Version{major: 3}, greaterThanOrEqualTo}, - want: interval{&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - wantErr: false, - }, - { - c1: &constraint{Version{major: 1}, lessThanOrEqualTo}, - c2: &constraint{Version{major: 2}, greaterThan}, - want: interval{}, - wantErr: true, - }, - { - c1: &constraint{Version{major: 1}, lessThanOrEqualTo}, - c2: &constraint{Version{major: 2}, greaterThanOrEqualTo}, - want: interval{}, - wantErr: true, - }, - { - c1: &constraint{Version{major: 1}, lessThan}, - c2: &constraint{Version{major: 2}, greaterThan}, - want: interval{}, - wantErr: true, - }, - { - c1: &constraint{Version{major: 1}, lessThan}, - c2: &constraint{Version{major: 2}, greaterThanOrEqualTo}, - want: interval{}, - wantErr: true, - }, - } -} - -func TestNewInterval(t *testing.T) { - t.Parallel() - - for _, tt := range newIntervalTestCases() { - t.Run(fmt.Sprintf("%s_%s_reversed", tt.c1, tt.c2), func(t *testing.T) { - t.Parallel() - - got, err := NewInterval(tt.c1, tt.c2) - - if (err != nil) != tt.wantErr { - t.Errorf("NewInterval(%q, %q) error = %v, wantErr %v", tt.c1, tt.c2, err, tt.wantErr) - return - } - - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewInterval(%q, %q) = %v, want %v", tt.c1, tt.c2, got, tt.want) - } - }) - } -} - -func TestNewInterval_reversed(t *testing.T) { - t.Parallel() - - for _, tt := range newIntervalTestCases() { - t.Run(fmt.Sprintf("%s_%s_reversed", tt.c2, tt.c1), func(t *testing.T) { - t.Parallel() - - got, err := NewInterval(tt.c2, tt.c1) - - if (err != nil) != tt.wantErr { - t.Fatalf("NewInterval(%q, %q) error = %v, wantErr %v", tt.c2, tt.c1, err, tt.wantErr) - return - } - - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewInterval(%q, %q) = %v, want %v", tt.c2, tt.c1, got, tt.want) - } - }) - } -} - -func Test_interval_Check(t *testing.T) { //nolint:maintidx - t.Parallel() - - tests := []struct { - i interval - v Version - want bool - }{ - { - i: interval{}, - v: Version{major: 9}, - want: true, - }, - { - i: interval{&constraint{Version{major: 9}, lessThanOrEqualTo}}, - v: Version{major: 10}, - want: false, - }, - { - i: interval{&constraint{Version{major: 9}, lessThanOrEqualTo}}, - v: Version{major: 9}, - want: true, - }, - { - i: interval{&constraint{Version{major: 9}, lessThanOrEqualTo}}, - v: Version{major: 8}, - want: true, - }, - { - i: interval{&constraint{Version{major: 9}, lessThan}}, - v: Version{major: 10}, - want: false, - }, - { - i: interval{&constraint{Version{major: 9}, lessThan}}, - v: Version{major: 9}, - want: false, - }, - { - i: interval{&constraint{Version{major: 9}, lessThan}}, - v: Version{major: 8}, - want: true, - }, - { - i: interval{&constraint{Version{major: 9}, greaterThan}}, - v: Version{major: 10}, - want: true, - }, - { - i: interval{&constraint{Version{major: 9}, greaterThan}}, - v: Version{major: 9}, - want: false, - }, - { - i: interval{&constraint{Version{major: 9}, greaterThan}}, - v: Version{major: 8}, - want: false, - }, - { - i: interval{&constraint{Version{major: 9}, greaterThanOrEqualTo}}, - v: Version{major: 10}, - want: true, - }, - { - i: interval{&constraint{Version{major: 9}, greaterThanOrEqualTo}}, - v: Version{major: 9}, - want: true, - }, - { - i: interval{&constraint{Version{major: 9}, greaterThanOrEqualTo}}, - v: Version{major: 8}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 9}, greaterThanOrEqualTo}, - &constraint{Version{major: 9}, lessThanOrEqualTo}, - }, - v: Version{major: 10}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 9}, greaterThanOrEqualTo}, - &constraint{Version{major: 9}, lessThanOrEqualTo}, - }, - v: Version{major: 9}, - want: true, - }, - { - i: interval{ - &constraint{Version{major: 9}, greaterThanOrEqualTo}, - &constraint{Version{major: 9}, lessThanOrEqualTo}, - }, - v: Version{major: 8}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThanOrEqualTo}, - &constraint{Version{major: 6}, lessThanOrEqualTo}, - }, - v: Version{major: 7}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThanOrEqualTo}, - &constraint{Version{major: 6}, lessThanOrEqualTo}, - }, - v: Version{major: 6}, - want: true, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThanOrEqualTo}, - &constraint{Version{major: 6}, lessThanOrEqualTo}, - }, - v: Version{major: 5}, - want: true, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThanOrEqualTo}, - &constraint{Version{major: 6}, lessThanOrEqualTo}, - }, - v: Version{major: 4}, - want: true, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThanOrEqualTo}, - &constraint{Version{major: 6}, lessThanOrEqualTo}, - }, - v: Version{major: 3}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThanOrEqualTo}, - &constraint{Version{major: 6}, lessThan}, - }, - v: Version{major: 7}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThanOrEqualTo}, - &constraint{Version{major: 6}, lessThan}, - }, - v: Version{major: 6}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThanOrEqualTo}, - &constraint{Version{major: 6}, lessThan}, - }, - v: Version{major: 5}, - want: true, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThanOrEqualTo}, - &constraint{Version{major: 6}, lessThan}, - }, - v: Version{major: 4}, - want: true, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThanOrEqualTo}, - &constraint{Version{major: 6}, lessThan}, - }, - v: Version{major: 3}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThan}, - &constraint{Version{major: 6}, lessThanOrEqualTo}, - }, - v: Version{major: 7}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThan}, - &constraint{Version{major: 6}, lessThanOrEqualTo}, - }, - v: Version{major: 6}, - want: true, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThan}, - &constraint{Version{major: 6}, lessThanOrEqualTo}, - }, - v: Version{major: 5}, - want: true, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThan}, - &constraint{Version{major: 6}, lessThanOrEqualTo}, - }, - v: Version{major: 4}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThan}, - &constraint{Version{major: 6}, lessThanOrEqualTo}, - }, - v: Version{major: 3}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThan}, - &constraint{Version{major: 6}, lessThan}, - }, - v: Version{major: 7}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThan}, - &constraint{Version{major: 6}, lessThan}, - }, - v: Version{major: 6}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThan}, - &constraint{Version{major: 6}, lessThan}, - }, - v: Version{major: 5}, - want: true, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThan}, - &constraint{Version{major: 6}, lessThan}, - }, - v: Version{major: 4}, - want: false, - }, - { - i: interval{ - &constraint{Version{major: 4}, greaterThan}, - &constraint{Version{major: 6}, lessThan}, - }, - v: Version{major: 3}, - want: false, - }, - } - for i, tt := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - t.Parallel() - - if got := tt.i.Check(tt.v); got != tt.want { - t.Errorf("%q.Check(%q) = %v, want %v", tt.i, tt.v.Short(), got, tt.want) - } - }) - } -} - -func Test_interval_String(t *testing.T) { - t.Parallel() - - tests := []struct { - i interval - want string - }{ - { - i: interval{}, - want: "*", - }, - { - i: interval{&constraint{Version{major: 9}, lessThanOrEqualTo}}, - want: "<=9", - }, - { - i: interval{&constraint{Version{major: 9}, lessThan}}, - want: "<9", - }, - { - i: interval{&constraint{Version{major: 9}, greaterThan}}, - want: ">9", - }, - { - i: interval{&constraint{Version{major: 9}, greaterThanOrEqualTo}}, - want: ">=9", - }, - { - i: interval{ - &constraint{Version{major: 9}, greaterThanOrEqualTo}, - &constraint{Version{major: 9}, lessThanOrEqualTo}, - }, - want: "9", - }, - { - i: interval{ - &constraint{Version{major: 7}, greaterThanOrEqualTo}, - &constraint{Version{major: 8}, lessThanOrEqualTo}, - }, - want: ">=7 <=8", - }, - { - i: interval{ - &constraint{Version{major: 7}, greaterThanOrEqualTo}, - &constraint{Version{major: 8}, lessThan}, - }, - want: ">=7 <8", - }, - { - i: interval{ - &constraint{Version{major: 7}, greaterThan}, - &constraint{Version{major: 8}, lessThanOrEqualTo}, - }, - want: ">7 <=8", - }, - { - i: interval{ - &constraint{Version{major: 7}, greaterThan}, - &constraint{Version{major: 8}, lessThan}, - }, - want: ">7 <8", - }, - } - for _, tt := range tests { - t.Run(tt.want, func(t *testing.T) { - t.Parallel() - - if got := tt.i.String(); got != tt.want { - t.Errorf("String() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/intervals.go b/intervals.go deleted file mode 100644 index c9a074c..0000000 --- a/intervals.go +++ /dev/null @@ -1,138 +0,0 @@ -package comver - -import ( - "slices" -) - -// Intervals represent the union (logical OR) of multiple intervals. -type Intervals []interval - -func (is Intervals) String() string { - s := "" - for _, i := range is { - if len(s) > 0 { - s += " || " - } - s += i.String() - } - return s -} - -// Compact returns a new [Intervals] covering the same version ranges but with the smallest number of intervals. -func Compact(is Intervals) Intervals { - if len(is) == 1 { - return is - } - if len(is) == 0 { - return Intervals{} - } - - is = slices.Clone(is) - - // if there is a wildcard, return only the wildcard - wIndex := slices.IndexFunc(is, func(i interval) bool { - return i.wildcard() - }) - if wIndex >= 0 { - return Intervals{is[wIndex]} - } - - head := lowestCeilingless(is) - tail := highestFloorless(is) - - is = slices.DeleteFunc(is, func(i interval) bool { - return i.floorless() || i.ceilingless() - }) - - is = slices.Clip(slices.Concat(head, is, tail)) - - // sort the intervals - slices.SortFunc(is, func(a, b interval) int { - return a.compare(b) - }) - // remove duplicates - is = slices.CompactFunc(is, func(a, b interval) bool { - return a.compare(b) == 0 - }) - - vals := make(Intervals, 0, len(is)) - pendingI := is[0] - for index := range is { - i, ok := compactTwo(pendingI, is[index]) - if ok { - pendingI = i - } else { - vals = append(vals, pendingI) - pendingI = is[index] - } - - if index == len(is)-1 { - vals = append(vals, pendingI) - } - } - - return slices.Clip(vals) -} - -func compactTwo(a, b interval) (interval, bool) { - cmp := a.compare(b) - if cmp > 0 { - a, b = b, a - } - - if a.compare(b) == 0 { - return a, true - } - - overlap := a.Check(b.floor().version) || - (a.ceiling().version.Compare(b.floor().version) == 0 && - (a.ceiling().op == lessThanOrEqualTo || b.floor().op == greaterThanOrEqualTo)) - - if !overlap { - return a, false - } - - ccmp := a.ceiling().compare(b.ceiling()) - if ccmp > 0 { - return a, true - } - - i, err := NewInterval(a.floor(), b.ceiling()) - if err != nil { - // this should not happen - return a, false - } - return i, true -} - -func lowestCeilingless(is Intervals) Intervals { - ceilingless := slices.DeleteFunc(slices.Clone(is), func(i interval) bool { - return !i.ceilingless() - }) - - if len(ceilingless) == 0 { - return Intervals{} - } - - i := slices.MinFunc(ceilingless, func(a, b interval) int { - return a.floor().compare(b.floor()) - }) - - return Intervals{i} -} - -func highestFloorless(is Intervals) Intervals { - floorlesses := slices.DeleteFunc(slices.Clone(is), func(i interval) bool { - return !i.floorless() - }) - - if len(floorlesses) == 0 { - return Intervals{} - } - - i := slices.MaxFunc(floorlesses, func(a, b interval) int { - return a.ceiling().compare(b.ceiling()) - }) - - return Intervals{i} -} diff --git a/intervals_example_test.go b/intervals_example_test.go deleted file mode 100644 index 6182699..0000000 --- a/intervals_example_test.go +++ /dev/null @@ -1,149 +0,0 @@ -package comver_test - -import ( - "fmt" - - "github.com/typisttech/comver" -) - -func buildIntervals(ks ...string) comver.Intervals { - v1, _ := comver.NewVersion("1") - v2, _ := comver.NewVersion("2") - v3, _ := comver.NewVersion("3") - v4, _ := comver.NewVersion("4") - v5, _ := comver.NewVersion("5") - v6, _ := comver.NewVersion("6") - v7, _ := comver.NewVersion("7") - v8, _ := comver.NewVersion("8") - v9, _ := comver.NewVersion("9") - v10, _ := comver.NewVersion("10") - - g1 := comver.NewGreaterThanConstraint(v1) - g2 := comver.NewGreaterThanConstraint(v2) - g3 := comver.NewGreaterThanConstraint(v3) - g4 := comver.NewGreaterThanConstraint(v4) - g7 := comver.NewGreaterThanConstraint(v7) - g8 := comver.NewGreaterThanConstraint(v8) - g10 := comver.NewGreaterThanConstraint(v10) - - gt4 := comver.NewGreaterThanOrEqualToConstraint(v4) - - l2 := comver.NewLessThanConstraint(v2) - l3 := comver.NewLessThanConstraint(v3) - l4 := comver.NewLessThanConstraint(v4) - l1 := comver.NewLessThanConstraint(v1) - l5 := comver.NewLessThanConstraint(v5) - l6 := comver.NewLessThanConstraint(v6) - l9 := comver.NewLessThanConstraint(v9) - - g1l2, _ := comver.NewInterval(g1, l2) - g1l3, _ := comver.NewInterval(g1, l3) - g2l4, _ := comver.NewInterval(g2, l4) - g3l4, _ := comver.NewInterval(g3, l4) - g4l5, _ := comver.NewInterval(g4, l5) - g8l9, _ := comver.NewInterval(g8, l9) - gt4l6, _ := comver.NewInterval(gt4, l6) - - ig1, _ := comver.NewInterval(g1, nil) - ig10, _ := comver.NewInterval(g10, nil) - ig7, _ := comver.NewInterval(g7, nil) - - il1, _ := comver.NewInterval(l1, nil) - il3, _ := comver.NewInterval(l3, nil) - il4, _ := comver.NewInterval(l4, nil) - - wildcard, _ := comver.NewInterval(nil, nil) - - m := map[string]comver.Intervals{ - "*": {wildcard}, - "<1": {il1}, - "<3": {il3}, - "<4": {il4}, - ">=5 <6": {gt4l6}, - ">1 <2": {g1l2}, - ">1 <3": {g1l3}, - ">1": {ig1}, - ">10": {ig10}, - ">2 <4": {g2l4}, - ">3 <4": {g3l4}, - ">4 <5": {g4l5}, - ">7": {ig7}, - ">8 <9": {g8l9}, - } - - is := make(comver.Intervals, 0, len(ks)) - for _, k := range ks { - is = append(is, m[k]...) - } - - return is -} - -func ExampleCompact() { - is := buildIntervals(">1 <3", ">2 <4") - fmt.Println("Original:", is) - - is = comver.Compact(is) - fmt.Println("Compacted:", is) - - // Output: - // Original: >1 <3 || >2 <4 - // Compacted: >1 <4 -} - -func ExampleCompact_wildcard() { - is := buildIntervals(">1", "<4") - fmt.Println("Original:", is) - - is = comver.Compact(is) - fmt.Println("Compacted:", is) - - // Output: - // Original: >1 || <4 - // Compacted: * -} - -func ExampleCompact_wildcard2() { - is := buildIntervals(">1 <3", "*") - fmt.Println("Original:", is) - - is = comver.Compact(is) - fmt.Println("Compacted:", is) - - // Output: - // Original: >1 <3 || * - // Compacted: * -} - -func ExampleCompact_unrelated() { - is := buildIntervals(">1 <2", ">3 <4") - fmt.Println("Original:", is) - - is = comver.Compact(is) - fmt.Println("Compacted:", is) - - // Output: - // Original: >1 <2 || >3 <4 - // Compacted: >1 <2 || >3 <4 -} - -func ExampleCompact_mega() { - is := buildIntervals( - "<1", - ">1 <2", - "<3", - ">4 <5", - ">=5 <6", - ">7", - ">8 <9", - ">10", - ) - fmt.Println("Original:", is) - - is = comver.Compact(is) - fmt.Println("Compacted:", is) - - // Output: - // Original: <1 || >1 <2 || <3 || >4 <5 || >=4 <6 || >7 || >8 <9 || >10 - // Compacted: <3 || >=4 <6 || >7 -} diff --git a/intervals_test.go b/intervals_test.go deleted file mode 100644 index f49c3b5..0000000 --- a/intervals_test.go +++ /dev/null @@ -1,1734 +0,0 @@ -package comver - -import ( - "math/rand/v2" - "reflect" - "slices" - "testing" -) - -func compactTestCases() []struct { //nolint:maintidx - name string - is Intervals - want Intervals -} { - return []struct { - name string - is Intervals - want Intervals - }{ - { - name: "nil", - is: nil, - want: Intervals{}, - }, - { - name: "empty", - is: Intervals{}, - want: Intervals{}, - }, - { - name: "single_wildcard", - is: Intervals{{}}, - want: Intervals{{}}, - }, - { - name: "single_boundless", - is: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}}, - want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}}, - }, - { - name: "single_bounded", - is: Intervals{ - {&constraint{Version{major: 7}, greaterThanOrEqualTo}, &constraint{Version{major: 8}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 7}, greaterThanOrEqualTo}, &constraint{Version{major: 8}, lessThanOrEqualTo}}, - }, - }, - { - name: "wildcard_with_<=", - is: Intervals{{}, {&constraint{Version{major: 9}, lessThanOrEqualTo}}}, - want: Intervals{{}}, - }, - { - name: "wildcard_with_<", - is: Intervals{{}, {&constraint{Version{major: 9}, lessThan}}}, - want: Intervals{{}}, - }, - { - name: "wildcard_with_=", - is: Intervals{ - {}, - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - }, - want: Intervals{{}}, - }, - { - name: "wildcard_with_>", - is: Intervals{{}, {&constraint{Version{major: 9}, greaterThan}}}, - want: Intervals{{}}, - }, - { - name: "wildcard_with_>=", - is: Intervals{{}, {&constraint{Version{major: 9}, greaterThanOrEqualTo}}}, - want: Intervals{{}}, - }, - { - name: "wildcard_with_single_bounded", - is: Intervals{ - {}, - {&constraint{Version{major: 7}, greaterThanOrEqualTo}, &constraint{Version{major: 8}, lessThanOrEqualTo}}, - }, - want: Intervals{{}}, - }, - { - name: "wildcard_with_wildcard", - is: Intervals{{}, {}}, - want: Intervals{{}}, - }, - { - name: "<=_<=_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}}, - }, - { - name: "<=_<=_different_versions", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 10}, lessThanOrEqualTo}}}, - }, - { - name: "<=_<_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 9}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}}, - }, - { - name: "<=_<_lower_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 8}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}}, - }, - { - name: "<=_<_higher_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 10}, lessThan}}}, - }, - { - name: "<=_=_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}}, - }, - { - name: "<=_=_lower_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 8}, greaterThanOrEqualTo}, &constraint{Version{major: 8}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}}, - }, - { - name: "<=_=_higher_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}, &constraint{Version{major: 10}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}, &constraint{Version{major: 10}, lessThanOrEqualTo}}, - }, - }, - - { - name: "<=_>_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 9}, greaterThan}}, - }, - want: Intervals{{}}, - }, - { - name: "<=_>_lower_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 8}, greaterThan}}, - }, - want: Intervals{{}}, - }, - { - name: "<=_>_higher_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThan}}, - }, - }, - - { - name: "<=_>=_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 9}, greaterThanOrEqualTo}}, - }, - want: Intervals{{}}, - }, - { - name: "<=_>=_lower_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 8}, greaterThanOrEqualTo}}, - }, - want: Intervals{{}}, - }, - { - name: "<=_>=_higher_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}}, - }, - }, - - { - name: "<_<_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 9}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 9}, lessThan}}}, - }, - { - name: "<_<_different_versions", - is: Intervals{{&constraint{Version{major: 9}, lessThan}}, {&constraint{Version{major: 10}, lessThan}}}, - want: Intervals{{&constraint{Version{major: 10}, lessThan}}}, - }, - - { - name: "<_=_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 9}, lessThanOrEqualTo}}}, - }, - { - name: "<_=_lower_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 8}, greaterThanOrEqualTo}, &constraint{Version{major: 8}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 9}, lessThan}}}, - }, - { - name: "<_=_higher_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}, &constraint{Version{major: 10}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}, &constraint{Version{major: 10}, lessThanOrEqualTo}}, - }, - }, - - { - name: "<_>_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 9}, greaterThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 9}, greaterThan}}, - }, - }, - { - name: "<_>_lower_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 8}, greaterThan}}, - }, - want: Intervals{{}}, - }, - { - name: "<_>_higher_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 10}, greaterThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 10}, greaterThan}}, - }, - }, - - { - name: "<_>=_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 9}, greaterThanOrEqualTo}}, - }, - want: Intervals{{}}, - }, - { - name: "<_>=_lower_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 8}, greaterThanOrEqualTo}}, - }, - want: Intervals{{}}, - }, - { - name: "<_>=_higher_version", - is: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 9}, lessThan}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}}, - }, - }, - - { - name: "=_=_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - }, - }, - { - name: "=_=_different_versions", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}, &constraint{Version{major: 10}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}, &constraint{Version{major: 10}, lessThanOrEqualTo}}, - }, - }, - - { - name: "=_>_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 9}, greaterThan}}, - }, - want: Intervals{{&constraint{Version{major: 9}, greaterThanOrEqualTo}}}, - }, - { - name: "=_>_lower_version", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 8}, greaterThan}}, - }, - want: Intervals{{&constraint{Version{major: 8}, greaterThan}}}, - }, - { - name: "=_>_higher_version", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThan}}, - }, - }, - - { - name: "=_>=_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 9}, greaterThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 9}, greaterThanOrEqualTo}}}, - }, - { - name: "=_>=_lower_version", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 8}, greaterThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 8}, greaterThanOrEqualTo}}}, - }, - { - name: "=_>=_higher_version", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}, &constraint{Version{major: 9}, lessThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}}, - }, - }, - - { - name: ">_>_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThan}}, - {&constraint{Version{major: 9}, greaterThan}}, - }, - want: Intervals{{&constraint{Version{major: 9}, greaterThan}}}, - }, - { - name: ">_>_different_versions", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThan}}, - {&constraint{Version{major: 10}, greaterThan}}, - }, - want: Intervals{{&constraint{Version{major: 9}, greaterThan}}}, - }, - - { - name: ">_>=_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThan}}, - {&constraint{Version{major: 9}, greaterThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 9}, greaterThanOrEqualTo}}}, - }, - { - name: ">_>=_lower_version", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThan}}, - {&constraint{Version{major: 8}, greaterThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 8}, greaterThanOrEqualTo}}}, - }, - { - name: ">_>=_higher_version", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThan}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 9}, greaterThan}}, - }, - }, - - { - name: ">=_>=_same_version", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 9}, greaterThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 9}, greaterThanOrEqualTo}}}, - }, - { - name: ">=_>=_different_versions", - is: Intervals{ - {&constraint{Version{major: 9}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 10}, greaterThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 9}, greaterThanOrEqualTo}}}, - }, - - { - name: "<=_>=_gap_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<=_>=_gap_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: "<=_>=_gap_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<=_>=_gap_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<=_>_gap_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<=_>_gap_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: "<=_>_gap_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<=_>_gap_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<_>=_gap_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<_>=_gap_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: "<_>=_gap_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<_>=_gap_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<_>_gap_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<_>_gap_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: "<_>_gap_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<_>_gap_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<=_>=_seamless_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<=_>=_seamless_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: "<=_>=_seamless_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<=_>=_seamless_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<=_>_seamless_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<=_>_seamless_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: "<=_>_seamless_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<=_>_seamless_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<_>=_seamless_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<_>=_seamless_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: "<_>=_seamless_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<_>=_seamless_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<_>_seamless_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<_>_seamless_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: "<_>_seamless_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<_>_seamless_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 2}, lessThan}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<=_>=_overlap_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<=_>=_overlap_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: "<=_>=_overlap_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<=_>=_overlap_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<=_>_overlap_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThan}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<=_>_overlap_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThan}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: "<=_>_overlap_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThan}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<=_>_overlap_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 3}, lessThan}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<_>=_overlap_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<_>=_overlap_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: "<_>=_overlap_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<_>=_overlap_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThanOrEqualTo}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<_>_overlap_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThan}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<_>_overlap_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThan}}, - {&constraint{Version{major: 2}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: "<_>_overlap_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThan}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: "<_>_overlap_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 3}, lessThan}}, - {&constraint{Version{major: 2}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<=_gap_<=_>=", - is: Intervals{ - {&constraint{Version{major: 5}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - {&constraint{Version{major: 5}, greaterThanOrEqualTo}}, - }, - }, - { - name: "<=_gap_<=_>", - is: Intervals{ - {&constraint{Version{major: 5}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - {&constraint{Version{major: 5}, greaterThanOrEqualTo}}, - }, - }, - { - name: "<=_gap_<_>=", - is: Intervals{ - {&constraint{Version{major: 5}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - {&constraint{Version{major: 5}, greaterThanOrEqualTo}}, - }, - }, - { - name: "<=_gap_<_>", - is: Intervals{ - {&constraint{Version{major: 5}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - {&constraint{Version{major: 5}, greaterThanOrEqualTo}}, - }, - }, - - { - name: "<_gap_<=_>=", - is: Intervals{ - {&constraint{Version{major: 5}, greaterThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - {&constraint{Version{major: 5}, greaterThan}}, - }, - }, - { - name: "<_gap_<=_>", - is: Intervals{ - {&constraint{Version{major: 5}, greaterThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - {&constraint{Version{major: 5}, greaterThan}}, - }, - }, - { - name: "<_gap_<_>=", - is: Intervals{ - {&constraint{Version{major: 5}, greaterThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - {&constraint{Version{major: 5}, greaterThan}}, - }, - }, - { - name: "<_gap_<_>", - is: Intervals{ - {&constraint{Version{major: 5}, greaterThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - {&constraint{Version{major: 5}, greaterThan}}, - }, - }, - - { - name: ">=_gap_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: ">=_gap_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: ">=_gap_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: ">=_gap_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: ">_gap_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: ">_gap_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - { - name: ">_gap_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: ">_gap_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<=_cover_<=_>=", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 2}, greaterThanOrEqualTo}}}, - }, - { - name: "<=_cover_<=_>", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 2}, greaterThanOrEqualTo}}}, - }, - { - name: "<=_cover_<_>=", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 2}, greaterThanOrEqualTo}}}, - }, - { - name: "<=_cover_<_>", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 2}, greaterThanOrEqualTo}}}, - }, - - { - name: "<_cover_<=_>=", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 2}, greaterThan}}}, - }, - { - name: "<_cover_<=_>", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 2}, greaterThan}}}, - }, - { - name: "<_cover_<_>=", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 2}, greaterThan}}}, - }, - { - name: "<_cover_<_>", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 2}, greaterThan}}}, - }, - - { - name: ">=_cover_<=_>=", - is: Intervals{ - {&constraint{Version{major: 5}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 5}, lessThanOrEqualTo}}}, - }, - { - name: ">=_cover_<=_>", - is: Intervals{ - {&constraint{Version{major: 5}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 5}, lessThanOrEqualTo}}}, - }, - { - name: ">=_cover_<_>=", - is: Intervals{ - {&constraint{Version{major: 5}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 5}, lessThanOrEqualTo}}}, - }, - { - name: ">=_cover_<_>", - is: Intervals{ - {&constraint{Version{major: 5}, lessThanOrEqualTo}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 5}, lessThanOrEqualTo}}}, - }, - - { - name: ">_cover_<=_>=", - is: Intervals{ - {&constraint{Version{major: 5}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 5}, lessThan}}}, - }, - { - name: ">_cover_<=_>", - is: Intervals{ - {&constraint{Version{major: 5}, lessThan}}, - {&constraint{Version{major: 3}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 5}, lessThan}}}, - }, - { - name: ">_cover_<_>=", - is: Intervals{ - {&constraint{Version{major: 5}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 5}, lessThan}}}, - }, - { - name: ">_cover_<_>", - is: Intervals{ - {&constraint{Version{major: 5}, lessThan}}, - {&constraint{Version{major: 3}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 5}, lessThan}}}, - }, - - { - name: "<=_within_<=_>=", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - { - name: "<=_within_<=_>", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - { - name: "<=_within_<_>=", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThan}}}, - }, - { - name: "<=_within_<_>", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThan}}}, - }, - - { - name: "<_within_<=_>=", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThan}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - { - name: "<_within_<=_>", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThan}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - { - name: "<_within_<_>=", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThan}}}, - }, - { - name: "<_within_<_>", - is: Intervals{ - {&constraint{Version{major: 2}, greaterThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThan}}}, - }, - - { - name: ">=_within_<=_>=", - is: Intervals{ - {&constraint{Version{major: 3}, lessThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - { - name: ">=_within_<=_>", - is: Intervals{ - {&constraint{Version{major: 3}, lessThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThan}}}, - }, - { - name: ">=_within_<_>=", - is: Intervals{ - {&constraint{Version{major: 3}, lessThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - { - name: ">=_within_<_>", - is: Intervals{ - {&constraint{Version{major: 3}, lessThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThan}}}, - }, - - { - name: ">_within_<=_>=", - is: Intervals{ - {&constraint{Version{major: 3}, lessThan}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - { - name: ">_within_<=_>", - is: Intervals{ - {&constraint{Version{major: 3}, lessThan}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThan}}}, - }, - { - name: ">_within_<_>=", - is: Intervals{ - {&constraint{Version{major: 3}, lessThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - { - name: ">_within_<_>", - is: Intervals{ - {&constraint{Version{major: 3}, lessThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThan}}}, - }, - - { - name: "<=_seamless_floor_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - { - name: "<=_seamless_floor_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - { - name: "<=_seamless_floor_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - { - name: "<=_seamless_floor_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - - { - name: "<_seamless_floor_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - { - name: "<_seamless_floor_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - { - name: "<_seamless_floor_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThan}}}, - }, - { - name: "<_seamless_floor_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, greaterThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThan}}}, - }, - - { - name: ">=_seamless_floor_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, lessThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - { - name: ">=_seamless_floor_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, lessThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThan}}}, - }, - { - name: ">=_seamless_floor_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, lessThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - { - name: ">=_seamless_floor_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, lessThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThan}}}, - }, - - { - name: ">_seamless_floor_<=_>=", - is: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - { - name: ">_seamless_floor_<=_>", - is: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThan}}}, - }, - { - name: ">_seamless_floor_<_>=", - is: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - }, - { - name: ">_seamless_floor_<_>", - is: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, lessThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - }, - - { - name: "<=_seamless_ceiling_<=_>=", - is: Intervals{ - {&constraint{Version{major: 4}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - { - name: "<=_seamless_ceiling_<=_>", - is: Intervals{ - {&constraint{Version{major: 4}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - { - name: "<=_seamless_ceiling_<_>=", - is: Intervals{ - {&constraint{Version{major: 4}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThan}}}, - }, - { - name: "<=_seamless_ceiling_<_>", - is: Intervals{ - {&constraint{Version{major: 4}, greaterThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThan}}}, - }, - - { - name: "<_seamless_ceiling_<=_>=", - is: Intervals{ - {&constraint{Version{major: 4}, greaterThan}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThanOrEqualTo}}}, - }, - { - name: "<_seamless_ceiling_<=_>", - is: Intervals{ - {&constraint{Version{major: 4}, greaterThan}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - {&constraint{Version{major: 4}, greaterThan}}, - }, - }, - { - name: "<_seamless_ceiling_<_>=", - is: Intervals{ - {&constraint{Version{major: 4}, greaterThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 1}, greaterThan}}}, - }, - { - name: "<_seamless_ceiling_<_>", - is: Intervals{ - {&constraint{Version{major: 4}, greaterThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{ - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - {&constraint{Version{major: 4}, greaterThan}}, - }, - }, - - { - name: ">=_seamless_ceiling_<=_>=", - is: Intervals{ - {&constraint{Version{major: 4}, lessThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - { - name: ">=_seamless_ceiling_<=_>", - is: Intervals{ - {&constraint{Version{major: 4}, lessThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - { - name: ">=_seamless_ceiling_<_>=", - is: Intervals{ - {&constraint{Version{major: 4}, lessThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - { - name: ">=_seamless_ceiling_<_>", - is: Intervals{ - {&constraint{Version{major: 4}, lessThanOrEqualTo}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - - { - name: ">_seamless_ceiling_<=_>=", - is: Intervals{ - {&constraint{Version{major: 4}, lessThan}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - { - name: ">_seamless_ceiling_<=_>", - is: Intervals{ - {&constraint{Version{major: 4}, lessThan}}, - {&constraint{Version{major: 1}, greaterThanOrEqualTo}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThan}}}, - }, - { - name: ">_seamless_ceiling_<_>=", - is: Intervals{ - {&constraint{Version{major: 4}, lessThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThanOrEqualTo}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThanOrEqualTo}}}, - }, - { - name: ">_seamless_ceiling_<_>", - is: Intervals{ - {&constraint{Version{major: 4}, lessThan}}, - {&constraint{Version{major: 1}, greaterThan}, &constraint{Version{major: 4}, lessThan}}, - }, - want: Intervals{{&constraint{Version{major: 4}, lessThan}}}, - }, - } -} - -func TestCompact(t *testing.T) { - t.Parallel() - - for _, tt := range compactTestCases() { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - if got := Compact(tt.is); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Compact(%q) = %q, want %q", tt.is, got, tt.want) - } - }) - } -} - -func TestCompact_reverse(t *testing.T) { - t.Parallel() - - for _, tt := range compactTestCases() { - t.Run(tt.name+"_reverse", func(t *testing.T) { - t.Parallel() - - slices.Reverse(tt.is) - - if got := Compact(tt.is); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Compact(%q) = %q, want %q", tt.is, got, tt.want) - } - }) - } -} - -func TestCompact_shuffle(t *testing.T) { - t.Parallel() - - for _, tt := range compactTestCases() { - t.Run(tt.name+"_shuffle", func(t *testing.T) { - t.Parallel() - - rand.Shuffle(len(tt.is), func(i, j int) { - tt.is[i], tt.is[j] = tt.is[j], tt.is[i] - }) - - if got := Compact(tt.is); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Compact(%q) = %q, want %q", tt.is, got, tt.want) - } - }) - } -} diff --git a/modifier.go b/modifier.go index 2c3a5c3..2d2c803 100644 --- a/modifier.go +++ b/modifier.go @@ -8,7 +8,7 @@ const ( modifierRC modifier = -10 modifierBeta modifier = -20 modifierAlpha modifier = -30 - ErrUnexpectedModifier stringError = "unexpected modifier" + errUnexpectedModifier stringError = "unexpected modifier" ) func newModifier(s string) (modifier, error) { @@ -24,7 +24,8 @@ func newModifier(s string) (modifier, error) { case "alpha", "a": return modifierAlpha, nil } - return modifierStable, ErrUnexpectedModifier + + return modifierStable, errUnexpectedModifier } func (s modifier) String() string { diff --git a/op.go b/op.go new file mode 100644 index 0000000..05cc668 --- /dev/null +++ b/op.go @@ -0,0 +1,53 @@ +package comver + +const ( + greaterThanOrEqualTo op = iota + greaterThan + lessThan + lessThanOrEqualTo + + errUnexpectedOp stringError = "unexpected op" +) + +type op int8 + +func (o op) String() string { + switch o { + case lessThanOrEqualTo: + return "<=" + case lessThan: + return "<" + case greaterThan: + return ">" + case greaterThanOrEqualTo: + return ">=" + default: + // logic error! This should never happen + panic(errUnexpectedOp) + } +} + +func (o op) compare(other op) int { + i := int(o) - int(other) + + switch { + case i < 0: + return -1 + case i > 0: + return 1 + default: + return 0 + } +} + +func (o op) ceilingBounded() bool { + return o == lessThan || o == lessThanOrEqualTo +} + +func (o op) floorBounded() bool { + return o == greaterThan || o == greaterThanOrEqualTo +} + +func (o op) inclusive() bool { + return o == lessThanOrEqualTo || o == greaterThanOrEqualTo +} diff --git a/op_test.go b/op_test.go new file mode 100644 index 0000000..7e72cb7 --- /dev/null +++ b/op_test.go @@ -0,0 +1,120 @@ +package comver + +import "testing" + +func Test_op_compare(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + o op + other op + want int + }{ + { + name: "greaterThanOrEqualTo_compare_greaterThanOrEqualTo", + o: greaterThanOrEqualTo, + other: greaterThanOrEqualTo, + want: 0, + }, + { + name: "greaterThanOrEqualTo_compare_greaterThan", + o: greaterThanOrEqualTo, + other: greaterThan, + want: -1, + }, + { + name: "greaterThanOrEqualTo_compare_lessThan", + o: greaterThanOrEqualTo, + other: lessThan, + want: -1, + }, + { + name: "greaterThanOrEqualTo_compare_lessThanOrEqualTo", + o: greaterThanOrEqualTo, + other: lessThanOrEqualTo, + want: -1, + }, + { + name: "greaterThan_compare_greaterThanOrEqualTo", + o: greaterThan, + other: greaterThanOrEqualTo, + want: 1, + }, + { + name: "greaterThan_compare_greaterThan", + o: greaterThan, + other: greaterThan, + want: 0, + }, + { + name: "greaterThan_compare_lessThan", + o: greaterThan, + other: lessThan, + want: -1, + }, + { + name: "greaterThan_compare_lessThanOrEqualTo", + o: greaterThan, + other: lessThanOrEqualTo, + want: -1, + }, + { + name: "lessThan_compare_greaterThanOrEqualTo", + o: lessThan, + other: greaterThanOrEqualTo, + want: 1, + }, + { + name: "lessThan_compare_greaterThan", + o: lessThan, + other: greaterThan, + want: 1, + }, + { + name: "lessThan_compare_lessThan", + o: lessThan, + other: lessThan, + want: 0, + }, + { + name: "lessThan_compare_lessThanOrEqualTo", + o: lessThan, + other: lessThanOrEqualTo, + want: -1, + }, + { + name: "lessThanOrEqualTo_compare_greaterThanOrEqualTo", + o: lessThanOrEqualTo, + other: greaterThanOrEqualTo, + want: 1, + }, + { + name: "lessThanOrEqualTo_compare_greaterThan", + o: lessThanOrEqualTo, + other: greaterThan, + want: 1, + }, + { + name: "lessThanOrEqualTo_compare_lessThan", + o: lessThanOrEqualTo, + other: lessThan, + want: 1, + }, + { + name: "lessThanOrEqualTo_compare_lessThanOrEqualTo", + o: lessThanOrEqualTo, + other: lessThanOrEqualTo, + want: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if got := tt.o.compare(tt.other); got != tt.want { + t.Errorf("compare() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/or.go b/or.go new file mode 100644 index 0000000..8b83cef --- /dev/null +++ b/or.go @@ -0,0 +1,32 @@ +package comver + +//nolint:godox +// TODO: Make Or to be []Constrainter so that we can nest Or + +// Or represents a logical OR operation between multiple [CeilingFloorConstrainter] instances. +// The zero value for Or is a constraint could never be satisfied. +type Or []CeilingFloorConstrainter + +// Check reports whether a [Version] satisfies the constraint. +func (o Or) Check(v Version) bool { + for i := range o { + if o[i].Check(v) { + return true + } + } + + return false +} + +func (o Or) String() string { + s := "" + + for i := range o { + if i > 0 { + s += " || " + } + s += o[i].String() + } + + return s +} diff --git a/version.go b/version.go index 31d104b..836ddc3 100644 --- a/version.go +++ b/version.go @@ -11,10 +11,10 @@ const ( classicalVersioningRegex = `(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?` dateOnlyVersioningRegex = `(\d{4})(?:[.:-]?(\d{2}))(?:[.:-]?(\d{2}))?(?:\.(\d+))?` modifierRegex = `[._-]?(?:(stable|beta|b|rc|alpha|a|patch|pl|p)((?:[.-]?\d+)+)?)?` - ErrEmptyString stringError = "version string is empty" - ErrInvalidVersionString stringError = "invalid version string" - ErrNotFixedVersion stringError = "not a fixed version" - ErrDateVersionWithFourBits stringError = "date versions with 4 bits" + errEmptyString stringError = "version string is empty" + errInvalidVersionString stringError = "invalid version string" + errNotFixedVersion stringError = "not a fixed version" + errDateVersionWithFourBits stringError = "date versions with 4 bits" ) var ( @@ -23,6 +23,7 @@ var ( ) // Version represents a single composer version. +// The zero value for Version is v0.0.0.0 with an empty original string. type Version struct { major, minor, patch, tweak uint64 `exhaustruct:"optional"` modifier modifier `exhaustruct:"optional"` @@ -30,10 +31,10 @@ type Version struct { original string `exhaustruct:"optional"` } -// NewVersion parses a given version string, attempts to coerce a version string into +// Parse parses a given version string, attempts to coerce a version string into // a [Version] object or return an error if unable to parse the version string. // -// If there is a leading v or a version listed without all parts (e.g. v1.2.p5+foo) it will +// If there is a leading v or a version listed without all parts (e.g. v1.2.p5+foo) it // attempt to coerce it into a valid composer version (e.g. 1.2.0.0-patch5). In both cases // a [Version] object is returned that can be sorted, compared, and used in constraints. // @@ -42,7 +43,7 @@ type Version struct { // // [composer versioning]: https://github.com/composer/semver/ // [version_test.go]: https://github.com/typisttech/comver/blob/main/version_test.go -func NewVersion(v string) (Version, error) { //nolint:cyclop,funlen +func Parse(v string) (Version, error) { //nolint:cyclop,funlen original := v // normalize to lowercase for easier pattern matching @@ -50,38 +51,38 @@ func NewVersion(v string) (Version, error) { //nolint:cyclop,funlen v = strings.TrimSpace(v) if v == "" { - return Version{}, &ParseError{original, ErrEmptyString} + return Version{}, &ParseError{original, errEmptyString} } v = strings.TrimPrefix(v, "v") if v == "" { - return Version{}, &ParseError{original, ErrInvalidVersionString} + return Version{}, &ParseError{original, errInvalidVersionString} } if strings.Contains(v, " as ") { - return Version{}, &ParseError{original, ErrNotFixedVersion} + return Version{}, &ParseError{original, errNotFixedVersion} } if hasSuffixAnyOf(v, "@stable", "@rc", "@beta", "@alpha", "@dev") { - return Version{}, &ParseError{original, ErrNotFixedVersion} + return Version{}, &ParseError{original, errNotFixedVersion} } if containsAnyOf(v, "master", "trunk", "default") { - return Version{}, &ParseError{original, ErrNotFixedVersion} + return Version{}, &ParseError{original, errNotFixedVersion} } if strings.HasPrefix(v, "dev-") { - return Version{}, &ParseError{original, ErrNotFixedVersion} + return Version{}, &ParseError{original, errNotFixedVersion} } // strip off build metadata v, metadata, _ := strings.Cut(v, "+") if v == "" || strings.Contains(metadata, " ") { - return Version{}, &ParseError{original, ErrInvalidVersionString} + return Version{}, &ParseError{original, errInvalidVersionString} } if strings.HasSuffix(v, "dev") { - return Version{}, &ParseError{original, ErrNotFixedVersion} + return Version{}, &ParseError{original, errNotFixedVersion} } cv := Version{ @@ -96,7 +97,7 @@ func NewVersion(v string) (Version, error) { //nolint:cyclop,funlen match = dm } if match == nil || len(match) != 7 { - return Version{}, &ParseError{original, ErrInvalidVersionString} + return Version{}, &ParseError{original, errInvalidVersionString} } var err error @@ -106,7 +107,7 @@ func NewVersion(v string) (Version, error) { //nolint:cyclop,funlen } // CalVer (as MAJOR) must be in YYYYMMDDhhmm or YYYYMMDD formats if s := strconv.FormatUint(cv.major, 10); len(s) > 12 || len(s) == 11 || len(s) == 9 || len(s) == 7 { - return Version{}, &ParseError{original, ErrInvalidVersionString} + return Version{}, &ParseError{original, errInvalidVersionString} } if cv.minor, err = strconv.ParseUint(match[2], 10, 64); match[2] != "" && err != nil { @@ -118,7 +119,7 @@ func NewVersion(v string) (Version, error) { //nolint:cyclop,funlen } if cv.major >= 1000_00 && match[4] != "" { - return Version{}, &ParseError{original, ErrDateVersionWithFourBits} + return Version{}, &ParseError{original, errDateVersionWithFourBits} } if cv.tweak, err = strconv.ParseUint(match[4], 10, 64); match[4] != "" && err != nil { return Version{}, &ParseError{original, err} @@ -133,12 +134,23 @@ func NewVersion(v string) (Version, error) { //nolint:cyclop,funlen return cv, nil } +// MustParse is like [Parse] but panics if the version string cannot be parsed. +func MustParse(v string) Version { + cv, err := Parse(v) + if err != nil { + panic(err) + } + + return cv +} + func hasSuffixAnyOf(s string, suffixes ...string) bool { for _, suffix := range suffixes { if strings.HasSuffix(s, suffix) { return true } } + return false } @@ -148,6 +160,7 @@ func containsAnyOf(s string, substrs ...string) bool { return true } } + return false } @@ -177,15 +190,19 @@ func (v Version) Short() string { return s } +// Original returns the original version string passed into [Parse]. +// Empty string is returned when [Version] is the zero value. func (v Version) Original() string { return v.original } -// Compare returns an integer comparing two versions. +// Compare returns an integer comparing two [Version] instances. // -// Pre-release versions are compared according to semantic version precedence. +// Pre-release versions are compared according to [semantic version precedence]. // The result is 0 when v == w, -1 when v < w, or +1 when v > w. -func (v Version) Compare(w Version) int { ////nolint:cyclop,funlen +// +// [semantic version precedence]: https://semver.org/#spec-item-11 +func (v Version) Compare(w Version) int { //nolint:cyclop,funlen switch { case v.String() == w.String(): return 0 @@ -218,7 +235,7 @@ func (v Version) Compare(w Version) int { ////nolint:cyclop,funlen vPres := strings.Split(v.preRelease, ".") wPres := strings.Split(w.preRelease, ".") - // comparing each dot separated identifier from left to right + // comparing each dot separated identifier from ceiling to floor for i := range vPres { // a larger set of pre-release fields has a higher precedence than a smaller set if i >= len(wPres) { @@ -241,6 +258,7 @@ func (v Version) Compare(w Version) int { ////nolint:cyclop,funlen if vii > wii { return +1 } + return -1 } @@ -252,16 +270,18 @@ func (v Version) Compare(w Version) int { ////nolint:cyclop,funlen if vi > wi { return +1 } + return -1 } //nolint:godox // TODO: Find out whether composer/semver supports this // - // numeric identifiers always have lower precedence than non-numeric identifiers + // numeric identifiers always have floor precedence than non-numeric identifiers if !vid && wid { return +1 } + return -1 } @@ -275,5 +295,6 @@ func isDigits(s string) bool { return false } } + return true } diff --git a/version_example_test.go b/version_example_test.go index cdac780..6f2c75c 100644 --- a/version_example_test.go +++ b/version_example_test.go @@ -6,93 +6,38 @@ import ( "github.com/typisttech/comver" ) -func ExampleNewVersion() { - v, _ := comver.NewVersion("1.2.3") +func ExampleParse() { + v, _ := comver.Parse("1.2.3") fmt.Println(v) // Output: 1.2.3.0 } -func ExampleNewVersion_full() { - v, _ := comver.NewVersion("1.2.3.4-beta.5+foo") +func ExampleParse_full() { + v, _ := comver.Parse("1.2.3.4-beta.5+foo") fmt.Println(v) // Output: 1.2.3.4-beta5 } -func ExampleNewVersion_withLeadingV() { - v, _ := comver.NewVersion("v1.2.3.4-beta.5+foo") - - fmt.Println(v) - // Output: 1.2.3.4-beta5 -} - -func ExampleNewVersion_error() { - _, err := comver.NewVersion("not a version") +func ExampleParse_error() { + _, err := comver.Parse("not a version") fmt.Println(err) // Output: error parsing version string "not a version" } -func ExampleNewVersion_dateCal() { - v, _ := comver.NewVersion("2010-01-02") - - fmt.Println(v) - // Output: 2010.1.2.0 -} - -func ExampleNewVersion_spacePadding() { - v, _ := comver.NewVersion(" 1.0.0") - - fmt.Println(v) - // Output: 1.0.0.0 -} - -func ExampleNewVersion_modifierShorthand() { - v, _ := comver.NewVersion("1.2.3-b5") - - fmt.Println(v) - // Output: 1.2.3.0-beta5 -} - -func ExampleNewVersion_zeroPadding() { - v, _ := comver.NewVersion("00.01.03.04") - - fmt.Println(v) - // Output: 0.1.3.4 -} - -func ExampleNewVersion_dateWithFourBits() { - _, err := comver.NewVersion("20100102.0.3.4") - - fmt.Println(err) - // Output: error parsing version string "20100102.0.3.4" -} - -func ExampleNewVersion_invalidModifier() { - _, err := comver.NewVersion("1.0.0-meh") - - fmt.Println(err) - // Output: error parsing version string "1.0.0-meh" -} - -func ExampleNewVersion_tooManyBits() { - _, err := comver.NewVersion("1.0.0.0.0") - - fmt.Println(err) - // Output: error parsing version string "1.0.0.0.0" -} - func ExampleVersion_Compare() { - v1, _ := comver.NewVersion("1") - v2, _ := comver.NewVersion("2") - v3, _ := comver.NewVersion("3") + v1 := comver.MustParse("1") + v2 := comver.MustParse("2") + w2 := comver.MustParse("2") + v3 := comver.MustParse("3") v2v1 := v2.Compare(v1) fmt.Println(v2v1) - v2v2 := v2.Compare(v2) //nolint:gocritic - fmt.Println(v2v2) + v2w2 := v2.Compare(w2) + fmt.Println(v2w2) v2v3 := v2.Compare(v3) fmt.Println(v2v3) @@ -104,93 +49,95 @@ func ExampleVersion_Compare() { } func ExampleVersion_Compare_patch() { - v1, _ := comver.NewVersion("1") - v1p, _ := comver.NewVersion("1.patch") + v1 := comver.MustParse("1") + v1p := comver.MustParse("1.patch") - v1v1p := v1.Compare(v1p) + got := v1.Compare(v1p) - fmt.Println(v1v1p) + fmt.Println(got) // Output: -1 } func ExampleVersion_Compare_preRelease() { - v1b5, _ := comver.NewVersion("1.0.0-beta.5") - v1b6, _ := comver.NewVersion("1.0.0-beta.6") + v1b5 := comver.MustParse("1.0.0-beta.5") + v1b6 := comver.MustParse("1.0.0-beta.6") - v1b5v1b6 := v1b5.Compare(v1b6) + got := v1b5.Compare(v1b6) - fmt.Println(v1b5v1b6) + fmt.Println(got) // Output: -1 } -func ExampleVersion_Short_major() { - v, _ := comver.NewVersion("1") - - s := v.Short() - - fmt.Println(s) - // Output: 1 -} - -func ExampleVersion_Short_minor() { - v, _ := comver.NewVersion("1.2") - - s := v.Short() - - fmt.Println(s) - // Output: 1.2 -} - -func ExampleVersion_Short_patch() { - v, _ := comver.NewVersion("1.2.3") - - s := v.Short() - - fmt.Println(s) - // Output: 1.2.3 -} - -func ExampleVersion_Short_tweak() { - v, _ := comver.NewVersion("1.2.3.4") +func ExampleVersion_Compare_metadata() { + foo := comver.MustParse("1.0.0+foo") + bar := comver.MustParse("1.0.0+bar") - s := v.Short() + got := foo.Compare(bar) - fmt.Println(s) - // Output: 1.2.3.4 + fmt.Println(got) + // Output: 0 } -func ExampleVersion_Short_modifier() { - v, _ := comver.NewVersion("1.2.3.4.beta") - - s := v.Short() - - fmt.Println(s) - // Output: 1.2.3.4-beta -} - -func ExampleVersion_Short_preRelease() { - v, _ := comver.NewVersion("1.2.3.4-beta5") - - s := v.Short() +func ExampleVersion_Short() { + ss := []string{ + "1", + "1.2", + "1.2.3", + "1.2.3+foo", + "1.2.3.4", + "1.2.3.4.beta", + "1.2.3.4-beta5", + "1.2.3.4-beta5+foo", + "1.b5+foo", + } - fmt.Println(s) - // Output: 1.2.3.4-beta5 -} + for _, s := range ss { + v := comver.MustParse(s) + got := v.Short() -func ExampleVersion_Short_metadata() { - v, _ := comver.NewVersion("1.2.3.4-beta5+foo") + fmt.Printf("%-19q => %v\n", s, got) + } - s := v.Short() - - fmt.Println(s) - // Output: 1.2.3.4-beta5 + // Output: + // "1" => 1 + // "1.2" => 1.2 + // "1.2.3" => 1.2.3 + // "1.2.3+foo" => 1.2.3 + // "1.2.3.4" => 1.2.3.4 + // "1.2.3.4.beta" => 1.2.3.4-beta + // "1.2.3.4-beta5" => 1.2.3.4-beta5 + // "1.2.3.4-beta5+foo" => 1.2.3.4-beta5 + // "1.b5+foo" => 1-beta5 } func ExampleVersion_Original() { - v, _ := comver.NewVersion("1.b5+foo") - - s := v.Original() + ss := []string{ + "1", + "1.2", + "1.2.3", + "1.2.3+foo", + "1.2.3.4", + "1.2.3.4.beta", + "1.2.3.4-beta5", + "1.2.3.4-beta5+foo", + "1.b5+foo", + } + + for _, s := range ss { + v := comver.MustParse(s) + got := v.Original() + + fmt.Printf("%-19q => %v\n", s, got) + } - fmt.Println(s) - // Output: 1.b5+foo + // Output: + // "1" => 1 + // "1.2" => 1.2 + // "1.2.3" => 1.2.3 + // "1.2.3+foo" => 1.2.3+foo + // "1.2.3.4" => 1.2.3.4 + // "1.2.3.4.beta" => 1.2.3.4.beta + // "1.2.3.4-beta5" => 1.2.3.4-beta5 + // "1.2.3.4-beta5+foo" => 1.2.3.4-beta5+foo + // "1.b5+foo" => 1.b5+foo } diff --git a/version_test.go b/version_test.go index 08ccbde..6d33d28 100644 --- a/version_test.go +++ b/version_test.go @@ -5,10 +5,12 @@ import ( "testing" ) -func TestNewVersion(t *testing.T) { - t.Parallel() - - tests := []struct { +func goodVersionTestCases() []struct { + name string + v string + want string +} { + return []struct { name string v string want string @@ -69,28 +71,54 @@ func TestNewVersion(t *testing.T) { // additional tests {"parses dates y-m", "2010-01", "2010.1.0.0"}, } - for _, tt := range tests { +} + +func TestParse(t *testing.T) { + t.Parallel() + + for _, tt := range goodVersionTestCases() { t.Run(tt.name, func(t *testing.T) { t.Parallel() - got, err := NewVersion(tt.v) + got, err := Parse(tt.v) if err != nil { - t.Fatalf("NewVersion() error = %v, wantErr %v", err, nil) + t.Fatalf("Parse() error = %v, wantErr %v", err, nil) } if gotString := got.String(); gotString != tt.want { - t.Errorf("NewVersion().String() got = %q, want %v", gotString, tt.want) + t.Errorf("Parse().String() got = %q, want %v", gotString, tt.want) } - if gotOriginal := got.original; gotOriginal != tt.v { - t.Errorf("NewVersion().original got = %q, want %v", gotOriginal, tt.v) + if gotOriginal := got.Original(); gotOriginal != tt.v { + t.Errorf("Parse().Original() got = %q, want %v", gotOriginal, tt.v) } }) } } -func TestNewVersion_ParseError(t *testing.T) { +func TestMustParse(t *testing.T) { t.Parallel() - tests := []struct { + for _, tt := range goodVersionTestCases() { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got := MustParse(tt.v) + + if gotString := got.String(); gotString != tt.want { + t.Errorf("MustParse().String() got = %q, want %v", gotString, tt.want) + } + if gotOriginal := got.Original(); gotOriginal != tt.v { + t.Errorf("MustParse().Original() got = %q, want %v", gotOriginal, tt.v) + } + }) + } +} + +func badVersionTestCases() []struct { + name string + v string + wantErr error +} { + return []struct { name string v string wantErr error @@ -98,99 +126,143 @@ func TestNewVersion_ParseError(t *testing.T) { // composer/semver supports a lot of different version formats, but we only support a subset of them // taken from composer/semver VersionParserTest::successfulNormalizedVersions() // https://github.com/composer/semver/blob/1d09200268e7d1052ded8e5da9c73c96a63d18f5/tests/VersionParserTest.php#L65-L142 - {"parses state", "1.0.0RC1dev", ErrNotFixedVersion}, - {"CI parsing", "1.0.0-rC15-dev", ErrNotFixedVersion}, - {"delimiters", "1.0.0.RC.15-dev", ErrNotFixedVersion}, - {"patch replace", "1.0.0.pl3-dev", ErrNotFixedVersion}, - {"forces w.x.y.z", "1.0-dev", ErrNotFixedVersion}, - {"parses dates w/ - and .", "2010-01-02-10-20-30.0.3", ErrInvalidVersionString}, - {"parses dates w/ - and ./2", "2010-01-02-10-20-30.5", ErrInvalidVersionString}, - {"parses datetime", "20100102-203040", ErrInvalidVersionString}, - {"parses date dev", "20100102.x-dev", ErrNotFixedVersion}, - {"parses datetime dev", "20100102.203040.x-dev", ErrNotFixedVersion}, - {"parses dt+number", "20100102203040-10", ErrInvalidVersionString}, - {"parses dt+patch", "20100102-203040-p1", ErrInvalidVersionString}, - {"parses dt Ym dev", "201903.x-dev", ErrNotFixedVersion}, - {"parses master", "dev-master", ErrNotFixedVersion}, - {"parses master w/o dev", "master", ErrNotFixedVersion}, - {"parses trunk", "dev-trunk", ErrNotFixedVersion}, - {"parses branches", "1.x-dev", ErrNotFixedVersion}, - {"parses arbitrary", "dev-feature-foo", ErrNotFixedVersion}, - {"parses arbitrary/2", "DEV-FOOBAR", ErrNotFixedVersion}, - {"parses arbitrary/3", "dev-feature/foo", ErrNotFixedVersion}, - {"parses arbitrary/4", "dev-feature+issue-1", ErrNotFixedVersion}, - {"ignores aliases", "dev-master as 1.0.0", ErrNotFixedVersion}, - {"ignores aliases/2", "dev-load-varnish-only-when-used as ^2.0", ErrNotFixedVersion}, - {"ignores aliases/3", "dev-load-varnish-only-when-used@dev as ^2.0@dev", ErrNotFixedVersion}, - {"ignores stability", "1.0.0+foo@dev", ErrNotFixedVersion}, - {"ignores stability/2", "dev-load-varnish-only-when-used@stable", ErrNotFixedVersion}, - {"semver metadata/7", "1.0.0-0.3.7", ErrInvalidVersionString}, // composer/semver doesn't support this - {"semver metadata/8", "1.0.0-x.7.z.92", ErrInvalidVersionString}, // composer/semver doesn't support this - {"metadata w/ alias", "1.0.0+foo as 2.0", ErrNotFixedVersion}, - {"keep zero-padding/5", "041.x-dev", ErrNotFixedVersion}, - {"keep zero-padding/6", "dev-041.003", ErrNotFixedVersion}, - {"dev with mad name", "dev-1.0.0-dev<1.0.5-dev", ErrNotFixedVersion}, - {"dev prefix with spaces", "dev-foo bar", ErrNotFixedVersion}, + {"parses state", "1.0.0RC1dev", errNotFixedVersion}, + {"CI parsing", "1.0.0-rC15-dev", errNotFixedVersion}, + {"delimiters", "1.0.0.RC.15-dev", errNotFixedVersion}, + {"patch replace", "1.0.0.pl3-dev", errNotFixedVersion}, + {"forces w.x.y.z", "1.0-dev", errNotFixedVersion}, + {"parses dates w/ - and .", "2010-01-02-10-20-30.0.3", errInvalidVersionString}, + {"parses dates w/ - and ./2", "2010-01-02-10-20-30.5", errInvalidVersionString}, + {"parses datetime", "20100102-203040", errInvalidVersionString}, + {"parses date dev", "20100102.x-dev", errNotFixedVersion}, + {"parses datetime dev", "20100102.203040.x-dev", errNotFixedVersion}, + {"parses dt+number", "20100102203040-10", errInvalidVersionString}, + {"parses dt+patch", "20100102-203040-p1", errInvalidVersionString}, + {"parses dt Ym dev", "201903.x-dev", errNotFixedVersion}, + {"parses master", "dev-master", errNotFixedVersion}, + {"parses master w/o dev", "master", errNotFixedVersion}, + {"parses trunk", "dev-trunk", errNotFixedVersion}, + {"parses branches", "1.x-dev", errNotFixedVersion}, + {"parses arbitrary", "dev-feature-foo", errNotFixedVersion}, + {"parses arbitrary/2", "DEV-FOOBAR", errNotFixedVersion}, + {"parses arbitrary/3", "dev-feature/foo", errNotFixedVersion}, + {"parses arbitrary/4", "dev-feature+issue-1", errNotFixedVersion}, + {"ignores aliases", "dev-master as 1.0.0", errNotFixedVersion}, + {"ignores aliases/2", "dev-load-varnish-only-when-used as ^2.0", errNotFixedVersion}, + {"ignores aliases/3", "dev-load-varnish-only-when-used@dev as ^2.0@dev", errNotFixedVersion}, + {"ignores stability", "1.0.0+foo@dev", errNotFixedVersion}, + {"ignores stability/2", "dev-load-varnish-only-when-used@stable", errNotFixedVersion}, + {"semver metadata/7", "1.0.0-0.3.7", errInvalidVersionString}, // composer/semver doesn't support this + {"semver metadata/8", "1.0.0-x.7.z.92", errInvalidVersionString}, // composer/semver doesn't support this + {"metadata w/ alias", "1.0.0+foo as 2.0", errNotFixedVersion}, + {"keep zero-padding/5", "041.x-dev", errNotFixedVersion}, + {"keep zero-padding/6", "dev-041.003", errNotFixedVersion}, + {"dev with mad name", "dev-1.0.0-dev<1.0.5-dev", errNotFixedVersion}, + {"dev prefix with spaces", "dev-foo bar", errNotFixedVersion}, // composer/semver doesn't support these // taken from composer/semver VersionParserTest::failingNormalizedVersions() // https://github.com/composer/semver/blob/1d09200268e7d1052ded8e5da9c73c96a63d18f5/tests/VersionParserTest.php#L158-L183 - {"empty", "", ErrEmptyString}, - {"invalid chars", "a", ErrInvalidVersionString}, - {"invalid type", "1.0.0-meh", ErrInvalidVersionString}, - {"too many bits", "1.0.0.0.0", ErrInvalidVersionString}, - {"non-dev arbitrary", "feature-foo", ErrInvalidVersionString}, - {"metadata w/ space", "1.0.0+foo bar", ErrInvalidVersionString}, - {"maven style release", "1.0.1-SNAPSHOT", ErrInvalidVersionString}, - {"dev with less than", "1.0.0<1.0.5-dev", ErrNotFixedVersion}, - {"dev with less than/2", "1.0.0-dev<1.0.5-dev", ErrNotFixedVersion}, - {"dev suffix with spaces", "foo bar-dev", ErrNotFixedVersion}, - {"any with spaces", "1.0 .2", ErrInvalidVersionString}, - {"no version, no alias", " as ", ErrInvalidVersionString}, - {"no version, only alias", " as 1.2", ErrInvalidVersionString}, - {"just an operator", "^", ErrInvalidVersionString}, - {"just an operator/2", "^8 || ^", ErrInvalidVersionString}, - {"just an operator/3", "~", ErrInvalidVersionString}, - {"just an operator/4", "~1 ~", ErrInvalidVersionString}, - {"constraint", "~1", ErrInvalidVersionString}, - {"constraint/2", "^1", ErrInvalidVersionString}, - {"constraint/3", "1.*", ErrInvalidVersionString}, - {"date versions with 4 bits", "20100102.0.3.4", ErrDateVersionWithFourBits}, - {"date versions with 4 bits/earliest year", "100000.0.0.0", ErrDateVersionWithFourBits}, - {"invalid CalVer (as MAJOR) versions/YYYYMMD", "2023013.0.0", ErrInvalidVersionString}, - {"invalid CalVer (as MAJOR) versions/YYYYMMDDh", "202301311.0.0", ErrInvalidVersionString}, - {"invalid CalVer (as MAJOR) versions/YYYYMMDDhhm", "20230131000.0.0", ErrInvalidVersionString}, - {"invalid CalVer (as MAJOR) versions/YYYYMMDDhhmmX", "2023013100000.0.0", ErrInvalidVersionString}, + {"empty", "", errEmptyString}, + {"invalid chars", "a", errInvalidVersionString}, + {"invalid type", "1.0.0-meh", errInvalidVersionString}, + {"too many bits", "1.0.0.0.0", errInvalidVersionString}, + {"non-dev arbitrary", "feature-foo", errInvalidVersionString}, + {"metadata w/ space", "1.0.0+foo bar", errInvalidVersionString}, + {"maven style release", "1.0.1-SNAPSHOT", errInvalidVersionString}, + {"dev with less than", "1.0.0<1.0.5-dev", errNotFixedVersion}, + {"dev with less than/2", "1.0.0-dev<1.0.5-dev", errNotFixedVersion}, + {"dev suffix with spaces", "foo bar-dev", errNotFixedVersion}, + {"any with spaces", "1.0 .2", errInvalidVersionString}, + {"no version, no alias", " as ", errInvalidVersionString}, + {"no version, only alias", " as 1.2", errInvalidVersionString}, + {"just an operator", "^", errInvalidVersionString}, + {"just an operator/2", "^8 || ^", errInvalidVersionString}, + {"just an operator/3", "~", errInvalidVersionString}, + {"just an operator/4", "~1 ~", errInvalidVersionString}, + {"constraint", "~1", errInvalidVersionString}, + {"constraint/2", "^1", errInvalidVersionString}, + {"constraint/3", "1.*", errInvalidVersionString}, + {"date versions with 4 bits", "20100102.0.3.4", errDateVersionWithFourBits}, + {"date versions with 4 bits/earliest year", "100000.0.0.0", errDateVersionWithFourBits}, + {"invalid CalVer (as MAJOR) versions/YYYYMMD", "2023013.0.0", errInvalidVersionString}, + {"invalid CalVer (as MAJOR) versions/YYYYMMDDh", "202301311.0.0", errInvalidVersionString}, + {"invalid CalVer (as MAJOR) versions/YYYYMMDDhhm", "20230131000.0.0", errInvalidVersionString}, + {"invalid CalVer (as MAJOR) versions/YYYYMMDDhhmmX", "2023013100000.0.0", errInvalidVersionString}, // composer/semver doesn't support these. // taken from https://semver.org/#spec-item-11 - {"incompatible semver", "1.0.0-alpha.beta", ErrInvalidVersionString}, + {"incompatible semver", "1.0.0-alpha.beta", errInvalidVersionString}, } - for _, tt := range tests { +} + +func TestParse_ParseError(t *testing.T) { + t.Parallel() + + for _, tt := range badVersionTestCases() { t.Run(tt.name, func(t *testing.T) { t.Parallel() - got, err := NewVersion(tt.v) + got, err := Parse(tt.v) if err == nil { - t.Fatalf("NewVersion() got = %s error = %v, wantErr %v", got, err, tt.wantErr) + t.Fatalf("Parse() got = %s error = %v, wantErr %v", got, err, tt.wantErr) } if !errors.Is(err, tt.wantErr) { - t.Errorf("NewVersion() error = %#v, wantErr %#v", err, tt.wantErr) + t.Errorf("Parse() error = %#v, wantErr %#v", err, tt.wantErr) } var wantParseError *ParseError if !errors.As(err, &wantParseError) { - t.Fatalf("NewVersion() error = %#v, wantErr %#v", err, wantParseError) + t.Fatalf("Parse() error = %#v, wantErr %#v", err, wantParseError) } if wantParseError.original != tt.v { - t.Errorf("NewVersion() error.original = %v, want %v", wantParseError.original, tt.v) + t.Errorf("Parse() error.original = %v, want %v", wantParseError.original, tt.v) } }) } } +func TestMustParse_ParseError(t *testing.T) { + t.Parallel() + + for _, tt := range badVersionTestCases() { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var got Version + + defer func() { + err := recover() + if err == nil { + t.Fatalf("MustParse() got = %s panic = %v, wantErr %v", got, err, tt.wantErr) + } + + e, ok := err.(error) + if !ok { + t.Fatalf("MustParse() doesn't panic with error got = %s panic = %v, wantErr %v", got, err, tt.wantErr) + } + + if !errors.Is(e, tt.wantErr) { + t.Fatalf("MustParse() got = %s panic = %v, wantErr %v", got, err, tt.wantErr) + } + + var wantParseError *ParseError + if !errors.As(e, &wantParseError) { + t.Fatalf("MustParse() panic = %#v, wantErr %#v", err, wantParseError) + } + + if wantParseError.original != tt.v { + t.Errorf("MustParse() error.original = %v, want %v", wantParseError.original, tt.v) + } + }() + + got = MustParse(tt.v) + }) + } +} + func TestVersion_Compare(t *testing.T) { t.Parallel() @@ -236,21 +308,21 @@ func TestVersion_Compare(t *testing.T) { t.Run(tt.v+"<=>"+tt.w, func(t *testing.T) { t.Parallel() - v, err := NewVersion(tt.v) + v, err := Parse(tt.v) if err != nil { - t.Fatalf("NewVersion(%q) error = %v, wantErr %v", tt.v, err, nil) + t.Fatalf("Parse(%q) error = %v, wantErr %v", tt.v, err, nil) } - w, err := NewVersion(tt.w) + w, err := Parse(tt.w) if err != nil { - t.Fatalf("NewVersion(%q) error = %v, wantErr %v", tt.w, err, nil) + t.Fatalf("Parse(%q) error = %v, wantErr %v", tt.w, err, nil) } if got := v.Compare(w); got != tt.want { - t.Errorf("%q.Compare(%q) = %v, want %v", tt.v, tt.w, got, tt.want) + t.Errorf("%q.compare(%q) = %v, want %v", tt.v, tt.w, got, tt.want) } if got := w.Compare(v); got != -1*tt.want { - t.Errorf("%q.Compare(%q) = %v, want %v", tt.w, tt.v, got, tt.want) + t.Errorf("%q.compare(%q) = %v, want %v", tt.w, tt.v, got, tt.want) } }) } @@ -311,9 +383,9 @@ func TestVersion_String(t *testing.T) { t.Run(tt.v, func(t *testing.T) { t.Parallel() - v, err := NewVersion(tt.v) + v, err := Parse(tt.v) if err != nil { - t.Fatalf("NewVersion(%q) error = %v, wantErr %v", tt.v, err, nil) + t.Fatalf("Parse(%q) error = %v, wantErr %v", tt.v, err, nil) } if got := v.String(); got != tt.want { @@ -378,9 +450,9 @@ func TestVersion_Short(t *testing.T) { t.Run(tt.v, func(t *testing.T) { t.Parallel() - v, err := NewVersion(tt.v) + v, err := Parse(tt.v) if err != nil { - t.Fatalf("NewVersion(%q) error = %v, wantErr %v", tt.v, err, nil) + t.Fatalf("Parse(%q) error = %v, wantErr %v", tt.v, err, nil) } if got := v.Short(); got != tt.want { @@ -389,3 +461,21 @@ func TestVersion_Short(t *testing.T) { }) } } + +func TestVersion_zero(t *testing.T) { + t.Parallel() + + v := Version{} + + if got := v.String(); got != "0.0.0.0" { + t.Errorf("Version{}.String() = %q, want %q", got, "0.0.0.0") + } + + if got := v.Short(); got != "0" { + t.Errorf("Version{}.Short() = %q, want %q", got, "0") + } + + if got := v.Original(); got != "" { + t.Errorf("Version{}.Original() = %q, want %q", got, "") + } +}