github.com/cilium/ebpf@v0.10.0/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 var _ Type = &Float{} 101 } 102 103 func TestType(t *testing.T) { 104 types := []func() Type{ 105 func() Type { return &Void{} }, 106 func() Type { return &Int{Size: 2} }, 107 func() Type { return &Pointer{Target: &Void{}} }, 108 func() Type { return &Array{Type: &Int{}} }, 109 func() Type { 110 return &Struct{ 111 Members: []Member{{Type: &Void{}}}, 112 } 113 }, 114 func() Type { 115 return &Union{ 116 Members: []Member{{Type: &Void{}}}, 117 } 118 }, 119 func() Type { return &Enum{} }, 120 func() Type { return &Fwd{Name: "thunk"} }, 121 func() Type { return &Typedef{Type: &Void{}} }, 122 func() Type { return &Volatile{Type: &Void{}} }, 123 func() Type { return &Const{Type: &Void{}} }, 124 func() Type { return &Restrict{Type: &Void{}} }, 125 func() Type { return &Func{Name: "foo", Type: &Void{}} }, 126 func() Type { 127 return &FuncProto{ 128 Params: []FuncParam{{Name: "bar", Type: &Void{}}}, 129 Return: &Void{}, 130 } 131 }, 132 func() Type { return &Var{Type: &Void{}} }, 133 func() Type { 134 return &Datasec{ 135 Vars: []VarSecinfo{{Type: &Void{}}}, 136 } 137 }, 138 func() Type { return &Float{} }, 139 func() Type { return &declTag{Type: &Void{}} }, 140 func() Type { return &typeTag{Type: &Void{}} }, 141 func() Type { return &cycle{&Void{}} }, 142 } 143 144 compareTypes := cmp.Comparer(func(a, b *Type) bool { 145 return a == b 146 }) 147 148 for _, fn := range types { 149 typ := fn() 150 t.Run(fmt.Sprintf("%T", typ), func(t *testing.T) { 151 t.Logf("%v", typ) 152 153 if typ == typ.copy() { 154 t.Error("Copy doesn't copy") 155 } 156 157 var a []*Type 158 walkType(typ, func(t *Type) { a = append(a, t) }) 159 160 if _, ok := typ.(*cycle); !ok { 161 if n := countChildren(t, reflect.TypeOf(typ)); len(a) < n { 162 t.Errorf("walkType visited %d children, expected at least %d", len(a), n) 163 } 164 } 165 166 var b []*Type 167 walkType(typ, func(t *Type) { b = append(b, t) }) 168 169 if diff := cmp.Diff(a, b, compareTypes); diff != "" { 170 t.Errorf("Walk mismatch (-want +got):\n%s", diff) 171 } 172 }) 173 } 174 } 175 176 func countChildren(t *testing.T, typ reflect.Type) int { 177 if typ.Kind() != reflect.Pointer { 178 t.Fatal("Expected pointer, got", typ.Kind()) 179 } 180 181 typ = typ.Elem() 182 if typ.Kind() != reflect.Struct { 183 t.Fatal("Expected struct, got", typ.Kind()) 184 } 185 186 var n int 187 for i := 0; i < typ.NumField(); i++ { 188 if typ.Field(i).Type == reflect.TypeOf((*Type)(nil)).Elem() { 189 n++ 190 } 191 } 192 193 return n 194 } 195 196 type testFormattableType struct { 197 name string 198 extra []interface{} 199 } 200 201 var _ formattableType = (*testFormattableType)(nil) 202 203 func (tft *testFormattableType) TypeName() string { return tft.name } 204 func (tft *testFormattableType) Format(fs fmt.State, verb rune) { 205 formatType(fs, verb, tft, tft.extra...) 206 } 207 208 func TestFormatType(t *testing.T) { 209 t1 := &testFormattableType{"", []interface{}{"extra"}} 210 t1Addr := fmt.Sprintf("%#p", t1) 211 goType := reflect.TypeOf(t1).Elem().Name() 212 213 t2 := &testFormattableType{"foo", []interface{}{t1}} 214 215 t3 := &testFormattableType{extra: []interface{}{""}} 216 217 tests := []struct { 218 t formattableType 219 fmt string 220 contains []string 221 omits []string 222 }{ 223 // %s doesn't contain address or extra. 224 {t1, "%s", []string{goType}, []string{t1Addr, "extra"}}, 225 // %+s doesn't contain extra. 226 {t1, "%+s", []string{goType, t1Addr}, []string{"extra"}}, 227 // %v does contain extra. 228 {t1, "%v", []string{goType, "extra"}, []string{t1Addr}}, 229 // %+v does contain address. 230 {t1, "%+v", []string{goType, "extra", t1Addr}, nil}, 231 // %v doesn't print nested types' extra. 232 {t2, "%v", []string{goType, t2.name}, []string{"extra"}}, 233 // %1v does print nested types' extra. 234 {t2, "%1v", []string{goType, t2.name, "extra"}, nil}, 235 // empty strings in extra don't emit anything. 236 {t3, "%v", []string{"[]"}, nil}, 237 } 238 239 for _, test := range tests { 240 t.Run(test.fmt, func(t *testing.T) { 241 str := fmt.Sprintf(test.fmt, test.t) 242 t.Log(str) 243 244 for _, want := range test.contains { 245 qt.Assert(t, str, qt.Contains, want) 246 } 247 248 for _, notWant := range test.omits { 249 qt.Assert(t, str, qt.Not(qt.Contains), notWant) 250 } 251 }) 252 } 253 } 254 255 func newCyclicalType(n int) Type { 256 ptr := &Pointer{} 257 prev := Type(ptr) 258 for i := 0; i < n; i++ { 259 switch i % 5 { 260 case 0: 261 prev = &Struct{ 262 Members: []Member{ 263 {Type: prev}, 264 }, 265 } 266 267 case 1: 268 prev = &Const{Type: prev} 269 case 2: 270 prev = &Volatile{Type: prev} 271 case 3: 272 prev = &Typedef{Type: prev} 273 case 4: 274 prev = &Array{Type: prev, Index: &Int{Size: 1}} 275 } 276 } 277 ptr.Target = prev 278 return ptr 279 } 280 281 func TestUnderlyingType(t *testing.T) { 282 wrappers := []struct { 283 name string 284 fn func(Type) Type 285 }{ 286 {"const", func(t Type) Type { return &Const{Type: t} }}, 287 {"volatile", func(t Type) Type { return &Volatile{Type: t} }}, 288 {"restrict", func(t Type) Type { return &Restrict{Type: t} }}, 289 {"typedef", func(t Type) Type { return &Typedef{Type: t} }}, 290 {"type tag", func(t Type) Type { return &typeTag{Type: t} }}, 291 } 292 293 for _, test := range wrappers { 294 t.Run(test.name+" cycle", func(t *testing.T) { 295 root := &Volatile{} 296 root.Type = test.fn(root) 297 298 got, ok := UnderlyingType(root).(*cycle) 299 qt.Assert(t, ok, qt.IsTrue) 300 qt.Assert(t, got.root, qt.Equals, root) 301 }) 302 } 303 304 for _, test := range wrappers { 305 t.Run(test.name, func(t *testing.T) { 306 want := &Int{} 307 got := UnderlyingType(test.fn(want)) 308 qt.Assert(t, got, qt.Equals, want) 309 }) 310 } 311 } 312 313 func TestInflateLegacyBitfield(t *testing.T) { 314 const offset = 3 315 const size = 5 316 317 var rawInt rawType 318 rawInt.SetKind(kindInt) 319 rawInt.SetSize(4) 320 var data btfInt 321 data.SetOffset(offset) 322 data.SetBits(size) 323 rawInt.data = &data 324 325 var beforeInt rawType 326 beforeInt.SetKind(kindStruct) 327 beforeInt.SetVlen(1) 328 beforeInt.data = []btfMember{{Type: 2}} 329 330 afterInt := beforeInt 331 afterInt.data = []btfMember{{Type: 1}} 332 333 emptyStrings := newStringTable("") 334 335 for _, test := range []struct { 336 name string 337 raw []rawType 338 }{ 339 {"struct before int", []rawType{beforeInt, rawInt}}, 340 {"struct after int", []rawType{rawInt, afterInt}}, 341 } { 342 t.Run(test.name, func(t *testing.T) { 343 types, err := inflateRawTypes(test.raw, nil, emptyStrings) 344 if err != nil { 345 t.Fatal(err) 346 } 347 348 for _, typ := range types { 349 s, ok := typ.(*Struct) 350 if !ok { 351 continue 352 } 353 354 i := s.Members[0] 355 if i.BitfieldSize != size { 356 t.Errorf("Expected bitfield size %d, got %d", size, i.BitfieldSize) 357 } 358 359 if i.Offset != offset { 360 t.Errorf("Expected offset %d, got %d", offset, i.Offset) 361 } 362 363 return 364 } 365 366 t.Fatal("No Struct returned from inflateRawTypes") 367 }) 368 } 369 } 370 371 func BenchmarkWalk(b *testing.B) { 372 types := []Type{ 373 &Void{}, 374 &Int{}, 375 &Pointer{}, 376 &Array{}, 377 &Struct{Members: make([]Member, 2)}, 378 &Union{Members: make([]Member, 2)}, 379 &Enum{}, 380 &Fwd{}, 381 &Typedef{}, 382 &Volatile{}, 383 &Const{}, 384 &Restrict{}, 385 &Func{}, 386 &FuncProto{Params: make([]FuncParam, 2)}, 387 &Var{}, 388 &Datasec{Vars: make([]VarSecinfo, 2)}, 389 } 390 391 for _, typ := range types { 392 b.Run(fmt.Sprint(typ), func(b *testing.B) { 393 b.ReportAllocs() 394 395 for i := 0; i < b.N; i++ { 396 var dq typeDeque 397 walkType(typ, dq.Push) 398 } 399 }) 400 } 401 } 402 403 func BenchmarkUnderlyingType(b *testing.B) { 404 b.Run("no unwrapping", func(b *testing.B) { 405 v := &Int{} 406 b.ReportAllocs() 407 b.ResetTimer() 408 409 for i := 0; i < b.N; i++ { 410 UnderlyingType(v) 411 } 412 }) 413 414 b.Run("single unwrapping", func(b *testing.B) { 415 v := &Typedef{Type: &Int{}} 416 b.ReportAllocs() 417 b.ResetTimer() 418 419 for i := 0; i < b.N; i++ { 420 UnderlyingType(v) 421 } 422 }) 423 } 424 425 // Copy can be used with UnderlyingType to strip qualifiers from a type graph. 426 func ExampleCopy_stripQualifiers() { 427 a := &Volatile{Type: &Pointer{Target: &Typedef{Name: "foo", Type: &Int{Size: 2}}}} 428 b := Copy(a, UnderlyingType) 429 // b has Volatile and Typedef removed. 430 fmt.Printf("%3v\n", b) 431 // Output: Pointer[target=Int[unsigned size=16]] 432 }