diff --git a/pkg/printers/internalversion/BUILD b/pkg/printers/internalversion/BUILD index e8ea8e8d917..bd200acae23 100644 --- a/pkg/printers/internalversion/BUILD +++ b/pkg/printers/internalversion/BUILD @@ -9,6 +9,7 @@ load( go_test( name = "go_default_test", srcs = [ + "additional_printers_test.go", "printers_test.go", "sorted_resource_name_list_test.go", ], @@ -41,8 +42,8 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library", "//staging/src/k8s.io/cli-runtime/pkg/printers:go_default_library", + "//vendor/gopkg.in/yaml.v2:go_default_library", "//vendor/k8s.io/utils/pointer:go_default_library", - "//vendor/sigs.k8s.io/yaml:go_default_library", ], ) diff --git a/pkg/printers/internalversion/additional_printers_test.go b/pkg/printers/internalversion/additional_printers_test.go new file mode 100644 index 00000000000..48efac4a860 --- /dev/null +++ b/pkg/printers/internalversion/additional_printers_test.go @@ -0,0 +1,589 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internalversion + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "regexp" + "testing" + + yaml "gopkg.in/yaml.v2" + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml" + "k8s.io/apimachinery/pkg/util/diff" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/cli-runtime/pkg/genericclioptions" + genericprinters "k8s.io/cli-runtime/pkg/printers" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/printers" +) + +/////////////////////////////////////////////////////////////// +// +// These tests do not belong in this package, and they +// should be moved (mostly to cli-runtime). seans3. +// +///////////////////////////////////////////////////////////// + +var testData = TestStruct{ + TypeMeta: metav1.TypeMeta{APIVersion: "foo/bar", Kind: "TestStruct"}, + Key: "testValue", + Map: map[string]int{"TestSubkey": 1}, + StringList: []string{"a", "b", "c"}, + IntList: []int{1, 2, 3}, +} + +type TestStruct struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Key string `json:"Key"` + Map map[string]int `json:"Map"` + StringList []string `json:"StringList"` + IntList []int `json:"IntList"` +} + +func (in *TestStruct) DeepCopyObject() runtime.Object { + panic("never called") +} + +// TODO(seans3): Move this test to tableprinter_test.go +func TestPrintUnstructuredObject(t *testing.T) { + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Test", + "dummy1": "present", + "dummy2": "present", + "metadata": map[string]interface{}{ + "name": "MyName", + "namespace": "MyNamespace", + "creationTimestamp": "2017-04-01T00:00:00Z", + "resourceVersion": 123, + "uid": "00000000-0000-0000-0000-000000000001", + "dummy3": "present", + "labels": map[string]interface{}{"test": "other"}, + }, + /*"items": []interface{}{ + map[string]interface{}{ + "itemBool": true, + "itemInt": 42, + }, + },*/ + "url": "http://localhost", + "status": "ok", + }, + } + + tests := []struct { + expected string + options printers.PrintOptions + object runtime.Object + }{ + { + expected: "NAME\\s+AGE\nMyName\\s+\\d+", + object: obj, + }, + { + options: printers.PrintOptions{ + WithNamespace: true, + }, + expected: "NAMESPACE\\s+NAME\\s+AGE\nMyNamespace\\s+MyName\\s+\\d+", + object: obj, + }, + { + options: printers.PrintOptions{ + ShowLabels: true, + WithNamespace: true, + }, + expected: "NAMESPACE\\s+NAME\\s+AGE\\s+LABELS\nMyNamespace\\s+MyName\\s+\\d+\\w+\\s+test\\=other", + object: obj, + }, + { + expected: "NAME\\s+AGE\nMyName\\s+\\d+\\w+\nMyName2\\s+\\d+", + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Test", + "dummy1": "present", + "dummy2": "present", + "items": []interface{}{ + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "MyName", + "namespace": "MyNamespace", + "creationTimestamp": "2017-04-01T00:00:00Z", + "resourceVersion": 123, + "uid": "00000000-0000-0000-0000-000000000001", + "dummy3": "present", + "labels": map[string]interface{}{"test": "other"}, + }, + }, + map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "MyName2", + "namespace": "MyNamespace", + "creationTimestamp": "2017-04-01T00:00:00Z", + "resourceVersion": 123, + "uid": "00000000-0000-0000-0000-000000000001", + "dummy3": "present", + "labels": "badlabel", + }, + }, + }, + "url": "http://localhost", + "status": "ok", + }, + }, + }, + } + out := bytes.NewBuffer([]byte{}) + + for _, test := range tests { + out.Reset() + printer := printers.NewTablePrinter(test.options) + printer.PrintObj(test.object, out) + + matches, err := regexp.MatchString(test.expected, out.String()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !matches { + t.Errorf("wanted:\n%s\ngot:\n%s", test.expected, out) + } + } +} + +type TestPrintType struct { + Data string +} + +func (obj *TestPrintType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (obj *TestPrintType) DeepCopyObject() runtime.Object { + if obj == nil { + return nil + } + clone := *obj + return &clone +} + +type TestUnknownType struct{} + +func (obj *TestUnknownType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } +func (obj *TestUnknownType) DeepCopyObject() runtime.Object { + if obj == nil { + return nil + } + clone := *obj + return &clone +} + +// TODO(seans3): Move this test to cli-runtime/pkg/printers. +func testPrinter(t *testing.T, printer printers.ResourcePrinter, unmarshalFunc func(data []byte, v interface{}) error) { + buf := bytes.NewBuffer([]byte{}) + + err := printer.PrintObj(&testData, buf) + if err != nil { + t.Fatal(err) + } + var poutput TestStruct + // Verify that given function runs without error. + err = unmarshalFunc(buf.Bytes(), &poutput) + if err != nil { + t.Fatal(err) + } + // Use real decode function to undo the versioning process. + poutput = TestStruct{} + s := yamlserializer.NewDecodingSerializer(testapi.Default.Codec()) + if err := runtime.DecodeInto(s, buf.Bytes(), &poutput); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(testData, poutput) { + t.Errorf("Test data and unmarshaled data are not equal: %v", diff.ObjectDiff(poutput, testData)) + } + + obj := &v1.Pod{ + TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"}, + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + } + // our decoder defaults, so we should default our expected object as well + legacyscheme.Scheme.Default(obj) + buf.Reset() + printer.PrintObj(obj, buf) + var objOut v1.Pod + // Verify that given function runs without error. + err = unmarshalFunc(buf.Bytes(), &objOut) + if err != nil { + t.Fatalf("unexpected error: %#v", err) + } + // Use real decode function to undo the versioning process. + objOut = v1.Pod{} + if err := runtime.DecodeInto(s, buf.Bytes(), &objOut); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(obj, &objOut) { + t.Errorf("Unexpected inequality:\n%v", diff.ObjectDiff(obj, &objOut)) + } +} + +func yamlUnmarshal(data []byte, v interface{}) error { + return yaml.Unmarshal(data, v) +} + +// TODO(seans3): Move this test to cli-runtime/pkg/printers. +func TestYAMLPrinter(t *testing.T) { + testPrinter(t, genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.YAMLPrinter{}), yamlUnmarshal) +} + +// TODO(seans3): Move this test to cli-runtime/pkg/printers. +func TestJSONPrinter(t *testing.T) { + testPrinter(t, genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.JSONPrinter{}), json.Unmarshal) +} + +// TODO(seans3): Move this test to cli-runtime/pkg/printers. +func TestFormatResourceName(t *testing.T) { + tests := []struct { + kind schema.GroupKind + name string + want string + }{ + {schema.GroupKind{}, "", ""}, + {schema.GroupKind{}, "name", "name"}, + {schema.GroupKind{Kind: "Kind"}, "", "kind/"}, // should not happen in practice + {schema.GroupKind{Kind: "Kind"}, "name", "kind/name"}, + {schema.GroupKind{Group: "group", Kind: "Kind"}, "name", "kind.group/name"}, + } + for _, tt := range tests { + if got := formatResourceName(tt.kind, tt.name, true); got != tt.want { + t.Errorf("formatResourceName(%q, %q) = %q, want %q", tt.kind, tt.name, got, tt.want) + } + } +} + +func PrintCustomType(obj *TestPrintType, options printers.GenerateOptions) ([]metav1beta1.TableRow, error) { + return []metav1beta1.TableRow{{Cells: []interface{}{obj.Data}}}, nil +} + +func ErrorPrintHandler(obj *TestPrintType, options printers.GenerateOptions) ([]metav1beta1.TableRow, error) { + return nil, fmt.Errorf("ErrorPrintHandler error") +} + +func TestCustomTypePrinting(t *testing.T) { + columns := []metav1beta1.TableColumnDefinition{{Name: "Data"}} + generator := printers.NewTableGenerator() + generator.TableHandler(columns, PrintCustomType) + + obj := TestPrintType{"test object"} + table, err := generator.GenerateTable(&obj, printers.GenerateOptions{}) + if err != nil { + t.Fatalf("An error occurred generating the table for custom type: %#v", err) + } + + printer := printers.NewTablePrinter(printers.PrintOptions{}) + buffer := &bytes.Buffer{} + err = printer.PrintObj(table, buffer) + if err != nil { + t.Fatalf("An error occurred printing the Table: %#v", err) + } + + expectedOutput := "DATA\ntest object\n" + if buffer.String() != expectedOutput { + t.Errorf("The data was not printed as expected. Expected:\n%s\nGot:\n%s", expectedOutput, buffer.String()) + } +} + +func TestPrintHandlerError(t *testing.T) { + columns := []metav1beta1.TableColumnDefinition{{Name: "Data"}} + generator := printers.NewTableGenerator() + generator.TableHandler(columns, ErrorPrintHandler) + obj := TestPrintType{"test object"} + _, err := generator.GenerateTable(&obj, printers.GenerateOptions{}) + if err == nil || err.Error() != "ErrorPrintHandler error" { + t.Errorf("Did not get the expected error: %#v", err) + } +} + +func TestUnknownTypePrinting(t *testing.T) { + printer := printers.NewTablePrinter(printers.PrintOptions{}) + buffer := &bytes.Buffer{} + err := printer.PrintObj(&TestUnknownType{}, buffer) + if err == nil { + t.Errorf("An error was expected from printing unknown type") + } +} + +func TestTemplatePanic(t *testing.T) { + tmpl := `{{and ((index .currentState.info "foo").state.running.startedAt) .currentState.info.net.state.running.startedAt}}` + printer, err := genericprinters.NewGoTemplatePrinter([]byte(tmpl)) + if err != nil { + t.Fatalf("tmpl fail: %v", err) + } + buffer := &bytes.Buffer{} + err = printer.PrintObj(&v1.Pod{}, buffer) + if err == nil { + t.Fatalf("expected that template to crash") + } + if buffer.String() == "" { + t.Errorf("no debugging info was printed") + } +} + +func TestNamePrinter(t *testing.T) { + tests := map[string]struct { + obj runtime.Object + expect string + }{ + "singleObject": { + &v1.Pod{ + TypeMeta: metav1.TypeMeta{ + Kind: "Pod", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, + "pod/foo\n"}, + "List": { + &unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", + "apiVersion": "v1", + }, + Items: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "Pod", + "apiVersion": "v1", + "metadata": map[string]interface{}{ + "name": "bar", + }, + }, + }, + }, + }, + "pod/bar\n"}, + } + + printFlags := genericclioptions.NewPrintFlags("").WithTypeSetter(legacyscheme.Scheme).WithDefaultOutput("name") + printer, err := printFlags.ToPrinter() + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + for name, item := range tests { + buff := &bytes.Buffer{} + err := printer.PrintObj(item.obj, buff) + if err != nil { + t.Errorf("%v: unexpected err: %v", name, err) + continue + } + got := buff.String() + if item.expect != got { + t.Errorf("%v: expected %v, got %v", name, item.expect, got) + } + } +} + +// TODO(seans3): Move this test to cli-runtime/pkg/printers. +func TestTemplateStrings(t *testing.T) { + // This unit tests the "exists" function as well as the template from update.sh + table := map[string]struct { + pod v1.Pod + expect string + }{ + "nilInfo": {v1.Pod{}, "false"}, + "emptyInfo": {v1.Pod{Status: v1.PodStatus{ContainerStatuses: []v1.ContainerStatus{}}}, "false"}, + "fooExists": { + v1.Pod{ + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "foo", + }, + }, + }, + }, + "false", + }, + "barExists": { + v1.Pod{ + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "bar", + }, + }, + }, + }, + "false", + }, + "bothExist": { + v1.Pod{ + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "foo", + }, + { + Name: "bar", + }, + }, + }, + }, + "false", + }, + "barValid": { + v1.Pod{ + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "foo", + }, + { + Name: "bar", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{ + StartedAt: metav1.Time{}, + }, + }, + }, + }, + }, + }, + "false", + }, + "bothValid": { + v1.Pod{ + Status: v1.PodStatus{ + ContainerStatuses: []v1.ContainerStatus{ + { + Name: "foo", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{ + StartedAt: metav1.Time{}, + }, + }, + }, + { + Name: "bar", + State: v1.ContainerState{ + Running: &v1.ContainerStateRunning{ + StartedAt: metav1.Time{}, + }, + }, + }, + }, + }, + }, + "true", + }, + } + // The point of this test is to verify that the below template works. + tmpl := `{{if (exists . "status" "containerStatuses")}}{{range .status.containerStatuses}}{{if (and (eq .name "foo") (exists . "state" "running"))}}true{{end}}{{end}}{{end}}` + printer, err := genericprinters.NewGoTemplatePrinter([]byte(tmpl)) + if err != nil { + t.Fatalf("tmpl fail: %v", err) + } + + for name, item := range table { + buffer := &bytes.Buffer{} + err = printer.PrintObj(&item.pod, buffer) + if err != nil { + t.Errorf("%v: unexpected err: %v", name, err) + continue + } + actual := buffer.String() + if len(actual) == 0 { + actual = "false" + } + if e := item.expect; e != actual { + t.Errorf("%v: expected %v, got %v", name, e, actual) + } + } +} + +// TODO(seans3): Move this test to cli-runtime/pkg/printers. +func TestPrinters(t *testing.T) { + om := func(name string) metav1.ObjectMeta { return metav1.ObjectMeta{Name: name} } + + var ( + err error + templatePrinter printers.ResourcePrinter + templatePrinter2 printers.ResourcePrinter + jsonpathPrinter printers.ResourcePrinter + ) + + templatePrinter, err = genericprinters.NewGoTemplatePrinter([]byte("{{.name}}")) + if err != nil { + t.Fatal(err) + } + + templatePrinter2, err = genericprinters.NewGoTemplatePrinter([]byte("{{len .items}}")) + if err != nil { + t.Fatal(err) + } + + jsonpathPrinter, err = genericprinters.NewJSONPathPrinter("{.metadata.name}") + if err != nil { + t.Fatal(err) + } + + genericPrinters := map[string]printers.ResourcePrinter{ + // TODO(juanvallejo): move "generic printer" tests to pkg/kubectl/genericclioptions/printers + "json": genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.JSONPrinter{}), + "yaml": genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.YAMLPrinter{}), + "template": templatePrinter, + "template2": templatePrinter2, + "jsonpath": jsonpathPrinter, + } + objects := map[string]runtime.Object{ + "pod": &v1.Pod{ObjectMeta: om("pod")}, + "emptyPodList": &v1.PodList{}, + "nonEmptyPodList": &v1.PodList{Items: []v1.Pod{{}}}, + "endpoints": &v1.Endpoints{ + Subsets: []v1.EndpointSubset{{ + Addresses: []v1.EndpointAddress{{IP: "127.0.0.1"}, {IP: "localhost"}}, + Ports: []v1.EndpointPort{{Port: 8080}}, + }}}, + } + // map of printer name to set of objects it should fail on. + expectedErrors := map[string]sets.String{ + "template2": sets.NewString("pod", "emptyPodList", "endpoints"), + "jsonpath": sets.NewString("emptyPodList", "nonEmptyPodList", "endpoints"), + } + + for pName, p := range genericPrinters { + for oName, obj := range objects { + b := &bytes.Buffer{} + if err := p.PrintObj(obj, b); err != nil { + if set, found := expectedErrors[pName]; found && set.Has(oName) { + // expected error + continue + } + t.Errorf("printer '%v', object '%v'; error: '%v'", pName, oName, err) + } + } + } +} diff --git a/pkg/printers/internalversion/printers_test.go b/pkg/printers/internalversion/printers_test.go index 3f50f8c9a0c..ab1c279ef5f 100644 --- a/pkg/printers/internalversion/printers_test.go +++ b/pkg/printers/internalversion/printers_test.go @@ -18,32 +18,20 @@ package internalversion import ( "bytes" - "encoding/json" "fmt" "reflect" - "regexp" "strconv" "strings" "testing" "time" - "sigs.k8s.io/yaml" - - "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml" "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/cli-runtime/pkg/genericclioptions" - genericprinters "k8s.io/cli-runtime/pkg/printers" - "k8s.io/kubernetes/pkg/api/legacyscheme" - "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" @@ -59,539 +47,6 @@ import ( utilpointer "k8s.io/utils/pointer" ) -var testData = TestStruct{ - TypeMeta: metav1.TypeMeta{APIVersion: "foo/bar", Kind: "TestStruct"}, - Key: "testValue", - Map: map[string]int{"TestSubkey": 1}, - StringList: []string{"a", "b", "c"}, - IntList: []int{1, 2, 3}, -} - -type TestStruct struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Key string `json:"Key"` - Map map[string]int `json:"Map"` - StringList []string `json:"StringList"` - IntList []int `json:"IntList"` -} - -func (in *TestStruct) DeepCopyObject() runtime.Object { - panic("never called") -} - -func TestPrintUnstructuredObject(t *testing.T) { - obj := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Test", - "dummy1": "present", - "dummy2": "present", - "metadata": map[string]interface{}{ - "name": "MyName", - "namespace": "MyNamespace", - "creationTimestamp": "2017-04-01T00:00:00Z", - "resourceVersion": 123, - "uid": "00000000-0000-0000-0000-000000000001", - "dummy3": "present", - "labels": map[string]interface{}{"test": "other"}, - }, - /*"items": []interface{}{ - map[string]interface{}{ - "itemBool": true, - "itemInt": 42, - }, - },*/ - "url": "http://localhost", - "status": "ok", - }, - } - - tests := []struct { - expected string - options printers.PrintOptions - object runtime.Object - }{ - { - expected: "NAME\\s+AGE\nMyName\\s+\\d+", - object: obj, - }, - { - options: printers.PrintOptions{ - WithNamespace: true, - }, - expected: "NAMESPACE\\s+NAME\\s+AGE\nMyNamespace\\s+MyName\\s+\\d+", - object: obj, - }, - { - options: printers.PrintOptions{ - ShowLabels: true, - WithNamespace: true, - }, - expected: "NAMESPACE\\s+NAME\\s+AGE\\s+LABELS\nMyNamespace\\s+MyName\\s+\\d+\\w+\\s+test\\=other", - object: obj, - }, - { - expected: "NAME\\s+AGE\nMyName\\s+\\d+\\w+\nMyName2\\s+\\d+", - object: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Test", - "dummy1": "present", - "dummy2": "present", - "items": []interface{}{ - map[string]interface{}{ - "metadata": map[string]interface{}{ - "name": "MyName", - "namespace": "MyNamespace", - "creationTimestamp": "2017-04-01T00:00:00Z", - "resourceVersion": 123, - "uid": "00000000-0000-0000-0000-000000000001", - "dummy3": "present", - "labels": map[string]interface{}{"test": "other"}, - }, - }, - map[string]interface{}{ - "metadata": map[string]interface{}{ - "name": "MyName2", - "namespace": "MyNamespace", - "creationTimestamp": "2017-04-01T00:00:00Z", - "resourceVersion": 123, - "uid": "00000000-0000-0000-0000-000000000001", - "dummy3": "present", - "labels": "badlabel", - }, - }, - }, - "url": "http://localhost", - "status": "ok", - }, - }, - }, - } - out := bytes.NewBuffer([]byte{}) - - for _, test := range tests { - out.Reset() - printer := printers.NewTablePrinter(test.options) - printer.PrintObj(test.object, out) - - matches, err := regexp.MatchString(test.expected, out.String()) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !matches { - t.Errorf("wanted:\n%s\ngot:\n%s", test.expected, out) - } - } -} - -type TestPrintType struct { - Data string -} - -func (obj *TestPrintType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } -func (obj *TestPrintType) DeepCopyObject() runtime.Object { - if obj == nil { - return nil - } - clone := *obj - return &clone -} - -type TestUnknownType struct{} - -func (obj *TestUnknownType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind } -func (obj *TestUnknownType) DeepCopyObject() runtime.Object { - if obj == nil { - return nil - } - clone := *obj - return &clone -} - -func testPrinter(t *testing.T, printer printers.ResourcePrinter, unmarshalFunc func(data []byte, v interface{}) error) { - buf := bytes.NewBuffer([]byte{}) - - err := printer.PrintObj(&testData, buf) - if err != nil { - t.Fatal(err) - } - var poutput TestStruct - // Verify that given function runs without error. - err = unmarshalFunc(buf.Bytes(), &poutput) - if err != nil { - t.Fatal(err) - } - // Use real decode function to undo the versioning process. - poutput = TestStruct{} - s := yamlserializer.NewDecodingSerializer(testapi.Default.Codec()) - if err := runtime.DecodeInto(s, buf.Bytes(), &poutput); err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(testData, poutput) { - t.Errorf("Test data and unmarshaled data are not equal: %v", diff.ObjectDiff(poutput, testData)) - } - - obj := &v1.Pod{ - TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"}, - ObjectMeta: metav1.ObjectMeta{Name: "foo"}, - } - // our decoder defaults, so we should default our expected object as well - legacyscheme.Scheme.Default(obj) - buf.Reset() - printer.PrintObj(obj, buf) - var objOut v1.Pod - // Verify that given function runs without error. - err = unmarshalFunc(buf.Bytes(), &objOut) - if err != nil { - t.Fatalf("unexpected error: %#v", err) - } - // Use real decode function to undo the versioning process. - objOut = v1.Pod{} - if err := runtime.DecodeInto(s, buf.Bytes(), &objOut); err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(obj, &objOut) { - t.Errorf("Unexpected inequality:\n%v", diff.ObjectDiff(obj, &objOut)) - } -} - -func yamlUnmarshal(data []byte, v interface{}) error { - return yaml.Unmarshal(data, v) -} - -func TestYAMLPrinter(t *testing.T) { - testPrinter(t, genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.YAMLPrinter{}), yamlUnmarshal) -} - -func TestJSONPrinter(t *testing.T) { - testPrinter(t, genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.JSONPrinter{}), json.Unmarshal) -} - -func TestFormatResourceName(t *testing.T) { - tests := []struct { - kind schema.GroupKind - name string - want string - }{ - {schema.GroupKind{}, "", ""}, - {schema.GroupKind{}, "name", "name"}, - {schema.GroupKind{Kind: "Kind"}, "", "kind/"}, // should not happen in practice - {schema.GroupKind{Kind: "Kind"}, "name", "kind/name"}, - {schema.GroupKind{Group: "group", Kind: "Kind"}, "name", "kind.group/name"}, - } - for _, tt := range tests { - if got := formatResourceName(tt.kind, tt.name, true); got != tt.want { - t.Errorf("formatResourceName(%q, %q) = %q, want %q", tt.kind, tt.name, got, tt.want) - } - } -} - -func PrintCustomType(obj *TestPrintType, options printers.GenerateOptions) ([]metav1beta1.TableRow, error) { - return []metav1beta1.TableRow{{Cells: []interface{}{obj.Data}}}, nil -} - -func ErrorPrintHandler(obj *TestPrintType, options printers.GenerateOptions) ([]metav1beta1.TableRow, error) { - return nil, fmt.Errorf("ErrorPrintHandler error") -} - -func TestCustomTypePrinting(t *testing.T) { - columns := []metav1beta1.TableColumnDefinition{{Name: "Data"}} - generator := printers.NewTableGenerator() - generator.TableHandler(columns, PrintCustomType) - - obj := TestPrintType{"test object"} - table, err := generator.GenerateTable(&obj, printers.GenerateOptions{}) - if err != nil { - t.Fatalf("An error occurred generating the table for custom type: %#v", err) - } - - printer := printers.NewTablePrinter(printers.PrintOptions{}) - buffer := &bytes.Buffer{} - err = printer.PrintObj(table, buffer) - if err != nil { - t.Fatalf("An error occurred printing the Table: %#v", err) - } - - expectedOutput := "DATA\ntest object\n" - if buffer.String() != expectedOutput { - t.Errorf("The data was not printed as expected. Expected:\n%s\nGot:\n%s", expectedOutput, buffer.String()) - } -} - -func TestPrintHandlerError(t *testing.T) { - columns := []metav1beta1.TableColumnDefinition{{Name: "Data"}} - generator := printers.NewTableGenerator() - generator.TableHandler(columns, ErrorPrintHandler) - obj := TestPrintType{"test object"} - _, err := generator.GenerateTable(&obj, printers.GenerateOptions{}) - if err == nil || err.Error() != "ErrorPrintHandler error" { - t.Errorf("Did not get the expected error: %#v", err) - } -} - -func TestUnknownTypePrinting(t *testing.T) { - printer := printers.NewTablePrinter(printers.PrintOptions{}) - buffer := &bytes.Buffer{} - err := printer.PrintObj(&TestUnknownType{}, buffer) - if err == nil { - t.Errorf("An error was expected from printing unknown type") - } -} - -func TestTemplatePanic(t *testing.T) { - tmpl := `{{and ((index .currentState.info "foo").state.running.startedAt) .currentState.info.net.state.running.startedAt}}` - printer, err := genericprinters.NewGoTemplatePrinter([]byte(tmpl)) - if err != nil { - t.Fatalf("tmpl fail: %v", err) - } - buffer := &bytes.Buffer{} - err = printer.PrintObj(&v1.Pod{}, buffer) - if err == nil { - t.Fatalf("expected that template to crash") - } - if buffer.String() == "" { - t.Errorf("no debugging info was printed") - } -} - -func TestNamePrinter(t *testing.T) { - tests := map[string]struct { - obj runtime.Object - expect string - }{ - "singleObject": { - &v1.Pod{ - TypeMeta: metav1.TypeMeta{ - Kind: "Pod", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - }, - "pod/foo\n"}, - "List": { - &unstructured.UnstructuredList{ - Object: map[string]interface{}{ - "kind": "List", - "apiVersion": "v1", - }, - Items: []unstructured.Unstructured{ - { - Object: map[string]interface{}{ - "kind": "Pod", - "apiVersion": "v1", - "metadata": map[string]interface{}{ - "name": "bar", - }, - }, - }, - }, - }, - "pod/bar\n"}, - } - - printFlags := genericclioptions.NewPrintFlags("").WithTypeSetter(legacyscheme.Scheme).WithDefaultOutput("name") - printer, err := printFlags.ToPrinter() - if err != nil { - t.Fatalf("unexpected err: %v", err) - } - - for name, item := range tests { - buff := &bytes.Buffer{} - err := printer.PrintObj(item.obj, buff) - if err != nil { - t.Errorf("%v: unexpected err: %v", name, err) - continue - } - got := buff.String() - if item.expect != got { - t.Errorf("%v: expected %v, got %v", name, item.expect, got) - } - } -} - -func TestTemplateStrings(t *testing.T) { - // This unit tests the "exists" function as well as the template from update.sh - table := map[string]struct { - pod v1.Pod - expect string - }{ - "nilInfo": {v1.Pod{}, "false"}, - "emptyInfo": {v1.Pod{Status: v1.PodStatus{ContainerStatuses: []v1.ContainerStatus{}}}, "false"}, - "fooExists": { - v1.Pod{ - Status: v1.PodStatus{ - ContainerStatuses: []v1.ContainerStatus{ - { - Name: "foo", - }, - }, - }, - }, - "false", - }, - "barExists": { - v1.Pod{ - Status: v1.PodStatus{ - ContainerStatuses: []v1.ContainerStatus{ - { - Name: "bar", - }, - }, - }, - }, - "false", - }, - "bothExist": { - v1.Pod{ - Status: v1.PodStatus{ - ContainerStatuses: []v1.ContainerStatus{ - { - Name: "foo", - }, - { - Name: "bar", - }, - }, - }, - }, - "false", - }, - "barValid": { - v1.Pod{ - Status: v1.PodStatus{ - ContainerStatuses: []v1.ContainerStatus{ - { - Name: "foo", - }, - { - Name: "bar", - State: v1.ContainerState{ - Running: &v1.ContainerStateRunning{ - StartedAt: metav1.Time{}, - }, - }, - }, - }, - }, - }, - "false", - }, - "bothValid": { - v1.Pod{ - Status: v1.PodStatus{ - ContainerStatuses: []v1.ContainerStatus{ - { - Name: "foo", - State: v1.ContainerState{ - Running: &v1.ContainerStateRunning{ - StartedAt: metav1.Time{}, - }, - }, - }, - { - Name: "bar", - State: v1.ContainerState{ - Running: &v1.ContainerStateRunning{ - StartedAt: metav1.Time{}, - }, - }, - }, - }, - }, - }, - "true", - }, - } - // The point of this test is to verify that the below template works. - tmpl := `{{if (exists . "status" "containerStatuses")}}{{range .status.containerStatuses}}{{if (and (eq .name "foo") (exists . "state" "running"))}}true{{end}}{{end}}{{end}}` - printer, err := genericprinters.NewGoTemplatePrinter([]byte(tmpl)) - if err != nil { - t.Fatalf("tmpl fail: %v", err) - } - - for name, item := range table { - buffer := &bytes.Buffer{} - err = printer.PrintObj(&item.pod, buffer) - if err != nil { - t.Errorf("%v: unexpected err: %v", name, err) - continue - } - actual := buffer.String() - if len(actual) == 0 { - actual = "false" - } - if e := item.expect; e != actual { - t.Errorf("%v: expected %v, got %v", name, e, actual) - } - } -} - -func TestPrinters(t *testing.T) { - om := func(name string) metav1.ObjectMeta { return metav1.ObjectMeta{Name: name} } - - var ( - err error - templatePrinter printers.ResourcePrinter - templatePrinter2 printers.ResourcePrinter - jsonpathPrinter printers.ResourcePrinter - ) - - templatePrinter, err = genericprinters.NewGoTemplatePrinter([]byte("{{.name}}")) - if err != nil { - t.Fatal(err) - } - - templatePrinter2, err = genericprinters.NewGoTemplatePrinter([]byte("{{len .items}}")) - if err != nil { - t.Fatal(err) - } - - jsonpathPrinter, err = genericprinters.NewJSONPathPrinter("{.metadata.name}") - if err != nil { - t.Fatal(err) - } - - genericPrinters := map[string]printers.ResourcePrinter{ - // TODO(juanvallejo): move "generic printer" tests to pkg/kubectl/genericclioptions/printers - "json": genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.JSONPrinter{}), - "yaml": genericprinters.NewTypeSetter(legacyscheme.Scheme).ToPrinter(&genericprinters.YAMLPrinter{}), - "template": templatePrinter, - "template2": templatePrinter2, - "jsonpath": jsonpathPrinter, - } - objects := map[string]runtime.Object{ - "pod": &v1.Pod{ObjectMeta: om("pod")}, - "emptyPodList": &v1.PodList{}, - "nonEmptyPodList": &v1.PodList{Items: []v1.Pod{{}}}, - "endpoints": &v1.Endpoints{ - Subsets: []v1.EndpointSubset{{ - Addresses: []v1.EndpointAddress{{IP: "127.0.0.1"}, {IP: "localhost"}}, - Ports: []v1.EndpointPort{{Port: 8080}}, - }}}, - } - // map of printer name to set of objects it should fail on. - expectedErrors := map[string]sets.String{ - "template2": sets.NewString("pod", "emptyPodList", "endpoints"), - "jsonpath": sets.NewString("emptyPodList", "nonEmptyPodList", "endpoints"), - } - - for pName, p := range genericPrinters { - for oName, obj := range objects { - b := &bytes.Buffer{} - if err := p.PrintObj(obj, b); err != nil { - if set, found := expectedErrors[pName]; found && set.Has(oName) { - // expected error - continue - } - t.Errorf("printer '%v', object '%v'; error: '%v'", pName, oName, err) - } - } - } -} - func TestPrintEventsResultSorted(t *testing.T) { obj := api.EventList{