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

     1  // Copyright 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 json
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/binary"
    20  	"encoding/json"
    21  	"reflect"
    22  	"testing"
    23  	"unsafe"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/require"
    27  
    28  	"github.com/inspektor-gadget/inspektor-gadget/pkg/columns"
    29  )
    30  
    31  type testStruct struct {
    32  	Name     string  `column:"name"`
    33  	Age      uint    `column:"age"`
    34  	Size     float32 `column:"size"`
    35  	Balance  int     `column:"balance"`
    36  	CanDance bool    `column:"canDance"`
    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 TestJSONFormatter_FormatEntry(t *testing.T) {
    49  	expected := []string{
    50  		`{"name": "Alice", "age": 32, "size": 1.74, "balance": 1000, "canDance": true}`,
    51  		`{"name": "Bob", "age": 26, "size": 1.73, "balance": -200, "canDance": true}`,
    52  		`{"name": "Eve", "age": 99, "size": 5.12, "balance": 1000000, "canDance": false}`,
    53  		`null`,
    54  	}
    55  	formatter := NewFormatter(testColumns)
    56  	for i, entry := range testEntries {
    57  		assert.Equal(t, expected[i], formatter.FormatEntry(entry))
    58  	}
    59  }
    60  
    61  func TestJSONFormatter_PrettyFormatEntry(t *testing.T) {
    62  	expected := []string{
    63  		`{
    64    "name": "Alice",
    65    "age": 32,
    66    "size": 1.74,
    67    "balance": 1000,
    68    "canDance": true
    69  }`,
    70  		`{
    71    "name": "Bob",
    72    "age": 26,
    73    "size": 1.73,
    74    "balance": -200,
    75    "canDance": true
    76  }`,
    77  		`{
    78    "name": "Eve",
    79    "age": 99,
    80    "size": 5.12,
    81    "balance": 1000000,
    82    "canDance": false
    83  }`,
    84  		`null`,
    85  	}
    86  	formatter := NewFormatter(testColumns, WithPrettyPrint())
    87  	for i, entry := range testEntries {
    88  		assert.Equal(t, expected[i], formatter.FormatEntry(entry))
    89  	}
    90  }
    91  
    92  func TestJSONFormatter_FormatEntries(t *testing.T) {
    93  	type testCase struct {
    94  		entries  []*testStruct
    95  		expected string
    96  	}
    97  
    98  	tests := map[string]testCase{
    99  		"nil": {
   100  			entries:  nil,
   101  			expected: "null",
   102  		},
   103  		"empty": {
   104  			entries:  []*testStruct{},
   105  			expected: "[]",
   106  		},
   107  		"multiple": {
   108  			entries:  testEntries,
   109  			expected: `[{"name": "Alice", "age": 32, "size": 1.74, "balance": 1000, "canDance": true}, {"name": "Bob", "age": 26, "size": 1.73, "balance": -200, "canDance": true}, {"name": "Eve", "age": 99, "size": 5.12, "balance": 1000000, "canDance": false}, null]`,
   110  		},
   111  	}
   112  
   113  	for name, test := range tests {
   114  		test := test
   115  		t.Run(name, func(t *testing.T) {
   116  			t.Parallel()
   117  			formatter := NewFormatter(testColumns)
   118  			assert.Equal(t, test.expected, formatter.FormatEntries(test.entries))
   119  		})
   120  	}
   121  }
   122  
   123  func TestJSONFormatter_PrettyFormatEntries(t *testing.T) {
   124  	expected := `[
   125    {
   126      "name": "Alice",
   127      "age": 32,
   128      "size": 1.74,
   129      "balance": 1000,
   130      "canDance": true
   131    },
   132    {
   133      "name": "Bob",
   134      "age": 26,
   135      "size": 1.73,
   136      "balance": -200,
   137      "canDance": true
   138    },
   139    {
   140      "name": "Eve",
   141      "age": 99,
   142      "size": 5.12,
   143      "balance": 1000000,
   144      "canDance": false
   145    },
   146    null
   147  ]`
   148  
   149  	formatter := NewFormatter(testColumns, WithPrettyPrint())
   150  	assert.Equal(t, expected, formatter.FormatEntries(testEntries))
   151  }
   152  
   153  func BenchmarkFormatter(b *testing.B) {
   154  	b.StopTimer()
   155  	formatter := NewFormatter(testColumns)
   156  	b.StartTimer()
   157  	for n := 0; n < b.N; n++ {
   158  		formatter.FormatEntry(testEntries[n%len(testEntries)])
   159  	}
   160  }
   161  
   162  func BenchmarkNative(b *testing.B) {
   163  	b.StopTimer()
   164  	// do a dry-run to enable caching
   165  	json.Marshal(testEntries[0])
   166  	b.StartTimer()
   167  	for n := 0; n < b.N; n++ {
   168  		json.Marshal(testEntries[n%len(testEntries)])
   169  	}
   170  }
   171  
   172  func TestDynamicFields(t *testing.T) {
   173  	// Write the data in its binary representation to a buffer
   174  	buf := new(bytes.Buffer)
   175  	err := binary.Write(buf, binary.LittleEndian, []uint8("foobar"))
   176  	require.NoError(t, err)
   177  	err = binary.Write(buf, binary.LittleEndian, int32(1234567890))
   178  	require.NoError(t, err)
   179  	err = binary.Write(buf, binary.LittleEndian, true)
   180  	require.NoError(t, err)
   181  
   182  	fields := []columns.DynamicField{{
   183  		Attributes: &columns.Attributes{
   184  			Name:    "str",
   185  			Width:   columns.GetDefault().DefaultWidth,
   186  			Visible: true,
   187  			Order:   0,
   188  		},
   189  		Type:   reflect.TypeOf([6]uint8{}),
   190  		Offset: 0,
   191  	}, {
   192  		Attributes: &columns.Attributes{
   193  			Name:    "int32",
   194  			Width:   columns.GetDefault().DefaultWidth,
   195  			Visible: true,
   196  			Order:   1,
   197  		},
   198  		Type:   reflect.TypeOf(int32(0)),
   199  		Offset: 6,
   200  	}, {
   201  		Attributes: &columns.Attributes{
   202  			Name:    "bool",
   203  			Width:   columns.GetDefault().DefaultWidth,
   204  			Visible: true,
   205  			Order:   2,
   206  		},
   207  		Type:   reflect.TypeOf(true),
   208  		Offset: 10,
   209  	}}
   210  
   211  	type empty struct{}
   212  	cols := columns.MustCreateColumns[empty]()
   213  	cols.AddFields(fields, func(ev *empty) unsafe.Pointer {
   214  		bytes := buf.Bytes()
   215  		return unsafe.Pointer(&bytes[0])
   216  	})
   217  	formatter := NewFormatter[empty](cols.GetColumnMap())
   218  	assert.Equal(t, `{"str": "foobar", "int32": 1234567890, "bool": true}`, formatter.FormatEntry(&empty{}))
   219  }
   220  
   221  func TestJSONFormatter(t *testing.T) {
   222  	type testStruct struct{}
   223  
   224  	cols := columns.MustCreateColumns[testStruct]()
   225  
   226  	require.NoError(t, cols.AddColumn(columns.Attributes{
   227  		Name: "parent.child1.grandchild1",
   228  	}, func(t *testStruct) any { return "parent.child1.grandchild1_text" }))
   229  	require.NoError(t, cols.AddColumn(columns.Attributes{
   230  		Name: "parent.child1.grandchild2",
   231  	}, func(t *testStruct) any { return "parent.child1.grandchild2_text" }))
   232  	require.NoError(t, cols.AddColumn(columns.Attributes{
   233  		Name: "parent.child2.grandchild1",
   234  	}, func(t *testStruct) any { return "parent.child2.grandchild1_text" }))
   235  	require.NoError(t, cols.AddColumn(columns.Attributes{
   236  		Name: "parent.child3",
   237  	}, func(t *testStruct) any { return "parent.child3_text" }))
   238  	require.NoError(t, cols.AddColumn(columns.Attributes{
   239  		Name: "parent.child4",
   240  	}, func(t *testStruct) any { return 42444 }))
   241  
   242  	require.NoError(t, cols.AddColumn(columns.Attributes{
   243  		Name: "parent.child1",
   244  	}, func(t *testStruct) any { return "This should be skipped/filtered" }))
   245  
   246  	expected := `{
   247    "parent": {
   248      "child1": {
   249        "grandchild1": "parent.child1.grandchild1_text",
   250        "grandchild2": "parent.child1.grandchild2_text"
   251      },
   252      "child2": {
   253        "grandchild1": "parent.child2.grandchild1_text"
   254      },
   255      "child3": "parent.child3_text",
   256      "child4": 42444
   257    }
   258  }`
   259  
   260  	formatter := NewFormatter[testStruct](cols.ColumnMap)
   261  	actual := formatter.FormatEntry(&testStruct{})
   262  	assert.JSONEq(t, expected, actual)
   263  
   264  	prettyFormatter := NewFormatter[testStruct](cols.ColumnMap, WithPrettyPrint())
   265  	actual = prettyFormatter.FormatEntry(&testStruct{})
   266  	// We don't know the ordering of the children inside the parent
   267  	// So we need to compare the JSON objects instead of the strings
   268  	assert.JSONEq(t, expected, actual)
   269  }
   270  
   271  func TestJSONFormatterDeeplyNested(t *testing.T) {
   272  	type testStruct struct{}
   273  
   274  	cols := columns.MustCreateColumns[testStruct]()
   275  
   276  	require.NoError(t, cols.AddColumn(columns.Attributes{
   277  		Name: "a.b.c.d.e.f.g",
   278  	}, func(t *testStruct) any { return "foobar" }))
   279  
   280  	expected := `{
   281    "a": {
   282      "b": {
   283        "c": {
   284          "d": {
   285            "e": {
   286              "f": {
   287                "g": "foobar"
   288              }
   289            }
   290          }
   291        }
   292      }
   293    }
   294  }`
   295  
   296  	formatter := NewFormatter[testStruct](cols.ColumnMap)
   297  	actual := formatter.FormatEntry(&testStruct{})
   298  	assert.JSONEq(t, expected, actual)
   299  
   300  	prettyFormatter := NewFormatter[testStruct](cols.ColumnMap, WithPrettyPrint())
   301  	actual = prettyFormatter.FormatEntry(&testStruct{})
   302  	assert.Equal(t, expected, actual)
   303  }