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 }