...

Source file src/github.com/cilium/ebpf/btf/types_test.go

Documentation: github.com/cilium/ebpf/btf

     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  }
   444  

View as plain text