k8s.io/kubernetes@v1.29.3/pkg/printers/internalversion/printers_test.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package internalversion
    18  
    19  import (
    20  	"fmt"
    21  	"math"
    22  	"reflect"
    23  	"runtime"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	apiv1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/api/resource"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	"k8s.io/apimachinery/pkg/util/intstr"
    33  	"k8s.io/client-go/util/certificate/csr"
    34  	"k8s.io/kubernetes/pkg/apis/admissionregistration"
    35  	"k8s.io/kubernetes/pkg/apis/apiserverinternal"
    36  	"k8s.io/kubernetes/pkg/apis/apps"
    37  	"k8s.io/kubernetes/pkg/apis/autoscaling"
    38  	"k8s.io/kubernetes/pkg/apis/batch"
    39  	"k8s.io/kubernetes/pkg/apis/certificates"
    40  	"k8s.io/kubernetes/pkg/apis/coordination"
    41  	api "k8s.io/kubernetes/pkg/apis/core"
    42  	"k8s.io/kubernetes/pkg/apis/discovery"
    43  	"k8s.io/kubernetes/pkg/apis/flowcontrol"
    44  	"k8s.io/kubernetes/pkg/apis/networking"
    45  	nodeapi "k8s.io/kubernetes/pkg/apis/node"
    46  	"k8s.io/kubernetes/pkg/apis/policy"
    47  	"k8s.io/kubernetes/pkg/apis/rbac"
    48  	"k8s.io/kubernetes/pkg/apis/scheduling"
    49  	"k8s.io/kubernetes/pkg/apis/storage"
    50  	"k8s.io/kubernetes/pkg/printers"
    51  	utilpointer "k8s.io/utils/pointer"
    52  )
    53  
    54  var containerRestartPolicyAlways = api.ContainerRestartPolicyAlways
    55  
    56  func TestFormatResourceName(t *testing.T) {
    57  	tests := []struct {
    58  		kind schema.GroupKind
    59  		name string
    60  		want string
    61  	}{
    62  		{schema.GroupKind{}, "", ""},
    63  		{schema.GroupKind{}, "name", "name"},
    64  		{schema.GroupKind{Kind: "Kind"}, "", "kind/"}, // should not happen in practice
    65  		{schema.GroupKind{Kind: "Kind"}, "name", "kind/name"},
    66  		{schema.GroupKind{Group: "group", Kind: "Kind"}, "name", "kind.group/name"},
    67  	}
    68  	for _, tt := range tests {
    69  		if got := formatResourceName(tt.kind, tt.name, true); got != tt.want {
    70  			t.Errorf("formatResourceName(%q, %q) = %q, want %q", tt.kind, tt.name, got, tt.want)
    71  		}
    72  	}
    73  }
    74  
    75  type TestPrintHandler struct {
    76  	numCalls int
    77  }
    78  
    79  func (t *TestPrintHandler) TableHandler(columnDefinitions []metav1.TableColumnDefinition, printFunc interface{}) error {
    80  	t.numCalls++
    81  	return nil
    82  }
    83  
    84  func (t *TestPrintHandler) getNumCalls() int {
    85  	return t.numCalls
    86  }
    87  
    88  func TestAllHandlers(t *testing.T) {
    89  	h := &TestPrintHandler{numCalls: 0}
    90  	AddHandlers(h)
    91  	if h.getNumCalls() == 0 {
    92  		t.Error("TableHandler not called in AddHandlers")
    93  	}
    94  }
    95  
    96  func TestPrintEvent(t *testing.T) {
    97  	tests := []struct {
    98  		event    api.Event
    99  		options  printers.GenerateOptions
   100  		expected []metav1.TableRow
   101  	}{
   102  		// Basic event; no generate options
   103  		{
   104  			event: api.Event{
   105  				Source: api.EventSource{Component: "kubelet"},
   106  				InvolvedObject: api.ObjectReference{
   107  					Kind:      "Pod",
   108  					Name:      "Pod Name",
   109  					FieldPath: "spec.containers{foo}",
   110  				},
   111  				Reason:         "Event Reason",
   112  				Message:        "Message Data",
   113  				FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)},
   114  				LastTimestamp:  metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)},
   115  				Count:          6,
   116  				Type:           api.EventTypeNormal,
   117  				ObjectMeta:     metav1.ObjectMeta{Name: "event1"},
   118  			},
   119  			options: printers.GenerateOptions{},
   120  			// Columns: Last Seen, Type, Reason, Object, Message
   121  			expected: []metav1.TableRow{{Cells: []interface{}{"2d", "Normal", "Event Reason", "pod/Pod Name", "Message Data"}}},
   122  		},
   123  		// Basic event; generate options=Wide
   124  		{
   125  			event: api.Event{
   126  				Source: api.EventSource{
   127  					Component: "kubelet",
   128  					Host:      "Node1",
   129  				},
   130  				InvolvedObject: api.ObjectReference{
   131  					Kind:      "Deployment",
   132  					Name:      "Deployment Name",
   133  					FieldPath: "spec.containers{foo}",
   134  				},
   135  				Reason:         "Event Reason",
   136  				Message:        "Message Data",
   137  				FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)},
   138  				LastTimestamp:  metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)},
   139  				Count:          6,
   140  				Type:           api.EventTypeWarning,
   141  				ObjectMeta:     metav1.ObjectMeta{Name: "event2"},
   142  			},
   143  			options: printers.GenerateOptions{Wide: true},
   144  			// Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name
   145  			expected: []metav1.TableRow{{Cells: []interface{}{"2d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(6), "event2"}}},
   146  		},
   147  		// Basic event, w/o FirstTimestamp set
   148  		{
   149  			event: api.Event{
   150  				Source: api.EventSource{
   151  					Component: "kubelet",
   152  					Host:      "Node1",
   153  				},
   154  				InvolvedObject: api.ObjectReference{
   155  					Kind:      "Deployment",
   156  					Name:      "Deployment Name",
   157  					FieldPath: "spec.containers{foo}",
   158  				},
   159  				Reason:        "Event Reason",
   160  				Message:       "Message Data",
   161  				EventTime:     metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)},
   162  				LastTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)},
   163  				Count:         1,
   164  				Type:          api.EventTypeWarning,
   165  				ObjectMeta:    metav1.ObjectMeta{Name: "event3"},
   166  			},
   167  			options: printers.GenerateOptions{Wide: true},
   168  			// Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name
   169  			expected: []metav1.TableRow{{Cells: []interface{}{"3d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(1), "event3"}}},
   170  		},
   171  		// Basic event, w/o LastTimestamp set
   172  		{
   173  			event: api.Event{
   174  				Source: api.EventSource{
   175  					Component: "kubelet",
   176  					Host:      "Node1",
   177  				},
   178  				InvolvedObject: api.ObjectReference{
   179  					Kind:      "Deployment",
   180  					Name:      "Deployment Name",
   181  					FieldPath: "spec.containers{foo}",
   182  				},
   183  				Reason:         "Event Reason",
   184  				Message:        "Message Data",
   185  				EventTime:      metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)},
   186  				FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)},
   187  				Count:          1,
   188  				Type:           api.EventTypeWarning,
   189  				ObjectMeta:     metav1.ObjectMeta{Name: "event4"},
   190  			},
   191  			options: printers.GenerateOptions{Wide: true},
   192  			// Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name
   193  			expected: []metav1.TableRow{{Cells: []interface{}{"3d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(1), "event4"}}},
   194  		},
   195  		// Basic event, w/o FirstTimestamp and LastTimestamp set
   196  		{
   197  			event: api.Event{
   198  				Source: api.EventSource{
   199  					Component: "kubelet",
   200  					Host:      "Node1",
   201  				},
   202  				InvolvedObject: api.ObjectReference{
   203  					Kind:      "Deployment",
   204  					Name:      "Deployment Name",
   205  					FieldPath: "spec.containers{foo}",
   206  				},
   207  				Reason:     "Event Reason",
   208  				Message:    "Message Data",
   209  				EventTime:  metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)},
   210  				Count:      1,
   211  				Type:       api.EventTypeWarning,
   212  				ObjectMeta: metav1.ObjectMeta{Name: "event5"},
   213  			},
   214  			options: printers.GenerateOptions{Wide: true},
   215  			// Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name
   216  			expected: []metav1.TableRow{{Cells: []interface{}{"3d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(1), "event5"}}},
   217  		},
   218  		// Basic event serie, w/o FirstTimestamp, LastTimestamp and Count set
   219  		{
   220  			event: api.Event{
   221  				Source: api.EventSource{
   222  					Component: "kubelet",
   223  					Host:      "Node1",
   224  				},
   225  				InvolvedObject: api.ObjectReference{
   226  					Kind:      "Deployment",
   227  					Name:      "Deployment Name",
   228  					FieldPath: "spec.containers{foo}",
   229  				},
   230  				Series: &api.EventSeries{
   231  					Count:            2,
   232  					LastObservedTime: metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -2)},
   233  				},
   234  				Reason:     "Event Reason",
   235  				Message:    "Message Data",
   236  				EventTime:  metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)},
   237  				Type:       api.EventTypeWarning,
   238  				ObjectMeta: metav1.ObjectMeta{Name: "event6"},
   239  			},
   240  			options: printers.GenerateOptions{Wide: true},
   241  			// Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name
   242  			expected: []metav1.TableRow{{Cells: []interface{}{"2d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(2), "event6"}}},
   243  		},
   244  		// Singleton event, w/o FirstTimestamp, LastTimestamp and Count set
   245  		{
   246  			event: api.Event{
   247  				Source: api.EventSource{
   248  					Component: "kubelet",
   249  					Host:      "Node1",
   250  				},
   251  				InvolvedObject: api.ObjectReference{
   252  					Kind:      "Deployment",
   253  					Name:      "Deployment Name",
   254  					FieldPath: "spec.containers{foo}",
   255  				},
   256  				Reason:     "Event Reason",
   257  				Message:    "Message Data",
   258  				EventTime:  metav1.MicroTime{Time: time.Now().UTC().AddDate(0, 0, -3)},
   259  				Type:       api.EventTypeWarning,
   260  				ObjectMeta: metav1.ObjectMeta{Name: "event7"},
   261  			},
   262  			options: printers.GenerateOptions{Wide: true},
   263  			// Columns: Last Seen, Type, Reason, Object, Subobject, Message, First Seen, Count, Name
   264  			expected: []metav1.TableRow{{Cells: []interface{}{"3d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, Node1", "Message Data", "3d", int64(1), "event7"}}},
   265  		},
   266  		// Basic event, with empty Source; generate options=Wide
   267  		{
   268  			event: api.Event{
   269  				ReportingController: "kubelet",
   270  				ReportingInstance:   "test",
   271  				InvolvedObject: api.ObjectReference{
   272  					Kind:      "Deployment",
   273  					Name:      "Deployment Name",
   274  					FieldPath: "spec.containers{foo}",
   275  				},
   276  				Reason:         "Event Reason",
   277  				Message:        "Message Data",
   278  				FirstTimestamp: metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -3)},
   279  				LastTimestamp:  metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)},
   280  				Count:          6,
   281  				Type:           api.EventTypeWarning,
   282  				ObjectMeta:     metav1.ObjectMeta{Name: "event2"},
   283  			},
   284  			options: printers.GenerateOptions{Wide: true},
   285  			// Columns: Last Seen, Type, Reason, Object, Subobject, Source, Message, First Seen, Count, Name
   286  			expected: []metav1.TableRow{{Cells: []interface{}{"2d", "Warning", "Event Reason", "deployment/Deployment Name", "spec.containers{foo}", "kubelet, test", "Message Data", "3d", int64(6), "event2"}}},
   287  		},
   288  	}
   289  
   290  	for i, test := range tests {
   291  		rows, err := printEvent(&test.event, test.options)
   292  		if err != nil {
   293  			t.Fatal(err)
   294  		}
   295  		for i := range rows {
   296  			rows[i].Object.Object = nil
   297  		}
   298  		if !reflect.DeepEqual(test.expected, rows) {
   299  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
   300  		}
   301  	}
   302  }
   303  
   304  func TestPrintEventsResultSorted(t *testing.T) {
   305  
   306  	eventList := api.EventList{
   307  		Items: []api.Event{
   308  			{
   309  				Source:         api.EventSource{Component: "kubelet"},
   310  				Message:        "Item 1",
   311  				FirstTimestamp: metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
   312  				LastTimestamp:  metav1.NewTime(time.Date(2014, time.January, 15, 0, 0, 0, 0, time.UTC)),
   313  				Count:          1,
   314  				Type:           api.EventTypeNormal,
   315  			},
   316  			{
   317  				Source:         api.EventSource{Component: "scheduler"},
   318  				Message:        "Item 2",
   319  				FirstTimestamp: metav1.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)),
   320  				LastTimestamp:  metav1.NewTime(time.Date(1987, time.June, 17, 0, 0, 0, 0, time.UTC)),
   321  				Count:          1,
   322  				Type:           api.EventTypeNormal,
   323  			},
   324  			{
   325  				Source:         api.EventSource{Component: "kubelet"},
   326  				Message:        "Item 3",
   327  				FirstTimestamp: metav1.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)),
   328  				LastTimestamp:  metav1.NewTime(time.Date(2002, time.December, 25, 0, 0, 0, 0, time.UTC)),
   329  				Count:          1,
   330  				Type:           api.EventTypeNormal,
   331  			},
   332  		},
   333  	}
   334  
   335  	rows, err := printEventList(&eventList, printers.GenerateOptions{})
   336  	if err != nil {
   337  		t.Fatal(err)
   338  	}
   339  	if len(rows) != 3 {
   340  		t.Errorf("Generate Event List: Wrong number of table rows returned. Expected 3, got (%d)", len(rows))
   341  	}
   342  	// Verify the watch event dates are in order.
   343  	firstRow := rows[0]
   344  	message1 := firstRow.Cells[4]
   345  	if message1.(string) != "Item 1" {
   346  		t.Errorf("Wrong event ordering: expecting (Item 1), got (%s)", message1)
   347  	}
   348  	secondRow := rows[1]
   349  	message2 := secondRow.Cells[4]
   350  	if message2 != "Item 2" {
   351  		t.Errorf("Wrong event ordering: expecting (Item 2), got (%s)", message2)
   352  	}
   353  	thirdRow := rows[2]
   354  	message3 := thirdRow.Cells[4]
   355  	if message3 != "Item 3" {
   356  		t.Errorf("Wrong event ordering: expecting (Item 3), got (%s)", message3)
   357  	}
   358  }
   359  
   360  func TestPrintNamespace(t *testing.T) {
   361  	tests := []struct {
   362  		namespace api.Namespace
   363  		expected  []metav1.TableRow
   364  	}{
   365  		// Basic namespace with status and age.
   366  		{
   367  			namespace: api.Namespace{
   368  				ObjectMeta: metav1.ObjectMeta{
   369  					Name:              "namespace1",
   370  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
   371  				},
   372  				Status: api.NamespaceStatus{
   373  					Phase: "FooStatus",
   374  				},
   375  			},
   376  			// Columns: Name, Status, Age
   377  			expected: []metav1.TableRow{{Cells: []interface{}{"namespace1", "FooStatus", "0s"}}},
   378  		},
   379  		// Basic namespace without status or age.
   380  		{
   381  			namespace: api.Namespace{
   382  				ObjectMeta: metav1.ObjectMeta{
   383  					Name: "namespace2",
   384  				},
   385  			},
   386  			// Columns: Name, Status, Age
   387  			expected: []metav1.TableRow{{Cells: []interface{}{"namespace2", "", "<unknown>"}}},
   388  		},
   389  	}
   390  
   391  	for i, test := range tests {
   392  		rows, err := printNamespace(&test.namespace, printers.GenerateOptions{})
   393  		if err != nil {
   394  			t.Fatal(err)
   395  		}
   396  		for i := range rows {
   397  			rows[i].Object.Object = nil
   398  		}
   399  		if !reflect.DeepEqual(test.expected, rows) {
   400  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
   401  		}
   402  	}
   403  }
   404  
   405  func TestPrintSecret(t *testing.T) {
   406  	tests := []struct {
   407  		secret   api.Secret
   408  		expected []metav1.TableRow
   409  	}{
   410  		// Basic namespace with type, data, and age.
   411  		{
   412  			secret: api.Secret{
   413  				ObjectMeta: metav1.ObjectMeta{
   414  					Name:              "secret1",
   415  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
   416  				},
   417  				Type: "kubernetes.io/service-account-token",
   418  				Data: map[string][]byte{
   419  					"token": []byte("secret data"),
   420  				},
   421  			},
   422  			// Columns: Name, Type, Data, Age
   423  			expected: []metav1.TableRow{{Cells: []interface{}{"secret1", "kubernetes.io/service-account-token", int64(1), "0s"}}},
   424  		},
   425  		// Basic namespace with type and age; no data.
   426  		{
   427  			secret: api.Secret{
   428  				ObjectMeta: metav1.ObjectMeta{
   429  					Name:              "secret1",
   430  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
   431  				},
   432  				Type: "kubernetes.io/service-account-token",
   433  			},
   434  			// Columns: Name, Type, Data, Age
   435  			expected: []metav1.TableRow{{Cells: []interface{}{"secret1", "kubernetes.io/service-account-token", int64(0), "0s"}}},
   436  		},
   437  	}
   438  
   439  	for i, test := range tests {
   440  		rows, err := printSecret(&test.secret, printers.GenerateOptions{})
   441  		if err != nil {
   442  			t.Fatal(err)
   443  		}
   444  		for i := range rows {
   445  			rows[i].Object.Object = nil
   446  		}
   447  		if !reflect.DeepEqual(test.expected, rows) {
   448  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
   449  		}
   450  	}
   451  }
   452  
   453  func TestPrintServiceAccount(t *testing.T) {
   454  	tests := []struct {
   455  		serviceAccount api.ServiceAccount
   456  		expected       []metav1.TableRow
   457  	}{
   458  		// Basic service account without secrets
   459  		{
   460  			serviceAccount: api.ServiceAccount{
   461  				ObjectMeta: metav1.ObjectMeta{
   462  					Name:              "sa1",
   463  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
   464  				},
   465  				Secrets: []api.ObjectReference{},
   466  			},
   467  			// Columns: Name, (Num) Secrets, Age
   468  			expected: []metav1.TableRow{{Cells: []interface{}{"sa1", int64(0), "0s"}}},
   469  		},
   470  		// Basic service account with two secrets.
   471  		{
   472  			serviceAccount: api.ServiceAccount{
   473  				ObjectMeta: metav1.ObjectMeta{
   474  					Name:              "sa1",
   475  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
   476  				},
   477  				Secrets: []api.ObjectReference{
   478  					{Name: "Secret1"},
   479  					{Name: "Secret2"},
   480  				},
   481  			},
   482  			// Columns: Name, (Num) Secrets, Age
   483  			expected: []metav1.TableRow{{Cells: []interface{}{"sa1", int64(2), "0s"}}},
   484  		},
   485  	}
   486  
   487  	for i, test := range tests {
   488  		rows, err := printServiceAccount(&test.serviceAccount, printers.GenerateOptions{})
   489  		if err != nil {
   490  			t.Fatal(err)
   491  		}
   492  		for i := range rows {
   493  			rows[i].Object.Object = nil
   494  		}
   495  		if !reflect.DeepEqual(test.expected, rows) {
   496  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
   497  		}
   498  	}
   499  }
   500  
   501  func TestPrintNodeStatus(t *testing.T) {
   502  
   503  	table := []struct {
   504  		node     api.Node
   505  		expected []metav1.TableRow
   506  	}{
   507  		{
   508  			node: api.Node{
   509  				ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
   510  				Status:     api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionTrue}}},
   511  			},
   512  			// Columns: Name, Status, Roles, Age, KubeletVersion
   513  			expected: []metav1.TableRow{{Cells: []interface{}{"foo1", "Ready", "<none>", "<unknown>", ""}}},
   514  		},
   515  		{
   516  			node: api.Node{
   517  				ObjectMeta: metav1.ObjectMeta{Name: "foo2"},
   518  				Spec:       api.NodeSpec{Unschedulable: true},
   519  				Status:     api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionTrue}}},
   520  			},
   521  			// Columns: Name, Status, Roles, Age, KubeletVersion
   522  			expected: []metav1.TableRow{{Cells: []interface{}{"foo2", "Ready,SchedulingDisabled", "<none>", "<unknown>", ""}}},
   523  		},
   524  		{
   525  			node: api.Node{
   526  				ObjectMeta: metav1.ObjectMeta{Name: "foo3"},
   527  				Status: api.NodeStatus{Conditions: []api.NodeCondition{
   528  					{Type: api.NodeReady, Status: api.ConditionTrue},
   529  					{Type: api.NodeReady, Status: api.ConditionTrue}}},
   530  			},
   531  			// Columns: Name, Status, Roles, Age, KubeletVersion
   532  			expected: []metav1.TableRow{{Cells: []interface{}{"foo3", "Ready", "<none>", "<unknown>", ""}}},
   533  		},
   534  		{
   535  			node: api.Node{
   536  				ObjectMeta: metav1.ObjectMeta{Name: "foo4"},
   537  				Status:     api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionFalse}}},
   538  			},
   539  			// Columns: Name, Status, Roles, Age, KubeletVersion
   540  			expected: []metav1.TableRow{{Cells: []interface{}{"foo4", "NotReady", "<none>", "<unknown>", ""}}},
   541  		},
   542  		{
   543  			node: api.Node{
   544  				ObjectMeta: metav1.ObjectMeta{Name: "foo5"},
   545  				Spec:       api.NodeSpec{Unschedulable: true},
   546  				Status:     api.NodeStatus{Conditions: []api.NodeCondition{{Type: api.NodeReady, Status: api.ConditionFalse}}},
   547  			},
   548  			// Columns: Name, Status, Roles, Age, KubeletVersion
   549  			expected: []metav1.TableRow{{Cells: []interface{}{"foo5", "NotReady,SchedulingDisabled", "<none>", "<unknown>", ""}}},
   550  		},
   551  		{
   552  			node: api.Node{
   553  				ObjectMeta: metav1.ObjectMeta{Name: "foo6"},
   554  				Status:     api.NodeStatus{Conditions: []api.NodeCondition{{Type: "InvalidValue", Status: api.ConditionTrue}}},
   555  			},
   556  			// Columns: Name, Status, Roles, Age, KubeletVersion
   557  			expected: []metav1.TableRow{{Cells: []interface{}{"foo6", "Unknown", "<none>", "<unknown>", ""}}},
   558  		},
   559  		{
   560  			node: api.Node{
   561  				ObjectMeta: metav1.ObjectMeta{Name: "foo7"},
   562  				Status:     api.NodeStatus{Conditions: []api.NodeCondition{{}}},
   563  			},
   564  			// Columns: Name, Status, Roles, Age, KubeletVersion
   565  			expected: []metav1.TableRow{{Cells: []interface{}{"foo7", "Unknown", "<none>", "<unknown>", ""}}},
   566  		},
   567  		{
   568  			node: api.Node{
   569  				ObjectMeta: metav1.ObjectMeta{Name: "foo8"},
   570  				Spec:       api.NodeSpec{Unschedulable: true},
   571  				Status:     api.NodeStatus{Conditions: []api.NodeCondition{{Type: "InvalidValue", Status: api.ConditionTrue}}},
   572  			},
   573  			// Columns: Name, Status, Roles, Age, KubeletVersion
   574  			expected: []metav1.TableRow{{Cells: []interface{}{"foo8", "Unknown,SchedulingDisabled", "<none>", "<unknown>", ""}}},
   575  		},
   576  		{
   577  			node: api.Node{
   578  				ObjectMeta: metav1.ObjectMeta{Name: "foo9"},
   579  				Spec:       api.NodeSpec{Unschedulable: true},
   580  				Status:     api.NodeStatus{Conditions: []api.NodeCondition{{}}},
   581  			},
   582  			// Columns: Name, Status, Roles, Age, KubeletVersion
   583  			expected: []metav1.TableRow{{Cells: []interface{}{"foo9", "Unknown,SchedulingDisabled", "<none>", "<unknown>", ""}}},
   584  		},
   585  	}
   586  
   587  	for i, test := range table {
   588  		rows, err := printNode(&test.node, printers.GenerateOptions{})
   589  		if err != nil {
   590  			t.Fatalf("Error generating table rows for Node: %#v", err)
   591  		}
   592  		for i := range rows {
   593  			rows[i].Object.Object = nil
   594  		}
   595  		if !reflect.DeepEqual(test.expected, rows) {
   596  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
   597  		}
   598  	}
   599  }
   600  
   601  func TestPrintNodeRole(t *testing.T) {
   602  
   603  	table := []struct {
   604  		node     api.Node
   605  		expected []metav1.TableRow
   606  	}{
   607  		{
   608  			node: api.Node{
   609  				ObjectMeta: metav1.ObjectMeta{Name: "foo9"},
   610  			},
   611  			// Columns: Name, Status, Roles, Age, KubeletVersion
   612  			expected: []metav1.TableRow{{Cells: []interface{}{"foo9", "Unknown", "<none>", "<unknown>", ""}}},
   613  		},
   614  		{
   615  			node: api.Node{
   616  				ObjectMeta: metav1.ObjectMeta{
   617  					Name:   "foo10",
   618  					Labels: map[string]string{"node-role.kubernetes.io/master": "", "node-role.kubernetes.io/control-plane": "", "node-role.kubernetes.io/proxy": "", "kubernetes.io/role": "node"},
   619  				},
   620  			},
   621  			// Columns: Name, Status, Roles, Age, KubeletVersion
   622  			expected: []metav1.TableRow{{Cells: []interface{}{"foo10", "Unknown", "control-plane,master,node,proxy", "<unknown>", ""}}},
   623  		},
   624  		{
   625  			node: api.Node{
   626  				ObjectMeta: metav1.ObjectMeta{
   627  					Name:   "foo11",
   628  					Labels: map[string]string{"kubernetes.io/role": "node"},
   629  				},
   630  			},
   631  			// Columns: Name, Status, Roles, Age, KubeletVersion
   632  			expected: []metav1.TableRow{{Cells: []interface{}{"foo11", "Unknown", "node", "<unknown>", ""}}},
   633  		},
   634  	}
   635  
   636  	for i, test := range table {
   637  		rows, err := printNode(&test.node, printers.GenerateOptions{})
   638  		if err != nil {
   639  			t.Fatalf("An error occurred generating table rows for Node: %#v", err)
   640  		}
   641  		for i := range rows {
   642  			rows[i].Object.Object = nil
   643  		}
   644  		if !reflect.DeepEqual(test.expected, rows) {
   645  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
   646  		}
   647  	}
   648  }
   649  
   650  func TestPrintNodeOSImage(t *testing.T) {
   651  
   652  	table := []struct {
   653  		node     api.Node
   654  		expected []metav1.TableRow
   655  	}{
   656  		{
   657  			node: api.Node{
   658  				ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
   659  				Status: api.NodeStatus{
   660  					NodeInfo:  api.NodeSystemInfo{OSImage: "fake-os-image"},
   661  					Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}},
   662  				},
   663  			},
   664  			// Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion
   665  			expected: []metav1.TableRow{
   666  				{
   667  					Cells: []interface{}{"foo1", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "fake-os-image", "<unknown>", "<unknown>"},
   668  				},
   669  			},
   670  		},
   671  		{
   672  			node: api.Node{
   673  				ObjectMeta: metav1.ObjectMeta{Name: "foo2"},
   674  				Status: api.NodeStatus{
   675  					NodeInfo:  api.NodeSystemInfo{KernelVersion: "fake-kernel-version"},
   676  					Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}},
   677  				},
   678  			},
   679  			// Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion
   680  			expected: []metav1.TableRow{
   681  				{
   682  					Cells: []interface{}{"foo2", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "<unknown>", "fake-kernel-version", "<unknown>"},
   683  				},
   684  			},
   685  		},
   686  	}
   687  
   688  	for i, test := range table {
   689  		rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true})
   690  		if err != nil {
   691  			t.Fatalf("An error occurred generating table for Node: %#v", err)
   692  		}
   693  		for i := range rows {
   694  			rows[i].Object.Object = nil
   695  		}
   696  		if !reflect.DeepEqual(test.expected, rows) {
   697  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
   698  		}
   699  	}
   700  }
   701  
   702  func TestPrintNodeKernelVersion(t *testing.T) {
   703  
   704  	table := []struct {
   705  		node     api.Node
   706  		expected []metav1.TableRow
   707  	}{
   708  		{
   709  			node: api.Node{
   710  				ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
   711  				Status: api.NodeStatus{
   712  					NodeInfo:  api.NodeSystemInfo{KernelVersion: "fake-kernel-version"},
   713  					Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}},
   714  				},
   715  			},
   716  			// Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion
   717  			expected: []metav1.TableRow{
   718  				{
   719  					Cells: []interface{}{"foo1", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "<unknown>", "fake-kernel-version", "<unknown>"},
   720  				},
   721  			},
   722  		},
   723  		{
   724  			node: api.Node{
   725  				ObjectMeta: metav1.ObjectMeta{Name: "foo2"},
   726  				Status: api.NodeStatus{
   727  					NodeInfo:  api.NodeSystemInfo{OSImage: "fake-os-image"},
   728  					Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}},
   729  				},
   730  			},
   731  			// Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion
   732  			expected: []metav1.TableRow{
   733  				{
   734  					Cells: []interface{}{"foo2", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "fake-os-image", "<unknown>", "<unknown>"},
   735  				},
   736  			},
   737  		},
   738  	}
   739  
   740  	for i, test := range table {
   741  		rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true})
   742  		if err != nil {
   743  			t.Fatalf("An error occurred generating table rows Node: %#v", err)
   744  		}
   745  		for i := range rows {
   746  			rows[i].Object.Object = nil
   747  		}
   748  		if !reflect.DeepEqual(test.expected, rows) {
   749  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
   750  		}
   751  	}
   752  }
   753  
   754  func TestPrintNodeContainerRuntimeVersion(t *testing.T) {
   755  
   756  	table := []struct {
   757  		node     api.Node
   758  		expected []metav1.TableRow
   759  	}{
   760  		{
   761  			node: api.Node{
   762  				ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
   763  				Status: api.NodeStatus{
   764  					NodeInfo:  api.NodeSystemInfo{ContainerRuntimeVersion: "foo://1.2.3"},
   765  					Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}},
   766  				},
   767  			},
   768  			// Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion
   769  			expected: []metav1.TableRow{
   770  				{
   771  					Cells: []interface{}{"foo1", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "<unknown>", "<unknown>", "foo://1.2.3"},
   772  				},
   773  			},
   774  		},
   775  		{
   776  			node: api.Node{
   777  				ObjectMeta: metav1.ObjectMeta{Name: "foo2"},
   778  				Status: api.NodeStatus{
   779  					NodeInfo:  api.NodeSystemInfo{},
   780  					Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}},
   781  				},
   782  			},
   783  			// Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion
   784  			expected: []metav1.TableRow{
   785  				{
   786  					Cells: []interface{}{"foo2", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "<unknown>", "<unknown>", "<unknown>"},
   787  				},
   788  			},
   789  		},
   790  	}
   791  
   792  	for i, test := range table {
   793  		rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true})
   794  		if err != nil {
   795  			t.Fatalf("An error occurred generating table rows Node: %#v", err)
   796  		}
   797  		for i := range rows {
   798  			rows[i].Object.Object = nil
   799  		}
   800  		if !reflect.DeepEqual(test.expected, rows) {
   801  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
   802  		}
   803  	}
   804  }
   805  
   806  func TestPrintNodeName(t *testing.T) {
   807  
   808  	table := []struct {
   809  		node     api.Node
   810  		expected []metav1.TableRow
   811  	}{
   812  		{
   813  			node: api.Node{
   814  				ObjectMeta: metav1.ObjectMeta{Name: "127.0.0.1"},
   815  				Status:     api.NodeStatus{},
   816  			},
   817  			// Columns: Name, Status, Roles, Age, KubeletVersion
   818  			expected: []metav1.TableRow{{Cells: []interface{}{"127.0.0.1", "Unknown", "<none>", "<unknown>", ""}}}},
   819  		{
   820  			node: api.Node{
   821  				ObjectMeta: metav1.ObjectMeta{Name: ""},
   822  				Status:     api.NodeStatus{},
   823  			},
   824  			// Columns: Name, Status, Roles, Age, KubeletVersion
   825  			expected: []metav1.TableRow{{Cells: []interface{}{"", "Unknown", "<none>", "<unknown>", ""}}},
   826  		},
   827  	}
   828  
   829  	for i, test := range table {
   830  		rows, err := printNode(&test.node, printers.GenerateOptions{})
   831  		if err != nil {
   832  			t.Fatalf("An error occurred generating table rows Node: %#v", err)
   833  		}
   834  		for i := range rows {
   835  			rows[i].Object.Object = nil
   836  		}
   837  		if !reflect.DeepEqual(test.expected, rows) {
   838  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
   839  		}
   840  	}
   841  }
   842  
   843  func TestPrintNodeExternalIP(t *testing.T) {
   844  
   845  	table := []struct {
   846  		node     api.Node
   847  		expected []metav1.TableRow
   848  	}{
   849  		{
   850  			node: api.Node{
   851  				ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
   852  				Status:     api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}},
   853  			},
   854  			// Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion
   855  			expected: []metav1.TableRow{
   856  				{
   857  					Cells: []interface{}{"foo1", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "<unknown>", "<unknown>", "<unknown>"},
   858  				},
   859  			},
   860  		},
   861  		{
   862  			node: api.Node{
   863  				ObjectMeta: metav1.ObjectMeta{Name: "foo2"},
   864  				Status:     api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeInternalIP, Address: "1.1.1.1"}}},
   865  			},
   866  			// Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion
   867  			expected: []metav1.TableRow{
   868  				{
   869  					Cells: []interface{}{"foo2", "Unknown", "<none>", "<unknown>", "", "1.1.1.1", "<none>", "<unknown>", "<unknown>", "<unknown>"},
   870  				},
   871  			},
   872  		},
   873  		{
   874  			node: api.Node{
   875  				ObjectMeta: metav1.ObjectMeta{Name: "foo3"},
   876  				Status: api.NodeStatus{Addresses: []api.NodeAddress{
   877  					{Type: api.NodeExternalIP, Address: "2.2.2.2"},
   878  					{Type: api.NodeInternalIP, Address: "3.3.3.3"},
   879  					{Type: api.NodeExternalIP, Address: "4.4.4.4"},
   880  				}},
   881  			},
   882  			expected: []metav1.TableRow{
   883  				{
   884  					Cells: []interface{}{"foo3", "Unknown", "<none>", "<unknown>", "", "3.3.3.3", "2.2.2.2", "<unknown>", "<unknown>", "<unknown>"},
   885  				},
   886  			},
   887  		},
   888  	}
   889  
   890  	for i, test := range table {
   891  		rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true})
   892  		if err != nil {
   893  			t.Fatalf("An error occurred generating table rows Node: %#v", err)
   894  		}
   895  		for i := range rows {
   896  			rows[i].Object.Object = nil
   897  		}
   898  		if !reflect.DeepEqual(test.expected, rows) {
   899  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
   900  		}
   901  	}
   902  }
   903  
   904  func TestPrintNodeInternalIP(t *testing.T) {
   905  
   906  	table := []struct {
   907  		node     api.Node
   908  		expected []metav1.TableRow
   909  	}{
   910  		{
   911  			node: api.Node{
   912  				ObjectMeta: metav1.ObjectMeta{Name: "foo1"},
   913  				Status:     api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeInternalIP, Address: "1.1.1.1"}}},
   914  			},
   915  			// Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion
   916  			expected: []metav1.TableRow{
   917  				{
   918  					Cells: []interface{}{"foo1", "Unknown", "<none>", "<unknown>", "", "1.1.1.1", "<none>", "<unknown>", "<unknown>", "<unknown>"},
   919  				},
   920  			},
   921  		},
   922  		{
   923  			node: api.Node{
   924  				ObjectMeta: metav1.ObjectMeta{Name: "foo2"},
   925  				Status:     api.NodeStatus{Addresses: []api.NodeAddress{{Type: api.NodeExternalIP, Address: "1.1.1.1"}}},
   926  			},
   927  			// Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion
   928  			expected: []metav1.TableRow{
   929  				{
   930  					Cells: []interface{}{"foo2", "Unknown", "<none>", "<unknown>", "", "<none>", "1.1.1.1", "<unknown>", "<unknown>", "<unknown>"},
   931  				},
   932  			},
   933  		},
   934  		{
   935  			node: api.Node{
   936  				ObjectMeta: metav1.ObjectMeta{Name: "foo3"},
   937  				Status: api.NodeStatus{Addresses: []api.NodeAddress{
   938  					{Type: api.NodeInternalIP, Address: "2.2.2.2"},
   939  					{Type: api.NodeExternalIP, Address: "3.3.3.3"},
   940  					{Type: api.NodeInternalIP, Address: "4.4.4.4"},
   941  				}},
   942  			},
   943  			// Columns: Name, Status, Roles, Age, KubeletVersion, NodeInternalIP, NodeExternalIP, OSImage, KernelVersion, ContainerRuntimeVersion
   944  			expected: []metav1.TableRow{
   945  				{
   946  					Cells: []interface{}{"foo3", "Unknown", "<none>", "<unknown>", "", "2.2.2.2", "3.3.3.3", "<unknown>", "<unknown>", "<unknown>"},
   947  				},
   948  			},
   949  		},
   950  	}
   951  
   952  	for i, test := range table {
   953  		rows, err := printNode(&test.node, printers.GenerateOptions{Wide: true})
   954  		if err != nil {
   955  			t.Fatalf("An error occurred generating table rows Node: %#v", err)
   956  		}
   957  		for i := range rows {
   958  			rows[i].Object.Object = nil
   959  		}
   960  		if !reflect.DeepEqual(test.expected, rows) {
   961  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
   962  		}
   963  	}
   964  }
   965  
   966  func TestPrintIngress(t *testing.T) {
   967  	ingress := networking.Ingress{
   968  		ObjectMeta: metav1.ObjectMeta{
   969  			Name:              "test1",
   970  			CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
   971  		},
   972  		Spec: networking.IngressSpec{
   973  			IngressClassName: utilpointer.StringPtr("foo"),
   974  			DefaultBackend: &networking.IngressBackend{
   975  				Service: &networking.IngressServiceBackend{
   976  					Name: "default-backend",
   977  					Port: networking.ServiceBackendPort{
   978  						Name:   "default-backend",
   979  						Number: 80,
   980  					},
   981  				},
   982  			},
   983  		},
   984  		Status: networking.IngressStatus{
   985  			LoadBalancer: networking.IngressLoadBalancerStatus{
   986  				Ingress: []networking.IngressLoadBalancerIngress{
   987  					{
   988  						IP:       "2.3.4.5",
   989  						Hostname: "localhost.localdomain",
   990  					},
   991  				},
   992  			},
   993  		},
   994  	}
   995  	// Columns: Name, Hosts, Address, Ports, Age
   996  	expected := []metav1.TableRow{{Cells: []interface{}{"test1", "foo", "*", "2.3.4.5", "80", "10y"}}}
   997  
   998  	rows, err := printIngress(&ingress, printers.GenerateOptions{})
   999  	if err != nil {
  1000  		t.Fatalf("Error generating table rows for Ingress: %#v", err)
  1001  	}
  1002  	rows[0].Object.Object = nil
  1003  	if !reflect.DeepEqual(expected, rows) {
  1004  		t.Errorf("mismatch: %s", cmp.Diff(expected, rows))
  1005  	}
  1006  }
  1007  
  1008  func TestPrintIngressClass(t *testing.T) {
  1009  	testCases := []struct {
  1010  		name         string
  1011  		ingressClass *networking.IngressClass
  1012  		expected     []metav1.TableRow
  1013  	}{{
  1014  		name: "example with params",
  1015  		ingressClass: &networking.IngressClass{
  1016  			ObjectMeta: metav1.ObjectMeta{
  1017  				Name:              "test1",
  1018  				CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
  1019  			},
  1020  			Spec: networking.IngressClassSpec{
  1021  				Controller: "example.com/controller",
  1022  				Parameters: &networking.IngressClassParametersReference{Kind: "customgroup", Name: "example"},
  1023  			},
  1024  		},
  1025  		expected: []metav1.TableRow{{Cells: []interface{}{"test1", "example.com/controller", "customgroup/example", "10y"}}},
  1026  	}, {
  1027  		name: "example with params + API Group",
  1028  		ingressClass: &networking.IngressClass{
  1029  			ObjectMeta: metav1.ObjectMeta{
  1030  				Name:              "test1",
  1031  				CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
  1032  			},
  1033  			Spec: networking.IngressClassSpec{
  1034  				Controller: "example.com/controller",
  1035  				Parameters: &networking.IngressClassParametersReference{
  1036  					APIGroup: utilpointer.StringPtr("example.com"),
  1037  					Kind:     "customgroup",
  1038  					Name:     "example",
  1039  				},
  1040  			},
  1041  		},
  1042  		expected: []metav1.TableRow{{Cells: []interface{}{"test1", "example.com/controller", "customgroup.example.com/example", "10y"}}},
  1043  	}, {
  1044  		name: "example without params",
  1045  		ingressClass: &networking.IngressClass{
  1046  			ObjectMeta: metav1.ObjectMeta{
  1047  				Name:              "test2",
  1048  				CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-11, 0, 0)},
  1049  			},
  1050  			Spec: networking.IngressClassSpec{
  1051  				Controller: "example.com/controller2",
  1052  			},
  1053  		},
  1054  		expected: []metav1.TableRow{{Cells: []interface{}{"test2", "example.com/controller2", "<none>", "11y"}}},
  1055  	}}
  1056  
  1057  	for _, testCase := range testCases {
  1058  		t.Run(testCase.name, func(t *testing.T) {
  1059  			rows, err := printIngressClass(testCase.ingressClass, printers.GenerateOptions{})
  1060  			if err != nil {
  1061  				t.Fatalf("Error generating table rows for Ingress: %#v", err)
  1062  			}
  1063  			for i := range rows {
  1064  				rows[i].Object.Object = nil
  1065  			}
  1066  			if !reflect.DeepEqual(testCase.expected, rows) {
  1067  				t.Errorf("mismatch: %s", cmp.Diff(testCase.expected, rows))
  1068  			}
  1069  		})
  1070  	}
  1071  }
  1072  
  1073  func TestPrintServiceLoadBalancer(t *testing.T) {
  1074  	tests := []struct {
  1075  		service  api.Service
  1076  		options  printers.GenerateOptions
  1077  		expected []metav1.TableRow
  1078  	}{
  1079  		// Test load balancer service with multiple external IP's
  1080  		{
  1081  			service: api.Service{
  1082  				ObjectMeta: metav1.ObjectMeta{Name: "service1"},
  1083  				Spec: api.ServiceSpec{
  1084  					ClusterIPs: []string{"1.2.3.4"},
  1085  					Type:       "LoadBalancer",
  1086  					Ports:      []api.ServicePort{{Port: 80, Protocol: "TCP"}},
  1087  				},
  1088  				Status: api.ServiceStatus{
  1089  					LoadBalancer: api.LoadBalancerStatus{
  1090  						Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}}},
  1091  				},
  1092  			},
  1093  			options: printers.GenerateOptions{},
  1094  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  1095  			expected: []metav1.TableRow{{Cells: []interface{}{"service1", "LoadBalancer", "1.2.3.4", "2.3.4.5,3.4.5.6", "80/TCP", "<unknown>"}}},
  1096  		},
  1097  		// Test load balancer service with pending external IP.
  1098  		{
  1099  			service: api.Service{
  1100  				ObjectMeta: metav1.ObjectMeta{Name: "service2"},
  1101  				Spec: api.ServiceSpec{
  1102  					ClusterIPs: []string{"1.3.4.5"},
  1103  					Type:       "LoadBalancer",
  1104  					Ports:      []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}, {Port: 7777, Protocol: "SCTP"}},
  1105  				},
  1106  			},
  1107  			options: printers.GenerateOptions{},
  1108  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  1109  			expected: []metav1.TableRow{{Cells: []interface{}{"service2", "LoadBalancer", "1.3.4.5", "<pending>", "80/TCP,8090/UDP,8000/TCP,7777/SCTP", "<unknown>"}}},
  1110  		},
  1111  		// Test load balancer service with multiple ports.
  1112  		{
  1113  			service: api.Service{
  1114  				ObjectMeta: metav1.ObjectMeta{Name: "service3"},
  1115  				Spec: api.ServiceSpec{
  1116  					ClusterIPs: []string{"1.4.5.6"},
  1117  					Type:       "LoadBalancer",
  1118  					Ports:      []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}},
  1119  				},
  1120  				Status: api.ServiceStatus{
  1121  					LoadBalancer: api.LoadBalancerStatus{
  1122  						Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}}},
  1123  				},
  1124  			},
  1125  			options: printers.GenerateOptions{},
  1126  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  1127  			expected: []metav1.TableRow{{Cells: []interface{}{"service3", "LoadBalancer", "1.4.5.6", "2.3.4.5", "80/TCP,8090/UDP,8000/TCP", "<unknown>"}}},
  1128  		},
  1129  		// Long external IP's list gets elided.
  1130  		{
  1131  			service: api.Service{
  1132  				ObjectMeta: metav1.ObjectMeta{Name: "service4"},
  1133  				Spec: api.ServiceSpec{
  1134  					ClusterIPs: []string{"1.5.6.7"},
  1135  					Type:       "LoadBalancer",
  1136  					Ports:      []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}},
  1137  				},
  1138  				Status: api.ServiceStatus{
  1139  					LoadBalancer: api.LoadBalancerStatus{
  1140  						Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}, {IP: "5.6.7.8", Hostname: "host5678"}}},
  1141  				},
  1142  			},
  1143  			options: printers.GenerateOptions{},
  1144  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  1145  			expected: []metav1.TableRow{{Cells: []interface{}{"service4", "LoadBalancer", "1.5.6.7", "2.3.4.5,3.4.5...", "80/TCP,8090/UDP,8000/TCP", "<unknown>"}}},
  1146  		},
  1147  		// Generate options: Wide, includes selectors.
  1148  		{
  1149  			service: api.Service{
  1150  				ObjectMeta: metav1.ObjectMeta{Name: "service4"},
  1151  				Spec: api.ServiceSpec{
  1152  					ClusterIPs: []string{"1.5.6.7"},
  1153  					Type:       "LoadBalancer",
  1154  					Ports:      []api.ServicePort{{Port: 80, Protocol: "TCP"}, {Port: 8090, Protocol: "UDP"}, {Port: 8000, Protocol: "TCP"}},
  1155  					Selector:   map[string]string{"foo": "bar"},
  1156  				},
  1157  				Status: api.ServiceStatus{
  1158  					LoadBalancer: api.LoadBalancerStatus{
  1159  						Ingress: []api.LoadBalancerIngress{{IP: "2.3.4.5"}, {IP: "3.4.5.6"}, {IP: "5.6.7.8", Hostname: "host5678"}}},
  1160  				},
  1161  			},
  1162  			options: printers.GenerateOptions{Wide: true},
  1163  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  1164  			expected: []metav1.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", "<unknown>", "foo=bar"}}},
  1165  		},
  1166  	}
  1167  
  1168  	for i, test := range tests {
  1169  		rows, err := printService(&test.service, test.options)
  1170  		if err != nil {
  1171  			t.Fatalf("Error printing table rows for Service: %#v", err)
  1172  		}
  1173  		for i := range rows {
  1174  			rows[i].Object.Object = nil
  1175  		}
  1176  		if !reflect.DeepEqual(test.expected, rows) {
  1177  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  1178  		}
  1179  	}
  1180  }
  1181  
  1182  func TestPrintPod(t *testing.T) {
  1183  	tests := []struct {
  1184  		pod    api.Pod
  1185  		expect []metav1.TableRow
  1186  	}{
  1187  		{
  1188  			// Test name, num of containers, restarts, container ready status
  1189  			api.Pod{
  1190  				ObjectMeta: metav1.ObjectMeta{Name: "test1"},
  1191  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1192  				Status: api.PodStatus{
  1193  					Phase: "podPhase",
  1194  					ContainerStatuses: []api.ContainerStatus{
  1195  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1196  						{RestartCount: 3},
  1197  					},
  1198  				},
  1199  			},
  1200  			[]metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", "6", "<unknown>"}}},
  1201  		},
  1202  		{
  1203  			// Test container error overwrites pod phase
  1204  			api.Pod{
  1205  				ObjectMeta: metav1.ObjectMeta{Name: "test2"},
  1206  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1207  				Status: api.PodStatus{
  1208  					Phase: "podPhase",
  1209  					ContainerStatuses: []api.ContainerStatus{
  1210  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1211  						{State: api.ContainerState{Waiting: &api.ContainerStateWaiting{Reason: "ContainerWaitingReason"}}, RestartCount: 3},
  1212  					},
  1213  				},
  1214  			},
  1215  			[]metav1.TableRow{{Cells: []interface{}{"test2", "1/2", "ContainerWaitingReason", "6", "<unknown>"}}},
  1216  		},
  1217  		{
  1218  			// Test the same as the above but with Terminated state and the first container overwrites the rest
  1219  			api.Pod{
  1220  				ObjectMeta: metav1.ObjectMeta{Name: "test3"},
  1221  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1222  				Status: api.PodStatus{
  1223  					Phase: "podPhase",
  1224  					ContainerStatuses: []api.ContainerStatus{
  1225  						{State: api.ContainerState{Waiting: &api.ContainerStateWaiting{Reason: "ContainerWaitingReason"}}, RestartCount: 3},
  1226  						{State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "ContainerTerminatedReason"}}, RestartCount: 3},
  1227  					},
  1228  				},
  1229  			},
  1230  			[]metav1.TableRow{{Cells: []interface{}{"test3", "0/2", "ContainerWaitingReason", "6", "<unknown>"}}},
  1231  		},
  1232  		{
  1233  			// Test ready is not enough for reporting running
  1234  			api.Pod{
  1235  				ObjectMeta: metav1.ObjectMeta{Name: "test4"},
  1236  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1237  				Status: api.PodStatus{
  1238  					Phase: "podPhase",
  1239  					ContainerStatuses: []api.ContainerStatus{
  1240  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1241  						{Ready: true, RestartCount: 3},
  1242  					},
  1243  				},
  1244  			},
  1245  			[]metav1.TableRow{{Cells: []interface{}{"test4", "1/2", "podPhase", "6", "<unknown>"}}},
  1246  		},
  1247  		{
  1248  			// Test ready is not enough for reporting running
  1249  			api.Pod{
  1250  				ObjectMeta: metav1.ObjectMeta{Name: "test5"},
  1251  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1252  				Status: api.PodStatus{
  1253  					Reason: "podReason",
  1254  					Phase:  "podPhase",
  1255  					ContainerStatuses: []api.ContainerStatus{
  1256  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1257  						{Ready: true, RestartCount: 3},
  1258  					},
  1259  				},
  1260  			},
  1261  			[]metav1.TableRow{{Cells: []interface{}{"test5", "1/2", "podReason", "6", "<unknown>"}}},
  1262  		},
  1263  		{
  1264  			// Test pod has 2 containers, one is running and the other is completed, w/o ready condition
  1265  			api.Pod{
  1266  				ObjectMeta: metav1.ObjectMeta{Name: "test6"},
  1267  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1268  				Status: api.PodStatus{
  1269  					Phase:  "Running",
  1270  					Reason: "",
  1271  					ContainerStatuses: []api.ContainerStatus{
  1272  						{Ready: true, RestartCount: 3, State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Completed", ExitCode: 0}}},
  1273  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1274  					},
  1275  				},
  1276  			},
  1277  			[]metav1.TableRow{{Cells: []interface{}{"test6", "1/2", "NotReady", "6", "<unknown>"}}},
  1278  		},
  1279  		{
  1280  			// Test pod has 2 containers, one is running and the other is completed, with ready condition
  1281  			api.Pod{
  1282  				ObjectMeta: metav1.ObjectMeta{Name: "test6"},
  1283  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1284  				Status: api.PodStatus{
  1285  					Phase:  "Running",
  1286  					Reason: "",
  1287  					ContainerStatuses: []api.ContainerStatus{
  1288  						{Ready: true, RestartCount: 3, State: api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Completed", ExitCode: 0}}},
  1289  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1290  					},
  1291  					Conditions: []api.PodCondition{
  1292  						{Type: api.PodReady, Status: api.ConditionTrue, LastProbeTime: metav1.Time{Time: time.Now()}},
  1293  					},
  1294  				},
  1295  			},
  1296  			[]metav1.TableRow{{Cells: []interface{}{"test6", "1/2", "Running", "6", "<unknown>"}}},
  1297  		},
  1298  		{
  1299  			// Test pod has 1 init container restarting and 1 container not running
  1300  			api.Pod{
  1301  				ObjectMeta: metav1.ObjectMeta{Name: "test7"},
  1302  				Spec:       api.PodSpec{InitContainers: make([]api.Container, 1), Containers: make([]api.Container, 1)},
  1303  				Status: api.PodStatus{
  1304  					Phase: "podPhase",
  1305  					InitContainerStatuses: []api.ContainerStatus{
  1306  						{
  1307  							Ready:                false,
  1308  							RestartCount:         3,
  1309  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1310  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}},
  1311  						},
  1312  					},
  1313  					ContainerStatuses: []api.ContainerStatus{
  1314  						{
  1315  							Ready:        false,
  1316  							RestartCount: 0,
  1317  							State:        api.ContainerState{Waiting: &api.ContainerStateWaiting{}},
  1318  						},
  1319  					},
  1320  				},
  1321  			},
  1322  			[]metav1.TableRow{{Cells: []interface{}{"test7", "0/1", "Init:0/1", "3 (10s ago)", "<unknown>"}}},
  1323  		},
  1324  		{
  1325  			// Test pod has 2 init containers, one restarting and the other not running, and 1 container not running
  1326  			api.Pod{
  1327  				ObjectMeta: metav1.ObjectMeta{Name: "test8"},
  1328  				Spec:       api.PodSpec{InitContainers: make([]api.Container, 2), Containers: make([]api.Container, 1)},
  1329  				Status: api.PodStatus{
  1330  					Phase: "podPhase",
  1331  					InitContainerStatuses: []api.ContainerStatus{
  1332  						{
  1333  							Ready:                false,
  1334  							RestartCount:         3,
  1335  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1336  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}},
  1337  						},
  1338  						{
  1339  							Ready: false,
  1340  							State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}},
  1341  						},
  1342  					},
  1343  					ContainerStatuses: []api.ContainerStatus{
  1344  						{
  1345  							Ready: false,
  1346  							State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}},
  1347  						},
  1348  					},
  1349  				},
  1350  			},
  1351  			[]metav1.TableRow{{Cells: []interface{}{"test8", "0/1", "Init:0/2", "3 (10s ago)", "<unknown>"}}},
  1352  		},
  1353  		{
  1354  			// Test pod has 2 init containers, one completed without restarts and the other restarting, and 1 container not running
  1355  			api.Pod{
  1356  				ObjectMeta: metav1.ObjectMeta{Name: "test9"},
  1357  				Spec:       api.PodSpec{InitContainers: make([]api.Container, 2), Containers: make([]api.Container, 1)},
  1358  				Status: api.PodStatus{
  1359  					Phase: "podPhase",
  1360  					InitContainerStatuses: []api.ContainerStatus{
  1361  						{
  1362  							Ready: false,
  1363  							State: api.ContainerState{Terminated: &api.ContainerStateTerminated{}},
  1364  						},
  1365  						{
  1366  							Ready:                false,
  1367  							RestartCount:         3,
  1368  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1369  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}},
  1370  						},
  1371  					},
  1372  					ContainerStatuses: []api.ContainerStatus{
  1373  						{
  1374  							Ready: false,
  1375  							State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}},
  1376  						},
  1377  					},
  1378  				},
  1379  			},
  1380  			[]metav1.TableRow{{Cells: []interface{}{"test9", "0/1", "Init:1/2", "3 (10s ago)", "<unknown>"}}},
  1381  		},
  1382  		{
  1383  			// Test pod has 2 init containers, one completed with restarts and the other restarting, and 1 container not running
  1384  			api.Pod{
  1385  				ObjectMeta: metav1.ObjectMeta{Name: "test10"},
  1386  				Spec:       api.PodSpec{InitContainers: make([]api.Container, 2), Containers: make([]api.Container, 1)},
  1387  				Status: api.PodStatus{
  1388  					Phase: "podPhase",
  1389  					InitContainerStatuses: []api.ContainerStatus{
  1390  						{
  1391  							Ready:                false,
  1392  							RestartCount:         2,
  1393  							State:                api.ContainerState{Terminated: &api.ContainerStateTerminated{}},
  1394  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-2 * time.Minute))}},
  1395  						},
  1396  						{
  1397  							Ready:                false,
  1398  							RestartCount:         3,
  1399  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1400  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}},
  1401  						},
  1402  					},
  1403  					ContainerStatuses: []api.ContainerStatus{
  1404  						{
  1405  							Ready: false,
  1406  							State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}},
  1407  						},
  1408  					},
  1409  				},
  1410  			},
  1411  			[]metav1.TableRow{{Cells: []interface{}{"test10", "0/1", "Init:1/2", "5 (10s ago)", "<unknown>"}}},
  1412  		},
  1413  		{
  1414  			// Test pod has 1 init container completed with restarts and one container restarting
  1415  			api.Pod{
  1416  				ObjectMeta: metav1.ObjectMeta{Name: "test11"},
  1417  				Spec:       api.PodSpec{InitContainers: make([]api.Container, 1), Containers: make([]api.Container, 1)},
  1418  				Status: api.PodStatus{
  1419  					Phase: "Running",
  1420  					InitContainerStatuses: []api.ContainerStatus{
  1421  						{
  1422  							Ready:                false,
  1423  							RestartCount:         2,
  1424  							State:                api.ContainerState{Terminated: &api.ContainerStateTerminated{}},
  1425  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-2 * time.Minute))}},
  1426  						},
  1427  					},
  1428  					ContainerStatuses: []api.ContainerStatus{
  1429  						{
  1430  							Ready:                false,
  1431  							RestartCount:         4,
  1432  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1433  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * time.Second))}},
  1434  						},
  1435  					},
  1436  				},
  1437  			},
  1438  			[]metav1.TableRow{{Cells: []interface{}{"test11", "0/1", "Running", "4 (20s ago)", "<unknown>"}}},
  1439  		},
  1440  		{
  1441  			// Test pod has 1 container that restarted 5d ago
  1442  			api.Pod{
  1443  				ObjectMeta: metav1.ObjectMeta{Name: "test12"},
  1444  				Spec:       api.PodSpec{Containers: make([]api.Container, 1)},
  1445  				Status: api.PodStatus{
  1446  					Phase: "Running",
  1447  					ContainerStatuses: []api.ContainerStatus{
  1448  						{
  1449  							Ready:                true,
  1450  							RestartCount:         3,
  1451  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1452  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-5 * 24 * time.Hour))}},
  1453  						},
  1454  					},
  1455  				},
  1456  			},
  1457  			[]metav1.TableRow{{Cells: []interface{}{"test12", "1/1", "Running", "3 (5d ago)", "<unknown>"}}},
  1458  		},
  1459  		{
  1460  			// Test pod has 2 containers, one has never restarted and the other has restarted 10d ago
  1461  			api.Pod{
  1462  				ObjectMeta: metav1.ObjectMeta{Name: "test13"},
  1463  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1464  				Status: api.PodStatus{
  1465  					Phase: "Running",
  1466  					ContainerStatuses: []api.ContainerStatus{
  1467  						{
  1468  							Ready:        true,
  1469  							RestartCount: 0,
  1470  							State:        api.ContainerState{Running: &api.ContainerStateRunning{}},
  1471  						},
  1472  						{
  1473  							Ready:                true,
  1474  							RestartCount:         3,
  1475  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1476  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * 24 * time.Hour))}},
  1477  						},
  1478  					},
  1479  				},
  1480  			},
  1481  			[]metav1.TableRow{{Cells: []interface{}{"test13", "2/2", "Running", "3 (10d ago)", "<unknown>"}}},
  1482  		},
  1483  		{
  1484  			// Test pod has 2 containers, one restarted 5d ago and the other restarted 20d ago
  1485  			api.Pod{
  1486  				ObjectMeta: metav1.ObjectMeta{Name: "test14"},
  1487  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1488  				Status: api.PodStatus{
  1489  					Phase: "Running",
  1490  					ContainerStatuses: []api.ContainerStatus{
  1491  						{
  1492  							Ready:                true,
  1493  							RestartCount:         6,
  1494  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1495  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-5 * 24 * time.Hour))}},
  1496  						},
  1497  						{
  1498  							Ready:                true,
  1499  							RestartCount:         3,
  1500  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1501  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * 24 * time.Hour))}},
  1502  						},
  1503  					},
  1504  				},
  1505  			},
  1506  			[]metav1.TableRow{{Cells: []interface{}{"test14", "2/2", "Running", "9 (5d ago)", "<unknown>"}}},
  1507  		},
  1508  		{
  1509  			// Test PodScheduled condition with reason WaitingForGates
  1510  			api.Pod{
  1511  				ObjectMeta: metav1.ObjectMeta{Name: "test15"},
  1512  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1513  				Status: api.PodStatus{
  1514  					Phase: "podPhase",
  1515  					Conditions: []api.PodCondition{
  1516  						{
  1517  							Type:   api.PodScheduled,
  1518  							Status: api.ConditionFalse,
  1519  							Reason: apiv1.PodReasonSchedulingGated,
  1520  						},
  1521  					},
  1522  				},
  1523  			},
  1524  			[]metav1.TableRow{{Cells: []interface{}{"test15", "0/2", apiv1.PodReasonSchedulingGated, "0", "<unknown>"}}},
  1525  		},
  1526  	}
  1527  
  1528  	for i, test := range tests {
  1529  		rows, err := printPod(&test.pod, printers.GenerateOptions{})
  1530  		if err != nil {
  1531  			t.Fatal(err)
  1532  		}
  1533  		for i := range rows {
  1534  			rows[i].Object.Object = nil
  1535  		}
  1536  		if !reflect.DeepEqual(test.expect, rows) {
  1537  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows))
  1538  		}
  1539  	}
  1540  }
  1541  
  1542  func TestPrintPodWithRestartableInitContainer(t *testing.T) {
  1543  	tests := []struct {
  1544  		pod    api.Pod
  1545  		expect []metav1.TableRow
  1546  	}{
  1547  		{
  1548  			// Test pod has 2 restartable init containers, the first one running but not started.
  1549  			api.Pod{
  1550  				ObjectMeta: metav1.ObjectMeta{Name: "test1"},
  1551  				Spec: api.PodSpec{
  1552  					InitContainers: []api.Container{
  1553  						{Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways},
  1554  						{Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways},
  1555  					}, Containers: make([]api.Container, 1)},
  1556  				Status: api.PodStatus{
  1557  					Phase: "Pending",
  1558  					InitContainerStatuses: []api.ContainerStatus{
  1559  						{
  1560  							Name:                 "restartable-init-1",
  1561  							Ready:                false,
  1562  							RestartCount:         3,
  1563  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1564  							Started:              utilpointer.Bool(false),
  1565  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}},
  1566  						},
  1567  						{
  1568  							Name:    "restartable-init-2",
  1569  							Ready:   false,
  1570  							State:   api.ContainerState{Waiting: &api.ContainerStateWaiting{}},
  1571  							Started: utilpointer.Bool(false),
  1572  						},
  1573  					},
  1574  					ContainerStatuses: []api.ContainerStatus{
  1575  						{
  1576  							Ready:        false,
  1577  							RestartCount: 0,
  1578  							State:        api.ContainerState{Waiting: &api.ContainerStateWaiting{}},
  1579  						},
  1580  					},
  1581  					Conditions: []api.PodCondition{
  1582  						{Type: api.PodInitialized, Status: api.ConditionFalse},
  1583  					},
  1584  				},
  1585  			},
  1586  			[]metav1.TableRow{{Cells: []interface{}{"test1", "0/3", "Init:0/2", "3 (10s ago)", "<unknown>"}}},
  1587  		},
  1588  		{
  1589  			// Test pod has 2 restartable init containers, the first one started and the second one running but not started.
  1590  			api.Pod{
  1591  				ObjectMeta: metav1.ObjectMeta{Name: "test1"},
  1592  				Spec: api.PodSpec{
  1593  					InitContainers: []api.Container{
  1594  						{Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways},
  1595  						{Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways},
  1596  					}, Containers: make([]api.Container, 1)},
  1597  				Status: api.PodStatus{
  1598  					Phase: "Pending",
  1599  					InitContainerStatuses: []api.ContainerStatus{
  1600  						{
  1601  							Name:                 "restartable-init-1",
  1602  							Ready:                false,
  1603  							RestartCount:         3,
  1604  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1605  							Started:              utilpointer.Bool(true),
  1606  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}},
  1607  						},
  1608  						{
  1609  							Name:    "restartable-init-2",
  1610  							Ready:   false,
  1611  							State:   api.ContainerState{Running: &api.ContainerStateRunning{}},
  1612  							Started: utilpointer.Bool(false),
  1613  						},
  1614  					},
  1615  					ContainerStatuses: []api.ContainerStatus{
  1616  						{
  1617  							Ready:        false,
  1618  							RestartCount: 0,
  1619  							State:        api.ContainerState{Waiting: &api.ContainerStateWaiting{}},
  1620  						},
  1621  					},
  1622  					Conditions: []api.PodCondition{
  1623  						{Type: api.PodInitialized, Status: api.ConditionFalse},
  1624  					},
  1625  				},
  1626  			},
  1627  			[]metav1.TableRow{{Cells: []interface{}{"test1", "0/3", "Init:1/2", "3 (10s ago)", "<unknown>"}}},
  1628  		},
  1629  		{
  1630  			// Test pod has 2 restartable init containers started and 1 container running
  1631  			api.Pod{
  1632  				ObjectMeta: metav1.ObjectMeta{Name: "test2"},
  1633  				Spec: api.PodSpec{
  1634  					InitContainers: []api.Container{
  1635  						{Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways},
  1636  						{Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways},
  1637  					}, Containers: make([]api.Container, 1)},
  1638  				Status: api.PodStatus{
  1639  					Phase: "Running",
  1640  					InitContainerStatuses: []api.ContainerStatus{
  1641  						{
  1642  							Name:                 "restartable-init-1",
  1643  							Ready:                false,
  1644  							RestartCount:         3,
  1645  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1646  							Started:              utilpointer.Bool(true),
  1647  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}},
  1648  						},
  1649  						{
  1650  							Name:    "restartable-init-2",
  1651  							Ready:   false,
  1652  							State:   api.ContainerState{Running: &api.ContainerStateRunning{}},
  1653  							Started: utilpointer.Bool(true),
  1654  						},
  1655  					},
  1656  					ContainerStatuses: []api.ContainerStatus{
  1657  						{
  1658  							Ready:                true,
  1659  							RestartCount:         4,
  1660  							State:                api.ContainerState{Running: &api.ContainerStateRunning{}},
  1661  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * time.Second))}},
  1662  						},
  1663  					},
  1664  					Conditions: []api.PodCondition{
  1665  						{Type: api.PodInitialized, Status: api.ConditionTrue},
  1666  					},
  1667  				},
  1668  			},
  1669  			[]metav1.TableRow{{Cells: []interface{}{"test2", "1/3", "Running", "7 (10s ago)", "<unknown>"}}},
  1670  		},
  1671  		{
  1672  			// Test pod has 2 restartable init containers completed with non-zero and 1 container completed
  1673  			api.Pod{
  1674  				ObjectMeta: metav1.ObjectMeta{Name: "test3"},
  1675  				Spec: api.PodSpec{
  1676  					InitContainers: []api.Container{
  1677  						{Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways},
  1678  						{Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways},
  1679  					}, Containers: make([]api.Container, 1)},
  1680  				Status: api.PodStatus{
  1681  					Phase: "Succeeded",
  1682  					InitContainerStatuses: []api.ContainerStatus{
  1683  						{
  1684  							Name:                 "restartable-init-1",
  1685  							Ready:                false,
  1686  							RestartCount:         3,
  1687  							State:                api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Error", ExitCode: 137}},
  1688  							Started:              utilpointer.Bool(false),
  1689  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-10 * time.Second))}},
  1690  						},
  1691  						{
  1692  							Name:    "restartable-init-2",
  1693  							Ready:   false,
  1694  							State:   api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Error", ExitCode: 137}},
  1695  							Started: utilpointer.Bool(false),
  1696  						},
  1697  					},
  1698  					ContainerStatuses: []api.ContainerStatus{
  1699  						{
  1700  							Ready:                false,
  1701  							RestartCount:         4,
  1702  							State:                api.ContainerState{Terminated: &api.ContainerStateTerminated{Reason: "Completed", ExitCode: 0}},
  1703  							LastTerminationState: api.ContainerState{Terminated: &api.ContainerStateTerminated{FinishedAt: metav1.NewTime(time.Now().Add(-20 * time.Second))}},
  1704  						},
  1705  					},
  1706  					Conditions: []api.PodCondition{
  1707  						{Type: api.PodInitialized, Status: api.ConditionTrue},
  1708  					},
  1709  				},
  1710  			},
  1711  			[]metav1.TableRow{
  1712  				{
  1713  					Cells: []interface{}{"test3", "0/3", "Completed", "7 (10s ago)", "<unknown>"},
  1714  					Conditions: []metav1.TableRowCondition{
  1715  						{Type: metav1.RowCompleted, Status: metav1.ConditionTrue, Reason: "Succeeded", Message: "The pod has completed successfully."},
  1716  					},
  1717  				},
  1718  			},
  1719  		},
  1720  	}
  1721  
  1722  	for i, test := range tests {
  1723  		rows, err := printPod(&test.pod, printers.GenerateOptions{})
  1724  		if err != nil {
  1725  			t.Fatal(err)
  1726  		}
  1727  		for i := range rows {
  1728  			rows[i].Object.Object = nil
  1729  		}
  1730  		if !reflect.DeepEqual(test.expect, rows) {
  1731  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows))
  1732  		}
  1733  	}
  1734  }
  1735  
  1736  func TestPrintPodwide(t *testing.T) {
  1737  	condition1 := "condition1"
  1738  	condition2 := "condition2"
  1739  	condition3 := "condition3"
  1740  	tests := []struct {
  1741  		pod    api.Pod
  1742  		expect []metav1.TableRow
  1743  	}{
  1744  		{
  1745  			// Test when the NodeName and PodIP are not none
  1746  			api.Pod{
  1747  				ObjectMeta: metav1.ObjectMeta{Name: "test1"},
  1748  				Spec: api.PodSpec{
  1749  					Containers: make([]api.Container, 2),
  1750  					NodeName:   "test1",
  1751  					ReadinessGates: []api.PodReadinessGate{
  1752  						{
  1753  							ConditionType: api.PodConditionType(condition1),
  1754  						},
  1755  						{
  1756  							ConditionType: api.PodConditionType(condition2),
  1757  						},
  1758  						{
  1759  							ConditionType: api.PodConditionType(condition3),
  1760  						},
  1761  					},
  1762  				},
  1763  				Status: api.PodStatus{
  1764  					Conditions: []api.PodCondition{
  1765  						{
  1766  							Type:   api.PodConditionType(condition1),
  1767  							Status: api.ConditionFalse,
  1768  						},
  1769  						{
  1770  							Type:   api.PodConditionType(condition2),
  1771  							Status: api.ConditionTrue,
  1772  						},
  1773  					},
  1774  					Phase:  "podPhase",
  1775  					PodIPs: []api.PodIP{{IP: "1.1.1.1"}},
  1776  					ContainerStatuses: []api.ContainerStatus{
  1777  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1778  						{RestartCount: 3},
  1779  					},
  1780  					NominatedNodeName: "node1",
  1781  				},
  1782  			},
  1783  			[]metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", "6", "<unknown>", "1.1.1.1", "test1", "node1", "1/3"}}},
  1784  		},
  1785  		{
  1786  			// Test when the NodeName and PodIP are not none
  1787  			api.Pod{
  1788  				ObjectMeta: metav1.ObjectMeta{Name: "test1"},
  1789  				Spec: api.PodSpec{
  1790  					Containers: make([]api.Container, 2),
  1791  					NodeName:   "test1",
  1792  					ReadinessGates: []api.PodReadinessGate{
  1793  						{
  1794  							ConditionType: api.PodConditionType(condition1),
  1795  						},
  1796  						{
  1797  							ConditionType: api.PodConditionType(condition2),
  1798  						},
  1799  						{
  1800  							ConditionType: api.PodConditionType(condition3),
  1801  						},
  1802  					},
  1803  				},
  1804  				Status: api.PodStatus{
  1805  					Conditions: []api.PodCondition{
  1806  						{
  1807  							Type:   api.PodConditionType(condition1),
  1808  							Status: api.ConditionFalse,
  1809  						},
  1810  						{
  1811  							Type:   api.PodConditionType(condition2),
  1812  							Status: api.ConditionTrue,
  1813  						},
  1814  					},
  1815  					Phase:  "podPhase",
  1816  					PodIPs: []api.PodIP{{IP: "1.1.1.1"}, {IP: "2001:db8::"}},
  1817  					ContainerStatuses: []api.ContainerStatus{
  1818  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1819  						{RestartCount: 3},
  1820  					},
  1821  					NominatedNodeName: "node1",
  1822  				},
  1823  			},
  1824  			[]metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "podPhase", "6", "<unknown>", "1.1.1.1", "test1", "node1", "1/3"}}},
  1825  		},
  1826  		{
  1827  			// Test when the NodeName and PodIP are none
  1828  			api.Pod{
  1829  				ObjectMeta: metav1.ObjectMeta{Name: "test2"},
  1830  				Spec: api.PodSpec{
  1831  					Containers: make([]api.Container, 2),
  1832  					NodeName:   "",
  1833  				},
  1834  				Status: api.PodStatus{
  1835  					Phase: "podPhase",
  1836  					ContainerStatuses: []api.ContainerStatus{
  1837  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1838  						{State: api.ContainerState{Waiting: &api.ContainerStateWaiting{Reason: "ContainerWaitingReason"}}, RestartCount: 3},
  1839  					},
  1840  				},
  1841  			},
  1842  			[]metav1.TableRow{{Cells: []interface{}{"test2", "1/2", "ContainerWaitingReason", "6", "<unknown>", "<none>", "<none>", "<none>", "<none>"}}},
  1843  		},
  1844  	}
  1845  
  1846  	for i, test := range tests {
  1847  		rows, err := printPod(&test.pod, printers.GenerateOptions{Wide: true})
  1848  		if err != nil {
  1849  			t.Fatal(err)
  1850  		}
  1851  		for i := range rows {
  1852  			rows[i].Object.Object = nil
  1853  		}
  1854  		if !reflect.DeepEqual(test.expect, rows) {
  1855  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows))
  1856  		}
  1857  	}
  1858  }
  1859  
  1860  func TestPrintPodConditions(t *testing.T) {
  1861  	runningPod := &api.Pod{
  1862  		ObjectMeta: metav1.ObjectMeta{Name: "test1", Labels: map[string]string{"a": "1", "b": "2"}},
  1863  		Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1864  		Status: api.PodStatus{
  1865  			Phase: "Running",
  1866  			ContainerStatuses: []api.ContainerStatus{
  1867  				{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1868  				{RestartCount: 3},
  1869  			},
  1870  		},
  1871  	}
  1872  	succeededPod := &api.Pod{
  1873  		ObjectMeta: metav1.ObjectMeta{Name: "test1", Labels: map[string]string{"a": "1", "b": "2"}},
  1874  		Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1875  		Status: api.PodStatus{
  1876  			Phase: "Succeeded",
  1877  			ContainerStatuses: []api.ContainerStatus{
  1878  				{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1879  				{RestartCount: 3},
  1880  			},
  1881  		},
  1882  	}
  1883  	failedPod := &api.Pod{
  1884  		ObjectMeta: metav1.ObjectMeta{Name: "test2", Labels: map[string]string{"b": "2"}},
  1885  		Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1886  		Status: api.PodStatus{
  1887  			Phase: "Failed",
  1888  			ContainerStatuses: []api.ContainerStatus{
  1889  				{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1890  				{RestartCount: 3},
  1891  			},
  1892  		},
  1893  	}
  1894  	tests := []struct {
  1895  		pod    *api.Pod
  1896  		expect []metav1.TableRow
  1897  	}{
  1898  		// Should not have TableRowCondition
  1899  		{
  1900  			pod: runningPod,
  1901  			// Columns: Name, Ready, Reason, Restarts, Age
  1902  			expect: []metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "Running", "6", "<unknown>"}}},
  1903  		},
  1904  		// Should have TableRowCondition: podSuccessConditions
  1905  		{
  1906  			pod: succeededPod,
  1907  			expect: []metav1.TableRow{
  1908  				{
  1909  					// Columns: Name, Ready, Reason, Restarts, Age
  1910  					Cells:      []interface{}{"test1", "1/2", "Succeeded", "6", "<unknown>"},
  1911  					Conditions: podSuccessConditions,
  1912  				},
  1913  			},
  1914  		},
  1915  		// Should have TableRowCondition: podFailedCondition
  1916  		{
  1917  			pod: failedPod,
  1918  			expect: []metav1.TableRow{
  1919  				{
  1920  					// Columns: Name, Ready, Reason, Restarts, Age
  1921  					Cells:      []interface{}{"test2", "1/2", "Failed", "6", "<unknown>"},
  1922  					Conditions: podFailedConditions,
  1923  				},
  1924  			},
  1925  		},
  1926  	}
  1927  
  1928  	for i, test := range tests {
  1929  		rows, err := printPod(test.pod, printers.GenerateOptions{})
  1930  		if err != nil {
  1931  			t.Fatal(err)
  1932  		}
  1933  		for i := range rows {
  1934  			rows[i].Object.Object = nil
  1935  		}
  1936  		if !reflect.DeepEqual(test.expect, rows) {
  1937  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows))
  1938  		}
  1939  	}
  1940  }
  1941  
  1942  func TestPrintPodList(t *testing.T) {
  1943  	tests := []struct {
  1944  		pods   api.PodList
  1945  		expect []metav1.TableRow
  1946  	}{
  1947  		// Test podList's pod: name, num of containers, restarts, container ready status
  1948  		{
  1949  			api.PodList{
  1950  				Items: []api.Pod{
  1951  					{
  1952  						ObjectMeta: metav1.ObjectMeta{Name: "test1"},
  1953  						Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  1954  						Status: api.PodStatus{
  1955  							Phase: "podPhase",
  1956  							ContainerStatuses: []api.ContainerStatus{
  1957  								{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1958  								{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1959  							},
  1960  						},
  1961  					},
  1962  					{
  1963  						ObjectMeta: metav1.ObjectMeta{Name: "test2"},
  1964  						Spec:       api.PodSpec{Containers: make([]api.Container, 1)},
  1965  						Status: api.PodStatus{
  1966  							Phase: "podPhase",
  1967  							ContainerStatuses: []api.ContainerStatus{
  1968  								{Ready: true, RestartCount: 1, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  1969  							},
  1970  						},
  1971  					},
  1972  				},
  1973  			},
  1974  			[]metav1.TableRow{{Cells: []interface{}{"test1", "2/2", "podPhase", "6", "<unknown>"}}, {Cells: []interface{}{"test2", "1/1", "podPhase", "1", "<unknown>"}}},
  1975  		},
  1976  	}
  1977  
  1978  	for _, test := range tests {
  1979  		rows, err := printPodList(&test.pods, printers.GenerateOptions{})
  1980  
  1981  		if err != nil {
  1982  			t.Fatal(err)
  1983  		}
  1984  		for i := range rows {
  1985  			rows[i].Object.Object = nil
  1986  		}
  1987  		if !reflect.DeepEqual(test.expect, rows) {
  1988  			t.Errorf("mismatch: %s", cmp.Diff(test.expect, rows))
  1989  		}
  1990  	}
  1991  }
  1992  
  1993  func TestPrintNonTerminatedPod(t *testing.T) {
  1994  	tests := []struct {
  1995  		pod    api.Pod
  1996  		expect []metav1.TableRow
  1997  	}{
  1998  		{
  1999  			// Test pod phase Running should be printed
  2000  			api.Pod{
  2001  				ObjectMeta: metav1.ObjectMeta{Name: "test1"},
  2002  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  2003  				Status: api.PodStatus{
  2004  					Phase: api.PodRunning,
  2005  					ContainerStatuses: []api.ContainerStatus{
  2006  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  2007  						{RestartCount: 3},
  2008  					},
  2009  				},
  2010  			},
  2011  			// Columns: Name, Ready, Reason, Restarts, Age
  2012  			[]metav1.TableRow{{Cells: []interface{}{"test1", "1/2", "Running", "6", "<unknown>"}}},
  2013  		},
  2014  		{
  2015  			// Test pod phase Pending should be printed
  2016  			api.Pod{
  2017  				ObjectMeta: metav1.ObjectMeta{Name: "test2"},
  2018  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  2019  				Status: api.PodStatus{
  2020  					Phase: api.PodPending,
  2021  					ContainerStatuses: []api.ContainerStatus{
  2022  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  2023  						{RestartCount: 3},
  2024  					},
  2025  				},
  2026  			},
  2027  			// Columns: Name, Ready, Reason, Restarts, Age
  2028  			[]metav1.TableRow{{Cells: []interface{}{"test2", "1/2", "Pending", "6", "<unknown>"}}},
  2029  		},
  2030  		{
  2031  			// Test pod phase Unknown should be printed
  2032  			api.Pod{
  2033  				ObjectMeta: metav1.ObjectMeta{Name: "test3"},
  2034  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  2035  				Status: api.PodStatus{
  2036  					Phase: api.PodUnknown,
  2037  					ContainerStatuses: []api.ContainerStatus{
  2038  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  2039  						{RestartCount: 3},
  2040  					},
  2041  				},
  2042  			},
  2043  			// Columns: Name, Ready, Reason, Restarts, Age
  2044  			[]metav1.TableRow{{Cells: []interface{}{"test3", "1/2", "Unknown", "6", "<unknown>"}}},
  2045  		},
  2046  		{
  2047  			// Test pod phase Succeeded should be printed
  2048  			api.Pod{
  2049  				ObjectMeta: metav1.ObjectMeta{Name: "test4"},
  2050  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  2051  				Status: api.PodStatus{
  2052  					Phase: api.PodSucceeded,
  2053  					ContainerStatuses: []api.ContainerStatus{
  2054  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  2055  						{RestartCount: 3},
  2056  					},
  2057  				},
  2058  			},
  2059  			// Columns: Name, Ready, Reason, Restarts, Age
  2060  			[]metav1.TableRow{
  2061  				{
  2062  					Cells:      []interface{}{"test4", "1/2", "Succeeded", "6", "<unknown>"},
  2063  					Conditions: podSuccessConditions,
  2064  				},
  2065  			},
  2066  		},
  2067  		{
  2068  			// Test pod phase Failed shouldn't be printed
  2069  			api.Pod{
  2070  				ObjectMeta: metav1.ObjectMeta{Name: "test5"},
  2071  				Spec:       api.PodSpec{Containers: make([]api.Container, 2)},
  2072  				Status: api.PodStatus{
  2073  					Phase: api.PodFailed,
  2074  					ContainerStatuses: []api.ContainerStatus{
  2075  						{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
  2076  						{Ready: true, RestartCount: 3},
  2077  					},
  2078  				},
  2079  			},
  2080  			// Columns: Name, Ready, Reason, Restarts, Age
  2081  			[]metav1.TableRow{
  2082  				{
  2083  					Cells:      []interface{}{"test5", "1/2", "Failed", "6", "<unknown>"},
  2084  					Conditions: podFailedConditions,
  2085  				},
  2086  			},
  2087  		},
  2088  	}
  2089  
  2090  	for i, test := range tests {
  2091  		rows, err := printPod(&test.pod, printers.GenerateOptions{})
  2092  		if err != nil {
  2093  			t.Fatal(err)
  2094  		}
  2095  		for i := range rows {
  2096  			rows[i].Object.Object = nil
  2097  		}
  2098  		if !reflect.DeepEqual(test.expect, rows) {
  2099  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expect, rows))
  2100  		}
  2101  	}
  2102  }
  2103  
  2104  func TestPrintPodTemplate(t *testing.T) {
  2105  	tests := []struct {
  2106  		podTemplate api.PodTemplate
  2107  		options     printers.GenerateOptions
  2108  		expected    []metav1.TableRow
  2109  	}{
  2110  		// Test basic pod template with no containers.
  2111  		{
  2112  			podTemplate: api.PodTemplate{
  2113  				ObjectMeta: metav1.ObjectMeta{Name: "pod-template-1"},
  2114  				Template: api.PodTemplateSpec{
  2115  					ObjectMeta: metav1.ObjectMeta{Name: "pod-template-1"},
  2116  					Spec: api.PodSpec{
  2117  						Containers: []api.Container{},
  2118  					},
  2119  				},
  2120  			},
  2121  
  2122  			options: printers.GenerateOptions{},
  2123  			// Columns: Name, Containers, Images, Pod Labels
  2124  			expected: []metav1.TableRow{{Cells: []interface{}{"pod-template-1", "", "", "<none>"}}},
  2125  		},
  2126  		// Test basic pod template with two containers.
  2127  		{
  2128  			podTemplate: api.PodTemplate{
  2129  				ObjectMeta: metav1.ObjectMeta{Name: "pod-template-2"},
  2130  				Template: api.PodTemplateSpec{
  2131  					ObjectMeta: metav1.ObjectMeta{Name: "pod-template-2"},
  2132  					Spec: api.PodSpec{
  2133  						Containers: []api.Container{
  2134  							{
  2135  								Name:  "fake-container1",
  2136  								Image: "fake-image1",
  2137  							},
  2138  							{
  2139  								Name:  "fake-container2",
  2140  								Image: "fake-image2",
  2141  							},
  2142  						},
  2143  					},
  2144  				},
  2145  			},
  2146  
  2147  			options: printers.GenerateOptions{},
  2148  			// Columns: Name, Containers, Images, Pod Labels
  2149  			expected: []metav1.TableRow{{Cells: []interface{}{"pod-template-2", "fake-container1,fake-container2", "fake-image1,fake-image2", "<none>"}}},
  2150  		},
  2151  		// Test basic pod template with pod labels
  2152  		{
  2153  			podTemplate: api.PodTemplate{
  2154  				ObjectMeta: metav1.ObjectMeta{Name: "pod-template-3"},
  2155  				Template: api.PodTemplateSpec{
  2156  					ObjectMeta: metav1.ObjectMeta{
  2157  						Name:   "pod-template-3",
  2158  						Labels: map[string]string{"foo": "bar"},
  2159  					},
  2160  					Spec: api.PodSpec{
  2161  						Containers: []api.Container{},
  2162  					},
  2163  				},
  2164  			},
  2165  
  2166  			options: printers.GenerateOptions{},
  2167  			// Columns: Name, Containers, Images, Pod Labels
  2168  			expected: []metav1.TableRow{{Cells: []interface{}{"pod-template-3", "", "", "foo=bar"}}},
  2169  		},
  2170  	}
  2171  
  2172  	for i, test := range tests {
  2173  		rows, err := printPodTemplate(&test.podTemplate, test.options)
  2174  		if err != nil {
  2175  			t.Fatal(err)
  2176  		}
  2177  		for i := range rows {
  2178  			rows[i].Object.Object = nil
  2179  		}
  2180  		if !reflect.DeepEqual(test.expected, rows) {
  2181  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  2182  		}
  2183  	}
  2184  }
  2185  
  2186  func TestPrintPodTemplateList(t *testing.T) {
  2187  
  2188  	templateList := api.PodTemplateList{
  2189  		Items: []api.PodTemplate{
  2190  			{
  2191  				ObjectMeta: metav1.ObjectMeta{Name: "pod-template-1"},
  2192  				Template: api.PodTemplateSpec{
  2193  					ObjectMeta: metav1.ObjectMeta{
  2194  						Name:   "pod-template-2",
  2195  						Labels: map[string]string{"foo": "bar"},
  2196  					},
  2197  					Spec: api.PodSpec{
  2198  						Containers: []api.Container{},
  2199  					},
  2200  				},
  2201  			},
  2202  			{
  2203  				ObjectMeta: metav1.ObjectMeta{Name: "pod-template-2"},
  2204  				Template: api.PodTemplateSpec{
  2205  					ObjectMeta: metav1.ObjectMeta{
  2206  						Name:   "pod-template-2",
  2207  						Labels: map[string]string{"a": "b"},
  2208  					},
  2209  					Spec: api.PodSpec{
  2210  						Containers: []api.Container{},
  2211  					},
  2212  				},
  2213  			},
  2214  		},
  2215  	}
  2216  
  2217  	// Columns: Name, Containers, Images, Pod Labels
  2218  	expectedRows := []metav1.TableRow{
  2219  		{Cells: []interface{}{"pod-template-1", "", "", "foo=bar"}},
  2220  		{Cells: []interface{}{"pod-template-2", "", "", "a=b"}},
  2221  	}
  2222  
  2223  	rows, err := printPodTemplateList(&templateList, printers.GenerateOptions{})
  2224  	if err != nil {
  2225  		t.Fatalf("Error printing pod template list: %#v", err)
  2226  	}
  2227  	for i := range rows {
  2228  		rows[i].Object.Object = nil
  2229  	}
  2230  	if !reflect.DeepEqual(expectedRows, rows) {
  2231  		t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows))
  2232  	}
  2233  }
  2234  
  2235  type stringTestList []struct {
  2236  	name, got, exp string
  2237  }
  2238  
  2239  func TestTranslateTimestampSince(t *testing.T) {
  2240  	tl := stringTestList{
  2241  		{"a while from now", translateTimestampSince(metav1.Time{Time: time.Now().Add(2.1e9)}), "<invalid>"},
  2242  		{"almost now", translateTimestampSince(metav1.Time{Time: time.Now().Add(1.9e9)}), "0s"},
  2243  		{"now", translateTimestampSince(metav1.Time{Time: time.Now()}), "0s"},
  2244  		{"unknown", translateTimestampSince(metav1.Time{}), "<unknown>"},
  2245  		{"30 seconds ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-3e10)}), "30s"},
  2246  		{"5 minutes ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-3e11)}), "5m"},
  2247  		{"an hour ago", translateTimestampSince(metav1.Time{Time: time.Now().Add(-6e12)}), "100m"},
  2248  		{"2 days ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}), "2d"},
  2249  		{"months ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, -90)}), "90d"},
  2250  		{"10 years ago", translateTimestampSince(metav1.Time{Time: time.Now().UTC().AddDate(-10, 0, 0)}), "10y"},
  2251  	}
  2252  	for _, test := range tl {
  2253  		if test.got != test.exp {
  2254  			t.Errorf("On %v, expected '%v', but got '%v'",
  2255  				test.name, test.exp, test.got)
  2256  		}
  2257  	}
  2258  }
  2259  
  2260  func TestTranslateTimestampUntil(t *testing.T) {
  2261  	// Since this method compares the time with time.Now() internally,
  2262  	// small buffers of 0.1 seconds are added on comparing times to consider method call overhead.
  2263  	// Otherwise, the output strings become shorter than expected.
  2264  	const buf = 1e8
  2265  	tl := stringTestList{
  2266  		{"a while ago", translateTimestampUntil(metav1.Time{Time: time.Now().Add(-2.1e9)}), "<invalid>"},
  2267  		{"almost now", translateTimestampUntil(metav1.Time{Time: time.Now().Add(-1.9e9)}), "0s"},
  2268  		{"now", translateTimestampUntil(metav1.Time{Time: time.Now()}), "0s"},
  2269  		{"unknown", translateTimestampUntil(metav1.Time{}), "<unknown>"},
  2270  		{"in 30 seconds", translateTimestampUntil(metav1.Time{Time: time.Now().Add(3e10 + buf)}), "30s"},
  2271  		{"in 5 minutes", translateTimestampUntil(metav1.Time{Time: time.Now().Add(3e11 + buf)}), "5m"},
  2272  		{"in an hour", translateTimestampUntil(metav1.Time{Time: time.Now().Add(6e12 + buf)}), "100m"},
  2273  		{"in 2 days", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, 2).Add(buf)}), "2d"},
  2274  		{"in months", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(0, 0, 90).Add(buf)}), "90d"},
  2275  		{"in 10 years", translateTimestampUntil(metav1.Time{Time: time.Now().UTC().AddDate(10, 0, 0).Add(buf)}), "10y"},
  2276  	}
  2277  	for _, test := range tl {
  2278  		if test.got != test.exp {
  2279  			t.Errorf("On %v, expected '%v', but got '%v'",
  2280  				test.name, test.exp, test.got)
  2281  		}
  2282  	}
  2283  }
  2284  
  2285  func TestPrintDeployment(t *testing.T) {
  2286  
  2287  	testDeployment := apps.Deployment{
  2288  		ObjectMeta: metav1.ObjectMeta{
  2289  			Name:              "test1",
  2290  			CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  2291  		},
  2292  		Spec: apps.DeploymentSpec{
  2293  			Replicas: 5,
  2294  			Template: api.PodTemplateSpec{
  2295  				Spec: api.PodSpec{
  2296  					Containers: []api.Container{
  2297  						{
  2298  							Name:  "fake-container1",
  2299  							Image: "fake-image1",
  2300  						},
  2301  						{
  2302  							Name:  "fake-container2",
  2303  							Image: "fake-image2",
  2304  						},
  2305  					},
  2306  				},
  2307  			},
  2308  			Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  2309  		},
  2310  		Status: apps.DeploymentStatus{
  2311  			Replicas:            10,
  2312  			UpdatedReplicas:     2,
  2313  			AvailableReplicas:   1,
  2314  			UnavailableReplicas: 4,
  2315  		},
  2316  	}
  2317  
  2318  	tests := []struct {
  2319  		deployment apps.Deployment
  2320  		options    printers.GenerateOptions
  2321  		expected   []metav1.TableRow
  2322  	}{
  2323  		// Test Deployment with no generate options.
  2324  		{
  2325  			deployment: testDeployment,
  2326  			options:    printers.GenerateOptions{},
  2327  			// Columns: Name, ReadyReplicas, UpdatedReplicas, AvailableReplicas, Age
  2328  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", "0/5", int64(2), int64(1), "0s"}}},
  2329  		},
  2330  		// Test generate options: Wide.
  2331  		{
  2332  			deployment: testDeployment,
  2333  			options:    printers.GenerateOptions{Wide: true},
  2334  			// Columns: Name, ReadyReplicas, UpdatedReplicas, AvailableReplicas, Age, Containers, Images, Selectors
  2335  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", "0/5", int64(2), int64(1), "0s", "fake-container1,fake-container2", "fake-image1,fake-image2", "foo=bar"}}},
  2336  		},
  2337  	}
  2338  
  2339  	for i, test := range tests {
  2340  		rows, err := printDeployment(&test.deployment, test.options)
  2341  		if err != nil {
  2342  			t.Fatal(err)
  2343  		}
  2344  		for i := range rows {
  2345  			rows[i].Object.Object = nil
  2346  		}
  2347  		if !reflect.DeepEqual(test.expected, rows) {
  2348  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  2349  		}
  2350  	}
  2351  }
  2352  
  2353  func TestPrintDaemonSet(t *testing.T) {
  2354  
  2355  	testDaemonSet := apps.DaemonSet{
  2356  		ObjectMeta: metav1.ObjectMeta{
  2357  			Name:              "test1",
  2358  			CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  2359  		},
  2360  		Spec: apps.DaemonSetSpec{
  2361  			Template: api.PodTemplateSpec{
  2362  				Spec: api.PodSpec{
  2363  					Containers: []api.Container{
  2364  						{
  2365  							Name:  "fake-container1",
  2366  							Image: "fake-image1",
  2367  						},
  2368  						{
  2369  							Name:  "fake-container2",
  2370  							Image: "fake-image2",
  2371  						},
  2372  					},
  2373  				},
  2374  			},
  2375  			Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  2376  		},
  2377  		Status: apps.DaemonSetStatus{
  2378  			CurrentNumberScheduled: 2,
  2379  			DesiredNumberScheduled: 3,
  2380  			NumberReady:            1,
  2381  			UpdatedNumberScheduled: 2,
  2382  			NumberAvailable:        0,
  2383  		},
  2384  	}
  2385  
  2386  	tests := []struct {
  2387  		daemonSet apps.DaemonSet
  2388  		options   printers.GenerateOptions
  2389  		expected  []metav1.TableRow
  2390  	}{
  2391  		// Test generate daemon set with no generate options.
  2392  		{
  2393  			daemonSet: testDaemonSet,
  2394  			options:   printers.GenerateOptions{},
  2395  			// Columns: Name, Num Desired, Num Current, Num Ready, Num Updated, Num Available, Selectors, Age
  2396  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", int64(3), int64(2), int64(1), int64(2), int64(0), "<none>", "0s"}}},
  2397  		},
  2398  		// Test generate daemon set with "Wide" generate options.
  2399  		{
  2400  			daemonSet: testDaemonSet,
  2401  			options:   printers.GenerateOptions{Wide: true},
  2402  			// Columns: Name, Num Desired, Num Current, Num Ready, Num Updated, Num Available, Node Selectors, Age, Containers, Images, Labels
  2403  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", int64(3), int64(2), int64(1), int64(2), int64(0), "<none>", "0s", "fake-container1,fake-container2", "fake-image1,fake-image2", "foo=bar"}}},
  2404  		},
  2405  	}
  2406  
  2407  	for i, test := range tests {
  2408  		rows, err := printDaemonSet(&test.daemonSet, test.options)
  2409  		if err != nil {
  2410  			t.Fatal(err)
  2411  		}
  2412  		for i := range rows {
  2413  			rows[i].Object.Object = nil
  2414  		}
  2415  		if !reflect.DeepEqual(test.expected, rows) {
  2416  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  2417  		}
  2418  	}
  2419  }
  2420  
  2421  func TestPrintDaemonSetList(t *testing.T) {
  2422  
  2423  	daemonSetList := apps.DaemonSetList{
  2424  		Items: []apps.DaemonSet{
  2425  			{
  2426  				ObjectMeta: metav1.ObjectMeta{
  2427  					Name:              "daemonset1",
  2428  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  2429  				},
  2430  				Spec: apps.DaemonSetSpec{
  2431  					Template: api.PodTemplateSpec{
  2432  						Spec: api.PodSpec{
  2433  							Containers: []api.Container{
  2434  								{
  2435  									Name:  "fake-container1",
  2436  									Image: "fake-image1",
  2437  								},
  2438  								{
  2439  									Name:  "fake-container2",
  2440  									Image: "fake-image2",
  2441  								},
  2442  							},
  2443  						},
  2444  					},
  2445  				},
  2446  				Status: apps.DaemonSetStatus{
  2447  					CurrentNumberScheduled: 2,
  2448  					DesiredNumberScheduled: 3,
  2449  					NumberReady:            1,
  2450  					UpdatedNumberScheduled: 2,
  2451  					NumberAvailable:        0,
  2452  				},
  2453  			},
  2454  			{
  2455  				ObjectMeta: metav1.ObjectMeta{
  2456  					Name:              "daemonset2",
  2457  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  2458  				},
  2459  				Spec: apps.DaemonSetSpec{
  2460  					Template: api.PodTemplateSpec{},
  2461  					Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  2462  				},
  2463  				Status: apps.DaemonSetStatus{
  2464  					CurrentNumberScheduled: 4,
  2465  					DesiredNumberScheduled: 2,
  2466  					NumberReady:            9,
  2467  					UpdatedNumberScheduled: 3,
  2468  					NumberAvailable:        3,
  2469  				},
  2470  			},
  2471  		},
  2472  	}
  2473  
  2474  	// Columns: Name, Num Desired, Num Current, Num Ready, Num Updated, Num Available, Selectors, Age
  2475  	expectedRows := []metav1.TableRow{
  2476  		{Cells: []interface{}{"daemonset1", int64(3), int64(2), int64(1), int64(2), int64(0), "<none>", "0s"}},
  2477  		{Cells: []interface{}{"daemonset2", int64(2), int64(4), int64(9), int64(3), int64(3), "<none>", "0s"}},
  2478  	}
  2479  
  2480  	rows, err := printDaemonSetList(&daemonSetList, printers.GenerateOptions{})
  2481  	if err != nil {
  2482  		t.Fatalf("Error printing daemon set list: %#v", err)
  2483  	}
  2484  	for i := range rows {
  2485  		rows[i].Object.Object = nil
  2486  	}
  2487  	if !reflect.DeepEqual(expectedRows, rows) {
  2488  		t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows))
  2489  	}
  2490  }
  2491  
  2492  func TestPrintJob(t *testing.T) {
  2493  	now := time.Now()
  2494  	completions := int32(2)
  2495  	tests := []struct {
  2496  		job      batch.Job
  2497  		options  printers.GenerateOptions
  2498  		expected []metav1.TableRow
  2499  	}{
  2500  		{
  2501  			// Generate table rows for Job with no generate options.
  2502  			job: batch.Job{
  2503  				ObjectMeta: metav1.ObjectMeta{
  2504  					Name:              "job1",
  2505  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  2506  				},
  2507  				Spec: batch.JobSpec{
  2508  					Completions: &completions,
  2509  					Template: api.PodTemplateSpec{
  2510  						Spec: api.PodSpec{
  2511  							Containers: []api.Container{
  2512  								{
  2513  									Name:  "fake-job-container1",
  2514  									Image: "fake-job-image1",
  2515  								},
  2516  								{
  2517  									Name:  "fake-job-container2",
  2518  									Image: "fake-job-image2",
  2519  								},
  2520  							},
  2521  						},
  2522  					},
  2523  					Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-lable-value"}},
  2524  				},
  2525  				Status: batch.JobStatus{
  2526  					Succeeded: 1,
  2527  				},
  2528  			},
  2529  			options: printers.GenerateOptions{},
  2530  			// Columns: Name, Completions, Duration, Age
  2531  			expected: []metav1.TableRow{{Cells: []interface{}{"job1", "1/2", "", "0s"}}},
  2532  		},
  2533  		// Generate table rows for Job with generate options "Wide".
  2534  		{
  2535  			job: batch.Job{
  2536  				ObjectMeta: metav1.ObjectMeta{
  2537  					Name:              "job1",
  2538  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  2539  				},
  2540  				Spec: batch.JobSpec{
  2541  					Completions: &completions,
  2542  					Template: api.PodTemplateSpec{
  2543  						Spec: api.PodSpec{
  2544  							Containers: []api.Container{
  2545  								{
  2546  									Name:  "fake-job-container1",
  2547  									Image: "fake-job-image1",
  2548  								},
  2549  								{
  2550  									Name:  "fake-job-container2",
  2551  									Image: "fake-job-image2",
  2552  								},
  2553  							},
  2554  						},
  2555  					},
  2556  					Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-label-value"}},
  2557  				},
  2558  				Status: batch.JobStatus{
  2559  					Succeeded: 1,
  2560  				},
  2561  			},
  2562  			options: printers.GenerateOptions{Wide: true},
  2563  			// Columns: Name, Completions, Duration, Age, Containers, Images, Selectors
  2564  			expected: []metav1.TableRow{
  2565  				{
  2566  					Cells: []interface{}{"job1", "1/2", "", "0s", "fake-job-container1,fake-job-container2", "fake-job-image1,fake-job-image2", "job-label=job-label-value"},
  2567  				},
  2568  			},
  2569  		},
  2570  		// Job with ten-year age.
  2571  		{
  2572  			job: batch.Job{
  2573  				ObjectMeta: metav1.ObjectMeta{
  2574  					Name:              "job2",
  2575  					CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
  2576  				},
  2577  				Spec: batch.JobSpec{
  2578  					Completions: nil,
  2579  				},
  2580  				Status: batch.JobStatus{
  2581  					Succeeded: 0,
  2582  				},
  2583  			},
  2584  			options: printers.GenerateOptions{},
  2585  			// Columns: Name, Completions, Duration, Age
  2586  			expected: []metav1.TableRow{{Cells: []interface{}{"job2", "0/1", "", "10y"}}},
  2587  		},
  2588  		// Job with duration.
  2589  		{
  2590  			job: batch.Job{
  2591  				ObjectMeta: metav1.ObjectMeta{
  2592  					Name:              "job3",
  2593  					CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
  2594  				},
  2595  				Spec: batch.JobSpec{
  2596  					Completions: nil,
  2597  				},
  2598  				Status: batch.JobStatus{
  2599  					Succeeded:      0,
  2600  					StartTime:      &metav1.Time{Time: now.Add(time.Minute)},
  2601  					CompletionTime: &metav1.Time{Time: now.Add(31 * time.Minute)},
  2602  				},
  2603  			},
  2604  			options: printers.GenerateOptions{},
  2605  			// Columns: Name, Completions, Duration, Age
  2606  			expected: []metav1.TableRow{{Cells: []interface{}{"job3", "0/1", "30m", "10y"}}},
  2607  		},
  2608  		{
  2609  			job: batch.Job{
  2610  				ObjectMeta: metav1.ObjectMeta{
  2611  					Name:              "job4",
  2612  					CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
  2613  				},
  2614  				Spec: batch.JobSpec{
  2615  					Completions: nil,
  2616  				},
  2617  				Status: batch.JobStatus{
  2618  					Succeeded: 0,
  2619  					StartTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)},
  2620  				},
  2621  			},
  2622  			options: printers.GenerateOptions{},
  2623  			// Columns: Name, Completions, Duration, Age
  2624  			expected: []metav1.TableRow{{Cells: []interface{}{"job4", "0/1", "20m", "10y"}}},
  2625  		},
  2626  	}
  2627  
  2628  	for i, test := range tests {
  2629  		rows, err := printJob(&test.job, test.options)
  2630  		if err != nil {
  2631  			t.Fatal(err)
  2632  		}
  2633  		for i := range rows {
  2634  			rows[i].Object.Object = nil
  2635  		}
  2636  		if !reflect.DeepEqual(test.expected, rows) {
  2637  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  2638  		}
  2639  	}
  2640  }
  2641  
  2642  func TestPrintJobList(t *testing.T) {
  2643  	completions := int32(2)
  2644  	jobList := batch.JobList{
  2645  		Items: []batch.Job{
  2646  			{
  2647  				ObjectMeta: metav1.ObjectMeta{
  2648  					Name:              "job1",
  2649  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  2650  				},
  2651  				Spec: batch.JobSpec{
  2652  					Completions: &completions,
  2653  					Template: api.PodTemplateSpec{
  2654  						Spec: api.PodSpec{
  2655  							Containers: []api.Container{
  2656  								{
  2657  									Name:  "fake-job-container1",
  2658  									Image: "fake-job-image1",
  2659  								},
  2660  								{
  2661  									Name:  "fake-job-container2",
  2662  									Image: "fake-job-image2",
  2663  								},
  2664  							},
  2665  						},
  2666  					},
  2667  					Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-lable-value"}},
  2668  				},
  2669  				Status: batch.JobStatus{
  2670  					Succeeded: 1,
  2671  				},
  2672  			},
  2673  			{
  2674  				ObjectMeta: metav1.ObjectMeta{
  2675  					Name:              "job2",
  2676  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  2677  				},
  2678  				Spec: batch.JobSpec{
  2679  					Completions: &completions,
  2680  					Template: api.PodTemplateSpec{
  2681  						Spec: api.PodSpec{
  2682  							Containers: []api.Container{
  2683  								{
  2684  									Name:  "fake-job-container1",
  2685  									Image: "fake-job-image1",
  2686  								},
  2687  								{
  2688  									Name:  "fake-job-container2",
  2689  									Image: "fake-job-image2",
  2690  								},
  2691  							},
  2692  						},
  2693  					},
  2694  					Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"job-label": "job-lable-value"}},
  2695  				},
  2696  				Status: batch.JobStatus{
  2697  					Succeeded: 2,
  2698  					StartTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)},
  2699  				},
  2700  			},
  2701  		},
  2702  	}
  2703  
  2704  	// Columns: Name, Completions, Duration, Age
  2705  	expectedRows := []metav1.TableRow{
  2706  		{Cells: []interface{}{"job1", "1/2", "", "0s"}},
  2707  		{Cells: []interface{}{"job2", "2/2", "20m", "0s"}},
  2708  	}
  2709  
  2710  	rows, err := printJobList(&jobList, printers.GenerateOptions{})
  2711  	if err != nil {
  2712  		t.Fatalf("Error printing job list: %#v", err)
  2713  	}
  2714  	for i := range rows {
  2715  		rows[i].Object.Object = nil
  2716  	}
  2717  	if !reflect.DeepEqual(expectedRows, rows) {
  2718  		t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows))
  2719  	}
  2720  }
  2721  
  2722  func TestPrintHPA(t *testing.T) {
  2723  	minReplicasVal := int32(2)
  2724  	targetUtilizationVal := int32(80)
  2725  	currentUtilizationVal := int32(50)
  2726  	metricLabelSelector, err := metav1.ParseToLabelSelector("label=value")
  2727  	if err != nil {
  2728  		t.Errorf("unable to parse label selector: %v", err)
  2729  	}
  2730  	tests := []struct {
  2731  		hpa      autoscaling.HorizontalPodAutoscaler
  2732  		expected []metav1.TableRow
  2733  	}{
  2734  		// minReplicas unset
  2735  		{
  2736  			hpa: autoscaling.HorizontalPodAutoscaler{
  2737  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  2738  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  2739  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  2740  						Name: "some-rc",
  2741  						Kind: "ReplicationController",
  2742  					},
  2743  					MaxReplicas: 10,
  2744  				},
  2745  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  2746  					CurrentReplicas: 4,
  2747  					DesiredReplicas: 5,
  2748  				},
  2749  			},
  2750  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  2751  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<none>", "<unset>", int64(10), int64(4), "<unknown>"}}},
  2752  		},
  2753  		// external source type, target average value (no current)
  2754  		{
  2755  			hpa: autoscaling.HorizontalPodAutoscaler{
  2756  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  2757  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  2758  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  2759  						Name: "some-rc",
  2760  						Kind: "ReplicationController",
  2761  					},
  2762  					MinReplicas: &minReplicasVal,
  2763  					MaxReplicas: 10,
  2764  					Metrics: []autoscaling.MetricSpec{
  2765  						{
  2766  							Type: autoscaling.ExternalMetricSourceType,
  2767  							External: &autoscaling.ExternalMetricSource{
  2768  								Metric: autoscaling.MetricIdentifier{
  2769  									Name:     "some-external-metric",
  2770  									Selector: metricLabelSelector,
  2771  								},
  2772  								Target: autoscaling.MetricTarget{
  2773  									Type:         autoscaling.AverageValueMetricType,
  2774  									AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
  2775  								},
  2776  							},
  2777  						},
  2778  					},
  2779  				},
  2780  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  2781  					CurrentReplicas: 4,
  2782  					DesiredReplicas: 5,
  2783  				},
  2784  			},
  2785  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  2786  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/100m (avg)", "2", int64(10), int64(4), "<unknown>"}}},
  2787  		},
  2788  		// external source type, target average value
  2789  		{
  2790  			hpa: autoscaling.HorizontalPodAutoscaler{
  2791  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  2792  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  2793  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  2794  						Name: "some-rc",
  2795  						Kind: "ReplicationController",
  2796  					},
  2797  					MinReplicas: &minReplicasVal,
  2798  					MaxReplicas: 10,
  2799  					Metrics: []autoscaling.MetricSpec{
  2800  						{
  2801  							Type: autoscaling.ExternalMetricSourceType,
  2802  							External: &autoscaling.ExternalMetricSource{
  2803  								Metric: autoscaling.MetricIdentifier{
  2804  									Name:     "some-external-metric",
  2805  									Selector: metricLabelSelector,
  2806  								},
  2807  								Target: autoscaling.MetricTarget{
  2808  									Type:         autoscaling.AverageValueMetricType,
  2809  									AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
  2810  								},
  2811  							},
  2812  						},
  2813  					},
  2814  				},
  2815  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  2816  					CurrentReplicas: 4,
  2817  					DesiredReplicas: 5,
  2818  					CurrentMetrics: []autoscaling.MetricStatus{
  2819  						{
  2820  							Type: autoscaling.ExternalMetricSourceType,
  2821  							External: &autoscaling.ExternalMetricStatus{
  2822  								Metric: autoscaling.MetricIdentifier{
  2823  									Name:     "some-external-metric",
  2824  									Selector: metricLabelSelector,
  2825  								},
  2826  								Current: autoscaling.MetricValueStatus{
  2827  									AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
  2828  								},
  2829  							},
  2830  						},
  2831  					},
  2832  				},
  2833  			},
  2834  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  2835  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m (avg)", "2", int64(10), int64(4), "<unknown>"}}},
  2836  		},
  2837  		// external source type, target value (no current)
  2838  		{
  2839  			hpa: autoscaling.HorizontalPodAutoscaler{
  2840  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  2841  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  2842  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  2843  						Name: "some-rc",
  2844  						Kind: "ReplicationController",
  2845  					},
  2846  					MinReplicas: &minReplicasVal,
  2847  					MaxReplicas: 10,
  2848  					Metrics: []autoscaling.MetricSpec{
  2849  						{
  2850  							Type: autoscaling.ExternalMetricSourceType,
  2851  							External: &autoscaling.ExternalMetricSource{
  2852  								Metric: autoscaling.MetricIdentifier{
  2853  									Name:     "some-service-metric",
  2854  									Selector: metricLabelSelector,
  2855  								},
  2856  								Target: autoscaling.MetricTarget{
  2857  									Type:  autoscaling.ValueMetricType,
  2858  									Value: resource.NewMilliQuantity(100, resource.DecimalSI),
  2859  								},
  2860  							},
  2861  						},
  2862  					},
  2863  				},
  2864  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  2865  					CurrentReplicas: 4,
  2866  					DesiredReplicas: 5,
  2867  				},
  2868  			},
  2869  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  2870  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/100m", "2", int64(10), int64(4), "<unknown>"}}},
  2871  		},
  2872  		// external source type, target value
  2873  		{
  2874  			hpa: autoscaling.HorizontalPodAutoscaler{
  2875  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  2876  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  2877  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  2878  						Name: "some-rc",
  2879  						Kind: "ReplicationController",
  2880  					},
  2881  					MinReplicas: &minReplicasVal,
  2882  					MaxReplicas: 10,
  2883  					Metrics: []autoscaling.MetricSpec{
  2884  						{
  2885  							Type: autoscaling.ExternalMetricSourceType,
  2886  							External: &autoscaling.ExternalMetricSource{
  2887  								Metric: autoscaling.MetricIdentifier{
  2888  									Name:     "some-external-metric",
  2889  									Selector: metricLabelSelector,
  2890  								},
  2891  								Target: autoscaling.MetricTarget{
  2892  									Type:  autoscaling.ValueMetricType,
  2893  									Value: resource.NewMilliQuantity(100, resource.DecimalSI),
  2894  								},
  2895  							},
  2896  						},
  2897  					},
  2898  				},
  2899  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  2900  					CurrentReplicas: 4,
  2901  					DesiredReplicas: 5,
  2902  					CurrentMetrics: []autoscaling.MetricStatus{
  2903  						{
  2904  							Type: autoscaling.ExternalMetricSourceType,
  2905  							External: &autoscaling.ExternalMetricStatus{
  2906  								Metric: autoscaling.MetricIdentifier{
  2907  									Name: "some-external-metric",
  2908  								},
  2909  								Current: autoscaling.MetricValueStatus{
  2910  									Value: resource.NewMilliQuantity(50, resource.DecimalSI),
  2911  								},
  2912  							},
  2913  						},
  2914  					},
  2915  				},
  2916  			},
  2917  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  2918  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), "<unknown>"}}},
  2919  		},
  2920  		// pods source type (no current)
  2921  		{
  2922  			hpa: autoscaling.HorizontalPodAutoscaler{
  2923  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  2924  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  2925  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  2926  						Name: "some-rc",
  2927  						Kind: "ReplicationController",
  2928  					},
  2929  					MinReplicas: &minReplicasVal,
  2930  					MaxReplicas: 10,
  2931  					Metrics: []autoscaling.MetricSpec{
  2932  						{
  2933  							Type: autoscaling.PodsMetricSourceType,
  2934  							Pods: &autoscaling.PodsMetricSource{
  2935  								Metric: autoscaling.MetricIdentifier{
  2936  									Name: "some-pods-metric",
  2937  								},
  2938  								Target: autoscaling.MetricTarget{
  2939  									Type:         autoscaling.AverageValueMetricType,
  2940  									AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
  2941  								},
  2942  							},
  2943  						},
  2944  					},
  2945  				},
  2946  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  2947  					CurrentReplicas: 4,
  2948  					DesiredReplicas: 5,
  2949  				},
  2950  			},
  2951  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  2952  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/100m", "2", int64(10), int64(4), "<unknown>"}}},
  2953  		},
  2954  		// pods source type
  2955  		{
  2956  			hpa: autoscaling.HorizontalPodAutoscaler{
  2957  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  2958  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  2959  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  2960  						Name: "some-rc",
  2961  						Kind: "ReplicationController",
  2962  					},
  2963  					MinReplicas: &minReplicasVal,
  2964  					MaxReplicas: 10,
  2965  					Metrics: []autoscaling.MetricSpec{
  2966  						{
  2967  							Type: autoscaling.PodsMetricSourceType,
  2968  							Pods: &autoscaling.PodsMetricSource{
  2969  								Metric: autoscaling.MetricIdentifier{
  2970  									Name: "some-pods-metric",
  2971  								},
  2972  								Target: autoscaling.MetricTarget{
  2973  									Type:         autoscaling.AverageValueMetricType,
  2974  									AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
  2975  								},
  2976  							},
  2977  						},
  2978  					},
  2979  				},
  2980  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  2981  					CurrentReplicas: 4,
  2982  					DesiredReplicas: 5,
  2983  					CurrentMetrics: []autoscaling.MetricStatus{
  2984  						{
  2985  							Type: autoscaling.PodsMetricSourceType,
  2986  							Pods: &autoscaling.PodsMetricStatus{
  2987  								Metric: autoscaling.MetricIdentifier{
  2988  									Name: "some-pods-metric",
  2989  								},
  2990  								Current: autoscaling.MetricValueStatus{
  2991  									AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
  2992  								},
  2993  							},
  2994  						},
  2995  					},
  2996  				},
  2997  			},
  2998  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  2999  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), "<unknown>"}}},
  3000  		},
  3001  		// object source type (no current)
  3002  		{
  3003  			hpa: autoscaling.HorizontalPodAutoscaler{
  3004  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  3005  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  3006  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  3007  						Name: "some-rc",
  3008  						Kind: "ReplicationController",
  3009  					},
  3010  					MinReplicas: &minReplicasVal,
  3011  					MaxReplicas: 10,
  3012  					Metrics: []autoscaling.MetricSpec{
  3013  						{
  3014  							Type: autoscaling.ObjectMetricSourceType,
  3015  							Object: &autoscaling.ObjectMetricSource{
  3016  								DescribedObject: autoscaling.CrossVersionObjectReference{
  3017  									Name: "some-service",
  3018  									Kind: "Service",
  3019  								},
  3020  								Metric: autoscaling.MetricIdentifier{
  3021  									Name: "some-service-metric",
  3022  								},
  3023  								Target: autoscaling.MetricTarget{
  3024  									Type:  autoscaling.ValueMetricType,
  3025  									Value: resource.NewMilliQuantity(100, resource.DecimalSI),
  3026  								},
  3027  							},
  3028  						},
  3029  					},
  3030  				},
  3031  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  3032  					CurrentReplicas: 4,
  3033  					DesiredReplicas: 5,
  3034  				},
  3035  			},
  3036  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  3037  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/100m", "2", int64(10), int64(4), "<unknown>"}}},
  3038  		},
  3039  		// object source type
  3040  		{
  3041  			hpa: autoscaling.HorizontalPodAutoscaler{
  3042  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  3043  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  3044  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  3045  						Name: "some-rc",
  3046  						Kind: "ReplicationController",
  3047  					},
  3048  					MinReplicas: &minReplicasVal,
  3049  					MaxReplicas: 10,
  3050  					Metrics: []autoscaling.MetricSpec{
  3051  						{
  3052  							Type: autoscaling.ObjectMetricSourceType,
  3053  							Object: &autoscaling.ObjectMetricSource{
  3054  								DescribedObject: autoscaling.CrossVersionObjectReference{
  3055  									Name: "some-service",
  3056  									Kind: "Service",
  3057  								},
  3058  								Metric: autoscaling.MetricIdentifier{
  3059  									Name: "some-service-metric",
  3060  								},
  3061  								Target: autoscaling.MetricTarget{
  3062  									Type:  autoscaling.ValueMetricType,
  3063  									Value: resource.NewMilliQuantity(100, resource.DecimalSI),
  3064  								},
  3065  							},
  3066  						},
  3067  					},
  3068  				},
  3069  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  3070  					CurrentReplicas: 4,
  3071  					DesiredReplicas: 5,
  3072  					CurrentMetrics: []autoscaling.MetricStatus{
  3073  						{
  3074  							Type: autoscaling.ObjectMetricSourceType,
  3075  							Object: &autoscaling.ObjectMetricStatus{
  3076  								DescribedObject: autoscaling.CrossVersionObjectReference{
  3077  									Name: "some-service",
  3078  									Kind: "Service",
  3079  								},
  3080  								Metric: autoscaling.MetricIdentifier{
  3081  									Name: "some-service-metric",
  3082  								},
  3083  								Current: autoscaling.MetricValueStatus{
  3084  									Value: resource.NewMilliQuantity(50, resource.DecimalSI),
  3085  								},
  3086  							},
  3087  						},
  3088  					},
  3089  				},
  3090  			},
  3091  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  3092  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), "<unknown>"}}},
  3093  		},
  3094  		// resource source type, targetVal (no current)
  3095  		{
  3096  			hpa: autoscaling.HorizontalPodAutoscaler{
  3097  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  3098  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  3099  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  3100  						Name: "some-rc",
  3101  						Kind: "ReplicationController",
  3102  					},
  3103  					MinReplicas: &minReplicasVal,
  3104  					MaxReplicas: 10,
  3105  					Metrics: []autoscaling.MetricSpec{
  3106  						{
  3107  							Type: autoscaling.ResourceMetricSourceType,
  3108  							Resource: &autoscaling.ResourceMetricSource{
  3109  								Name: api.ResourceCPU,
  3110  								Target: autoscaling.MetricTarget{
  3111  									Type:         autoscaling.AverageValueMetricType,
  3112  									AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
  3113  								},
  3114  							},
  3115  						},
  3116  					},
  3117  				},
  3118  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  3119  					CurrentReplicas: 4,
  3120  					DesiredReplicas: 5,
  3121  				},
  3122  			},
  3123  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  3124  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/100m", "2", int64(10), int64(4), "<unknown>"}}},
  3125  		},
  3126  		// resource source type, targetVal
  3127  		{
  3128  			hpa: autoscaling.HorizontalPodAutoscaler{
  3129  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  3130  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  3131  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  3132  						Name: "some-rc",
  3133  						Kind: "ReplicationController",
  3134  					},
  3135  					MinReplicas: &minReplicasVal,
  3136  					MaxReplicas: 10,
  3137  					Metrics: []autoscaling.MetricSpec{
  3138  						{
  3139  							Type: autoscaling.ResourceMetricSourceType,
  3140  							Resource: &autoscaling.ResourceMetricSource{
  3141  								Name: api.ResourceCPU,
  3142  								Target: autoscaling.MetricTarget{
  3143  									Type:         autoscaling.AverageValueMetricType,
  3144  									AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
  3145  								},
  3146  							},
  3147  						},
  3148  					},
  3149  				},
  3150  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  3151  					CurrentReplicas: 4,
  3152  					DesiredReplicas: 5,
  3153  					CurrentMetrics: []autoscaling.MetricStatus{
  3154  						{
  3155  							Type: autoscaling.ResourceMetricSourceType,
  3156  							Resource: &autoscaling.ResourceMetricStatus{
  3157  								Name: api.ResourceCPU,
  3158  								Current: autoscaling.MetricValueStatus{
  3159  									AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
  3160  								},
  3161  							},
  3162  						},
  3163  					},
  3164  				},
  3165  			},
  3166  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  3167  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), "<unknown>"}}},
  3168  		},
  3169  		// resource source type, targetUtil (no current)
  3170  		{
  3171  			hpa: autoscaling.HorizontalPodAutoscaler{
  3172  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  3173  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  3174  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  3175  						Name: "some-rc",
  3176  						Kind: "ReplicationController",
  3177  					},
  3178  					MinReplicas: &minReplicasVal,
  3179  					MaxReplicas: 10,
  3180  					Metrics: []autoscaling.MetricSpec{
  3181  						{
  3182  							Type: autoscaling.ResourceMetricSourceType,
  3183  							Resource: &autoscaling.ResourceMetricSource{
  3184  								Name: api.ResourceCPU,
  3185  								Target: autoscaling.MetricTarget{
  3186  									Type:               autoscaling.UtilizationMetricType,
  3187  									AverageUtilization: &targetUtilizationVal,
  3188  								},
  3189  							},
  3190  						},
  3191  					},
  3192  				},
  3193  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  3194  					CurrentReplicas: 4,
  3195  					DesiredReplicas: 5,
  3196  				},
  3197  			},
  3198  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  3199  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/80%", "2", int64(10), int64(4), "<unknown>"}}},
  3200  		},
  3201  		// resource source type, targetUtil
  3202  		{
  3203  			hpa: autoscaling.HorizontalPodAutoscaler{
  3204  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  3205  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  3206  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  3207  						Name: "some-rc",
  3208  						Kind: "ReplicationController",
  3209  					},
  3210  					MinReplicas: &minReplicasVal,
  3211  					MaxReplicas: 10,
  3212  					Metrics: []autoscaling.MetricSpec{
  3213  						{
  3214  							Type: autoscaling.ResourceMetricSourceType,
  3215  							Resource: &autoscaling.ResourceMetricSource{
  3216  								Name: api.ResourceCPU,
  3217  								Target: autoscaling.MetricTarget{
  3218  									Type:               autoscaling.UtilizationMetricType,
  3219  									AverageUtilization: &targetUtilizationVal,
  3220  								},
  3221  							},
  3222  						},
  3223  					},
  3224  				},
  3225  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  3226  					CurrentReplicas: 4,
  3227  					DesiredReplicas: 5,
  3228  					CurrentMetrics: []autoscaling.MetricStatus{
  3229  						{
  3230  							Type: autoscaling.ResourceMetricSourceType,
  3231  							Resource: &autoscaling.ResourceMetricStatus{
  3232  								Name: api.ResourceCPU,
  3233  								Current: autoscaling.MetricValueStatus{
  3234  									AverageUtilization: &currentUtilizationVal,
  3235  									AverageValue:       resource.NewMilliQuantity(40, resource.DecimalSI),
  3236  								},
  3237  							},
  3238  						},
  3239  					},
  3240  				},
  3241  			},
  3242  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  3243  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50%/80%", "2", int64(10), int64(4), "<unknown>"}}},
  3244  		},
  3245  		// container resource source type, targetVal (no current)
  3246  		{
  3247  			hpa: autoscaling.HorizontalPodAutoscaler{
  3248  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  3249  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  3250  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  3251  						Name: "some-rc",
  3252  						Kind: "ReplicationController",
  3253  					},
  3254  					MinReplicas: &minReplicasVal,
  3255  					MaxReplicas: 10,
  3256  					Metrics: []autoscaling.MetricSpec{
  3257  						{
  3258  							Type: autoscaling.ContainerResourceMetricSourceType,
  3259  							ContainerResource: &autoscaling.ContainerResourceMetricSource{
  3260  								Name:      api.ResourceCPU,
  3261  								Container: "application",
  3262  								Target: autoscaling.MetricTarget{
  3263  									Type:         autoscaling.AverageValueMetricType,
  3264  									AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
  3265  								},
  3266  							},
  3267  						},
  3268  					},
  3269  				},
  3270  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  3271  					CurrentReplicas: 4,
  3272  					DesiredReplicas: 5,
  3273  				},
  3274  			},
  3275  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  3276  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/100m", "2", int64(10), int64(4), "<unknown>"}}},
  3277  		},
  3278  		// container resource source type, targetVal
  3279  		{
  3280  			hpa: autoscaling.HorizontalPodAutoscaler{
  3281  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  3282  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  3283  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  3284  						Name: "some-rc",
  3285  						Kind: "ReplicationController",
  3286  					},
  3287  					MinReplicas: &minReplicasVal,
  3288  					MaxReplicas: 10,
  3289  					Metrics: []autoscaling.MetricSpec{
  3290  						{
  3291  							Type: autoscaling.ContainerResourceMetricSourceType,
  3292  							ContainerResource: &autoscaling.ContainerResourceMetricSource{
  3293  								Name: api.ResourceCPU,
  3294  								Target: autoscaling.MetricTarget{
  3295  									Type:         autoscaling.AverageValueMetricType,
  3296  									AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
  3297  								},
  3298  							},
  3299  						},
  3300  					},
  3301  				},
  3302  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  3303  					CurrentReplicas: 4,
  3304  					DesiredReplicas: 5,
  3305  					CurrentMetrics: []autoscaling.MetricStatus{
  3306  						{
  3307  							Type: autoscaling.ContainerResourceMetricSourceType,
  3308  							ContainerResource: &autoscaling.ContainerResourceMetricStatus{
  3309  								Name:      api.ResourceCPU,
  3310  								Container: "application",
  3311  								Current: autoscaling.MetricValueStatus{
  3312  									AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
  3313  								},
  3314  							},
  3315  						},
  3316  					},
  3317  				},
  3318  			},
  3319  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  3320  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m", "2", int64(10), int64(4), "<unknown>"}}},
  3321  		},
  3322  		// container resource source type, targetUtil (no current)
  3323  		{
  3324  			hpa: autoscaling.HorizontalPodAutoscaler{
  3325  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  3326  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  3327  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  3328  						Name: "some-rc",
  3329  						Kind: "ReplicationController",
  3330  					},
  3331  					MinReplicas: &minReplicasVal,
  3332  					MaxReplicas: 10,
  3333  					Metrics: []autoscaling.MetricSpec{
  3334  						{
  3335  							Type: autoscaling.ContainerResourceMetricSourceType,
  3336  							ContainerResource: &autoscaling.ContainerResourceMetricSource{
  3337  								Name:      api.ResourceCPU,
  3338  								Container: "application",
  3339  								Target: autoscaling.MetricTarget{
  3340  									Type:               autoscaling.UtilizationMetricType,
  3341  									AverageUtilization: &targetUtilizationVal,
  3342  								},
  3343  							},
  3344  						},
  3345  					},
  3346  				},
  3347  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  3348  					CurrentReplicas: 4,
  3349  					DesiredReplicas: 5,
  3350  				},
  3351  			},
  3352  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  3353  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "<unknown>/80%", "2", int64(10), int64(4), "<unknown>"}}},
  3354  		},
  3355  		// container resource source type, targetUtil
  3356  		{
  3357  			hpa: autoscaling.HorizontalPodAutoscaler{
  3358  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  3359  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  3360  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  3361  						Name: "some-rc",
  3362  						Kind: "ReplicationController",
  3363  					},
  3364  					MinReplicas: &minReplicasVal,
  3365  					MaxReplicas: 10,
  3366  					Metrics: []autoscaling.MetricSpec{
  3367  						{
  3368  							Type: autoscaling.ContainerResourceMetricSourceType,
  3369  							ContainerResource: &autoscaling.ContainerResourceMetricSource{
  3370  								Name: api.ResourceCPU,
  3371  								Target: autoscaling.MetricTarget{
  3372  									Type:               autoscaling.UtilizationMetricType,
  3373  									AverageUtilization: &targetUtilizationVal,
  3374  								},
  3375  							},
  3376  						},
  3377  					},
  3378  				},
  3379  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  3380  					CurrentReplicas: 4,
  3381  					DesiredReplicas: 5,
  3382  					CurrentMetrics: []autoscaling.MetricStatus{
  3383  						{
  3384  							Type: autoscaling.ContainerResourceMetricSourceType,
  3385  							ContainerResource: &autoscaling.ContainerResourceMetricStatus{
  3386  								Name:      api.ResourceCPU,
  3387  								Container: "application",
  3388  								Current: autoscaling.MetricValueStatus{
  3389  									AverageUtilization: &currentUtilizationVal,
  3390  									AverageValue:       resource.NewMilliQuantity(40, resource.DecimalSI),
  3391  								},
  3392  							},
  3393  						},
  3394  					},
  3395  				},
  3396  			},
  3397  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  3398  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50%/80%", "2", int64(10), int64(4), "<unknown>"}}},
  3399  		},
  3400  		// multiple specs
  3401  		{
  3402  			hpa: autoscaling.HorizontalPodAutoscaler{
  3403  				ObjectMeta: metav1.ObjectMeta{Name: "some-hpa"},
  3404  				Spec: autoscaling.HorizontalPodAutoscalerSpec{
  3405  					ScaleTargetRef: autoscaling.CrossVersionObjectReference{
  3406  						Name: "some-rc",
  3407  						Kind: "ReplicationController",
  3408  					},
  3409  					MinReplicas: &minReplicasVal,
  3410  					MaxReplicas: 10,
  3411  					Metrics: []autoscaling.MetricSpec{
  3412  						{
  3413  							Type: autoscaling.PodsMetricSourceType,
  3414  							Pods: &autoscaling.PodsMetricSource{
  3415  								Metric: autoscaling.MetricIdentifier{
  3416  									Name: "some-pods-metric",
  3417  								},
  3418  								Target: autoscaling.MetricTarget{
  3419  									Type:         autoscaling.AverageValueMetricType,
  3420  									AverageValue: resource.NewMilliQuantity(100, resource.DecimalSI),
  3421  								},
  3422  							},
  3423  						},
  3424  						{
  3425  							Type: autoscaling.ResourceMetricSourceType,
  3426  							Resource: &autoscaling.ResourceMetricSource{
  3427  								Name: api.ResourceCPU,
  3428  								Target: autoscaling.MetricTarget{
  3429  									Type:               autoscaling.UtilizationMetricType,
  3430  									AverageUtilization: &targetUtilizationVal,
  3431  								},
  3432  							},
  3433  						},
  3434  						{
  3435  							Type: autoscaling.PodsMetricSourceType,
  3436  							Pods: &autoscaling.PodsMetricSource{
  3437  								Metric: autoscaling.MetricIdentifier{
  3438  									Name: "other-pods-metric",
  3439  								},
  3440  								Target: autoscaling.MetricTarget{
  3441  									Type:         autoscaling.AverageValueMetricType,
  3442  									AverageValue: resource.NewMilliQuantity(400, resource.DecimalSI),
  3443  								},
  3444  							},
  3445  						},
  3446  					},
  3447  				},
  3448  				Status: autoscaling.HorizontalPodAutoscalerStatus{
  3449  					CurrentReplicas: 4,
  3450  					DesiredReplicas: 5,
  3451  					CurrentMetrics: []autoscaling.MetricStatus{
  3452  						{
  3453  							Type: autoscaling.PodsMetricSourceType,
  3454  							Pods: &autoscaling.PodsMetricStatus{
  3455  								Metric: autoscaling.MetricIdentifier{
  3456  									Name: "some-pods-metric",
  3457  								},
  3458  								Current: autoscaling.MetricValueStatus{
  3459  									AverageValue: resource.NewMilliQuantity(50, resource.DecimalSI),
  3460  								},
  3461  							},
  3462  						},
  3463  						{
  3464  							Type: autoscaling.ResourceMetricSourceType,
  3465  							Resource: &autoscaling.ResourceMetricStatus{
  3466  								Name: api.ResourceCPU,
  3467  								Current: autoscaling.MetricValueStatus{
  3468  									AverageUtilization: &currentUtilizationVal,
  3469  									AverageValue:       resource.NewMilliQuantity(40, resource.DecimalSI),
  3470  								},
  3471  							},
  3472  						},
  3473  					},
  3474  				},
  3475  			},
  3476  			// Columns: Name, Reference, Targets, MinPods, MaxPods, Replicas, Age
  3477  			expected: []metav1.TableRow{{Cells: []interface{}{"some-hpa", "ReplicationController/some-rc", "50m/100m, 50%/80% + 1 more...", "2", int64(10), int64(4), "<unknown>"}}},
  3478  		},
  3479  	}
  3480  
  3481  	for i, test := range tests {
  3482  		rows, err := printHorizontalPodAutoscaler(&test.hpa, printers.GenerateOptions{})
  3483  		if err != nil {
  3484  			t.Fatal(err)
  3485  		}
  3486  		for i := range rows {
  3487  			rows[i].Object.Object = nil
  3488  		}
  3489  		if !reflect.DeepEqual(test.expected, rows) {
  3490  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  3491  		}
  3492  	}
  3493  }
  3494  
  3495  func TestPrintService(t *testing.T) {
  3496  	singleExternalIP := []string{"80.11.12.10"}
  3497  	mulExternalIP := []string{"80.11.12.10", "80.11.12.11"}
  3498  	tests := []struct {
  3499  		service  api.Service
  3500  		options  printers.GenerateOptions
  3501  		expected []metav1.TableRow
  3502  	}{
  3503  		{
  3504  			// Test name, cluster ip, port with protocol
  3505  			service: api.Service{
  3506  				ObjectMeta: metav1.ObjectMeta{Name: "test1"},
  3507  				Spec: api.ServiceSpec{
  3508  					Type: api.ServiceTypeClusterIP,
  3509  					Ports: []api.ServicePort{
  3510  						{
  3511  							Protocol: "tcp",
  3512  							Port:     2233,
  3513  						},
  3514  					},
  3515  					ClusterIPs: []string{"10.9.8.7"},
  3516  					Selector:   map[string]string{"foo": "bar"}, // Does NOT get printed.
  3517  				},
  3518  			},
  3519  			options: printers.GenerateOptions{},
  3520  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  3521  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", "ClusterIP", "10.9.8.7", "<none>", "2233/tcp", "<unknown>"}}},
  3522  		},
  3523  		{
  3524  			// Test generate options: Wide includes selectors.
  3525  			service: api.Service{
  3526  				ObjectMeta: metav1.ObjectMeta{Name: "test1"},
  3527  				Spec: api.ServiceSpec{
  3528  					Type: api.ServiceTypeClusterIP,
  3529  					Ports: []api.ServicePort{
  3530  						{
  3531  							Protocol: "tcp",
  3532  							Port:     2233,
  3533  						},
  3534  					},
  3535  					ClusterIPs: []string{"10.9.8.7"},
  3536  					Selector:   map[string]string{"foo": "bar"},
  3537  				},
  3538  			},
  3539  			options: printers.GenerateOptions{Wide: true},
  3540  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age, Selector
  3541  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", "ClusterIP", "10.9.8.7", "<none>", "2233/tcp", "<unknown>", "foo=bar"}}},
  3542  		},
  3543  		{
  3544  			// Test NodePort service
  3545  			service: api.Service{
  3546  				ObjectMeta: metav1.ObjectMeta{Name: "test2"},
  3547  				Spec: api.ServiceSpec{
  3548  					Type: api.ServiceTypeNodePort,
  3549  					Ports: []api.ServicePort{
  3550  						{
  3551  							Protocol: "tcp",
  3552  							Port:     8888,
  3553  							NodePort: 9999,
  3554  						},
  3555  					},
  3556  					ClusterIPs: []string{"10.9.8.7"},
  3557  				},
  3558  			},
  3559  			options: printers.GenerateOptions{},
  3560  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  3561  			expected: []metav1.TableRow{{Cells: []interface{}{"test2", "NodePort", "10.9.8.7", "<none>", "8888:9999/tcp", "<unknown>"}}},
  3562  		},
  3563  		{
  3564  			// Test LoadBalancer service
  3565  			service: api.Service{
  3566  				ObjectMeta: metav1.ObjectMeta{Name: "test3"},
  3567  				Spec: api.ServiceSpec{
  3568  					Type: api.ServiceTypeLoadBalancer,
  3569  					Ports: []api.ServicePort{
  3570  						{
  3571  							Protocol: "tcp",
  3572  							Port:     8888,
  3573  						},
  3574  					},
  3575  					ClusterIPs: []string{"10.9.8.7"},
  3576  				},
  3577  			},
  3578  			options: printers.GenerateOptions{},
  3579  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  3580  			expected: []metav1.TableRow{{Cells: []interface{}{"test3", "LoadBalancer", "10.9.8.7", "<pending>", "8888/tcp", "<unknown>"}}},
  3581  		},
  3582  		{
  3583  			// Test LoadBalancer service with single ExternalIP and no LoadBalancerStatus
  3584  			service: api.Service{
  3585  				ObjectMeta: metav1.ObjectMeta{Name: "test4"},
  3586  				Spec: api.ServiceSpec{
  3587  					Type: api.ServiceTypeLoadBalancer,
  3588  					Ports: []api.ServicePort{
  3589  						{
  3590  							Protocol: "tcp",
  3591  							Port:     8888,
  3592  						},
  3593  					},
  3594  					ClusterIPs:  []string{"10.9.8.7"},
  3595  					ExternalIPs: singleExternalIP,
  3596  				},
  3597  			},
  3598  			options: printers.GenerateOptions{},
  3599  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  3600  			expected: []metav1.TableRow{{Cells: []interface{}{"test4", "LoadBalancer", "10.9.8.7", "80.11.12.10", "8888/tcp", "<unknown>"}}},
  3601  		},
  3602  		{
  3603  			// Test LoadBalancer service with single ExternalIP
  3604  			service: api.Service{
  3605  				ObjectMeta: metav1.ObjectMeta{Name: "test5"},
  3606  				Spec: api.ServiceSpec{
  3607  					Type: api.ServiceTypeLoadBalancer,
  3608  					Ports: []api.ServicePort{
  3609  						{
  3610  							Protocol: "tcp",
  3611  							Port:     8888,
  3612  						},
  3613  					},
  3614  					ClusterIPs:  []string{"10.9.8.7"},
  3615  					ExternalIPs: singleExternalIP,
  3616  				},
  3617  				Status: api.ServiceStatus{
  3618  					LoadBalancer: api.LoadBalancerStatus{
  3619  						Ingress: []api.LoadBalancerIngress{
  3620  							{
  3621  								IP:       "3.4.5.6",
  3622  								Hostname: "test.cluster.com",
  3623  							},
  3624  						},
  3625  					},
  3626  				},
  3627  			},
  3628  			options: printers.GenerateOptions{},
  3629  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  3630  			expected: []metav1.TableRow{{Cells: []interface{}{"test5", "LoadBalancer", "10.9.8.7", "3.4.5.6,80.11.12.10", "8888/tcp", "<unknown>"}}},
  3631  		},
  3632  		{
  3633  			// Test LoadBalancer service with mul ExternalIPs
  3634  			service: api.Service{
  3635  				ObjectMeta: metav1.ObjectMeta{Name: "test6"},
  3636  				Spec: api.ServiceSpec{
  3637  					Type: api.ServiceTypeLoadBalancer,
  3638  					Ports: []api.ServicePort{
  3639  						{
  3640  							Protocol: "tcp",
  3641  							Port:     8888,
  3642  						},
  3643  					},
  3644  					ClusterIPs:  []string{"10.9.8.7"},
  3645  					ExternalIPs: mulExternalIP,
  3646  				},
  3647  				Status: api.ServiceStatus{
  3648  					LoadBalancer: api.LoadBalancerStatus{
  3649  						Ingress: []api.LoadBalancerIngress{
  3650  							{
  3651  								IP:       "2.3.4.5",
  3652  								Hostname: "test.cluster.local",
  3653  							},
  3654  							{
  3655  								IP:       "3.4.5.6",
  3656  								Hostname: "test.cluster.com",
  3657  							},
  3658  						},
  3659  					},
  3660  				},
  3661  			},
  3662  			options: printers.GenerateOptions{},
  3663  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  3664  			expected: []metav1.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", "<unknown>"}}},
  3665  		},
  3666  		{
  3667  			// Test ExternalName service
  3668  			service: api.Service{
  3669  				ObjectMeta: metav1.ObjectMeta{Name: "test7"},
  3670  				Spec: api.ServiceSpec{
  3671  					Type:         api.ServiceTypeExternalName,
  3672  					ExternalName: "my.database.example.com",
  3673  				},
  3674  			},
  3675  			options: printers.GenerateOptions{},
  3676  			// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  3677  			expected: []metav1.TableRow{{Cells: []interface{}{"test7", "ExternalName", "<none>", "my.database.example.com", "<none>", "<unknown>"}}},
  3678  		},
  3679  	}
  3680  
  3681  	for i, test := range tests {
  3682  		rows, err := printService(&test.service, test.options)
  3683  		if err != nil {
  3684  			t.Fatal(err)
  3685  		}
  3686  		for i := range rows {
  3687  			rows[i].Object.Object = nil
  3688  		}
  3689  		if !reflect.DeepEqual(test.expected, rows) {
  3690  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  3691  		}
  3692  	}
  3693  }
  3694  
  3695  func TestPrintServiceList(t *testing.T) {
  3696  	serviceList := api.ServiceList{
  3697  		Items: []api.Service{
  3698  			{
  3699  				ObjectMeta: metav1.ObjectMeta{Name: "service1"},
  3700  				Spec: api.ServiceSpec{
  3701  					Type: api.ServiceTypeClusterIP,
  3702  					Ports: []api.ServicePort{
  3703  						{
  3704  							Protocol: "tcp",
  3705  							Port:     2233,
  3706  						},
  3707  					},
  3708  					ClusterIPs: []string{"10.9.8.7"},
  3709  				},
  3710  			},
  3711  			{
  3712  				ObjectMeta: metav1.ObjectMeta{Name: "service2"},
  3713  				Spec: api.ServiceSpec{
  3714  					Type: api.ServiceTypeNodePort,
  3715  					Ports: []api.ServicePort{
  3716  						{
  3717  							Protocol: "udp",
  3718  							Port:     5566,
  3719  						},
  3720  					},
  3721  					ClusterIPs: []string{"1.2.3.4"},
  3722  				},
  3723  			},
  3724  		},
  3725  	}
  3726  
  3727  	// Columns: Name, Type, Cluster-IP, External-IP, Port(s), Age
  3728  	expectedRows := []metav1.TableRow{
  3729  		{Cells: []interface{}{"service1", "ClusterIP", "10.9.8.7", "<none>", "2233/tcp", "<unknown>"}},
  3730  		{Cells: []interface{}{"service2", "NodePort", "1.2.3.4", "<none>", "5566/udp", "<unknown>"}},
  3731  	}
  3732  
  3733  	rows, err := printServiceList(&serviceList, printers.GenerateOptions{})
  3734  	if err != nil {
  3735  		t.Fatalf("Error printing service list: %#v", err)
  3736  	}
  3737  	for i := range rows {
  3738  		rows[i].Object.Object = nil
  3739  	}
  3740  	if !reflect.DeepEqual(expectedRows, rows) {
  3741  		t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows))
  3742  	}
  3743  }
  3744  
  3745  func TestPrintPodDisruptionBudget(t *testing.T) {
  3746  	minAvailable := intstr.FromInt32(22)
  3747  	maxUnavailable := intstr.FromInt32(11)
  3748  	tests := []struct {
  3749  		pdb      policy.PodDisruptionBudget
  3750  		expected []metav1.TableRow
  3751  	}{
  3752  		// Min Available set, no Max Available.
  3753  		{
  3754  			pdb: policy.PodDisruptionBudget{
  3755  				ObjectMeta: metav1.ObjectMeta{
  3756  					Namespace:         "ns1",
  3757  					Name:              "pdb1",
  3758  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  3759  				},
  3760  				Spec: policy.PodDisruptionBudgetSpec{
  3761  					MinAvailable: &minAvailable,
  3762  				},
  3763  				Status: policy.PodDisruptionBudgetStatus{
  3764  					DisruptionsAllowed: 5,
  3765  				},
  3766  			},
  3767  			// Columns: Name, Min Available, Max Available, Allowed Disruptions, Age
  3768  			expected: []metav1.TableRow{{Cells: []interface{}{"pdb1", "22", "N/A", int64(5), "0s"}}},
  3769  		},
  3770  		// Max Available set, no Min Available.
  3771  		{
  3772  			pdb: policy.PodDisruptionBudget{
  3773  				ObjectMeta: metav1.ObjectMeta{
  3774  					Namespace:         "ns2",
  3775  					Name:              "pdb2",
  3776  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  3777  				},
  3778  				Spec: policy.PodDisruptionBudgetSpec{
  3779  					MaxUnavailable: &maxUnavailable,
  3780  				},
  3781  				Status: policy.PodDisruptionBudgetStatus{
  3782  					DisruptionsAllowed: 5,
  3783  				},
  3784  			},
  3785  			// Columns: Name, Min Available, Max Available, Allowed Disruptions, Age
  3786  			expected: []metav1.TableRow{{Cells: []interface{}{"pdb2", "N/A", "11", int64(5), "0s"}}},
  3787  		}}
  3788  
  3789  	for i, test := range tests {
  3790  		rows, err := printPodDisruptionBudget(&test.pdb, printers.GenerateOptions{})
  3791  		if err != nil {
  3792  			t.Fatal(err)
  3793  		}
  3794  		for i := range rows {
  3795  			rows[i].Object.Object = nil
  3796  		}
  3797  		if !reflect.DeepEqual(test.expected, rows) {
  3798  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  3799  		}
  3800  	}
  3801  }
  3802  
  3803  func TestPrintPodDisruptionBudgetList(t *testing.T) {
  3804  	minAvailable := intstr.FromInt32(22)
  3805  	maxUnavailable := intstr.FromInt32(11)
  3806  
  3807  	pdbList := policy.PodDisruptionBudgetList{
  3808  		Items: []policy.PodDisruptionBudget{
  3809  			{
  3810  				ObjectMeta: metav1.ObjectMeta{
  3811  					Namespace:         "ns1",
  3812  					Name:              "pdb1",
  3813  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  3814  				},
  3815  				Spec: policy.PodDisruptionBudgetSpec{
  3816  					MaxUnavailable: &maxUnavailable,
  3817  				},
  3818  				Status: policy.PodDisruptionBudgetStatus{
  3819  					DisruptionsAllowed: 5,
  3820  				},
  3821  			},
  3822  			{
  3823  				ObjectMeta: metav1.ObjectMeta{
  3824  					Namespace:         "ns2",
  3825  					Name:              "pdb2",
  3826  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  3827  				},
  3828  				Spec: policy.PodDisruptionBudgetSpec{
  3829  					MinAvailable: &minAvailable,
  3830  				},
  3831  				Status: policy.PodDisruptionBudgetStatus{
  3832  					DisruptionsAllowed: 3,
  3833  				},
  3834  			},
  3835  		},
  3836  	}
  3837  
  3838  	// Columns: Name, Min Available, Max Available, Allowed Disruptions, Age
  3839  	expectedRows := []metav1.TableRow{
  3840  		{Cells: []interface{}{"pdb1", "N/A", "11", int64(5), "0s"}},
  3841  		{Cells: []interface{}{"pdb2", "22", "N/A", int64(3), "0s"}},
  3842  	}
  3843  
  3844  	rows, err := printPodDisruptionBudgetList(&pdbList, printers.GenerateOptions{})
  3845  	if err != nil {
  3846  		t.Fatalf("Error printing pod template list: %#v", err)
  3847  	}
  3848  	for i := range rows {
  3849  		rows[i].Object.Object = nil
  3850  	}
  3851  	if !reflect.DeepEqual(expectedRows, rows) {
  3852  		t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows))
  3853  	}
  3854  }
  3855  
  3856  func TestPrintControllerRevision(t *testing.T) {
  3857  	tests := []struct {
  3858  		history  apps.ControllerRevision
  3859  		expected []metav1.TableRow
  3860  	}{
  3861  		{
  3862  			history: apps.ControllerRevision{
  3863  				ObjectMeta: metav1.ObjectMeta{
  3864  					Name:              "test1",
  3865  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  3866  					OwnerReferences: []metav1.OwnerReference{
  3867  						{
  3868  							Controller: boolP(true),
  3869  							APIVersion: "apps/v1",
  3870  							Kind:       "DaemonSet",
  3871  							Name:       "foo",
  3872  						},
  3873  					},
  3874  				},
  3875  				Revision: 1,
  3876  			},
  3877  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", "daemonset.apps/foo", int64(1), "0s"}}},
  3878  		},
  3879  		{
  3880  			history: apps.ControllerRevision{
  3881  				ObjectMeta: metav1.ObjectMeta{
  3882  					Name:              "test2",
  3883  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  3884  					OwnerReferences: []metav1.OwnerReference{
  3885  						{
  3886  							Controller: boolP(false),
  3887  							Kind:       "ABC",
  3888  							Name:       "foo",
  3889  						},
  3890  					},
  3891  				},
  3892  				Revision: 2,
  3893  			},
  3894  			expected: []metav1.TableRow{{Cells: []interface{}{"test2", "<none>", int64(2), "0s"}}},
  3895  		},
  3896  		{
  3897  			history: apps.ControllerRevision{
  3898  				ObjectMeta: metav1.ObjectMeta{
  3899  					Name:              "test3",
  3900  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  3901  					OwnerReferences:   []metav1.OwnerReference{},
  3902  				},
  3903  				Revision: 3,
  3904  			},
  3905  			expected: []metav1.TableRow{{Cells: []interface{}{"test3", "<none>", int64(3), "0s"}}},
  3906  		},
  3907  		{
  3908  			history: apps.ControllerRevision{
  3909  				ObjectMeta: metav1.ObjectMeta{
  3910  					Name:              "test4",
  3911  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  3912  					OwnerReferences:   nil,
  3913  				},
  3914  				Revision: 4,
  3915  			},
  3916  			expected: []metav1.TableRow{{Cells: []interface{}{"test4", "<none>", int64(4), "0s"}}},
  3917  		},
  3918  	}
  3919  
  3920  	for i, test := range tests {
  3921  		rows, err := printControllerRevision(&test.history, printers.GenerateOptions{})
  3922  		if err != nil {
  3923  			t.Fatal(err)
  3924  		}
  3925  		for i := range rows {
  3926  			rows[i].Object.Object = nil
  3927  		}
  3928  		if !reflect.DeepEqual(test.expected, rows) {
  3929  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  3930  		}
  3931  	}
  3932  }
  3933  
  3934  func boolP(b bool) *bool {
  3935  	return &b
  3936  }
  3937  
  3938  func TestPrintConfigMap(t *testing.T) {
  3939  	tests := []struct {
  3940  		configMap api.ConfigMap
  3941  		expected  []metav1.TableRow
  3942  	}{
  3943  		// Basic config map with no data.
  3944  		{
  3945  			configMap: api.ConfigMap{
  3946  				ObjectMeta: metav1.ObjectMeta{
  3947  					Name:              "configmap1",
  3948  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  3949  				},
  3950  			},
  3951  			// Columns: Name, Data, Age
  3952  			expected: []metav1.TableRow{{Cells: []interface{}{"configmap1", int64(0), "0s"}}},
  3953  		},
  3954  		// Basic config map with one data entry
  3955  		{
  3956  			configMap: api.ConfigMap{
  3957  				ObjectMeta: metav1.ObjectMeta{
  3958  					Name:              "configmap2",
  3959  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  3960  				},
  3961  				Data: map[string]string{
  3962  					"foo": "bar",
  3963  				},
  3964  			},
  3965  			// Columns: Name, (Num) Data, Age
  3966  			expected: []metav1.TableRow{{Cells: []interface{}{"configmap2", int64(1), "0s"}}},
  3967  		},
  3968  		// Basic config map with one data and one binary data entry.
  3969  		{
  3970  			configMap: api.ConfigMap{
  3971  				ObjectMeta: metav1.ObjectMeta{
  3972  					Name:              "configmap3",
  3973  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  3974  				},
  3975  				Data: map[string]string{
  3976  					"foo": "bar",
  3977  				},
  3978  				BinaryData: map[string][]byte{
  3979  					"bin": []byte("binary data"),
  3980  				},
  3981  			},
  3982  			// Columns: Name, (Num) Data, Age
  3983  			expected: []metav1.TableRow{{Cells: []interface{}{"configmap3", int64(2), "0s"}}},
  3984  		},
  3985  	}
  3986  
  3987  	for i, test := range tests {
  3988  		rows, err := printConfigMap(&test.configMap, printers.GenerateOptions{})
  3989  		if err != nil {
  3990  			t.Fatal(err)
  3991  		}
  3992  		for i := range rows {
  3993  			rows[i].Object.Object = nil
  3994  		}
  3995  		if !reflect.DeepEqual(test.expected, rows) {
  3996  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  3997  		}
  3998  	}
  3999  }
  4000  
  4001  func TestPrintNetworkPolicy(t *testing.T) {
  4002  	tests := []struct {
  4003  		policy   networking.NetworkPolicy
  4004  		expected []metav1.TableRow
  4005  	}{
  4006  		// Basic network policy with empty spec.
  4007  		{
  4008  			policy: networking.NetworkPolicy{
  4009  				ObjectMeta: metav1.ObjectMeta{
  4010  					Name:              "policy1",
  4011  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4012  				},
  4013  				Spec: networking.NetworkPolicySpec{},
  4014  			},
  4015  			// Columns: Name, Pod-Selector, Age
  4016  			expected: []metav1.TableRow{{Cells: []interface{}{"policy1", "<none>", "0s"}}},
  4017  		},
  4018  		// Basic network policy with pod selector.
  4019  		{
  4020  			policy: networking.NetworkPolicy{
  4021  				ObjectMeta: metav1.ObjectMeta{
  4022  					Name:              "policy2",
  4023  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4024  				},
  4025  				Spec: networking.NetworkPolicySpec{
  4026  					PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  4027  				},
  4028  			},
  4029  			// Columns: Name, Pod-Selector, Age
  4030  			expected: []metav1.TableRow{{Cells: []interface{}{"policy2", "foo=bar", "0s"}}},
  4031  		},
  4032  	}
  4033  
  4034  	for i, test := range tests {
  4035  		rows, err := printNetworkPolicy(&test.policy, printers.GenerateOptions{})
  4036  		if err != nil {
  4037  			t.Fatal(err)
  4038  		}
  4039  		for i := range rows {
  4040  			rows[i].Object.Object = nil
  4041  		}
  4042  		if !reflect.DeepEqual(test.expected, rows) {
  4043  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  4044  		}
  4045  	}
  4046  }
  4047  
  4048  func TestPrintRoleBinding(t *testing.T) {
  4049  	tests := []struct {
  4050  		binding  rbac.RoleBinding
  4051  		options  printers.GenerateOptions
  4052  		expected []metav1.TableRow
  4053  	}{
  4054  		// Basic role binding
  4055  		{
  4056  			binding: rbac.RoleBinding{
  4057  				ObjectMeta: metav1.ObjectMeta{
  4058  					Name:              "binding1",
  4059  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4060  				},
  4061  				Subjects: []rbac.Subject{
  4062  					{
  4063  						Kind: "User",
  4064  						Name: "system:kube-controller-manager",
  4065  					},
  4066  				},
  4067  				RoleRef: rbac.RoleRef{
  4068  					Kind: "Role",
  4069  					Name: "extension-apiserver-authentication-reader",
  4070  				},
  4071  			},
  4072  			options: printers.GenerateOptions{},
  4073  			// Columns: Name, Age
  4074  			expected: []metav1.TableRow{{Cells: []interface{}{"binding1", "Role/extension-apiserver-authentication-reader", "0s"}}},
  4075  		},
  4076  		// Generate options=Wide; print subject and roles.
  4077  		{
  4078  			binding: rbac.RoleBinding{
  4079  				ObjectMeta: metav1.ObjectMeta{
  4080  					Name:              "binding2",
  4081  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4082  				},
  4083  				Subjects: []rbac.Subject{
  4084  					{
  4085  						Kind: "User",
  4086  						Name: "user-name",
  4087  					},
  4088  					{
  4089  						Kind: "Group",
  4090  						Name: "group-name",
  4091  					},
  4092  					{
  4093  						Kind:      "ServiceAccount",
  4094  						Name:      "service-account-name",
  4095  						Namespace: "service-account-namespace",
  4096  					},
  4097  				},
  4098  				RoleRef: rbac.RoleRef{
  4099  					Kind: "Role",
  4100  					Name: "role-name",
  4101  				},
  4102  			},
  4103  			options: printers.GenerateOptions{Wide: true},
  4104  			// Columns: Name, Age, Role, Users, Groups, ServiceAccounts
  4105  			expected: []metav1.TableRow{{Cells: []interface{}{"binding2", "Role/role-name", "0s", "user-name", "group-name", "service-account-namespace/service-account-name"}}},
  4106  		},
  4107  	}
  4108  
  4109  	for i, test := range tests {
  4110  		rows, err := printRoleBinding(&test.binding, test.options)
  4111  		if err != nil {
  4112  			t.Fatal(err)
  4113  		}
  4114  		for i := range rows {
  4115  			rows[i].Object.Object = nil
  4116  		}
  4117  		if !reflect.DeepEqual(test.expected, rows) {
  4118  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  4119  		}
  4120  	}
  4121  }
  4122  
  4123  func TestPrintClusterRoleBinding(t *testing.T) {
  4124  	tests := []struct {
  4125  		binding  rbac.ClusterRoleBinding
  4126  		options  printers.GenerateOptions
  4127  		expected []metav1.TableRow
  4128  	}{
  4129  		// Basic cluster role binding
  4130  		{
  4131  			binding: rbac.ClusterRoleBinding{
  4132  				ObjectMeta: metav1.ObjectMeta{
  4133  					Name:              "binding1",
  4134  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4135  				},
  4136  				Subjects: []rbac.Subject{
  4137  					{
  4138  						Kind: "User",
  4139  						Name: "system:kube-controller-manager",
  4140  					},
  4141  				},
  4142  				RoleRef: rbac.RoleRef{
  4143  					Kind: "Role",
  4144  					Name: "extension-apiserver-authentication-reader",
  4145  				},
  4146  			},
  4147  			options: printers.GenerateOptions{},
  4148  			// Columns: Name, Age
  4149  			expected: []metav1.TableRow{{Cells: []interface{}{"binding1", "Role/extension-apiserver-authentication-reader", "0s"}}},
  4150  		},
  4151  		// Generate options=Wide; print subject and roles.
  4152  		{
  4153  			binding: rbac.ClusterRoleBinding{
  4154  				ObjectMeta: metav1.ObjectMeta{
  4155  					Name:              "binding2",
  4156  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4157  				},
  4158  				Subjects: []rbac.Subject{
  4159  					{
  4160  						Kind: "User",
  4161  						Name: "user-name",
  4162  					},
  4163  					{
  4164  						Kind: "Group",
  4165  						Name: "group-name",
  4166  					},
  4167  					{
  4168  						Kind:      "ServiceAccount",
  4169  						Name:      "service-account-name",
  4170  						Namespace: "service-account-namespace",
  4171  					},
  4172  				},
  4173  				RoleRef: rbac.RoleRef{
  4174  					Kind: "Role",
  4175  					Name: "role-name",
  4176  				},
  4177  			},
  4178  			options: printers.GenerateOptions{Wide: true},
  4179  			// Columns: Name, Age, Role, Users, Groups, ServiceAccounts
  4180  			expected: []metav1.TableRow{{Cells: []interface{}{"binding2", "Role/role-name", "0s", "user-name", "group-name", "service-account-namespace/service-account-name"}}},
  4181  		},
  4182  	}
  4183  
  4184  	for i, test := range tests {
  4185  		rows, err := printClusterRoleBinding(&test.binding, test.options)
  4186  		if err != nil {
  4187  			t.Fatal(err)
  4188  		}
  4189  		for i := range rows {
  4190  			rows[i].Object.Object = nil
  4191  		}
  4192  		if !reflect.DeepEqual(test.expected, rows) {
  4193  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  4194  		}
  4195  	}
  4196  }
  4197  func TestPrintCertificateSigningRequest(t *testing.T) {
  4198  	tests := []struct {
  4199  		csr      certificates.CertificateSigningRequest
  4200  		expected []metav1.TableRow
  4201  	}{
  4202  		// Basic CSR with no spec or status; defaults to status: Pending.
  4203  		{
  4204  			csr: certificates.CertificateSigningRequest{
  4205  				ObjectMeta: metav1.ObjectMeta{
  4206  					Name:              "csr1",
  4207  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4208  				},
  4209  				Spec:   certificates.CertificateSigningRequestSpec{},
  4210  				Status: certificates.CertificateSigningRequestStatus{},
  4211  			},
  4212  			// Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition
  4213  			expected: []metav1.TableRow{{Cells: []interface{}{"csr1", "0s", "<none>", "", "<none>", "Pending"}}},
  4214  		},
  4215  		// Basic CSR with Spec and Status=Approved.
  4216  		{
  4217  			csr: certificates.CertificateSigningRequest{
  4218  				ObjectMeta: metav1.ObjectMeta{
  4219  					Name:              "csr2",
  4220  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4221  				},
  4222  				Spec: certificates.CertificateSigningRequestSpec{
  4223  					Username: "CSR Requestor",
  4224  				},
  4225  				Status: certificates.CertificateSigningRequestStatus{
  4226  					Conditions: []certificates.CertificateSigningRequestCondition{
  4227  						{
  4228  							Type: certificates.CertificateApproved,
  4229  						},
  4230  					},
  4231  				},
  4232  			},
  4233  			// Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition
  4234  			expected: []metav1.TableRow{{Cells: []interface{}{"csr2", "0s", "<none>", "CSR Requestor", "<none>", "Approved"}}},
  4235  		},
  4236  		// Basic CSR with Spec and SignerName set
  4237  		{
  4238  			csr: certificates.CertificateSigningRequest{
  4239  				ObjectMeta: metav1.ObjectMeta{
  4240  					Name:              "csr2",
  4241  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4242  				},
  4243  				Spec: certificates.CertificateSigningRequestSpec{
  4244  					Username:   "CSR Requestor",
  4245  					SignerName: "example.com/test-signer",
  4246  				},
  4247  				Status: certificates.CertificateSigningRequestStatus{
  4248  					Conditions: []certificates.CertificateSigningRequestCondition{
  4249  						{
  4250  							Type: certificates.CertificateApproved,
  4251  						},
  4252  					},
  4253  				},
  4254  			},
  4255  			// Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition
  4256  			expected: []metav1.TableRow{{Cells: []interface{}{"csr2", "0s", "example.com/test-signer", "CSR Requestor", "<none>", "Approved"}}},
  4257  		},
  4258  		// Basic CSR with Spec, SignerName and ExpirationSeconds set
  4259  		{
  4260  			csr: certificates.CertificateSigningRequest{
  4261  				ObjectMeta: metav1.ObjectMeta{
  4262  					Name:              "csr2",
  4263  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4264  				},
  4265  				Spec: certificates.CertificateSigningRequestSpec{
  4266  					Username:          "CSR Requestor",
  4267  					SignerName:        "example.com/test-signer",
  4268  					ExpirationSeconds: csr.DurationToExpirationSeconds(7*24*time.Hour + time.Hour), // a little bit more than a week
  4269  				},
  4270  				Status: certificates.CertificateSigningRequestStatus{
  4271  					Conditions: []certificates.CertificateSigningRequestCondition{
  4272  						{
  4273  							Type: certificates.CertificateApproved,
  4274  						},
  4275  					},
  4276  				},
  4277  			},
  4278  			// Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition
  4279  			expected: []metav1.TableRow{{Cells: []interface{}{"csr2", "0s", "example.com/test-signer", "CSR Requestor", "7d1h", "Approved"}}},
  4280  		},
  4281  		// Basic CSR with Spec and Status=Approved; certificate issued.
  4282  		{
  4283  			csr: certificates.CertificateSigningRequest{
  4284  				ObjectMeta: metav1.ObjectMeta{
  4285  					Name:              "csr2",
  4286  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4287  				},
  4288  				Spec: certificates.CertificateSigningRequestSpec{
  4289  					Username: "CSR Requestor",
  4290  				},
  4291  				Status: certificates.CertificateSigningRequestStatus{
  4292  					Conditions: []certificates.CertificateSigningRequestCondition{
  4293  						{
  4294  							Type: certificates.CertificateApproved,
  4295  						},
  4296  					},
  4297  					Certificate: []byte("cert data"),
  4298  				},
  4299  			},
  4300  			// Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition
  4301  			expected: []metav1.TableRow{{Cells: []interface{}{"csr2", "0s", "<none>", "CSR Requestor", "<none>", "Approved,Issued"}}},
  4302  		},
  4303  		// Basic CSR with Spec and Status=Denied.
  4304  		{
  4305  			csr: certificates.CertificateSigningRequest{
  4306  				ObjectMeta: metav1.ObjectMeta{
  4307  					Name:              "csr3",
  4308  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4309  				},
  4310  				Spec: certificates.CertificateSigningRequestSpec{
  4311  					Username: "CSR Requestor",
  4312  				},
  4313  				Status: certificates.CertificateSigningRequestStatus{
  4314  					Conditions: []certificates.CertificateSigningRequestCondition{
  4315  						{
  4316  							Type: certificates.CertificateDenied,
  4317  						},
  4318  					},
  4319  				},
  4320  			},
  4321  			// Columns: Name, Age, SignerName, Requestor, RequestedDuration, Condition
  4322  			expected: []metav1.TableRow{{Cells: []interface{}{"csr3", "0s", "<none>", "CSR Requestor", "<none>", "Denied"}}},
  4323  		},
  4324  	}
  4325  
  4326  	for i, test := range tests {
  4327  		rows, err := printCertificateSigningRequest(&test.csr, printers.GenerateOptions{})
  4328  		if err != nil {
  4329  			t.Fatal(err)
  4330  		}
  4331  		for i := range rows {
  4332  			rows[i].Object.Object = nil
  4333  		}
  4334  		if !reflect.DeepEqual(test.expected, rows) {
  4335  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  4336  		}
  4337  	}
  4338  }
  4339  
  4340  func TestPrintReplicationController(t *testing.T) {
  4341  	tests := []struct {
  4342  		rc       api.ReplicationController
  4343  		options  printers.GenerateOptions
  4344  		expected []metav1.TableRow
  4345  	}{
  4346  		// Basic print replication controller without replicas or status.
  4347  		{
  4348  			rc: api.ReplicationController{
  4349  				ObjectMeta: metav1.ObjectMeta{
  4350  					Name:      "rc1",
  4351  					Namespace: "test-namespace",
  4352  				},
  4353  				Spec: api.ReplicationControllerSpec{
  4354  					Selector: map[string]string{"a": "b"},
  4355  					Template: &api.PodTemplateSpec{
  4356  						ObjectMeta: metav1.ObjectMeta{
  4357  							Labels: map[string]string{"a": "b"},
  4358  						},
  4359  						Spec: api.PodSpec{
  4360  							Containers: []api.Container{
  4361  								{
  4362  									Name:                     "test",
  4363  									Image:                    "test_image",
  4364  									ImagePullPolicy:          api.PullIfNotPresent,
  4365  									TerminationMessagePolicy: api.TerminationMessageReadFile,
  4366  								},
  4367  							},
  4368  							RestartPolicy: api.RestartPolicyAlways,
  4369  							DNSPolicy:     api.DNSClusterFirst,
  4370  						},
  4371  					},
  4372  				},
  4373  			},
  4374  			options: printers.GenerateOptions{},
  4375  			// Columns: Name, Desired, Current, Ready, Age
  4376  			expected: []metav1.TableRow{{Cells: []interface{}{"rc1", int64(0), int64(0), int64(0), "<unknown>"}}},
  4377  		},
  4378  		// Basic print replication controller with replicas; does not print containers or labels
  4379  		{
  4380  			rc: api.ReplicationController{
  4381  				ObjectMeta: metav1.ObjectMeta{
  4382  					Name:      "rc1",
  4383  					Namespace: "test-namespace",
  4384  				},
  4385  				Spec: api.ReplicationControllerSpec{
  4386  					Replicas: 5,
  4387  					Selector: map[string]string{"a": "b"},
  4388  					Template: &api.PodTemplateSpec{
  4389  						ObjectMeta: metav1.ObjectMeta{
  4390  							Labels: map[string]string{"a": "b"},
  4391  						},
  4392  						Spec: api.PodSpec{
  4393  							Containers: []api.Container{
  4394  								{
  4395  									Name:                     "test",
  4396  									Image:                    "test_image",
  4397  									ImagePullPolicy:          api.PullIfNotPresent,
  4398  									TerminationMessagePolicy: api.TerminationMessageReadFile,
  4399  								},
  4400  							},
  4401  							RestartPolicy: api.RestartPolicyAlways,
  4402  							DNSPolicy:     api.DNSClusterFirst,
  4403  						},
  4404  					},
  4405  				},
  4406  				Status: api.ReplicationControllerStatus{
  4407  					Replicas:      3,
  4408  					ReadyReplicas: 1,
  4409  				},
  4410  			},
  4411  			options: printers.GenerateOptions{},
  4412  			// Columns: Name, Desired, Current, Ready, Age
  4413  			expected: []metav1.TableRow{{Cells: []interface{}{"rc1", int64(5), int64(3), int64(1), "<unknown>"}}},
  4414  		},
  4415  		// Generate options: Wide; print containers and labels.
  4416  		{
  4417  			rc: api.ReplicationController{
  4418  				ObjectMeta: metav1.ObjectMeta{
  4419  					Name: "rc1",
  4420  				},
  4421  				Spec: api.ReplicationControllerSpec{
  4422  					Replicas: 5,
  4423  					Selector: map[string]string{"a": "b"},
  4424  					Template: &api.PodTemplateSpec{
  4425  						ObjectMeta: metav1.ObjectMeta{
  4426  							Labels: map[string]string{"a": "b"},
  4427  						},
  4428  						Spec: api.PodSpec{
  4429  							Containers: []api.Container{
  4430  								{
  4431  									Name:                     "test",
  4432  									Image:                    "test_image",
  4433  									ImagePullPolicy:          api.PullIfNotPresent,
  4434  									TerminationMessagePolicy: api.TerminationMessageReadFile,
  4435  								},
  4436  							},
  4437  							RestartPolicy: api.RestartPolicyAlways,
  4438  							DNSPolicy:     api.DNSClusterFirst,
  4439  						},
  4440  					},
  4441  				},
  4442  				Status: api.ReplicationControllerStatus{
  4443  					Replicas:      3,
  4444  					ReadyReplicas: 1,
  4445  				},
  4446  			},
  4447  			options: printers.GenerateOptions{Wide: true},
  4448  			// Columns: Name, Desired, Current, Ready, Age, Containers, Images, Selector
  4449  			expected: []metav1.TableRow{{Cells: []interface{}{"rc1", int64(5), int64(3), int64(1), "<unknown>", "test", "test_image", "a=b"}}},
  4450  		},
  4451  	}
  4452  
  4453  	for i, test := range tests {
  4454  		rows, err := printReplicationController(&test.rc, test.options)
  4455  		if err != nil {
  4456  			t.Fatal(err)
  4457  		}
  4458  		for i := range rows {
  4459  			rows[i].Object.Object = nil
  4460  		}
  4461  		if !reflect.DeepEqual(test.expected, rows) {
  4462  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  4463  		}
  4464  	}
  4465  }
  4466  
  4467  func TestPrintReplicaSet(t *testing.T) {
  4468  	tests := []struct {
  4469  		replicaSet apps.ReplicaSet
  4470  		options    printers.GenerateOptions
  4471  		expected   []metav1.TableRow
  4472  	}{
  4473  		// Generate options empty
  4474  		{
  4475  			replicaSet: apps.ReplicaSet{
  4476  				ObjectMeta: metav1.ObjectMeta{
  4477  					Name:              "test1",
  4478  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4479  				},
  4480  				Spec: apps.ReplicaSetSpec{
  4481  					Replicas: 5,
  4482  					Template: api.PodTemplateSpec{
  4483  						Spec: api.PodSpec{
  4484  							Containers: []api.Container{
  4485  								{
  4486  									Name:  "fake-container1",
  4487  									Image: "fake-image1",
  4488  								},
  4489  								{
  4490  									Name:  "fake-container2",
  4491  									Image: "fake-image2",
  4492  								},
  4493  							},
  4494  						},
  4495  					},
  4496  					Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  4497  				},
  4498  				Status: apps.ReplicaSetStatus{
  4499  					Replicas:      5,
  4500  					ReadyReplicas: 2,
  4501  				},
  4502  			},
  4503  			options: printers.GenerateOptions{},
  4504  			// Columns: Name, Desired, Current, Ready, Age
  4505  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", int64(5), int64(5), int64(2), "0s"}}},
  4506  		},
  4507  		// Generate options "Wide"
  4508  		{
  4509  			replicaSet: apps.ReplicaSet{
  4510  				ObjectMeta: metav1.ObjectMeta{
  4511  					Name:              "test1",
  4512  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4513  				},
  4514  				Spec: apps.ReplicaSetSpec{
  4515  					Replicas: 5,
  4516  					Template: api.PodTemplateSpec{
  4517  						Spec: api.PodSpec{
  4518  							Containers: []api.Container{
  4519  								{
  4520  									Name:  "fake-container1",
  4521  									Image: "fake-image1",
  4522  								},
  4523  								{
  4524  									Name:  "fake-container2",
  4525  									Image: "fake-image2",
  4526  								},
  4527  							},
  4528  						},
  4529  					},
  4530  					Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  4531  				},
  4532  				Status: apps.ReplicaSetStatus{
  4533  					Replicas:      5,
  4534  					ReadyReplicas: 2,
  4535  				},
  4536  			},
  4537  			options: printers.GenerateOptions{Wide: true},
  4538  			// Columns: Name, Desired, Current, Ready, Age, Containers, Images, Selector
  4539  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", int64(5), int64(5), int64(2), "0s", "fake-container1,fake-container2", "fake-image1,fake-image2", "foo=bar"}}},
  4540  		},
  4541  	}
  4542  
  4543  	for i, test := range tests {
  4544  		rows, err := printReplicaSet(&test.replicaSet, test.options)
  4545  		if err != nil {
  4546  			t.Fatal(err)
  4547  		}
  4548  		for i := range rows {
  4549  			rows[i].Object.Object = nil
  4550  		}
  4551  		if !reflect.DeepEqual(test.expected, rows) {
  4552  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  4553  		}
  4554  	}
  4555  }
  4556  
  4557  func TestPrintReplicaSetList(t *testing.T) {
  4558  
  4559  	replicaSetList := apps.ReplicaSetList{
  4560  		Items: []apps.ReplicaSet{
  4561  			{
  4562  				ObjectMeta: metav1.ObjectMeta{
  4563  					Name:              "replicaset1",
  4564  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4565  				},
  4566  				Spec: apps.ReplicaSetSpec{
  4567  					Replicas: 5,
  4568  					Template: api.PodTemplateSpec{
  4569  						Spec: api.PodSpec{
  4570  							Containers: []api.Container{
  4571  								{
  4572  									Name:  "fake-container1",
  4573  									Image: "fake-image1",
  4574  								},
  4575  								{
  4576  									Name:  "fake-container2",
  4577  									Image: "fake-image2",
  4578  								},
  4579  							},
  4580  						},
  4581  					},
  4582  				},
  4583  				Status: apps.ReplicaSetStatus{
  4584  					Replicas:      5,
  4585  					ReadyReplicas: 2,
  4586  				},
  4587  			},
  4588  			{
  4589  				ObjectMeta: metav1.ObjectMeta{
  4590  					Name:              "replicaset2",
  4591  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4592  				},
  4593  				Spec: apps.ReplicaSetSpec{
  4594  					Replicas: 4,
  4595  					Template: api.PodTemplateSpec{},
  4596  					Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  4597  				},
  4598  				Status: apps.ReplicaSetStatus{
  4599  					Replicas:      3,
  4600  					ReadyReplicas: 1,
  4601  				},
  4602  			},
  4603  		},
  4604  	}
  4605  
  4606  	// Columns: Name, Desired, Current, Ready, Age
  4607  	expectedRows := []metav1.TableRow{
  4608  		{Cells: []interface{}{"replicaset1", int64(5), int64(5), int64(2), "0s"}},
  4609  		{Cells: []interface{}{"replicaset2", int64(4), int64(3), int64(1), "0s"}},
  4610  	}
  4611  
  4612  	rows, err := printReplicaSetList(&replicaSetList, printers.GenerateOptions{})
  4613  	if err != nil {
  4614  		t.Fatalf("Error printing replica set list: %#v", err)
  4615  	}
  4616  	for i := range rows {
  4617  		rows[i].Object.Object = nil
  4618  	}
  4619  	if !reflect.DeepEqual(expectedRows, rows) {
  4620  		t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows))
  4621  	}
  4622  }
  4623  
  4624  func TestPrintStatefulSet(t *testing.T) {
  4625  	tests := []struct {
  4626  		statefulSet apps.StatefulSet
  4627  		options     printers.GenerateOptions
  4628  		expected    []metav1.TableRow
  4629  	}{
  4630  		// Basic stateful set; no generate options.
  4631  		{
  4632  			statefulSet: apps.StatefulSet{
  4633  				ObjectMeta: metav1.ObjectMeta{
  4634  					Name:              "test1",
  4635  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4636  				},
  4637  				Spec: apps.StatefulSetSpec{
  4638  					Replicas: 5,
  4639  					Template: api.PodTemplateSpec{
  4640  						Spec: api.PodSpec{
  4641  							Containers: []api.Container{
  4642  								{
  4643  									Name:  "fake-container1",
  4644  									Image: "fake-image1",
  4645  								},
  4646  								{
  4647  									Name:  "fake-container2",
  4648  									Image: "fake-image2",
  4649  								},
  4650  							},
  4651  						},
  4652  					},
  4653  				},
  4654  				Status: apps.StatefulSetStatus{
  4655  					Replicas:      5,
  4656  					ReadyReplicas: 2,
  4657  				},
  4658  			},
  4659  			options: printers.GenerateOptions{},
  4660  			// Columns: Name, Ready, Age
  4661  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", "2/5", "0s"}}},
  4662  		},
  4663  		// Generate options "Wide"; includes containers and images.
  4664  		{
  4665  			statefulSet: apps.StatefulSet{
  4666  				ObjectMeta: metav1.ObjectMeta{
  4667  					Name:              "test1",
  4668  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  4669  				},
  4670  				Spec: apps.StatefulSetSpec{
  4671  					Replicas: 5,
  4672  					Template: api.PodTemplateSpec{
  4673  						Spec: api.PodSpec{
  4674  							Containers: []api.Container{
  4675  								{
  4676  									Name:  "fake-container1",
  4677  									Image: "fake-image1",
  4678  								},
  4679  								{
  4680  									Name:  "fake-container2",
  4681  									Image: "fake-image2",
  4682  								},
  4683  							},
  4684  						},
  4685  					},
  4686  				},
  4687  				Status: apps.StatefulSetStatus{
  4688  					Replicas:      5,
  4689  					ReadyReplicas: 2,
  4690  				},
  4691  			},
  4692  			options: printers.GenerateOptions{Wide: true},
  4693  			// Columns: Name, Ready, Age, Containers, Images
  4694  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", "2/5", "0s", "fake-container1,fake-container2", "fake-image1,fake-image2"}}},
  4695  		},
  4696  	}
  4697  
  4698  	for i, test := range tests {
  4699  		rows, err := printStatefulSet(&test.statefulSet, test.options)
  4700  		if err != nil {
  4701  			t.Fatal(err)
  4702  		}
  4703  		for i := range rows {
  4704  			rows[i].Object.Object = nil
  4705  		}
  4706  		if !reflect.DeepEqual(test.expected, rows) {
  4707  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  4708  		}
  4709  	}
  4710  }
  4711  
  4712  func TestPrintPersistentVolume(t *testing.T) {
  4713  	myScn := "my-scn"
  4714  	myVacn := "my-vacn"
  4715  
  4716  	claimRef := api.ObjectReference{
  4717  		Name:      "test",
  4718  		Namespace: "default",
  4719  	}
  4720  	tests := []struct {
  4721  		pv       api.PersistentVolume
  4722  		expected []metav1.TableRow
  4723  	}{
  4724  		{
  4725  			// Test bound
  4726  			pv: api.PersistentVolume{
  4727  				ObjectMeta: metav1.ObjectMeta{
  4728  					Name: "test1",
  4729  				},
  4730  				Spec: api.PersistentVolumeSpec{
  4731  					ClaimRef:    &claimRef,
  4732  					AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany},
  4733  					Capacity: map[api.ResourceName]resource.Quantity{
  4734  						api.ResourceStorage: resource.MustParse("4Gi"),
  4735  					},
  4736  				},
  4737  				Status: api.PersistentVolumeStatus{
  4738  					Phase: api.VolumeBound,
  4739  				},
  4740  			},
  4741  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", "4Gi", "ROX", "", "Bound", "default/test", "", "<unset>", "", "<unknown>", "<unset>"}}},
  4742  		},
  4743  		{
  4744  			// Test failed
  4745  			pv: api.PersistentVolume{
  4746  				ObjectMeta: metav1.ObjectMeta{
  4747  					Name: "test2",
  4748  				},
  4749  				Spec: api.PersistentVolumeSpec{
  4750  					ClaimRef:    &claimRef,
  4751  					AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany},
  4752  					Capacity: map[api.ResourceName]resource.Quantity{
  4753  						api.ResourceStorage: resource.MustParse("4Gi"),
  4754  					},
  4755  				},
  4756  				Status: api.PersistentVolumeStatus{
  4757  					Phase: api.VolumeFailed,
  4758  				},
  4759  			},
  4760  			expected: []metav1.TableRow{{Cells: []interface{}{"test2", "4Gi", "ROX", "", "Failed", "default/test", "", "<unset>", "", "<unknown>", "<unset>"}}},
  4761  		},
  4762  		{
  4763  			// Test pending
  4764  			pv: api.PersistentVolume{
  4765  				ObjectMeta: metav1.ObjectMeta{
  4766  					Name: "test3",
  4767  				},
  4768  				Spec: api.PersistentVolumeSpec{
  4769  					ClaimRef:    &claimRef,
  4770  					AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteMany},
  4771  					Capacity: map[api.ResourceName]resource.Quantity{
  4772  						api.ResourceStorage: resource.MustParse("10Gi"),
  4773  					},
  4774  				},
  4775  				Status: api.PersistentVolumeStatus{
  4776  					Phase: api.VolumePending,
  4777  				},
  4778  			},
  4779  			expected: []metav1.TableRow{{Cells: []interface{}{"test3", "10Gi", "RWX", "", "Pending", "default/test", "", "<unset>", "", "<unknown>", "<unset>"}}},
  4780  		},
  4781  		{
  4782  			// Test pending, storageClass
  4783  			pv: api.PersistentVolume{
  4784  				ObjectMeta: metav1.ObjectMeta{
  4785  					Name: "test4",
  4786  				},
  4787  				Spec: api.PersistentVolumeSpec{
  4788  					ClaimRef:         &claimRef,
  4789  					StorageClassName: myScn,
  4790  					AccessModes:      []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  4791  					Capacity: map[api.ResourceName]resource.Quantity{
  4792  						api.ResourceStorage: resource.MustParse("10Gi"),
  4793  					},
  4794  				},
  4795  				Status: api.PersistentVolumeStatus{
  4796  					Phase: api.VolumePending,
  4797  				},
  4798  			},
  4799  			expected: []metav1.TableRow{{Cells: []interface{}{"test4", "10Gi", "RWO", "", "Pending", "default/test", "my-scn", "<unset>", "", "<unknown>", "<unset>"}}},
  4800  		},
  4801  		{
  4802  			// Test pending, storageClass, volumeAttributesClass
  4803  			pv: api.PersistentVolume{
  4804  				ObjectMeta: metav1.ObjectMeta{
  4805  					Name: "test4",
  4806  				},
  4807  				Spec: api.PersistentVolumeSpec{
  4808  					ClaimRef:                  &claimRef,
  4809  					StorageClassName:          myScn,
  4810  					VolumeAttributesClassName: &myVacn,
  4811  					AccessModes:               []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  4812  					Capacity: map[api.ResourceName]resource.Quantity{
  4813  						api.ResourceStorage: resource.MustParse("10Gi"),
  4814  					},
  4815  				},
  4816  				Status: api.PersistentVolumeStatus{
  4817  					Phase: api.VolumePending,
  4818  				},
  4819  			},
  4820  			expected: []metav1.TableRow{{Cells: []interface{}{"test4", "10Gi", "RWO", "", "Pending", "default/test", "my-scn", "my-vacn", "", "<unknown>", "<unset>"}}},
  4821  		},
  4822  		{
  4823  			// Test available
  4824  			pv: api.PersistentVolume{
  4825  				ObjectMeta: metav1.ObjectMeta{
  4826  					Name: "test5",
  4827  				},
  4828  				Spec: api.PersistentVolumeSpec{
  4829  					ClaimRef:         &claimRef,
  4830  					StorageClassName: myScn,
  4831  					AccessModes:      []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  4832  					Capacity: map[api.ResourceName]resource.Quantity{
  4833  						api.ResourceStorage: resource.MustParse("10Gi"),
  4834  					},
  4835  				},
  4836  				Status: api.PersistentVolumeStatus{
  4837  					Phase: api.VolumeAvailable,
  4838  				},
  4839  			},
  4840  			expected: []metav1.TableRow{{Cells: []interface{}{"test5", "10Gi", "RWO", "", "Available", "default/test", "my-scn", "<unset>", "", "<unknown>", "<unset>"}}},
  4841  		},
  4842  		{
  4843  			// Test released
  4844  			pv: api.PersistentVolume{
  4845  				ObjectMeta: metav1.ObjectMeta{
  4846  					Name: "test6",
  4847  				},
  4848  				Spec: api.PersistentVolumeSpec{
  4849  					ClaimRef:         &claimRef,
  4850  					StorageClassName: myScn,
  4851  					AccessModes:      []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  4852  					Capacity: map[api.ResourceName]resource.Quantity{
  4853  						api.ResourceStorage: resource.MustParse("10Gi"),
  4854  					},
  4855  				},
  4856  				Status: api.PersistentVolumeStatus{
  4857  					Phase: api.VolumeReleased,
  4858  				},
  4859  			},
  4860  			expected: []metav1.TableRow{{Cells: []interface{}{"test6", "10Gi", "RWO", "", "Released", "default/test", "my-scn", "<unset>", "", "<unknown>", "<unset>"}}},
  4861  		},
  4862  	}
  4863  
  4864  	for i, test := range tests {
  4865  		rows, err := printPersistentVolume(&test.pv, printers.GenerateOptions{})
  4866  		if err != nil {
  4867  			t.Fatal(err)
  4868  		}
  4869  		for i := range rows {
  4870  			rows[i].Object.Object = nil
  4871  		}
  4872  		if !reflect.DeepEqual(test.expected, rows) {
  4873  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  4874  		}
  4875  	}
  4876  }
  4877  
  4878  func TestPrintPersistentVolumeClaim(t *testing.T) {
  4879  	volumeMode := api.PersistentVolumeFilesystem
  4880  	myVacn := "my-vacn"
  4881  	myScn := "my-scn"
  4882  	tests := []struct {
  4883  		pvc      api.PersistentVolumeClaim
  4884  		expected []metav1.TableRow
  4885  	}{
  4886  		{
  4887  			// Test name, num of containers, restarts, container ready status
  4888  			pvc: api.PersistentVolumeClaim{
  4889  				ObjectMeta: metav1.ObjectMeta{
  4890  					Name: "test1",
  4891  				},
  4892  				Spec: api.PersistentVolumeClaimSpec{
  4893  					VolumeName: "my-volume",
  4894  					VolumeMode: &volumeMode,
  4895  				},
  4896  				Status: api.PersistentVolumeClaimStatus{
  4897  					Phase:       api.ClaimBound,
  4898  					AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany},
  4899  					Capacity: map[api.ResourceName]resource.Quantity{
  4900  						api.ResourceStorage: resource.MustParse("4Gi"),
  4901  					},
  4902  				},
  4903  			},
  4904  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", "Bound", "my-volume", "4Gi", "ROX", "", "<unset>", "<unknown>", "Filesystem"}}},
  4905  		},
  4906  		{
  4907  			// Test name, num of containers, restarts, container ready status
  4908  			pvc: api.PersistentVolumeClaim{
  4909  				ObjectMeta: metav1.ObjectMeta{
  4910  					Name: "test2",
  4911  				},
  4912  				Spec: api.PersistentVolumeClaimSpec{
  4913  					VolumeMode: &volumeMode,
  4914  				},
  4915  				Status: api.PersistentVolumeClaimStatus{
  4916  					Phase:       api.ClaimLost,
  4917  					AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany},
  4918  					Capacity: map[api.ResourceName]resource.Quantity{
  4919  						api.ResourceStorage: resource.MustParse("4Gi"),
  4920  					},
  4921  				},
  4922  			},
  4923  			expected: []metav1.TableRow{{Cells: []interface{}{"test2", "Lost", "", "", "", "", "<unset>", "<unknown>", "Filesystem"}}},
  4924  		},
  4925  		{
  4926  			// Test name, num of containers, restarts, container ready status
  4927  			pvc: api.PersistentVolumeClaim{
  4928  				ObjectMeta: metav1.ObjectMeta{
  4929  					Name: "test3",
  4930  				},
  4931  				Spec: api.PersistentVolumeClaimSpec{
  4932  					VolumeName: "my-volume",
  4933  					VolumeMode: &volumeMode,
  4934  				},
  4935  				Status: api.PersistentVolumeClaimStatus{
  4936  					Phase:       api.ClaimPending,
  4937  					AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteMany},
  4938  					Capacity: map[api.ResourceName]resource.Quantity{
  4939  						api.ResourceStorage: resource.MustParse("10Gi"),
  4940  					},
  4941  				},
  4942  			},
  4943  			expected: []metav1.TableRow{{Cells: []interface{}{"test3", "Pending", "my-volume", "10Gi", "RWX", "", "<unset>", "<unknown>", "Filesystem"}}},
  4944  		},
  4945  		{
  4946  			// Test name, num of containers, restarts, container ready status
  4947  			pvc: api.PersistentVolumeClaim{
  4948  				ObjectMeta: metav1.ObjectMeta{
  4949  					Name: "test4",
  4950  				},
  4951  				Spec: api.PersistentVolumeClaimSpec{
  4952  					VolumeName:       "my-volume",
  4953  					StorageClassName: &myScn,
  4954  					VolumeMode:       &volumeMode,
  4955  				},
  4956  				Status: api.PersistentVolumeClaimStatus{
  4957  					Phase:       api.ClaimPending,
  4958  					AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  4959  					Capacity: map[api.ResourceName]resource.Quantity{
  4960  						api.ResourceStorage: resource.MustParse("10Gi"),
  4961  					},
  4962  				},
  4963  			},
  4964  			expected: []metav1.TableRow{{Cells: []interface{}{"test4", "Pending", "my-volume", "10Gi", "RWO", "my-scn", "<unset>", "<unknown>", "Filesystem"}}},
  4965  		},
  4966  		{
  4967  			// Test name, num of containers, restarts, container ready status
  4968  			pvc: api.PersistentVolumeClaim{
  4969  				ObjectMeta: metav1.ObjectMeta{
  4970  					Name: "test5",
  4971  				},
  4972  				Spec: api.PersistentVolumeClaimSpec{
  4973  					VolumeName:       "my-volume",
  4974  					StorageClassName: &myScn,
  4975  				},
  4976  				Status: api.PersistentVolumeClaimStatus{
  4977  					Phase:       api.ClaimPending,
  4978  					AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  4979  					Capacity: map[api.ResourceName]resource.Quantity{
  4980  						api.ResourceStorage: resource.MustParse("10Gi"),
  4981  					},
  4982  				},
  4983  			},
  4984  			expected: []metav1.TableRow{{Cells: []interface{}{"test5", "Pending", "my-volume", "10Gi", "RWO", "my-scn", "<unset>", "<unknown>", "<unset>"}}},
  4985  		},
  4986  		{
  4987  			// Test name, num of containers, restarts, container ready status
  4988  			pvc: api.PersistentVolumeClaim{
  4989  				ObjectMeta: metav1.ObjectMeta{
  4990  					Name: "test5",
  4991  				},
  4992  				Spec: api.PersistentVolumeClaimSpec{
  4993  					VolumeName:                "my-volume",
  4994  					StorageClassName:          &myScn,
  4995  					VolumeAttributesClassName: &myVacn,
  4996  				},
  4997  				Status: api.PersistentVolumeClaimStatus{
  4998  					Phase:       api.ClaimPending,
  4999  					AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  5000  					Capacity: map[api.ResourceName]resource.Quantity{
  5001  						api.ResourceStorage: resource.MustParse("10Gi"),
  5002  					},
  5003  				},
  5004  			},
  5005  			expected: []metav1.TableRow{{Cells: []interface{}{"test5", "Pending", "my-volume", "10Gi", "RWO", "my-scn", "my-vacn", "<unknown>", "<unset>"}}},
  5006  		},
  5007  	}
  5008  
  5009  	for i, test := range tests {
  5010  		rows, err := printPersistentVolumeClaim(&test.pvc, printers.GenerateOptions{})
  5011  		if err != nil {
  5012  			t.Fatal(err)
  5013  		}
  5014  		for i := range rows {
  5015  			rows[i].Object.Object = nil
  5016  		}
  5017  		if !reflect.DeepEqual(test.expected, rows) {
  5018  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  5019  		}
  5020  	}
  5021  }
  5022  
  5023  func TestPrintComponentStatus(t *testing.T) {
  5024  	tests := []struct {
  5025  		componentStatus api.ComponentStatus
  5026  		expected        []metav1.TableRow
  5027  	}{
  5028  		// Basic component status without conditions
  5029  		{
  5030  			componentStatus: api.ComponentStatus{
  5031  				ObjectMeta: metav1.ObjectMeta{
  5032  					Name: "cs1",
  5033  				},
  5034  				Conditions: []api.ComponentCondition{},
  5035  			},
  5036  			// Columns: Name, Status, Message, Error
  5037  			expected: []metav1.TableRow{{Cells: []interface{}{"cs1", "Unknown", "", ""}}},
  5038  		},
  5039  		// Basic component status with healthy condition.
  5040  		{
  5041  			componentStatus: api.ComponentStatus{
  5042  				ObjectMeta: metav1.ObjectMeta{
  5043  					Name: "cs2",
  5044  				},
  5045  				Conditions: []api.ComponentCondition{
  5046  					{
  5047  						Type:    "Healthy",
  5048  						Status:  api.ConditionTrue,
  5049  						Message: "test message",
  5050  						Error:   "test error",
  5051  					},
  5052  				},
  5053  			},
  5054  			// Columns: Name, Status, Message, Error
  5055  			expected: []metav1.TableRow{{Cells: []interface{}{"cs2", "Healthy", "test message", "test error"}}},
  5056  		},
  5057  		// Basic component status with healthy condition.
  5058  		{
  5059  			componentStatus: api.ComponentStatus{
  5060  				ObjectMeta: metav1.ObjectMeta{
  5061  					Name: "cs3",
  5062  				},
  5063  				Conditions: []api.ComponentCondition{
  5064  					{
  5065  						Type:    "Healthy",
  5066  						Status:  api.ConditionFalse,
  5067  						Message: "test message",
  5068  						Error:   "test error",
  5069  					},
  5070  				},
  5071  			},
  5072  			// Columns: Name, Status, Message, Error
  5073  			expected: []metav1.TableRow{{Cells: []interface{}{"cs3", "Unhealthy", "test message", "test error"}}},
  5074  		},
  5075  	}
  5076  
  5077  	for i, test := range tests {
  5078  		rows, err := printComponentStatus(&test.componentStatus, printers.GenerateOptions{})
  5079  		if err != nil {
  5080  			t.Fatal(err)
  5081  		}
  5082  		for i := range rows {
  5083  			rows[i].Object.Object = nil
  5084  		}
  5085  		if !reflect.DeepEqual(test.expected, rows) {
  5086  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  5087  		}
  5088  	}
  5089  }
  5090  
  5091  func TestPrintCronJob(t *testing.T) {
  5092  	completions := int32(2)
  5093  	suspend := false
  5094  	tests := []struct {
  5095  		cronjob  batch.CronJob
  5096  		options  printers.GenerateOptions
  5097  		expected []metav1.TableRow
  5098  	}{
  5099  		// Basic cron job; does not print containers, images, or labels.
  5100  		{
  5101  			cronjob: batch.CronJob{
  5102  				ObjectMeta: metav1.ObjectMeta{
  5103  					Name:              "cronjob1",
  5104  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5105  				},
  5106  				Spec: batch.CronJobSpec{
  5107  					Schedule: "0/5 * * * ?",
  5108  					Suspend:  &suspend,
  5109  					JobTemplate: batch.JobTemplateSpec{
  5110  						Spec: batch.JobSpec{
  5111  							Completions: &completions,
  5112  							Template: api.PodTemplateSpec{
  5113  								Spec: api.PodSpec{
  5114  									Containers: []api.Container{
  5115  										{
  5116  											Name:  "fake-job-container1",
  5117  											Image: "fake-job-image1",
  5118  										},
  5119  										{
  5120  											Name:  "fake-job-container2",
  5121  											Image: "fake-job-image2",
  5122  										},
  5123  									},
  5124  								},
  5125  							},
  5126  							Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
  5127  						},
  5128  					},
  5129  				},
  5130  				Status: batch.CronJobStatus{
  5131  					LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)},
  5132  				},
  5133  			},
  5134  			options: printers.GenerateOptions{},
  5135  			// Columns: Name, Schedule, Suspend, Active, Last Schedule, Age
  5136  			expected: []metav1.TableRow{{Cells: []interface{}{"cronjob1", "0/5 * * * ?", "False", int64(0), "0s", "0s"}}},
  5137  		},
  5138  		// Generate options: Wide; prints containers, images, and labels.
  5139  		{
  5140  			cronjob: batch.CronJob{
  5141  				ObjectMeta: metav1.ObjectMeta{
  5142  					Name:              "cronjob1",
  5143  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5144  				},
  5145  				Spec: batch.CronJobSpec{
  5146  					Schedule: "0/5 * * * ?",
  5147  					Suspend:  &suspend,
  5148  					JobTemplate: batch.JobTemplateSpec{
  5149  						Spec: batch.JobSpec{
  5150  							Completions: &completions,
  5151  							Template: api.PodTemplateSpec{
  5152  								Spec: api.PodSpec{
  5153  									Containers: []api.Container{
  5154  										{
  5155  											Name:  "fake-job-container1",
  5156  											Image: "fake-job-image1",
  5157  										},
  5158  										{
  5159  											Name:  "fake-job-container2",
  5160  											Image: "fake-job-image2",
  5161  										},
  5162  									},
  5163  								},
  5164  							},
  5165  							Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
  5166  						},
  5167  					},
  5168  				},
  5169  				Status: batch.CronJobStatus{
  5170  					LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)},
  5171  				},
  5172  			},
  5173  			options: printers.GenerateOptions{Wide: true},
  5174  			// Columns: Name, Schedule, Suspend, Active, Last Schedule, Age
  5175  			expected: []metav1.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"}}},
  5176  		},
  5177  		// CronJob with Last Schedule and Age
  5178  		{
  5179  			cronjob: batch.CronJob{
  5180  				ObjectMeta: metav1.ObjectMeta{
  5181  					Name:              "cronjob2",
  5182  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5183  				},
  5184  				Spec: batch.CronJobSpec{
  5185  					Schedule: "0/5 * * * ?",
  5186  					Suspend:  &suspend,
  5187  				},
  5188  				Status: batch.CronJobStatus{
  5189  					LastScheduleTime: &metav1.Time{Time: time.Now().Add(-3e10)},
  5190  				},
  5191  			},
  5192  			options: printers.GenerateOptions{},
  5193  			// Columns: Name, Schedule, Suspend, Active, Last Schedule, Age
  5194  			expected: []metav1.TableRow{{Cells: []interface{}{"cronjob2", "0/5 * * * ?", "False", int64(0), "30s", "5m"}}},
  5195  		},
  5196  		// CronJob without Last Schedule
  5197  		{
  5198  			cronjob: batch.CronJob{
  5199  				ObjectMeta: metav1.ObjectMeta{
  5200  					Name:              "cronjob3",
  5201  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5202  				},
  5203  				Spec: batch.CronJobSpec{
  5204  					Schedule: "0/5 * * * ?",
  5205  					Suspend:  &suspend,
  5206  				},
  5207  				Status: batch.CronJobStatus{},
  5208  			},
  5209  			options: printers.GenerateOptions{},
  5210  			// Columns: Name, Schedule, Suspend, Active, Last Schedule, Age
  5211  			expected: []metav1.TableRow{{Cells: []interface{}{"cronjob3", "0/5 * * * ?", "False", int64(0), "<none>", "5m"}}},
  5212  		},
  5213  	}
  5214  
  5215  	for i, test := range tests {
  5216  		rows, err := printCronJob(&test.cronjob, test.options)
  5217  		if err != nil {
  5218  			t.Fatal(err)
  5219  		}
  5220  		for i := range rows {
  5221  			rows[i].Object.Object = nil
  5222  		}
  5223  		if !reflect.DeepEqual(test.expected, rows) {
  5224  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  5225  		}
  5226  	}
  5227  }
  5228  
  5229  func TestPrintCronJobList(t *testing.T) {
  5230  	completions := int32(2)
  5231  	suspend := false
  5232  
  5233  	cronJobList := batch.CronJobList{
  5234  		Items: []batch.CronJob{
  5235  			{
  5236  				ObjectMeta: metav1.ObjectMeta{
  5237  					Name:              "cronjob1",
  5238  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5239  				},
  5240  				Spec: batch.CronJobSpec{
  5241  					Schedule: "0/5 * * * ?",
  5242  					Suspend:  &suspend,
  5243  					JobTemplate: batch.JobTemplateSpec{
  5244  						Spec: batch.JobSpec{
  5245  							Completions: &completions,
  5246  							Selector:    &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
  5247  						},
  5248  					},
  5249  				},
  5250  				Status: batch.CronJobStatus{
  5251  					LastScheduleTime: &metav1.Time{Time: time.Now().Add(1.9e9)},
  5252  				},
  5253  			},
  5254  			{
  5255  				ObjectMeta: metav1.ObjectMeta{
  5256  					Name:              "cronjob2",
  5257  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5258  				},
  5259  				Spec: batch.CronJobSpec{
  5260  					Schedule: "4/5 1 1 1 ?",
  5261  					Suspend:  &suspend,
  5262  					JobTemplate: batch.JobTemplateSpec{
  5263  						Spec: batch.JobSpec{
  5264  							Completions: &completions,
  5265  							Selector:    &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
  5266  						},
  5267  					},
  5268  				},
  5269  				Status: batch.CronJobStatus{
  5270  					LastScheduleTime: &metav1.Time{Time: time.Now().Add(-20 * time.Minute)},
  5271  				},
  5272  			},
  5273  		},
  5274  	}
  5275  
  5276  	// Columns: Name, Schedule, Suspend, Active, Last Schedule, Age
  5277  	expectedRows := []metav1.TableRow{
  5278  		{Cells: []interface{}{"cronjob1", "0/5 * * * ?", "False", int64(0), "0s", "0s"}},
  5279  		{Cells: []interface{}{"cronjob2", "4/5 1 1 1 ?", "False", int64(0), "20m", "0s"}},
  5280  	}
  5281  
  5282  	rows, err := printCronJobList(&cronJobList, printers.GenerateOptions{})
  5283  	if err != nil {
  5284  		t.Fatalf("Error printing job list: %#v", err)
  5285  	}
  5286  	for i := range rows {
  5287  		rows[i].Object.Object = nil
  5288  	}
  5289  	if !reflect.DeepEqual(expectedRows, rows) {
  5290  		t.Errorf("mismatch: %s", cmp.Diff(expectedRows, rows))
  5291  	}
  5292  }
  5293  
  5294  func TestPrintStorageClass(t *testing.T) {
  5295  	policyDelte := api.PersistentVolumeReclaimDelete
  5296  	policyRetain := api.PersistentVolumeReclaimRetain
  5297  	bindModeImmediate := storage.VolumeBindingImmediate
  5298  	bindModeWait := storage.VolumeBindingWaitForFirstConsumer
  5299  	tests := []struct {
  5300  		sc       storage.StorageClass
  5301  		expected []metav1.TableRow
  5302  	}{
  5303  		{
  5304  			sc: storage.StorageClass{
  5305  				ObjectMeta: metav1.ObjectMeta{
  5306  					Name:              "sc1",
  5307  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5308  				},
  5309  				Provisioner: "kubernetes.io/glusterfs",
  5310  			},
  5311  			expected: []metav1.TableRow{{Cells: []interface{}{"sc1", "kubernetes.io/glusterfs", "Delete",
  5312  				"Immediate", false, "0s"}}},
  5313  		},
  5314  		{
  5315  			sc: storage.StorageClass{
  5316  				ObjectMeta: metav1.ObjectMeta{
  5317  					Name:              "sc2",
  5318  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5319  				},
  5320  				Provisioner: "kubernetes.io/nfs",
  5321  			},
  5322  			expected: []metav1.TableRow{{Cells: []interface{}{"sc2", "kubernetes.io/nfs", "Delete",
  5323  				"Immediate", false, "5m"}}},
  5324  		},
  5325  		{
  5326  			sc: storage.StorageClass{
  5327  				ObjectMeta: metav1.ObjectMeta{
  5328  					Name:              "sc3",
  5329  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5330  				},
  5331  				Provisioner:   "kubernetes.io/nfs",
  5332  				ReclaimPolicy: &policyDelte,
  5333  			},
  5334  			expected: []metav1.TableRow{{Cells: []interface{}{"sc3", "kubernetes.io/nfs", "Delete",
  5335  				"Immediate", false, "5m"}}},
  5336  		},
  5337  		{
  5338  			sc: storage.StorageClass{
  5339  				ObjectMeta: metav1.ObjectMeta{
  5340  					Name:              "sc4",
  5341  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5342  				},
  5343  				Provisioner:       "kubernetes.io/nfs",
  5344  				ReclaimPolicy:     &policyRetain,
  5345  				VolumeBindingMode: &bindModeImmediate,
  5346  			},
  5347  			expected: []metav1.TableRow{{Cells: []interface{}{"sc4", "kubernetes.io/nfs", "Retain",
  5348  				"Immediate", false, "5m"}}},
  5349  		},
  5350  		{
  5351  			sc: storage.StorageClass{
  5352  				ObjectMeta: metav1.ObjectMeta{
  5353  					Name:              "sc5",
  5354  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5355  				},
  5356  				Provisioner:       "kubernetes.io/nfs",
  5357  				ReclaimPolicy:     &policyRetain,
  5358  				VolumeBindingMode: &bindModeWait,
  5359  			},
  5360  			expected: []metav1.TableRow{{Cells: []interface{}{"sc5", "kubernetes.io/nfs", "Retain",
  5361  				"WaitForFirstConsumer", false, "5m"}}},
  5362  		},
  5363  		{
  5364  			sc: storage.StorageClass{
  5365  				ObjectMeta: metav1.ObjectMeta{
  5366  					Name:              "sc6",
  5367  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5368  				},
  5369  				Provisioner:          "kubernetes.io/nfs",
  5370  				ReclaimPolicy:        &policyRetain,
  5371  				AllowVolumeExpansion: boolP(true),
  5372  				VolumeBindingMode:    &bindModeWait,
  5373  			},
  5374  			expected: []metav1.TableRow{{Cells: []interface{}{"sc6", "kubernetes.io/nfs", "Retain",
  5375  				"WaitForFirstConsumer", true, "5m"}}},
  5376  		},
  5377  	}
  5378  
  5379  	for i, test := range tests {
  5380  		rows, err := printStorageClass(&test.sc, printers.GenerateOptions{})
  5381  		if err != nil {
  5382  			t.Fatal(err)
  5383  		}
  5384  		for i := range rows {
  5385  			rows[i].Object.Object = nil
  5386  		}
  5387  		if !reflect.DeepEqual(test.expected, rows) {
  5388  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  5389  		}
  5390  	}
  5391  }
  5392  
  5393  func TestPrintVolumeAttributesClass(t *testing.T) {
  5394  	tests := []struct {
  5395  		vac      storage.VolumeAttributesClass
  5396  		expected []metav1.TableRow
  5397  	}{
  5398  		{
  5399  			vac: storage.VolumeAttributesClass{
  5400  				ObjectMeta: metav1.ObjectMeta{
  5401  					Name:              "vac1",
  5402  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5403  				},
  5404  				DriverName: "fake",
  5405  			},
  5406  			expected: []metav1.TableRow{{Cells: []interface{}{"vac1", "fake", "0s"}}},
  5407  		},
  5408  		{
  5409  			vac: storage.VolumeAttributesClass{
  5410  				ObjectMeta: metav1.ObjectMeta{
  5411  					Name:              "vac2",
  5412  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5413  				},
  5414  				DriverName: "fake",
  5415  				Parameters: map[string]string{
  5416  					"iops":       "500",
  5417  					"throughput": "50MiB/s",
  5418  				},
  5419  			},
  5420  			expected: []metav1.TableRow{{Cells: []interface{}{"vac2", "fake", "5m"}}},
  5421  		},
  5422  	}
  5423  
  5424  	for i, test := range tests {
  5425  		rows, err := printVolumeAttributesClass(&test.vac, printers.GenerateOptions{})
  5426  		if err != nil {
  5427  			t.Fatal(err)
  5428  		}
  5429  		for i := range rows {
  5430  			rows[i].Object.Object = nil
  5431  		}
  5432  		if !reflect.DeepEqual(test.expected, rows) {
  5433  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  5434  		}
  5435  	}
  5436  }
  5437  
  5438  func TestPrintLease(t *testing.T) {
  5439  	holder1 := "holder1"
  5440  	holder2 := "holder2"
  5441  	tests := []struct {
  5442  		lease    coordination.Lease
  5443  		expected []metav1.TableRow
  5444  	}{
  5445  		{
  5446  			lease: coordination.Lease{
  5447  				ObjectMeta: metav1.ObjectMeta{
  5448  					Name:              "lease1",
  5449  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5450  				},
  5451  				Spec: coordination.LeaseSpec{
  5452  					HolderIdentity: &holder1,
  5453  				},
  5454  			},
  5455  			expected: []metav1.TableRow{{Cells: []interface{}{"lease1", "holder1", "0s"}}},
  5456  		},
  5457  		{
  5458  			lease: coordination.Lease{
  5459  				ObjectMeta: metav1.ObjectMeta{
  5460  					Name:              "lease2",
  5461  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5462  				},
  5463  				Spec: coordination.LeaseSpec{
  5464  					HolderIdentity: &holder2,
  5465  				},
  5466  			},
  5467  			expected: []metav1.TableRow{{Cells: []interface{}{"lease2", "holder2", "5m"}}},
  5468  		},
  5469  	}
  5470  
  5471  	for i, test := range tests {
  5472  		rows, err := printLease(&test.lease, printers.GenerateOptions{})
  5473  		if err != nil {
  5474  			t.Fatal(err)
  5475  		}
  5476  		for i := range rows {
  5477  			rows[i].Object.Object = nil
  5478  		}
  5479  		if !reflect.DeepEqual(test.expected, rows) {
  5480  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  5481  		}
  5482  	}
  5483  }
  5484  
  5485  func TestPrintPriorityClass(t *testing.T) {
  5486  	tests := []struct {
  5487  		pc       scheduling.PriorityClass
  5488  		expected []metav1.TableRow
  5489  	}{
  5490  		{
  5491  			pc: scheduling.PriorityClass{
  5492  				ObjectMeta: metav1.ObjectMeta{
  5493  					Name:              "pc1",
  5494  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5495  				},
  5496  				Value: 1,
  5497  			},
  5498  			expected: []metav1.TableRow{{Cells: []interface{}{"pc1", int64(1), bool(false), "0s"}}},
  5499  		},
  5500  		{
  5501  			pc: scheduling.PriorityClass{
  5502  				ObjectMeta: metav1.ObjectMeta{
  5503  					Name:              "pc2",
  5504  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5505  				},
  5506  				Value:         1000000000,
  5507  				GlobalDefault: true,
  5508  			},
  5509  			expected: []metav1.TableRow{{Cells: []interface{}{"pc2", int64(1000000000), bool(true), "5m"}}},
  5510  		},
  5511  	}
  5512  
  5513  	for i, test := range tests {
  5514  		rows, err := printPriorityClass(&test.pc, printers.GenerateOptions{})
  5515  		if err != nil {
  5516  			t.Fatal(err)
  5517  		}
  5518  		for i := range rows {
  5519  			rows[i].Object.Object = nil
  5520  		}
  5521  		if !reflect.DeepEqual(test.expected, rows) {
  5522  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  5523  		}
  5524  	}
  5525  }
  5526  
  5527  func TestPrintRuntimeClass(t *testing.T) {
  5528  	tests := []struct {
  5529  		rc       nodeapi.RuntimeClass
  5530  		expected []metav1.TableRow
  5531  	}{
  5532  		{
  5533  			rc: nodeapi.RuntimeClass{
  5534  				ObjectMeta: metav1.ObjectMeta{
  5535  					Name:              "rc1",
  5536  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5537  				},
  5538  				Handler: "h1",
  5539  			},
  5540  			expected: []metav1.TableRow{{Cells: []interface{}{"rc1", "h1", "0s"}}},
  5541  		},
  5542  		{
  5543  			rc: nodeapi.RuntimeClass{
  5544  				ObjectMeta: metav1.ObjectMeta{
  5545  					Name:              "rc2",
  5546  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5547  				},
  5548  				Handler: "h2",
  5549  			},
  5550  			expected: []metav1.TableRow{{Cells: []interface{}{"rc2", "h2", "5m"}}},
  5551  		},
  5552  	}
  5553  
  5554  	for i, test := range tests {
  5555  		rows, err := printRuntimeClass(&test.rc, printers.GenerateOptions{})
  5556  		if err != nil {
  5557  			t.Fatal(err)
  5558  		}
  5559  		for i := range rows {
  5560  			rows[i].Object.Object = nil
  5561  		}
  5562  		if !reflect.DeepEqual(test.expected, rows) {
  5563  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  5564  		}
  5565  	}
  5566  }
  5567  
  5568  func TestPrintEndpoint(t *testing.T) {
  5569  
  5570  	tests := []struct {
  5571  		endpoint api.Endpoints
  5572  		expected []metav1.TableRow
  5573  	}{
  5574  		// Basic endpoint with no IP's
  5575  		{
  5576  			endpoint: api.Endpoints{
  5577  				ObjectMeta: metav1.ObjectMeta{
  5578  					Name:              "endpoint1",
  5579  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5580  				},
  5581  			},
  5582  			// Columns: Name, Endpoints, Age
  5583  			expected: []metav1.TableRow{{Cells: []interface{}{"endpoint1", "<none>", "0s"}}},
  5584  		},
  5585  		// Endpoint with no ports
  5586  		{
  5587  			endpoint: api.Endpoints{
  5588  				ObjectMeta: metav1.ObjectMeta{
  5589  					Name:              "endpoint3",
  5590  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5591  				},
  5592  				Subsets: []api.EndpointSubset{
  5593  					{
  5594  						Addresses: []api.EndpointAddress{
  5595  							{
  5596  								IP: "1.2.3.4",
  5597  							},
  5598  							{
  5599  								IP: "5.6.7.8",
  5600  							},
  5601  						},
  5602  					},
  5603  				},
  5604  			},
  5605  			// Columns: Name, Endpoints, Age
  5606  			expected: []metav1.TableRow{{Cells: []interface{}{"endpoint3", "1.2.3.4,5.6.7.8", "5m"}}},
  5607  		},
  5608  		// Basic endpoint with two IP's and one port
  5609  		{
  5610  			endpoint: api.Endpoints{
  5611  				ObjectMeta: metav1.ObjectMeta{
  5612  					Name:              "endpoint2",
  5613  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5614  				},
  5615  				Subsets: []api.EndpointSubset{
  5616  					{
  5617  						Addresses: []api.EndpointAddress{
  5618  							{
  5619  								IP: "1.2.3.4",
  5620  							},
  5621  							{
  5622  								IP: "5.6.7.8",
  5623  							},
  5624  						},
  5625  						Ports: []api.EndpointPort{
  5626  							{
  5627  								Port:     8001,
  5628  								Protocol: "tcp",
  5629  							},
  5630  						},
  5631  					},
  5632  				},
  5633  			},
  5634  			// Columns: Name, Endpoints, Age
  5635  			expected: []metav1.TableRow{{Cells: []interface{}{"endpoint2", "1.2.3.4:8001,5.6.7.8:8001", "0s"}}},
  5636  		},
  5637  		// Basic endpoint with greater than three IP's triggering "more" string
  5638  		{
  5639  			endpoint: api.Endpoints{
  5640  				ObjectMeta: metav1.ObjectMeta{
  5641  					Name:              "endpoint2",
  5642  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5643  				},
  5644  				Subsets: []api.EndpointSubset{
  5645  					{
  5646  						Addresses: []api.EndpointAddress{
  5647  							{
  5648  								IP: "1.2.3.4",
  5649  							},
  5650  							{
  5651  								IP: "5.6.7.8",
  5652  							},
  5653  							{
  5654  								IP: "9.8.7.6",
  5655  							},
  5656  							{
  5657  								IP: "6.6.6.6",
  5658  							},
  5659  						},
  5660  						Ports: []api.EndpointPort{
  5661  							{
  5662  								Port:     8001,
  5663  								Protocol: "tcp",
  5664  							},
  5665  						},
  5666  					},
  5667  				},
  5668  			},
  5669  			// Columns: Name, Endpoints, Age
  5670  			expected: []metav1.TableRow{{Cells: []interface{}{"endpoint2", "1.2.3.4:8001,5.6.7.8:8001,9.8.7.6:8001 + 1 more...", "0s"}}},
  5671  		},
  5672  	}
  5673  
  5674  	for i, test := range tests {
  5675  		rows, err := printEndpoints(&test.endpoint, printers.GenerateOptions{})
  5676  		if err != nil {
  5677  			t.Fatal(err)
  5678  		}
  5679  		for i := range rows {
  5680  			rows[i].Object.Object = nil
  5681  		}
  5682  		if !reflect.DeepEqual(test.expected, rows) {
  5683  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  5684  		}
  5685  	}
  5686  
  5687  }
  5688  
  5689  func TestPrintEndpointSlice(t *testing.T) {
  5690  	tcpProtocol := api.ProtocolTCP
  5691  
  5692  	tests := []struct {
  5693  		endpointSlice discovery.EndpointSlice
  5694  		expected      []metav1.TableRow
  5695  	}{
  5696  		{
  5697  			endpointSlice: discovery.EndpointSlice{
  5698  				ObjectMeta: metav1.ObjectMeta{
  5699  					Name:              "abcslice.123",
  5700  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5701  				},
  5702  				AddressType: discovery.AddressTypeIPv4,
  5703  				Ports: []discovery.EndpointPort{{
  5704  					Name:     utilpointer.StringPtr("http"),
  5705  					Port:     utilpointer.Int32Ptr(80),
  5706  					Protocol: &tcpProtocol,
  5707  				}},
  5708  				Endpoints: []discovery.Endpoint{{
  5709  					Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"},
  5710  				}},
  5711  			},
  5712  			// Columns: Name, AddressType, Ports, Endpoints, Age
  5713  			expected: []metav1.TableRow{{Cells: []interface{}{"abcslice.123", "IPv4", "80", "10.1.2.3,2001:db8::1234:5678", "0s"}}},
  5714  		}, {
  5715  			endpointSlice: discovery.EndpointSlice{
  5716  				ObjectMeta: metav1.ObjectMeta{
  5717  					Name:              "longerslicename.123",
  5718  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5719  				},
  5720  				AddressType: discovery.AddressTypeIPv6,
  5721  				Ports: []discovery.EndpointPort{{
  5722  					Name:     utilpointer.StringPtr("http"),
  5723  					Port:     utilpointer.Int32Ptr(80),
  5724  					Protocol: &tcpProtocol,
  5725  				}, {
  5726  					Name:     utilpointer.StringPtr("https"),
  5727  					Port:     utilpointer.Int32Ptr(443),
  5728  					Protocol: &tcpProtocol,
  5729  				}},
  5730  				Endpoints: []discovery.Endpoint{{
  5731  					Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"},
  5732  				}, {
  5733  					Addresses: []string{"10.2.3.4", "2001:db8::2345:6789"},
  5734  				}},
  5735  			},
  5736  			// Columns: Name, AddressType, Ports, Endpoints, Age
  5737  			expected: []metav1.TableRow{{Cells: []interface{}{"longerslicename.123", "IPv6", "80,443", "10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more...", "5m"}}},
  5738  		}, {
  5739  			endpointSlice: discovery.EndpointSlice{
  5740  				ObjectMeta: metav1.ObjectMeta{
  5741  					Name:              "multiportslice.123",
  5742  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5743  				},
  5744  				AddressType: discovery.AddressTypeIPv4,
  5745  				Ports: []discovery.EndpointPort{{
  5746  					Name:     utilpointer.StringPtr("http"),
  5747  					Port:     utilpointer.Int32Ptr(80),
  5748  					Protocol: &tcpProtocol,
  5749  				}, {
  5750  					Name:     utilpointer.StringPtr("https"),
  5751  					Port:     utilpointer.Int32Ptr(443),
  5752  					Protocol: &tcpProtocol,
  5753  				}, {
  5754  					Name:     utilpointer.StringPtr("extra1"),
  5755  					Port:     utilpointer.Int32Ptr(3000),
  5756  					Protocol: &tcpProtocol,
  5757  				}, {
  5758  					Name:     utilpointer.StringPtr("extra2"),
  5759  					Port:     utilpointer.Int32Ptr(3001),
  5760  					Protocol: &tcpProtocol,
  5761  				}},
  5762  				Endpoints: []discovery.Endpoint{{
  5763  					Addresses: []string{"10.1.2.3", "2001:db8::1234:5678"},
  5764  				}, {
  5765  					Addresses: []string{"10.2.3.4", "2001:db8::2345:6789"},
  5766  				}},
  5767  			},
  5768  			// Columns: Name, AddressType, Ports, Endpoints, Age
  5769  			expected: []metav1.TableRow{{Cells: []interface{}{"multiportslice.123", "IPv4", "80,443,3000 + 1 more...", "10.1.2.3,2001:db8::1234:5678,10.2.3.4 + 1 more...", "5m"}}},
  5770  		},
  5771  	}
  5772  
  5773  	for i, test := range tests {
  5774  		rows, err := printEndpointSlice(&test.endpointSlice, printers.GenerateOptions{})
  5775  		if err != nil {
  5776  			t.Fatal(err)
  5777  		}
  5778  		for i := range rows {
  5779  			rows[i].Object.Object = nil
  5780  		}
  5781  		if !reflect.DeepEqual(test.expected, rows) {
  5782  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  5783  		}
  5784  	}
  5785  }
  5786  
  5787  func TestPrintFlowSchema(t *testing.T) {
  5788  	all := []string{"*"}
  5789  
  5790  	tests := []struct {
  5791  		fs       flowcontrol.FlowSchema
  5792  		expected []metav1.TableRow
  5793  	}{
  5794  		{
  5795  			fs: flowcontrol.FlowSchema{
  5796  				ObjectMeta: metav1.ObjectMeta{
  5797  					Name:              "all-matcher",
  5798  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5799  				},
  5800  				Spec: flowcontrol.FlowSchemaSpec{
  5801  					PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{Name: "allee"},
  5802  					MatchingPrecedence:         math.MaxInt32,
  5803  					DistinguisherMethod:        &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByUserType},
  5804  					Rules: []flowcontrol.PolicyRulesWithSubjects{{
  5805  						Subjects: []flowcontrol.Subject{{
  5806  							Kind:  flowcontrol.SubjectKindGroup,
  5807  							Group: &flowcontrol.GroupSubject{Name: "system:authenticated"},
  5808  						}},
  5809  						ResourceRules: []flowcontrol.ResourcePolicyRule{{
  5810  							Verbs:        all,
  5811  							APIGroups:    all,
  5812  							Resources:    all,
  5813  							ClusterScope: true,
  5814  							Namespaces:   all,
  5815  						}},
  5816  					}, {
  5817  						Subjects: []flowcontrol.Subject{{
  5818  							Kind:  flowcontrol.SubjectKindGroup,
  5819  							Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"},
  5820  						}},
  5821  						ResourceRules: []flowcontrol.ResourcePolicyRule{{
  5822  							Verbs:        all,
  5823  							APIGroups:    all,
  5824  							Resources:    all,
  5825  							ClusterScope: true,
  5826  							Namespaces:   all,
  5827  						}},
  5828  					}, {
  5829  						Subjects: []flowcontrol.Subject{{
  5830  							Kind:  flowcontrol.SubjectKindGroup,
  5831  							Group: &flowcontrol.GroupSubject{Name: "system:authenticated"},
  5832  						}, {
  5833  							Kind:  flowcontrol.SubjectKindGroup,
  5834  							Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"},
  5835  						}},
  5836  						NonResourceRules: []flowcontrol.NonResourcePolicyRule{{
  5837  							Verbs:           all,
  5838  							NonResourceURLs: all,
  5839  						}},
  5840  					}},
  5841  				},
  5842  			},
  5843  			// Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, Age, MissingPL
  5844  			expected: []metav1.TableRow{{Cells: []interface{}{"all-matcher", "allee", int64(math.MaxInt32), "ByUser", "0s", "?"}}},
  5845  		}, {
  5846  			fs: flowcontrol.FlowSchema{
  5847  				ObjectMeta: metav1.ObjectMeta{
  5848  					Name:              "some-matcher",
  5849  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5850  				},
  5851  				Spec: flowcontrol.FlowSchemaSpec{
  5852  					PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{Name: "allee"},
  5853  					MatchingPrecedence:         0,
  5854  					DistinguisherMethod:        &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByNamespaceType},
  5855  					Rules: []flowcontrol.PolicyRulesWithSubjects{{
  5856  						Subjects: []flowcontrol.Subject{{
  5857  							Kind:  flowcontrol.SubjectKindGroup,
  5858  							Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"},
  5859  						}},
  5860  						ResourceRules: []flowcontrol.ResourcePolicyRule{{
  5861  							Verbs:        all,
  5862  							APIGroups:    all,
  5863  							Resources:    all,
  5864  							ClusterScope: true,
  5865  							Namespaces:   all,
  5866  						}},
  5867  					}, {
  5868  						Subjects: []flowcontrol.Subject{{
  5869  							Kind:  flowcontrol.SubjectKindGroup,
  5870  							Group: &flowcontrol.GroupSubject{Name: "system:authenticated"},
  5871  						}, {
  5872  							Kind:  flowcontrol.SubjectKindGroup,
  5873  							Group: &flowcontrol.GroupSubject{Name: "system:unauthenticated"},
  5874  						}},
  5875  						NonResourceRules: []flowcontrol.NonResourcePolicyRule{{
  5876  							Verbs:           all,
  5877  							NonResourceURLs: all,
  5878  						}},
  5879  					}},
  5880  				},
  5881  				Status: flowcontrol.FlowSchemaStatus{
  5882  					Conditions: []flowcontrol.FlowSchemaCondition{{
  5883  						Type:               flowcontrol.FlowSchemaConditionDangling,
  5884  						Status:             "True",
  5885  						LastTransitionTime: metav1.Time{Time: time.Now().Add(-time.Hour)},
  5886  					}},
  5887  				},
  5888  			},
  5889  			// Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, Age, MissingPL
  5890  			expected: []metav1.TableRow{{Cells: []interface{}{"some-matcher", "allee", int64(0), "ByNamespace", "5m", "True"}}},
  5891  		}, {
  5892  			fs: flowcontrol.FlowSchema{
  5893  				ObjectMeta: metav1.ObjectMeta{
  5894  					Name:              "exempt",
  5895  					CreationTimestamp: metav1.Time{Time: time.Now().Add(-3e11)},
  5896  				},
  5897  				Spec: flowcontrol.FlowSchemaSpec{
  5898  					PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{Name: "allee"},
  5899  					MatchingPrecedence:         0,
  5900  					DistinguisherMethod:        nil,
  5901  					Rules: []flowcontrol.PolicyRulesWithSubjects{{
  5902  						Subjects: []flowcontrol.Subject{{
  5903  							Kind:  flowcontrol.SubjectKindGroup,
  5904  							Group: &flowcontrol.GroupSubject{Name: "system:masters"},
  5905  						}},
  5906  						ResourceRules: []flowcontrol.ResourcePolicyRule{{
  5907  							Verbs:        all,
  5908  							APIGroups:    all,
  5909  							Resources:    all,
  5910  							ClusterScope: true,
  5911  							Namespaces:   all,
  5912  						}},
  5913  					}},
  5914  				},
  5915  			},
  5916  			// Columns: Name, PriorityLevelName, MatchingPrecedence, DistinguisherMethod, Age, MissingPL
  5917  			expected: []metav1.TableRow{{Cells: []interface{}{"exempt", "allee", int64(0), "<none>", "5m", "?"}}},
  5918  		},
  5919  	}
  5920  
  5921  	for i, test := range tests {
  5922  		rows, err := printFlowSchema(&test.fs, printers.GenerateOptions{})
  5923  		if err != nil {
  5924  			t.Fatal(err)
  5925  		}
  5926  		for i := range rows {
  5927  			rows[i].Object.Object = nil
  5928  		}
  5929  		if !reflect.DeepEqual(test.expected, rows) {
  5930  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  5931  		}
  5932  	}
  5933  }
  5934  
  5935  func TestPrintPriorityLevelConfiguration(t *testing.T) {
  5936  	tests := []struct {
  5937  		pl       flowcontrol.PriorityLevelConfiguration
  5938  		expected []metav1.TableRow
  5939  	}{
  5940  		{
  5941  			pl: flowcontrol.PriorityLevelConfiguration{
  5942  				ObjectMeta: metav1.ObjectMeta{
  5943  					Name:              "unlimited",
  5944  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5945  				},
  5946  				Spec: flowcontrol.PriorityLevelConfigurationSpec{
  5947  					Type: flowcontrol.PriorityLevelEnablementExempt,
  5948  				},
  5949  			},
  5950  			// Columns: Name, Type, NominalConcurrencyShares, Queues, HandSize, QueueLengthLimit, Age
  5951  			expected: []metav1.TableRow{{Cells: []interface{}{"unlimited", "Exempt", "<none>", "<none>", "<none>", "<none>", "0s"}}},
  5952  		},
  5953  		{
  5954  			pl: flowcontrol.PriorityLevelConfiguration{
  5955  				ObjectMeta: metav1.ObjectMeta{
  5956  					Name:              "unqueued",
  5957  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5958  				},
  5959  				Spec: flowcontrol.PriorityLevelConfigurationSpec{
  5960  					Type: flowcontrol.PriorityLevelEnablementLimited,
  5961  					Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
  5962  						NominalConcurrencyShares: 47,
  5963  						LimitResponse: flowcontrol.LimitResponse{
  5964  							Type: flowcontrol.LimitResponseTypeReject,
  5965  						},
  5966  					},
  5967  				},
  5968  			},
  5969  			// Columns: Name, Type, NominalConcurrencyShares, Queues, HandSize, QueueLengthLimit, Age
  5970  			expected: []metav1.TableRow{{Cells: []interface{}{"unqueued", "Limited", int32(47), "<none>", "<none>", "<none>", "0s"}}},
  5971  		},
  5972  		{
  5973  			pl: flowcontrol.PriorityLevelConfiguration{
  5974  				ObjectMeta: metav1.ObjectMeta{
  5975  					Name:              "queued",
  5976  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  5977  				},
  5978  				Spec: flowcontrol.PriorityLevelConfigurationSpec{
  5979  					Type: flowcontrol.PriorityLevelEnablementLimited,
  5980  					Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
  5981  						NominalConcurrencyShares: 42,
  5982  						LimitResponse: flowcontrol.LimitResponse{
  5983  							Type: flowcontrol.LimitResponseTypeQueue,
  5984  							Queuing: &flowcontrol.QueuingConfiguration{
  5985  								Queues:           8,
  5986  								HandSize:         3,
  5987  								QueueLengthLimit: 4,
  5988  							},
  5989  						},
  5990  					},
  5991  				},
  5992  			},
  5993  			// Columns: Name, Type, NominalConcurrencyShares, Queues, HandSize, QueueLengthLimit, Age
  5994  			expected: []metav1.TableRow{{Cells: []interface{}{"queued", "Limited", int32(42), int32(8), int32(3), int32(4), "0s"}}},
  5995  		},
  5996  	}
  5997  
  5998  	for i, test := range tests {
  5999  		rows, err := printPriorityLevelConfiguration(&test.pl, printers.GenerateOptions{})
  6000  		if err != nil {
  6001  			t.Fatal(err)
  6002  		}
  6003  		for i := range rows {
  6004  			rows[i].Object.Object = nil
  6005  		}
  6006  		if !reflect.DeepEqual(test.expected, rows) {
  6007  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  6008  		}
  6009  	}
  6010  }
  6011  
  6012  func TestPrintStorageVersion(t *testing.T) {
  6013  	commonEncodingVersion := "v1"
  6014  	tests := []struct {
  6015  		sv       apiserverinternal.StorageVersion
  6016  		expected []metav1.TableRow
  6017  	}{
  6018  		{
  6019  			sv: apiserverinternal.StorageVersion{
  6020  				ObjectMeta: metav1.ObjectMeta{
  6021  					Name:              "empty",
  6022  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  6023  				},
  6024  				Status: apiserverinternal.StorageVersionStatus{},
  6025  			},
  6026  			// Columns: Name, CommonEncodingVersion, StorageVersions, Age
  6027  			expected: []metav1.TableRow{{Cells: []interface{}{"empty", "<unset>", "<unset>", "0s"}}},
  6028  		},
  6029  		{
  6030  			sv: apiserverinternal.StorageVersion{
  6031  				ObjectMeta: metav1.ObjectMeta{
  6032  					Name:              "valid",
  6033  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  6034  				},
  6035  				Status: apiserverinternal.StorageVersionStatus{
  6036  					StorageVersions: []apiserverinternal.ServerStorageVersion{
  6037  						{
  6038  							APIServerID:       "1",
  6039  							EncodingVersion:   "v1",
  6040  							DecodableVersions: []string{"v1"},
  6041  						},
  6042  						{
  6043  							APIServerID:       "2",
  6044  							EncodingVersion:   "v1",
  6045  							DecodableVersions: []string{"v1", "v2"},
  6046  						},
  6047  					},
  6048  					CommonEncodingVersion: &commonEncodingVersion,
  6049  				},
  6050  			},
  6051  			// Columns: Name, CommonEncodingVersion, StorageVersions, Age
  6052  			expected: []metav1.TableRow{{Cells: []interface{}{"valid", "v1", "1=v1,2=v1", "0s"}}},
  6053  		},
  6054  		{
  6055  			sv: apiserverinternal.StorageVersion{
  6056  				ObjectMeta: metav1.ObjectMeta{
  6057  					Name:              "disagree",
  6058  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  6059  				},
  6060  				Status: apiserverinternal.StorageVersionStatus{
  6061  					StorageVersions: []apiserverinternal.ServerStorageVersion{
  6062  						{
  6063  							APIServerID:       "1",
  6064  							EncodingVersion:   "v1",
  6065  							DecodableVersions: []string{"v1"},
  6066  						},
  6067  						{
  6068  							APIServerID:       "2",
  6069  							EncodingVersion:   "v1",
  6070  							DecodableVersions: []string{"v1", "v2"},
  6071  						},
  6072  						{
  6073  							APIServerID:       "3",
  6074  							EncodingVersion:   "v2",
  6075  							DecodableVersions: []string{"v2"},
  6076  						},
  6077  					},
  6078  				},
  6079  			},
  6080  			// Columns: Name, CommonEncodingVersion, StorageVersions, Age
  6081  			expected: []metav1.TableRow{{Cells: []interface{}{"disagree", "<unset>", "1=v1,2=v1,3=v2", "0s"}}},
  6082  		},
  6083  		{
  6084  			sv: apiserverinternal.StorageVersion{
  6085  				ObjectMeta: metav1.ObjectMeta{
  6086  					Name:              "agreeWithMore",
  6087  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  6088  				},
  6089  				Status: apiserverinternal.StorageVersionStatus{
  6090  					StorageVersions: []apiserverinternal.ServerStorageVersion{
  6091  						{
  6092  							APIServerID:       "1",
  6093  							EncodingVersion:   "v1",
  6094  							DecodableVersions: []string{"v1"},
  6095  						},
  6096  						{
  6097  							APIServerID:       "2",
  6098  							EncodingVersion:   "v1",
  6099  							DecodableVersions: []string{"v1", "v2"},
  6100  						},
  6101  						{
  6102  							APIServerID:       "3",
  6103  							EncodingVersion:   "v1",
  6104  							DecodableVersions: []string{"v1", "v2"},
  6105  						},
  6106  						{
  6107  							APIServerID:       "4",
  6108  							EncodingVersion:   "v1",
  6109  							DecodableVersions: []string{"v1", "v2", "v3alpha1"},
  6110  						},
  6111  					},
  6112  					CommonEncodingVersion: &commonEncodingVersion,
  6113  				},
  6114  			},
  6115  			// Columns: Name, CommonEncodingVersion, StorageVersions, Age
  6116  			expected: []metav1.TableRow{{Cells: []interface{}{"agreeWithMore", "v1", "1=v1,2=v1,3=v1 + 1 more...", "0s"}}},
  6117  		},
  6118  	}
  6119  
  6120  	for i, test := range tests {
  6121  		rows, err := printStorageVersion(&test.sv, printers.GenerateOptions{})
  6122  		if err != nil {
  6123  			t.Fatal(err)
  6124  		}
  6125  		for i := range rows {
  6126  			rows[i].Object.Object = nil
  6127  		}
  6128  		if !reflect.DeepEqual(test.expected, rows) {
  6129  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  6130  		}
  6131  	}
  6132  }
  6133  
  6134  func TestPrintScale(t *testing.T) {
  6135  	tests := []struct {
  6136  		scale    autoscaling.Scale
  6137  		options  printers.GenerateOptions
  6138  		expected []metav1.TableRow
  6139  	}{
  6140  		{
  6141  			scale: autoscaling.Scale{
  6142  				ObjectMeta: metav1.ObjectMeta{
  6143  					Name:              "test-autoscaling",
  6144  					CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
  6145  				},
  6146  				Spec:   autoscaling.ScaleSpec{Replicas: 2},
  6147  				Status: autoscaling.ScaleStatus{Replicas: 1},
  6148  			},
  6149  			expected: []metav1.TableRow{
  6150  				{
  6151  					Cells: []interface{}{"test-autoscaling", int64(2), int64(1), string("0s")},
  6152  				},
  6153  			},
  6154  		},
  6155  	}
  6156  
  6157  	for i, test := range tests {
  6158  		rows, err := printScale(&test.scale, test.options)
  6159  		if err != nil {
  6160  			t.Fatal(err)
  6161  		}
  6162  		for i := range rows {
  6163  			rows[i].Object.Object = nil
  6164  		}
  6165  		if !reflect.DeepEqual(test.expected, rows) {
  6166  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  6167  		}
  6168  	}
  6169  }
  6170  
  6171  func TestTableRowDeepCopyShouldNotPanic(t *testing.T) {
  6172  	tests := []struct {
  6173  		name    string
  6174  		printer func() ([]metav1.TableRow, error)
  6175  	}{
  6176  		{
  6177  			name: "Pod",
  6178  			printer: func() ([]metav1.TableRow, error) {
  6179  				return printPod(&api.Pod{}, printers.GenerateOptions{})
  6180  			},
  6181  		},
  6182  		{
  6183  			name: "PodTemplate",
  6184  			printer: func() ([]metav1.TableRow, error) {
  6185  				return printPodTemplate(&api.PodTemplate{}, printers.GenerateOptions{})
  6186  			},
  6187  		},
  6188  		{
  6189  			name: "PodDisruptionBudget",
  6190  			printer: func() ([]metav1.TableRow, error) {
  6191  				return printPodDisruptionBudget(&policy.PodDisruptionBudget{}, printers.GenerateOptions{})
  6192  			},
  6193  		},
  6194  		{
  6195  			name: "ReplicationController",
  6196  			printer: func() ([]metav1.TableRow, error) {
  6197  				return printReplicationController(&api.ReplicationController{}, printers.GenerateOptions{})
  6198  			},
  6199  		},
  6200  		{
  6201  			name: "ReplicaSet",
  6202  			printer: func() ([]metav1.TableRow, error) {
  6203  				return printReplicaSet(&apps.ReplicaSet{}, printers.GenerateOptions{})
  6204  			},
  6205  		},
  6206  		{
  6207  			name: "Job",
  6208  			printer: func() ([]metav1.TableRow, error) {
  6209  				return printJob(&batch.Job{}, printers.GenerateOptions{})
  6210  			},
  6211  		},
  6212  		{
  6213  			name: "CronJob",
  6214  			printer: func() ([]metav1.TableRow, error) {
  6215  				return printCronJob(&batch.CronJob{}, printers.GenerateOptions{})
  6216  			},
  6217  		},
  6218  		{
  6219  			name: "Service",
  6220  			printer: func() ([]metav1.TableRow, error) {
  6221  				return printService(&api.Service{}, printers.GenerateOptions{})
  6222  			},
  6223  		},
  6224  		{
  6225  			name: "Ingress",
  6226  			printer: func() ([]metav1.TableRow, error) {
  6227  				return printIngress(&networking.Ingress{}, printers.GenerateOptions{})
  6228  			},
  6229  		},
  6230  		{
  6231  			name: "IngressClass",
  6232  			printer: func() ([]metav1.TableRow, error) {
  6233  				return printIngressClass(&networking.IngressClass{}, printers.GenerateOptions{})
  6234  			},
  6235  		},
  6236  		{
  6237  			name: "StatefulSet",
  6238  			printer: func() ([]metav1.TableRow, error) {
  6239  				return printStatefulSet(&apps.StatefulSet{}, printers.GenerateOptions{})
  6240  			},
  6241  		},
  6242  		{
  6243  			name: "DaemonSet",
  6244  			printer: func() ([]metav1.TableRow, error) {
  6245  				return printDaemonSet(&apps.DaemonSet{}, printers.GenerateOptions{})
  6246  			},
  6247  		},
  6248  		{
  6249  			name: "Endpoints",
  6250  			printer: func() ([]metav1.TableRow, error) {
  6251  				return printEndpoints(&api.Endpoints{}, printers.GenerateOptions{})
  6252  			},
  6253  		},
  6254  		{
  6255  			name: "EndpointSlice",
  6256  			printer: func() ([]metav1.TableRow, error) {
  6257  				return printEndpointSlice(&discovery.EndpointSlice{}, printers.GenerateOptions{})
  6258  			},
  6259  		},
  6260  		{
  6261  			name: "CSINode",
  6262  			printer: func() ([]metav1.TableRow, error) {
  6263  				return printCSINode(&storage.CSINode{}, printers.GenerateOptions{})
  6264  			},
  6265  		},
  6266  		{
  6267  			name: "CSIDriver",
  6268  			printer: func() ([]metav1.TableRow, error) {
  6269  				return printCSIDriver(&storage.CSIDriver{}, printers.GenerateOptions{})
  6270  			},
  6271  		},
  6272  		{
  6273  			name: "CSIStorageCapacity",
  6274  			printer: func() ([]metav1.TableRow, error) {
  6275  				return printCSIStorageCapacity(&storage.CSIStorageCapacity{}, printers.GenerateOptions{})
  6276  			},
  6277  		},
  6278  		{
  6279  			name: "MutatingWebhookConfiguration",
  6280  			printer: func() ([]metav1.TableRow, error) {
  6281  				return printMutatingWebhook(&admissionregistration.MutatingWebhookConfiguration{}, printers.GenerateOptions{})
  6282  			},
  6283  		},
  6284  		{
  6285  			name: "ValidatingWebhookConfiguration",
  6286  			printer: func() ([]metav1.TableRow, error) {
  6287  				return printValidatingWebhook(&admissionregistration.ValidatingWebhookConfiguration{}, printers.GenerateOptions{})
  6288  			},
  6289  		},
  6290  		{
  6291  			name: "ValidatingAdmissionPolicy",
  6292  			printer: func() ([]metav1.TableRow, error) {
  6293  				return printValidatingAdmissionPolicy(&admissionregistration.ValidatingAdmissionPolicy{}, printers.GenerateOptions{})
  6294  			},
  6295  		},
  6296  		{
  6297  			name: "ValidatingAdmissionPolicyBinding",
  6298  			printer: func() ([]metav1.TableRow, error) {
  6299  				return printValidatingAdmissionPolicyBinding(&admissionregistration.ValidatingAdmissionPolicyBinding{}, printers.GenerateOptions{})
  6300  			},
  6301  		},
  6302  		{
  6303  			name: "Namespace",
  6304  			printer: func() ([]metav1.TableRow, error) {
  6305  				return printNamespace(&api.Namespace{}, printers.GenerateOptions{})
  6306  			},
  6307  		},
  6308  		{
  6309  			name: "Secret",
  6310  			printer: func() ([]metav1.TableRow, error) {
  6311  				return printSecret(&api.Secret{}, printers.GenerateOptions{})
  6312  			},
  6313  		},
  6314  		{
  6315  			name: "ServiceAccount",
  6316  			printer: func() ([]metav1.TableRow, error) {
  6317  				return printServiceAccount(&api.ServiceAccount{}, printers.GenerateOptions{})
  6318  			},
  6319  		},
  6320  		{
  6321  			name: "Node",
  6322  			printer: func() ([]metav1.TableRow, error) {
  6323  				return printNode(&api.Node{}, printers.GenerateOptions{})
  6324  			},
  6325  		},
  6326  		{
  6327  			name: "PersistentVolume",
  6328  			printer: func() ([]metav1.TableRow, error) {
  6329  				return printPersistentVolume(&api.PersistentVolume{}, printers.GenerateOptions{})
  6330  			},
  6331  		},
  6332  		{
  6333  			name: "PersistentVolumeClaim",
  6334  			printer: func() ([]metav1.TableRow, error) {
  6335  				return printPersistentVolumeClaim(&api.PersistentVolumeClaim{}, printers.GenerateOptions{})
  6336  			},
  6337  		},
  6338  		{
  6339  			name: "Event",
  6340  			printer: func() ([]metav1.TableRow, error) {
  6341  				return printEvent(&api.Event{}, printers.GenerateOptions{})
  6342  			},
  6343  		},
  6344  		{
  6345  			name: "RoleBinding",
  6346  			printer: func() ([]metav1.TableRow, error) {
  6347  				return printRoleBinding(&rbac.RoleBinding{}, printers.GenerateOptions{})
  6348  			},
  6349  		},
  6350  		{
  6351  			name: "ClusterRoleBinding",
  6352  			printer: func() ([]metav1.TableRow, error) {
  6353  				return printClusterRoleBinding(&rbac.ClusterRoleBinding{}, printers.GenerateOptions{})
  6354  			},
  6355  		},
  6356  		{
  6357  			name: "CertificateSigningRequest",
  6358  			printer: func() ([]metav1.TableRow, error) {
  6359  				return printCertificateSigningRequest(&certificates.CertificateSigningRequest{}, printers.GenerateOptions{})
  6360  			},
  6361  		},
  6362  		{
  6363  			name: "ComponentStatus",
  6364  			printer: func() ([]metav1.TableRow, error) {
  6365  				return printComponentStatus(&api.ComponentStatus{}, printers.GenerateOptions{})
  6366  			},
  6367  		},
  6368  		{
  6369  			name: "Deployment",
  6370  			printer: func() ([]metav1.TableRow, error) {
  6371  				return printDeployment(&apps.Deployment{}, printers.GenerateOptions{})
  6372  			},
  6373  		},
  6374  		{
  6375  			name: "HorizontalPodAutoscaler",
  6376  			printer: func() ([]metav1.TableRow, error) {
  6377  				return printHorizontalPodAutoscaler(&autoscaling.HorizontalPodAutoscaler{}, printers.GenerateOptions{})
  6378  			},
  6379  		},
  6380  		{
  6381  			name: "ConfigMap",
  6382  			printer: func() ([]metav1.TableRow, error) {
  6383  				return printConfigMap(&api.ConfigMap{}, printers.GenerateOptions{})
  6384  			},
  6385  		},
  6386  		{
  6387  			name: "NetworkPolicy",
  6388  			printer: func() ([]metav1.TableRow, error) {
  6389  				return printNetworkPolicy(&networking.NetworkPolicy{}, printers.GenerateOptions{})
  6390  			},
  6391  		},
  6392  		{
  6393  			name: "StorageClass",
  6394  			printer: func() ([]metav1.TableRow, error) {
  6395  				return printStorageClass(&storage.StorageClass{}, printers.GenerateOptions{})
  6396  			},
  6397  		},
  6398  		{
  6399  			name: "Lease",
  6400  			printer: func() ([]metav1.TableRow, error) {
  6401  				return printLease(&coordination.Lease{}, printers.GenerateOptions{})
  6402  			},
  6403  		},
  6404  		{
  6405  			name: "ControllerRevision",
  6406  			printer: func() ([]metav1.TableRow, error) {
  6407  				return printControllerRevision(&apps.ControllerRevision{}, printers.GenerateOptions{})
  6408  			},
  6409  		},
  6410  		{
  6411  			name: "ResourceQuota",
  6412  			printer: func() ([]metav1.TableRow, error) {
  6413  				return printResourceQuota(&api.ResourceQuota{}, printers.GenerateOptions{})
  6414  			},
  6415  		},
  6416  		{
  6417  			name: "PriorityClass",
  6418  			printer: func() ([]metav1.TableRow, error) {
  6419  				return printPriorityClass(&scheduling.PriorityClass{}, printers.GenerateOptions{})
  6420  			},
  6421  		},
  6422  		{
  6423  			name: "RuntimeClass",
  6424  			printer: func() ([]metav1.TableRow, error) {
  6425  				return printRuntimeClass(&nodeapi.RuntimeClass{}, printers.GenerateOptions{})
  6426  			},
  6427  		},
  6428  		{
  6429  			name: "VolumeAttachment",
  6430  			printer: func() ([]metav1.TableRow, error) {
  6431  				return printVolumeAttachment(&storage.VolumeAttachment{}, printers.GenerateOptions{})
  6432  			},
  6433  		},
  6434  		{
  6435  			name: "FlowSchema",
  6436  			printer: func() ([]metav1.TableRow, error) {
  6437  				return printFlowSchema(&flowcontrol.FlowSchema{}, printers.GenerateOptions{})
  6438  			},
  6439  		},
  6440  		{
  6441  			name: "StorageVersion",
  6442  			printer: func() ([]metav1.TableRow, error) {
  6443  				return printStorageVersion(&apiserverinternal.StorageVersion{}, printers.GenerateOptions{})
  6444  			},
  6445  		},
  6446  		{
  6447  			name: "PriorityLevelConfiguration",
  6448  			printer: func() ([]metav1.TableRow, error) {
  6449  				return printPriorityLevelConfiguration(&flowcontrol.PriorityLevelConfiguration{}, printers.GenerateOptions{})
  6450  			},
  6451  		},
  6452  		{
  6453  			name: "Scale",
  6454  			printer: func() ([]metav1.TableRow, error) {
  6455  				return printScale(&autoscaling.Scale{}, printers.GenerateOptions{})
  6456  			},
  6457  		},
  6458  		{
  6459  			name: "Status",
  6460  			printer: func() ([]metav1.TableRow, error) {
  6461  				return printStatus(&metav1.Status{}, printers.GenerateOptions{})
  6462  			},
  6463  		},
  6464  	}
  6465  
  6466  	for _, test := range tests {
  6467  		t.Run(test.name, func(t *testing.T) {
  6468  			rows, err := test.printer()
  6469  			if err != nil {
  6470  				t.Fatalf("expected no error, but got: %#v", err)
  6471  			}
  6472  			if len(rows) <= 0 {
  6473  				t.Fatalf("expected to have at least one TableRow, but got: %d", len(rows))
  6474  			}
  6475  
  6476  			func() {
  6477  				defer func() {
  6478  					if err := recover(); err != nil {
  6479  						// Same as stdlib http server code. Manually allocate stack
  6480  						// trace buffer size to prevent excessively large logs
  6481  						const size = 64 << 10
  6482  						buf := make([]byte, size)
  6483  						buf = buf[:runtime.Stack(buf, false)]
  6484  						err = fmt.Errorf("%q stack:\n%s", err, buf)
  6485  
  6486  						t.Errorf("Expected no panic, but got: %v", err)
  6487  					}
  6488  				}()
  6489  
  6490  				// should not panic
  6491  				rows[0].DeepCopy()
  6492  			}()
  6493  
  6494  		})
  6495  	}
  6496  }
  6497  
  6498  func TestPrintIPAddress(t *testing.T) {
  6499  	ip := networking.IPAddress{
  6500  		ObjectMeta: metav1.ObjectMeta{
  6501  			Name:              "192.168.2.2",
  6502  			CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
  6503  		},
  6504  		Spec: networking.IPAddressSpec{
  6505  			ParentRef: &networking.ParentReference{
  6506  				Group:     "mygroup",
  6507  				Resource:  "myresource",
  6508  				Namespace: "mynamespace",
  6509  				Name:      "myname",
  6510  			},
  6511  		},
  6512  	}
  6513  	// Columns: Name, ParentRef, Age
  6514  	expected := []metav1.TableRow{{Cells: []interface{}{"192.168.2.2", "myresource.mygroup/mynamespace/myname", "10y"}}}
  6515  
  6516  	rows, err := printIPAddress(&ip, printers.GenerateOptions{})
  6517  	if err != nil {
  6518  		t.Fatalf("Error generating table rows for IPAddress: %#v", err)
  6519  	}
  6520  	rows[0].Object.Object = nil
  6521  	if !reflect.DeepEqual(expected, rows) {
  6522  		t.Errorf("mismatch: %s", cmp.Diff(expected, rows))
  6523  	}
  6524  }
  6525  
  6526  func TestPrintIPAddressList(t *testing.T) {
  6527  	ipList := networking.IPAddressList{
  6528  		Items: []networking.IPAddress{
  6529  			{
  6530  				ObjectMeta: metav1.ObjectMeta{
  6531  					Name:              "192.168.2.2",
  6532  					CreationTimestamp: metav1.Time{},
  6533  				},
  6534  				Spec: networking.IPAddressSpec{
  6535  					ParentRef: &networking.ParentReference{
  6536  						Group:     "mygroup",
  6537  						Resource:  "myresource",
  6538  						Namespace: "mynamespace",
  6539  						Name:      "myname",
  6540  					},
  6541  				},
  6542  			}, {
  6543  				ObjectMeta: metav1.ObjectMeta{
  6544  					Name:              "2001:db8::2",
  6545  					CreationTimestamp: metav1.Time{},
  6546  				},
  6547  				Spec: networking.IPAddressSpec{
  6548  					ParentRef: &networking.ParentReference{
  6549  						Group:     "mygroup2",
  6550  						Resource:  "myresource2",
  6551  						Namespace: "mynamespace2",
  6552  						Name:      "myname2",
  6553  					},
  6554  				},
  6555  			},
  6556  		},
  6557  	}
  6558  	// Columns: Name, ParentRef, Age
  6559  	expected := []metav1.TableRow{
  6560  		{Cells: []interface{}{"192.168.2.2", "myresource.mygroup/mynamespace/myname", "<unknown>"}},
  6561  		{Cells: []interface{}{"2001:db8::2", "myresource2.mygroup2/mynamespace2/myname2", "<unknown>"}},
  6562  	}
  6563  
  6564  	rows, err := printIPAddressList(&ipList, printers.GenerateOptions{})
  6565  	if err != nil {
  6566  		t.Fatalf("Error generating table rows for IPAddress: %#v", err)
  6567  	}
  6568  	for i := range rows {
  6569  		rows[i].Object.Object = nil
  6570  
  6571  	}
  6572  	if !reflect.DeepEqual(expected, rows) {
  6573  		t.Errorf("mismatch: %s", cmp.Diff(expected, rows))
  6574  	}
  6575  
  6576  }
  6577  
  6578  func TestPrintServiceCIDR(t *testing.T) {
  6579  	ipv4CIDR := "10.1.0.0/16"
  6580  	ipv6CIDR := "fd00:1:1::/64"
  6581  
  6582  	tests := []struct {
  6583  		ccc      networking.ServiceCIDR
  6584  		options  printers.GenerateOptions
  6585  		expected []metav1.TableRow
  6586  	}{
  6587  		{
  6588  			// Test name, IPv4 only.
  6589  			ccc: networking.ServiceCIDR{
  6590  				ObjectMeta: metav1.ObjectMeta{Name: "test1"},
  6591  				Spec: networking.ServiceCIDRSpec{
  6592  					CIDRs: []string{ipv4CIDR},
  6593  				},
  6594  			},
  6595  			options: printers.GenerateOptions{},
  6596  			// Columns: Name, IPv4, IPv6, Age.
  6597  			expected: []metav1.TableRow{{Cells: []interface{}{"test1", ipv4CIDR, "<unknown>"}}},
  6598  		},
  6599  		{
  6600  			// Test name, IPv6 only.
  6601  			ccc: networking.ServiceCIDR{
  6602  				ObjectMeta: metav1.ObjectMeta{Name: "test5"},
  6603  				Spec: networking.ServiceCIDRSpec{
  6604  					CIDRs: []string{ipv6CIDR},
  6605  				},
  6606  			},
  6607  			options: printers.GenerateOptions{},
  6608  			// Columns: Name, PerNodeHostBits, IPv4, IPv6, Age
  6609  			expected: []metav1.TableRow{{Cells: []interface{}{"test5", ipv6CIDR, "<unknown>"}}},
  6610  		},
  6611  		{
  6612  			// Test name, DualStack.
  6613  			ccc: networking.ServiceCIDR{
  6614  				ObjectMeta: metav1.ObjectMeta{Name: "test9"},
  6615  				Spec: networking.ServiceCIDRSpec{
  6616  					CIDRs: []string{ipv4CIDR, ipv6CIDR},
  6617  				},
  6618  			},
  6619  			options: printers.GenerateOptions{},
  6620  			// Columns: Name, PerNodeHostBits, IPv4, IPv6, Age.
  6621  			expected: []metav1.TableRow{{Cells: []interface{}{"test9", ipv4CIDR + "," + ipv6CIDR, "<unknown>"}}},
  6622  		},
  6623  	}
  6624  
  6625  	for i, test := range tests {
  6626  		rows, err := printServiceCIDR(&test.ccc, test.options)
  6627  		if err != nil {
  6628  			t.Fatal(err)
  6629  		}
  6630  		for i := range rows {
  6631  			rows[i].Object.Object = nil
  6632  		}
  6633  		if !reflect.DeepEqual(test.expected, rows) {
  6634  			t.Errorf("%d mismatch: %s", i, cmp.Diff(test.expected, rows))
  6635  		}
  6636  	}
  6637  }
  6638  
  6639  func TestPrintServiceCIDRList(t *testing.T) {
  6640  	cccList := networking.ServiceCIDRList{
  6641  		Items: []networking.ServiceCIDR{
  6642  			{
  6643  				ObjectMeta: metav1.ObjectMeta{Name: "ccc1"},
  6644  				Spec: networking.ServiceCIDRSpec{
  6645  					CIDRs: []string{"10.1.0.0/16", "fd00:1:1::/64"},
  6646  				},
  6647  			},
  6648  			{
  6649  				ObjectMeta: metav1.ObjectMeta{Name: "ccc2"},
  6650  				Spec: networking.ServiceCIDRSpec{
  6651  					CIDRs: []string{"10.2.0.0/16", "fd00:2:1::/64"},
  6652  				},
  6653  			},
  6654  		},
  6655  	}
  6656  
  6657  	tests := []struct {
  6658  		options  printers.GenerateOptions
  6659  		expected []metav1.TableRow
  6660  	}{
  6661  		{
  6662  			// Test name, DualStack with node selector, wide.
  6663  			options: printers.GenerateOptions{Wide: false},
  6664  			expected: []metav1.TableRow{
  6665  				// Columns: Name, IPv4, IPv6, Age.
  6666  				{Cells: []interface{}{"ccc1", "10.1.0.0/16,fd00:1:1::/64", "<unknown>"}},
  6667  				{Cells: []interface{}{"ccc2", "10.2.0.0/16,fd00:2:1::/64", "<unknown>"}},
  6668  			},
  6669  		},
  6670  		{
  6671  			// Test name, DualStack with node selector, wide.
  6672  			options: printers.GenerateOptions{Wide: true},
  6673  			expected: []metav1.TableRow{
  6674  				// Columns: Name, CIDRs, Age.
  6675  				{Cells: []interface{}{"ccc1", "10.1.0.0/16,fd00:1:1::/64", "<unknown>"}},
  6676  				{Cells: []interface{}{"ccc2", "10.2.0.0/16,fd00:2:1::/64", "<unknown>"}},
  6677  			},
  6678  		},
  6679  	}
  6680  
  6681  	for _, test := range tests {
  6682  		rows, err := printServiceCIDRList(&cccList, test.options)
  6683  		if err != nil {
  6684  			t.Fatalf("Error printing service list: %#v", err)
  6685  		}
  6686  		for i := range rows {
  6687  			rows[i].Object.Object = nil
  6688  		}
  6689  		if !reflect.DeepEqual(test.expected, rows) {
  6690  			t.Errorf("mismatch: %s", cmp.Diff(test.expected, rows))
  6691  		}
  6692  	}
  6693  }