github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/btf/format_test.go (about)

     1  package btf
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"go/format"
     7  	"math"
     8  	"strings"
     9  	"testing"
    10  )
    11  
    12  func TestGoTypeDeclaration(t *testing.T) {
    13  	tests := []struct {
    14  		typ    Type
    15  		output string
    16  	}{
    17  		{&Int{Size: 1}, "type t uint8"},
    18  		{&Int{Size: 1, Encoding: Bool}, "type t bool"},
    19  		{&Int{Size: 1, Encoding: Char}, "type t uint8"},
    20  		{&Int{Size: 2, Encoding: Signed}, "type t int16"},
    21  		{&Int{Size: 4, Encoding: Signed}, "type t int32"},
    22  		{&Int{Size: 8}, "type t uint64"},
    23  		{&Typedef{Name: "frob", Type: &Int{Size: 8}}, "type t uint64"},
    24  		{&Int{Size: 16}, "type t [16]byte /* uint128 */"},
    25  		{&Enum{Values: []EnumValue{{"FOO", 32}}, Size: 4}, "type t uint32; const ( tFOO t = 32; )"},
    26  		{
    27  			&Enum{
    28  				Values: []EnumValue{
    29  					{"MINUS_ONE", math.MaxUint64},
    30  					{"MINUS_TWO", math.MaxUint64 - 1},
    31  				},
    32  				Size:   1,
    33  				Signed: true,
    34  			},
    35  			"type t int8; const ( tMINUS_ONE t = -1; tMINUS_TWO t = -2; )",
    36  		},
    37  		{
    38  			&Struct{
    39  				Name: "enum literals",
    40  				Size: 2,
    41  				Members: []Member{
    42  					{Name: "enum", Type: &Enum{Values: []EnumValue{{"BAR", 1}}, Size: 2}, Offset: 0},
    43  				},
    44  			},
    45  			"type t struct { enum uint16; }",
    46  		},
    47  		{&Array{Nelems: 2, Type: &Int{Size: 1}}, "type t [2]uint8"},
    48  		{
    49  			&Union{
    50  				Size: 8,
    51  				Members: []Member{
    52  					{Name: "a", Type: &Int{Size: 4}},
    53  					{Name: "b", Type: &Int{Size: 8}},
    54  				},
    55  			},
    56  			"type t struct { a uint32; _ [4]byte; }",
    57  		},
    58  		{
    59  			&Struct{
    60  				Name: "field padding",
    61  				Size: 16,
    62  				Members: []Member{
    63  					{Name: "frob", Type: &Int{Size: 4}, Offset: 0},
    64  					{Name: "foo", Type: &Int{Size: 8}, Offset: 8 * 8},
    65  				},
    66  			},
    67  			"type t struct { frob uint32; _ [4]byte; foo uint64; }",
    68  		},
    69  		{
    70  			&Struct{
    71  				Name: "end padding",
    72  				Size: 16,
    73  				Members: []Member{
    74  					{Name: "foo", Type: &Int{Size: 8}, Offset: 0},
    75  					{Name: "frob", Type: &Int{Size: 4}, Offset: 8 * 8},
    76  				},
    77  			},
    78  			"type t struct { foo uint64; frob uint32; _ [4]byte; }",
    79  		},
    80  		{
    81  			&Struct{
    82  				Name: "bitfield",
    83  				Size: 8,
    84  				Members: []Member{
    85  					{Name: "foo", Type: &Int{Size: 4}, Offset: 0, BitfieldSize: 1},
    86  					{Name: "frob", Type: &Int{Size: 4}, Offset: 4 * 8},
    87  				},
    88  			},
    89  			"type t struct { _ [4]byte /* unsupported bitfield */; frob uint32; }",
    90  		},
    91  		{
    92  			&Struct{
    93  				Name: "nested",
    94  				Size: 8,
    95  				Members: []Member{
    96  					{
    97  						Name: "foo",
    98  						Type: &Struct{
    99  							Size: 4,
   100  							Members: []Member{
   101  								{Name: "bar", Type: &Int{Size: 4}, Offset: 0},
   102  							},
   103  						},
   104  					},
   105  					{Name: "frob", Type: &Int{Size: 4}, Offset: 4 * 8},
   106  				},
   107  			},
   108  			"type t struct { foo struct { bar uint32; }; frob uint32; }",
   109  		},
   110  		{
   111  			&Struct{
   112  				Name: "nested anon union",
   113  				Size: 8,
   114  				Members: []Member{
   115  					{
   116  						Name: "",
   117  						Type: &Union{
   118  							Size: 4,
   119  							Members: []Member{
   120  								{Name: "foo", Type: &Int{Size: 4}, Offset: 0},
   121  								{Name: "bar", Type: &Int{Size: 4}, Offset: 0},
   122  							},
   123  						},
   124  					},
   125  				},
   126  			},
   127  			"type t struct { foo uint32; _ [4]byte; }",
   128  		},
   129  		{
   130  			&Datasec{
   131  				Size: 16,
   132  				Vars: []VarSecinfo{
   133  					{&Var{Name: "s", Type: &Int{Size: 2}, Linkage: StaticVar}, 0, 2},
   134  					{&Var{Name: "g", Type: &Int{Size: 4}, Linkage: GlobalVar}, 4, 4},
   135  					{&Var{Name: "e", Type: &Int{Size: 8}, Linkage: ExternVar}, 8, 8},
   136  				},
   137  			},
   138  			"type t struct { _ [4]byte; g uint32; _ [8]byte; }",
   139  		},
   140  	}
   141  
   142  	for _, test := range tests {
   143  		t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
   144  			have := mustGoTypeDeclaration(t, test.typ, nil, nil)
   145  			if have != test.output {
   146  				t.Errorf("Unexpected output:\n\t-%s\n\t+%s", test.output, have)
   147  			}
   148  		})
   149  	}
   150  }
   151  
   152  func TestGoTypeDeclarationNamed(t *testing.T) {
   153  	e1 := &Enum{Name: "e1", Size: 4}
   154  	s1 := &Struct{
   155  		Name: "s1",
   156  		Size: 4,
   157  		Members: []Member{
   158  			{Name: "frob", Type: e1},
   159  		},
   160  	}
   161  	s2 := &Struct{
   162  		Name: "s2",
   163  		Size: 4,
   164  		Members: []Member{
   165  			{Name: "frood", Type: s1},
   166  		},
   167  	}
   168  	td := &Typedef{Name: "td", Type: e1}
   169  	arr := &Array{Nelems: 1, Type: td}
   170  
   171  	tests := []struct {
   172  		typ    Type
   173  		named  []Type
   174  		output string
   175  	}{
   176  		{e1, []Type{e1}, "type t uint32"},
   177  		{s1, []Type{e1, s1}, "type t struct { frob E1; }"},
   178  		{s2, []Type{e1}, "type t struct { frood struct { frob E1; }; }"},
   179  		{s2, []Type{e1, s1}, "type t struct { frood S1; }"},
   180  		{td, nil, "type t uint32"},
   181  		{td, []Type{td}, "type t uint32"},
   182  		{arr, []Type{td}, "type t [1]TD"},
   183  	}
   184  
   185  	for _, test := range tests {
   186  		t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
   187  			names := make(map[Type]string)
   188  			for _, t := range test.named {
   189  				names[t] = strings.ToUpper(t.TypeName())
   190  			}
   191  
   192  			have := mustGoTypeDeclaration(t, test.typ, names, nil)
   193  			if have != test.output {
   194  				t.Errorf("Unexpected output:\n\t-%s\n\t+%s", test.output, have)
   195  			}
   196  		})
   197  	}
   198  }
   199  
   200  func TestGoTypeDeclarationQualifiers(t *testing.T) {
   201  	i := &Int{Size: 4}
   202  	want := mustGoTypeDeclaration(t, i, nil, nil)
   203  
   204  	tests := []struct {
   205  		typ Type
   206  	}{
   207  		{&Volatile{Type: i}},
   208  		{&Const{Type: i}},
   209  		{&Restrict{Type: i}},
   210  	}
   211  
   212  	for _, test := range tests {
   213  		t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
   214  			have := mustGoTypeDeclaration(t, test.typ, nil, nil)
   215  			if have != want {
   216  				t.Errorf("Unexpected output:\n\t-%s\n\t+%s", want, have)
   217  			}
   218  		})
   219  	}
   220  }
   221  
   222  func TestGoTypeDeclarationCycle(t *testing.T) {
   223  	s := &Struct{Name: "cycle"}
   224  	s.Members = []Member{{Name: "f", Type: s}}
   225  
   226  	var gf GoFormatter
   227  	_, err := gf.TypeDeclaration("t", s)
   228  	if !errors.Is(err, errNestedTooDeep) {
   229  		t.Fatal("Expected errNestedTooDeep, got", err)
   230  	}
   231  }
   232  
   233  func TestRejectBogusTypes(t *testing.T) {
   234  	tests := []struct {
   235  		typ Type
   236  	}{
   237  		{&Struct{
   238  			Size: 1,
   239  			Members: []Member{
   240  				{Name: "foo", Type: &Int{Size: 2}, Offset: 0},
   241  			},
   242  		}},
   243  		{&Int{Size: 2, Encoding: Bool}},
   244  		{&Int{Size: 1, Encoding: Char | Signed}},
   245  		{&Int{Size: 2, Encoding: Char}},
   246  	}
   247  	for _, test := range tests {
   248  		t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
   249  			var gf GoFormatter
   250  
   251  			_, err := gf.TypeDeclaration("t", test.typ)
   252  			if err == nil {
   253  				t.Fatal("TypeDeclaration does not reject bogus type")
   254  			}
   255  		})
   256  	}
   257  }
   258  
   259  func mustGoTypeDeclaration(tb testing.TB, typ Type, names map[Type]string, id func(string) string) string {
   260  	tb.Helper()
   261  
   262  	gf := GoFormatter{
   263  		Names:      names,
   264  		Identifier: id,
   265  	}
   266  
   267  	have, err := gf.TypeDeclaration("t", typ)
   268  	if err != nil {
   269  		tb.Fatal(err)
   270  	}
   271  
   272  	_, err = format.Source([]byte(have))
   273  	if err != nil {
   274  		tb.Fatalf("Output can't be formatted: %s\n%s", err, have)
   275  	}
   276  
   277  	return have
   278  }