sorter_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. /*
  2. Copyright 2014 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 get
  14. import (
  15. "encoding/json"
  16. "reflect"
  17. "strings"
  18. "testing"
  19. corev1 "k8s.io/api/core/v1"
  20. "k8s.io/apimachinery/pkg/api/meta"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  23. metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
  24. "k8s.io/apimachinery/pkg/runtime"
  25. "k8s.io/apimachinery/pkg/util/diff"
  26. "k8s.io/kubernetes/pkg/kubectl/scheme"
  27. )
  28. func toUnstructuredOrDie(data []byte) *unstructured.Unstructured {
  29. unstrBody := map[string]interface{}{}
  30. err := json.Unmarshal(data, &unstrBody)
  31. if err != nil {
  32. panic(err)
  33. }
  34. return &unstructured.Unstructured{Object: unstrBody}
  35. }
  36. func encodeOrDie(obj runtime.Object) []byte {
  37. data, err := runtime.Encode(scheme.Codecs.LegacyCodec(corev1.SchemeGroupVersion), obj)
  38. if err != nil {
  39. panic(err.Error())
  40. }
  41. return data
  42. }
  43. func TestSortingPrinter(t *testing.T) {
  44. intPtr := func(val int32) *int32 { return &val }
  45. a := &corev1.Pod{
  46. ObjectMeta: metav1.ObjectMeta{
  47. Name: "a",
  48. },
  49. }
  50. b := &corev1.Pod{
  51. ObjectMeta: metav1.ObjectMeta{
  52. Name: "b",
  53. },
  54. }
  55. c := &corev1.Pod{
  56. ObjectMeta: metav1.ObjectMeta{
  57. Name: "c",
  58. },
  59. }
  60. tests := []struct {
  61. obj runtime.Object
  62. sort runtime.Object
  63. field string
  64. name string
  65. expectedErr string
  66. }{
  67. {
  68. name: "empty",
  69. obj: &corev1.PodList{
  70. Items: []corev1.Pod{},
  71. },
  72. sort: &corev1.PodList{
  73. Items: []corev1.Pod{},
  74. },
  75. field: "{.metadata.name}",
  76. },
  77. {
  78. name: "in-order-already",
  79. obj: &corev1.PodList{
  80. Items: []corev1.Pod{
  81. {
  82. ObjectMeta: metav1.ObjectMeta{
  83. Name: "a",
  84. },
  85. },
  86. {
  87. ObjectMeta: metav1.ObjectMeta{
  88. Name: "b",
  89. },
  90. },
  91. {
  92. ObjectMeta: metav1.ObjectMeta{
  93. Name: "c",
  94. },
  95. },
  96. },
  97. },
  98. sort: &corev1.PodList{
  99. Items: []corev1.Pod{
  100. {
  101. ObjectMeta: metav1.ObjectMeta{
  102. Name: "a",
  103. },
  104. },
  105. {
  106. ObjectMeta: metav1.ObjectMeta{
  107. Name: "b",
  108. },
  109. },
  110. {
  111. ObjectMeta: metav1.ObjectMeta{
  112. Name: "c",
  113. },
  114. },
  115. },
  116. },
  117. field: "{.metadata.name}",
  118. },
  119. {
  120. name: "reverse-order",
  121. obj: &corev1.PodList{
  122. Items: []corev1.Pod{
  123. {
  124. ObjectMeta: metav1.ObjectMeta{
  125. Name: "b",
  126. },
  127. },
  128. {
  129. ObjectMeta: metav1.ObjectMeta{
  130. Name: "c",
  131. },
  132. },
  133. {
  134. ObjectMeta: metav1.ObjectMeta{
  135. Name: "a",
  136. },
  137. },
  138. },
  139. },
  140. sort: &corev1.PodList{
  141. Items: []corev1.Pod{
  142. {
  143. ObjectMeta: metav1.ObjectMeta{
  144. Name: "a",
  145. },
  146. },
  147. {
  148. ObjectMeta: metav1.ObjectMeta{
  149. Name: "b",
  150. },
  151. },
  152. {
  153. ObjectMeta: metav1.ObjectMeta{
  154. Name: "c",
  155. },
  156. },
  157. },
  158. },
  159. field: "{.metadata.name}",
  160. },
  161. {
  162. name: "random-order-timestamp",
  163. obj: &corev1.PodList{
  164. Items: []corev1.Pod{
  165. {
  166. ObjectMeta: metav1.ObjectMeta{
  167. CreationTimestamp: metav1.Unix(300, 0),
  168. },
  169. },
  170. {
  171. ObjectMeta: metav1.ObjectMeta{
  172. CreationTimestamp: metav1.Unix(100, 0),
  173. },
  174. },
  175. {
  176. ObjectMeta: metav1.ObjectMeta{
  177. CreationTimestamp: metav1.Unix(200, 0),
  178. },
  179. },
  180. },
  181. },
  182. sort: &corev1.PodList{
  183. Items: []corev1.Pod{
  184. {
  185. ObjectMeta: metav1.ObjectMeta{
  186. CreationTimestamp: metav1.Unix(100, 0),
  187. },
  188. },
  189. {
  190. ObjectMeta: metav1.ObjectMeta{
  191. CreationTimestamp: metav1.Unix(200, 0),
  192. },
  193. },
  194. {
  195. ObjectMeta: metav1.ObjectMeta{
  196. CreationTimestamp: metav1.Unix(300, 0),
  197. },
  198. },
  199. },
  200. },
  201. field: "{.metadata.creationTimestamp}",
  202. },
  203. {
  204. name: "random-order-numbers",
  205. obj: &corev1.ReplicationControllerList{
  206. Items: []corev1.ReplicationController{
  207. {
  208. Spec: corev1.ReplicationControllerSpec{
  209. Replicas: intPtr(5),
  210. },
  211. },
  212. {
  213. Spec: corev1.ReplicationControllerSpec{
  214. Replicas: intPtr(1),
  215. },
  216. },
  217. {
  218. Spec: corev1.ReplicationControllerSpec{
  219. Replicas: intPtr(9),
  220. },
  221. },
  222. },
  223. },
  224. sort: &corev1.ReplicationControllerList{
  225. Items: []corev1.ReplicationController{
  226. {
  227. Spec: corev1.ReplicationControllerSpec{
  228. Replicas: intPtr(1),
  229. },
  230. },
  231. {
  232. Spec: corev1.ReplicationControllerSpec{
  233. Replicas: intPtr(5),
  234. },
  235. },
  236. {
  237. Spec: corev1.ReplicationControllerSpec{
  238. Replicas: intPtr(9),
  239. },
  240. },
  241. },
  242. },
  243. field: "{.spec.replicas}",
  244. },
  245. {
  246. name: "v1.List in order",
  247. obj: &corev1.List{
  248. Items: []runtime.RawExtension{
  249. {Object: a, Raw: encodeOrDie(a)},
  250. {Object: b, Raw: encodeOrDie(b)},
  251. {Object: c, Raw: encodeOrDie(c)},
  252. },
  253. },
  254. sort: &corev1.List{
  255. Items: []runtime.RawExtension{
  256. {Object: a, Raw: encodeOrDie(a)},
  257. {Object: b, Raw: encodeOrDie(b)},
  258. {Object: c, Raw: encodeOrDie(c)},
  259. },
  260. },
  261. field: "{.metadata.name}",
  262. },
  263. {
  264. name: "v1.List in reverse",
  265. obj: &corev1.List{
  266. Items: []runtime.RawExtension{
  267. {Object: c, Raw: encodeOrDie(c)},
  268. {Object: b, Raw: encodeOrDie(b)},
  269. {Object: a, Raw: encodeOrDie(a)},
  270. },
  271. },
  272. sort: &corev1.List{
  273. Items: []runtime.RawExtension{
  274. {Object: a, Raw: encodeOrDie(a)},
  275. {Object: b, Raw: encodeOrDie(b)},
  276. {Object: c, Raw: encodeOrDie(c)},
  277. },
  278. },
  279. field: "{.metadata.name}",
  280. },
  281. {
  282. name: "some-missing-fields",
  283. obj: &unstructured.UnstructuredList{
  284. Object: map[string]interface{}{
  285. "kind": "List",
  286. "apiVersion": "v1",
  287. },
  288. Items: []unstructured.Unstructured{
  289. {
  290. Object: map[string]interface{}{
  291. "kind": "ReplicationController",
  292. "apiVersion": "v1",
  293. "status": map[string]interface{}{
  294. "availableReplicas": 2,
  295. },
  296. },
  297. },
  298. {
  299. Object: map[string]interface{}{
  300. "kind": "ReplicationController",
  301. "apiVersion": "v1",
  302. "status": map[string]interface{}{},
  303. },
  304. },
  305. {
  306. Object: map[string]interface{}{
  307. "kind": "ReplicationController",
  308. "apiVersion": "v1",
  309. "status": map[string]interface{}{
  310. "availableReplicas": 1,
  311. },
  312. },
  313. },
  314. },
  315. },
  316. sort: &unstructured.UnstructuredList{
  317. Object: map[string]interface{}{
  318. "kind": "List",
  319. "apiVersion": "v1",
  320. },
  321. Items: []unstructured.Unstructured{
  322. {
  323. Object: map[string]interface{}{
  324. "kind": "ReplicationController",
  325. "apiVersion": "v1",
  326. "status": map[string]interface{}{},
  327. },
  328. },
  329. {
  330. Object: map[string]interface{}{
  331. "kind": "ReplicationController",
  332. "apiVersion": "v1",
  333. "status": map[string]interface{}{
  334. "availableReplicas": 1,
  335. },
  336. },
  337. },
  338. {
  339. Object: map[string]interface{}{
  340. "kind": "ReplicationController",
  341. "apiVersion": "v1",
  342. "status": map[string]interface{}{
  343. "availableReplicas": 2,
  344. },
  345. },
  346. },
  347. },
  348. },
  349. field: "{.status.availableReplicas}",
  350. },
  351. {
  352. name: "all-missing-fields",
  353. obj: &unstructured.UnstructuredList{
  354. Object: map[string]interface{}{
  355. "kind": "List",
  356. "apiVersion": "v1",
  357. },
  358. Items: []unstructured.Unstructured{
  359. {
  360. Object: map[string]interface{}{
  361. "kind": "ReplicationController",
  362. "apiVersion": "v1",
  363. "status": map[string]interface{}{
  364. "replicas": 0,
  365. },
  366. },
  367. },
  368. {
  369. Object: map[string]interface{}{
  370. "kind": "ReplicationController",
  371. "apiVersion": "v1",
  372. "status": map[string]interface{}{
  373. "replicas": 0,
  374. },
  375. },
  376. },
  377. },
  378. },
  379. field: "{.status.availableReplicas}",
  380. expectedErr: "couldn't find any field with path \"{.status.availableReplicas}\" in the list of objects",
  381. },
  382. {
  383. name: "model-invalid-fields",
  384. obj: &corev1.ReplicationControllerList{
  385. Items: []corev1.ReplicationController{
  386. {
  387. Status: corev1.ReplicationControllerStatus{},
  388. },
  389. {
  390. Status: corev1.ReplicationControllerStatus{},
  391. },
  392. {
  393. Status: corev1.ReplicationControllerStatus{},
  394. },
  395. },
  396. },
  397. field: "{.invalid}",
  398. expectedErr: "couldn't find any field with path \"{.invalid}\" in the list of objects",
  399. },
  400. }
  401. for _, tt := range tests {
  402. t.Run(tt.name+" table", func(t *testing.T) {
  403. table := &metav1beta1.Table{}
  404. meta.EachListItem(tt.obj, func(item runtime.Object) error {
  405. table.Rows = append(table.Rows, metav1beta1.TableRow{
  406. Object: runtime.RawExtension{Object: toUnstructuredOrDie(encodeOrDie(item))},
  407. })
  408. return nil
  409. })
  410. expectedTable := &metav1beta1.Table{}
  411. meta.EachListItem(tt.sort, func(item runtime.Object) error {
  412. expectedTable.Rows = append(expectedTable.Rows, metav1beta1.TableRow{
  413. Object: runtime.RawExtension{Object: toUnstructuredOrDie(encodeOrDie(item))},
  414. })
  415. return nil
  416. })
  417. sorter, err := NewTableSorter(table, tt.field)
  418. if err == nil {
  419. err = sorter.Sort()
  420. }
  421. if err != nil {
  422. if len(tt.expectedErr) > 0 {
  423. if strings.Contains(err.Error(), tt.expectedErr) {
  424. return
  425. }
  426. t.Fatalf("%s: expected error containing: %q, got: \"%v\"", tt.name, tt.expectedErr, err)
  427. }
  428. t.Fatalf("%s: unexpected error: %v", tt.name, err)
  429. }
  430. if len(tt.expectedErr) > 0 {
  431. t.Fatalf("%s: expected error containing: %q, got none", tt.name, tt.expectedErr)
  432. }
  433. if !reflect.DeepEqual(table, expectedTable) {
  434. t.Errorf("[%s]\nexpected/saw:\n%s", tt.name, diff.ObjectReflectDiff(expectedTable, table))
  435. }
  436. })
  437. t.Run(tt.name, func(t *testing.T) {
  438. sort := &SortingPrinter{SortField: tt.field, Decoder: scheme.Codecs.UniversalDecoder()}
  439. err := sort.sortObj(tt.obj)
  440. if err != nil {
  441. if len(tt.expectedErr) > 0 {
  442. if strings.Contains(err.Error(), tt.expectedErr) {
  443. return
  444. }
  445. t.Fatalf("%s: expected error containing: %q, got: \"%v\"", tt.name, tt.expectedErr, err)
  446. }
  447. t.Fatalf("%s: unexpected error: %v", tt.name, err)
  448. }
  449. if len(tt.expectedErr) > 0 {
  450. t.Fatalf("%s: expected error containing: %q, got none", tt.name, tt.expectedErr)
  451. }
  452. if !reflect.DeepEqual(tt.obj, tt.sort) {
  453. t.Errorf("[%s]\nexpected:\n%v\nsaw:\n%v", tt.name, tt.sort, tt.obj)
  454. }
  455. })
  456. }
  457. }
  458. func TestRuntimeSortLess(t *testing.T) {
  459. var testobj runtime.Object
  460. testobj = &corev1.PodList{
  461. Items: []corev1.Pod{
  462. {
  463. ObjectMeta: metav1.ObjectMeta{
  464. Name: "b",
  465. },
  466. },
  467. {
  468. ObjectMeta: metav1.ObjectMeta{
  469. Name: "c",
  470. },
  471. },
  472. {
  473. ObjectMeta: metav1.ObjectMeta{
  474. Name: "a",
  475. },
  476. },
  477. },
  478. }
  479. testobjs, err := meta.ExtractList(testobj)
  480. if err != nil {
  481. t.Fatalf("ExtractList testobj got unexpected error: %v", err)
  482. }
  483. testfield := "{.metadata.name}"
  484. testruntimeSort := NewRuntimeSort(testfield, testobjs)
  485. tests := []struct {
  486. name string
  487. runtimeSort *RuntimeSort
  488. i int
  489. j int
  490. expectResult bool
  491. expectErr bool
  492. }{
  493. {
  494. name: "test less true",
  495. runtimeSort: testruntimeSort,
  496. i: 0,
  497. j: 1,
  498. expectResult: true,
  499. },
  500. {
  501. name: "test less false",
  502. runtimeSort: testruntimeSort,
  503. i: 1,
  504. j: 2,
  505. expectResult: false,
  506. },
  507. }
  508. for i, test := range tests {
  509. t.Run(test.name, func(t *testing.T) {
  510. result := test.runtimeSort.Less(test.i, test.j)
  511. if result != test.expectResult {
  512. t.Errorf("case[%d]:%s Expected result: %v, Got result: %v", i, test.name, test.expectResult, result)
  513. }
  514. })
  515. }
  516. }