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
80
81
82
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
268 {t1, "%s", []string{goType}, []string{t1Addr, "extra"}},
269
270 {t1, "%+s", []string{goType, t1Addr}, []string{"extra"}},
271
272 {t1, "%v", []string{goType, "extra"}, []string{t1Addr}},
273
274 {t1, "%+v", []string{goType, "extra", t1Addr}, nil},
275
276 {t2, "%v", []string{goType, t2.name}, []string{"extra"}},
277
278 {t2, "%1v", []string{goType, t2.name, "extra"}, nil},
279
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
437 func ExampleCopy_stripQualifiers() {
438 a := &Volatile{Type: &Pointer{Target: &Typedef{Name: "foo", Type: &Int{Size: 2}}}}
439 b := Copy(a, UnderlyingType)
440
441 fmt.Printf("%3v\n", b)
442
443 }
444
View as plain text