...

Source file src/k8s.io/cli-runtime/pkg/printers/tableprinter_test.go

Documentation: k8s.io/cli-runtime/pkg/printers

     1  /*
     2  Copyright 2019 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 printers
    18  
    19  import (
    20  	"bytes"
    21  	"regexp"
    22  	"testing"
    23  
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  )
    30  
    31  var testPodName = "test-pod-name"
    32  var testPodNamespace = "test-namespace"
    33  
    34  var testPod = &corev1.Pod{
    35  	TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
    36  	ObjectMeta: metav1.ObjectMeta{
    37  		Name:      testPodName,
    38  		Namespace: testPodNamespace,
    39  		Labels: map[string]string{
    40  			"first-label":  "12",
    41  			"second-label": "label-value",
    42  		},
    43  	},
    44  }
    45  
    46  var testStatus = &metav1.Status{
    47  	TypeMeta: metav1.TypeMeta{APIVersion: "metav1", Kind: "Status"},
    48  	Status:   "Failure",
    49  	Message:  "test-status-message",
    50  	Reason:   "test-status-reason",
    51  }
    52  
    53  func TestPrintTable_MissingColumnsRows(t *testing.T) {
    54  	tests := []struct {
    55  		columns  []metav1.TableColumnDefinition
    56  		rows     []metav1.TableRow
    57  		options  PrintOptions
    58  		expected string
    59  	}{
    60  		// No columns and no rows means table string is empty.
    61  		{
    62  			columns:  []metav1.TableColumnDefinition{},
    63  			rows:     []metav1.TableRow{},
    64  			options:  PrintOptions{},
    65  			expected: "",
    66  		},
    67  		// No rows, means table string is empty (columns aren't printed).
    68  		{
    69  			columns: []metav1.TableColumnDefinition{
    70  				{Name: "Name", Type: "string"},
    71  				{Name: "Ready", Type: "string"},
    72  				{Name: "Status", Type: "string"},
    73  				{Name: "Retries", Type: "integer"},
    74  				{Name: "Age", Type: "string"},
    75  			},
    76  			rows:     []metav1.TableRow{},
    77  			options:  PrintOptions{},
    78  			expected: "",
    79  		},
    80  	}
    81  
    82  	for _, test := range tests {
    83  		// Create the table from the columns and rows.
    84  		table := &metav1.Table{
    85  			ColumnDefinitions: test.columns,
    86  			Rows:              test.rows,
    87  		}
    88  		// Print the table
    89  		out := bytes.NewBuffer([]byte{})
    90  		printer := NewTablePrinter(test.options)
    91  		printer.PrintObj(table, out)
    92  
    93  		// Validate the printed table is empty.
    94  		if len(out.String()) > 0 {
    95  			t.Errorf("Error Printing Table. Should be empty; got (%s)", out.String())
    96  		}
    97  	}
    98  }
    99  
   100  func TestPrintTable_ColumnPriority(t *testing.T) {
   101  	tests := []struct {
   102  		columns  []metav1.TableColumnDefinition
   103  		rows     []metav1.TableRow
   104  		options  PrintOptions
   105  		expected string
   106  	}{
   107  		// Test a basic single row table. Columns with priority > 0 are not printed.
   108  		{
   109  			columns: []metav1.TableColumnDefinition{
   110  				{Name: "Name", Type: "string"},
   111  				{Name: "Ready", Type: "string"},
   112  				{Name: "Status", Type: "string"},
   113  				{Name: "Retries", Type: "integer", Priority: 1}, // Priority > 0
   114  				{Name: "Age", Type: "string", Priority: 1},      // Priority > 0
   115  			},
   116  			rows: []metav1.TableRow{
   117  				{Cells: []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"}},
   118  			},
   119  			options:  PrintOptions{},
   120  			expected: "NAME    READY   STATUS\ntest1   1/1     podPhase\n",
   121  		},
   122  		// Test a basic multi-row table row table. Columns with priority > 0 are not printed.
   123  		{
   124  			columns: []metav1.TableColumnDefinition{
   125  				{Name: "Name", Type: "string"},
   126  				{Name: "Ready", Type: "string"},
   127  				{Name: "Status", Type: "string"},
   128  				{Name: "Retries", Type: "integer", Priority: 1}, // Priority > 0
   129  				{Name: "Age", Type: "string", Priority: 1},      // Priority > 0
   130  			},
   131  			rows: []metav1.TableRow{
   132  				{Cells: []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"}},
   133  				{Cells: []interface{}{"test2", "1/2", "podPhase", int64(30), "21h"}},
   134  				{Cells: []interface{}{"test3", "4/4", "podPhase", int64(1), "22h"}},
   135  			},
   136  			options: PrintOptions{},
   137  			expected: `NAME    READY   STATUS
   138  test1   1/1     podPhase
   139  test2   1/2     podPhase
   140  test3   4/4     podPhase
   141  `,
   142  		},
   143  		// Test a single row table with "wide" printing option. Columns with priority > 0 are printed.
   144  		{
   145  			columns: []metav1.TableColumnDefinition{
   146  				{Name: "Name", Type: "string"},
   147  				{Name: "Ready", Type: "string"},
   148  				{Name: "Status", Type: "string"},
   149  				{Name: "Retries", Type: "integer", Priority: 1},
   150  				{Name: "Age", Type: "string", Priority: 1},
   151  			},
   152  			rows: []metav1.TableRow{
   153  				{Cells: []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"}},
   154  			},
   155  			// Print with no headers.
   156  			options:  PrintOptions{Wide: true},
   157  			expected: "NAME    READY   STATUS     RETRIES   AGE\ntest1   1/1     podPhase   5         20h\n",
   158  		},
   159  		// Test a multi-row table row table with "wide" printing option. Columns with priority > 0 are printed.
   160  		{
   161  			columns: []metav1.TableColumnDefinition{
   162  				{Name: "Name", Type: "string"},
   163  				{Name: "Ready", Type: "string"},
   164  				{Name: "Status", Type: "string"},
   165  				{Name: "Retries", Type: "integer", Priority: 1},
   166  				{Name: "Age", Type: "string", Priority: 1},
   167  			},
   168  			rows: []metav1.TableRow{
   169  				{Cells: []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"}},
   170  				{Cells: []interface{}{"test2", "1/2", "podPhase", int64(30), "21h"}},
   171  				{Cells: []interface{}{"test3", "4/4", "podPhase", int64(1), "22h"}},
   172  			},
   173  			options: PrintOptions{Wide: true},
   174  			expected: `NAME    READY   STATUS     RETRIES   AGE
   175  test1   1/1     podPhase   5         20h
   176  test2   1/2     podPhase   30        21h
   177  test3   4/4     podPhase   1         22h
   178  `,
   179  		},
   180  	}
   181  	for _, test := range tests {
   182  		// Create the table from the columns and rows.
   183  		table := &metav1.Table{
   184  			ColumnDefinitions: test.columns,
   185  			Rows:              test.rows,
   186  		}
   187  		// Print the table
   188  		out := bytes.NewBuffer([]byte{})
   189  		printer := NewTablePrinter(test.options)
   190  		printer.PrintObj(table, out)
   191  		// Validate the expected output matches the printed table.
   192  		if test.expected != out.String() {
   193  			t.Errorf("Table printing error: expected (%s), got (%s)", test.expected, out.String())
   194  		}
   195  	}
   196  }
   197  
   198  func TestPrintTable_ColumnHeaders(t *testing.T) {
   199  	tests := []struct {
   200  		columns  []metav1.TableColumnDefinition
   201  		rows     []metav1.TableRow
   202  		options  PrintOptions
   203  		expected string
   204  	}{
   205  		// Test a basic single row table, shows columns.
   206  		{
   207  			columns: []metav1.TableColumnDefinition{
   208  				{Name: "Name", Type: "string"},
   209  				{Name: "Ready", Type: "string"},
   210  				{Name: "Status", Type: "string"},
   211  				{Name: "Retries", Type: "integer"},
   212  				{Name: "Age", Type: "string"},
   213  			},
   214  			rows: []metav1.TableRow{
   215  				{Cells: []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"}},
   216  			},
   217  			options:  PrintOptions{},
   218  			expected: "NAME    READY   STATUS     RETRIES   AGE\ntest1   1/1     podPhase   5         20h\n",
   219  		},
   220  		// Test a basic multi-row table row table, shows columns.
   221  		{
   222  			columns: []metav1.TableColumnDefinition{
   223  				{Name: "Name", Type: "string"},
   224  				{Name: "Ready", Type: "string"},
   225  				{Name: "Status", Type: "string"},
   226  				{Name: "Retries", Type: "integer"},
   227  				{Name: "Age", Type: "string"},
   228  			},
   229  			rows: []metav1.TableRow{
   230  				{Cells: []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"}},
   231  				{Cells: []interface{}{"test2", "1/2", "podPhase", int64(30), "21h"}},
   232  				{Cells: []interface{}{"test3", "4/4", "podPhase", int64(1), "22h"}},
   233  			},
   234  			options: PrintOptions{},
   235  			expected: `NAME    READY   STATUS     RETRIES   AGE
   236  test1   1/1     podPhase   5         20h
   237  test2   1/2     podPhase   30        21h
   238  test3   4/4     podPhase   1         22h
   239  `,
   240  		},
   241  		// Test a single row table with "NoHeaders" option, doesn't print columns.
   242  		{
   243  			columns: []metav1.TableColumnDefinition{
   244  				{Name: "Name", Type: "string"},
   245  				{Name: "Ready", Type: "string"},
   246  				{Name: "Status", Type: "string"},
   247  				{Name: "Retries", Type: "integer"},
   248  				{Name: "Age", Type: "string"},
   249  			},
   250  			rows: []metav1.TableRow{
   251  				{Cells: []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"}},
   252  			},
   253  			// Print with no headers.
   254  			options:  PrintOptions{NoHeaders: true},
   255  			expected: "test1   1/1   podPhase   5     20h\n",
   256  		},
   257  		// Test a multi-row table row table with "NoHeaders" option, doesn't print columns.
   258  		{
   259  			columns: []metav1.TableColumnDefinition{
   260  				{Name: "Name", Type: "string"},
   261  				{Name: "Ready", Type: "string"},
   262  				{Name: "Status", Type: "string"},
   263  				{Name: "Retries", Type: "integer"},
   264  				{Name: "Age", Type: "string"},
   265  			},
   266  			rows: []metav1.TableRow{
   267  				{Cells: []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"}},
   268  				{Cells: []interface{}{"test2", "1/2", "podPhase", int64(30), "21h"}},
   269  				{Cells: []interface{}{"test3", "4/4", "podPhase", int64(1), "22h"}},
   270  			},
   271  			options: PrintOptions{NoHeaders: true},
   272  			expected: `test1   1/1   podPhase   5     20h
   273  test2   1/2   podPhase   30    21h
   274  test3   4/4   podPhase   1     22h
   275  `,
   276  		},
   277  	}
   278  	for _, test := range tests {
   279  		// Create the table from the columns and rows.
   280  		table := &metav1.Table{
   281  			ColumnDefinitions: test.columns,
   282  			Rows:              test.rows,
   283  		}
   284  		// Print the table
   285  		out := bytes.NewBuffer([]byte{})
   286  		printer := NewTablePrinter(test.options)
   287  		printer.PrintObj(table, out)
   288  		// Validate the expected output matches the printed table.
   289  		if test.expected != out.String() {
   290  			t.Errorf("Table printing error: expected (%s), got (%s)", test.expected, out.String())
   291  		}
   292  	}
   293  }
   294  
   295  func TestPrintTable_WithNamespace(t *testing.T) {
   296  	tests := []struct {
   297  		columns  []metav1.TableColumnDefinition
   298  		rows     []metav1.TableRow
   299  		options  PrintOptions
   300  		expected string
   301  	}{
   302  		// Test a single row table "WithNamespace" option, prepends NAMESPACE column.
   303  		{
   304  			columns: []metav1.TableColumnDefinition{
   305  				{Name: "Name", Type: "string"},
   306  				{Name: "Ready", Type: "string"},
   307  				{Name: "Status", Type: "string"},
   308  				{Name: "Retries", Type: "integer"},
   309  				{Name: "Age", Type: "string"},
   310  			},
   311  			rows: []metav1.TableRow{
   312  				{
   313  					Cells:  []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"},
   314  					Object: runtime.RawExtension{Object: testPod},
   315  				},
   316  			},
   317  			// Print with namespace column prepended.
   318  			options: PrintOptions{WithNamespace: true},
   319  			expected: `NAMESPACE        NAME    READY   STATUS     RETRIES   AGE
   320  test-namespace   test1   1/1     podPhase   5         20h
   321  `,
   322  		},
   323  	}
   324  	for _, test := range tests {
   325  		// Create the table from the columns and rows.
   326  		table := &metav1.Table{
   327  			ColumnDefinitions: test.columns,
   328  			Rows:              test.rows,
   329  		}
   330  		// Print the table
   331  		out := bytes.NewBuffer([]byte{})
   332  		printer := NewTablePrinter(test.options)
   333  		printer.PrintObj(table, out)
   334  		// Validate the expected output matches the printed table.
   335  		if test.expected != out.String() {
   336  			t.Errorf("Table printing error: expected (%s), got (%s)", test.expected, out.String())
   337  		}
   338  	}
   339  }
   340  
   341  func TestPrintTable_WithKind(t *testing.T) {
   342  	tests := []struct {
   343  		columns  []metav1.TableColumnDefinition
   344  		rows     []metav1.TableRow
   345  		options  PrintOptions
   346  		expected string
   347  	}{
   348  		// Test a single row table "WithKind" option, prepends "pod" to name.
   349  		{
   350  			columns: []metav1.TableColumnDefinition{
   351  				{Name: "Name", Type: "string", Format: "name"},
   352  				{Name: "Ready", Type: "string"},
   353  				{Name: "Status", Type: "string"},
   354  				{Name: "Retries", Type: "integer"},
   355  				{Name: "Age", Type: "string"},
   356  			},
   357  			rows: []metav1.TableRow{
   358  				{
   359  					Cells: []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"},
   360  				},
   361  			},
   362  			// Print with Kind "pod" prepended to name.
   363  			options: PrintOptions{WithKind: true, Kind: schema.GroupKind{Kind: "Pod"}},
   364  			expected: `NAME        READY   STATUS     RETRIES   AGE
   365  pod/test1   1/1     podPhase   5         20h
   366  `,
   367  		},
   368  	}
   369  	for _, test := range tests {
   370  		// Create the table from the columns and rows.
   371  		table := &metav1.Table{
   372  			ColumnDefinitions: test.columns,
   373  			Rows:              test.rows,
   374  		}
   375  		// Print the table
   376  		out := bytes.NewBuffer([]byte{})
   377  		printer := NewTablePrinter(test.options)
   378  		printer.PrintObj(table, out)
   379  		// Validate the expected output matches the printed table.
   380  		if test.expected != out.String() {
   381  			t.Errorf("Table printing error: expected (%s), got (%s)", test.expected, out.String())
   382  		}
   383  	}
   384  }
   385  
   386  func TestPrintTable_WithLabels(t *testing.T) {
   387  	tests := []struct {
   388  		columns  []metav1.TableColumnDefinition
   389  		rows     []metav1.TableRow
   390  		options  PrintOptions
   391  		expected string
   392  	}{
   393  		// Test a table "ShowLabels" option, appends labels as columns.
   394  		{
   395  			columns: []metav1.TableColumnDefinition{
   396  				{Name: "Name", Type: "string"},
   397  				{Name: "Ready", Type: "string"},
   398  				{Name: "Status", Type: "string"},
   399  				{Name: "Retries", Type: "integer"},
   400  				{Name: "Age", Type: "string"},
   401  			},
   402  			rows: []metav1.TableRow{
   403  				{
   404  					Cells:  []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"},
   405  					Object: runtime.RawExtension{Object: testPod},
   406  				},
   407  			},
   408  			options: PrintOptions{ShowLabels: true},
   409  			expected: `NAME    READY   STATUS     RETRIES   AGE   LABELS
   410  test1   1/1     podPhase   5         20h   first-label=12,second-label=label-value
   411  `,
   412  		},
   413  		// Test a table "ColumnLabels" option, appends labels as columns.
   414  		{
   415  			columns: []metav1.TableColumnDefinition{
   416  				{Name: "Name", Type: "string"},
   417  				{Name: "Ready", Type: "string"},
   418  				{Name: "Status", Type: "string"},
   419  				{Name: "Retries", Type: "integer"},
   420  				{Name: "Age", Type: "string"},
   421  			},
   422  			rows: []metav1.TableRow{
   423  				{
   424  					Cells:  []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"},
   425  					Object: runtime.RawExtension{Object: testPod},
   426  				},
   427  			},
   428  			// Print "second-label" as column name, with label value.
   429  			options: PrintOptions{ColumnLabels: []string{"second-label"}},
   430  			expected: `NAME    READY   STATUS     RETRIES   AGE   SECOND-LABEL
   431  test1   1/1     podPhase   5         20h   label-value
   432  `,
   433  		},
   434  		// Test a table "ColumnLabels" option, appends labels as columns.
   435  		{
   436  			columns: []metav1.TableColumnDefinition{
   437  				{Name: "Name", Type: "string"},
   438  				{Name: "Ready", Type: "string"},
   439  				{Name: "Status", Type: "string"},
   440  				{Name: "Retries", Type: "integer"},
   441  				{Name: "Age", Type: "string"},
   442  			},
   443  			rows: []metav1.TableRow{
   444  				{
   445  					Cells:  []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"},
   446  					Object: runtime.RawExtension{Object: testPod},
   447  				},
   448  			},
   449  			// Print multiple-labels as columns, with their values.
   450  			options: PrintOptions{ColumnLabels: []string{"first-label", "second-label"}},
   451  			expected: `NAME    READY   STATUS     RETRIES   AGE   FIRST-LABEL   SECOND-LABEL
   452  test1   1/1     podPhase   5         20h   12            label-value
   453  `,
   454  		},
   455  	}
   456  	for _, test := range tests {
   457  		// Create the table from the columns and rows.
   458  		table := &metav1.Table{
   459  			ColumnDefinitions: test.columns,
   460  			Rows:              test.rows,
   461  		}
   462  		// Print the table
   463  		out := bytes.NewBuffer([]byte{})
   464  		printer := NewTablePrinter(test.options)
   465  		printer.PrintObj(table, out)
   466  		// Validate the expected output matches the printed table.
   467  		if test.expected != out.String() {
   468  			t.Errorf("Table printing error: expected (%s), got (%s)", test.expected, out.String())
   469  		}
   470  	}
   471  }
   472  
   473  func TestPrintTable_NonTable(t *testing.T) {
   474  	tests := []struct {
   475  		object   runtime.Object
   476  		options  PrintOptions
   477  		expected string
   478  	}{
   479  		// Test non-table default printing for a pod.
   480  		{
   481  			object:   testPod,
   482  			options:  PrintOptions{},
   483  			expected: "NAME            AGE\ntest-pod-name   <unknown>\n",
   484  		},
   485  		// Test non-table default printing for a pod with "NoHeaders" option.
   486  		{
   487  			object:   testPod,
   488  			options:  PrintOptions{NoHeaders: true},
   489  			expected: "test-pod-name   <unknown>\n",
   490  		},
   491  		// Test non-table default printing for a pod with "WithNamespace" option.
   492  		{
   493  			object:   testPod,
   494  			options:  PrintOptions{WithNamespace: true},
   495  			expected: "NAMESPACE        NAME            AGE\ntest-namespace   test-pod-name   <unknown>\n",
   496  		},
   497  		// Test non-table default printing for a pod with "ShowLabels" option.
   498  		{
   499  			object:  testPod,
   500  			options: PrintOptions{ShowLabels: true},
   501  			expected: `NAME            AGE         LABELS
   502  test-pod-name   <unknown>   first-label=12,second-label=label-value
   503  `,
   504  		},
   505  		// Test non-table default printing for a Status resource.
   506  		{
   507  			object:  testStatus,
   508  			options: PrintOptions{},
   509  			expected: `STATUS    REASON               MESSAGE
   510  Failure   test-status-reason   test-status-message
   511  `,
   512  		},
   513  		// Test non-table default printing for a Status resource with NoHeaders options.
   514  		{
   515  			object:   testStatus,
   516  			options:  PrintOptions{NoHeaders: true},
   517  			expected: "Failure   test-status-reason   test-status-message\n",
   518  		},
   519  	}
   520  	for _, test := range tests {
   521  		// Print the table
   522  		out := bytes.NewBuffer([]byte{})
   523  		printer := NewTablePrinter(test.options)
   524  		printer.PrintObj(test.object, out)
   525  		// Validate the expected output matches the printed table.
   526  		if test.expected != out.String() {
   527  			t.Errorf("Table printing error: expected (%s), got (%s)", test.expected, out.String())
   528  		}
   529  	}
   530  }
   531  
   532  func TestPrintTable_WatchEvents(t *testing.T) {
   533  	tests := []struct {
   534  		columns  []metav1.TableColumnDefinition
   535  		rows     []metav1.TableRow
   536  		options  PrintOptions
   537  		expected string
   538  	}{
   539  		// Print WatchEvent with Table resource.
   540  		{
   541  			columns: []metav1.TableColumnDefinition{
   542  				{Name: "Name", Type: "string"},
   543  				{Name: "Ready", Type: "string"},
   544  				{Name: "Status", Type: "string"},
   545  				{Name: "Retries", Type: "integer"},
   546  				{Name: "Age", Type: "string"},
   547  			},
   548  			rows: []metav1.TableRow{
   549  				{
   550  					Cells:  []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"},
   551  					Object: runtime.RawExtension{Object: testPod},
   552  				},
   553  			},
   554  			options: PrintOptions{},
   555  			expected: `EVENT   NAME    READY   STATUS     RETRIES   AGE
   556  Added   test1   1/1     podPhase   5         20h
   557  `,
   558  		},
   559  		// Print WatchEvent with Table, and "NoHeaders", "WithNamespace", and "ShowLabels" options.
   560  		{
   561  			columns: []metav1.TableColumnDefinition{
   562  				{Name: "Name", Type: "string"},
   563  				{Name: "Ready", Type: "string"},
   564  				{Name: "Status", Type: "string"},
   565  				{Name: "Retries", Type: "integer"},
   566  				{Name: "Age", Type: "string"},
   567  			},
   568  			rows: []metav1.TableRow{
   569  				{
   570  					Cells:  []interface{}{"test1", "1/1", "podPhase", int64(5), "20h"},
   571  					Object: runtime.RawExtension{Object: testPod},
   572  				},
   573  			},
   574  			options:  PrintOptions{NoHeaders: true, WithNamespace: true, ShowLabels: true},
   575  			expected: "Added   test-namespace   test1   1/1   podPhase   5     20h   first-label=12,second-label=label-value\n",
   576  		},
   577  	}
   578  	for _, test := range tests {
   579  		// Create the table from the columns and rows.
   580  		table := &metav1.Table{
   581  			ColumnDefinitions: test.columns,
   582  			Rows:              test.rows,
   583  		}
   584  		// Add the table to the WatchEvent.
   585  		event := &metav1.WatchEvent{
   586  			Type:   "Added",
   587  			Object: runtime.RawExtension{Object: table},
   588  		}
   589  		// Print the event
   590  		out := bytes.NewBuffer([]byte{})
   591  		printer := NewTablePrinter(test.options)
   592  		printer.PrintObj(event, out)
   593  		// Validate the expected output matches the printed table.
   594  		if test.expected != out.String() {
   595  			t.Errorf("Event/Table printing error: expected (%s), got (%s)", test.expected, out.String())
   596  		}
   597  	}
   598  }
   599  
   600  func TestPrintUnstructuredObject(t *testing.T) {
   601  	obj := &unstructured.Unstructured{
   602  		Object: map[string]interface{}{
   603  			"apiVersion": "v1",
   604  			"kind":       "Test",
   605  			"dummy1":     "present",
   606  			"dummy2":     "present",
   607  			"metadata": map[string]interface{}{
   608  				"name":              "MyName",
   609  				"namespace":         "MyNamespace",
   610  				"creationTimestamp": "2017-04-01T00:00:00Z",
   611  				"resourceVersion":   123,
   612  				"uid":               "00000000-0000-0000-0000-000000000001",
   613  				"dummy3":            "present",
   614  				"labels":            map[string]interface{}{"test": "other"},
   615  			},
   616  			/*"items": []interface{}{
   617  				map[string]interface{}{
   618  					"itemBool": true,
   619  					"itemInt":  42,
   620  				},
   621  			},*/
   622  			"url":    "http://localhost",
   623  			"status": "ok",
   624  		},
   625  	}
   626  
   627  	tests := []struct {
   628  		expected string
   629  		options  PrintOptions
   630  		object   runtime.Object
   631  	}{
   632  		{
   633  			expected: "NAME\\s+AGE\nMyName\\s+\\d+",
   634  			object:   obj,
   635  		},
   636  		{
   637  			options: PrintOptions{
   638  				WithNamespace: true,
   639  			},
   640  			expected: "NAMESPACE\\s+NAME\\s+AGE\nMyNamespace\\s+MyName\\s+\\d+",
   641  			object:   obj,
   642  		},
   643  		{
   644  			options: PrintOptions{
   645  				ShowLabels:    true,
   646  				WithNamespace: true,
   647  			},
   648  			expected: "NAMESPACE\\s+NAME\\s+AGE\\s+LABELS\nMyNamespace\\s+MyName\\s+\\d+\\w+\\s+test\\=other",
   649  			object:   obj,
   650  		},
   651  		{
   652  			expected: "NAME\\s+AGE\nMyName\\s+\\d+\\w+\nMyName2\\s+\\d+",
   653  			object: &unstructured.Unstructured{
   654  				Object: map[string]interface{}{
   655  					"apiVersion": "v1",
   656  					"kind":       "Test",
   657  					"dummy1":     "present",
   658  					"dummy2":     "present",
   659  					"items": []interface{}{
   660  						map[string]interface{}{
   661  							"metadata": map[string]interface{}{
   662  								"name":              "MyName",
   663  								"namespace":         "MyNamespace",
   664  								"creationTimestamp": "2017-04-01T00:00:00Z",
   665  								"resourceVersion":   123,
   666  								"uid":               "00000000-0000-0000-0000-000000000001",
   667  								"dummy3":            "present",
   668  								"labels":            map[string]interface{}{"test": "other"},
   669  							},
   670  						},
   671  						map[string]interface{}{
   672  							"metadata": map[string]interface{}{
   673  								"name":              "MyName2",
   674  								"namespace":         "MyNamespace",
   675  								"creationTimestamp": "2017-04-01T00:00:00Z",
   676  								"resourceVersion":   123,
   677  								"uid":               "00000000-0000-0000-0000-000000000001",
   678  								"dummy3":            "present",
   679  								"labels":            "badlabel",
   680  							},
   681  						},
   682  					},
   683  					"url":    "http://localhost",
   684  					"status": "ok",
   685  				},
   686  			},
   687  		},
   688  	}
   689  	out := bytes.NewBuffer([]byte{})
   690  
   691  	for _, test := range tests {
   692  		out.Reset()
   693  		printer := NewTablePrinter(test.options)
   694  		printer.PrintObj(test.object, out)
   695  
   696  		matches, err := regexp.MatchString(test.expected, out.String())
   697  		if err != nil {
   698  			t.Fatalf("unexpected error: %v", err)
   699  		}
   700  		if !matches {
   701  			t.Errorf("wanted:\n%s\ngot:\n%s", test.expected, out)
   702  		}
   703  	}
   704  }
   705  
   706  type TestUnknownType struct{}
   707  
   708  func (obj *TestUnknownType) GetObjectKind() schema.ObjectKind { return schema.EmptyObjectKind }
   709  func (obj *TestUnknownType) DeepCopyObject() runtime.Object {
   710  	if obj == nil {
   711  		return nil
   712  	}
   713  	clone := *obj
   714  	return &clone
   715  }
   716  
   717  func TestUnknownTypePrinting(t *testing.T) {
   718  	printer := NewTablePrinter(PrintOptions{})
   719  	buffer := &bytes.Buffer{}
   720  	err := printer.PrintObj(&TestUnknownType{}, buffer)
   721  	if err == nil {
   722  		t.Errorf("An error was expected from printing unknown type")
   723  	}
   724  }
   725  
   726  func TestStringPrinting(t *testing.T) {
   727  	tests := []struct {
   728  		columns  []metav1.TableColumnDefinition
   729  		rows     []metav1.TableRow
   730  		expected string
   731  	}{
   732  		// multiline string
   733  		{
   734  			columns: []metav1.TableColumnDefinition{
   735  				{Name: "Name", Type: "string"},
   736  				{Name: "Age", Type: "string"},
   737  				{Name: "Description", Type: "string"},
   738  			},
   739  			rows: []metav1.TableRow{
   740  				{Cells: []interface{}{"test1", "20h", "This is first line\nThis is second line\nThis is third line\nand another one\n"}},
   741  			},
   742  			expected: `NAME    AGE   DESCRIPTION
   743  test1   20h   This is first line...
   744  `,
   745  		},
   746  		// lengthy string
   747  		{
   748  			columns: []metav1.TableColumnDefinition{
   749  				{Name: "Name", Type: "string"},
   750  				{Name: "Age", Type: "string"},
   751  				{Name: "Description", Type: "string"},
   752  			},
   753  			rows: []metav1.TableRow{
   754  				{Cells: []interface{}{"test1", "20h", "This is first line which is long and goes for on and on and on an on and on and on and on and on and on and on and on and on and on and on"}},
   755  			},
   756  			expected: `NAME    AGE   DESCRIPTION
   757  test1   20h   This is first line which is long and goes for on and on and on an on and on and on and on and on and on and on and on and on and on and on
   758  `,
   759  		},
   760  		// lengthy string + newline
   761  		{
   762  			columns: []metav1.TableColumnDefinition{
   763  				{Name: "Name", Type: "string"},
   764  				{Name: "Age", Type: "string"},
   765  				{Name: "Description", Type: "string"},
   766  			},
   767  			rows: []metav1.TableRow{
   768  				{Cells: []interface{}{"test1", "20h", "This is first\n line which is long and goes for on and on and on an on and on and on and on and on and on and on and on and on and on and on"}},
   769  			},
   770  			expected: `NAME    AGE   DESCRIPTION
   771  test1   20h   This is first...
   772  `,
   773  		},
   774  		// terminal special character, should be escaped
   775  		{
   776  			columns: []metav1.TableColumnDefinition{
   777  				{Name: "Name", Type: "string"},
   778  			},
   779  			rows: []metav1.TableRow{
   780  				{Cells: []interface{}{"test1\x1b"}},
   781  			},
   782  			expected: `NAME
   783  test1^[
   784  `,
   785  		},
   786  	}
   787  
   788  	for _, test := range tests {
   789  		// Create the table from the columns and rows.
   790  		table := &metav1.Table{
   791  			ColumnDefinitions: test.columns,
   792  			Rows:              test.rows,
   793  		}
   794  		// Print the table
   795  		out := bytes.NewBuffer([]byte{})
   796  		printer := NewTablePrinter(PrintOptions{})
   797  		printer.PrintObj(table, out)
   798  
   799  		if test.expected != out.String() {
   800  			t.Errorf("Table printing error: expected \n(%s), got \n(%s)", test.expected, out.String())
   801  		}
   802  	}
   803  }
   804  

View as plain text