Binary API generator improvements
[govpp.git] / binapigen / generate_test.go
1 //  Copyright (c) 2020 Cisco and/or its affiliates.
2 //
3 //  Licensed under the Apache License, Version 2.0 (the "License");
4 //  you may not use this file except in compliance with the License.
5 //  You may obtain a copy of the License at:
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 //  Unless required by applicable law or agreed to in writing, software
10 //  distributed under the License is distributed on an "AS IS" BASIS,
11 //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 //  See the License for the specific language governing permissions and
13 //  limitations under the License.
14
15 package binapigen
16
17 import (
18         "git.fd.io/govpp.git/examples/binapi/interfaces"
19         "git.fd.io/govpp.git/examples/binapi/ip_types"
20         "os"
21         "strings"
22         "testing"
23
24         . "github.com/onsi/gomega"
25
26         "git.fd.io/govpp.git/binapigen/vppapi"
27 )
28
29 const testOutputDir = "test_output_directory"
30
31 func GenerateFromFile(file, outputDir string, opts Options) error {
32         apifile, err := vppapi.ParseFile(file)
33         if err != nil {
34                 return err
35         }
36
37         g, err := New(opts, []*vppapi.File{apifile})
38         if err != nil {
39                 return err
40         }
41         for _, file := range g.Files {
42                 if !file.Generate {
43                         continue
44                 }
45                 GenerateBinapi(g, file, outputDir)
46                 if file.Service != nil {
47                         GenerateRPC(g, file, outputDir)
48                 }
49         }
50
51         if err = g.Generate(); err != nil {
52                 return err
53         }
54
55         return nil
56 }
57
58 func TestGenerateFromFile(t *testing.T) {
59         RegisterTestingT(t)
60
61         // remove directory created during test
62         defer os.RemoveAll(testOutputDir)
63
64         err := GenerateFromFile("vppapi/testdata/acl.api.json", testOutputDir, Options{FilesToGenerate: []string{"acl"}})
65         Expect(err).ShouldNot(HaveOccurred())
66         fileInfo, err := os.Stat(testOutputDir + "/acl/acl.ba.go")
67         Expect(err).ShouldNot(HaveOccurred())
68         Expect(fileInfo.IsDir()).To(BeFalse())
69         Expect(fileInfo.Name()).To(BeEquivalentTo("acl.ba.go"))
70 }
71
72 func TestGenerateFromFileInputError(t *testing.T) {
73         RegisterTestingT(t)
74
75         err := GenerateFromFile("vppapi/testdata/nonexisting.json", testOutputDir, Options{})
76         Expect(err).Should(HaveOccurred())
77         Expect(err.Error()).To(ContainSubstring("unsupported"))
78 }
79
80 func TestGenerateFromFileReadJsonError(t *testing.T) {
81         RegisterTestingT(t)
82
83         err := GenerateFromFile("vppapi/testdata/input-read-json-error.json", testOutputDir, Options{})
84         Expect(err).Should(HaveOccurred())
85         Expect(err.Error()).To(ContainSubstring("unsupported"))
86 }
87
88 func TestGenerateFromFileGeneratePackageError(t *testing.T) {
89         RegisterTestingT(t)
90
91         // generate package throws panic, recover after it
92         defer func() {
93                 if recovery := recover(); recovery != nil {
94                         t.Logf("Recovered from panic: %v", recovery)
95                 }
96                 os.RemoveAll(testOutputDir)
97         }()
98
99         err := GenerateFromFile("vppapi/testdata/input-generate-error.json", testOutputDir, Options{})
100         Expect(err).Should(HaveOccurred())
101 }
102
103 func TestGeneratedParseAddress(t *testing.T) {
104         RegisterTestingT(t)
105
106         var data = []struct {
107                 input  string
108                 result ip_types.Address
109         }{
110                 {"192.168.0.1", ip_types.Address{
111                         Af: ip_types.ADDRESS_IP4,
112                         Un: ip_types.AddressUnionIP4(ip_types.IP4Address{192, 168, 0, 1}),
113                 }},
114                 {"aac1:0:ab45::", ip_types.Address{
115                         Af: ip_types.ADDRESS_IP6,
116                         Un: ip_types.AddressUnionIP6(ip_types.IP6Address{170, 193, 0, 0, 171, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
117                 }},
118         }
119
120         for _, entry := range data {
121                 t.Run(entry.input, func(t *testing.T) {
122                         parsedAddress, err := ip_types.ParseAddress(entry.input)
123                         Expect(err).ShouldNot(HaveOccurred())
124                         Expect(parsedAddress).To(Equal(entry.result))
125
126                         originAddress := parsedAddress.ToString()
127                         Expect(originAddress).To(Equal(entry.input))
128                 })
129         }
130 }
131
132 func TestGeneratedParseAddressError(t *testing.T) {
133         RegisterTestingT(t)
134
135         _, err := ip_types.ParseAddress("malformed_ip")
136         Expect(err).Should(HaveOccurred())
137 }
138
139 func TestGeneratedParsePrefix(t *testing.T) {
140         RegisterTestingT(t)
141
142         var data = []struct {
143                 input  string
144                 result ip_types.Prefix
145         }{
146                 {"192.168.0.1/24", ip_types.Prefix{
147                         Address: ip_types.Address{
148                                 Af: ip_types.ADDRESS_IP4,
149                                 Un: ip_types.AddressUnionIP4(ip_types.IP4Address{192, 168, 0, 1}),
150                         },
151                         Len: 24,
152                 }},
153                 {"192.168.0.1", ip_types.Prefix{
154                         Address: ip_types.Address{
155                                 Af: ip_types.ADDRESS_IP4,
156                                 Un: ip_types.AddressUnionIP4(ip_types.IP4Address{192, 168, 0, 1}),
157                         },
158                         Len: 32,
159                 }},
160                 {"aac1:0:ab45::/96", ip_types.Prefix{
161                         Address: ip_types.Address{
162                                 Af: ip_types.ADDRESS_IP6,
163                                 Un: ip_types.AddressUnionIP6(ip_types.IP6Address{170, 193, 0, 0, 171, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
164                         },
165                         Len: 96,
166                 }},
167                 {"aac1:0:ab45::", ip_types.Prefix{
168                         Address: ip_types.Address{
169                                 Af: ip_types.ADDRESS_IP6,
170                                 Un: ip_types.AddressUnionIP6(ip_types.IP6Address{170, 193, 0, 0, 171, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
171                         },
172                         Len: 128,
173                 }},
174         }
175
176         for _, entry := range data {
177                 t.Run(entry.input, func(t *testing.T) {
178                         parsedAddress, err := ip_types.ParsePrefix(entry.input)
179                         Expect(err).ShouldNot(HaveOccurred())
180                         Expect(parsedAddress).To(Equal(entry.result))
181
182                         // Parsed IP without prefix receives a default one
183                         // so the input data must be adjusted
184                         if entry.result.Address.Af == ip_types.ADDRESS_IP4 && !strings.Contains(entry.input, "/") {
185                                 entry.input = entry.input + "/32"
186                         }
187                         if entry.result.Address.Af == ip_types.ADDRESS_IP6 && !strings.Contains(entry.input, "/") {
188                                 entry.input = entry.input + "/128"
189                         }
190                         originAddress := parsedAddress.ToString()
191                         Expect(originAddress).To(Equal(entry.input))
192                 })
193         }
194 }
195
196 func TestGeneratedParsePrefixError(t *testing.T) {
197         RegisterTestingT(t)
198
199         _, err := ip_types.ParsePrefix("malformed_ip")
200         Expect(err).Should(HaveOccurred())
201 }
202
203 func TestGeneratedParseMAC(t *testing.T) {
204         RegisterTestingT(t)
205
206         var data = []struct {
207                 input  string
208                 result interfaces.MacAddress
209         }{
210                 {"b7:b9:bb:a1:5c:af", interfaces.MacAddress{183, 185, 187, 161, 92, 175}},
211                 {"47:4b:c7:3e:06:c8", interfaces.MacAddress{71, 75, 199, 62, 6, 200}},
212                 {"a7:cc:9f:10:18:e3", interfaces.MacAddress{167, 204, 159, 16, 24, 227}},
213         }
214
215         for _, entry := range data {
216                 t.Run(entry.input, func(t *testing.T) {
217                         parsedMac, err := interfaces.ParseMAC(entry.input)
218                         Expect(err).ShouldNot(HaveOccurred())
219                         Expect(parsedMac).To(Equal(entry.result))
220
221                         originAddress := parsedMac.ToString()
222                         Expect(originAddress).To(Equal(entry.input))
223                 })
224         }
225 }
226
227 func TestGeneratedParseMACError(t *testing.T) {
228         RegisterTestingT(t)
229
230         _, err := interfaces.ParseMAC("malformed_mac")
231         Expect(err).Should(HaveOccurred())
232 }
233
234 /*func TestGetContext(t *testing.T) {
235         RegisterTestingT(t)
236         outDir := "test_output_directory"
237         result, err := newContext("testdata/af_packet.api.json", outDir)
238         Expect(err).ShouldNot(HaveOccurred())
239         Expect(result).ToNot(BeNil())
240         Expect(result.outputFile).To(BeEquivalentTo(outDir + "/af_packet/af_packet.ba.go"))
241 }
242
243 func TestGetContextNoJsonFile(t *testing.T) {
244         RegisterTestingT(t)
245         outDir := "test_output_directory"
246         result, err := newContext("testdata/input.txt", outDir)
247         Expect(err).Should(HaveOccurred())
248         Expect(err.Error()).To(ContainSubstring("invalid input file name"))
249         Expect(result).To(BeNil())
250 }
251
252 func TestGetContextInterfaceJson(t *testing.T) {
253         RegisterTestingT(t)
254         outDir := "test_output_directory"
255         result, err := newContext("testdata/ip.api.json", outDir)
256         Expect(err).ShouldNot(HaveOccurred())
257         Expect(result).ToNot(BeNil())
258         Expect(result.outputFile)
259         Expect(result.outputFile).To(BeEquivalentTo(outDir + "/ip/ip.ba.go"))
260 }*/
261
262 /*func TestGeneratePackage(t *testing.T) {
263         RegisterTestingT(t)
264         // prepare context
265         testCtx := new(GenFile)
266         testCtx.packageName = "test-package-name"
267
268         // prepare input/output output files
269         inputData, err := ioutil.ReadFile("testdata/ip.api.json")
270         Expect(err).ShouldNot(HaveOccurred())
271         jsonRoot, err := parseInputJSON(inputData)
272         Expect(err).ShouldNot(HaveOccurred())
273         testCtx.file, err = parseModule(testCtx, jsonRoot)
274         Expect(err).ShouldNot(HaveOccurred())
275         outDir := "test_output_directory"
276         outFile, err := os.Create(outDir)
277         Expect(err).ShouldNot(HaveOccurred())
278         defer os.RemoveAll(outDir)
279
280         // prepare writer
281         writer := bufio.NewWriter(outFile)
282         Expect(writer.Buffered()).To(BeZero())
283         err = generateFileBinapi(testCtx, writer)
284         Expect(err).ShouldNot(HaveOccurred())
285 }
286
287 func TestGenerateMessageType(t *testing.T) {
288         RegisterTestingT(t)
289         // prepare context
290         testCtx := new(GenFile)
291         testCtx.packageName = "test-package-name"
292
293         // prepare input/output output files
294         inputData, err := ioutil.ReadFile("testdata/ip.api.json")
295         Expect(err).ShouldNot(HaveOccurred())
296         jsonRoot, err := parseInputJSON(inputData)
297         Expect(err).ShouldNot(HaveOccurred())
298         outDir := "test_output_directory"
299         outFile, err := os.Create(outDir)
300         Expect(err).ShouldNot(HaveOccurred())
301         testCtx.file, err = parseModule(testCtx, jsonRoot)
302         Expect(err).ShouldNot(HaveOccurred())
303         defer os.RemoveAll(outDir)
304
305         // prepare writer
306         writer := bufio.NewWriter(outFile)
307
308         for _, msg := range testCtx.file.Messages {
309                 generateMessage(testCtx, writer, &msg)
310                 Expect(writer.Buffered()).ToNot(BeZero())
311         }
312 }*/
313
314 /*func TestGenerateMessageName(t *testing.T) {
315         RegisterTestingT(t)
316         // prepare context
317         testCtx := new(context)
318         testCtx.packageName = "test-package-name"
319
320         // prepare input/output output files
321         inputData, err := readFile("testdata/ip.api.json")
322         Expect(err).ShouldNot(HaveOccurred())
323         testCtx.inputBuff = bytes.NewBuffer(inputData)
324         inFile, _ := parseJSON(inputData)
325         outDir := "test_output_directory"
326         outFile, err := os.Create(outDir)
327         Expect(err).ShouldNot(HaveOccurred())
328         defer os.RemoveAll(outDir)
329
330         // prepare writer
331         writer := bufio.NewWriter(outFile)
332
333         types := inFile.Map("types")
334         Expect(types.Len()).To(BeEquivalentTo(1))
335         for i := 0; i < types.Len(); i++ {
336                 typ := types.At(i)
337                 Expect(writer.Buffered()).To(BeZero())
338                 err := generateMessage(testCtx, writer, typ, false)
339                 Expect(err).ShouldNot(HaveOccurred())
340                 Expect(writer.Buffered()).ToNot(BeZero())
341
342         }
343 }
344
345 func TestGenerateMessageFieldTypes(t *testing.T) {
346         // expected results according to acl.api.json in testdata
347         expectedTypes := []string{
348                 "\tIsPermit uint8",
349                 "\tIsIpv6 uint8",
350                 "\tSrcIPAddr []byte     `struc:\"[16]byte\"`",
351                 "\tSrcIPPrefixLen uint8",
352                 "\tDstIPAddr []byte     `struc:\"[16]byte\"`",
353                 "\tDstIPPrefixLen uint8",
354                 "\tProto uint8",
355                 "\tSrcportOrIcmptypeFirst uint16",
356                 "\tSrcportOrIcmptypeLast uint16",
357                 "\tDstportOrIcmpcodeFirst uint16",
358                 "\tDstportOrIcmpcodeLast uint16",
359                 "\tTCPFlagsMask uint8",
360                 "\tTCPFlagsValue uint8"}
361         RegisterTestingT(t)
362         // prepare context
363         testCtx := new(context)
364         testCtx.packageName = "test-package-name"
365
366         // prepare input/output output files
367         inputData, err := readFile("testdata/acl.api.json")
368         Expect(err).ShouldNot(HaveOccurred())
369         inFile, err := parseJSON(inputData)
370         Expect(err).ShouldNot(HaveOccurred())
371         Expect(inFile).ToNot(BeNil())
372
373         // test types
374         types := inFile.Map("types")
375         fields := make([]string, 0)
376         for i := 0; i < types.Len(); i++ {
377                 for j := 0; j < types.At(i).Len(); j++ {
378                         field := types.At(i).At(j)
379                         if field.GetType() == jsongo.TypeArray {
380                                 err := processMessageField(testCtx, &fields, field, false)
381                                 Expect(err).ShouldNot(HaveOccurred())
382                                 Expect(fields[j-1]).To(BeEquivalentTo(expectedTypes[j-1]))
383                         }
384                 }
385         }
386 }
387
388 func TestGenerateMessageFieldMessages(t *testing.T) {
389         // expected results according to acl.api.json in testdata
390         expectedFields := []string{"\tMajor uint32", "\tMinor uint32", "\tRetval int32",
391                 "\tVpePid uint32", "\tACLIndex uint32", "\tTag []byte   `struc:\"[64]byte\"`",
392                 "\tCount uint32"}
393         RegisterTestingT(t)
394         // prepare context
395         testCtx := new(context)
396         testCtx.packageName = "test-package-name"
397
398         // prepare input/output output files
399         inputData, err := readFile("testdata/acl.api.json")
400         Expect(err).ShouldNot(HaveOccurred())
401         inFile, err := parseJSON(inputData)
402         Expect(err).ShouldNot(HaveOccurred())
403         Expect(inFile).ToNot(BeNil())
404
405         // test message fields
406         messages := inFile.Map("messages")
407         customIndex := 0
408         fields := make([]string, 0)
409         for i := 0; i < messages.Len(); i++ {
410                 for j := 0; j < messages.At(i).Len(); j++ {
411                         field := messages.At(i).At(j)
412                         if field.GetType() == jsongo.TypeArray {
413                                 specificFieldName := field.At(1).Get().(string)
414                                 if specificFieldName == "crc" || specificFieldName == "_vl_msg_id" ||
415                                         specificFieldName == "client_index" || specificFieldName == "context" {
416                                         continue
417                                 }
418                                 err := processMessageField(testCtx, &fields, field, false)
419                                 Expect(err).ShouldNot(HaveOccurred())
420                                 Expect(fields[customIndex]).To(BeEquivalentTo(expectedFields[customIndex]))
421                                 customIndex++
422                                 if customIndex >= len(expectedFields) {
423                                         // there is too much fields now for one UT...
424                                         return
425                                 }
426                         }
427                 }
428         }
429 }
430
431 func TestGeneratePackageHeader(t *testing.T) {
432         RegisterTestingT(t)
433         // prepare context
434         testCtx := new(context)
435         testCtx.packageName = "test-package-name"
436
437         // prepare input/output output files
438         inputData, err := readFile("testdata/acl.api.json")
439         Expect(err).ShouldNot(HaveOccurred())
440         inFile, err := parseJSON(inputData)
441         Expect(err).ShouldNot(HaveOccurred())
442         outDir := "test_output_directory"
443         outFile, err := os.Create(outDir)
444         Expect(err).ShouldNot(HaveOccurred())
445         defer os.RemoveAll(outDir)
446         // prepare writer
447         writer := bufio.NewWriter(outFile)
448         Expect(writer.Buffered()).To(BeZero())
449         generatePackageHeader(testCtx, writer, inFile)
450         Expect(writer.Buffered()).ToNot(BeZero())
451 }
452
453 func TestGenerateMessageCommentType(t *testing.T) {
454         RegisterTestingT(t)
455         // prepare context
456         testCtx := new(context)
457         testCtx.packageName = "test-package-name"
458         testCtx.inputBuff = bytes.NewBuffer([]byte("test content"))
459
460         outDir := "test_output_directory"
461         outFile, err := os.Create(outDir)
462         Expect(err).ShouldNot(HaveOccurred())
463         writer := bufio.NewWriter(outFile)
464         defer os.RemoveAll(outDir)
465         Expect(writer.Buffered()).To(BeZero())
466         generateMessageComment(testCtx, writer, "test-struct", "msg-name", true)
467         Expect(writer.Buffered()).ToNot(BeZero())
468 }
469
470 func TestGenerateMessageCommentMessage(t *testing.T) {
471         RegisterTestingT(t)
472         // prepare context
473         testCtx := new(context)
474         testCtx.packageName = "test-package-name"
475         testCtx.inputBuff = bytes.NewBuffer([]byte("test content"))
476
477         outDir := "test_output_directory"
478         outFile, err := os.Create(outDir)
479         Expect(err).ShouldNot(HaveOccurred())
480         writer := bufio.NewWriter(outFile)
481         defer os.RemoveAll(outDir)
482         Expect(writer.Buffered()).To(BeZero())
483         generateMessageComment(testCtx, writer, "test-struct", "msg-name", false)
484         Expect(writer.Buffered()).ToNot(BeZero())
485 }
486
487 func TestGenerateMessageNameGetter(t *testing.T) {
488         RegisterTestingT(t)
489         outDir := "test_output_directory"
490         outFile, err := os.Create(outDir)
491         Expect(err).ShouldNot(HaveOccurred())
492         writer := bufio.NewWriter(outFile)
493         defer os.RemoveAll(outDir)
494         Expect(writer.Buffered()).To(BeZero())
495         generateMessageNameGetter(writer, "test-struct", "msg-name")
496         Expect(writer.Buffered()).ToNot(BeZero())
497 }
498
499 func TestGenerateTypeNameGetter(t *testing.T) {
500         RegisterTestingT(t)
501         outDir := "test_output_directory"
502         outFile, err := os.Create(outDir)
503         Expect(err).ShouldNot(HaveOccurred())
504         writer := bufio.NewWriter(outFile)
505         defer os.RemoveAll(outDir)
506         Expect(writer.Buffered()).To(BeZero())
507         generateTypeNameGetter(writer, "test-struct", "msg-name")
508         Expect(writer.Buffered()).ToNot(BeZero())
509 }
510
511 func TestGenerateCrcGetter(t *testing.T) {
512         RegisterTestingT(t)
513         outDir := "test_output_directory"
514         outFile, err := os.Create(outDir)
515         Expect(err).ShouldNot(HaveOccurred())
516         writer := bufio.NewWriter(outFile)
517         defer os.RemoveAll(outDir)
518         Expect(writer.Buffered()).To(BeZero())
519         generateCrcGetter(writer, "test-struct", "msg-name")
520         Expect(writer.Buffered()).ToNot(BeZero())
521 }
522
523 func TestTranslateVppType(t *testing.T) {
524         RegisterTestingT(t)
525         context := new(context)
526         typesToTranslate := []string{"u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "f64"}
527         expected := []string{"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64", "float64"}
528         var translated []string
529         for _, value := range typesToTranslate {
530                 translated = append(translated, convertToGoType(context, value, false))
531         }
532         for index, value := range expected {
533                 Expect(value).To(BeEquivalentTo(translated[index]))
534         }
535
536 }
537
538 func TestTranslateVppTypeArray(t *testing.T) {
539         RegisterTestingT(t)
540         context := new(context)
541         translated := convertToGoType(context, "u8", true)
542         Expect(translated).To(BeEquivalentTo("byte"))
543 }
544
545 func TestTranslateVppUnknownType(t *testing.T) {
546         defer func() {
547                 if recovery := recover(); recovery != nil {
548                         t.Logf("Recovered from panic: %v", recovery)
549                 }
550         }()
551         context := new(context)
552         convertToGoType(context, "?", false)
553 }
554
555 func TestCamelCase(t *testing.T) {
556         RegisterTestingT(t)
557         // test camel case functionality
558         expected := "allYourBaseAreBelongToUs"
559         result := camelCaseName("all_your_base_are_belong_to_us")
560         Expect(expected).To(BeEquivalentTo(result))
561         // test underscore
562         expected = "_"
563         result = camelCaseName(expected)
564         Expect(expected).To(BeEquivalentTo(result))
565         // test all lower
566         expected = "lower"
567         result = camelCaseName(expected)
568         Expect(expected).To(BeEquivalentTo(result))
569 }
570
571 func TestCommonInitialisms(t *testing.T) {
572         RegisterTestingT(t)
573
574         for key, value := range commonInitialisms {
575                 Expect(value).ShouldNot(BeFalse())
576                 Expect(key).ShouldNot(BeEmpty())
577         }
578 }
579 */