version_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package util
  14. import (
  15. "fmt"
  16. "net/http"
  17. "net/http/httptest"
  18. "path"
  19. "strings"
  20. "testing"
  21. )
  22. func TestEmptyVersion(t *testing.T) {
  23. ver, err := KubernetesReleaseVersion("")
  24. if err == nil {
  25. t.Error("KubernetesReleaseVersion returned successfully, but error expected")
  26. }
  27. if ver != "" {
  28. t.Error("KubernetesReleaseVersion returned value, expected only error")
  29. }
  30. }
  31. func TestValidVersion(t *testing.T) {
  32. validVersions := []string{
  33. "v1.3.0",
  34. "v1.4.0-alpha.0",
  35. "v1.4.5",
  36. "v1.4.0-beta.0",
  37. "v2.0.0",
  38. "v1.6.0-alpha.0.536+d60d9f3269288f",
  39. "v1.5.0-alpha.0.1078+1044b6822497da-pull",
  40. "v1.5.0-alpha.1.822+49b9e32fad9f32-pull-gke-gci",
  41. "v1.6.1+coreos.0",
  42. }
  43. for _, s := range validVersions {
  44. t.Run(s, func(t *testing.T) {
  45. ver, err := KubernetesReleaseVersion(s)
  46. t.Log("Valid: ", s, ver, err)
  47. if err != nil {
  48. t.Errorf("KubernetesReleaseVersion unexpected error for version %q: %v", s, err)
  49. }
  50. if ver != s {
  51. t.Errorf("KubernetesReleaseVersion should return same valid version string. %q != %q", s, ver)
  52. }
  53. })
  54. }
  55. }
  56. func TestInvalidVersion(t *testing.T) {
  57. invalidVersions := []string{
  58. "v1.3",
  59. "1.4",
  60. "b1.4.0",
  61. "c1.4.5+git",
  62. "something1.2",
  63. }
  64. for _, s := range invalidVersions {
  65. t.Run(s, func(t *testing.T) {
  66. ver, err := KubernetesReleaseVersion(s)
  67. t.Log("Invalid: ", s, ver, err)
  68. if err == nil {
  69. t.Errorf("KubernetesReleaseVersion error expected for version %q, but returned successfully", s)
  70. }
  71. if ver != "" {
  72. t.Errorf("KubernetesReleaseVersion should return empty string in case of error. Returned %q for version %q", ver, s)
  73. }
  74. })
  75. }
  76. }
  77. func TestValidConvenientForUserVersion(t *testing.T) {
  78. validVersions := []string{
  79. "1.4.0",
  80. "1.4.5+git",
  81. "1.6.1_coreos.0",
  82. }
  83. for _, s := range validVersions {
  84. t.Run(s, func(t *testing.T) {
  85. ver, err := KubernetesReleaseVersion(s)
  86. t.Log("Valid: ", s, ver, err)
  87. if err != nil {
  88. t.Errorf("KubernetesReleaseVersion unexpected error for version %q: %v", s, err)
  89. }
  90. if ver != "v"+s {
  91. t.Errorf("KubernetesReleaseVersion should return semantic version string. %q vs. %q", s, ver)
  92. }
  93. })
  94. }
  95. }
  96. func TestVersionFromNetwork(t *testing.T) {
  97. type T struct {
  98. Content string
  99. Status int
  100. Expected string
  101. ErrorExpected bool
  102. }
  103. cases := map[string]T{
  104. "stable": {"stable-1", http.StatusOK, "v1.4.6", false}, // recursive pointer to stable-1
  105. "stable-1": {"v1.4.6", http.StatusOK, "v1.4.6", false},
  106. "stable-1.3": {"v1.3.10", http.StatusOK, "v1.3.10", false},
  107. "latest": {"v1.6.0-alpha.0", http.StatusOK, "v1.6.0-alpha.0", false},
  108. "latest-1.3": {"v1.3.11-beta.0", http.StatusOK, "v1.3.11-beta.0", false},
  109. "empty": {"", http.StatusOK, "", true},
  110. "garbage": {"<?xml version='1.0'?><Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message></Error>", http.StatusOK, "", true},
  111. "unknown": {"The requested URL was not found on this server.", http.StatusNotFound, "", true},
  112. }
  113. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  114. key := strings.TrimSuffix(path.Base(r.URL.Path), ".txt")
  115. res, found := cases[key]
  116. if found {
  117. http.Error(w, res.Content, res.Status)
  118. } else {
  119. http.Error(w, "Unknown test case key!", http.StatusNotFound)
  120. }
  121. }))
  122. defer server.Close()
  123. kubeReleaseBucketURL = server.URL
  124. for k, v := range cases {
  125. t.Run(k, func(t *testing.T) {
  126. ver, err := KubernetesReleaseVersion(k)
  127. t.Logf("Key: %q. Result: %q, Error: %v", k, ver, err)
  128. switch {
  129. case err != nil && !v.ErrorExpected:
  130. t.Errorf("KubernetesReleaseVersion: unexpected error for %q. Error: %+v", k, err)
  131. case err == nil && v.ErrorExpected:
  132. t.Errorf("KubernetesReleaseVersion: error expected for key %q, but result is %q", k, ver)
  133. case ver != v.Expected:
  134. t.Errorf("KubernetesReleaseVersion: unexpected result for key %q. Expected: %q Actual: %q", k, v.Expected, ver)
  135. }
  136. })
  137. }
  138. }
  139. func TestVersionToTag(t *testing.T) {
  140. type T struct {
  141. input string
  142. expected string
  143. }
  144. cases := []T{
  145. // NOP
  146. {"", ""},
  147. // Official releases
  148. {"v1.0.0", "v1.0.0"},
  149. // CI or custom builds
  150. {"v10.1.2-alpha.1.100+0123456789abcdef+SOMETHING", "v10.1.2-alpha.1.100_0123456789abcdef_SOMETHING"},
  151. // random and invalid input: should return safe value
  152. {"v1,0!0+üñµ", "v1_0_0____"},
  153. }
  154. for _, tc := range cases {
  155. t.Run(fmt.Sprintf("input:%s/expected:%s", tc.input, tc.expected), func(t *testing.T) {
  156. tag := KubernetesVersionToImageTag(tc.input)
  157. t.Logf("KubernetesVersionToImageTag: Input: %q. Result: %q. Expected: %q", tc.input, tag, tc.expected)
  158. if tag != tc.expected {
  159. t.Errorf("failed KubernetesVersionToImageTag: Input: %q. Result: %q. Expected: %q", tc.input, tag, tc.expected)
  160. }
  161. })
  162. }
  163. }
  164. func TestSplitVersion(t *testing.T) {
  165. type T struct {
  166. input string
  167. bucket string
  168. label string
  169. valid bool
  170. }
  171. cases := []T{
  172. // Release area
  173. {"v1.7.0", "https://dl.k8s.io/release", "v1.7.0", true},
  174. {"v1.8.0-alpha.2.1231+afabd012389d53a", "https://dl.k8s.io/release", "v1.8.0-alpha.2.1231+afabd012389d53a", true},
  175. {"release/v1.7.0", "https://dl.k8s.io/release", "v1.7.0", true},
  176. {"release/latest-1.7", "https://dl.k8s.io/release", "latest-1.7", true},
  177. // CI builds area, lookup actual builds at ci-cross/*.txt
  178. {"ci/latest", "https://dl.k8s.io/ci", "latest", true},
  179. {"ci-cross/latest", "https://dl.k8s.io/ci-cross", "latest", true},
  180. {"ci/latest-1.7", "https://dl.k8s.io/ci", "latest-1.7", true},
  181. {"ci-cross/latest-1.7", "https://dl.k8s.io/ci-cross", "latest-1.7", true},
  182. // unknown label in default (release) area: splitVersion validate only areas.
  183. {"unknown-1", "https://dl.k8s.io/release", "unknown-1", true},
  184. // unknown area, not valid input.
  185. {"unknown/latest-1", "", "", false},
  186. }
  187. // kubeReleaseBucketURL can be overridden during network tests, thus ensure
  188. // it will contain value corresponding to expected outcome for this unit test
  189. kubeReleaseBucketURL = "https://dl.k8s.io"
  190. for _, tc := range cases {
  191. t.Run(fmt.Sprintf("input:%s/label:%s", tc.input, tc.label), func(t *testing.T) {
  192. bucket, label, err := splitVersion(tc.input)
  193. switch {
  194. case err != nil && tc.valid:
  195. t.Errorf("splitVersion: unexpected error for %q. Error: %v", tc.input, err)
  196. case err == nil && !tc.valid:
  197. t.Errorf("splitVersion: error expected for key %q, but result is %q, %q", tc.input, bucket, label)
  198. case bucket != tc.bucket:
  199. t.Errorf("splitVersion: unexpected bucket result for key %q. Expected: %q Actual: %q", tc.input, tc.bucket, bucket)
  200. case label != tc.label:
  201. t.Errorf("splitVersion: unexpected label result for key %q. Expected: %q Actual: %q", tc.input, tc.label, label)
  202. }
  203. })
  204. }
  205. }
  206. func TestKubernetesIsCIVersion(t *testing.T) {
  207. type T struct {
  208. input string
  209. expected bool
  210. }
  211. cases := []T{
  212. {"", false},
  213. // Official releases
  214. {"v1.0.0", false},
  215. {"release/v1.0.0", false},
  216. // CI builds
  217. {"ci/latest-1", true},
  218. {"ci-cross/latest", true},
  219. {"ci/v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
  220. {"ci-cross/v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
  221. }
  222. for _, tc := range cases {
  223. t.Run(fmt.Sprintf("input:%s/expected:%t", tc.input, tc.expected), func(t *testing.T) {
  224. result := KubernetesIsCIVersion(tc.input)
  225. t.Logf("KubernetesIsCIVersion: Input: %q. Result: %v. Expected: %v", tc.input, result, tc.expected)
  226. if result != tc.expected {
  227. t.Errorf("failed KubernetesIsCIVersion: Input: %q. Result: %v. Expected: %v", tc.input, result, tc.expected)
  228. }
  229. })
  230. }
  231. }
  232. // Validate KubernetesReleaseVersion but with bucket prefixes
  233. func TestCIBuildVersion(t *testing.T) {
  234. type T struct {
  235. input string
  236. expected string
  237. valid bool
  238. }
  239. cases := []T{
  240. // Official releases
  241. {"v1.7.0", "v1.7.0", true},
  242. {"release/v1.8.0", "v1.8.0", true},
  243. {"1.4.0-beta.0", "v1.4.0-beta.0", true},
  244. {"release/0invalid", "", false},
  245. // CI or custom builds
  246. {"ci/v1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
  247. {"ci-cross/v1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
  248. {"ci/1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
  249. {"ci-cross/1.9.0-alpha.1.123+acbcbfd53bfa0a", "v1.9.0-alpha.1.123+acbcbfd53bfa0a", true},
  250. {"ci/0invalid", "", false},
  251. }
  252. for _, tc := range cases {
  253. t.Run(fmt.Sprintf("input:%s/expected:%s", tc.input, tc.expected), func(t *testing.T) {
  254. ver, err := KubernetesReleaseVersion(tc.input)
  255. t.Logf("Input: %q. Result: %q, Error: %v", tc.input, ver, err)
  256. switch {
  257. case err != nil && tc.valid:
  258. t.Errorf("KubernetesReleaseVersion: unexpected error for input %q. Error: %v", tc.input, err)
  259. case err == nil && !tc.valid:
  260. t.Errorf("KubernetesReleaseVersion: error expected for input %q, but result is %q", tc.input, ver)
  261. case ver != tc.expected:
  262. t.Errorf("KubernetesReleaseVersion: unexpected result for input %q. Expected: %q Actual: %q", tc.input, tc.expected, ver)
  263. }
  264. })
  265. }
  266. }
  267. func TestNormalizedBuildVersionVersion(t *testing.T) {
  268. type T struct {
  269. input string
  270. expected string
  271. }
  272. cases := []T{
  273. {"v1.7.0", "v1.7.0"},
  274. {"v1.8.0-alpha.2.1231+afabd012389d53a", "v1.8.0-alpha.2.1231+afabd012389d53a"},
  275. {"1.7.0", "v1.7.0"},
  276. {"unknown-1", ""},
  277. }
  278. for _, tc := range cases {
  279. t.Run(fmt.Sprintf("input:%s/expected:%s", tc.input, tc.expected), func(t *testing.T) {
  280. output := normalizedBuildVersion(tc.input)
  281. if output != tc.expected {
  282. t.Errorf("normalizedBuildVersion: unexpected output %q for input %q. Expected: %q", output, tc.input, tc.expected)
  283. }
  284. })
  285. }
  286. }
  287. func TestKubeadmVersion(t *testing.T) {
  288. type T struct {
  289. name string
  290. input string
  291. output string
  292. outputError bool
  293. parsingError bool
  294. }
  295. cases := []T{
  296. {
  297. name: "valid version with label and metadata",
  298. input: "v1.8.0-alpha.2.1231+afabd012389d53a",
  299. output: "v1.8.0-alpha.2",
  300. },
  301. {
  302. name: "valid version with label and extra metadata",
  303. input: "v1.8.0-alpha.2.1231+afabd012389d53a.extra",
  304. output: "v1.8.0-alpha.2",
  305. },
  306. {
  307. name: "valid patch version with label and extra metadata",
  308. input: "v1.11.3-beta.0.38+135cc4c1f47994",
  309. output: "v1.11.2",
  310. },
  311. {
  312. name: "valid version with label extra",
  313. input: "v1.8.0-alpha.2.1231",
  314. output: "v1.8.0-alpha.2",
  315. },
  316. {
  317. name: "valid patch version with label",
  318. input: "v1.9.11-beta.0",
  319. output: "v1.9.10",
  320. },
  321. {
  322. name: "handle version with partial label",
  323. input: "v1.8.0-alpha",
  324. output: "v1.8.0-alpha.0",
  325. },
  326. {
  327. name: "handle version missing 'v'",
  328. input: "1.11.0",
  329. output: "v1.11.0",
  330. },
  331. {
  332. name: "valid version without label and metadata",
  333. input: "v1.8.0",
  334. output: "v1.8.0",
  335. },
  336. {
  337. name: "valid patch version without label and metadata",
  338. input: "v1.8.2",
  339. output: "v1.8.2",
  340. },
  341. {
  342. name: "invalid version",
  343. input: "foo",
  344. parsingError: true,
  345. },
  346. {
  347. name: "invalid version with stray dash",
  348. input: "v1.9.11-",
  349. parsingError: true,
  350. },
  351. {
  352. name: "invalid version without patch release",
  353. input: "v1.9",
  354. parsingError: true,
  355. },
  356. {
  357. name: "invalid version with label and metadata",
  358. input: "v1.8.0-alpha.2.1231+afabd012389d53a",
  359. output: "v1.8.0-alpha.3",
  360. outputError: true,
  361. },
  362. }
  363. for _, tc := range cases {
  364. t.Run(tc.name, func(t *testing.T) {
  365. output, err := kubeadmVersion(tc.input)
  366. if (err != nil) != tc.parsingError {
  367. t.Fatalf("expected error: %v, got: %v", tc.parsingError, err != nil)
  368. }
  369. if (output != tc.output) != tc.outputError {
  370. t.Fatalf("expected output: %s, got: %s, for input: %s", tc.output, output, tc.input)
  371. }
  372. })
  373. }
  374. }
  375. func TestValidateStableVersion(t *testing.T) {
  376. type T struct {
  377. name string
  378. remoteVersion string
  379. clientVersion string
  380. output string
  381. expectedError bool
  382. }
  383. cases := []T{
  384. {
  385. name: "valid: remote version is newer; return stable label [1]",
  386. remoteVersion: "v1.12.0",
  387. clientVersion: "v1.11.0",
  388. output: "stable-1.11",
  389. },
  390. {
  391. name: "valid: remote version is newer; return stable label [2]",
  392. remoteVersion: "v2.0.0",
  393. clientVersion: "v1.11.0",
  394. output: "stable-1.11",
  395. },
  396. {
  397. name: "valid: remote version is newer; return stable label [3]",
  398. remoteVersion: "v2.1.5",
  399. clientVersion: "v1.11.5",
  400. output: "stable-1.11",
  401. },
  402. {
  403. name: "valid: return the remote version as it is part of the same release",
  404. remoteVersion: "v1.11.5",
  405. clientVersion: "v1.11.0",
  406. output: "v1.11.5",
  407. },
  408. {
  409. name: "valid: return the same version",
  410. remoteVersion: "v1.11.0",
  411. clientVersion: "v1.11.0",
  412. output: "v1.11.0",
  413. },
  414. {
  415. name: "invalid: client version is empty",
  416. remoteVersion: "v1.12.1",
  417. clientVersion: "",
  418. expectedError: true,
  419. },
  420. {
  421. name: "invalid: error parsing the remote version",
  422. remoteVersion: "invalid-version",
  423. clientVersion: "v1.12.0",
  424. expectedError: true,
  425. },
  426. {
  427. name: "invalid: error parsing the client version",
  428. remoteVersion: "v1.12.0",
  429. clientVersion: "invalid-version",
  430. expectedError: true,
  431. },
  432. }
  433. for _, tc := range cases {
  434. t.Run(tc.name, func(t *testing.T) {
  435. output, err := validateStableVersion(tc.remoteVersion, tc.clientVersion)
  436. if (err != nil) != tc.expectedError {
  437. t.Fatalf("expected error: %v, got: %v", tc.expectedError, err != nil)
  438. }
  439. if output != tc.output {
  440. t.Fatalf("expected output: %s, got: %s", tc.output, output)
  441. }
  442. })
  443. }
  444. }