github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/columns/formatter/textcolumns/textcolumns_test.go (about)

     1  // Copyright 2022 The Inspektor Gadget authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package textcolumns
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/binary"
    20  	"reflect"
    21  	"strings"
    22  	"testing"
    23  	"unsafe"
    24  
    25  	"github.com/inspektor-gadget/inspektor-gadget/pkg/columns"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  )
    30  
    31  type testStruct struct {
    32  	Name     string  `column:"name,width:10"`
    33  	Age      uint    `column:"age,width:4,align:right,fixed"`
    34  	Size     float32 `column:"size,width:6,precision:2,align:right"`
    35  	Balance  int     `column:"balance,width:8,align:right"`
    36  	CanDance bool    `column:"canDance,width:8"`
    37  }
    38  
    39  var testEntries = []*testStruct{
    40  	{"Alice", 32, 1.74, 1000, true},
    41  	{"Bob", 26, 1.73, -200, true},
    42  	{"Eve", 99, 5.12, 1000000, false},
    43  	nil,
    44  }
    45  
    46  var testColumns = columns.MustCreateColumns[testStruct]().GetColumnMap()
    47  
    48  func TestTextColumnsFormatter_FormatEntryAndTable(t *testing.T) {
    49  	expected := []string{
    50  		"Alice        32   1.74     1000 true    ",
    51  		"Bob          26   1.73     -200 true    ",
    52  		"Eve          99   5.12  1000000 false   ",
    53  		"",
    54  	}
    55  	formatter := NewFormatter(testColumns, WithRowDivider(DividerDash))
    56  
    57  	t.Run("FormatEntry", func(t *testing.T) {
    58  		for i, entry := range testEntries {
    59  			assert.Equal(t, expected[i], formatter.FormatEntry(entry))
    60  		}
    61  	})
    62  
    63  	t.Run("FormatTable", func(t *testing.T) {
    64  		assert.Equal(t,
    65  			strings.Join(append([]string{"NAME        AGE   SIZE  BALANCE CANDANCE", "————————————————————————————————————————"}, expected...), "\n"),
    66  			formatter.FormatTable(testEntries),
    67  		)
    68  	})
    69  }
    70  
    71  func TestTextColumnsFormatter_FormatHeader(t *testing.T) {
    72  	formatter := NewFormatter(testColumns)
    73  
    74  	assert.Equal(t, "NAME        AGE   SIZE  BALANCE CANDANCE", formatter.FormatHeader())
    75  
    76  	formatter.options.HeaderStyle = HeaderStyleLowercase
    77  	assert.Equal(t, "name        age   size  balance candance", formatter.FormatHeader())
    78  
    79  	formatter.options.HeaderStyle = HeaderStyleNormal
    80  	assert.Equal(t, "name        age   size  balance canDance", formatter.FormatHeader())
    81  }
    82  
    83  func TestTextColumnsFormatter_FormatRowDivider(t *testing.T) {
    84  	formatter := NewFormatter(testColumns, WithRowDivider(DividerDash))
    85  	assert.Equal(t, "————————————————————————————————————————", formatter.FormatRowDivider())
    86  }
    87  
    88  func TestTextColumnsFormatter_RecalculateWidths(t *testing.T) {
    89  	formatter := NewFormatter(testColumns, WithRowDivider(DividerDash))
    90  	maxWidth := 100
    91  	formatter.RecalculateWidths(maxWidth, true)
    92  	assert.Equal(t, 100, len([]rune(formatter.FormatHeader())), "bad header width")
    93  	assert.Equal(t, 100, len([]rune(formatter.FormatRowDivider())), "bad row divider width")
    94  
    95  	for _, e := range testEntries {
    96  		if e != nil {
    97  			assert.Equal(t, 100, len([]rune(formatter.FormatEntry(e))), "bad entry width")
    98  		}
    99  	}
   100  }
   101  
   102  func TestTextColumnsFormatter_AdjustWidthsToContent(t *testing.T) {
   103  	/*
   104  		Expected result (32 characters):
   105  		NAME   AGE SIZE BALANCE CANDANCE
   106  		————————————————————————————————
   107  		Alice   32 1.74    1000 true
   108  		Bob     26 1.73    -200 true
   109  		Eve     99 5.12 1000000 false
   110  	*/
   111  	formatter := NewFormatter(testColumns, WithRowDivider(DividerDash))
   112  	formatter.AdjustWidthsToContent(testEntries, true, 0, false)
   113  	assert.Equal(t, "NAME   AGE SIZE BALANCE CANDANCE", formatter.FormatHeader(), "header does not match")
   114  	assert.Equal(t, "————————————————————————————————", formatter.FormatRowDivider(), "row divider does not match")
   115  	assert.Equal(t, "Alice   32 1.74    1000 true    ", formatter.FormatEntry(testEntries[0]), "entry does not match")
   116  }
   117  
   118  func TestTextColumnsFormatter_AdjustWidthsToContentNoHeaders(t *testing.T) {
   119  	/*
   120  		Expected result (29 characters):
   121  		NAME   AGE SIZE BALANCE CAND…
   122  		—————————————————————————————
   123  		Alice   32 1.74    1000 true
   124  		Bob     26 1.73    -200 true
   125  		Eve     99 5.12 1000000 false
   126  	*/
   127  	formatter := NewFormatter(testColumns, WithRowDivider(DividerDash))
   128  	formatter.AdjustWidthsToContent(testEntries, false, 0, false)
   129  	assert.Equal(t, "NAME   AGE SIZE BALANCE CAND…", formatter.FormatHeader(), "header does not match")
   130  	assert.Equal(t, "—————————————————————————————", formatter.FormatRowDivider(), "row divider does not match")
   131  	assert.Equal(t, "Alice   32 1.74    1000 true ", formatter.FormatEntry(testEntries[0]), "entry does not match")
   132  }
   133  
   134  func TestTextColumnsFormatter_AdjustWidthsMaxWidth(t *testing.T) {
   135  	/*
   136  		Expected result (9 characters):
   137  		N… …  … …
   138  		—————————
   139  		A… …  … …
   140  		B… …  … …
   141  		E… …  … …
   142  	*/
   143  	formatter := NewFormatter(testColumns, WithRowDivider(DividerDash))
   144  	formatter.AdjustWidthsToContent(testEntries, false, 9, true)
   145  	assert.Equal(t, "N… …  … …", formatter.FormatHeader(), "header does not match")
   146  	assert.Equal(t, "—————————", formatter.FormatRowDivider(), "row divider does not match")
   147  	assert.Equal(t, "A… …  … …", formatter.FormatEntry(testEntries[0]), "entry does not match")
   148  }
   149  
   150  func TestWidthRestrictions(t *testing.T) {
   151  	type testStruct struct {
   152  		Name        string `column:"name,width:5,minWidth:2,maxWidth:10"`
   153  		SecondField string `column:"second"`
   154  	}
   155  	entries := []*testStruct{
   156  		{"123456789012", "123456789012"},
   157  		{"234567890123", "234567890123"},
   158  	}
   159  	cols, err := columns.NewColumns[testStruct]()
   160  	require.Nil(t, err, "error initializing: %s", err)
   161  
   162  	formatter := NewFormatter(cols.GetColumnMap(), WithRowDivider(DividerDash), WithAutoScale(true))
   163  	t.Run("maxWidth", func(t *testing.T) {
   164  		formatter.RecalculateWidths(40, false)
   165  		assert.Equal(t, "123456789… 123456789012", strings.TrimSpace(formatter.FormatEntry(entries[0])), "entry does not match")
   166  	})
   167  	t.Run("minWidth", func(t *testing.T) {
   168  		formatter.RecalculateWidths(1, false)
   169  		assert.Equal(t, "1… …", strings.TrimSpace(formatter.FormatEntry(entries[0])), "entry does not match")
   170  	})
   171  }
   172  
   173  func TestWithTypeDefinition(t *testing.T) {
   174  	type StringAlias string
   175  	type testStruct struct {
   176  		Name StringAlias `column:"name,width:5,minWidth:2,maxWidth:10"`
   177  	}
   178  	entries := []*testStruct{
   179  		{"123456789012"},
   180  		{"234567890123"},
   181  	}
   182  	cols, err := columns.NewColumns[testStruct]()
   183  	require.Nil(t, err, "error initializing: %s", err)
   184  
   185  	formatter := NewFormatter(cols.GetColumnMap(), WithAutoScale(false))
   186  	formatter.AdjustWidthsToContent(entries, false, 0, false)
   187  	for _, entry := range entries {
   188  		assert.Equal(t, string(entry.Name), formatter.FormatEntry(entry))
   189  	}
   190  }
   191  
   192  func TestTextColumnsFormatter_SetShownColumns(t *testing.T) {
   193  	type test struct {
   194  		name     string
   195  		setShown []string
   196  		expected []string
   197  		err      bool
   198  	}
   199  
   200  	tests := []test{
   201  		{
   202  			name:     "default",
   203  			setShown: nil,
   204  			expected: []string{"name", "age", "size", "balance", "canDance"},
   205  		},
   206  		{
   207  			name:     "empty",
   208  			setShown: []string{},
   209  			expected: []string{},
   210  		},
   211  		{
   212  			name:     "shown-columns-match",
   213  			setShown: []string{"name"},
   214  			expected: []string{"name"},
   215  		},
   216  		{
   217  			name:     "multipe-shown-columns-match",
   218  			setShown: []string{"name", "canDance"},
   219  			expected: []string{"name", "canDance"},
   220  		},
   221  		{
   222  			name:     "column-not-found",
   223  			setShown: []string{"foo"},
   224  			err:      true,
   225  		},
   226  	}
   227  
   228  	for _, test := range tests {
   229  		t.Run(test.name, func(t *testing.T) {
   230  			formatter := NewFormatter(testColumns)
   231  
   232  			err := formatter.SetShowColumns(test.setShown)
   233  			if test.err {
   234  				require.NotNil(t, err, "SetShowColumns should have failed")
   235  				return
   236  			}
   237  
   238  			require.Nil(t, err, "SetShowColumns failed: %s", err)
   239  
   240  			found := []string{}
   241  			for _, c := range formatter.showColumns {
   242  				found = append(found, c.col.Name)
   243  			}
   244  
   245  			require.Equal(t, test.expected, found, "shown columns doesn't match the expected ones")
   246  		})
   247  	}
   248  }
   249  
   250  func TestDynamicFields(t *testing.T) {
   251  	// Write the data in its binary representation to a buffer
   252  	buf := new(bytes.Buffer)
   253  	err := binary.Write(buf, binary.LittleEndian, []uint8("foobar"))
   254  	require.NoError(t, err)
   255  	err = binary.Write(buf, binary.LittleEndian, int32(1234567890))
   256  	require.NoError(t, err)
   257  	err = binary.Write(buf, binary.LittleEndian, true)
   258  	require.NoError(t, err)
   259  
   260  	fields := []columns.DynamicField{{
   261  		Attributes: &columns.Attributes{
   262  			Name:    "str",
   263  			Width:   columns.GetDefault().DefaultWidth,
   264  			Visible: true,
   265  			Order:   0,
   266  		},
   267  		Type:   reflect.TypeOf([6]uint8{}),
   268  		Offset: 0,
   269  	}, {
   270  		Attributes: &columns.Attributes{
   271  			Name:    "int32",
   272  			Width:   columns.GetDefault().DefaultWidth,
   273  			Visible: true,
   274  			Order:   1,
   275  		},
   276  		Type:   reflect.TypeOf(int32(0)),
   277  		Offset: 6,
   278  	}, {
   279  		Attributes: &columns.Attributes{
   280  			Name:    "bool",
   281  			Width:   columns.GetDefault().DefaultWidth,
   282  			Visible: true,
   283  			Order:   2,
   284  		},
   285  		Type:   reflect.TypeOf(true),
   286  		Offset: 10,
   287  	}}
   288  
   289  	type empty struct{}
   290  	cols := columns.MustCreateColumns[empty]()
   291  	cols.AddFields(fields, func(ev *empty) unsafe.Pointer {
   292  		bytes := buf.Bytes()
   293  		return unsafe.Pointer(&bytes[0])
   294  	})
   295  	formatter := NewFormatter[empty](cols.GetColumnMap())
   296  	assert.Equal(t, "STR              INT32            BOOL            ", formatter.FormatHeader())
   297  	assert.Equal(t, "foobar           1234567890       true            ", formatter.FormatEntry(&empty{}))
   298  }