├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── version.go └── version_test.go /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | jobs: 8 | unittest: 9 | name: Unit Test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out code into the Go module directory 13 | uses: actions/checkout@v3 14 | - name: Set up Go 15 | uses: actions/setup-go@v3 16 | with: 17 | go-version: oldstable 18 | - name: Run unit tests 19 | run: go test -v ./... -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Teppei Fukuda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-deb-version 2 | 3 | [![Build Status](https://travis-ci.org/knqyf263/go-deb-version.svg?branch=master)](https://travis-ci.org/knqyf263/go-deb-version) 4 | [![Coverage Status](https://coveralls.io/repos/github/knqyf263/go-deb-version/badge.svg)](https://coveralls.io/github/knqyf263/go-deb-version) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/knqyf263/go-deb-version)](https://goreportcard.com/report/github.com/knqyf263/go-deb-version) 6 | [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/knqyf263/go-deb-version/blob/master/LICENSE) 7 | 8 | A Go library for parsing package versions 9 | 10 | go-deb-version is a library for parsing and comparing versions 11 | 12 | Versions used with go-deb-version must follow [deb-version](http://man.he.net/man5/deb-version) (ex. 2:6.0-9ubuntu1) 13 | The implementation is based on [Debian Policy Manual](https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version) 14 | 15 | OS: Debian, Ubnutu 16 | 17 | 18 | # Installation and Usage 19 | 20 | Installation can be done with a normal go get: 21 | 22 | ``` 23 | $ go get github.com/knqyf263/go-deb-version 24 | ``` 25 | 26 | ## Version Parsing and Comparison 27 | 28 | ``` 29 | import "github.com/knqyf263/go-deb-version" 30 | 31 | v1, err := version.NewVersion("2:6.0-9") 32 | v2, err := version.NewVersion("2:6.0-9ubuntu1") 33 | 34 | // Comparison example. There is also GreaterThan, Equal. 35 | if v1.LessThan(v2) { 36 | fmt.Printf("%s is less than %s", v1, v2) 37 | } 38 | ``` 39 | 40 | ## Version Sorting 41 | 42 | ``` 43 | raw := []string{"7.4.052-1ubuntu3.1", "7.4.052-1ubuntu3", "7.1-022+1ubuntu1", "7.1.291-1", "7.3.000+hg~ee53a39d5896-1"} 44 | vs := make([]version.Version, len(raw)) 45 | for i, r := range raw { 46 | v, _ := version.NewVersion(r) 47 | vs[i] = v 48 | } 49 | 50 | sort.Slice(vs, func(i, j int) bool { 51 | return vs[i].LessThan(vs[j]) 52 | }) 53 | ``` 54 | 55 | # Contribute 56 | 57 | 1. fork a repository: github.com/knqyf263/go-deb-version to github.com/you/repo 58 | 2. get original code: `go get github.com/knqyf263/go-deb-version` 59 | 3. work on original code 60 | 4. add remote to your repo: git remote add myfork https://github.com/you/repo.git 61 | 5. push your changes: git push myfork 62 | 6. create a new Pull Request 63 | 64 | - see [GitHub and Go: forking, pull requests, and go-getting](http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html) 65 | 66 | ---- 67 | 68 | # License 69 | MIT 70 | 71 | # Author 72 | Teppei Fukuda 73 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/knqyf263/go-deb-version 2 | 3 | go 1.22.9 4 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | "unicode" 11 | ) 12 | 13 | type defaultNumSlice []int 14 | 15 | // get function returns 0, if the slice does not have the specified index. 16 | func (n defaultNumSlice) get(i int) int { 17 | if len(n) > i { 18 | return n[i] 19 | } 20 | return 0 21 | } 22 | 23 | type defaultStringSlice []string 24 | 25 | // get function returns "", if the slice does not have the specified index. 26 | func (s defaultStringSlice) get(i int) string { 27 | if len(s) > i { 28 | return s[i] 29 | } 30 | return "" 31 | } 32 | 33 | // Version represents a package version (http://man.he.net/man5/deb-version). 34 | type Version struct { 35 | epoch int 36 | upstreamVersion string 37 | debianRevision string 38 | } 39 | 40 | var ( 41 | digitRegexp = regexp.MustCompile(`[0-9]+`) 42 | nonDigitRegexp = regexp.MustCompile(`[^0-9]+`) 43 | ) 44 | 45 | // NewVersion returns a parsed version 46 | func NewVersion(ver string) (version Version, err error) { 47 | // Trim space 48 | ver = strings.TrimSpace(ver) 49 | 50 | // Parse epoch 51 | splitted := strings.SplitN(ver, ":", 2) 52 | if len(splitted) == 1 { 53 | version.epoch = 0 54 | ver = splitted[0] 55 | } else { 56 | version.epoch, err = strconv.Atoi(splitted[0]) 57 | if err != nil { 58 | return Version{}, fmt.Errorf("epoch parse error: %v", err) 59 | } 60 | 61 | if version.epoch < 0 { 62 | return Version{}, errors.New("epoch is negative") 63 | } 64 | ver = splitted[1] 65 | } 66 | 67 | // Parse upstream_version and debian_revision 68 | index := strings.LastIndex(ver, "-") 69 | if index >= 0 { 70 | version.upstreamVersion = ver[:index] 71 | version.debianRevision = ver[index+1:] 72 | 73 | } else { 74 | version.upstreamVersion = ver 75 | } 76 | 77 | // Verify upstream_version is valid 78 | err = verifyUpstreamVersion(version.upstreamVersion) 79 | if err != nil { 80 | return Version{}, err 81 | } 82 | 83 | // Verify debian_revision is valid 84 | err = verifyDebianRevision(version.debianRevision) 85 | if err != nil { 86 | return Version{}, err 87 | } 88 | 89 | return version, nil 90 | } 91 | 92 | func verifyUpstreamVersion(str string) error { 93 | if len(str) == 0 { 94 | return errors.New("upstream_version is empty") 95 | } 96 | 97 | // The upstream-version should start with a digit 98 | if !unicode.IsDigit(rune(str[0])) { 99 | return errors.New("upstream_version must start with digit") 100 | } 101 | 102 | // The upstream-version may contain only alphanumerics("A-Za-z0-9") and the characters .+-:~ 103 | allowedSymbols := ".-+~:_" 104 | for _, s := range str { 105 | if !unicode.IsDigit(s) && !unicode.IsLetter(s) && !strings.ContainsRune(allowedSymbols, s) { 106 | return errors.New("upstream_version includes invalid character") 107 | } 108 | } 109 | return nil 110 | } 111 | 112 | func verifyDebianRevision(str string) error { 113 | // The debian-revision may contain only alphanumerics and the characters +.~ 114 | allowedSymbols := "+.~_" 115 | for _, s := range str { 116 | if !unicode.IsDigit(s) && !unicode.IsLetter(s) && !strings.ContainsRune(allowedSymbols, s) { 117 | return errors.New("debian_revision includes invalid character") 118 | } 119 | } 120 | return nil 121 | } 122 | 123 | // Valid validates the version 124 | func Valid(ver string) bool { 125 | _, err := NewVersion(ver) 126 | return err == nil 127 | } 128 | 129 | // Equal returns whether this version is equal with another version. 130 | func (v1 *Version) Equal(v2 Version) bool { 131 | return v1.Compare(v2) == 0 132 | } 133 | 134 | // GreaterThan returns whether this version is greater than another version. 135 | func (v1 *Version) GreaterThan(v2 Version) bool { 136 | return v1.Compare(v2) > 0 137 | } 138 | 139 | // LessThan returns whether this version is less than another version. 140 | func (v1 *Version) LessThan(v2 Version) bool { 141 | return v1.Compare(v2) < 0 142 | } 143 | 144 | // Compare returns an integer comparing two version according to deb-version. 145 | // The result will be 0 if v1==v2, -1 if v1 < v2, and +1 if v1 > v2. 146 | func (v1 *Version) Compare(v2 Version) int { 147 | // Equal 148 | if reflect.DeepEqual(v1, v2) { 149 | return 0 150 | } 151 | 152 | // Compare epochs 153 | if v1.epoch > v2.epoch { 154 | return 1 155 | } else if v1.epoch < v2.epoch { 156 | return -1 157 | } 158 | 159 | // Compare version 160 | ret := compare(v1.upstreamVersion, v2.upstreamVersion) 161 | if ret != 0 { 162 | return ret 163 | } 164 | 165 | //Compare debian_revision 166 | return compare(v1.debianRevision, v2.debianRevision) 167 | } 168 | 169 | // String returns the full version string 170 | func (v1 *Version) String() string { 171 | version := "" 172 | if v1.epoch > 0 { 173 | version += fmt.Sprintf("%d:", v1.epoch) 174 | } 175 | version += v1.upstreamVersion 176 | 177 | if v1.debianRevision != "" { 178 | version += fmt.Sprintf("-%s", v1.debianRevision) 179 | 180 | } 181 | return version 182 | } 183 | 184 | func (v1 *Version) Epoch() int { 185 | return v1.epoch 186 | } 187 | 188 | func (v1 *Version) Version() string { 189 | return v1.upstreamVersion 190 | } 191 | 192 | func (v1 *Version) Revision() string { 193 | return v1.debianRevision 194 | } 195 | 196 | func compare(v1, v2 string) int { 197 | // Equal 198 | if v1 == v2 { 199 | return 0 200 | } 201 | 202 | // Extract digit strings and non-digit strings 203 | numbers1, strings1 := extract(v1) 204 | numbers2, strings2 := extract(v2) 205 | 206 | if len(v1) > 0 && unicode.IsDigit(rune(v1[0])) { 207 | strings1 = append([]string{""}, strings1...) 208 | } 209 | if len(v2) > 0 && unicode.IsDigit(rune(v2[0])) { 210 | strings2 = append([]string{""}, strings2...) 211 | } 212 | 213 | index := max(len(numbers1), len(numbers2), len(strings1), len(strings2)) 214 | for i := 0; i < index; i++ { 215 | // Compare non-digit strings 216 | diff := compareString(strings1.get(i), strings2.get(i)) 217 | if diff != 0 { 218 | return diff 219 | } 220 | 221 | // Compare digit strings 222 | diff = numbers1.get(i) - numbers2.get(i) 223 | if diff != 0 { 224 | return diff 225 | } 226 | } 227 | 228 | return 0 229 | } 230 | 231 | func compareString(s1, s2 string) int { 232 | if s1 == s2 { 233 | return 0 234 | } 235 | 236 | index := max(len(s1), len(s2)) 237 | for i := 0; i < index; i++ { 238 | a := 0 239 | if i < len(s1) { 240 | a = order(rune(s1[i])) 241 | } 242 | 243 | b := 0 244 | if i < len(s2) { 245 | b = order(rune(s2[i])) 246 | } 247 | 248 | if a != b { 249 | return a - b 250 | } 251 | } 252 | return 0 253 | } 254 | 255 | // order function returns the number corresponding to rune 256 | func order(r rune) int { 257 | // all the letters sort earlier than all the non-letters 258 | if unicode.IsLetter(r) { 259 | return int(r) 260 | } 261 | 262 | // a tilde sorts before anything 263 | if r == '~' { 264 | return -1 265 | } 266 | 267 | return int(r) + 256 268 | } 269 | 270 | func extract(version string) (defaultNumSlice, defaultStringSlice) { 271 | numbers := digitRegexp.FindAllString(version, -1) 272 | 273 | var dnum defaultNumSlice 274 | for _, number := range numbers { 275 | n, _ := strconv.Atoi(number) 276 | dnum = append(dnum, n) 277 | } 278 | 279 | s := nonDigitRegexp.FindAllString(version, -1) 280 | 281 | return dnum, defaultStringSlice(s) 282 | 283 | } 284 | -------------------------------------------------------------------------------- /version_test.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestVersion(t *testing.T) { 9 | cases := []struct { 10 | v1 string 11 | v2 string 12 | expected bool // If true, v1 less than v2 13 | }{ 14 | // RedHat 15 | {"7.4.629-3", "7.4.629-5", true}, 16 | {"7.4.622-1", "7.4.629-1", true}, 17 | {"6.0-4.el6.x86_64", "6.0-5.el6.x86_64", true}, 18 | {"6.0-4.el6.x86_64", "6.1-3.el6.x86_64", true}, 19 | {"7.0-4.el6.x86_64", "6.1-3.el6.x86_64", false}, 20 | // Debian 21 | {"2:7.4.052-1ubuntu3", "2:7.4.052-1ubuntu3.1", true}, 22 | {"2:7.4.052-1ubuntu2", "2:7.4.052-1ubuntu3", true}, 23 | {"2:7.4.052-1", "2:7.4.052-1ubuntu3", true}, 24 | {"2:7.4.052", "2:7.4.052-1", true}, 25 | {"1:7.4.052", "2:7.4.052", true}, 26 | {"1:7.4.052", "7.4.052", false}, 27 | {"2:7.4.052-1ubuntu3.2", "2:7.4.052-1ubuntu3.1", false}, 28 | } 29 | for _, tc := range cases { 30 | v1, _ := NewVersion(tc.v1) 31 | v2, _ := NewVersion(tc.v2) 32 | actual := v1.LessThan(v2) 33 | if actual != tc.expected { 34 | t.Fatalf("v1: %v\nv2: %v\nexpected: %v\nactual: %v", 35 | tc.v1, tc.v2, tc.expected, actual) 36 | } 37 | } 38 | 39 | } 40 | 41 | func TestNewVersion(t *testing.T) { 42 | cases := []struct { 43 | version string 44 | expected Version 45 | err bool 46 | }{ 47 | {"1.2.3", Version{0, "1.2.3", ""}, false}, 48 | {"1:1.2.3", Version{1, "1.2.3", ""}, false}, 49 | {"A:1.2.3", Version{}, true}, 50 | {"-1:1.2.3", Version{}, true}, 51 | {"6.0-4.el6.x86_64", Version{0, "6.0", "4.el6.x86_64"}, false}, 52 | {"6.0-9ubuntu1.5", Version{0, "6.0", "9ubuntu1.5"}, false}, 53 | {"2:7.4.052-1ubuntu3.1", Version{2, "7.4.052", "1ubuntu3.1"}, false}, 54 | {"2:-1ubuntu3.1", Version{}, true}, 55 | {"2:A7.4.052-1ubuntu3.1", Version{}, true}, 56 | {"2:7.4.!052-1ubuntu3.1", Version{}, true}, 57 | {"7.4.052-!1ubuntu3.1", Version{}, true}, 58 | } 59 | 60 | for _, tc := range cases { 61 | actual, err := NewVersion(tc.version) 62 | if tc.err && err == nil { 63 | t.Fatalf("expected error for version: %s", tc.version) 64 | } else if !tc.err && err != nil { 65 | t.Fatalf("unexpected error for version %s: %v", tc.version, err) 66 | } else if !tc.err && err == nil { 67 | if actual.epoch != tc.expected.epoch { 68 | t.Fatalf( 69 | "version: %s\nexpected: %v\nactual: %v", 70 | tc.version, tc.expected, actual) 71 | } 72 | } 73 | } 74 | } 75 | 76 | func TestValid(t *testing.T) { 77 | cases := []struct { 78 | version string 79 | expected bool 80 | }{ 81 | {"1.2.3", true}, 82 | {"1:1.2.3", true}, 83 | {"A:1.2.3", false}, 84 | {"-1:1.2.3", false}, 85 | {"6.0-4.el6.x86_64", true}, 86 | {"6.0-9ubuntu1.5", true}, 87 | {"2:7.4.052-1ubuntu3.1", true}, 88 | {"2:-1ubuntu3.1", false}, 89 | {"2:A7.4.052-1ubuntu3.1", false}, 90 | {"2:7.4.!052-1ubuntu3.1", false}, 91 | {"7.4.052-!1ubuntu3.1", false}, 92 | } 93 | 94 | for _, tc := range cases { 95 | actual := Valid(tc.version) 96 | if actual != tc.expected { 97 | t.Fatalf( 98 | "valid: %s\nexpected: %t\nactual: %t", 99 | tc.version, tc.expected, actual) 100 | } 101 | } 102 | } 103 | func TestEqual(t *testing.T) { 104 | cases := []struct { 105 | v1 Version 106 | v2 Version 107 | expected bool // If true, v1 = v2 108 | }{ 109 | {Version{2, "7.4.052", "1ubuntu3"}, Version{2, "7.4.052", "1ubuntu3.1"}, false}, 110 | {Version{2, "7.4.052", "1ubuntu2"}, Version{2, "7.4.052", "1ubuntu3"}, false}, 111 | {Version{2, "7.4.052", "1ubuntu3"}, Version{2, "7.4.052", "1ubuntu3"}, true}, 112 | {Version{2, "7.4.052", "1ubuntu1"}, Version{2, "7.4.052", "1"}, false}, 113 | {Version{upstreamVersion: "7.4.052"}, Version{upstreamVersion: "7.4.052"}, true}, 114 | {Version{upstreamVersion: "3.12", debianRevision: "1+deb9u1build0.16.4.1"}, Version{upstreamVersion: "3.12", debianRevision: "1+deb9u1build0.16.04.1"}, true}, 115 | } 116 | for _, tc := range cases { 117 | actual := tc.v1.Equal(tc.v2) 118 | if actual != tc.expected { 119 | t.Fatalf("v1: %v\nv2: %v\nexpected: %v\nactual: %v", 120 | tc.v1, tc.v2, tc.expected, actual) 121 | } 122 | } 123 | } 124 | 125 | func TestGreaterThan(t *testing.T) { 126 | cases := []struct { 127 | v1 Version 128 | v2 Version 129 | expected bool // If true, v1 greater than v2 130 | }{ 131 | {Version{2, "7.4.052", "1ubuntu3"}, Version{2, "7.4.052", "1ubuntu3.1"}, false}, 132 | {Version{2, "7.4.052", "1ubuntu2"}, Version{2, "7.4.052", "1ubuntu3"}, false}, 133 | {Version{2, "7.4.052", "1ubuntu1"}, Version{2, "7.4.052", "1"}, true}, 134 | {Version{2, "6.0", "9ubuntu1.4"}, Version{2, "6.0", "9ubuntu1.5"}, false}, 135 | { 136 | Version{upstreamVersion: "7.4.052"}, 137 | Version{upstreamVersion: "7.4.052", debianRevision: "1ubuntu2"}, 138 | false, 139 | }, 140 | { 141 | Version{upstreamVersion: "7.4.052"}, 142 | Version{epoch: 2, upstreamVersion: "7.4.052"}, 143 | false, 144 | }, 145 | } 146 | for _, tc := range cases { 147 | actual := tc.v1.GreaterThan(tc.v2) 148 | if actual != tc.expected { 149 | t.Fatalf("v1: %v\nv2: %v\nexpected: %v\nactual: %v", 150 | tc.v1, tc.v2, tc.expected, actual) 151 | } 152 | } 153 | } 154 | 155 | func TestLessThan(t *testing.T) { 156 | cases := []struct { 157 | v1 Version 158 | v2 Version 159 | expected bool // If true, v1 less than v2 160 | }{ 161 | {Version{2, "7.4.052", "1ubuntu3"}, Version{2, "7.4.052", "1ubuntu3.1"}, true}, 162 | {Version{2, "7.4.052", "1ubuntu2"}, Version{2, "7.4.052", "1ubuntu3"}, true}, 163 | {Version{2, "7.4.052", "1ubuntu1"}, Version{2, "7.4.052", "1"}, false}, 164 | {Version{2, "6.0", "9ubuntu1.4"}, Version{2, "6.0", "9ubuntu1.5"}, true}, 165 | { 166 | Version{upstreamVersion: "7.4.052"}, 167 | Version{upstreamVersion: "7.4.052", debianRevision: "1ubuntu2"}, 168 | true, 169 | }, 170 | { 171 | Version{upstreamVersion: "1.9.1", debianRevision: "2"}, 172 | Version{upstreamVersion: "1.16", debianRevision: "1+deb8u1"}, 173 | true, 174 | }, 175 | { 176 | Version{upstreamVersion: "1.9", debianRevision: "2"}, 177 | Version{upstreamVersion: "1.9.1", debianRevision: "1+deb8u1"}, 178 | true, 179 | }, 180 | { 181 | Version{upstreamVersion: "7.4.052"}, 182 | Version{epoch: 2, upstreamVersion: "7.4.052"}, 183 | true, 184 | }, 185 | } 186 | for _, tc := range cases { 187 | actual := tc.v1.LessThan(tc.v2) 188 | if actual != tc.expected { 189 | t.Fatalf("v1: %v\nv2: %v\nexpected: %v\nactual: %v", 190 | tc.v1, tc.v2, tc.expected, actual) 191 | } 192 | } 193 | } 194 | 195 | func TestString(t *testing.T) { 196 | cases := []struct { 197 | v Version 198 | expected string 199 | }{ 200 | {Version{2, "7.4.052", "1ubuntu3"}, "2:7.4.052-1ubuntu3"}, 201 | {Version{2, "7.4.052", "1"}, "2:7.4.052-1"}, 202 | {Version{0, "7.4.052", "1"}, "7.4.052-1"}, 203 | {Version{upstreamVersion: "7.4.052", debianRevision: "1"}, "7.4.052-1"}, 204 | {Version{epoch: 1, upstreamVersion: "7.4.052"}, "1:7.4.052"}, 205 | {Version{upstreamVersion: "7.4.052"}, "7.4.052"}, 206 | } 207 | for _, tc := range cases { 208 | actual := tc.v.String() 209 | if actual != tc.expected { 210 | t.Fatalf("v: %v\n\nexpected: %v\nactual: %v", 211 | tc.v, tc.expected, actual) 212 | } 213 | } 214 | } 215 | 216 | func TestCompare(t *testing.T) { 217 | cases := []struct { 218 | v1 string 219 | v2 string 220 | negative bool // If true, v1 less than v2 221 | }{ 222 | {"6.4.052", "7.4.052", true}, 223 | {"6.4.052", "6.5.052", true}, 224 | {"6.4.052", "6.4.053", true}, 225 | {"1ubuntu1", "1ubuntu3.1", true}, 226 | {"1", "1ubuntu1", true}, 227 | {"7.4.027", "7.4.052", true}, 228 | } 229 | for _, tc := range cases { 230 | ret := compare(tc.v1, tc.v2) 231 | if tc.negative && ret > 0 { 232 | t.Fatalf("v1: %s\nv2: %s\nexpected: %v\nactual: %v", 233 | tc.v1, tc.v2, tc.negative, ret < 0) 234 | } 235 | } 236 | } 237 | 238 | func TestExtract(t *testing.T) { 239 | cases := []struct { 240 | version string 241 | expectedNum defaultNumSlice 242 | expectedStr defaultStringSlice 243 | }{ 244 | {"1.2.3", []int{1, 2, 3}, defaultStringSlice{".", "."}}, 245 | {"12.+34.~45", []int{12, 34, 45}, []string{".+", ".~"}}, 246 | {".+-:~123.45", []int{123, 45}, []string{".+-:~", "."}}, 247 | } 248 | for _, tc := range cases { 249 | n, s := extract(tc.version) 250 | if !reflect.DeepEqual(n, tc.expectedNum) { 251 | t.Fatalf("version: %s\nexpected: %v\nactual: %v", 252 | tc.version, tc.expectedNum, n) 253 | } 254 | if !reflect.DeepEqual(s, tc.expectedStr) { 255 | t.Fatalf("version: %s\nexpected: %v\nactual: %v", 256 | tc.version, tc.expectedStr, s) 257 | } 258 | } 259 | } 260 | 261 | func TestOrder(t *testing.T) { 262 | cases := []struct { 263 | r rune 264 | expected int 265 | }{ 266 | {'A', 65}, 267 | {'~', -1}, 268 | {'\a', 263}, 269 | } 270 | for _, tc := range cases { 271 | o := order(tc.r) 272 | if o != tc.expected { 273 | t.Fatalf("rune: %c\n\nexpected: %d\nactual: %d", 274 | tc.r, tc.expected, o) 275 | } 276 | } 277 | 278 | } 279 | 280 | func TestCompareString(t *testing.T) { 281 | cases := []struct { 282 | s1 string 283 | s2 string 284 | negative bool // If true, s1 less than s2 285 | }{ 286 | {"~~", "~~a", true}, 287 | {"~~a", "~", true}, 288 | {"~", "", true}, 289 | {"", "a", true}, 290 | {"a", ".", true}, 291 | } 292 | for _, tc := range cases { 293 | ret := compareString(tc.s1, tc.s2) 294 | if tc.negative && ret > 0 { 295 | t.Fatalf("s1: %s\ns2: %s\nexpected: %v\nactual: %v", 296 | tc.s1, tc.s2, tc.negative, ret < 0) 297 | } 298 | } 299 | } 300 | --------------------------------------------------------------------------------