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  }