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