1
16
17 package util
18
19 import (
20 "fmt"
21 "path"
22 "strings"
23 "testing"
24 "time"
25
26 "github.com/pkg/errors"
27
28 "k8s.io/kubernetes/cmd/kubeadm/app/constants"
29 )
30
31 func TestEmptyVersion(t *testing.T) {
32
33 ver, err := KubernetesReleaseVersion("")
34 if err == nil {
35 t.Error("KubernetesReleaseVersion returned successfully, but error expected")
36 }
37 if ver != "" {
38 t.Error("KubernetesReleaseVersion returned value, expected only error")
39 }
40 }
41
42 func TestValidVersion(t *testing.T) {
43 validVersions := []string{
44 "v1.3.0",
45 "v1.4.0-alpha.0",
46 "v1.4.5",
47 "v1.4.0-beta.0",
48 "v2.0.0",
49 "v1.6.0-alpha.0.536+d60d9f3269288f",
50 "v1.5.0-alpha.0.1078+1044b6822497da-pull",
51 "v1.5.0-alpha.1.822+49b9e32fad9f32-pull-gke-gci",
52 "v1.6.1+coreos.0",
53 "1.7.1",
54 }
55 for _, s := range validVersions {
56 t.Run(s, func(t *testing.T) {
57 ver, err := kubernetesReleaseVersion(s, errorFetcher)
58 t.Log("Valid: ", s, ver, err)
59 if err != nil {
60 t.Errorf("KubernetesReleaseVersion unexpected error for version %q: %v", s, err)
61 }
62 if ver != s && ver != "v"+s {
63 t.Errorf("KubernetesReleaseVersion should return same valid version string. %q != %q", s, ver)
64 }
65 })
66 }
67 }
68
69 func TestInvalidVersion(t *testing.T) {
70 invalidVersions := []string{
71 "v1.3",
72 "1.4",
73 "b1.4.0",
74 "c1.4.5+git",
75 "something1.2",
76 }
77 for _, s := range invalidVersions {
78 t.Run(s, func(t *testing.T) {
79 ver, err := kubernetesReleaseVersion(s, errorFetcher)
80 t.Log("Invalid: ", s, ver, err)
81 if err == nil {
82 t.Errorf("KubernetesReleaseVersion error expected for version %q, but returned successfully", s)
83 }
84 if ver != "" {
85 t.Errorf("KubernetesReleaseVersion should return empty string in case of error. Returned %q for version %q", ver, s)
86 }
87 })
88 }
89 }
90
91 func TestValidConvenientForUserVersion(t *testing.T) {
92 validVersions := []string{
93 "1.4.0",
94 "1.4.5+git",
95 "1.6.1_coreos.0",
96 }
97 for _, s := range validVersions {
98 t.Run(s, func(t *testing.T) {
99 ver, err := kubernetesReleaseVersion(s, errorFetcher)
100 t.Log("Valid: ", s, ver, err)
101 if err != nil {
102 t.Errorf("KubernetesReleaseVersion unexpected error for version %q: %v", s, err)
103 }
104 if ver != "v"+s {
105 t.Errorf("KubernetesReleaseVersion should return semantic version string. %q vs. %q", s, ver)
106 }
107 })
108 }
109 }
110
111 func TestVersionFromNetwork(t *testing.T) {
112 type T struct {
113 Content string
114 Expected string
115 FetcherErrorExpected bool
116 ErrorExpected bool
117 }
118
119 currentVersion := normalizedBuildVersion(constants.CurrentKubernetesVersion.String())
120
121 cases := map[string]T{
122 "stable": {"stable-1", "v1.4.6", false, false},
123 "stable-1": {"v1.4.6", "v1.4.6", false, false},
124 "stable-1.3": {"v1.3.10", "v1.3.10", false, false},
125 "latest": {"v1.6.0-alpha.0", "v1.6.0-alpha.0", false, false},
126 "latest-1.3": {"v1.3.11-beta.0", "v1.3.11-beta.0", false, false},
127 "latest-1.5": {"", currentVersion, true, false},
128 "invalid-version": {"", "", false, true},
129 }
130
131 for k, v := range cases {
132 t.Run(k, func(t *testing.T) {
133
134 fileFetcher := func(url string, timeout time.Duration) (string, error) {
135 key := strings.TrimSuffix(path.Base(url), ".txt")
136 res, found := cases[key]
137 if found {
138 if v.FetcherErrorExpected {
139 return "error", errors.New("expected error")
140 }
141 return res.Content, nil
142 }
143 return "Unknown test case key!", errors.New("unknown test case key")
144 }
145
146 ver, err := kubernetesReleaseVersion(k, fileFetcher)
147 t.Logf("Key: %q. Result: %q, Error: %v", k, ver, err)
148 switch {
149 case err != nil && !v.ErrorExpected:
150 t.Errorf("KubernetesReleaseVersion: unexpected error for %q. Error: %+v", k, err)
151 case err == nil && v.ErrorExpected:
152 t.Errorf("KubernetesReleaseVersion: error expected for key %q, but result is %q", k, ver)
153 case ver != v.Expected:
154 t.Errorf("KubernetesReleaseVersion: unexpected result for key %q. Expected: %q Actual: %q", k, v.Expected, ver)
155 }
156 })
157 }
158 }
159
160 func TestVersionToTag(t *testing.T) {
161 type T struct {
162 input string
163 expected string
164 }
165 cases := []T{
166
167 {"", ""},
168
169 {"v1.0.0", "v1.0.0"},
170
171 {"v10.1.2-alpha.1.100+0123456789abcdef+SOMETHING", "v10.1.2-alpha.1.100_0123456789abcdef_SOMETHING"},
172
173 {"v1,0!0+üñµ", "v1_0_0____"},
174 }
175
176 for _, tc := range cases {
177 t.Run(fmt.Sprintf("input:%s/expected:%s", tc.input, tc.expected), func(t *testing.T) {
178 tag := KubernetesVersionToImageTag(tc.input)
179 t.Logf("KubernetesVersionToImageTag: Input: %q. Result: %q. Expected: %q", tc.input, tag, tc.expected)
180 if tag != tc.expected {
181 t.Errorf("failed KubernetesVersionToImageTag: Input: %q. Result: %q. Expected: %q", tc.input, tag, tc.expected)
182 }
183 })
184 }
185 }
186
187 func TestSplitVersion(t *testing.T) {
188 type T struct {
189 input string
190 bucket string
191 label string
192 valid bool
193 }
194 cases := []T{
195
196 {"v1.7.0", "https://dl.k8s.io/release", "v1.7.0", true},
197 {"v1.8.0-alpha.2.1231+afabd012389d53a", "https://dl.k8s.io/release", "v1.8.0-alpha.2.1231+afabd012389d53a", true},
198 {"release/v1.7.0", "https://dl.k8s.io/release", "v1.7.0", true},
199 {"release/latest-1.7", "https://dl.k8s.io/release", "latest-1.7", true},
200
201 {"ci/latest", "https://storage.googleapis.com/k8s-release-dev/ci", "latest", true},
202 {"ci/latest-1.7", "https://storage.googleapis.com/k8s-release-dev/ci", "latest-1.7", true},
203
204 {"unknown-1", "https://dl.k8s.io/release", "unknown-1", true},
205
206 {"unknown/latest-1", "", "", false},
207
208 {"", "", "", false},
209 {"ci/", "", "", false},
210 }
211
212 for _, tc := range cases {
213 t.Run(fmt.Sprintf("input:%s/label:%s", tc.input, tc.label), func(t *testing.T) {
214 bucket, label, err := splitVersion(tc.input)
215 switch {
216 case err != nil && tc.valid:
217 t.Errorf("splitVersion: unexpected error for %q. Error: %v", tc.input, err)
218 case err == nil && !tc.valid:
219 t.Errorf("splitVersion: error expected for key %q, but result is %q, %q", tc.input, bucket, label)
220 case bucket != tc.bucket:
221 t.Errorf("splitVersion: unexpected bucket result for key %q. Expected: %q Actual: %q", tc.input, tc.bucket, bucket)
222 case label != tc.label:
223 t.Errorf("splitVersion: unexpected label result for key %q. Expected: %q Actual: %q", tc.input, tc.label, label)
224 }
225 })
226 }
227 }
228
229 func TestKubernetesIsCIVersion(t *testing.T) {
230 type T struct {
231 input string
232 expected bool
233 }
234 cases := []T{
235 {"", false},
236
237 {"v1.0.0", false},
238 {"release/v1.0.0", false},
239
240 {"ci/latest-1", true},
241 {"ci/v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
242 {"ci/", false},
243 }
244
245 for _, tc := range cases {
246 t.Run(fmt.Sprintf("input:%s/expected:%t", tc.input, tc.expected), func(t *testing.T) {
247 result := KubernetesIsCIVersion(tc.input)
248 t.Logf("KubernetesIsCIVersion: Input: %q. Result: %v. Expected: %v", tc.input, result, tc.expected)
249 if result != tc.expected {
250 t.Errorf("failed KubernetesIsCIVersion: Input: %q. Result: %v. Expected: %v", tc.input, result, tc.expected)
251 }
252 })
253 }
254 }
255
256
257 func TestCIBuildVersion(t *testing.T) {
258 type T struct {
259 input string
260 expected string
261 valid bool
262 }
263 cases := []T{
264
265 {"v1.7.0", "v1.7.0", true},
266 {"release/v1.8.0", "v1.8.0", true},
267 {"1.4.0-beta.0", "v1.4.0-beta.0", true},
268 {"release/0invalid", "", false},
269
270 {"ci/v1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
271 {"ci/1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
272 {"ci/0invalid", "", false},
273 {"0invalid", "", false},
274 }
275
276 for _, tc := range cases {
277 t.Run(fmt.Sprintf("input:%s/expected:%s", tc.input, tc.expected), func(t *testing.T) {
278
279 fileFetcher := func(url string, timeout time.Duration) (string, error) {
280 if tc.valid {
281 return tc.expected, nil
282 }
283 return "Unknown test case key!", errors.New("unknown test case key")
284 }
285
286 ver, err := kubernetesReleaseVersion(tc.input, fileFetcher)
287 t.Logf("Input: %q. Result: %q, Error: %v", tc.input, ver, err)
288 switch {
289 case err != nil && tc.valid:
290 t.Errorf("KubernetesReleaseVersion: unexpected error for input %q. Error: %v", tc.input, err)
291 case err == nil && !tc.valid:
292 t.Errorf("KubernetesReleaseVersion: error expected for input %q, but result is %q", tc.input, ver)
293 case ver != tc.expected:
294 t.Errorf("KubernetesReleaseVersion: unexpected result for input %q. Expected: %q Actual: %q", tc.input, tc.expected, ver)
295 }
296 })
297 }
298 }
299
300 func TestNormalizedBuildVersionVersion(t *testing.T) {
301 type T struct {
302 input string
303 expected string
304 }
305 cases := []T{
306 {"v1.7.0", "v1.7.0"},
307 {"v1.8.0-alpha.2.1231+afabd012389d53a", "v1.8.0-alpha.2.1231+afabd012389d53a"},
308 {"1.7.0", "v1.7.0"},
309 {"unknown-1", ""},
310 }
311
312 for _, tc := range cases {
313 t.Run(fmt.Sprintf("input:%s/expected:%s", tc.input, tc.expected), func(t *testing.T) {
314 output := normalizedBuildVersion(tc.input)
315 if output != tc.expected {
316 t.Errorf("normalizedBuildVersion: unexpected output %q for input %q. Expected: %q", output, tc.input, tc.expected)
317 }
318 })
319 }
320 }
321
322 func TestKubeadmVersion(t *testing.T) {
323 type T struct {
324 name string
325 input string
326 output string
327 outputError bool
328 parsingError bool
329 }
330 cases := []T{
331 {
332 name: "valid version with label and metadata",
333 input: "v1.8.0-alpha.2.1231+afabd012389d53a",
334 output: "v1.8.0-alpha.2",
335 },
336 {
337 name: "valid version with label and extra metadata",
338 input: "v1.8.0-alpha.2.1231+afabd012389d53a.extra",
339 output: "v1.8.0-alpha.2",
340 },
341 {
342 name: "valid patch version with label and extra metadata",
343 input: "v1.11.3-beta.0.38+135cc4c1f47994",
344 output: "v1.11.2",
345 },
346 {
347 name: "valid version with label extra",
348 input: "v1.8.0-alpha.2.1231",
349 output: "v1.8.0-alpha.2",
350 },
351 {
352 name: "valid patch version with label",
353 input: "v1.9.11-beta.0",
354 output: "v1.9.10",
355 },
356 {
357 name: "handle version with partial label",
358 input: "v1.8.0-alpha",
359 output: "v1.8.0-alpha.0",
360 },
361 {
362 name: "handle version missing 'v'",
363 input: "1.11.0",
364 output: "v1.11.0",
365 },
366 {
367 name: "valid version without label and metadata",
368 input: "v1.8.0",
369 output: "v1.8.0",
370 },
371 {
372 name: "valid patch version without label and metadata",
373 input: "v1.8.2",
374 output: "v1.8.2",
375 },
376 {
377 name: "invalid version",
378 input: "foo",
379 parsingError: true,
380 },
381 {
382 name: "invalid version with stray dash",
383 input: "v1.9.11-",
384 parsingError: true,
385 },
386 {
387 name: "invalid version without patch release",
388 input: "v1.9",
389 parsingError: true,
390 },
391 {
392 name: "invalid version with label and stray dot",
393 input: "v1.8.0-alpha.2.",
394 parsingError: true,
395 },
396 {
397 name: "invalid version with label and metadata",
398 input: "v1.8.0-alpha.2.1231+afabd012389d53a",
399 output: "v1.8.0-alpha.3",
400 outputError: true,
401 },
402 }
403
404 for _, tc := range cases {
405 t.Run(tc.name, func(t *testing.T) {
406 output, err := kubeadmVersion(tc.input)
407 if (err != nil) != tc.parsingError {
408 t.Fatalf("expected error: %v, got: %v", tc.parsingError, err != nil)
409 }
410 if (output != tc.output) != tc.outputError {
411 t.Fatalf("expected output: %s, got: %s, for input: %s", tc.output, output, tc.input)
412 }
413 })
414 }
415 }
416
417 func TestValidateStableVersion(t *testing.T) {
418 type T struct {
419 name string
420 remoteVersion string
421 clientVersion string
422 output string
423 expectedError bool
424 }
425 cases := []T{
426 {
427 name: "valid: remote version is newer; return stable label [1]",
428 remoteVersion: "v1.12.0",
429 clientVersion: "v1.11.0",
430 output: "stable-1.11",
431 },
432 {
433 name: "valid: remote version is newer; return stable label [2]",
434 remoteVersion: "v2.0.0",
435 clientVersion: "v1.11.0",
436 output: "stable-1.11",
437 },
438 {
439 name: "valid: remote version is newer; return stable label [3]",
440 remoteVersion: "v2.1.5",
441 clientVersion: "v1.11.5",
442 output: "stable-1.11",
443 },
444 {
445 name: "valid: return the remote version as it is part of the same release",
446 remoteVersion: "v1.11.5",
447 clientVersion: "v1.11.0",
448 output: "v1.11.5",
449 },
450 {
451 name: "valid: return the same version",
452 remoteVersion: "v1.11.0",
453 clientVersion: "v1.11.0",
454 output: "v1.11.0",
455 },
456 {
457 name: "invalid: client version is empty",
458 remoteVersion: "v1.12.1",
459 clientVersion: "",
460 expectedError: true,
461 },
462 {
463 name: "invalid: error parsing the remote version",
464 remoteVersion: "invalid-version",
465 clientVersion: "v1.12.0",
466 expectedError: true,
467 },
468 {
469 name: "invalid: error parsing the client version",
470 remoteVersion: "v1.12.0",
471 clientVersion: "invalid-version",
472 expectedError: true,
473 },
474 }
475
476 for _, tc := range cases {
477 t.Run(tc.name, func(t *testing.T) {
478 output, err := validateStableVersion(tc.remoteVersion, tc.clientVersion)
479 if (err != nil) != tc.expectedError {
480 t.Fatalf("expected error: %v, got: %v", tc.expectedError, err != nil)
481 }
482 if output != tc.output {
483 t.Fatalf("expected output: %s, got: %s", tc.output, output)
484 }
485 })
486 }
487 }
488
489 func errorFetcher(url string, timeout time.Duration) (string, error) {
490 return "should not make internet calls", errors.Errorf("should not make internet calls, tried to request url: %s", url)
491 }
492
View as plain text