From 6ced323a94779d60f9181648115a73864a5c27f8 Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Fri, 11 Oct 2019 11:33:18 -0700 Subject: [PATCH] Adds missing tests. Added test TestPrintEvent() Added test TestPrintNamespace() Added test TestPrintSecret() Added test TestPrintServiceAccount() Added test TestPrintPodCondition() Added test TestPrintDaemonSetLists Added test TestPrintJobList Added test TestPrintPodDisruptionBudgetList Adds test TestPrintConfigMap Adds test TestPrintNetworkPolicy Adds tests TestPrintRoleBinding and TestPrintClusterRoleBinding Adds test TestPrintCertificateSigningRequest Adds test TestPrintEndpoint Adds test TestPrintReplicaSetList Adds test TestPrintComponentStatus Adds test TestPrintCronJobList Adds test TestPrintPodTemplate Adds test TestPrintPodTemplateList Adds test TestPrintReplicationController Adds test TestPrintServiceList Adds test TestPrintStatefulSet --- pkg/printers/internalversion/printers_test.go | 1640 ++++++++++++++++- 1 file changed, 1570 insertions(+), 70 deletions(-) diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index 2c66c4c8f71..266dbf8c3ff 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -17,6 +17,7 @@ limitations under the License. package internalversion import ( + "bytes" "reflect" "strconv" "strings" @@ -33,18 +34,108 @@ import ( "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/apis/certificates" "k8s.io/kubernetes/pkg/apis/coordination" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/discovery" "k8s.io/kubernetes/pkg/apis/networking" nodeapi "k8s.io/kubernetes/pkg/apis/node" "k8s.io/kubernetes/pkg/apis/policy" + "k8s.io/kubernetes/pkg/apis/rbac" "k8s.io/kubernetes/pkg/apis/scheduling" "k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/printers" utilpointer "k8s.io/utils/pointer" ) +type TestPrintHandler struct { + numCalls int +} + +func (t *TestPrintHandler) TableHandler(columnDefinitions []metav1beta1.TableColumnDefinition, printFunc interface{}) error { + t.numCalls++ + return nil +} + +func (t *TestPrintHandler) getNumCalls() int { + return t.numCalls +} + +func TestAllHandlers(t *testing.T) { + h := &TestPrintHandler{numCalls: 0} + AddHandlers(h) + if h.getNumCalls() == 0 { + t.Error("TableHandler not called in AddHandlers") + } +} + +func TestPrintEvent(t *testing.T) { + tests := []struct { + event api.Event + options printers.GenerateOptions + expected []metav1beta1.TableRow + }{ + // Basic event; no generate options + { + event: api.Event{ + Source: api.EventSource{Component: "kubelet"}, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "Pod Name", + FieldPath: "spec.containers{foo}", + }, + Reason: "Event Reason", + Message: "Message Data", + FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)}, + LastTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}, + Count: 6, + Type: api.EventTypeNormal, + ObjectMeta: metav1.ObjectMeta{Name: "event1"}, + }, + options: printers.GenerateOptions{}, + // Columns: Last Seen, Type, Reason, Object, Message + expected: []metav1beta1.TableRow{{Cells: []interface{}{"2d", "Normal", "Event Reason", "pod/Pod Name", "Message Data"}}}, + }, + // Basic event; generate options=Wide + { + event: api.Event{ + Source: api.EventSource{ + Component: "kubelet", + Host: "Node1", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Deployment", + Name: "Deployment Name", + FieldPath: "spec.containers{foo}", + }, + Reason: "Event Reason", + Message: "Message Data", + FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)}, + LastTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}, + Count: 6, + Type: api.EventTypeWarning, + ObjectMeta: metav1.ObjectMeta{Name: "event2"}, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name + expected: []metav1beta1.TableRow{{Cells: []interface{}{"2d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(6), "event2"}}}, + }, + } + + for i, test := range tests { + rows, err := printEvent(&test.event, test.options) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + func TestPrintEventsResultSorted(t *testing.T) { eventList := api.EventList{ @@ -101,6 +192,147 @@ func TestPrintEventsResultSorted(t *testing.T) { } } +func TestPrintNamespace(t *testing.T) { + tests := []struct { + namespace api.Namespace + expected []metav1beta1.TableRow + }{ + // Basic namespace with status and age. + { + namespace: api.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "namespace1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Status: api.NamespaceStatus{ + Phase: "FooStatus", + }, + }, + // Columns: Name, Status, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"namespace1", "FooStatus", "0s"}}}, + }, + // Basic namespace without status or age. + { + namespace: api.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "namespace2", + }, + }, + // Columns: Name, Status, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"namespace2", "", ""}}}, + }, + } + + for i, test := range tests { + rows, err := printNamespace(&test.namespace, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + +func TestPrintSecret(t *testing.T) { + tests := []struct { + secret api.Secret + expected []metav1beta1.TableRow + }{ + // Basic namespace with type, data, and age. + { + secret: api.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Type: "kubernetes.io/service-account-token", + Data: map[string][]byte{ + "token": []byte("secret data"), + }, + }, + // Columns: Name, Type, Data, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"secret1", "kubernetes.io/service-account-token", int64(1), "0s"}}}, + }, + // Basic namespace with type and age; no data. + { + secret: api.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Type: "kubernetes.io/service-account-token", + }, + // Columns: Name, Type, Data, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"secret1", "kubernetes.io/service-account-token", int64(0), "0s"}}}, + }, + } + + for i, test := range tests { + rows, err := printSecret(&test.secret, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + +func TestPrintServiceAccount(t *testing.T) { + tests := []struct { + serviceAccount api.ServiceAccount + expected []metav1beta1.TableRow + }{ + // Basic service account without secrets + { + serviceAccount: api.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sa1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Secrets: []api.ObjectReference{}, + }, + // Columns: Name, (Num) Secrets, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"sa1", int64(0), "0s"}}}, + }, + // Basic service account with two secrets. + { + serviceAccount: api.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sa1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Secrets: []api.ObjectReference{ + {Name: "Secret1"}, + {Name: "Secret2"}, + }, + }, + // Columns: Name, (Num) Secrets, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"sa1", int64(2), "0s"}}}, + }, + } + + for i, test := range tests { + rows, err := printServiceAccount(&test.serviceAccount, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + func TestPrintNodeStatus(t *testing.T) { table := []struct { @@ -739,7 +971,7 @@ func TestPrintHumanReadableWithNamespace(t *testing.T) { Spec: api.PodSpec{ Containers: []api.Container{ { - Image: "foo/bar", + Image: "foo/bar", TerminationMessagePath: api.TerminationMessagePathDefault, ImagePullPolicy: api.PullIfNotPresent, }, @@ -1206,6 +1438,88 @@ func TestPrintPodwide(t *testing.T) { } } +func TestPrintPodConditions(t *testing.T) { + runningPod := &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test1", Labels: map[string]string{"a": "1", "b": "2"}}, + Spec: api.PodSpec{Containers: make([]api.Container, 2)}, + Status: api.PodStatus{ + Phase: "Running", + ContainerStatuses: []api.ContainerStatus{ + {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, + {RestartCount: 3}, + }, + }, + } + succeededPod := &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test1", Labels: map[string]string{"a": "1", "b": "2"}}, + Spec: api.PodSpec{Containers: make([]api.Container, 2)}, + Status: api.PodStatus{ + Phase: "Succeeded", + ContainerStatuses: []api.ContainerStatus{ + {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, + {RestartCount: 3}, + }, + }, + } + failedPod := &api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "test2", Labels: map[string]string{"b": "2"}}, + Spec: api.PodSpec{Containers: make([]api.Container, 2)}, + Status: api.PodStatus{ + Phase: "Failed", + ContainerStatuses: []api.ContainerStatus{ + {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, + {RestartCount: 3}, + }, + }, + } + tests := []struct { + pod *api.Pod + expect []metav1beta1.TableRow + }{ + // Should not have TableRowCondition + { + pod: runningPod, + // Columns: Name, Ready, Reason, Restarts, Age + expect: []metav1beta1.TableRow{{Cells: []interface{}{"test1", "1/2", "Running", int64(6), ""}}}, + }, + // Should have TableRowCondition: podSuccessConditions + { + pod: succeededPod, + expect: []metav1beta1.TableRow{ + { + // Columns: Name, Ready, Reason, Restarts, Age + Cells: []interface{}{"test1", "1/2", "Succeeded", int64(6), ""}, + Conditions: podSuccessConditions, + }, + }, + }, + // Should have TableRowCondition: podFailedCondition + { + pod: failedPod, + expect: []metav1beta1.TableRow{ + { + // Columns: Name, Ready, Reason, Restarts, Age + Cells: []interface{}{"test2", "1/2", "Failed", int64(6), ""}, + Conditions: podFailedConditions, + }, + }, + }, + } + + for i, test := range tests { + rows, err := printPod(test.pod, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expect, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expect, rows)) + } + } +} + func TestPrintPodList(t *testing.T) { tests := []struct { pods api.PodList @@ -1368,95 +1682,134 @@ func TestPrintNonTerminatedPod(t *testing.T) { } } -func TestPrintPodWithLabels(t *testing.T) { +func TestPrintPodTemplate(t *testing.T) { tests := []struct { - pod api.Pod - labelColumns []string - expectedLabelValues []string - labelsPrinted bool + podTemplate api.PodTemplate + options printers.GenerateOptions + expected []metav1beta1.TableRow }{ + // Test basic pod template with no containers. { - // Test name, num of containers, restarts, container ready status - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test1", - Labels: map[string]string{"col1": "asd", "COL2": "zxc"}, - }, - Spec: api.PodSpec{Containers: make([]api.Container, 2)}, - Status: api.PodStatus{ - Phase: "podPhase", - ContainerStatuses: []api.ContainerStatus{ - {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, - {RestartCount: 3}, + podTemplate: api.PodTemplate{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-template-1"}, + Template: api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-template-1"}, + Spec: api.PodSpec{ + Containers: []api.Container{}, }, }, }, - []string{"col1", "COL2"}, - []string{"asd", "zxc"}, - true, + + options: printers.GenerateOptions{}, + // Columns: Name, Containers, Images, Pod Labels + expected: []metav1beta1.TableRow{{Cells: []interface{}{"pod-template-1", "", "", ""}}}, }, + // Test basic pod template with two containers. { - // Test name, num of containers, restarts, container ready status - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test1", - Labels: map[string]string{"col1": "asd", "COL2": "zxc"}, - }, - Spec: api.PodSpec{Containers: make([]api.Container, 2)}, - Status: api.PodStatus{ - Phase: "podPhase", - ContainerStatuses: []api.ContainerStatus{ - {Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}}, - {RestartCount: 3}, + podTemplate: api.PodTemplate{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-template-2"}, + Template: api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-template-2"}, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "fake-container1", + Image: "fake-image1", + }, + { + Name: "fake-container2", + Image: "fake-image2", + }, + }, }, }, }, - []string{"col1", "COL2"}, - []string{"asd", "zxc"}, - false, + + options: printers.GenerateOptions{}, + // Columns: Name, Containers, Images, Pod Labels + expected: []metav1beta1.TableRow{{Cells: []interface{}{"pod-template-2", "fake-container1,fake-container2", "fake-image1,fake-image2", ""}}}, + }, + // Test basic pod template with pod labels + { + podTemplate: api.PodTemplate{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-template-3"}, + Template: api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-template-3", + Labels: map[string]string{"foo": "bar"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{}, + }, + }, + }, + + options: printers.GenerateOptions{}, + // Columns: Name, Containers, Images, Pod Labels + expected: []metav1beta1.TableRow{{Cells: []interface{}{"pod-template-3", "", "", "foo=bar"}}}, }, } - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.pod, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printPodTemplate(&test.podTemplate, test.options) if err != nil { t.Fatal(err) } - buf := bytes.NewBuffer([]byte{}) - options := printers.PrintOptions{} - if test.labelsPrinted { - options = printers.PrintOptions{ColumnLabels: test.labelColumns} + for i := range rows { + rows[i].Object.Object = nil } - printer := printers.NewTablePrinter(options) - if err := printer.PrintObj(table, buf); err != nil { - t.Errorf("Error printing table: %v", err) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } + } +} - if test.labelsPrinted { - // Labels columns should be printed. - for _, columnName := range test.labelColumns { - if !strings.Contains(buf.String(), strings.ToUpper(columnName)) { - t.Errorf("Error printing table: expected column %s not printed", columnName) - } - } - for _, labelValue := range test.expectedLabelValues { - if !strings.Contains(buf.String(), labelValue) { - t.Errorf("Error printing table: expected column value %s not printed", labelValue) - } - } - } else { - // Lable columns should not be printed. - for _, columnName := range test.labelColumns { - if strings.Contains(buf.String(), strings.ToUpper(columnName)) { - t.Errorf("Error printing table: expected column %s not printed", columnName) - } - } - for _, labelValue := range test.expectedLabelValues { - if strings.Contains(buf.String(), labelValue) { - t.Errorf("Error printing table: expected column value %s not printed", labelValue) - } - } - } +func TestPrintPodTemplateList(t *testing.T) { + + templateList := api.PodTemplateList{ + Items: []api.PodTemplate{ + { + ObjectMeta: metav1.ObjectMeta{Name: "pod-template-1"}, + Template: api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-template-2", + Labels: map[string]string{"foo": "bar"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "pod-template-2"}, + Template: api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-template-2", + Labels: map[string]string{"a": "b"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{}, + }, + }, + }, + }, + } + + // Columns: Name, Containers, Images, Pod Labels + expectedRows := []metav1beta1.TableRow{ + {Cells: []interface{}{"pod-template-1", "", "", "foo=bar"}}, + {Cells: []interface{}{"pod-template-2", "", "", "a=b"}}, + } + + rows, err := printPodTemplateList(&templateList, printers.GenerateOptions{}) + if err != nil { + t.Fatalf("Error printing pod template list: %#v", err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(expectedRows, rows) { + t.Errorf("mismatch: %s", diff.ObjectReflectDiff(expectedRows, rows)) } } @@ -1646,6 +1999,77 @@ func TestPrintDaemonSet(t *testing.T) { } } +func TestPrintDaemonSetList(t *testing.T) { + + daemonSetList := apps.DaemonSetList{ + Items: []apps.DaemonSet{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "daemonset1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: apps.DaemonSetSpec{ + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "fake-container1", + Image: "fake-image1", + }, + { + Name: "fake-container2", + Image: "fake-image2", + }, + }, + }, + }, + }, + Status: apps.DaemonSetStatus{ + CurrentNumberScheduled: 2, + DesiredNumberScheduled: 3, + NumberReady: 1, + UpdatedNumberScheduled: 2, + NumberAvailable: 0, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "daemonset2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: apps.DaemonSetSpec{ + Template: api.PodTemplateSpec{}, + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + }, + Status: apps.DaemonSetStatus{ + CurrentNumberScheduled: 4, + DesiredNumberScheduled: 2, + NumberReady: 9, + UpdatedNumberScheduled: 3, + NumberAvailable: 3, + }, + }, + }, + } + + // Columns: Name, Num Desired, Num Current, Num Ready, Num Updated, Num Available, Selectors, Age + expectedRows := []metav1beta1.TableRow{ + {Cells: []interface{}{"daemonset1", int64(3), int64(2), int64(1), int64(2), int64(0), "", "0s"}}, + {Cells: []interface{}{"daemonset2", int64(2), int64(4), int64(9), int64(3), int64(3), "", "0s"}}, + } + + rows, err := printDaemonSetList(&daemonSetList, printers.GenerateOptions{}) + if err != nil { + t.Fatalf("Error printing daemon set list: %#v", err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(expectedRows, rows) { + t.Errorf("mismatch: %s", diff.ObjectReflectDiff(expectedRows, rows)) + } +} + func TestPrintJob(t *testing.T) { now := time.Now() completions := int32(2) @@ -1796,6 +2220,86 @@ func TestPrintJob(t *testing.T) { } } +func TestPrintJobList(t *testing.T) { + completions := int32(2) + jobList := batch.JobList{ + Items: []batch.Job{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "job1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: batch.JobSpec{ + Completions: &completions, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "fake-job-container1", + Image: "fake-job-image1", + }, + { + Name: "fake-job-container2", + Image: "fake-job-image2", + }, + }, + }, + }, + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-lable-value"}}, + }, + Status: batch.JobStatus{ + Succeeded: 1, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "job2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: batch.JobSpec{ + Completions: &completions, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "fake-job-container1", + Image: "fake-job-image1", + }, + { + Name: "fake-job-container2", + Image: "fake-job-image2", + }, + }, + }, + }, + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-lable-value"}}, + }, + Status: batch.JobStatus{ + Succeeded: 2, + StartTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)}, + }, + }, + }, + } + + // Columns: Name, Completions, Duration, Age + expectedRows := []metav1beta1.TableRow{ + {Cells: []interface{}{"job1", "1/2", "", "0s"}}, + {Cells: []interface{}{"job2", "2/2", "20m", "0s"}}, + } + + rows, err := printJobList(&jobList, printers.GenerateOptions{}) + if err != nil { + t.Fatalf("Error printing job list: %#v", err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(expectedRows, rows) { + t.Errorf("mismatch: %s", diff.ObjectReflectDiff(expectedRows, rows)) + } +} + func TestPrintHPA(t *testing.T) { minReplicasVal := int32(2) targetUtilizationVal := int32(80) @@ -2692,6 +3196,56 @@ func TestPrintService(t *testing.T) { } } +func TestPrintServiceList(t *testing.T) { + serviceList := api.ServiceList{ + Items: []api.Service{ + { + ObjectMeta: metav1.ObjectMeta{Name: "service1"}, + Spec: api.ServiceSpec{ + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{ + { + Protocol: "tcp", + Port: 2233, + }, + }, + ClusterIP: "10.9.8.7", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "service2"}, + Spec: api.ServiceSpec{ + Type: api.ServiceTypeNodePort, + Ports: []api.ServicePort{ + { + Protocol: "udp", + Port: 5566, + }, + }, + ClusterIP: "1.2.3.4", + }, + }, + }, + } + + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expectedRows := []metav1beta1.TableRow{ + {Cells: []interface{}{"service1", "ClusterIP", "10.9.8.7", "", "2233/tcp", ""}}, + {Cells: []interface{}{"service2", "NodePort", "1.2.3.4", "", "5566/udp", ""}}, + } + + rows, err := printServiceList(&serviceList, printers.GenerateOptions{}) + if err != nil { + t.Fatalf("Error printing service list: %#v", err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(expectedRows, rows) { + t.Errorf("mismatch: %s", diff.ObjectReflectDiff(expectedRows, rows)) + } +} + func TestPrintPodDisruptionBudget(t *testing.T) { minAvailable := intstr.FromInt(22) maxUnavailable := intstr.FromInt(11) @@ -2750,6 +3304,59 @@ func TestPrintPodDisruptionBudget(t *testing.T) { } } +func TestPrintPodDisruptionBudgetList(t *testing.T) { + minAvailable := intstr.FromInt(22) + maxUnavailable := intstr.FromInt(11) + + pdbList := policy.PodDisruptionBudgetList{ + Items: []policy.PodDisruptionBudget{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns1", + Name: "pdb1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: policy.PodDisruptionBudgetSpec{ + MaxUnavailable: &maxUnavailable, + }, + Status: policy.PodDisruptionBudgetStatus{ + PodDisruptionsAllowed: 5, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns2", + Name: "pdb2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: policy.PodDisruptionBudgetSpec{ + MinAvailable: &minAvailable, + }, + Status: policy.PodDisruptionBudgetStatus{ + PodDisruptionsAllowed: 3, + }, + }, + }, + } + + // Columns: Name, Min Available, Max Available, Allowed Disruptions, Age + expectedRows := []metav1beta1.TableRow{ + {Cells: []interface{}{"pdb1", "N/A", "11", int64(5), "0s"}}, + {Cells: []interface{}{"pdb2", "22", "N/A", int64(3), "0s"}}, + } + + rows, err := printPodDisruptionBudgetList(&pdbList, printers.GenerateOptions{}) + if err != nil { + t.Fatalf("Error printing pod template list: %#v", err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(expectedRows, rows) { + t.Errorf("mismatch: %s", diff.ObjectReflectDiff(expectedRows, rows)) + } +} + func TestPrintControllerRevision(t *testing.T) { tests := []struct { history apps.ControllerRevision @@ -2832,6 +3439,490 @@ func boolP(b bool) *bool { return &b } +func TestPrintConfigMap(t *testing.T) { + tests := []struct { + configMap api.ConfigMap + expected []metav1beta1.TableRow + }{ + // Basic config map with no data. + { + configMap: api.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "configmap1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + }, + // Columns: Name, Data, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"configmap1", int64(0), "0s"}}}, + }, + // Basic config map with one data entry + { + configMap: api.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "configmap2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Data: map[string]string{ + "foo": "bar", + }, + }, + // Columns: Name, (Num) Data, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"configmap2", int64(1), "0s"}}}, + }, + // Basic config map with one data and one binary data entry. + { + configMap: api.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "configmap3", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Data: map[string]string{ + "foo": "bar", + }, + BinaryData: map[string][]byte{ + "bin": []byte("binary data"), + }, + }, + // Columns: Name, (Num) Data, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"configmap3", int64(2), "0s"}}}, + }, + } + + for i, test := range tests { + rows, err := printConfigMap(&test.configMap, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + +func TestPrintNetworkPolicy(t *testing.T) { + tests := []struct { + policy networking.NetworkPolicy + expected []metav1beta1.TableRow + }{ + // Basic network policy with empty spec. + { + policy: networking.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "policy1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: networking.NetworkPolicySpec{}, + }, + // Columns: Name, Pod-Selector, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"policy1", "", "0s"}}}, + }, + // Basic network policy with pod selector. + { + policy: networking.NetworkPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "policy2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: networking.NetworkPolicySpec{ + PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + }, + }, + // Columns: Name, Pod-Selector, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"policy2", "foo=bar", "0s"}}}, + }, + } + + for i, test := range tests { + rows, err := printNetworkPolicy(&test.policy, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + +func TestPrintRoleBinding(t *testing.T) { + tests := []struct { + binding rbac.RoleBinding + options printers.GenerateOptions + expected []metav1beta1.TableRow + }{ + // Basic role binding + { + binding: rbac.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "binding1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Subjects: []rbac.Subject{ + { + Kind: "User", + Name: "system:kube-controller-manager", + }, + }, + RoleRef: rbac.RoleRef{ + Kind: "Role", + Name: "extension-apiserver-authentication-reader", + }, + }, + options: printers.GenerateOptions{}, + // Columns: Name, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"binding1", "0s"}}}, + }, + // Generate options=Wide; print subject and roles. + { + binding: rbac.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "binding2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Subjects: []rbac.Subject{ + { + Kind: "User", + Name: "user-name", + }, + { + Kind: "Group", + Name: "group-name", + }, + { + Kind: "ServiceAccount", + Name: "service-account-name", + Namespace: "service-account-namespace", + }, + }, + RoleRef: rbac.RoleRef{ + Kind: "Role", + Name: "role-name", + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, Age, Role, Users, Groups, ServiceAccounts + expected: []metav1beta1.TableRow{{Cells: []interface{}{"binding2", "0s", "Role/role-name", "user-name", "group-name", "service-account-namespace/service-account-name"}}}, + }, + } + + for i, test := range tests { + rows, err := printRoleBinding(&test.binding, test.options) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + +func TestPrintClusterRoleBinding(t *testing.T) { + tests := []struct { + binding rbac.ClusterRoleBinding + options printers.GenerateOptions + expected []metav1beta1.TableRow + }{ + // Basic cluster role binding + { + binding: rbac.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "binding1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Subjects: []rbac.Subject{ + { + Kind: "User", + Name: "system:kube-controller-manager", + }, + }, + RoleRef: rbac.RoleRef{ + Kind: "Role", + Name: "extension-apiserver-authentication-reader", + }, + }, + options: printers.GenerateOptions{}, + // Columns: Name, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"binding1", "0s"}}}, + }, + // Generate options=Wide; print subject and roles. + { + binding: rbac.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "binding2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Subjects: []rbac.Subject{ + { + Kind: "User", + Name: "user-name", + }, + { + Kind: "Group", + Name: "group-name", + }, + { + Kind: "ServiceAccount", + Name: "service-account-name", + Namespace: "service-account-namespace", + }, + }, + RoleRef: rbac.RoleRef{ + Kind: "Role", + Name: "role-name", + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, Age, Role, Users, Groups, ServiceAccounts + expected: []metav1beta1.TableRow{{Cells: []interface{}{"binding2", "0s", "Role/role-name", "user-name", "group-name", "service-account-namespace/service-account-name"}}}, + }, + } + + for i, test := range tests { + rows, err := printClusterRoleBinding(&test.binding, test.options) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} +func TestPrintCertificateSigningRequest(t *testing.T) { + tests := []struct { + csr certificates.CertificateSigningRequest + expected []metav1beta1.TableRow + }{ + // Basic CSR with no spec or status; defaults to status: Pending. + { + csr: certificates.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csr1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: certificates.CertificateSigningRequestSpec{}, + Status: certificates.CertificateSigningRequestStatus{}, + }, + // Columns: Name, Age, Requestor, Condition + expected: []metav1beta1.TableRow{{Cells: []interface{}{"csr1", "0s", "", "Pending"}}}, + }, + // Basic CSR with Spec and Status=Approved. + { + csr: certificates.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csr2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: certificates.CertificateSigningRequestSpec{ + Username: "CSR Requestor", + }, + Status: certificates.CertificateSigningRequestStatus{ + Conditions: []certificates.CertificateSigningRequestCondition{ + { + Type: certificates.CertificateApproved, + }, + }, + }, + }, + // Columns: Name, Age, Requestor, Condition + expected: []metav1beta1.TableRow{{Cells: []interface{}{"csr2", "0s", "CSR Requestor", "Approved"}}}, + }, + // Basic CSR with Spec and Status=Approved; certificate issued. + { + csr: certificates.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csr2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: certificates.CertificateSigningRequestSpec{ + Username: "CSR Requestor", + }, + Status: certificates.CertificateSigningRequestStatus{ + Conditions: []certificates.CertificateSigningRequestCondition{ + { + Type: certificates.CertificateApproved, + }, + }, + Certificate: []byte("cert data"), + }, + }, + // Columns: Name, Age, Requestor, Condition + expected: []metav1beta1.TableRow{{Cells: []interface{}{"csr2", "0s", "CSR Requestor", "Approved,Issued"}}}, + }, + // Basic CSR with Spec and Status=Denied. + { + csr: certificates.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csr3", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: certificates.CertificateSigningRequestSpec{ + Username: "CSR Requestor", + }, + Status: certificates.CertificateSigningRequestStatus{ + Conditions: []certificates.CertificateSigningRequestCondition{ + { + Type: certificates.CertificateDenied, + }, + }, + }, + }, + // Columns: Name, Age, Requestor, Condition + expected: []metav1beta1.TableRow{{Cells: []interface{}{"csr3", "0s", "CSR Requestor", "Denied"}}}, + }, + } + + for i, test := range tests { + rows, err := printCertificateSigningRequest(&test.csr, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + +func TestPrintReplicationController(t *testing.T) { + tests := []struct { + rc api.ReplicationController + options printers.GenerateOptions + expected []metav1beta1.TableRow + }{ + // Basic print replication controller without replicas or status. + { + rc: api.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rc1", + Namespace: "test-namespace", + }, + Spec: api.ReplicationControllerSpec{ + Selector: map[string]string{"a": "b"}, + Template: &api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"a": "b"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "test", + Image: "test_image", + ImagePullPolicy: api.PullIfNotPresent, + TerminationMessagePolicy: api.TerminationMessageReadFile, + }, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, + }, + }, + options: printers.GenerateOptions{}, + // Columns: Name, Desired, Current, Ready, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"rc1", int64(0), int64(0), int64(0), ""}}}, + }, + // Basic print replication controller with replicas; does not print containers or labels + { + rc: api.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rc1", + Namespace: "test-namespace", + }, + Spec: api.ReplicationControllerSpec{ + Replicas: 5, + Selector: map[string]string{"a": "b"}, + Template: &api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"a": "b"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "test", + Image: "test_image", + ImagePullPolicy: api.PullIfNotPresent, + TerminationMessagePolicy: api.TerminationMessageReadFile, + }, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, + }, + Status: api.ReplicationControllerStatus{ + Replicas: 3, + ReadyReplicas: 1, + }, + }, + options: printers.GenerateOptions{}, + // Columns: Name, Desired, Current, Ready, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"rc1", int64(5), int64(3), int64(1), ""}}}, + }, + // Generate options: Wide; print containers and labels. + { + rc: api.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rc1", + }, + Spec: api.ReplicationControllerSpec{ + Replicas: 5, + Selector: map[string]string{"a": "b"}, + Template: &api.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"a": "b"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "test", + Image: "test_image", + ImagePullPolicy: api.PullIfNotPresent, + TerminationMessagePolicy: api.TerminationMessageReadFile, + }, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, + }, + Status: api.ReplicationControllerStatus{ + Replicas: 3, + ReadyReplicas: 1, + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, Desired, Current, Ready, Age, Containers, Images, Selector + expected: []metav1beta1.TableRow{{Cells: []interface{}{"rc1", int64(5), int64(3), int64(1), "", "test", "test_image", "a=b"}}}, + }, + } + + for i, test := range tests { + rows, err := printReplicationController(&test.rc, test.options) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + func TestPrintReplicaSet(t *testing.T) { tests := []struct { replicaSet apps.ReplicaSet @@ -2922,6 +4013,161 @@ func TestPrintReplicaSet(t *testing.T) { } } +func TestPrintReplicaSetList(t *testing.T) { + + replicaSetList := apps.ReplicaSetList{ + Items: []apps.ReplicaSet{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "replicaset1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: apps.ReplicaSetSpec{ + Replicas: 5, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "fake-container1", + Image: "fake-image1", + }, + { + Name: "fake-container2", + Image: "fake-image2", + }, + }, + }, + }, + }, + Status: apps.ReplicaSetStatus{ + Replicas: 5, + ReadyReplicas: 2, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "replicaset2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: apps.ReplicaSetSpec{ + Replicas: 4, + Template: api.PodTemplateSpec{}, + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + }, + Status: apps.ReplicaSetStatus{ + Replicas: 3, + ReadyReplicas: 1, + }, + }, + }, + } + + // Columns: Name, Desired, Current, Ready, Age + expectedRows := []metav1beta1.TableRow{ + {Cells: []interface{}{"replicaset1", int64(5), int64(5), int64(2), "0s"}}, + {Cells: []interface{}{"replicaset2", int64(4), int64(3), int64(1), "0s"}}, + } + + rows, err := printReplicaSetList(&replicaSetList, printers.GenerateOptions{}) + if err != nil { + t.Fatalf("Error printing replica set list: %#v", err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(expectedRows, rows) { + t.Errorf("mismatch: %s", diff.ObjectReflectDiff(expectedRows, rows)) + } +} + +func TestPrintStatefulSet(t *testing.T) { + tests := []struct { + statefulSet apps.StatefulSet + options printers.GenerateOptions + expected []metav1beta1.TableRow + }{ + // Basic stateful set; no generate options. + { + statefulSet: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: apps.StatefulSetSpec{ + Replicas: 5, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "fake-container1", + Image: "fake-image1", + }, + { + Name: "fake-container2", + Image: "fake-image2", + }, + }, + }, + }, + }, + Status: apps.StatefulSetStatus{ + Replicas: 5, + ReadyReplicas: 2, + }, + }, + options: printers.GenerateOptions{}, + // Columns: Name, Ready, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", "2/5", "0s"}}}, + }, + // Generate options "Wide"; includes containers and images. + { + statefulSet: apps.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: apps.StatefulSetSpec{ + Replicas: 5, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "fake-container1", + Image: "fake-image1", + }, + { + Name: "fake-container2", + Image: "fake-image2", + }, + }, + }, + }, + }, + Status: apps.StatefulSetStatus{ + Replicas: 5, + ReadyReplicas: 2, + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, Ready, Age, Containers, Images + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", "2/5", "0s", "fake-container1,fake-container2", "fake-image1,fake-image2"}}}, + }, + } + + for i, test := range tests { + rows, err := printStatefulSet(&test.statefulSet, test.options) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + func TestPrintPersistentVolume(t *testing.T) { myScn := "my-scn" @@ -3189,6 +4435,74 @@ func TestPrintPersistentVolumeClaim(t *testing.T) { } } +func TestPrintComponentStatus(t *testing.T) { + tests := []struct { + componentStatus api.ComponentStatus + expected []metav1beta1.TableRow + }{ + // Basic component status without conditions + { + componentStatus: api.ComponentStatus{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cs1", + }, + Conditions: []api.ComponentCondition{}, + }, + // Columns: Name, Status, Message, Error + expected: []metav1beta1.TableRow{{Cells: []interface{}{"cs1", "Unknown", "", ""}}}, + }, + // Basic component status with healthy condition. + { + componentStatus: api.ComponentStatus{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cs2", + }, + Conditions: []api.ComponentCondition{ + { + Type: "Healthy", + Status: api.ConditionTrue, + Message: "test message", + Error: "test error", + }, + }, + }, + // Columns: Name, Status, Message, Error + expected: []metav1beta1.TableRow{{Cells: []interface{}{"cs2", "Healthy", "test message", "test error"}}}, + }, + // Basic component status with healthy condition. + { + componentStatus: api.ComponentStatus{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cs3", + }, + Conditions: []api.ComponentCondition{ + { + Type: "Healthy", + Status: api.ConditionFalse, + Message: "test message", + Error: "test error", + }, + }, + }, + // Columns: Name, Status, Message, Error + expected: []metav1beta1.TableRow{{Cells: []interface{}{"cs3", "Unhealthy", "test message", "test error"}}}, + }, + } + + for i, test := range tests { + rows, err := printComponentStatus(&test.componentStatus, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } +} + func TestPrintCronJob(t *testing.T) { completions := int32(2) suspend := false @@ -3327,6 +4641,71 @@ func TestPrintCronJob(t *testing.T) { } } +func TestPrintCronJobList(t *testing.T) { + completions := int32(2) + suspend := false + + cronJobList := batch.CronJobList{ + Items: []batch.CronJob{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cronjob1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: batch.CronJobSpec{ + Schedule: "0/5 * * * ?", + Suspend: &suspend, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Completions: &completions, + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + }, + }, + }, + Status: batch.CronJobStatus{ + LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cronjob2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: batch.CronJobSpec{ + Schedule: "4/5 1 1 1 ?", + Suspend: &suspend, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Completions: &completions, + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + }, + }, + }, + Status: batch.CronJobStatus{ + LastScheduleTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)}, + }, + }, + }, + } + + // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age + expectedRows := []metav1beta1.TableRow{ + {Cells: []interface{}{"cronjob1", "0/5 * * * ?", "False", int64(0), "0s", "0s"}}, + {Cells: []interface{}{"cronjob2", "4/5 1 1 1 ?", "False", int64(0), "20m", "0s"}}, + } + + rows, err := printCronJobList(&cronJobList, printers.GenerateOptions{}) + if err != nil { + t.Fatalf("Error printing job list: %#v", err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(expectedRows, rows) { + t.Errorf("mismatch: %s", diff.ObjectReflectDiff(expectedRows, rows)) + } +} + func TestPrintStorageClass(t *testing.T) { tests := []struct { sc storage.StorageClass @@ -3498,6 +4877,127 @@ func TestPrintRuntimeClass(t *testing.T) { } } +func TestPrintEndpoint(t *testing.T) { + + tests := []struct { + endpoint api.Endpoints + expected []metav1beta1.TableRow + }{ + // Basic endpoint with no IP's + { + endpoint: api.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "endpoint1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + }, + // Columns: Name, Endpoints, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"endpoint1", "", "0s"}}}, + }, + // Endpoint with no ports + { + endpoint: api.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "endpoint3", + CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, + }, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{ + { + IP: "1.2.3.4", + }, + { + IP: "5.6.7.8", + }, + }, + }, + }, + }, + // Columns: Name, Endpoints, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"endpoint3", "1.2.3.4,5.6.7.8", "5m"}}}, + }, + // Basic endpoint with two IP's and one port + { + endpoint: api.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "endpoint2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{ + { + IP: "1.2.3.4", + }, + { + IP: "5.6.7.8", + }, + }, + Ports: []api.EndpointPort{ + { + Port: 8001, + Protocol: "tcp", + }, + }, + }, + }, + }, + // Columns: Name, Endpoints, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"endpoint2", "1.2.3.4:8001,5.6.7.8:8001", "0s"}}}, + }, + // Basic endpoint with greater than three IP's triggering "more" string + { + endpoint: api.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "endpoint2", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{ + { + IP: "1.2.3.4", + }, + { + IP: "5.6.7.8", + }, + { + IP: "9.8.7.6", + }, + { + IP: "6.6.6.6", + }, + }, + Ports: []api.EndpointPort{ + { + Port: 8001, + Protocol: "tcp", + }, + }, + }, + }, + }, + // Columns: Name, Endpoints, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"endpoint2", "1.2.3.4:8001,5.6.7.8:8001,9.8.7.6:8001 + 1 more...", "0s"}}}, + }, + } + + for i, test := range tests { + rows, err := printEndpoints(&test.endpoint, printers.GenerateOptions{}) + if err != nil { + t.Fatal(err) + } + for i := range rows { + rows[i].Object.Object = nil + } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) + } + } + +} + func TestPrintEndpointSlice(t *testing.T) { ipAddressType := discovery.AddressTypeIP tcpProtocol := api.ProtocolTCP