k8s.io/apimachinery@v0.29.2/pkg/util/version/version_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package version 18 19 import ( 20 "fmt" 21 "reflect" 22 "testing" 23 ) 24 25 type testItem struct { 26 version string 27 unparsed string 28 equalsPrev bool 29 } 30 31 func testOne(v *Version, item, prev testItem) error { 32 str := v.String() 33 if item.unparsed == "" { 34 if str != item.version { 35 return fmt.Errorf("bad round-trip: %q -> %q", item.version, str) 36 } 37 } else { 38 if str != item.unparsed { 39 return fmt.Errorf("bad unparse: %q -> %q, expected %q", item.version, str, item.unparsed) 40 } 41 } 42 43 if prev.version != "" { 44 cmp, err := v.Compare(prev.version) 45 if err != nil { 46 return fmt.Errorf("unexpected parse error: %v", err) 47 } 48 rv, err := parse(prev.version, v.semver) 49 if err != nil { 50 return fmt.Errorf("unexpected parse error: %v", err) 51 } 52 rcmp, err := rv.Compare(item.version) 53 if err != nil { 54 return fmt.Errorf("unexpected parse error: %v", err) 55 } 56 57 switch { 58 case cmp == -1: 59 return fmt.Errorf("unexpected ordering %q < %q", item.version, prev.version) 60 case cmp == 0 && !item.equalsPrev: 61 return fmt.Errorf("unexpected comparison %q == %q", item.version, prev.version) 62 case cmp == 1 && item.equalsPrev: 63 return fmt.Errorf("unexpected comparison %q != %q", item.version, prev.version) 64 case cmp != -rcmp: 65 return fmt.Errorf("unexpected reverse comparison %q <=> %q %v %v %v %v", item.version, prev.version, cmp, rcmp, v.Components(), rv.Components()) 66 } 67 } 68 69 return nil 70 } 71 72 func TestSemanticVersions(t *testing.T) { 73 tests := []testItem{ 74 // This is every version string that appears in the 2.0 semver spec, 75 // sorted in strictly increasing order except as noted. 76 {version: "0.1.0"}, 77 {version: "1.0.0-0.3.7"}, 78 {version: "1.0.0-alpha"}, 79 {version: "1.0.0-alpha+001", equalsPrev: true}, 80 {version: "1.0.0-alpha.1"}, 81 {version: "1.0.0-alpha.beta"}, 82 {version: "1.0.0-beta"}, 83 {version: "1.0.0-beta+exp.sha.5114f85", equalsPrev: true}, 84 {version: "1.0.0-beta.2"}, 85 {version: "1.0.0-beta.11"}, 86 {version: "1.0.0-rc.1"}, 87 {version: "1.0.0-x.7.z.92"}, 88 {version: "1.0.0"}, 89 {version: "1.0.0+20130313144700", equalsPrev: true}, 90 {version: "1.8.0-alpha.3"}, 91 {version: "1.8.0-alpha.3.673+73326ef01d2d7c"}, 92 {version: "1.9.0"}, 93 {version: "1.10.0"}, 94 {version: "1.11.0"}, 95 {version: "2.0.0"}, 96 {version: "2.1.0"}, 97 {version: "2.1.1"}, 98 {version: "42.0.0"}, 99 100 // We also allow whitespace and "v" prefix 101 {version: " 42.0.0", unparsed: "42.0.0", equalsPrev: true}, 102 {version: "\t42.0.0 ", unparsed: "42.0.0", equalsPrev: true}, 103 {version: "43.0.0-1", unparsed: "43.0.0-1"}, 104 {version: "43.0.0-1 ", unparsed: "43.0.0-1", equalsPrev: true}, 105 {version: "v43.0.0-1", unparsed: "43.0.0-1", equalsPrev: true}, 106 {version: " v43.0.0", unparsed: "43.0.0"}, 107 {version: " 43.0.0 ", unparsed: "43.0.0", equalsPrev: true}, 108 } 109 110 var prev testItem 111 for _, item := range tests { 112 v, err := ParseSemantic(item.version) 113 if err != nil { 114 t.Errorf("unexpected parse error: %v", err) 115 continue 116 } 117 err = testOne(v, item, prev) 118 if err != nil { 119 t.Errorf("%v", err) 120 } 121 prev = item 122 } 123 } 124 125 func TestBadSemanticVersions(t *testing.T) { 126 tests := []string{ 127 // "MUST take the form X.Y.Z" 128 "1", 129 "1.2", 130 "1.2.3.4", 131 ".2.3", 132 "1..3", 133 "1.2.", 134 "", 135 "..", 136 // "where X, Y, and Z are non-negative integers" 137 "-1.2.3", 138 "1.-2.3", 139 "1.2.-3", 140 "1a.2.3", 141 "1.2a.3", 142 "1.2.3a", 143 "a1.2.3", 144 "a.b.c", 145 "1 .2.3", 146 "1. 2.3", 147 // "and MUST NOT contain leading zeroes." 148 "01.2.3", 149 "1.02.3", 150 "1.2.03", 151 // "[pre-release] identifiers MUST comprise only ASCII alphanumerics and hyphen" 152 "1.2.3-/", 153 // "[pre-release] identifiers MUST NOT be empty" 154 "1.2.3-", 155 "1.2.3-.", 156 "1.2.3-foo.", 157 "1.2.3-.foo", 158 // "Numeric [pre-release] identifiers MUST NOT include leading zeroes" 159 "1.2.3-01", 160 // "[build metadata] identifiers MUST comprise only ASCII alphanumerics and hyphen" 161 "1.2.3+/", 162 // "[build metadata] identifiers MUST NOT be empty" 163 "1.2.3+", 164 "1.2.3+.", 165 "1.2.3+foo.", 166 "1.2.3+.foo", 167 168 // whitespace/"v"-prefix checks 169 "v 1.2.3", 170 "vv1.2.3", 171 } 172 173 for i := range tests { 174 _, err := ParseSemantic(tests[i]) 175 if err == nil { 176 t.Errorf("unexpected success parsing invalid semver %q", tests[i]) 177 } 178 } 179 } 180 181 func TestGenericVersions(t *testing.T) { 182 tests := []testItem{ 183 // This is all of the strings from TestSemanticVersions, plus some strings 184 // from TestBadSemanticVersions that should parse as generic versions, 185 // plus some additional strings. 186 {version: "0.1.0", unparsed: "0.1.0"}, 187 {version: "1.0.0-0.3.7", unparsed: "1.0.0"}, 188 {version: "1.0.0-alpha", unparsed: "1.0.0", equalsPrev: true}, 189 {version: "1.0.0-alpha+001", unparsed: "1.0.0", equalsPrev: true}, 190 {version: "1.0.0-alpha.1", unparsed: "1.0.0", equalsPrev: true}, 191 {version: "1.0.0-alpha.beta", unparsed: "1.0.0", equalsPrev: true}, 192 {version: "1.0.0.beta", unparsed: "1.0.0", equalsPrev: true}, 193 {version: "1.0.0-beta+exp.sha.5114f85", unparsed: "1.0.0", equalsPrev: true}, 194 {version: "1.0.0.beta.2", unparsed: "1.0.0", equalsPrev: true}, 195 {version: "1.0.0.beta.11", unparsed: "1.0.0", equalsPrev: true}, 196 {version: "1.0.0.rc.1", unparsed: "1.0.0", equalsPrev: true}, 197 {version: "1.0.0-x.7.z.92", unparsed: "1.0.0", equalsPrev: true}, 198 {version: "1.0.0", unparsed: "1.0.0", equalsPrev: true}, 199 {version: "1.0.0+20130313144700", unparsed: "1.0.0", equalsPrev: true}, 200 {version: "1.2", unparsed: "1.2"}, 201 {version: "1.2a.3", unparsed: "1.2", equalsPrev: true}, 202 {version: "1.2.3", unparsed: "1.2.3"}, 203 {version: "1.2.3.0", unparsed: "1.2.3.0", equalsPrev: true}, 204 {version: "1.2.3a", unparsed: "1.2.3", equalsPrev: true}, 205 {version: "1.2.3-foo.", unparsed: "1.2.3", equalsPrev: true}, 206 {version: "1.2.3-.foo", unparsed: "1.2.3", equalsPrev: true}, 207 {version: "1.2.3-01", unparsed: "1.2.3", equalsPrev: true}, 208 {version: "1.2.3+", unparsed: "1.2.3", equalsPrev: true}, 209 {version: "1.2.3+foo.", unparsed: "1.2.3", equalsPrev: true}, 210 {version: "1.2.3+.foo", unparsed: "1.2.3", equalsPrev: true}, 211 {version: "1.02.3", unparsed: "1.2.3", equalsPrev: true}, 212 {version: "1.2.03", unparsed: "1.2.3", equalsPrev: true}, 213 {version: "1.2.003", unparsed: "1.2.3", equalsPrev: true}, 214 {version: "1.2.3.4", unparsed: "1.2.3.4"}, 215 {version: "1.2.3.4b3", unparsed: "1.2.3.4", equalsPrev: true}, 216 {version: "1.2.3.4.5", unparsed: "1.2.3.4.5"}, 217 {version: "1.9.0", unparsed: "1.9.0"}, 218 {version: "1.9.0.0.0.0.0.0", unparsed: "1.9.0.0.0.0.0.0", equalsPrev: true}, 219 {version: "1.10.0", unparsed: "1.10.0"}, 220 {version: "1.11.0", unparsed: "1.11.0"}, 221 {version: "1.11.0.0.5", unparsed: "1.11.0.0.5"}, 222 {version: "2.0.0", unparsed: "2.0.0"}, 223 {version: "2.1.0", unparsed: "2.1.0"}, 224 {version: "2.1.1", unparsed: "2.1.1"}, 225 {version: "42.0.0", unparsed: "42.0.0"}, 226 {version: " 42.0.0", unparsed: "42.0.0", equalsPrev: true}, 227 {version: "\t42.0.0 ", unparsed: "42.0.0", equalsPrev: true}, 228 {version: "42.0.0-1", unparsed: "42.0.0", equalsPrev: true}, 229 {version: "42.0.0-1 ", unparsed: "42.0.0", equalsPrev: true}, 230 {version: "v42.0.0-1", unparsed: "42.0.0", equalsPrev: true}, 231 {version: " v43.0.0", unparsed: "43.0.0"}, 232 {version: " 43.0.0 ", unparsed: "43.0.0", equalsPrev: true}, 233 } 234 235 var prev testItem 236 for _, item := range tests { 237 v, err := ParseGeneric(item.version) 238 if err != nil { 239 t.Errorf("unexpected parse error: %v", err) 240 continue 241 } 242 err = testOne(v, item, prev) 243 if err != nil { 244 t.Errorf("%v", err) 245 } 246 prev = item 247 } 248 } 249 250 func TestBadGenericVersions(t *testing.T) { 251 tests := []string{ 252 "1", 253 "01.2.3", 254 "-1.2.3", 255 "1.-2.3", 256 ".2.3", 257 "1..3", 258 "1a.2.3", 259 "a1.2.3", 260 "1 .2.3", 261 "1. 2.3", 262 "1.bob", 263 "bob", 264 "v 1.2.3", 265 "vv1.2.3", 266 "", 267 ".", 268 } 269 270 for i := range tests { 271 _, err := ParseGeneric(tests[i]) 272 if err == nil { 273 t.Errorf("unexpected success parsing invalid version %q", tests[i]) 274 } 275 } 276 } 277 278 func TestComponents(t *testing.T) { 279 280 var tests = []struct { 281 version string 282 semver bool 283 expectedComponents []uint 284 expectedMajor uint 285 expectedMinor uint 286 expectedPatch uint 287 expectedPreRelease string 288 expectedBuildMetadata string 289 }{ 290 { 291 version: "1.0.2", 292 semver: true, 293 expectedComponents: []uint{1, 0, 2}, 294 expectedMajor: 1, 295 expectedMinor: 0, 296 expectedPatch: 2, 297 }, 298 { 299 version: "1.0.2-alpha+001", 300 semver: true, 301 expectedComponents: []uint{1, 0, 2}, 302 expectedMajor: 1, 303 expectedMinor: 0, 304 expectedPatch: 2, 305 expectedPreRelease: "alpha", 306 expectedBuildMetadata: "001", 307 }, 308 { 309 version: "1.2", 310 semver: false, 311 expectedComponents: []uint{1, 2}, 312 expectedMajor: 1, 313 expectedMinor: 2, 314 }, 315 { 316 version: "1.0.2-beta+exp.sha.5114f85", 317 semver: true, 318 expectedComponents: []uint{1, 0, 2}, 319 expectedMajor: 1, 320 expectedMinor: 0, 321 expectedPatch: 2, 322 expectedPreRelease: "beta", 323 expectedBuildMetadata: "exp.sha.5114f85", 324 }, 325 } 326 327 for _, test := range tests { 328 version, _ := parse(test.version, test.semver) 329 if !reflect.DeepEqual(test.expectedComponents, version.Components()) { 330 t.Error("parse returned un'expected components") 331 } 332 if test.expectedMajor != version.Major() { 333 t.Errorf("parse returned version.Major %d, expected %d", test.expectedMajor, version.Major()) 334 } 335 if test.expectedMinor != version.Minor() { 336 t.Errorf("parse returned version.Minor %d, expected %d", test.expectedMinor, version.Minor()) 337 } 338 if test.expectedPatch != version.Patch() { 339 t.Errorf("parse returned version.Patch %d, expected %d", test.expectedPatch, version.Patch()) 340 } 341 if test.expectedPreRelease != version.PreRelease() { 342 t.Errorf("parse returned version.PreRelease %s, expected %s", test.expectedPreRelease, version.PreRelease()) 343 } 344 if test.expectedBuildMetadata != version.BuildMetadata() { 345 t.Errorf("parse returned version.BuildMetadata %s, expected %s", test.expectedBuildMetadata, version.BuildMetadata()) 346 } 347 } 348 } 349 350 func TestHighestSupportedVersion(t *testing.T) { 351 testCases := []struct { 352 versions []string 353 expectedHighestSupportedVersion string 354 shouldFail bool 355 }{ 356 { 357 versions: []string{"v1.0.0"}, 358 expectedHighestSupportedVersion: "1.0.0", 359 shouldFail: false, 360 }, 361 { 362 versions: []string{"0.3.0"}, 363 shouldFail: true, 364 }, 365 { 366 versions: []string{"0.2.0"}, 367 shouldFail: true, 368 }, 369 { 370 versions: []string{"1.0.0"}, 371 expectedHighestSupportedVersion: "1.0.0", 372 shouldFail: false, 373 }, 374 { 375 versions: []string{"v0.3.0"}, 376 shouldFail: true, 377 }, 378 { 379 versions: []string{"v0.2.0"}, 380 shouldFail: true, 381 }, 382 { 383 versions: []string{"0.2.0", "v0.3.0"}, 384 shouldFail: true, 385 }, 386 { 387 versions: []string{"0.2.0", "v1.0.0"}, 388 expectedHighestSupportedVersion: "1.0.0", 389 shouldFail: false, 390 }, 391 { 392 versions: []string{"0.2.0", "v1.2.3"}, 393 expectedHighestSupportedVersion: "1.2.3", 394 shouldFail: false, 395 }, 396 { 397 versions: []string{"v1.2.3", "v0.3.0"}, 398 expectedHighestSupportedVersion: "1.2.3", 399 shouldFail: false, 400 }, 401 { 402 versions: []string{"v1.2.3", "v0.3.0", "2.0.1"}, 403 expectedHighestSupportedVersion: "1.2.3", 404 shouldFail: false, 405 }, 406 { 407 versions: []string{"v1.2.3", "4.9.12", "v0.3.0", "2.0.1"}, 408 expectedHighestSupportedVersion: "1.2.3", 409 shouldFail: false, 410 }, 411 { 412 versions: []string{"4.9.12", "2.0.1"}, 413 expectedHighestSupportedVersion: "", 414 shouldFail: true, 415 }, 416 { 417 versions: []string{"v1.2.3", "boo", "v0.3.0", "2.0.1"}, 418 expectedHighestSupportedVersion: "1.2.3", 419 shouldFail: false, 420 }, 421 { 422 versions: []string{}, 423 expectedHighestSupportedVersion: "", 424 shouldFail: true, 425 }, 426 { 427 versions: []string{"var", "boo", "foo"}, 428 expectedHighestSupportedVersion: "", 429 shouldFail: true, 430 }, 431 } 432 433 for _, tc := range testCases { 434 // Arrange & Act 435 actual, err := HighestSupportedVersion(tc.versions) 436 437 // Assert 438 if tc.shouldFail && err == nil { 439 t.Fatalf("expecting highestSupportedVersion to fail, but got nil error for testcase: %#v", tc) 440 } 441 if !tc.shouldFail && err != nil { 442 t.Fatalf("unexpected error during ValidatePlugin for testcase: %#v\r\n err:%v", tc, err) 443 } 444 if tc.expectedHighestSupportedVersion != "" { 445 result, err := actual.Compare(tc.expectedHighestSupportedVersion) 446 if err != nil { 447 t.Fatalf("comparison failed with %v for testcase %#v", err, tc) 448 } 449 if result != 0 { 450 t.Fatalf("expectedHighestSupportedVersion %v, but got %v for tc: %#v", tc.expectedHighestSupportedVersion, actual, tc) 451 } 452 } 453 } 454 }