github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/columns/columns_test.go (about) 1 // Copyright 2022-2023 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 columns 16 17 import ( 18 "fmt" 19 "reflect" 20 "testing" 21 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 ) 25 26 func TestColumnMap(t *testing.T) { 27 type testStruct struct { 28 StringField string `column:"stringField"` 29 IntField int `column:"intField"` 30 } 31 cols := expectColumnsSuccess[testStruct](t) 32 columnMap := cols.GetColumnMap() 33 assert.Contains(t, columnMap, "stringfield") 34 assert.Contains(t, columnMap, "intfield") 35 } 36 37 func TestEmptyStruct(t *testing.T) { 38 type testStruct struct { 39 StringField string 40 IntField int 41 } 42 cols := expectColumnsSuccess[testStruct](t) 43 require.Empty(t, cols.GetColumnMap()) 44 } 45 46 func TestFieldsWithTypeDefinition(t *testing.T) { 47 type StringAlias string 48 type IntAlias int 49 type testStruct struct { 50 StringField StringAlias `column:"stringField"` 51 IntField IntAlias `column:"intField"` 52 } 53 54 testVar := &testStruct{ 55 StringField: "abc", 56 IntField: 123, 57 } 58 59 cols := expectColumnsSuccess[testStruct](t) 60 assert.Equal(t, expectColumn(t, cols, "stringField").Get(testVar).Interface(), testVar.StringField) 61 assert.Equal(t, expectColumn(t, cols, "intField").Get(testVar).Interface(), testVar.IntField) 62 } 63 64 func TestGetColumnNames(t *testing.T) { 65 type testStruct struct { 66 StringField string `column:"stringField,order:500"` 67 IntField int `column:"intField,order:200"` 68 } 69 cols := expectColumnsSuccess[testStruct](t).GetColumnNames() 70 require.Len(t, cols, 2) 71 assert.Equal(t, cols[0], "intField") 72 assert.Equal(t, cols[1], "stringField") 73 } 74 75 func TestGetSortedColumns(t *testing.T) { 76 type testStruct struct { 77 StringField string `column:"stringField,order:500"` 78 IntField int `column:"intField,order:200"` 79 } 80 cols := expectColumnsSuccess[testStruct](t).GetOrderedColumns() 81 require.Len(t, cols, 2) 82 assert.Equal(t, cols[0].Name, "intField") 83 assert.Equal(t, cols[1].Name, "stringField") 84 } 85 86 func TestGetters(t *testing.T) { 87 type embeddedStruct struct { 88 EmbeddedString string `column:"embeddedString"` 89 } 90 type embeddedPtrStruct struct { 91 EmbeddedString2 string `column:"embeddedString2"` 92 } 93 type ptrStruct struct { 94 EmbeddedString string `column:"ptrStructString"` 95 } 96 type normalStruct struct { 97 EmbeddedString string `column:"normalStructString"` 98 } 99 type testStruct struct { 100 embeddedStruct 101 *embeddedPtrStruct 102 PointerStruct *ptrStruct 103 NormalStruct normalStruct 104 NotEmbeddedPtrStruct *ptrStruct `column:"ptrStruct,noembed"` 105 NotEmbeddedNormalStruct normalStruct `column:"normalStruct,noembed"` 106 StringField string `column:"stringField"` 107 IntField int `column:"intField"` 108 MapField map[string]string `column:"mapField"` 109 } 110 cols := expectColumnsSuccess[testStruct](t) 111 112 // String tests 113 col := expectColumn(t, cols, "StRiNgFiElD") 114 require.Equal(t, col.Kind(), reflect.String) 115 _, ok := col.Get(nil).Interface().(string) 116 require.True(t, ok, "type should be string") 117 str, ok := col.Get(&testStruct{StringField: "demo"}).Interface().(string) 118 require.True(t, ok, "type should be string") 119 assert.Equal(t, str, "demo") 120 121 // Raw access should return the same 122 str, ok = col.GetRaw(&testStruct{StringField: "demo"}).Interface().(string) 123 require.True(t, ok, "type should be string") 124 assert.Equal(t, str, "demo") 125 126 // Map tests 127 col = expectColumn(t, cols, "MaPfiELd") 128 require.Equal(t, col.Kind(), reflect.Map) 129 _, ok = col.Get(nil).Interface().(map[string]string) 130 require.True(t, ok, "type should be map[string]string") 131 m, ok := col.Get(&testStruct{MapField: map[string]string{"demo": "foo"}}).Interface().(map[string]string) 132 require.True(t, ok, "type should be map[string]string") 133 assert.Equal(t, m["demo"], "foo") 134 135 // Int tests 136 col = expectColumn(t, cols, "InTfIeLd") 137 138 i, ok := col.Get(&testStruct{IntField: 5}).Interface().(int) 139 require.True(t, ok, "type should be int") 140 assert.Equal(t, i, 5) 141 142 _, ok = cols.GetColumn("uNkNoWn") 143 require.False(t, ok, "no column should be present") 144 145 // Embedded string tests 146 col = expectColumn(t, cols, "embeddedstring") 147 148 _, ok = col.Get(nil).Interface().(string) 149 require.True(t, ok, "type should be string") 150 str, ok = col.Get(&testStruct{embeddedStruct: embeddedStruct{EmbeddedString: "demo"}}).Interface().(string) 151 require.True(t, ok, "type should be string") 152 assert.Equal(t, str, "demo") 153 154 // Reflection access 155 refStruct := reflect.ValueOf(&testStruct{embeddedStruct: embeddedStruct{EmbeddedString: "demo"}}) 156 str, ok = col.GetRef(refStruct).Interface().(string) 157 require.True(t, ok, "type should be string") 158 assert.Equal(t, str, "demo") 159 160 // Embedded (via pointer) string tests 161 col = expectColumn(t, cols, "embeddedstring2") 162 163 _, ok = col.Get(nil).Interface().(string) 164 require.True(t, ok, "type should be string") 165 str, ok = col.Get(&testStruct{embeddedPtrStruct: &embeddedPtrStruct{EmbeddedString2: "demo"}}).Interface().(string) 166 require.True(t, ok, "type should be string") 167 assert.Equal(t, str, "demo") 168 169 str, ok = col.Get(&testStruct{}).Interface().(string) 170 require.True(t, ok, "type should be string") 171 assert.Equal(t, str, "") 172 173 // Embedded named structs (via pointer) string tests 174 col = expectColumn(t, cols, "ptrStructString") 175 176 _, ok = col.Get(nil).Interface().(string) 177 require.True(t, ok, "type should be string") 178 str, ok = col.Get(&testStruct{PointerStruct: &ptrStruct{EmbeddedString: "demo"}}).Interface().(string) 179 require.True(t, ok, "type should be string") 180 assert.Equal(t, str, "demo") 181 182 str, ok = col.Get(&testStruct{}).Interface().(string) 183 require.True(t, ok, "type should be string") 184 assert.Equal(t, str, "") 185 186 // Embedded named structs (without pointer) string tests 187 col = expectColumn(t, cols, "normalStructString") 188 189 _, ok = col.Get(nil).Interface().(string) 190 require.True(t, ok, "type should be string") 191 str, ok = col.Get(&testStruct{NormalStruct: normalStruct{EmbeddedString: "demo"}}).Interface().(string) 192 require.True(t, ok, "type should be string") 193 assert.Equal(t, str, "demo") 194 195 str, ok = col.Get(&testStruct{}).Interface().(string) 196 require.True(t, ok, "type should be string") 197 assert.Equal(t, str, "") 198 199 // Not-Embedded named structs (with pointer) string tests 200 col = expectColumn(t, cols, "ptrStruct") 201 202 tmpPtrStruct, ok := col.Get(&testStruct{NotEmbeddedPtrStruct: &ptrStruct{EmbeddedString: "demo"}}).Interface().(*ptrStruct) 203 require.True(t, ok, "type should be *ptrStruct") 204 assert.Equal(t, tmpPtrStruct.EmbeddedString, "demo") 205 206 // Not-Embedded named structs (without pointer) string tests 207 col = expectColumn(t, cols, "normalStruct") 208 209 tmpNormalStruct, ok := col.Get(&testStruct{NotEmbeddedNormalStruct: normalStruct{EmbeddedString: "demo"}}).Interface().(normalStruct) 210 require.True(t, ok, "type should be normalStruct") 211 assert.Equal(t, tmpNormalStruct.EmbeddedString, "demo") 212 } 213 214 func TestInvalidType(t *testing.T) { 215 expectColumnsFail[int](t, "non-struct type int") 216 } 217 218 func TestMustCreateHelper(t *testing.T) { 219 type testStruct struct { 220 StringField string `column:"stringField"` 221 } 222 MustCreateColumns[testStruct]() 223 224 defer func() { 225 if err := recover(); err == nil { 226 t.Errorf("Expected panic") 227 } 228 }() 229 MustCreateColumns[int]() 230 } 231 232 func TestExtractor(t *testing.T) { 233 type testStruct struct { 234 StringField string `column:"stringField"` 235 } 236 cols := expectColumnsSuccess[testStruct](t) 237 assert.NoError(t, cols.SetExtractor("sTrInGfIeLd", func(t *testStruct) any { 238 return "empty" 239 })) 240 assert.Error(t, cols.SetExtractor("unknown", func(t *testStruct) any { 241 return "empty" 242 }), "should return error when trying to set extractor for non-existent field") 243 assert.Error( 244 t, 245 cols.SetExtractor("sTrInGfIeLd", nil), 246 "should return error when no extractor has been set", 247 ) 248 } 249 250 type Uint32 uint32 251 252 func (v Uint32) String() string { 253 return fmt.Sprintf("%d-from-stringer", v) 254 } 255 256 func TestStringer(t *testing.T) { 257 type testStruct struct { 258 StringerField Uint32 `column:"stringerField,stringer"` 259 } 260 cols := expectColumnsSuccess[testStruct](t) 261 col := expectColumn(t, cols, "stringerField") 262 263 ts := &testStruct{StringerField: 12345} 264 265 val, ok := col.Get(ts).Interface().(string) 266 require.True(t, ok, "type should be string") 267 assert.Equal(t, val, "12345-from-stringer") 268 } 269 270 func TestVirtualColumns(t *testing.T) { 271 type testStruct struct { 272 StringField string `column:"stringField"` 273 } 274 275 cols := expectColumnsSuccess[testStruct](t) 276 277 assert.Error(t, cols.AddColumn(Attributes{ 278 Name: "vcol", 279 }, nil), "should return error when adding a column without extractor func") 280 281 assert.Error(t, cols.AddColumn(Attributes{}, func(_ *testStruct) any { 282 return "" 283 }), "should return error when adding a column without name") 284 285 assert.Error(t, cols.AddColumn(Attributes{ 286 Name: "stringfield", 287 }, func(_ *testStruct) any { 288 return "" 289 }), "should return error when adding a column with already existing name") 290 291 assert.NoError(t, cols.AddColumn(Attributes{ 292 Name: "foobarstring", 293 }, func(t *testStruct) any { 294 return "FooBar" 295 })) 296 297 colStr := expectColumn(t, cols, "foobarstring") 298 _, ok := colStr.Get(nil).Interface().(string) 299 require.True(t, ok, "type should be string") 300 str, ok := colStr.Get(&testStruct{}).Interface().(string) 301 require.True(t, ok, "type should be string") 302 assert.Equal(t, str, "FooBar") 303 304 // Test GetRef also 305 str, ok = colStr.GetRef(reflect.ValueOf(&testStruct{})).Interface().(string) 306 require.True(t, ok, "type should be string") 307 assert.Equal(t, str, "FooBar") 308 309 // Raw access should return an empty string 310 str, ok = colStr.GetRaw(&testStruct{}).Interface().(string) 311 require.True(t, ok, "type should be string") 312 assert.Equal(t, str, "", "should be empty on a virtual column") 313 314 assert.NoError(t, cols.AddColumn(Attributes{ 315 Name: "foobarint", 316 }, func(t *testStruct) any { 317 return 42 318 })) 319 320 colInt := expectColumn(t, cols, "foobarint") 321 _, ok = colInt.Get(nil).Interface().(int) 322 require.True(t, ok, "type should be int") 323 intv, ok := colInt.Get(&testStruct{}).Interface().(int) 324 require.True(t, ok, "type should be int") 325 assert.Equal(t, intv, 42) 326 } 327 328 func TestVerifyColumnNames(t *testing.T) { 329 type testStruct struct { 330 StringField string `column:"stringField"` 331 IntField string `column:"intField"` 332 } 333 334 cols := expectColumnsSuccess[testStruct](t) 335 336 valid, invalid := cols.VerifyColumnNames([]string{"-stringField", "intField", "notExistingField", "notExistingField2"}) 337 assert.Len(t, valid, 2) 338 assert.Len(t, invalid, 2) 339 } 340 341 func TestEmbeddedStructs(t *testing.T) { 342 type embeddedStructUnnamed struct { 343 foo int `column:"foo"` 344 } 345 type embeddedStructNamed struct { 346 foo int `column:"foo"` 347 } 348 type embeddedStructNamedWithTemplate struct { 349 foo int `column:"foo,template:bar"` 350 } 351 type testStruct struct { 352 embeddedStructUnnamed 353 embeddedStructNamed `column:"named" columnTags:"abc,def"` 354 embeddedStructNamedWithTemplate `column:"withTemplate" columnTags:"ghi"` 355 } 356 357 assert.NoError(t, RegisterTemplate("bar", "width:123")) 358 359 cols := MustCreateColumns[testStruct]() 360 361 _, found := cols.GetColumn("embeddedStructUnnamed.foo") 362 assert.False(t, found) 363 364 fooCol, found := cols.GetColumn("foo") 365 require.True(t, found) 366 assert.Equal(t, fooCol.Name, "foo") 367 368 _, found = cols.GetColumn("embeddedStructNamed.foo") 369 assert.False(t, found) 370 371 fooCol, found = cols.GetColumn("named.foo") 372 require.True(t, found) 373 assert.Equal(t, fooCol.Name, "named.foo") 374 375 assert.Contains(t, fooCol.Tags, "def", "tags from parent should be inherited") 376 377 _, found = cols.GetColumn("embeddedStructNamedWithTemplate.foo") 378 assert.False(t, found) 379 380 fooCol, found = cols.GetColumn("withTemplate.foo") 381 require.True(t, found) 382 assert.Equal(t, fooCol.Name, "withTemplate.foo") 383 384 assert.Contains(t, fooCol.Tags, "ghi", "tags from parent should be inherited") 385 386 expectColumnValue(t, expectColumn(t, cols, "withTemplate.foo"), "Width", 123) 387 } 388 389 func TestFieldFuncs(t *testing.T) { 390 type testStruct struct { 391 stringField string `column:"stringField"` 392 uint8ArrField [16]uint8 `column:"uint8ArrField"` 393 uint64ArrField [4]uint64 `column:"uint64ArrField"` 394 mapField map[string]string `column:"mapField"` 395 } 396 397 testInstance := &testStruct{ 398 stringField: "foo", 399 uint8ArrField: [16]uint8{}, // Will be setup by copy 400 uint64ArrField: [4]uint64{1123, 4567, 8910, 111213141516}, 401 mapField: map[string]string{"foo": "bar", "abc": "xyz"}, 402 } 403 testInstanceDefault := &testStruct{} 404 405 copy(testInstance.uint8ArrField[:], []uint8("foobarbaz\x00asdfgh")) 406 407 cols := MustCreateColumns[testStruct]() 408 cols.MustSetExtractor("uint64ArrField", func(t *testStruct) any { 409 return "This should be ignored for GetFieldAsArrayFunc" 410 }) 411 412 stringFieldCol, _ := cols.GetColumn("stringField") 413 stringFieldFunc := GetFieldFunc[string, testStruct](stringFieldCol) 414 assert.Equal(t, "foo", stringFieldFunc(testInstance)) 415 uint8ArrFieldCol, _ := cols.GetColumn("uint8ArrField") 416 uint8ArrFieldFunc := GetFieldAsArrayFunc[uint8, testStruct](uint8ArrFieldCol) 417 assert.Equal(t, "foobarbaz\x00asdfgh", string(uint8ArrFieldFunc(testInstance))) 418 uint8ArrFieldStringFunc := GetFieldAsString[testStruct](uint8ArrFieldCol) 419 assert.Equal(t, "foobarbaz", uint8ArrFieldStringFunc(testInstance)) 420 uint64ArrFieldCol, _ := cols.GetColumn("uint64ArrField") 421 uint64ArrFieldFunc := GetFieldAsArrayFunc[uint64, testStruct](uint64ArrFieldCol) 422 assert.Equal(t, []uint64{1123, 4567, 8910, 111213141516}, uint64ArrFieldFunc(testInstance)) 423 mapFieldCol, _ := cols.GetColumn("mapField") 424 mapFieldFunc := GetFieldAsString[testStruct](mapFieldCol) 425 assert.Equal(t, "abc=xyz,foo=bar", mapFieldFunc(testInstance)) 426 assert.Equal(t, "", mapFieldFunc(testInstanceDefault)) 427 }