github.com/kubeshark/ebpf@v0.9.2/btf/types_test.go (about) 1 package btf 2 3 import ( 4 "fmt" 5 "reflect" 6 "testing" 7 8 qt "github.com/frankban/quicktest" 9 "github.com/google/go-cmp/cmp" 10 ) 11 12 func TestSizeof(t *testing.T) { 13 testcases := []struct { 14 size int 15 typ Type 16 }{ 17 {0, (*Void)(nil)}, 18 {1, &Int{Size: 1}}, 19 {8, &Enum{Size: 8}}, 20 {0, &Array{Type: &Pointer{Target: (*Void)(nil)}, Nelems: 0}}, 21 {12, &Array{Type: &Enum{Size: 4}, Nelems: 3}}, 22 } 23 24 for _, tc := range testcases { 25 name := fmt.Sprint(tc.typ) 26 t.Run(name, func(t *testing.T) { 27 have, err := Sizeof(tc.typ) 28 if err != nil { 29 t.Fatal("Can't calculate size:", err) 30 } 31 if have != tc.size { 32 t.Errorf("Expected size %d, got %d", tc.size, have) 33 } 34 }) 35 } 36 } 37 38 func TestCopy(t *testing.T) { 39 _ = Copy((*Void)(nil), nil) 40 41 in := &Int{Size: 4} 42 out := Copy(in, nil) 43 44 in.Size = 8 45 if size := out.(*Int).Size; size != 4 { 46 t.Error("Copy doesn't make a copy, expected size 4, got", size) 47 } 48 49 t.Run("cyclical", func(t *testing.T) { 50 _ = Copy(newCyclicalType(2), nil) 51 }) 52 53 t.Run("identity", func(t *testing.T) { 54 u16 := &Int{Size: 2} 55 56 out := Copy(&Struct{ 57 Members: []Member{ 58 {Name: "a", Type: u16}, 59 {Name: "b", Type: u16}, 60 }, 61 }, nil) 62 63 outStruct := out.(*Struct) 64 qt.Assert(t, outStruct.Members[0].Type, qt.Equals, outStruct.Members[1].Type) 65 }) 66 } 67 68 func BenchmarkCopy(b *testing.B) { 69 typ := newCyclicalType(10) 70 71 b.ReportAllocs() 72 b.ResetTimer() 73 74 for i := 0; i < b.N; i++ { 75 Copy(typ, nil) 76 } 77 } 78 79 // The following are valid Types. 80 // 81 // There currently is no better way to document which 82 // types implement an interface. 83 func ExampleType_validTypes() { 84 var _ Type = &Void{} 85 var _ Type = &Int{} 86 var _ Type = &Pointer{} 87 var _ Type = &Array{} 88 var _ Type = &Struct{} 89 var _ Type = &Union{} 90 var _ Type = &Enum{} 91 var _ Type = &Fwd{} 92 var _ Type = &Typedef{} 93 var _ Type = &Volatile{} 94 var _ Type = &Const{} 95 var _ Type = &Restrict{} 96 var _ Type = &Func{} 97 var _ Type = &FuncProto{} 98 var _ Type = &Var{} 99 var _ Type = &Datasec{} 100 } 101 102 func TestType(t *testing.T) { 103 types := []func() Type{ 104 func() Type { return &Void{} }, 105 func() Type { return &Int{Size: 2} }, 106 func() Type { return &Pointer{Target: &Void{}} }, 107 func() Type { return &Array{Type: &Int{}} }, 108 func() Type { 109 return &Struct{ 110 Members: []Member{{Type: &Void{}}}, 111 } 112 }, 113 func() Type { 114 return &Union{ 115 Members: []Member{{Type: &Void{}}}, 116 } 117 }, 118 func() Type { return &Enum{} }, 119 func() Type { return &Fwd{Name: "thunk"} }, 120 func() Type { return &Typedef{Type: &Void{}} }, 121 func() Type { return &Volatile{Type: &Void{}} }, 122 func() Type { return &Const{Type: &Void{}} }, 123 func() Type { return &Restrict{Type: &Void{}} }, 124 func() Type { return &Func{Name: "foo", Type: &Void{}} }, 125 func() Type { 126 return &FuncProto{ 127 Params: []FuncParam{{Name: "bar", Type: &Void{}}}, 128 Return: &Void{}, 129 } 130 }, 131 func() Type { return &Var{Type: &Void{}} }, 132 func() Type { 133 return &Datasec{ 134 Vars: []VarSecinfo{{Type: &Void{}}}, 135 } 136 }, 137 func() Type { return &cycle{&Void{}} }, 138 } 139 140 compareTypes := cmp.Comparer(func(a, b *Type) bool { 141 return a == b 142 }) 143 144 for _, fn := range types { 145 typ := fn() 146 t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) { 147 t.Logf("%v", typ) 148 149 if typ == typ.copy() { 150 t.Error("Copy doesn't copy") 151 } 152 153 var first, second typeDeque 154 typ.walk(&first) 155 typ.walk(&second) 156 157 if diff := cmp.Diff(first.all(), second.all(), compareTypes); diff != "" { 158 t.Errorf("Walk mismatch (-want +got):\n%s", diff) 159 } 160 }) 161 } 162 } 163 164 func TestTypeDeque(t *testing.T) { 165 a, b := new(Type), new(Type) 166 167 t.Run("pop", func(t *testing.T) { 168 var td typeDeque 169 td.push(a) 170 td.push(b) 171 172 if td.pop() != b { 173 t.Error("Didn't pop b first") 174 } 175 176 if td.pop() != a { 177 t.Error("Didn't pop a second") 178 } 179 180 if td.pop() != nil { 181 t.Error("Didn't pop nil") 182 } 183 }) 184 185 t.Run("shift", func(t *testing.T) { 186 var td typeDeque 187 td.push(a) 188 td.push(b) 189 190 if td.shift() != a { 191 t.Error("Didn't shift a second") 192 } 193 194 if td.shift() != b { 195 t.Error("Didn't shift b first") 196 } 197 198 if td.shift() != nil { 199 t.Error("Didn't shift nil") 200 } 201 }) 202 203 t.Run("push", func(t *testing.T) { 204 var td typeDeque 205 td.push(a) 206 td.push(b) 207 td.shift() 208 209 ts := make([]Type, 12) 210 for i := range ts { 211 td.push(&ts[i]) 212 } 213 214 if td.shift() != b { 215 t.Error("Didn't shift b first") 216 } 217 for i := range ts { 218 if td.shift() != &ts[i] { 219 t.Fatal("Shifted wrong Type at pos", i) 220 } 221 } 222 }) 223 224 t.Run("all", func(t *testing.T) { 225 var td typeDeque 226 td.push(a) 227 td.push(b) 228 229 all := td.all() 230 if len(all) != 2 { 231 t.Fatal("Expected 2 elements, got", len(all)) 232 } 233 234 if all[0] != a || all[1] != b { 235 t.Fatal("Elements don't match") 236 } 237 }) 238 } 239 240 type testFormattableType struct { 241 name string 242 extra []interface{} 243 } 244 245 var _ formattableType = (*testFormattableType)(nil) 246 247 func (tft *testFormattableType) TypeName() string { return tft.name } 248 func (tft *testFormattableType) Format(fs fmt.State, verb rune) { 249 formatType(fs, verb, tft, tft.extra...) 250 } 251 252 func TestFormatType(t *testing.T) { 253 t1 := &testFormattableType{"", []interface{}{"extra"}} 254 t1Addr := fmt.Sprintf("%#p", t1) 255 goType := reflect.TypeOf(t1).Elem().Name() 256 257 t2 := &testFormattableType{"foo", []interface{}{t1}} 258 259 t3 := &testFormattableType{extra: []interface{}{""}} 260 261 tests := []struct { 262 t formattableType 263 fmt string 264 contains []string 265 omits []string 266 }{ 267 // %s doesn't contain address or extra. 268 {t1, "%s", []string{goType}, []string{t1Addr, "extra"}}, 269 // %+s doesn't contain extra. 270 {t1, "%+s", []string{goType, t1Addr}, []string{"extra"}}, 271 // %v does contain extra. 272 {t1, "%v", []string{goType, "extra"}, []string{t1Addr}}, 273 // %+v does contain address. 274 {t1, "%+v", []string{goType, "extra", t1Addr}, nil}, 275 // %v doesn't print nested types' extra. 276 {t2, "%v", []string{goType, t2.name}, []string{"extra"}}, 277 // %1v does print nested types' extra. 278 {t2, "%1v", []string{goType, t2.name, "extra"}, nil}, 279 // empty strings in extra don't emit anything. 280 {t3, "%v", []string{"[]"}, nil}, 281 } 282 283 for _, test := range tests { 284 t.Run(test.fmt, func(t *testing.T) { 285 str := fmt.Sprintf(test.fmt, test.t) 286 t.Log(str) 287 288 for _, want := range test.contains { 289 qt.Assert(t, str, qt.Contains, want) 290 } 291 292 for _, notWant := range test.omits { 293 qt.Assert(t, str, qt.Not(qt.Contains), notWant) 294 } 295 }) 296 } 297 } 298 299 func newCyclicalType(n int) Type { 300 ptr := &Pointer{} 301 prev := Type(ptr) 302 for i := 0; i < n; i++ { 303 switch i % 5 { 304 case 0: 305 prev = &Struct{ 306 Members: []Member{ 307 {Type: prev}, 308 }, 309 } 310 311 case 1: 312 prev = &Const{Type: prev} 313 case 2: 314 prev = &Volatile{Type: prev} 315 case 3: 316 prev = &Typedef{Type: prev} 317 case 4: 318 prev = &Array{Type: prev} 319 } 320 } 321 ptr.Target = prev 322 return ptr 323 } 324 325 func TestUnderlyingType(t *testing.T) { 326 wrappers := []struct { 327 name string 328 fn func(Type) Type 329 }{ 330 {"const", func(t Type) Type { return &Const{Type: t} }}, 331 {"volatile", func(t Type) Type { return &Volatile{Type: t} }}, 332 {"restrict", func(t Type) Type { return &Restrict{Type: t} }}, 333 {"typedef", func(t Type) Type { return &Typedef{Type: t} }}, 334 } 335 336 for _, test := range wrappers { 337 t.Run(test.name+" cycle", func(t *testing.T) { 338 root := &Volatile{} 339 root.Type = test.fn(root) 340 341 got, ok := UnderlyingType(root).(*cycle) 342 qt.Assert(t, ok, qt.IsTrue) 343 qt.Assert(t, got.root, qt.Equals, root) 344 }) 345 } 346 347 for _, test := range wrappers { 348 t.Run(test.name, func(t *testing.T) { 349 want := &Int{} 350 got := UnderlyingType(test.fn(want)) 351 qt.Assert(t, got, qt.Equals, want) 352 }) 353 } 354 } 355 356 func TestInflateLegacyBitfield(t *testing.T) { 357 const offset = 3 358 const size = 5 359 360 var rawInt rawType 361 rawInt.SetKind(kindInt) 362 rawInt.SetSize(4) 363 var data btfInt 364 data.SetOffset(offset) 365 data.SetBits(size) 366 rawInt.data = &data 367 368 var beforeInt rawType 369 beforeInt.SetKind(kindStruct) 370 beforeInt.SetVlen(1) 371 beforeInt.data = []btfMember{{Type: 2}} 372 373 afterInt := beforeInt 374 afterInt.data = []btfMember{{Type: 1}} 375 376 emptyStrings := newStringTable("") 377 378 for _, test := range []struct { 379 name string 380 raw []rawType 381 }{ 382 {"struct before int", []rawType{beforeInt, rawInt}}, 383 {"struct after int", []rawType{rawInt, afterInt}}, 384 } { 385 t.Run(test.name, func(t *testing.T) { 386 types, err := inflateRawTypes(test.raw, nil, emptyStrings) 387 if err != nil { 388 t.Fatal(err) 389 } 390 391 for _, typ := range types { 392 s, ok := typ.(*Struct) 393 if !ok { 394 continue 395 } 396 397 i := s.Members[0] 398 if i.BitfieldSize != size { 399 t.Errorf("Expected bitfield size %d, got %d", size, i.BitfieldSize) 400 } 401 402 if i.Offset != offset { 403 t.Errorf("Expected offset %d, got %d", offset, i.Offset) 404 } 405 406 return 407 } 408 409 t.Fatal("No Struct returned from inflateRawTypes") 410 }) 411 } 412 } 413 414 func BenchmarkUnderlyingType(b *testing.B) { 415 b.Run("no unwrapping", func(b *testing.B) { 416 v := &Int{} 417 b.ReportAllocs() 418 b.ResetTimer() 419 420 for i := 0; i < b.N; i++ { 421 UnderlyingType(v) 422 } 423 }) 424 425 b.Run("single unwrapping", func(b *testing.B) { 426 v := &Typedef{Type: &Int{}} 427 b.ReportAllocs() 428 b.ResetTimer() 429 430 for i := 0; i < b.N; i++ { 431 UnderlyingType(v) 432 } 433 }) 434 } 435 436 // Copy can be used with UnderlyingType to strip qualifiers from a type graph. 437 func ExampleCopy_stripQualifiers() { 438 a := &Volatile{Type: &Pointer{Target: &Typedef{Name: "foo", Type: &Int{Size: 2}}}} 439 b := Copy(a, UnderlyingType) 440 // b has Volatile and Typedef removed. 441 fmt.Printf("%3v\n", b) 442 // Output: Pointer[target=Int[unsigned size=16]] 443 }