From 3e7d8bc64ec646efef2be709919293b068f9fc1f Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Fri, 11 Oct 2019 11:29:49 -0700 Subject: [PATCH 1/5] Moves tangential tests into another file. --- pkg/printers/internalversion/BUILD | 3 +- .../additional_printers_test.go | 589 ++++++++++++++++++ pkg/printers/internalversion/printers_test.go | 545 ---------------- 3 files changed, 591 insertions(+), 546 deletions(-) create mode 100644 pkg/printers/internalversion/additional_printers_test.go diff --git a/pkg/printers/internalversion/BUILD b/pkg/printers/internalversion/BUILD index e8ea8e8d917..bd200acae23 100644 --- a/pkg/printers/internalversion/BUILD +++ b/pkg/printers/internalversion/BUILD @@ -9,6 +9,7 @@ load( go_test( name = "go_default_test", srcs = [ + "additional_printers_test.go", "printers_test.go", "sorted_resource_name_list_test.go", ], @@ -41,8 +42,8 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/printers:go_default_library", + "//vendor/gopkg.in/yaml.v2:go_default_library", "//vendor/k8s.io/utils/pointer:go_default_library", - "//vendor/sigs.k8s.io/yaml:go_default_library", ], ) diff --git a/pkg/printers/internalversion/additional_printers_test.go b/pkg/printers/internalversion/additional_printers_test.go new file mode 100644 index 00000000000..48efac4a860 --- /dev/null +++ b/pkg/printers/internalversion/additional_printers_test.go @@ -0,0 +1,589 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internalversion + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "regexp" + "testing" + + yaml "gopkg.in/yaml.v2" + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml" + "k8s.io/apimachinery/pkg/util/diff" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/cli-runtime/pkg/genericclioptions" + genericprinters "k8s.io/cli-runtime/pkg/printers" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/printers" +) + +/////////////////////////////////////////////////////////////// +// +// These tests do not belong in this package, and they +// should be moved (mostly to cli-runtime). seans3. +// +///////////////////////////////////////////////////////////// + +var testData = TestStruct{ + TypeMeta: metav1.TypeMeta{APIVersion: "foo/bar", Kind: "TestStruct"}, + Key: "testValue", + Map: map[string]int{"TestSubkey": 1}, + StringList: []string{"a", "b", "c"}, + IntList: []int{1, 2, 3}, +} + +type TestStruct struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Key string `json:"Key"` + Map map[string]int `json:"Map"` + StringList []string `json:"StringList"` + IntList []int `json:"IntList"` +} + +func (in *TestStruct) DeepCopyObject() runtime.Object { + panic("never called") +} + +// TODO(seans3): Move this test to tableprinter_test.go +func TestPrintUnstructuredObject(t *testing.T) { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Test", + "dummy1": "present", + "dummy2": "present", + "metadata": map[string]interface{}{ + "name": "MyName", + "namespace": "MyNamespace", + "creationTimestamp": "2017-04-01T00:00:00Z", + "resourceVersion": 123, + "uid": "00000000-0000-0000-0000-000000000001", + "dummy3": "present", + "labels": map[string]interface{}{"test": "other"}, + }, + /*"items": []interface{}{ + map[string]interface{}{ + "itemBool": true, + "itemInt": 42, + }, + },*/ + "url": "http://localhost", + "status": "ok", + }, + } + + tests := []struct { + expected string + options printers.PrintOptions + object runtime.Object + }{ + { + expected: "NAME\\s+AGE\nMyName\\s+\\d+", + object: obj, + }, + { + options: printers.PrintOptions{ + WithNamespace: true, + }, + expected: "NAMESPACE\\s+NAME\\s+AGE\nMyNamespace\\s+MyName\\s+\\d+", + object: obj, + }, + { + options: printers.PrintOptions{ + ShowLabels: true, + WithNamespace: true, + }, + expected: "NAMESPACE\\s+NAME\\s+AGE\\s+LABELS\nMyNamespace\\s+MyName\\s+\\d+\\w+\\s+test\\=other", + object: obj, + }, + { + expected: "NAME\\s+AGE\nMyName\\s+\\d+\\w+\nMyName2\\s+\\d+", + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Test", + "dummy1": "present", + "dummy2": "present", + "items": []interface{}{ + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "MyName", + "namespace": "MyNamespace", + "creationTimestamp": "2017-04-01T00:00:00Z", + "resourceVersion": 123, + "uid": "00000000-0000-0000-0000-000000000001", + "dummy3": "present", + "labels": map[string]interface{}{"test": "other"}, + }, + }, + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "MyName2", + "namespace": "MyNamespace", + "creationTimestamp": "2017-04-01T00:00:00Z", + "resourceVersion": 123, + "uid": "00000000-0000-0000-0000-000000000001", + "dummy3": "present", + "labels": "badlabel", + }, + }, + }, + "url": "http://localhost", + "status": "ok", + }, + }, + }, + } + out := bytes.NewBuffer([]byte{}) + + for _, test := range tests { + out.Reset() + printer := printers.NewTablePrinter(test.options) + printer.PrintObj(test.object, out) + + matches, err := regexp.MatchString(test.expected, out.String()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !matches { + t.Errorf("wanted:\n%s\ngot:\n%s", test.expected, out) + } + } +} + +type TestPrintType struct { + Data string +} + +func (obj *TestPrintType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (obj *TestPrintType) DeepCopyObject() runtime.Object { + if obj == nil { + return nil + } + clone := *obj + return &clone +} + +type TestUnknownType struct{} + +func (obj *TestUnknownType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (obj *TestUnknownType) DeepCopyObject() runtime.Object { + if obj == nil { + return nil + } + clone := *obj + return &clone +} + +// TODO(seans3): Move this test to cli-runtime/pkg/printers. +func testPrinter(t *testing.T, printer printers.ResourcePrinter, unmarshalFunc func(data []byte, v interface{}) error) { + buf := bytes.NewBuffer([]byte{}) + + err := printer.PrintObj(&testData, buf) + if err != nil { + t.Fatal(err) + } + var poutput TestStruct + // Verify that given function runs without error. + err = unmarshalFunc(buf.Bytes(), &poutput) + if err != nil { + t.Fatal(err) + } + // Use real decode function to undo the versioning process. + poutput = TestStruct{} + s := yamlserializer.NewDecodingSerializer(testapi.Default.Codec()) + if err := runtime.DecodeInto(s, buf.Bytes(), &poutput); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(testData, poutput) { + t.Errorf("Test data and unmarshaled data are not equal: %v", diff.ObjectDiff(poutput, testData)) + } + + obj := &v1.Pod{ + TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"}, + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + } + // our decoder defaults, so we should default our expected object as well + legacyscheme.Scheme.Default(obj) + buf.Reset() + printer.PrintObj(obj, buf) + var objOut v1.Pod + // Verify that given function runs without error. + err = unmarshalFunc(buf.Bytes(), &objOut) + if err != nil { + t.Fatalf("unexpected error: %#v", err) + } + // Use real decode function to undo the versioning process. + objOut = v1.Pod{} + if err := runtime.DecodeInto(s, buf.Bytes(), &objOut); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(obj, &objOut) { + t.Errorf("Unexpected inequality:\n%v", diff.ObjectDiff(obj, &objOut)) + } +} + +func yamlUnmarshal(data []byte, v interface{}) error { + return yaml.Unmarshal(data, v) +} + +// TODO(seans3): Move this test to cli-runtime/pkg/printers. +func TestYAMLPrinter(t *testing.T) { + testPrinter(t, genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.YAMLPrinter{}), yamlUnmarshal) +} + +// TODO(seans3): Move this test to cli-runtime/pkg/printers. +func TestJSONPrinter(t *testing.T) { + testPrinter(t, genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.JSONPrinter{}), json.Unmarshal) +} + +// TODO(seans3): Move this test to cli-runtime/pkg/printers. +func TestFormatResourceName(t *testing.T) { + tests := []struct { + kind schema.GroupKind + name string + want string + }{ + {schema.GroupKind{}, "", ""}, + {schema.GroupKind{}, "name", "name"}, + {schema.GroupKind{Kind: "Kind"}, "", "kind/"}, // should not happen in practice + {schema.GroupKind{Kind: "Kind"}, "name", "kind/name"}, + {schema.GroupKind{Group: "group", Kind: "Kind"}, "name", "kind.group/name"}, + } + for _, tt := range tests { + if got := formatResourceName(tt.kind, tt.name, true); got != tt.want { + t.Errorf("formatResourceName(%q, %q) = %q, want %q", tt.kind, tt.name, got, tt.want) + } + } +} + +func PrintCustomType(obj *TestPrintType, options printers.GenerateOptions) ([]metav1beta1.TableRow, error) { + return []metav1beta1.TableRow{{Cells: []interface{}{obj.Data}}}, nil +} + +func ErrorPrintHandler(obj *TestPrintType, options printers.GenerateOptions) ([]metav1beta1.TableRow, error) { + return nil, fmt.Errorf("ErrorPrintHandler error") +} + +func TestCustomTypePrinting(t *testing.T) { + columns := []metav1beta1.TableColumnDefinition{{Name: "Data"}} + generator := printers.NewTableGenerator() + generator.TableHandler(columns, PrintCustomType) + + obj := TestPrintType{"test object"} + table, err := generator.GenerateTable(&obj, printers.GenerateOptions{}) + if err != nil { + t.Fatalf("An error occurred generating the table for custom type: %#v", err) + } + + printer := printers.NewTablePrinter(printers.PrintOptions{}) + buffer := &bytes.Buffer{} + err = printer.PrintObj(table, buffer) + if err != nil { + t.Fatalf("An error occurred printing the Table: %#v", err) + } + + expectedOutput := "DATA\ntest object\n" + if buffer.String() != expectedOutput { + t.Errorf("The data was not printed as expected. Expected:\n%s\nGot:\n%s", expectedOutput, buffer.String()) + } +} + +func TestPrintHandlerError(t *testing.T) { + columns := []metav1beta1.TableColumnDefinition{{Name: "Data"}} + generator := printers.NewTableGenerator() + generator.TableHandler(columns, ErrorPrintHandler) + obj := TestPrintType{"test object"} + _, err := generator.GenerateTable(&obj, printers.GenerateOptions{}) + if err == nil || err.Error() != "ErrorPrintHandler error" { + t.Errorf("Did not get the expected error: %#v", err) + } +} + +func TestUnknownTypePrinting(t *testing.T) { + printer := printers.NewTablePrinter(printers.PrintOptions{}) + buffer := &bytes.Buffer{} + err := printer.PrintObj(&TestUnknownType{}, buffer) + if err == nil { + t.Errorf("An error was expected from printing unknown type") + } +} + +func TestTemplatePanic(t *testing.T) { + tmpl := `{{and ((index .currentState.info "foo").state.running.startedAt) .currentState.info.net.state.running.startedAt}}` + printer, err := genericprinters.NewGoTemplatePrinter([]byte(tmpl)) + if err != nil { + t.Fatalf("tmpl fail: %v", err) + } + buffer := &bytes.Buffer{} + err = printer.PrintObj(&v1.Pod{}, buffer) + if err == nil { + t.Fatalf("expected that template to crash") + } + if buffer.String() == "" { + t.Errorf("no debugging info was printed") + } +} + +func TestNamePrinter(t *testing.T) { + tests := map[string]struct { + obj runtime.Object + expect string + }{ + "singleObject": { + &v1.Pod{ + TypeMeta: metav1.TypeMeta{ + Kind: "Pod", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, + "pod/foo\n"}, + "List": { + &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", + "apiVersion": "v1", + }, + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "Pod", + "apiVersion": "v1", + "metadata": map[string]interface{}{ + "name": "bar", + }, + }, + }, + }, + }, + "pod/bar\n"}, + } + + printFlags := genericclioptions.NewPrintFlags("").WithTypeSetter(legacyscheme.Scheme).WithDefaultOutput("name") + printer, err := printFlags.ToPrinter() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + for name, item := range tests { + buff := &bytes.Buffer{} + err := printer.PrintObj(item.obj, buff) + if err != nil { + t.Errorf("%v: unexpected err: %v", name, err) + continue + } + got := buff.String() + if item.expect != got { + t.Errorf("%v: expected %v, got %v", name, item.expect, got) + } + } +} + +// TODO(seans3): Move this test to cli-runtime/pkg/printers. +func TestTemplateStrings(t *testing.T) { + // This unit tests the "exists" function as well as the template from update.sh + table := map[string]struct { + pod v1.Pod + expect string + }{ + "nilInfo": {v1.Pod{}, "false"}, + "emptyInfo": {v1.Pod{Status: v1.PodStatus{ContainerStatuses: []v1.ContainerStatus{}}}, "false"}, + "fooExists": { + v1.Pod{ + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "foo", + }, + }, + }, + }, + "false", + }, + "barExists": { + v1.Pod{ + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "bar", + }, + }, + }, + }, + "false", + }, + "bothExist": { + v1.Pod{ + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "foo", + }, + { + Name: "bar", + }, + }, + }, + }, + "false", + }, + "barValid": { + v1.Pod{ + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "foo", + }, + { + Name: "bar", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{ + StartedAt: metav1.Time{}, + }, + }, + }, + }, + }, + }, + "false", + }, + "bothValid": { + v1.Pod{ + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "foo", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{ + StartedAt: metav1.Time{}, + }, + }, + }, + { + Name: "bar", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{ + StartedAt: metav1.Time{}, + }, + }, + }, + }, + }, + }, + "true", + }, + } + // The point of this test is to verify that the below template works. + tmpl := `{{if (exists . "status" "containerStatuses")}}{{range .status.containerStatuses}}{{if (and (eq .name "foo") (exists . "state" "running"))}}true{{end}}{{end}}{{end}}` + printer, err := genericprinters.NewGoTemplatePrinter([]byte(tmpl)) + if err != nil { + t.Fatalf("tmpl fail: %v", err) + } + + for name, item := range table { + buffer := &bytes.Buffer{} + err = printer.PrintObj(&item.pod, buffer) + if err != nil { + t.Errorf("%v: unexpected err: %v", name, err) + continue + } + actual := buffer.String() + if len(actual) == 0 { + actual = "false" + } + if e := item.expect; e != actual { + t.Errorf("%v: expected %v, got %v", name, e, actual) + } + } +} + +// TODO(seans3): Move this test to cli-runtime/pkg/printers. +func TestPrinters(t *testing.T) { + om := func(name string) metav1.ObjectMeta { return metav1.ObjectMeta{Name: name} } + + var ( + err error + templatePrinter printers.ResourcePrinter + templatePrinter2 printers.ResourcePrinter + jsonpathPrinter printers.ResourcePrinter + ) + + templatePrinter, err = genericprinters.NewGoTemplatePrinter([]byte("{{.name}}")) + if err != nil { + t.Fatal(err) + } + + templatePrinter2, err = genericprinters.NewGoTemplatePrinter([]byte("{{len .items}}")) + if err != nil { + t.Fatal(err) + } + + jsonpathPrinter, err = genericprinters.NewJSONPathPrinter("{.metadata.name}") + if err != nil { + t.Fatal(err) + } + + genericPrinters := map[string]printers.ResourcePrinter{ + // TODO(juanvallejo): move "generic printer" tests to pkg/kubectl/genericclioptions/printers + "json": genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.JSONPrinter{}), + "yaml": genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.YAMLPrinter{}), + "template": templatePrinter, + "template2": templatePrinter2, + "jsonpath": jsonpathPrinter, + } + objects := map[string]runtime.Object{ + "pod": &v1.Pod{ObjectMeta: om("pod")}, + "emptyPodList": &v1.PodList{}, + "nonEmptyPodList": &v1.PodList{Items: []v1.Pod{{}}}, + "endpoints": &v1.Endpoints{ + Subsets: []v1.EndpointSubset{{ + Addresses: []v1.EndpointAddress{{IP: "127.0.0.1"}, {IP: "localhost"}}, + Ports: []v1.EndpointPort{{Port: 8080}}, + }}}, + } + // map of printer name to set of objects it should fail on. + expectedErrors := map[string]sets.String{ + "template2": sets.NewString("pod", "emptyPodList", "endpoints"), + "jsonpath": sets.NewString("emptyPodList", "nonEmptyPodList", "endpoints"), + } + + for pName, p := range genericPrinters { + for oName, obj := range objects { + b := &bytes.Buffer{} + if err := p.PrintObj(obj, b); err != nil { + if set, found := expectedErrors[pName]; found && set.Has(oName) { + // expected error + continue + } + t.Errorf("printer '%v', object '%v'; error: '%v'", pName, oName, err) + } + } + } +} diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index 3f50f8c9a0c..ab1c279ef5f 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -18,32 +18,20 @@ package internalversion import ( "bytes" - "encoding/json" "fmt" "reflect" - "regexp" "strconv" "strings" "testing" "time" - "sigs.k8s.io/yaml" - - "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/cli-runtime/pkg/genericclioptions" - genericprinters "k8s.io/cli-runtime/pkg/printers" - "k8s.io/kubernetes/pkg/api/legacyscheme" - "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" @@ -59,539 +47,6 @@ import ( utilpointer "k8s.io/utils/pointer" ) -var testData = TestStruct{ - TypeMeta: metav1.TypeMeta{APIVersion: "foo/bar", Kind: "TestStruct"}, - Key: "testValue", - Map: map[string]int{"TestSubkey": 1}, - StringList: []string{"a", "b", "c"}, - IntList: []int{1, 2, 3}, -} - -type TestStruct struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Key string `json:"Key"` - Map map[string]int `json:"Map"` - StringList []string `json:"StringList"` - IntList []int `json:"IntList"` -} - -func (in *TestStruct) DeepCopyObject() runtime.Object { - panic("never called") -} - -func TestPrintUnstructuredObject(t *testing.T) { - obj := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Test", - "dummy1": "present", - "dummy2": "present", - "metadata": map[string]interface{}{ - "name": "MyName", - "namespace": "MyNamespace", - "creationTimestamp": "2017-04-01T00:00:00Z", - "resourceVersion": 123, - "uid": "00000000-0000-0000-0000-000000000001", - "dummy3": "present", - "labels": map[string]interface{}{"test": "other"}, - }, - /*"items": []interface{}{ - map[string]interface{}{ - "itemBool": true, - "itemInt": 42, - }, - },*/ - "url": "http://localhost", - "status": "ok", - }, - } - - tests := []struct { - expected string - options printers.PrintOptions - object runtime.Object - }{ - { - expected: "NAME\\s+AGE\nMyName\\s+\\d+", - object: obj, - }, - { - options: printers.PrintOptions{ - WithNamespace: true, - }, - expected: "NAMESPACE\\s+NAME\\s+AGE\nMyNamespace\\s+MyName\\s+\\d+", - object: obj, - }, - { - options: printers.PrintOptions{ - ShowLabels: true, - WithNamespace: true, - }, - expected: "NAMESPACE\\s+NAME\\s+AGE\\s+LABELS\nMyNamespace\\s+MyName\\s+\\d+\\w+\\s+test\\=other", - object: obj, - }, - { - expected: "NAME\\s+AGE\nMyName\\s+\\d+\\w+\nMyName2\\s+\\d+", - object: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Test", - "dummy1": "present", - "dummy2": "present", - "items": []interface{}{ - map[string]interface{}{ - "metadata": map[string]interface{}{ - "name": "MyName", - "namespace": "MyNamespace", - "creationTimestamp": "2017-04-01T00:00:00Z", - "resourceVersion": 123, - "uid": "00000000-0000-0000-0000-000000000001", - "dummy3": "present", - "labels": map[string]interface{}{"test": "other"}, - }, - }, - map[string]interface{}{ - "metadata": map[string]interface{}{ - "name": "MyName2", - "namespace": "MyNamespace", - "creationTimestamp": "2017-04-01T00:00:00Z", - "resourceVersion": 123, - "uid": "00000000-0000-0000-0000-000000000001", - "dummy3": "present", - "labels": "badlabel", - }, - }, - }, - "url": "http://localhost", - "status": "ok", - }, - }, - }, - } - out := bytes.NewBuffer([]byte{}) - - for _, test := range tests { - out.Reset() - printer := printers.NewTablePrinter(test.options) - printer.PrintObj(test.object, out) - - matches, err := regexp.MatchString(test.expected, out.String()) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !matches { - t.Errorf("wanted:\n%s\ngot:\n%s", test.expected, out) - } - } -} - -type TestPrintType struct { - Data string -} - -func (obj *TestPrintType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } -func (obj *TestPrintType) DeepCopyObject() runtime.Object { - if obj == nil { - return nil - } - clone := *obj - return &clone -} - -type TestUnknownType struct{} - -func (obj *TestUnknownType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } -func (obj *TestUnknownType) DeepCopyObject() runtime.Object { - if obj == nil { - return nil - } - clone := *obj - return &clone -} - -func testPrinter(t *testing.T, printer printers.ResourcePrinter, unmarshalFunc func(data []byte, v interface{}) error) { - buf := bytes.NewBuffer([]byte{}) - - err := printer.PrintObj(&testData, buf) - if err != nil { - t.Fatal(err) - } - var poutput TestStruct - // Verify that given function runs without error. - err = unmarshalFunc(buf.Bytes(), &poutput) - if err != nil { - t.Fatal(err) - } - // Use real decode function to undo the versioning process. - poutput = TestStruct{} - s := yamlserializer.NewDecodingSerializer(testapi.Default.Codec()) - if err := runtime.DecodeInto(s, buf.Bytes(), &poutput); err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(testData, poutput) { - t.Errorf("Test data and unmarshaled data are not equal: %v", diff.ObjectDiff(poutput, testData)) - } - - obj := &v1.Pod{ - TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"}, - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - } - // our decoder defaults, so we should default our expected object as well - legacyscheme.Scheme.Default(obj) - buf.Reset() - printer.PrintObj(obj, buf) - var objOut v1.Pod - // Verify that given function runs without error. - err = unmarshalFunc(buf.Bytes(), &objOut) - if err != nil { - t.Fatalf("unexpected error: %#v", err) - } - // Use real decode function to undo the versioning process. - objOut = v1.Pod{} - if err := runtime.DecodeInto(s, buf.Bytes(), &objOut); err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(obj, &objOut) { - t.Errorf("Unexpected inequality:\n%v", diff.ObjectDiff(obj, &objOut)) - } -} - -func yamlUnmarshal(data []byte, v interface{}) error { - return yaml.Unmarshal(data, v) -} - -func TestYAMLPrinter(t *testing.T) { - testPrinter(t, genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.YAMLPrinter{}), yamlUnmarshal) -} - -func TestJSONPrinter(t *testing.T) { - testPrinter(t, genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.JSONPrinter{}), json.Unmarshal) -} - -func TestFormatResourceName(t *testing.T) { - tests := []struct { - kind schema.GroupKind - name string - want string - }{ - {schema.GroupKind{}, "", ""}, - {schema.GroupKind{}, "name", "name"}, - {schema.GroupKind{Kind: "Kind"}, "", "kind/"}, // should not happen in practice - {schema.GroupKind{Kind: "Kind"}, "name", "kind/name"}, - {schema.GroupKind{Group: "group", Kind: "Kind"}, "name", "kind.group/name"}, - } - for _, tt := range tests { - if got := formatResourceName(tt.kind, tt.name, true); got != tt.want { - t.Errorf("formatResourceName(%q, %q) = %q, want %q", tt.kind, tt.name, got, tt.want) - } - } -} - -func PrintCustomType(obj *TestPrintType, options printers.GenerateOptions) ([]metav1beta1.TableRow, error) { - return []metav1beta1.TableRow{{Cells: []interface{}{obj.Data}}}, nil -} - -func ErrorPrintHandler(obj *TestPrintType, options printers.GenerateOptions) ([]metav1beta1.TableRow, error) { - return nil, fmt.Errorf("ErrorPrintHandler error") -} - -func TestCustomTypePrinting(t *testing.T) { - columns := []metav1beta1.TableColumnDefinition{{Name: "Data"}} - generator := printers.NewTableGenerator() - generator.TableHandler(columns, PrintCustomType) - - obj := TestPrintType{"test object"} - table, err := generator.GenerateTable(&obj, printers.GenerateOptions{}) - if err != nil { - t.Fatalf("An error occurred generating the table for custom type: %#v", err) - } - - printer := printers.NewTablePrinter(printers.PrintOptions{}) - buffer := &bytes.Buffer{} - err = printer.PrintObj(table, buffer) - if err != nil { - t.Fatalf("An error occurred printing the Table: %#v", err) - } - - expectedOutput := "DATA\ntest object\n" - if buffer.String() != expectedOutput { - t.Errorf("The data was not printed as expected. Expected:\n%s\nGot:\n%s", expectedOutput, buffer.String()) - } -} - -func TestPrintHandlerError(t *testing.T) { - columns := []metav1beta1.TableColumnDefinition{{Name: "Data"}} - generator := printers.NewTableGenerator() - generator.TableHandler(columns, ErrorPrintHandler) - obj := TestPrintType{"test object"} - _, err := generator.GenerateTable(&obj, printers.GenerateOptions{}) - if err == nil || err.Error() != "ErrorPrintHandler error" { - t.Errorf("Did not get the expected error: %#v", err) - } -} - -func TestUnknownTypePrinting(t *testing.T) { - printer := printers.NewTablePrinter(printers.PrintOptions{}) - buffer := &bytes.Buffer{} - err := printer.PrintObj(&TestUnknownType{}, buffer) - if err == nil { - t.Errorf("An error was expected from printing unknown type") - } -} - -func TestTemplatePanic(t *testing.T) { - tmpl := `{{and ((index .currentState.info "foo").state.running.startedAt) .currentState.info.net.state.running.startedAt}}` - printer, err := genericprinters.NewGoTemplatePrinter([]byte(tmpl)) - if err != nil { - t.Fatalf("tmpl fail: %v", err) - } - buffer := &bytes.Buffer{} - err = printer.PrintObj(&v1.Pod{}, buffer) - if err == nil { - t.Fatalf("expected that template to crash") - } - if buffer.String() == "" { - t.Errorf("no debugging info was printed") - } -} - -func TestNamePrinter(t *testing.T) { - tests := map[string]struct { - obj runtime.Object - expect string - }{ - "singleObject": { - &v1.Pod{ - TypeMeta: metav1.TypeMeta{ - Kind: "Pod", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - }, - "pod/foo\n"}, - "List": { - &unstructured.UnstructuredList{ - Object: map[string]interface{}{ - "kind": "List", - "apiVersion": "v1", - }, - Items: []unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Pod", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "bar", - }, - }, - }, - }, - }, - "pod/bar\n"}, - } - - printFlags := genericclioptions.NewPrintFlags("").WithTypeSetter(legacyscheme.Scheme).WithDefaultOutput("name") - printer, err := printFlags.ToPrinter() - if err != nil { - t.Fatalf("unexpected err: %v", err) - } - - for name, item := range tests { - buff := &bytes.Buffer{} - err := printer.PrintObj(item.obj, buff) - if err != nil { - t.Errorf("%v: unexpected err: %v", name, err) - continue - } - got := buff.String() - if item.expect != got { - t.Errorf("%v: expected %v, got %v", name, item.expect, got) - } - } -} - -func TestTemplateStrings(t *testing.T) { - // This unit tests the "exists" function as well as the template from update.sh - table := map[string]struct { - pod v1.Pod - expect string - }{ - "nilInfo": {v1.Pod{}, "false"}, - "emptyInfo": {v1.Pod{Status: v1.PodStatus{ContainerStatuses: []v1.ContainerStatus{}}}, "false"}, - "fooExists": { - v1.Pod{ - Status: v1.PodStatus{ - ContainerStatuses: []v1.ContainerStatus{ - { - Name: "foo", - }, - }, - }, - }, - "false", - }, - "barExists": { - v1.Pod{ - Status: v1.PodStatus{ - ContainerStatuses: []v1.ContainerStatus{ - { - Name: "bar", - }, - }, - }, - }, - "false", - }, - "bothExist": { - v1.Pod{ - Status: v1.PodStatus{ - ContainerStatuses: []v1.ContainerStatus{ - { - Name: "foo", - }, - { - Name: "bar", - }, - }, - }, - }, - "false", - }, - "barValid": { - v1.Pod{ - Status: v1.PodStatus{ - ContainerStatuses: []v1.ContainerStatus{ - { - Name: "foo", - }, - { - Name: "bar", - State: v1.ContainerState{ - Running: &v1.ContainerStateRunning{ - StartedAt: metav1.Time{}, - }, - }, - }, - }, - }, - }, - "false", - }, - "bothValid": { - v1.Pod{ - Status: v1.PodStatus{ - ContainerStatuses: []v1.ContainerStatus{ - { - Name: "foo", - State: v1.ContainerState{ - Running: &v1.ContainerStateRunning{ - StartedAt: metav1.Time{}, - }, - }, - }, - { - Name: "bar", - State: v1.ContainerState{ - Running: &v1.ContainerStateRunning{ - StartedAt: metav1.Time{}, - }, - }, - }, - }, - }, - }, - "true", - }, - } - // The point of this test is to verify that the below template works. - tmpl := `{{if (exists . "status" "containerStatuses")}}{{range .status.containerStatuses}}{{if (and (eq .name "foo") (exists . "state" "running"))}}true{{end}}{{end}}{{end}}` - printer, err := genericprinters.NewGoTemplatePrinter([]byte(tmpl)) - if err != nil { - t.Fatalf("tmpl fail: %v", err) - } - - for name, item := range table { - buffer := &bytes.Buffer{} - err = printer.PrintObj(&item.pod, buffer) - if err != nil { - t.Errorf("%v: unexpected err: %v", name, err) - continue - } - actual := buffer.String() - if len(actual) == 0 { - actual = "false" - } - if e := item.expect; e != actual { - t.Errorf("%v: expected %v, got %v", name, e, actual) - } - } -} - -func TestPrinters(t *testing.T) { - om := func(name string) metav1.ObjectMeta { return metav1.ObjectMeta{Name: name} } - - var ( - err error - templatePrinter printers.ResourcePrinter - templatePrinter2 printers.ResourcePrinter - jsonpathPrinter printers.ResourcePrinter - ) - - templatePrinter, err = genericprinters.NewGoTemplatePrinter([]byte("{{.name}}")) - if err != nil { - t.Fatal(err) - } - - templatePrinter2, err = genericprinters.NewGoTemplatePrinter([]byte("{{len .items}}")) - if err != nil { - t.Fatal(err) - } - - jsonpathPrinter, err = genericprinters.NewJSONPathPrinter("{.metadata.name}") - if err != nil { - t.Fatal(err) - } - - genericPrinters := map[string]printers.ResourcePrinter{ - // TODO(juanvallejo): move "generic printer" tests to pkg/kubectl/genericclioptions/printers - "json": genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.JSONPrinter{}), - "yaml": genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.YAMLPrinter{}), - "template": templatePrinter, - "template2": templatePrinter2, - "jsonpath": jsonpathPrinter, - } - objects := map[string]runtime.Object{ - "pod": &v1.Pod{ObjectMeta: om("pod")}, - "emptyPodList": &v1.PodList{}, - "nonEmptyPodList": &v1.PodList{Items: []v1.Pod{{}}}, - "endpoints": &v1.Endpoints{ - Subsets: []v1.EndpointSubset{{ - Addresses: []v1.EndpointAddress{{IP: "127.0.0.1"}, {IP: "localhost"}}, - Ports: []v1.EndpointPort{{Port: 8080}}, - }}}, - } - // map of printer name to set of objects it should fail on. - expectedErrors := map[string]sets.String{ - "template2": sets.NewString("pod", "emptyPodList", "endpoints"), - "jsonpath": sets.NewString("emptyPodList", "nonEmptyPodList", "endpoints"), - } - - for pName, p := range genericPrinters { - for oName, obj := range objects { - b := &bytes.Buffer{} - if err := p.PrintObj(obj, b); err != nil { - if set, found := expectedErrors[pName]; found && set.Has(oName) { - // expected error - continue - } - t.Errorf("printer '%v', object '%v'; error: '%v'", pName, oName, err) - } - } - } -} - func TestPrintEventsResultSorted(t *testing.T) { obj := api.EventList{ From cd4349ffbd6fd2f0c4190e93cf9fc9cd00fb9309 Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Fri, 11 Oct 2019 11:39:41 -0700 Subject: [PATCH 2/5] Updates tests to expect metav1.TableRows instead of string, since this is what the "print" functions return. Updated test TestPrintNodeStatus() to not use NewTableGenerator or NewTablePrinter Updated test TestPrintNodeRode() to not use NewTableGenerator or NewTablePrinter Updated test TestPrintNodeOSImage() to remove NewTableGenerator and NewTablePrinter Updated test TestPrintNodeKernelVersion() to remove NewTableGenerator and NewTablePrinter Updated test TestPrintNodeContainerRuntimeVersion() to remove NewTableGenerator and NewTablePrinter Updated test TestPrintNodeName() to remove NewTableGenerator and NewTablePrinter Updated test TestPrintNodeExternalIP() to remove NewTableGenerator and NewTablePrinter Updated test TestPrintNodeInternalIP() to remove NewTableGenerator and NewTablePrinter Updated ingress printing test to TestPrintIngress() Updated test TestPrintService() to remove NewTableGenerator and NewTablePrinter Updated test to TestPrintServiceLoadBalancer, removing NewTableGenerator and NewTablePrinter Updated test TestPrintNonTerminatedPod() to remove NewTableGenerator Updates test TestPrintDeployment() removing NewTableGenerator and NewTablePrinter Updated test TestPrintDaemonSet(), removing NewTableGenerator and NewTablePrinter Updated test TestPrintJob, removing NewTableGenerator and NewTablePrinter Updates test TestPrintHPA(), removing NewTableGenerator and NewTablePrinter Updated test TestPrintPodDisruptionBudget(), removing NewTableGenerator and NewTablePrinter Updated test TestPrintControllerRevision(), removing NewTableGenerator and NewTablePrinter Updates test TestPrintLease, removing NewTableGenerator and NewTablePrinter Updates test TestPrintPriorityClass(), removing NewTableGenerator and NewTablePrinter Updates test TestPrintRuntimeClass(), removing NewTableGenerator and NewTablePrinter Updates test TestPrintEndpointSlice(), removing NewTableGenerator and NewTablePrinter Updates test TestPrintReplicaSet(), removing NewTableGenerator and NewTablePrinter Updates test TestPrintPersistentVolume(), removing NewTableGenerator and NewTablePrinter Updates test TestPrintPersistentVolumneClaim(), removing NewTableGenerator and NewTablePrinter Updates test TestPrintCronJob(), removing NewTableGenerator and NewTablePrinter Updates test TestPrintStorageClass(), removing NewTableGenerator and NewTablePrinter --- pkg/printers/internalversion/printers_test.go | 1553 +++++++++-------- 1 file changed, 863 insertions(+), 690 deletions(-) diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index ab1c279ef5f..2c66c4c8f71 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -17,8 +17,6 @@ limitations under the License. package internalversion import ( - "bytes" - "fmt" "reflect" "strconv" "strings" @@ -49,7 +47,7 @@ import ( func TestPrintEventsResultSorted(t *testing.T) { - obj := api.EventList{ + eventList := api.EventList{ Items: []api.Event{ { Source: api.EventSource{Component: "kubelet"}, @@ -78,35 +76,44 @@ func TestPrintEventsResultSorted(t *testing.T) { }, } - // Act - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&obj, printers.GenerateOptions{}) + rows, err := printEventList(&eventList, printers.GenerateOptions{}) if err != nil { - t.Fatalf("An error occurred generating the Table: %#v", err) + t.Fatal(err) } - printer := printers.NewTablePrinter(printers.PrintOptions{}) - buffer := &bytes.Buffer{} - err = printer.PrintObj(table, buffer) - - // Assert - if err != nil { - t.Fatalf("An error occurred printing the Table: %#v", err) + if len(rows) != 3 { + t.Errorf("Generate Event List: Wrong number of table rows returned. Expected 3, got (%d)", len(rows)) + } + // Verify the watch event dates are in order. + firstRow := rows[0] + message1 := firstRow.Cells[4] + if message1.(string) != "Item 1" { + t.Errorf("Wrong event ordering: expecting (Item 1), got (%s)", message1) + } + secondRow := rows[1] + message2 := secondRow.Cells[4] + if message2 != "Item 2" { + t.Errorf("Wrong event ordering: expecting (Item 2), got (%s)", message2) + } + thirdRow := rows[2] + message3 := thirdRow.Cells[4] + if message3 != "Item 3" { + t.Errorf("Wrong event ordering: expecting (Item 3), got (%s)", message3) } - out := buffer.String() - VerifyDatesInOrder(out, "\n" /* rowDelimiter */, " " /* columnDelimiter */, t) } func TestPrintNodeStatus(t *testing.T) { table := []struct { - node api.Node - status string + node api.Node + expected []metav1beta1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionTrue}}}, }, - status: "Ready", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"foo1", "Ready", "", "", ""}}}, }, { node: api.Node{ @@ -114,7 +121,8 @@ func TestPrintNodeStatus(t *testing.T) { Spec: api.NodeSpec{Unschedulable: true}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionTrue}}}, }, - status: "Ready,SchedulingDisabled", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"foo2", "Ready,SchedulingDisabled", "", "", ""}}}, }, { node: api.Node{ @@ -123,14 +131,16 @@ func TestPrintNodeStatus(t *testing.T) { {Type: api.NodeReady, Status: api.ConditionTrue}, {Type: api.NodeReady, Status: api.ConditionTrue}}}, }, - status: "Ready", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"foo3", "Ready", "", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo4"}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionFalse}}}, }, - status: "NotReady", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"foo4", "NotReady", "", "", ""}}}, }, { node: api.Node{ @@ -138,21 +148,24 @@ func TestPrintNodeStatus(t *testing.T) { Spec: api.NodeSpec{Unschedulable: true}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionFalse}}}, }, - status: "NotReady,SchedulingDisabled", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"foo5", "NotReady,SchedulingDisabled", "", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo6"}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: "InvalidValue", Status: api.ConditionTrue}}}, }, - status: "Unknown", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"foo6", "Unknown", "", "", ""}}}, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo7"}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{}}}, }, - status: "Unknown", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"foo7", "Unknown", "", "", ""}}}, }, { node: api.Node{ @@ -160,7 +173,8 @@ func TestPrintNodeStatus(t *testing.T) { Spec: api.NodeSpec{Unschedulable: true}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{Type: "InvalidValue", Status: api.ConditionTrue}}}, }, - status: "Unknown,SchedulingDisabled", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"foo8", "Unknown,SchedulingDisabled", "", "", ""}}}, }, { node: api.Node{ @@ -168,24 +182,21 @@ func TestPrintNodeStatus(t *testing.T) { Spec: api.NodeSpec{Unschedulable: true}, Status: api.NodeStatus{Conditions: []api.NodeCondition{{}}}, }, - status: "Unknown,SchedulingDisabled", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"foo9", "Unknown,SchedulingDisabled", "", "", ""}}}, }, } - generator := printers.NewTableGenerator().With(AddHandlers) - printer := printers.NewTablePrinter(printers.PrintOptions{}) - for _, test := range table { - table, err := generator.GenerateTable(&test.node, printers.GenerateOptions{}) + for i, test := range table { + rows, err := printNode(&test.node, printers.GenerateOptions{}) if err != nil { - t.Fatalf("An error occurred printing Node: %#v", err) + t.Fatalf("Error generating table rows for Node: %#v", err) } - buffer := &bytes.Buffer{} - err = printer.PrintObj(table, buffer) - if err != nil { - t.Fatalf("An error occurred printing Node: %#v", err) + for i := range rows { + rows[i].Object.Object = nil } - if !contains(strings.Fields(buffer.String()), test.status) { - t.Fatalf("Expect printing node %s with status %#v, got: %#v", test.node.Name, test.status, buffer.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } } } @@ -194,13 +205,14 @@ func TestPrintNodeRole(t *testing.T) { table := []struct { node api.Node - expected string + expected []metav1beta1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo9"}, }, - expected: "", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"foo9", "Unknown", "", "", ""}}}, }, { node: api.Node{ @@ -209,7 +221,8 @@ func TestPrintNodeRole(t *testing.T) { Labels: map[string]string{"node-role.kubernetes.io/master": "", "node-role.kubernetes.io/proxy": "", "kubernetes.io/role": "node"}, }, }, - expected: "master,node,proxy", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"foo10", "Unknown", "master,node,proxy", "", ""}}}, }, { node: api.Node{ @@ -218,24 +231,21 @@ func TestPrintNodeRole(t *testing.T) { Labels: map[string]string{"kubernetes.io/role": "node"}, }, }, - expected: "node", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"foo11", "Unknown", "node", "", ""}}}, }, } - generator := printers.NewTableGenerator().With(AddHandlers) - printer := printers.NewTablePrinter(printers.PrintOptions{}) - for _, test := range table { - table, err := generator.GenerateTable(&test.node, printers.GenerateOptions{}) + for i, test := range table { + rows, err := printNode(&test.node, printers.GenerateOptions{}) if err != nil { - t.Fatalf("An error occurred generating table for Node: %#v", err) + t.Fatalf("An error occurred generating table rows for Node: %#v", err) } - buffer := &bytes.Buffer{} - err = printer.PrintObj(table, buffer) - if err != nil { - t.Fatalf("An error occurred printing Node: %#v", err) + for i := range rows { + rows[i].Object.Object = nil } - if !contains(strings.Fields(buffer.String()), test.expected) { - t.Fatalf("Expect printing node %s with role %#v, got: %#v", test.node.Name, test.expected, buffer.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } } } @@ -243,8 +253,8 @@ func TestPrintNodeRole(t *testing.T) { func TestPrintNodeOSImage(t *testing.T) { table := []struct { - node api.Node - osImage string + node api.Node + expected []metav1beta1.TableRow }{ { node: api.Node{ @@ -254,7 +264,12 @@ func TestPrintNodeOSImage(t *testing.T) { Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, }, }, - osImage: "fake-os-image", + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"foo1", "Unknown", "", "", "", "", "1.1.1.1", "fake-os-image", "", ""}, + }, + }, }, { node: api.Node{ @@ -264,24 +279,25 @@ func TestPrintNodeOSImage(t *testing.T) { Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, }, }, - osImage: "", + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"foo2", "Unknown", "", "", "", "", "1.1.1.1", "", "fake-kernel-version", ""}, + }, + }, }, } - generator := printers.NewTableGenerator().With(AddHandlers) - printer := printers.NewTablePrinter(printers.PrintOptions{Wide: true}) - for _, test := range table { - table, err := generator.GenerateTable(&test.node, printers.GenerateOptions{Wide: true}) + for i, test := range table { + rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) if err != nil { t.Fatalf("An error occurred generating table for Node: %#v", err) } - buffer := &bytes.Buffer{} - err = printer.PrintObj(table, buffer) - if err != nil { - t.Fatalf("An error occurred printing Node: %#v", err) + for i := range rows { + rows[i].Object.Object = nil } - if !contains(strings.Fields(buffer.String()), test.osImage) { - t.Fatalf("Expect printing node %s with os image %#v, got: %#v", test.node.Name, test.osImage, buffer.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } } } @@ -289,8 +305,8 @@ func TestPrintNodeOSImage(t *testing.T) { func TestPrintNodeKernelVersion(t *testing.T) { table := []struct { - node api.Node - kernelVersion string + node api.Node + expected []metav1beta1.TableRow }{ { node: api.Node{ @@ -300,7 +316,12 @@ func TestPrintNodeKernelVersion(t *testing.T) { Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, }, }, - kernelVersion: "fake-kernel-version", + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"foo1", "Unknown", "", "", "", "", "1.1.1.1", "", "fake-kernel-version", ""}, + }, + }, }, { node: api.Node{ @@ -310,24 +331,25 @@ func TestPrintNodeKernelVersion(t *testing.T) { Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, }, }, - kernelVersion: "", + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"foo2", "Unknown", "", "", "", "", "1.1.1.1", "fake-os-image", "", ""}, + }, + }, }, } - generator := printers.NewTableGenerator().With(AddHandlers) - printer := printers.NewTablePrinter(printers.PrintOptions{Wide: true}) - for _, test := range table { - table, err := generator.GenerateTable(&test.node, printers.GenerateOptions{Wide: true}) + for i, test := range table { + rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) if err != nil { - t.Fatalf("An error occurred generating table for Node: %#v", err) + t.Fatalf("An error occurred generating table rows Node: %#v", err) } - buffer := &bytes.Buffer{} - err = printer.PrintObj(table, buffer) - if err != nil { - t.Fatalf("An error occurred printing Node: %#v", err) + for i := range rows { + rows[i].Object.Object = nil } - if !contains(strings.Fields(buffer.String()), test.kernelVersion) { - t.Fatalf("Expect printing node %s with kernel version %#v, got: %#v", test.node.Name, test.kernelVersion, buffer.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } } } @@ -335,8 +357,8 @@ func TestPrintNodeKernelVersion(t *testing.T) { func TestPrintNodeContainerRuntimeVersion(t *testing.T) { table := []struct { - node api.Node - containerRuntimeVersion string + node api.Node + expected []metav1beta1.TableRow }{ { node: api.Node{ @@ -346,7 +368,12 @@ func TestPrintNodeContainerRuntimeVersion(t *testing.T) { Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, }, }, - containerRuntimeVersion: "foo://1.2.3", + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"foo1", "Unknown", "", "", "", "", "1.1.1.1", "", "", "foo://1.2.3"}, + }, + }, }, { node: api.Node{ @@ -356,24 +383,25 @@ func TestPrintNodeContainerRuntimeVersion(t *testing.T) { Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}, }, }, - containerRuntimeVersion: "", + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"foo2", "Unknown", "", "", "", "", "1.1.1.1", "", "", ""}, + }, + }, }, } - generator := printers.NewTableGenerator().With(AddHandlers) - printer := printers.NewTablePrinter(printers.PrintOptions{Wide: true}) - for _, test := range table { - table, err := generator.GenerateTable(&test.node, printers.GenerateOptions{Wide: true}) + for i, test := range table { + rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) if err != nil { - t.Fatalf("An error occurred generating table for Node: %#v", err) + t.Fatalf("An error occurred generating table rows Node: %#v", err) } - buffer := &bytes.Buffer{} - err = printer.PrintObj(table, buffer) - if err != nil { - t.Fatalf("An error occurred printing Node: %#v", err) + for i := range rows { + rows[i].Object.Object = nil } - if !contains(strings.Fields(buffer.String()), test.containerRuntimeVersion) { - t.Fatalf("Expect printing node %s with kernel version %#v, got: %#v", test.node.Name, test.containerRuntimeVersion, buffer.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } } } @@ -381,39 +409,36 @@ func TestPrintNodeContainerRuntimeVersion(t *testing.T) { func TestPrintNodeName(t *testing.T) { table := []struct { - node api.Node - Name string + node api.Node + expected []metav1beta1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "127.0.0.1"}, Status: api.NodeStatus{}, }, - Name: "127.0.0.1", - }, + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"127.0.0.1", "Unknown", "", "", ""}}}}, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: ""}, Status: api.NodeStatus{}, }, - Name: "", + // Columns: Name, Status, Roles, Age, KubeletVersion + expected: []metav1beta1.TableRow{{Cells: []interface{}{"", "Unknown", "", "", ""}}}, }, } - generator := printers.NewTableGenerator().With(AddHandlers) - printer := printers.NewTablePrinter(printers.PrintOptions{Wide: true}) - for _, test := range table { - table, err := generator.GenerateTable(&test.node, printers.GenerateOptions{Wide: true}) + for i, test := range table { + rows, err := printNode(&test.node, printers.GenerateOptions{}) if err != nil { - t.Fatalf("An error occurred generating table for Node: %#v", err) + t.Fatalf("An error occurred generating table rows Node: %#v", err) } - buffer := &bytes.Buffer{} - err = printer.PrintObj(table, buffer) - if err != nil { - t.Fatalf("An error occurred printing Node: %#v", err) + for i := range rows { + rows[i].Object.Object = nil } - if !contains(strings.Fields(buffer.String()), test.Name) { - t.Fatalf("Expect printing node %s with node name %#v, got: %#v", test.node.Name, test.Name, buffer.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } } } @@ -421,22 +446,32 @@ func TestPrintNodeName(t *testing.T) { func TestPrintNodeExternalIP(t *testing.T) { table := []struct { - node api.Node - externalIP string + node api.Node + expected []metav1beta1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, Status: api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}}, }, - externalIP: "1.1.1.1", + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"foo1", "Unknown", "", "", "", "", "1.1.1.1", "", "", ""}, + }, + }, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, Status: api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeInternalIP, Address: "1.1.1.1"}}}, }, - externalIP: "", + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"foo2", "Unknown", "", "", "", "1.1.1.1", "", "", "", ""}, + }, + }, }, { node: api.Node{ @@ -447,24 +482,24 @@ func TestPrintNodeExternalIP(t *testing.T) { {Type: api.NodeExternalIP, Address: "4.4.4.4"}, }}, }, - externalIP: "2.2.2.2", + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"foo3", "Unknown", "", "", "", "3.3.3.3", "2.2.2.2", "", "", ""}, + }, + }, }, } - generator := printers.NewTableGenerator().With(AddHandlers) - printer := printers.NewTablePrinter(printers.PrintOptions{Wide: true}) - for _, test := range table { - table, err := generator.GenerateTable(&test.node, printers.GenerateOptions{Wide: true}) + for i, test := range table { + rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) if err != nil { - t.Fatalf("An error occurred generating table for Node: %#v", err) + t.Fatalf("An error occurred generating table rows Node: %#v", err) } - buffer := &bytes.Buffer{} - err = printer.PrintObj(table, buffer) - if err != nil { - t.Fatalf("An error occurred printing Node: %#v", err) + for i := range rows { + rows[i].Object.Object = nil } - if !contains(strings.Fields(buffer.String()), test.externalIP) { - t.Fatalf("Expect printing node %s with external ip %#v, got: %#v", test.node.Name, test.externalIP, buffer.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } } } @@ -472,22 +507,32 @@ func TestPrintNodeExternalIP(t *testing.T) { func TestPrintNodeInternalIP(t *testing.T) { table := []struct { - node api.Node - internalIP string + node api.Node + expected []metav1beta1.TableRow }{ { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo1"}, Status: api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeInternalIP, Address: "1.1.1.1"}}}, }, - internalIP: "1.1.1.1", + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"foo1", "Unknown", "", "", "", "1.1.1.1", "", "", "", ""}, + }, + }, }, { node: api.Node{ ObjectMeta: metav1.ObjectMeta{Name: "foo2"}, Status: api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}}, }, - internalIP: "", + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"foo2", "Unknown", "", "", "", "", "1.1.1.1", "", "", ""}, + }, + }, }, { node: api.Node{ @@ -498,45 +543,34 @@ func TestPrintNodeInternalIP(t *testing.T) { {Type: api.NodeInternalIP, Address: "4.4.4.4"}, }}, }, - internalIP: "2.2.2.2", + // Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"foo3", "Unknown", "", "", "", "2.2.2.2", "3.3.3.3", "", "", ""}, + }, + }, }, } - generator := printers.NewTableGenerator().With(AddHandlers) - printer := printers.NewTablePrinter(printers.PrintOptions{Wide: true}) - for _, test := range table { - table, err := generator.GenerateTable(&test.node, printers.GenerateOptions{Wide: true}) + for i, test := range table { + rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true}) if err != nil { - t.Fatalf("An error occurred generating table for Node: %#v", err) + t.Fatalf("An error occurred generating table rows Node: %#v", err) } - buffer := &bytes.Buffer{} - err = printer.PrintObj(table, buffer) - if err != nil { - t.Fatalf("An error occurred printing Node: %#v", err) + for i := range rows { + rows[i].Object.Object = nil } - if !contains(strings.Fields(buffer.String()), test.internalIP) { - t.Fatalf("Expect printing node %s with internal ip %#v, got: %#v", test.node.Name, test.internalIP, buffer.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } } } -func contains(fields []string, field string) bool { - for _, v := range fields { - if v == field { - return true - } - } - return false -} - -func TestPrintHunmanReadableIngressWithColumnLabels(t *testing.T) { +func TestPrintIngress(t *testing.T) { ingress := networking.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, - Labels: map[string]string{ - "app_name": "kubectl_test_ingress", - }, }, Spec: networking.IngressSpec{ Backend: &networking.IngressBackend{ @@ -555,175 +589,124 @@ func TestPrintHunmanReadableIngressWithColumnLabels(t *testing.T) { }, }, } - buff := bytes.NewBuffer([]byte{}) - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&ingress, printers.GenerateOptions{}) + // Columns: Name, Hosts, Address, Ports, Age + expected := []metav1beta1.TableRow{{Cells: []interface{}{"test1", "*", "2.3.4.5", "80", "10y"}}} + + rows, err := printIngress(&ingress, printers.GenerateOptions{}) if err != nil { - t.Fatal(err) + t.Fatalf("Error generating table rows for Ingress: %#v", err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true, ColumnLabels: []string{"app_name"}}) - if err := printer.PrintObj(table, buff); err != nil { - t.Fatal(err) - } - output := string(buff.Bytes()) - appName := ingress.ObjectMeta.Labels["app_name"] - if !strings.Contains(output, appName) { - t.Errorf("expected to container app_name label value %s, but doesn't %s", appName, output) + rows[0].Object.Object = nil + if !reflect.DeepEqual(expected, rows) { + t.Errorf("mismatch: %s", diff.ObjectReflectDiff(expected, rows)) } } -func TestPrintHumanReadableService(t *testing.T) { - tests := []api.Service{ +func TestPrintServiceLoadBalancer(t *testing.T) { + tests := []struct { + service api.Service + options printers.GenerateOptions + expected []metav1beta1.TableRow + }{ + // Test load balancer service with multiple external IP's { - Spec: api.ServiceSpec{ - ClusterIP: "1.2.3.4", - Type: "LoadBalancer", - Ports: []api.ServicePort{ - { - Port: 80, - Protocol: "TCP", - }, - }, - }, - Status: api.ServiceStatus{ - LoadBalancer: api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - { - IP: "2.3.4.5", - }, - { - IP: "3.4.5.6", - }, - }, + service: api.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "service1"}, + Spec: api.ServiceSpec{ + ClusterIP: "1.2.3.4", + Type: "LoadBalancer", + Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}}, + }, + Status: api.ServiceStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}}}, }, }, + options: printers.GenerateOptions{}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"service1", "LoadBalancer", "1.2.3.4", "2.3.4.5,3.4.5.6", "80/TCP", ""}}}, }, + // Test load balancer service with pending external IP. { - Spec: api.ServiceSpec{ - ClusterIP: "1.3.4.5", - Ports: []api.ServicePort{ - { - Port: 80, - Protocol: "TCP", - }, - { - Port: 8090, - Protocol: "UDP", - }, - { - Port: 8000, - Protocol: "TCP", - }, - { - Port: 7777, - Protocol: "SCTP", - }, + service: api.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "service2"}, + Spec: api.ServiceSpec{ + ClusterIP: "1.3.4.5", + Type: "LoadBalancer", + Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}, {Port: 7777, Protocol: "SCTP"}}, }, }, + options: printers.GenerateOptions{}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"service2", "LoadBalancer", "1.3.4.5", "", "80/TCP,8090/UDP,8000/TCP,7777/SCTP", ""}}}, }, + // Test load balancer service with multiple ports. { - Spec: api.ServiceSpec{ - ClusterIP: "1.4.5.6", - Type: "LoadBalancer", - Ports: []api.ServicePort{ - { - Port: 80, - Protocol: "TCP", - }, - { - Port: 8090, - Protocol: "UDP", - }, - { - Port: 8000, - Protocol: "TCP", - }, - }, - }, - Status: api.ServiceStatus{ - LoadBalancer: api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - { - IP: "2.3.4.5", - }, - }, + service: api.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "service3"}, + Spec: api.ServiceSpec{ + ClusterIP: "1.4.5.6", + Type: "LoadBalancer", + Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}}, + }, + Status: api.ServiceStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}}}, }, }, + options: printers.GenerateOptions{}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"service3", "LoadBalancer", "1.4.5.6", "2.3.4.5", "80/TCP,8090/UDP,8000/TCP", ""}}}, }, + // Long external IP's list gets elided. { - Spec: api.ServiceSpec{ - ClusterIP: "1.5.6.7", - Type: "LoadBalancer", - Ports: []api.ServicePort{ - { - Port: 80, - Protocol: "TCP", - }, - { - Port: 8090, - Protocol: "UDP", - }, - { - Port: 8000, - Protocol: "TCP", - }, + service: api.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "service4"}, + Spec: api.ServiceSpec{ + ClusterIP: "1.5.6.7", + Type: "LoadBalancer", + Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}}, + }, + Status: api.ServiceStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}, {IP: "5.6.7.8", Hostname: "host5678"}}}, }, }, - Status: api.ServiceStatus{ - LoadBalancer: api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - { - IP: "2.3.4.5", - }, - { - IP: "3.4.5.6", - }, - { - IP: "5.6.7.8", - Hostname: "host5678", - }, - }, + options: printers.GenerateOptions{}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"service4", "LoadBalancer", "1.5.6.7", "2.3.4.5,3.4.5...", "80/TCP,8090/UDP,8000/TCP", ""}}}, + }, + // Generate options: Wide, includes selectors. + { + service: api.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "service4"}, + Spec: api.ServiceSpec{ + ClusterIP: "1.5.6.7", + Type: "LoadBalancer", + Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}}, + Selector: map[string]string{"foo": "bar"}, + }, + Status: api.ServiceStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}, {IP: "5.6.7.8", Hostname: "host5678"}}}, }, }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"service4", "LoadBalancer", "1.5.6.7", "2.3.4.5,3.4.5.6,5.6.7.8", "80/TCP,8090/UDP,8000/TCP", "", "foo=bar"}}}, }, } - for _, svc := range tests { - for _, wide := range []bool{false, true} { - buff := bytes.NewBuffer([]byte{}) - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&svc, printers.GenerateOptions{Wide: wide}) - if err != nil { - t.Fatal(err) - } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buff); err != nil { - t.Fatal(err) - } - output := string(buff.Bytes()) - ip := svc.Spec.ClusterIP - if !strings.Contains(output, ip) { - t.Errorf("expected to contain ClusterIP %s, but doesn't: %s", ip, output) - } - - for n, ingress := range svc.Status.LoadBalancer.Ingress { - ip = ingress.IP - // For non-wide output, we only guarantee the first IP to be printed - if (n == 0 || wide) && !strings.Contains(output, ip) { - t.Errorf("expected to contain ingress ip %s with wide=%v, but doesn't: %s", ip, wide, output) - } - } - - for _, port := range svc.Spec.Ports { - portSpec := fmt.Sprintf("%d/%s", port.Port, port.Protocol) - if !strings.Contains(output, portSpec) { - t.Errorf("expected to contain port: %s, but doesn't: %s", portSpec, output) - } - } - // Each service should print on one line - if 1 != strings.Count(output, "\n") { - t.Errorf("expected a single newline, found %d", strings.Count(output, "\n")) - } + for i, test := range tests { + rows, err := printService(&test.service, test.options) + if err != nil { + t.Fatalf("Error printing table rows for Service: %#v", 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)) } } } @@ -1292,6 +1275,7 @@ func TestPrintNonTerminatedPod(t *testing.T) { }, }, }, + // Columns: Name, Ready, Reason, Restarts, Age []metav1beta1.TableRow{{Cells: []interface{}{"test1", "1/2", "Running", int64(6), ""}}}, }, { @@ -1307,6 +1291,7 @@ func TestPrintNonTerminatedPod(t *testing.T) { }, }, }, + // Columns: Name, Ready, Reason, Restarts, Age []metav1beta1.TableRow{{Cells: []interface{}{"test2", "1/2", "Pending", int64(6), ""}}}, }, { @@ -1322,10 +1307,11 @@ func TestPrintNonTerminatedPod(t *testing.T) { }, }, }, + // Columns: Name, Ready, Reason, Restarts, Age []metav1beta1.TableRow{{Cells: []interface{}{"test3", "1/2", "Unknown", int64(6), ""}}}, }, { - // Test pod phase Succeeded shouldn't be printed + // Test pod phase Succeeded should be printed api.Pod{ ObjectMeta: metav1.ObjectMeta{Name: "test4"}, Spec: api.PodSpec{Containers: make([]api.Container, 2)}, @@ -1337,7 +1323,13 @@ func TestPrintNonTerminatedPod(t *testing.T) { }, }, }, - []metav1beta1.TableRow{{Cells: []interface{}{"test4", "1/2", "Succeeded", int64(6), ""}, Conditions: podSuccessConditions}}, + // Columns: Name, Ready, Reason, Restarts, Age + []metav1beta1.TableRow{ + { + Cells: []interface{}{"test4", "1/2", "Succeeded", int64(6), ""}, + Conditions: podSuccessConditions, + }, + }, }, { // Test pod phase Failed shouldn't be printed @@ -1352,17 +1344,21 @@ func TestPrintNonTerminatedPod(t *testing.T) { }, }, }, - []metav1beta1.TableRow{{Cells: []interface{}{"test5", "1/2", "Failed", int64(6), ""}, Conditions: podFailedConditions}}, + // Columns: Name, Ready, Reason, Restarts, Age + []metav1beta1.TableRow{ + { + Cells: []interface{}{"test5", "1/2", "Failed", int64(6), ""}, + Conditions: podFailedConditions, + }, + }, }, } for i, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.pod, printers.GenerateOptions{}) + rows, err := printPod(&test.pod, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } - verifyTable(t, table) - rows := table.Rows for i := range rows { rows[i].Object.Object = nil } @@ -1515,119 +1511,138 @@ func TestTranslateTimestampUntil(t *testing.T) { } func TestPrintDeployment(t *testing.T) { - tests := []struct { - deployment apps.Deployment - expect string - wideExpect string - }{ - { - apps.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test1", - CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, - }, - Spec: apps.DeploymentSpec{ - Replicas: 5, - Template: api.PodTemplateSpec{ - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Name: "fake-container1", - Image: "fake-image1", - }, - { - Name: "fake-container2", - Image: "fake-image2", - }, - }, + + testDeployment := apps.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test1", + CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: apps.DeploymentSpec{ + Replicas: 5, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "fake-container1", + Image: "fake-image1", + }, + { + Name: "fake-container2", + Image: "fake-image2", }, }, - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, - }, - Status: apps.DeploymentStatus{ - Replicas: 10, - UpdatedReplicas: 2, - AvailableReplicas: 1, - UnavailableReplicas: 4, }, }, - "test1 0/5 2 1 0s\n", - "test1 0/5 2 1 0s fake-container1,fake-container2 fake-image1,fake-image2 foo=bar\n", + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + }, + Status: apps.DeploymentStatus{ + Replicas: 10, + UpdatedReplicas: 2, + AvailableReplicas: 1, + UnavailableReplicas: 4, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.deployment, printers.GenerateOptions{}) + tests := []struct { + deployment apps.Deployment + options printers.GenerateOptions + expected []metav1beta1.TableRow + }{ + // Test Deployment with no generate options. + { + deployment: testDeployment, + options: printers.GenerateOptions{}, + // Columns: Name, ReadyReplicas, UpdatedReplicas, AvailableReplicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", "0/5", int64(2), int64(1), "0s"}}}, + }, + // Test generate options: Wide. + { + deployment: testDeployment, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, ReadyReplicas, UpdatedReplicas, AvailableReplicas, Age, Containers, Images, Selectors + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", "0/5", int64(2), int64(1), "0s", "fake-container1,fake-container2", "fake-image1,fake-image2", "foo=bar"}}}, + }, + } + + for i, test := range tests { + rows, err := printDeployment(&test.deployment, test.options) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Fatalf("Expected: %s, got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() - table, err = printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.deployment, printers.GenerateOptions{Wide: true}) - verifyTable(t, table) - // print deployment with '-o wide' option - printer = printers.NewTablePrinter(printers.PrintOptions{Wide: true, NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) - } - if buf.String() != test.wideExpect { - t.Fatalf("Expected: %s, got: %s", test.wideExpect, buf.String()) - } - buf.Reset() } } func TestPrintDaemonSet(t *testing.T) { - tests := []struct { - ds apps.DaemonSet - startsWith string - }{ - { - apps.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test1", - CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, - }, - Spec: apps.DaemonSetSpec{ - Template: api.PodTemplateSpec{ - Spec: api.PodSpec{Containers: make([]api.Container, 2)}, + + testDaemonSet := apps.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test1", + 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, - }, }, - "test1 3 2 1 2 0 0s", + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + }, + Status: apps.DaemonSetStatus{ + CurrentNumberScheduled: 2, + DesiredNumberScheduled: 3, + NumberReady: 1, + UpdatedNumberScheduled: 2, + NumberAvailable: 0, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.ds, printers.GenerateOptions{}) + tests := []struct { + daemonSet apps.DaemonSet + options printers.GenerateOptions + expected []metav1beta1.TableRow + }{ + // Test generate daemon set with no generate options. + { + daemonSet: testDaemonSet, + options: printers.GenerateOptions{}, + // Columns: Name, Num Desired, Num Current, Num Ready, Num Updated, Num Available, Selectors, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", int64(3), int64(2), int64(1), int64(2), int64(0), "", "0s"}}}, + }, + // Test generate daemon set with "Wide" generate options. + { + daemonSet: testDaemonSet, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, Num Desired, Num Current, Num Ready, Num Updated, Num Available, Node Selectors, Age, Containers, Images, Labels + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", int64(3), int64(2), int64(1), int64(2), int64(0), "", "0s", "fake-container1,fake-container2", "fake-image1,fake-image2", "foo=bar"}}}, + }, + } + + for i, test := range tests { + rows, err := printDaemonSet(&test.daemonSet, test.options) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if !strings.HasPrefix(buf.String(), test.startsWith) { - t.Fatalf("Expected to start with %s but got %s", test.startsWith, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } @@ -1635,26 +1650,83 @@ func TestPrintJob(t *testing.T) { now := time.Now() completions := int32(2) tests := []struct { - job batch.Job - expect string + job batch.Job + options printers.GenerateOptions + expected []metav1beta1.TableRow }{ { - batch.Job{ + // Generate table rows for Job with no generate options. + job: 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, }, }, - "job1 1/2 0s\n", + options: printers.GenerateOptions{}, + // Columns: Name, Completions, Duration, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"job1", "1/2", "", "0s"}}}, }, + // Generate table rows for Job with generate options "Wide". { - batch.Job{ + job: 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-label-value"}}, + }, + Status: batch.JobStatus{ + Succeeded: 1, + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, Completions, Duration, Age, Containers, Images, Selectors + expected: []metav1beta1.TableRow{ + { + Cells: []interface{}{"job1", "1/2", "", "0s", "fake-job-container1,fake-job-container2", "fake-job-image1,fake-job-image2", "job-label=job-label-value"}, + }, + }, + }, + // Job with ten-year age. + { + job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job2", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, @@ -1666,10 +1738,13 @@ func TestPrintJob(t *testing.T) { Succeeded: 0, }, }, - "job2 0/1 10y\n", + options: printers.GenerateOptions{}, + // Columns: Name, Completions, Duration, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"job2", "0/1", "", "10y"}}}, }, + // Job with duration. { - batch.Job{ + job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job3", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, @@ -1683,10 +1758,12 @@ func TestPrintJob(t *testing.T) { CompletionTime: &metav1.Time{Time: now.Add(31 * time.Minute)}, }, }, - "job3 0/1 30m 10y\n", + options: printers.GenerateOptions{}, + // Columns: Name, Completions, Duration, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"job3", "0/1", "30m", "10y"}}}, }, { - batch.Job{ + job: batch.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "job4", CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)}, @@ -1699,25 +1776,23 @@ func TestPrintJob(t *testing.T) { StartTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)}, }, }, - "job4 0/1 20m 10y\n", + options: printers.GenerateOptions{}, + // Columns: Name, Completions, Duration, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"job4", "0/1", "20m", "10y"}}}, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.job, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printJob(&test.job, test.options) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Errorf("Expected: %s, got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } @@ -1731,11 +1806,11 @@ func TestPrintHPA(t *testing.T) { } tests := []struct { hpa autoscaling.HorizontalPodAutoscaler - expected string + expected []metav1beta1.TableRow }{ // minReplicas unset { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -1749,11 +1824,12 @@ func TestPrintHPA(t *testing.T) { DesiredReplicas: 5, }, }, - "some-hpa ReplicationController/some-rc 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "", "", int64(10), int64(4), ""}}}, }, // external source type, target average value (no current) { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -1783,11 +1859,12 @@ func TestPrintHPA(t *testing.T) { DesiredReplicas: 5, }, }, - "some-hpa ReplicationController/some-rc /100m (avg) 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "/100m (avg)", "2", int64(10), int64(4), ""}}}, }, // external source type, target average value { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -1831,11 +1908,12 @@ func TestPrintHPA(t *testing.T) { }, }, }, - "some-hpa ReplicationController/some-rc 50m/100m (avg) 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m (avg)", "2", int64(10), int64(4), ""}}}, }, // external source type, target value (no current) { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -1865,11 +1943,12 @@ func TestPrintHPA(t *testing.T) { DesiredReplicas: 5, }, }, - "some-hpa ReplicationController/some-rc /100m 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "/100m", "2", int64(10), int64(4), ""}}}, }, // external source type, target value { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -1912,11 +1991,12 @@ func TestPrintHPA(t *testing.T) { }, }, }, - "some-hpa ReplicationController/some-rc 50m/100m 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), ""}}}, }, // pods source type (no current) { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -1945,11 +2025,12 @@ func TestPrintHPA(t *testing.T) { DesiredReplicas: 5, }, }, - "some-hpa ReplicationController/some-rc /100m 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "/100m", "2", int64(10), int64(4), ""}}}, }, // pods source type { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -1991,11 +2072,12 @@ func TestPrintHPA(t *testing.T) { }, }, }, - "some-hpa ReplicationController/some-rc 50m/100m 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), ""}}}, }, // object source type (no current) { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -2028,11 +2110,12 @@ func TestPrintHPA(t *testing.T) { DesiredReplicas: 5, }, }, - "some-hpa ReplicationController/some-rc /100m 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "/100m", "2", int64(10), int64(4), ""}}}, }, // object source type { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -2082,11 +2165,12 @@ func TestPrintHPA(t *testing.T) { }, }, }, - "some-hpa ReplicationController/some-rc 50m/100m 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), ""}}}, }, // resource source type, targetVal (no current) { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -2113,11 +2197,12 @@ func TestPrintHPA(t *testing.T) { DesiredReplicas: 5, }, }, - "some-hpa ReplicationController/some-rc /100m 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "/100m", "2", int64(10), int64(4), ""}}}, }, // resource source type, targetVal { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -2155,11 +2240,12 @@ func TestPrintHPA(t *testing.T) { }, }, }, - "some-hpa ReplicationController/some-rc 50m/100m 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), ""}}}, }, // resource source type, targetUtil (no current) { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -2186,11 +2272,12 @@ func TestPrintHPA(t *testing.T) { DesiredReplicas: 5, }, }, - "some-hpa ReplicationController/some-rc /80% 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "/80%", "2", int64(10), int64(4), ""}}}, }, // resource source type, targetUtil { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -2229,11 +2316,12 @@ func TestPrintHPA(t *testing.T) { }, }, }, - "some-hpa ReplicationController/some-rc 50%/80% 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50%/80%", "2", int64(10), int64(4), ""}}}, }, // multiple specs { - autoscaling.HorizontalPodAutoscaler{ + hpa: autoscaling.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"}, Spec: autoscaling.HorizontalPodAutoscalerSpec{ ScaleTargetRef: autoscaling.CrossVersionObjectReference{ @@ -2307,26 +2395,22 @@ func TestPrintHPA(t *testing.T) { }, }, }, - "some-hpa ReplicationController/some-rc 50m/100m, 50%/80% + 1 more... 2 10 4 \n", + // Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m, 50%/80% + 1 more...", "2", int64(10), int64(4), ""}}}, }, } - buff := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.hpa, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printHorizontalPodAutoscaler(&test.hpa, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buff); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buff.String() != test.expected { - t.Errorf("expected %q, got %q", test.expected, buff.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - - buff.Reset() } } @@ -2412,12 +2496,13 @@ func TestPrintService(t *testing.T) { singleExternalIP := []string{"80.11.12.10"} mulExternalIP := []string{"80.11.12.10", "80.11.12.11"} tests := []struct { - service api.Service - expect string + service api.Service + options printers.GenerateOptions + expected []metav1beta1.TableRow }{ { // Test name, cluster ip, port with protocol - api.Service{ + service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test1"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeClusterIP, @@ -2428,13 +2513,36 @@ func TestPrintService(t *testing.T) { }, }, ClusterIP: "10.9.8.7", + Selector: map[string]string{"foo": "bar"}, // Does NOT get printed. }, }, - "test1 ClusterIP 10.9.8.7 2233/tcp \n", + options: printers.GenerateOptions{}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", "ClusterIP", "10.9.8.7", "", "2233/tcp", ""}}}, + }, + { + // Test generate options: Wide includes selectors. + service: api.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "test1"}, + Spec: api.ServiceSpec{ + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{ + { + Protocol: "tcp", + Port: 2233, + }, + }, + ClusterIP: "10.9.8.7", + Selector: map[string]string{"foo": "bar"}, + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age, Selector + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", "ClusterIP", "10.9.8.7", "", "2233/tcp", "", "foo=bar"}}}, }, { // Test NodePort service - api.Service{ + service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test2"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeNodePort, @@ -2448,11 +2556,13 @@ func TestPrintService(t *testing.T) { ClusterIP: "10.9.8.7", }, }, - "test2 NodePort 10.9.8.7 8888:9999/tcp \n", + options: printers.GenerateOptions{}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test2", "NodePort", "10.9.8.7", "", "8888:9999/tcp", ""}}}, }, { // Test LoadBalancer service - api.Service{ + service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test3"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeLoadBalancer, @@ -2465,11 +2575,13 @@ func TestPrintService(t *testing.T) { ClusterIP: "10.9.8.7", }, }, - "test3 LoadBalancer 10.9.8.7 8888/tcp \n", + options: printers.GenerateOptions{}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test3", "LoadBalancer", "10.9.8.7", "", "8888/tcp", ""}}}, }, { // Test LoadBalancer service with single ExternalIP and no LoadBalancerStatus - api.Service{ + service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test4"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeLoadBalancer, @@ -2483,11 +2595,13 @@ func TestPrintService(t *testing.T) { ExternalIPs: singleExternalIP, }, }, - "test4 LoadBalancer 10.9.8.7 80.11.12.10 8888/tcp \n", + options: printers.GenerateOptions{}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test4", "LoadBalancer", "10.9.8.7", "80.11.12.10", "8888/tcp", ""}}}, }, { // Test LoadBalancer service with single ExternalIP - api.Service{ + service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test5"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeLoadBalancer, @@ -2511,11 +2625,13 @@ func TestPrintService(t *testing.T) { }, }, }, - "test5 LoadBalancer 10.9.8.7 3.4.5.6,80.11.12.10 8888/tcp \n", + options: printers.GenerateOptions{}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test5", "LoadBalancer", "10.9.8.7", "3.4.5.6,80.11.12.10", "8888/tcp", ""}}}, }, { // Test LoadBalancer service with mul ExternalIPs - api.Service{ + service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test6"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeLoadBalancer, @@ -2543,37 +2659,36 @@ func TestPrintService(t *testing.T) { }, }, }, - "test6 LoadBalancer 10.9.8.7 2.3.4.5,3.4.5.6,80.11.12.10,80.11.12.11 8888/tcp \n", + options: printers.GenerateOptions{}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test6", "LoadBalancer", "10.9.8.7", "2.3.4.5,3.4.5.6,80.11.12.10,80.11.12.11", "8888/tcp", ""}}}, }, { // Test ExternalName service - api.Service{ + service: api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "test7"}, Spec: api.ServiceSpec{ Type: api.ServiceTypeExternalName, ExternalName: "my.database.example.com", }, }, - "test7 ExternalName my.database.example.com \n", + options: printers.GenerateOptions{}, + // Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test7", "ExternalName", "", "my.database.example.com", "", ""}}}, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.service, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printService(&test.service, test.options) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - // We ignore time - if buf.String() != test.expect { - t.Errorf("Expected: %s, but got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } @@ -2581,11 +2696,12 @@ func TestPrintPodDisruptionBudget(t *testing.T) { minAvailable := intstr.FromInt(22) maxUnavailable := intstr.FromInt(11) tests := []struct { - pdb policy.PodDisruptionBudget - expect string + pdb policy.PodDisruptionBudget + expected []metav1beta1.TableRow }{ + // Min Available set, no Max Available. { - policy.PodDisruptionBudget{ + pdb: policy.PodDisruptionBudget{ ObjectMeta: metav1.ObjectMeta{ Namespace: "ns1", Name: "pdb1", @@ -2598,10 +2714,12 @@ func TestPrintPodDisruptionBudget(t *testing.T) { PodDisruptionsAllowed: 5, }, }, - "pdb1 22 N/A 5 0s\n", + // Columns: Name, Min Available, Max Available, Allowed Disruptions, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"pdb1", "22", "N/A", int64(5), "0s"}}}, }, + // Max Available set, no Min Available. { - policy.PodDisruptionBudget{ + pdb: policy.PodDisruptionBudget{ ObjectMeta: metav1.ObjectMeta{ Namespace: "ns2", Name: "pdb2", @@ -2614,34 +2732,31 @@ func TestPrintPodDisruptionBudget(t *testing.T) { PodDisruptionsAllowed: 5, }, }, - "pdb2 N/A 11 5 0s\n", + // Columns: Name, Min Available, Max Available, Allowed Disruptions, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"pdb2", "N/A", "11", int64(5), "0s"}}}, }} - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.pdb, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printPodDisruptionBudget(&test.pdb, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Errorf("Expected: %s, got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } func TestPrintControllerRevision(t *testing.T) { tests := []struct { - history apps.ControllerRevision - expect string + history apps.ControllerRevision + expected []metav1beta1.TableRow }{ { - apps.ControllerRevision{ + history: apps.ControllerRevision{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, @@ -2656,10 +2771,10 @@ func TestPrintControllerRevision(t *testing.T) { }, Revision: 1, }, - "test1 daemonset.apps/foo 1 0s\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", "daemonset.apps/foo", int64(1), "0s"}}}, }, { - apps.ControllerRevision{ + history: apps.ControllerRevision{ ObjectMeta: metav1.ObjectMeta{ Name: "test2", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, @@ -2673,10 +2788,10 @@ func TestPrintControllerRevision(t *testing.T) { }, Revision: 2, }, - "test2 2 0s\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test2", "", int64(2), "0s"}}}, }, { - apps.ControllerRevision{ + history: apps.ControllerRevision{ ObjectMeta: metav1.ObjectMeta{ Name: "test3", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, @@ -2684,10 +2799,10 @@ func TestPrintControllerRevision(t *testing.T) { }, Revision: 3, }, - "test3 3 0s\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test3", "", int64(3), "0s"}}}, }, { - apps.ControllerRevision{ + history: apps.ControllerRevision{ ObjectMeta: metav1.ObjectMeta{ Name: "test4", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, @@ -2695,25 +2810,21 @@ func TestPrintControllerRevision(t *testing.T) { }, Revision: 4, }, - "test4 4 0s\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test4", "", int64(4), "0s"}}}, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.history, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printControllerRevision(&test.history, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Errorf("Expected: %s, but got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } @@ -2724,11 +2835,12 @@ func boolP(b bool) *bool { func TestPrintReplicaSet(t *testing.T) { tests := []struct { replicaSet apps.ReplicaSet - expect string - wideExpect string + options printers.GenerateOptions + expected []metav1beta1.TableRow }{ + // Generate options empty { - apps.ReplicaSet{ + replicaSet: apps.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, @@ -2756,40 +2868,57 @@ func TestPrintReplicaSet(t *testing.T) { ReadyReplicas: 2, }, }, - "test1 5 5 2 0s\n", - "test1 5 5 2 0s fake-container1,fake-container2 fake-image1,fake-image2 foo=bar\n", + options: printers.GenerateOptions{}, + // Columns: Name, Desired, Current, Ready, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", int64(5), int64(5), int64(2), "0s"}}}, + }, + // Generate options "Wide" + { + replicaSet: apps.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test1", + 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", + }, + }, + }, + }, + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + }, + Status: apps.ReplicaSetStatus{ + Replicas: 5, + ReadyReplicas: 2, + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, Desired, Current, Ready, Age, Containers, Images, Selector + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", int64(5), int64(5), int64(2), "0s", "fake-container1,fake-container2", "fake-image1,fake-image2", "foo=bar"}}}, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.replicaSet, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printReplicaSet(&test.replicaSet, test.options) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Errorf("Expected: %s, got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() - - table, err = printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.replicaSet, printers.GenerateOptions{Wide: true}) - if err != nil { - t.Fatal(err) - } - verifyTable(t, table) - printer = printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true, Wide: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) - } - if buf.String() != test.wideExpect { - t.Errorf("Expected: %s, got: %s", test.wideExpect, buf.String()) - } - buf.Reset() } } @@ -2801,12 +2930,12 @@ func TestPrintPersistentVolume(t *testing.T) { Namespace: "default", } tests := []struct { - pv api.PersistentVolume - expect string + pv api.PersistentVolume + expected []metav1beta1.TableRow }{ { // Test bound - api.PersistentVolume{ + pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", }, @@ -2821,11 +2950,11 @@ func TestPrintPersistentVolume(t *testing.T) { Phase: api.VolumeBound, }, }, - "test1 4Gi ROX Bound default/test \n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", "4Gi", "ROX", "", "Bound", "default/test", "", "", "", ""}}}, }, { - // // Test failed - api.PersistentVolume{ + // Test failed + pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test2", }, @@ -2840,11 +2969,11 @@ func TestPrintPersistentVolume(t *testing.T) { Phase: api.VolumeFailed, }, }, - "test2 4Gi ROX Failed default/test \n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test2", "4Gi", "ROX", "", "Failed", "default/test", "", "", "", ""}}}, }, { // Test pending - api.PersistentVolume{ + pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test3", }, @@ -2859,11 +2988,11 @@ func TestPrintPersistentVolume(t *testing.T) { Phase: api.VolumePending, }, }, - "test3 10Gi RWX Pending default/test \n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test3", "10Gi", "RWX", "", "Pending", "default/test", "", "", "", ""}}}, }, { // Test pending, storageClass - api.PersistentVolume{ + pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test4", }, @@ -2879,11 +3008,11 @@ func TestPrintPersistentVolume(t *testing.T) { Phase: api.VolumePending, }, }, - "test4 10Gi RWO Pending default/test my-scn \n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test4", "10Gi", "RWO", "", "Pending", "default/test", "my-scn", "", "", ""}}}, }, { // Test available - api.PersistentVolume{ + pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test5", }, @@ -2899,11 +3028,11 @@ func TestPrintPersistentVolume(t *testing.T) { Phase: api.VolumeAvailable, }, }, - "test5 10Gi RWO Available default/test my-scn \n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test5", "10Gi", "RWO", "", "Available", "default/test", "my-scn", "", "", ""}}}, }, { // Test released - api.PersistentVolume{ + pv: api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "test6", }, @@ -2919,24 +3048,21 @@ func TestPrintPersistentVolume(t *testing.T) { Phase: api.VolumeReleased, }, }, - "test6 10Gi RWO Released default/test my-scn \n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test6", "10Gi", "RWO", "", "Released", "default/test", "my-scn", "", "", ""}}}, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.pv, printers.GenerateOptions{}) + + for i, test := range tests { + rows, err := printPersistentVolume(&test.pv, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Errorf("Expected: %s, but got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } @@ -2944,12 +3070,12 @@ func TestPrintPersistentVolumeClaim(t *testing.T) { volumeMode := api.PersistentVolumeFilesystem myScn := "my-scn" tests := []struct { - pvc api.PersistentVolumeClaim - expect string + pvc api.PersistentVolumeClaim + expected []metav1beta1.TableRow }{ { // Test name, num of containers, restarts, container ready status - api.PersistentVolumeClaim{ + pvc: api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test1", }, @@ -2965,11 +3091,11 @@ func TestPrintPersistentVolumeClaim(t *testing.T) { }, }, }, - "test1 Bound my-volume 4Gi ROX Filesystem\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test1", "Bound", "my-volume", "4Gi", "ROX", "", "", "Filesystem"}}}, }, { // Test name, num of containers, restarts, container ready status - api.PersistentVolumeClaim{ + pvc: api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test2", }, @@ -2984,11 +3110,11 @@ func TestPrintPersistentVolumeClaim(t *testing.T) { }, }, }, - "test2 Lost Filesystem\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test2", "Lost", "", "", "", "", "", "Filesystem"}}}, }, { // Test name, num of containers, restarts, container ready status - api.PersistentVolumeClaim{ + pvc: api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test3", }, @@ -3004,11 +3130,11 @@ func TestPrintPersistentVolumeClaim(t *testing.T) { }, }, }, - "test3 Pending my-volume 10Gi RWX Filesystem\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test3", "Pending", "my-volume", "10Gi", "RWX", "", "", "Filesystem"}}}, }, { // Test name, num of containers, restarts, container ready status - api.PersistentVolumeClaim{ + pvc: api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test4", }, @@ -3025,11 +3151,11 @@ func TestPrintPersistentVolumeClaim(t *testing.T) { }, }, }, - "test4 Pending my-volume 10Gi RWO my-scn Filesystem\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test4", "Pending", "my-volume", "10Gi", "RWO", "my-scn", "", "Filesystem"}}}, }, { // Test name, num of containers, restarts, container ready status - api.PersistentVolumeClaim{ + pvc: api.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "test5", }, @@ -3045,35 +3171,35 @@ func TestPrintPersistentVolumeClaim(t *testing.T) { }, }, }, - "test5 Pending my-volume 10Gi RWO my-scn \n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"test5", "Pending", "my-volume", "10Gi", "RWO", "my-scn", "", ""}}}, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.pvc, printers.GenerateOptions{Wide: true}) + + for i, test := range tests { + rows, err := printPersistentVolumeClaim(&test.pvc, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true, Wide: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Errorf("Expected: %s, but got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } func TestPrintCronJob(t *testing.T) { + completions := int32(2) suspend := false tests := []struct { - cronjob batch.CronJob - expect string + cronjob batch.CronJob + options printers.GenerateOptions + expected []metav1beta1.TableRow }{ + // Basic cron job; does not print containers, images, or labels. { - batch.CronJob{ + cronjob: batch.CronJob{ ObjectMeta: metav1.ObjectMeta{ Name: "cronjob1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, @@ -3081,15 +3207,77 @@ func TestPrintCronJob(t *testing.T) { Spec: batch.CronJobSpec{ Schedule: "0/5 * * * ?", Suspend: &suspend, + JobTemplate: batch.JobTemplateSpec{ + 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{"a": "b"}}, + }, + }, }, Status: batch.CronJobStatus{ LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)}, }, }, - "cronjob1 0/5 * * * ? False 0 0s 0s\n", + options: printers.GenerateOptions{}, + // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"cronjob1", "0/5 * * * ?", "False", int64(0), "0s", "0s"}}}, }, + // Generate options: Wide; prints containers, images, and labels. { - batch.CronJob{ + cronjob: 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, + 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{"a": "b"}}, + }, + }, + }, + Status: batch.CronJobStatus{ + LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)}, + }, + }, + options: printers.GenerateOptions{Wide: true}, + // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"cronjob1", "0/5 * * * ?", "False", int64(0), "0s", "0s", "fake-job-container1,fake-job-container2", "fake-job-image1,fake-job-image2", "a=b"}}}, + }, + // CronJob with Last Schedule and Age + { + cronjob: batch.CronJob{ ObjectMeta: metav1.ObjectMeta{ Name: "cronjob2", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, @@ -3102,10 +3290,13 @@ func TestPrintCronJob(t *testing.T) { LastScheduleTime: &metav1.Time{Time: time.Now().Add(-3e10)}, }, }, - "cronjob2 0/5 * * * ? False 0 30s 5m\n", + options: printers.GenerateOptions{}, + // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"cronjob2", "0/5 * * * ?", "False", int64(0), "30s", "5m"}}}, }, + // CronJob without Last Schedule { - batch.CronJob{ + cronjob: batch.CronJob{ ObjectMeta: metav1.ObjectMeta{ Name: "cronjob3", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, @@ -3116,70 +3307,64 @@ func TestPrintCronJob(t *testing.T) { }, Status: batch.CronJobStatus{}, }, - "cronjob3 0/5 * * * ? False 0 5m\n", + options: printers.GenerateOptions{}, + // Columns: Name, Schedule, Suspend, Active, Last Schedule, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"cronjob3", "0/5 * * * ?", "False", int64(0), "", "5m"}}}, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.cronjob, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printCronJob(&test.cronjob, test.options) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Errorf("Expected: %s, got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } func TestPrintStorageClass(t *testing.T) { tests := []struct { - sc storage.StorageClass - expect string + sc storage.StorageClass + expected []metav1beta1.TableRow }{ { - storage.StorageClass{ + sc: storage.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "sc1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Provisioner: "kubernetes.io/glusterfs", }, - "sc1 kubernetes.io/glusterfs 0s\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"sc1", "kubernetes.io/glusterfs", "0s"}}}, }, { - storage.StorageClass{ + sc: storage.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "sc2", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Provisioner: "kubernetes.io/nfs", }, - "sc2 kubernetes.io/nfs 5m\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"sc2", "kubernetes.io/nfs", "5m"}}}, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.sc, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printStorageClass(&test.sc, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Errorf("Expected: %s, got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } @@ -3187,11 +3372,11 @@ func TestPrintLease(t *testing.T) { holder1 := "holder1" holder2 := "holder2" tests := []struct { - sc coordination.Lease - expect string + lease coordination.Lease + expected []metav1beta1.TableRow }{ { - coordination.Lease{ + lease: coordination.Lease{ ObjectMeta: metav1.ObjectMeta{ Name: "lease1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, @@ -3200,10 +3385,10 @@ func TestPrintLease(t *testing.T) { HolderIdentity: &holder1, }, }, - "lease1 holder1 0s\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"lease1", "holder1", "0s"}}}, }, { - coordination.Lease{ + lease: coordination.Lease{ ObjectMeta: metav1.ObjectMeta{ Name: "lease2", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, @@ -3212,44 +3397,41 @@ func TestPrintLease(t *testing.T) { HolderIdentity: &holder2, }, }, - "lease2 holder2 5m\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"lease2", "holder2", "5m"}}}, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.sc, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printLease(&test.lease, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Errorf("Expected: %s, got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } func TestPrintPriorityClass(t *testing.T) { tests := []struct { - pc scheduling.PriorityClass - expect string + pc scheduling.PriorityClass + expected []metav1beta1.TableRow }{ { - scheduling.PriorityClass{ + pc: scheduling.PriorityClass{ ObjectMeta: metav1.ObjectMeta{ Name: "pc1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Value: 1, }, - "pc1 1 false 0s\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"pc1", int64(1), bool(false), "0s"}}}, }, { - scheduling.PriorityClass{ + pc: scheduling.PriorityClass{ ObjectMeta: metav1.ObjectMeta{ Name: "pc2", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, @@ -3257,70 +3439,62 @@ func TestPrintPriorityClass(t *testing.T) { Value: 1000000000, GlobalDefault: true, }, - "pc2 1000000000 true 5m\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"pc2", int64(1000000000), bool(true), "5m"}}}, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.pc, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printPriorityClass(&test.pc, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Errorf("Expected: %s, got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } func TestPrintRuntimeClass(t *testing.T) { tests := []struct { - rc nodeapi.RuntimeClass - expect string + rc nodeapi.RuntimeClass + expected []metav1beta1.TableRow }{ { - nodeapi.RuntimeClass{ + rc: nodeapi.RuntimeClass{ ObjectMeta: metav1.ObjectMeta{ Name: "rc1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, Handler: "h1", }, - "rc1 h1 0s\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"rc1", "h1", "0s"}}}, }, { - nodeapi.RuntimeClass{ + rc: nodeapi.RuntimeClass{ ObjectMeta: metav1.ObjectMeta{ Name: "rc2", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, }, Handler: "h2", }, - "rc2 h2 5m\n", + expected: []metav1beta1.TableRow{{Cells: []interface{}{"rc2", "h2", "5m"}}}, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.rc, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printRuntimeClass(&test.rc, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Errorf("Expected: %s, got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } @@ -3330,10 +3504,10 @@ func TestPrintEndpointSlice(t *testing.T) { tests := []struct { endpointSlice discovery.EndpointSlice - expect string + expected []metav1beta1.TableRow }{ { - discovery.EndpointSlice{ + endpointSlice: discovery.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ Name: "abcslice.123", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, @@ -3348,9 +3522,10 @@ func TestPrintEndpointSlice(t *testing.T) { Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"}, }}, }, - "abcslice.123 IP 80 10.1.2.3,2001:db8::1234:5678 0s\n", + // Columns: Name, AddressType, Ports, Endpoints, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"abcslice.123", "IP", "80", "10.1.2.3,2001:db8::1234:5678", "0s"}}}, }, { - discovery.EndpointSlice{ + endpointSlice: discovery.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ Name: "longerslicename.123", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, @@ -3371,9 +3546,10 @@ func TestPrintEndpointSlice(t *testing.T) { Addresses: []string{"10.2.3.4", "2001:db8::2345:6789"}, }}, }, - "longerslicename.123 IP 80,443 10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more... 5m\n", + // Columns: Name, AddressType, Ports, Endpoints, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"longerslicename.123", "IP", "80,443", "10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more...", "5m"}}}, }, { - discovery.EndpointSlice{ + endpointSlice: discovery.EndpointSlice{ ObjectMeta: metav1.ObjectMeta{ Name: "multiportslice.123", CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)}, @@ -3402,25 +3578,22 @@ func TestPrintEndpointSlice(t *testing.T) { Addresses: []string{"10.2.3.4", "2001:db8::2345:6789"}, }}, }, - "multiportslice.123 IP 80,443,3000 + 1 more... 10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more... 5m\n", + // Columns: Name, AddressType, Ports, Endpoints, Age + expected: []metav1beta1.TableRow{{Cells: []interface{}{"multiportslice.123", "IP", "80,443,3000 + 1 more...", "10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more...", "5m"}}}, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.endpointSlice, printers.GenerateOptions{}) + for i, test := range tests { + rows, err := printEndpointSlice(&test.endpointSlice, printers.GenerateOptions{}) if err != nil { t.Fatal(err) } - verifyTable(t, table) - printer := printers.NewTablePrinter(printers.PrintOptions{NoHeaders: true}) - if err := printer.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if buf.String() != test.expect { - t.Errorf("Expected: %s, got: %s", test.expect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } From 6ced323a94779d60f9181648115a73864a5c27f8 Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Fri, 11 Oct 2019 11:33:18 -0700 Subject: [PATCH 3/5] 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 From cc56738a9e5d2969ec8d01f42816ce86671c25d0 Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Fri, 11 Oct 2019 12:31:36 -0700 Subject: [PATCH 4/5] Removes unnecessary/irrelevant tests Remove test TestPrintPodTable Removes test TestPrintPodShowLabels --- pkg/printers/internalversion/printers_test.go | 339 ------------------ 1 file changed, 339 deletions(-) diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index 266dbf8c3ff..9b35d8423b0 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -19,7 +19,6 @@ package internalversion import ( "bytes" "reflect" - "strconv" "strings" "testing" "time" @@ -27,8 +26,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/kubernetes/pkg/apis/apps" @@ -943,264 +940,6 @@ func TestPrintServiceLoadBalancer(t *testing.T) { } } -func TestPrintHumanReadableWithNamespace(t *testing.T) { - namespaceName := "testnamespace" - name := "test" - table := []struct { - obj runtime.Object - isNamespaced bool - }{ - { - obj: &api.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceName}, - }, - isNamespaced: true, - }, - { - obj: &api.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceName}, - Spec: api.ReplicationControllerSpec{ - Replicas: 2, - Template: &api.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "name": "foo", - "type": "production", - }, - }, - Spec: api.PodSpec{ - Containers: []api.Container{ - { - Image: "foo/bar", - TerminationMessagePath: api.TerminationMessagePathDefault, - ImagePullPolicy: api.PullIfNotPresent, - }, - }, - RestartPolicy: api.RestartPolicyAlways, - DNSPolicy: api.DNSDefault, - NodeSelector: map[string]string{ - "baz": "blah", - }, - }, - }, - }, - }, - isNamespaced: true, - }, - { - obj: &api.Service{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceName}, - Spec: api.ServiceSpec{ - ClusterIP: "1.2.3.4", - Ports: []api.ServicePort{ - { - Port: 80, - Protocol: "TCP", - }, - }, - }, - Status: api.ServiceStatus{ - LoadBalancer: api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - { - IP: "2.3.4.5", - }, - }, - }, - }, - }, - isNamespaced: true, - }, - { - obj: &api.Endpoints{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceName}, - Subsets: []api.EndpointSubset{{ - Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}, {IP: "localhost"}}, - Ports: []api.EndpointPort{{Port: 8080}}, - }, - }, - }, - isNamespaced: true, - }, - { - obj: &api.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: name}, - }, - isNamespaced: false, - }, - { - obj: &api.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceName}, - }, - isNamespaced: true, - }, - { - obj: &api.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceName}, - Secrets: []api.ObjectReference{}, - }, - isNamespaced: true, - }, - { - obj: &api.Node{ - ObjectMeta: metav1.ObjectMeta{Name: name}, - Status: api.NodeStatus{}, - }, - isNamespaced: false, - }, - { - obj: &api.PersistentVolume{ - ObjectMeta: metav1.ObjectMeta{Name: name}, - Spec: api.PersistentVolumeSpec{}, - }, - isNamespaced: false, - }, - { - obj: &api.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceName}, - Spec: api.PersistentVolumeClaimSpec{}, - }, - isNamespaced: true, - }, - { - obj: &api.Event{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceName}, - Source: api.EventSource{Component: "kubelet"}, - Message: "Item 1", - FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), - LastTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)), - Count: 1, - Type: api.EventTypeNormal, - }, - isNamespaced: true, - }, - { - obj: &api.ResourceQuota{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceName}, - }, - isNamespaced: true, - }, - { - obj: &api.ComponentStatus{ - Conditions: []api.ComponentCondition{ - {Type: api.ComponentHealthy, Status: api.ConditionTrue, Message: "ok", Error: ""}, - }, - }, - isNamespaced: false, - }, - } - - //*******// - options := printers.PrintOptions{ - WithNamespace: true, - NoHeaders: true, - } - generator := printers.NewTableGenerator().With(AddHandlers) - printer := printers.NewTablePrinter(options) - for i, test := range table { - table, err := generator.GenerateTable(test.obj, printers.GenerateOptions{}) - if err != nil { - t.Fatalf("An error occurred generating table for object: %#v", err) - } - buffer := &bytes.Buffer{} - err = printer.PrintObj(table, buffer) - if err != nil { - t.Fatalf("An error occurred printing object: %#v", err) - } - if test.isNamespaced { - if !strings.HasPrefix(buffer.String(), namespaceName+" ") { - t.Errorf("%d: Expect printing object %T to contain namespace %q, got %s", i, test.obj, namespaceName, buffer.String()) - } - } else { - if !strings.HasPrefix(buffer.String(), " ") { - t.Errorf("%d: Expect printing object %T to not contain namespace got %s", i, test.obj, buffer.String()) - } - } - } -} - -func TestPrintPodTable(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}, - }, - }, - } - 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 { - obj runtime.Object - opts printers.PrintOptions - expect string - }{ - { - obj: runningPod, - opts: printers.PrintOptions{}, - expect: "NAME READY STATUS RESTARTS AGE\ntest1 1/2 Running 6 \n", - }, - { - obj: runningPod, - opts: printers.PrintOptions{WithKind: true, Kind: schema.GroupKind{Kind: "Pod"}}, - expect: "NAME READY STATUS RESTARTS AGE\npod/test1 1/2 Running 6 \n", - }, - { - obj: runningPod, - opts: printers.PrintOptions{ShowLabels: true}, - expect: "NAME READY STATUS RESTARTS AGE LABELS\ntest1 1/2 Running 6 a=1,b=2\n", - }, - { - obj: &api.PodList{Items: []api.Pod{*runningPod, *failedPod}}, - opts: printers.PrintOptions{ColumnLabels: []string{"a"}}, - expect: "NAME READY STATUS RESTARTS AGE A\ntest1 1/2 Running 6 1\ntest2 1/2 Failed 6 \n", - }, - { - obj: runningPod, - opts: printers.PrintOptions{NoHeaders: true}, - expect: "test1 1/2 Running 6 \n", - }, - { - obj: failedPod, - opts: printers.PrintOptions{}, - expect: "NAME READY STATUS RESTARTS AGE\ntest2 1/2 Failed 6 \n", - }, - { - obj: failedPod, - opts: printers.PrintOptions{}, - expect: "NAME READY STATUS RESTARTS AGE\ntest2 1/2 Failed 6 \n", - }, - } - - for i, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(test.obj, printers.GenerateOptions{}) - if err != nil { - t.Fatal(err) - } - verifyTable(t, table) - buf := &bytes.Buffer{} - p := printers.NewTablePrinter(test.opts) - if err := p.PrintObj(table, buf); err != nil { - t.Fatal(err) - } - if test.expect != buf.String() { - t.Errorf("%d mismatch:\n%s\n%s", i, strconv.Quote(test.expect), strconv.Quote(buf.String())) - } - } -} - func TestPrintPod(t *testing.T) { tests := []struct { pod api.Pod @@ -2918,84 +2657,6 @@ func TestPrintHPA(t *testing.T) { } } -func TestPrintPodShowLabels(t *testing.T) { - tests := []struct { - pod api.Pod - showLabels bool - expectLabels []string - }{ - { - // 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}, - }, - }, - }, - true, - []string{"col1=asd", "COL2=zxc"}, - }, - { - // Test name, num of containers, restarts, container ready status - api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test1", - Labels: map[string]string{"col3": "asd", "COL4": "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}, - }, - }, - }, - false, - []string{}, - }, - } - - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.pod, printers.GenerateOptions{}) - if err != nil { - t.Fatal(err) - } - - buf := bytes.NewBuffer([]byte{}) - printer := printers.NewTablePrinter(printers.PrintOptions{ShowLabels: test.showLabels}) - if err := printer.PrintObj(table, buf); err != nil { - t.Errorf("Error printing table: %v", err) - } - - if test.showLabels { - // LABELS column header should be present. - if !strings.Contains(buf.String(), "LABELS") { - t.Errorf("Error Printing Table: missing LABELS column heading: (%s)", buf.String()) - } - // Validate that each of the expected labels is present. - for _, label := range test.expectLabels { - if !strings.Contains(buf.String(), label) { - t.Errorf("Error Printing Table: missing LABEL column value: (%s) from (%s)", label, buf.String()) - } - } - } else { - // LABELS column header should not be present. - if strings.Contains(buf.String(), "LABELS") { - t.Errorf("Error Printing Table: unexpected LABEL column heading: (%s)", buf.String()) - } - } - } -} - func TestPrintService(t *testing.T) { singleExternalIP := []string{"80.11.12.10"} mulExternalIP := []string{"80.11.12.10", "80.11.12.11"} From 30d8a9376816e3d143f4b3b89d45ece9eef551a3 Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Fri, 11 Oct 2019 13:28:21 -0700 Subject: [PATCH 5/5] Cleanup printer test package. Updates internalversion BUILD file dependencies Removes pkg/printers/internalversion from staticcheck failures --- hack/.staticcheck_failures | 1 - pkg/printers/internalversion/BUILD | 2 + pkg/printers/internalversion/printers_test.go | 40 ------------------- 3 files changed, 2 insertions(+), 41 deletions(-) diff --git a/hack/.staticcheck_failures b/hack/.staticcheck_failures index c4cf3952eea..5b90195de87 100644 --- a/hack/.staticcheck_failures +++ b/hack/.staticcheck_failures @@ -28,7 +28,6 @@ pkg/kubelet/pluginmanager/pluginwatcher pkg/kubelet/remote pkg/master pkg/printers -pkg/printers/internalversion pkg/probe/http pkg/proxy/healthcheck pkg/proxy/iptables diff --git a/pkg/printers/internalversion/BUILD b/pkg/printers/internalversion/BUILD index bd200acae23..8f23319ae43 100644 --- a/pkg/printers/internalversion/BUILD +++ b/pkg/printers/internalversion/BUILD @@ -20,12 +20,14 @@ go_test( "//pkg/apis/apps:go_default_library", "//pkg/apis/autoscaling:go_default_library", "//pkg/apis/batch:go_default_library", + "//pkg/apis/certificates:go_default_library", "//pkg/apis/coordination:go_default_library", "//pkg/apis/core:go_default_library", "//pkg/apis/discovery:go_default_library", "//pkg/apis/networking:go_default_library", "//pkg/apis/node:go_default_library", "//pkg/apis/policy:go_default_library", + "//pkg/apis/rbac:go_default_library", "//pkg/apis/scheduling:go_default_library", "//pkg/apis/storage:go_default_library", "//pkg/printers:go_default_library", diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index 9b35d8423b0..3b08ce5b8ab 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -17,9 +17,7 @@ limitations under the License. package internalversion import ( - "bytes" "reflect" - "strings" "testing" "time" @@ -4757,41 +4755,3 @@ func TestPrintEndpointSlice(t *testing.T) { } } } - -func verifyTable(t *testing.T, table *metav1beta1.Table) { - var panicErr interface{} - func() { - defer func() { - panicErr = recover() - }() - table.DeepCopyObject() // cells are untyped, better check that types are JSON types and can be deep copied - }() - - if panicErr != nil { - t.Errorf("unexpected panic during deepcopy of table %#v: %v", table, panicErr) - } -} - -// VerifyDatesInOrder checks the start of each line for a RFC1123Z date -// and posts error if all subsequent dates are not equal or increasing -func VerifyDatesInOrder( - resultToTest, rowDelimiter, columnDelimiter string, t *testing.T) { - lines := strings.Split(resultToTest, rowDelimiter) - var previousTime time.Time - for _, str := range lines { - columns := strings.Split(str, columnDelimiter) - if len(columns) > 0 { - currentTime, err := time.Parse(time.RFC1123Z, columns[0]) - if err == nil { - if previousTime.After(currentTime) { - t.Errorf( - "Output is not sorted by time. %s should be listed after %s. Complete output: %s", - previousTime.Format(time.RFC1123Z), - currentTime.Format(time.RFC1123Z), - resultToTest) - } - previousTime = currentTime - } - } - } -}