...

Source file src/sigs.k8s.io/cli-utils/pkg/print/table/columndefs_test.go

Documentation: sigs.k8s.io/cli-utils/pkg/print/table

     1  // Copyright 2020 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package table
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"testing"
    10  	"time"
    11  
    12  	v1 "k8s.io/api/core/v1"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    15  	"k8s.io/apimachinery/pkg/runtime/schema"
    16  	pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
    17  	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
    18  	"sigs.k8s.io/cli-utils/pkg/object"
    19  )
    20  
    21  func TestColumnDefs(t *testing.T) {
    22  	testCases := map[string]struct {
    23  		columnName     string
    24  		resource       Resource
    25  		columnWidth    int
    26  		expectedOutput string
    27  	}{
    28  		"namespace": {
    29  			columnName: "namespace",
    30  			resource: &fakeResource{
    31  				resourceStatus: &pe.ResourceStatus{
    32  					Identifier: object.ObjMetadata{
    33  						Namespace: "Foo",
    34  					},
    35  				},
    36  			},
    37  			columnWidth:    10,
    38  			expectedOutput: "Foo",
    39  		},
    40  		"namespace trimmed": {
    41  			columnName: "namespace",
    42  			resource: &fakeResource{
    43  				resourceStatus: &pe.ResourceStatus{
    44  					Identifier: object.ObjMetadata{
    45  						Namespace: "ICanHearTheHeartBeatingAsOne",
    46  					},
    47  				},
    48  			},
    49  			columnWidth:    10,
    50  			expectedOutput: "ICanHearTh",
    51  		},
    52  		"resource": {
    53  			columnName: "resource",
    54  			resource: &fakeResource{
    55  				resourceStatus: &pe.ResourceStatus{
    56  					Identifier: object.ObjMetadata{
    57  						Name: "YoLaTengo",
    58  						GroupKind: schema.GroupKind{
    59  							Kind: "RoleBinding",
    60  						},
    61  					},
    62  				},
    63  			},
    64  			columnWidth:    40,
    65  			expectedOutput: "RoleBinding/YoLaTengo",
    66  		},
    67  		"resource trimmed": {
    68  			columnName: "resource",
    69  			resource: &fakeResource{
    70  				resourceStatus: &pe.ResourceStatus{
    71  					Identifier: object.ObjMetadata{
    72  						Name: "SlantedAndEnchanted",
    73  						GroupKind: schema.GroupKind{
    74  							Kind: "Pavement",
    75  						},
    76  					},
    77  				},
    78  			},
    79  			columnWidth:    25,
    80  			expectedOutput: "Pavement/SlantedAndEnchan",
    81  		},
    82  		"status with color": {
    83  			columnName: "status",
    84  			resource: &fakeResource{
    85  				resourceStatus: &pe.ResourceStatus{
    86  					Status: status.CurrentStatus,
    87  				},
    88  			},
    89  			columnWidth:    10,
    90  			expectedOutput: "\x1b[32mCurrent\x1b[0m",
    91  		},
    92  		"status trimmed": {
    93  			columnName: "status",
    94  			resource: &fakeResource{
    95  				resourceStatus: &pe.ResourceStatus{
    96  					Status: status.NotFoundStatus,
    97  				},
    98  			},
    99  			columnWidth:    5,
   100  			expectedOutput: "NotFo",
   101  		},
   102  		"conditions with color": {
   103  			columnName: "conditions",
   104  			resource: &fakeResource{
   105  				resourceStatus: &pe.ResourceStatus{
   106  					Resource: mustResourceWithConditions([]condition{
   107  						{
   108  							Type:   "Ready",
   109  							Status: v1.ConditionUnknown,
   110  						},
   111  					}),
   112  				},
   113  			},
   114  			columnWidth:    10,
   115  			expectedOutput: "\x1b[33mReady\x1b[0m",
   116  		},
   117  		"conditions trimmed": {
   118  			columnName: "conditions",
   119  			resource: &fakeResource{
   120  				resourceStatus: &pe.ResourceStatus{
   121  					Resource: mustResourceWithConditions([]condition{
   122  						{
   123  							Type:   "Ready",
   124  							Status: v1.ConditionTrue,
   125  						},
   126  						{
   127  							Type:   "Reconciling",
   128  							Status: v1.ConditionFalse,
   129  						},
   130  					}),
   131  				},
   132  			},
   133  			columnWidth:    10,
   134  			expectedOutput: "\x1b[32mReady\x1b[0m,\x1b[31mReco\x1b[0m",
   135  		},
   136  		"age not found": {
   137  			columnName: "age",
   138  			resource: &fakeResource{
   139  				resourceStatus: &pe.ResourceStatus{
   140  					Resource: &unstructured.Unstructured{},
   141  				},
   142  			},
   143  			columnWidth:    10,
   144  			expectedOutput: "-",
   145  		},
   146  		"age": {
   147  			columnName: "age",
   148  			resource: &fakeResource{
   149  				resourceStatus: &pe.ResourceStatus{
   150  					Resource: mustResourceWithCreationTimestamp(45 * time.Minute),
   151  				},
   152  			},
   153  			columnWidth:    10,
   154  			expectedOutput: "45m",
   155  		},
   156  		"message without error": {
   157  			columnName: "message",
   158  			resource: &fakeResource{
   159  				resourceStatus: &pe.ResourceStatus{
   160  					Message: "this is a test",
   161  				},
   162  			},
   163  			columnWidth:    30,
   164  			expectedOutput: "this is a test",
   165  		},
   166  		"message from error": {
   167  			columnName: "message",
   168  			resource: &fakeResource{
   169  				resourceStatus: &pe.ResourceStatus{
   170  					Message: "this is a test",
   171  					Error:   fmt.Errorf("something went wrong somewhere"),
   172  				},
   173  			},
   174  			columnWidth:    50,
   175  			expectedOutput: "something went wrong somewhere",
   176  		},
   177  		"message trimmed": {
   178  			columnName: "message",
   179  			resource: &fakeResource{
   180  				resourceStatus: &pe.ResourceStatus{
   181  					Message: "this is a test",
   182  				},
   183  			},
   184  			columnWidth:    6,
   185  			expectedOutput: "this i",
   186  		},
   187  	}
   188  
   189  	for tn, tc := range testCases {
   190  		t.Run(tn, func(t *testing.T) {
   191  			columnDef := MustColumn(tc.columnName)
   192  
   193  			var buf bytes.Buffer
   194  			_, err := columnDef.PrintResource(&buf, tc.columnWidth, tc.resource)
   195  			if err != nil {
   196  				t.Error(err)
   197  			}
   198  
   199  			if want, got := tc.expectedOutput, buf.String(); want != got {
   200  				t.Errorf("expected %q, but got %q", want, got)
   201  			}
   202  		})
   203  	}
   204  }
   205  
   206  type condition struct {
   207  	Type   string
   208  	Status v1.ConditionStatus
   209  }
   210  
   211  func mustResourceWithConditions(conditions []condition) *unstructured.Unstructured {
   212  	u := &unstructured.Unstructured{
   213  		Object: make(map[string]interface{}),
   214  	}
   215  	var conditionsSlice []interface{}
   216  	for _, c := range conditions {
   217  		cond := make(map[string]interface{})
   218  		cond["type"] = c.Type
   219  		cond["status"] = string(c.Status)
   220  		conditionsSlice = append(conditionsSlice, cond)
   221  	}
   222  	err := unstructured.SetNestedSlice(u.Object, conditionsSlice,
   223  		"status", "conditions")
   224  	if err != nil {
   225  		panic(err)
   226  	}
   227  	return u
   228  }
   229  
   230  func mustResourceWithCreationTimestamp(age time.Duration) *unstructured.Unstructured {
   231  	u := &unstructured.Unstructured{
   232  		Object: make(map[string]interface{}),
   233  	}
   234  	creationTime := time.Now().Add(-age)
   235  	u.SetCreationTimestamp(metav1.Time{
   236  		Time: creationTime,
   237  	})
   238  	return u
   239  }
   240  

View as plain text