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 }