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 e8ea8e8d917..8f23319ae43 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", ], @@ -19,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", @@ -41,8 +44,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..3b08ce5b8ab 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -17,584 +17,123 @@ limitations under the License. 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" + "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" ) -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 TestPrintHandler struct { + numCalls int } -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 (t *TestPrintHandler) TableHandler(columnDefinitions []metav1beta1.TableColumnDefinition, printFunc interface{}) error { + t.numCalls++ + return nil } -func (in *TestStruct) DeepCopyObject() runtime.Object { - panic("never called") +func (t *TestPrintHandler) getNumCalls() int { + return t.numCalls } -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", - }, +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 { - expected string - options printers.PrintOptions - object runtime.Object + event api.Event + options printers.GenerateOptions + expected []metav1beta1.TableRow }{ + // Basic event; no generate options { - 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", + 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"}}}, }, } - 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()) + for i, test := range tests { + rows, err := printEvent(&test.event, test.options) if err != nil { - t.Fatalf("unexpected error: %v", err) + t.Fatal(err) } - if !matches { - t.Errorf("wanted:\n%s\ngot:\n%s", test.expected, out) + for i := range rows { + rows[i].Object.Object = nil } - } -} - -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) - } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } } } func TestPrintEventsResultSorted(t *testing.T) { - obj := api.EventList{ + eventList := api.EventList{ Items: []api.Event{ { Source: api.EventSource{Component: "kubelet"}, @@ -623,35 +162,185 @@ 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) + 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) + } +} - // Assert - if err != nil { - t.Fatalf("An error occurred printing the Table: %#v", err) +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)) + } } - 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{ @@ -659,7 +348,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{ @@ -668,14 +358,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{ @@ -683,21 +375,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{ @@ -705,7 +400,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{ @@ -713,24 +409,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)) } } } @@ -739,13 +432,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{ @@ -754,7 +448,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{ @@ -763,24 +458,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)) } } } @@ -788,8 +480,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{ @@ -799,7 +491,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{ @@ -809,24 +506,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)) } } } @@ -834,8 +532,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{ @@ -845,7 +543,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{ @@ -855,24 +558,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)) } } } @@ -880,8 +584,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{ @@ -891,7 +595,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{ @@ -901,24 +610,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)) } } } @@ -926,39 +636,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)) } } } @@ -966,22 +673,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{ @@ -992,24 +709,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)) } } } @@ -1017,22 +734,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{ @@ -1043,45 +770,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{ @@ -1100,433 +816,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{ - { - 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", - }, - }, - }, - }, - }, - { - 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", - }, - }, - }, - }, - { - 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", - }, - }, - }, - }, - }, - { - 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", - }, - }, - }, - }, - }, - } - - 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")) - } - } - } -} - -func TestPrintHumanReadableWithNamespace(t *testing.T) { - namespaceName := "testnamespace" - name := "test" - table := []struct { - obj runtime.Object - isNamespaced bool +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 { - 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}, + service: api.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "service1"}, Spec: api.ServiceSpec{ ClusterIP: "1.2.3.4", - Ports: []api.ServicePort{ - { - Port: 80, - Protocol: "TCP", - }, - }, + Type: "LoadBalancer", + Ports: []api.ServicePort{{Port: 80, Protocol: "TCP"}}, }, Status: api.ServiceStatus{ LoadBalancer: api.LoadBalancerStatus{ - Ingress: []api.LoadBalancerIngress{ - { - IP: "2.3.4.5", - }, - }, - }, + Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}}}, }, }, - isNamespaced: true, + 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. { - 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}}, - }, + 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"}}, }, }, - isNamespaced: true, + 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. { - 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: ""}, + 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"}}}, }, }, - isNamespaced: false, + 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", ""}}}, }, - } - - //*******// - 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}, + // Long external IP's list gets elided. + { + 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"}}}, + }, }, + 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", ""}}}, }, - } - 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}, + // 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"}}}, + }, }, - }, - } - 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", + 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 i, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(test.obj, printers.GenerateOptions{}) + rows, err := printService(&test.service, test.options) if err != nil { - t.Fatal(err) + t.Fatalf("Error printing table rows for Service: %#v", err) } - verifyTable(t, table) - buf := &bytes.Buffer{} - p := printers.NewTablePrinter(test.opts) - if err := p.PrintObj(table, buf); err != nil { - t.Fatal(err) + for i := range rows { + rows[i].Object.Object = nil } - if test.expect != buf.String() { - t.Errorf("%d mismatch:\n%s\n%s", i, strconv.Quote(test.expect), strconv.Quote(buf.String())) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } } } @@ -1768,6 +1175,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 @@ -1837,6 +1326,7 @@ func TestPrintNonTerminatedPod(t *testing.T) { }, }, }, + // Columns: Name, Ready, Reason, Restarts, Age []metav1beta1.TableRow{{Cells: []interface{}{"test1", "1/2", "Running", int64(6), ""}}}, }, { @@ -1852,6 +1342,7 @@ func TestPrintNonTerminatedPod(t *testing.T) { }, }, }, + // Columns: Name, Ready, Reason, Restarts, Age []metav1beta1.TableRow{{Cells: []interface{}{"test2", "1/2", "Pending", int64(6), ""}}}, }, { @@ -1867,10 +1358,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)}, @@ -1882,7 +1374,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 @@ -1897,17 +1395,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 } @@ -1917,95 +1419,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)) } } @@ -2060,19 +1601,151 @@ func TestTranslateTimestampUntil(t *testing.T) { } func TestPrintDeployment(t *testing.T) { + + 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, + }, + } + tests := []struct { deployment apps.Deployment - expect string - wideExpect string + options printers.GenerateOptions + expected []metav1beta1.TableRow }{ + // Test Deployment with no generate options. { - apps.Deployment{ + 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) + } + 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 TestPrintDaemonSet(t *testing.T) { + + 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", + }, + }, + }, + }, + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + }, + Status: apps.DaemonSetStatus{ + CurrentNumberScheduled: 2, + DesiredNumberScheduled: 3, + NumberReady: 1, + UpdatedNumberScheduled: 2, + NumberAvailable: 0, + }, + } + + 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) + } + 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 TestPrintDaemonSetList(t *testing.T) { + + daemonSetList := apps.DaemonSetList{ + Items: []apps.DaemonSet{ + { ObjectMeta: metav1.ObjectMeta{ - Name: "test1", + Name: "daemonset1", CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)}, }, - Spec: apps.DeploymentSpec{ - Replicas: 5, + Spec: apps.DaemonSetSpec{ Template: api.PodTemplateSpec{ Spec: api.PodSpec{ Containers: []api.Container{ @@ -2087,64 +1760,6 @@ func TestPrintDeployment(t *testing.T) { }, }, }, - 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", - }, - } - - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.deployment, 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) - } - if buf.String() != test.expect { - t.Fatalf("Expected: %s, got: %s", test.expect, buf.String()) - } - 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)}, - }, }, Status: apps.DaemonSetStatus{ CurrentNumberScheduled: 2, @@ -2154,25 +1769,41 @@ func TestPrintDaemonSet(t *testing.T) { NumberAvailable: 0, }, }, - "test1 3 2 1 2 0 0s", + { + 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, + }, + }, }, } - buf := bytes.NewBuffer([]byte{}) - for _, test := range tests { - table, err := printers.NewTableGenerator().With(AddHandlers).GenerateTable(&test.ds, 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) - } - if !strings.HasPrefix(buf.String(), test.startsWith) { - t.Fatalf("Expected to start with %s but got %s", test.startsWith, buf.String()) - } - buf.Reset() + // 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)) } } @@ -2180,26 +1811,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)}, @@ -2211,10 +1899,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)}, @@ -2228,10 +1919,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)}, @@ -2244,25 +1937,103 @@ 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() + } +} + +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)) } } @@ -2276,11 +2047,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{ @@ -2294,11 +2065,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{ @@ -2328,11 +2100,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{ @@ -2376,11 +2149,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{ @@ -2410,11 +2184,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{ @@ -2457,11 +2232,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{ @@ -2490,11 +2266,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{ @@ -2536,11 +2313,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{ @@ -2573,11 +2351,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{ @@ -2627,11 +2406,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{ @@ -2658,11 +2438,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{ @@ -2700,11 +2481,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{ @@ -2731,11 +2513,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{ @@ -2774,11 +2557,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{ @@ -2852,103 +2636,21 @@ 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()) - } - - buff.Reset() - } -} - -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()) - } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } } } @@ -2957,12 +2659,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, @@ -2973,13 +2676,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, @@ -2993,11 +2719,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, @@ -3010,11 +2738,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, @@ -3028,11 +2758,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, @@ -3056,11 +2788,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, @@ -3088,37 +2822,86 @@ 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() + } +} + +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)) } } @@ -3126,11 +2909,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", @@ -3143,10 +2927,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", @@ -3159,34 +2945,84 @@ 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 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 - 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)}, @@ -3201,10 +3037,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)}, @@ -3218,10 +3054,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)}, @@ -3229,10 +3065,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)}, @@ -3240,25 +3076,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() } } @@ -3266,14 +3098,499 @@ 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 - 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)}, @@ -3301,40 +3618,212 @@ 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}) +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) } - 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.wideExpect { - t.Errorf("Expected: %s, got: %s", test.wideExpect, buf.String()) + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } - buf.Reset() } } @@ -3346,12 +3835,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", }, @@ -3366,11 +3855,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", }, @@ -3385,11 +3874,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", }, @@ -3404,11 +3893,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", }, @@ -3424,11 +3913,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", }, @@ -3444,11 +3933,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", }, @@ -3464,24 +3953,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() } } @@ -3489,12 +3975,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", }, @@ -3510,11 +3996,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", }, @@ -3529,11 +4015,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", }, @@ -3549,11 +4035,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", }, @@ -3570,11 +4056,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", }, @@ -3590,35 +4076,103 @@ 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 { + 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 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) } - if buf.String() != test.expect { - t.Errorf("Expected: %s, but got: %s", test.expect, buf.String()) + 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)) } - 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)}, @@ -3626,15 +4180,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)}, @@ -3647,10 +4263,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)}, @@ -3661,70 +4280,129 @@ 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 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 - 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() } } @@ -3732,11 +4410,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)}, @@ -3745,10 +4423,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)}, @@ -3757,44 +4435,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)}, @@ -3802,71 +4477,184 @@ 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 { + 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 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) } - if buf.String() != test.expect { - t.Errorf("Expected: %s, got: %s", test.expect, buf.String()) + 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)) } - buf.Reset() } + } func TestPrintEndpointSlice(t *testing.T) { @@ -3875,10 +4663,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)}, @@ -3893,9 +4681,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)}, @@ -3916,9 +4705,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)}, @@ -3947,62 +4737,21 @@ 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()) - } - buf.Reset() - } -} - -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 - } + if !reflect.DeepEqual(test.expected, rows) { + t.Errorf("%d mismatch: %s", i, diff.ObjectReflectDiff(test.expected, rows)) } } }