blob: 3525a004b6c8d636aa84423900c0f886c28dedfb [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// +build ignore
2
3package main
4
5import (
6 "bytes"
7 "fmt"
8 "go/format"
9 "html/template"
10 "io/ioutil"
11 "log"
12 "path/filepath"
13 "strings"
14
15 "github.com/globalsign/mgo/internal/json"
16)
17
18func main() {
19 log.SetFlags(0)
20 log.SetPrefix(name + ": ")
21
22 var g Generator
23
24 fmt.Fprintf(&g, "// Code generated by \"%s.go\"; DO NOT EDIT\n\n", name)
25
26 src := g.generate()
27
28 err := ioutil.WriteFile(fmt.Sprintf("%s.go", strings.TrimSuffix(name, "_generator")), src, 0644)
29 if err != nil {
30 log.Fatalf("writing output: %s", err)
31 }
32}
33
34// Generator holds the state of the analysis. Primarily used to buffer
35// the output for format.Source.
36type Generator struct {
37 bytes.Buffer // Accumulated output.
38}
39
40// format returns the gofmt-ed contents of the Generator's buffer.
41func (g *Generator) format() []byte {
42 src, err := format.Source(g.Bytes())
43 if err != nil {
44 // Should never happen, but can arise when developing this code.
45 // The user can compile the output to see the error.
46 log.Printf("warning: internal error: invalid Go generated: %s", err)
47 log.Printf("warning: compile the package to analyze the error")
48 return g.Bytes()
49 }
50 return src
51}
52
53// EVERYTHING ABOVE IS CONSTANT BETWEEN THE GENERATORS
54
55const name = "bson_corpus_spec_test_generator"
56
57func (g *Generator) generate() []byte {
58
59 testFiles, err := filepath.Glob("./specdata/specifications/source/bson-corpus/tests/*.json")
60 if err != nil {
61 log.Fatalf("error reading bson-corpus files: %s", err)
62 }
63
64 tests, err := g.loadTests(testFiles)
65 if err != nil {
66 log.Fatalf("error loading tests: %s", err)
67 }
68
69 tmpl, err := g.getTemplate()
70 if err != nil {
71 log.Fatalf("error loading template: %s", err)
72 }
73
74 tmpl.Execute(&g.Buffer, tests)
75
76 return g.format()
77}
78
79func (g *Generator) loadTests(filenames []string) ([]*testDef, error) {
80 var tests []*testDef
81 for _, filename := range filenames {
82 test, err := g.loadTest(filename)
83 if err != nil {
84 return nil, err
85 }
86
87 tests = append(tests, test)
88 }
89
90 return tests, nil
91}
92
93func (g *Generator) loadTest(filename string) (*testDef, error) {
94 content, err := ioutil.ReadFile(filename)
95 if err != nil {
96 return nil, err
97 }
98
99 var testDef testDef
100 err = json.Unmarshal(content, &testDef)
101 if err != nil {
102 return nil, err
103 }
104
105 names := make(map[string]struct{})
106
107 for i := len(testDef.Valid) - 1; i >= 0; i-- {
108 if testDef.BsonType == "0x05" && testDef.Valid[i].Description == "subtype 0x02" {
109 testDef.Valid = append(testDef.Valid[:i], testDef.Valid[i+1:]...)
110 continue
111 }
112
113 name := cleanupFuncName(testDef.Description + "_" + testDef.Valid[i].Description)
114 nameIdx := name
115 j := 1
116 for {
117 if _, ok := names[nameIdx]; !ok {
118 break
119 }
120
121 nameIdx = fmt.Sprintf("%s_%d", name, j)
122 }
123
124 names[nameIdx] = struct{}{}
125
126 testDef.Valid[i].TestDef = &testDef
127 testDef.Valid[i].Name = nameIdx
128 testDef.Valid[i].StructTest = testDef.TestKey != "" &&
129 (testDef.BsonType != "0x05" || strings.Contains(testDef.Valid[i].Description, "0x00")) &&
130 !testDef.Deprecated
131 }
132
133 for i := len(testDef.DecodeErrors) - 1; i >= 0; i-- {
134 if strings.Contains(testDef.DecodeErrors[i].Description, "UTF-8") {
135 testDef.DecodeErrors = append(testDef.DecodeErrors[:i], testDef.DecodeErrors[i+1:]...)
136 continue
137 }
138
139 name := cleanupFuncName(testDef.Description + "_" + testDef.DecodeErrors[i].Description)
140 nameIdx := name
141 j := 1
142 for {
143 if _, ok := names[nameIdx]; !ok {
144 break
145 }
146
147 nameIdx = fmt.Sprintf("%s_%d", name, j)
148 }
149 names[nameIdx] = struct{}{}
150
151 testDef.DecodeErrors[i].Name = nameIdx
152 }
153
154 return &testDef, nil
155}
156
157func (g *Generator) getTemplate() (*template.Template, error) {
158 content := `package bson_test
159
160import (
161 "encoding/hex"
162 "time"
163
164 . "gopkg.in/check.v1"
165 "github.com/globalsign/mgo/bson"
166)
167
168func testValid(c *C, in []byte, expected []byte, result interface{}) {
169 err := bson.Unmarshal(in, result)
170 c.Assert(err, IsNil)
171
172 out, err := bson.Marshal(result)
173 c.Assert(err, IsNil)
174
175 c.Assert(string(expected), Equals, string(out), Commentf("roundtrip failed for %T, expected '%x' but got '%x'", result, expected, out))
176}
177
178func testDecodeSkip(c *C, in []byte) {
179 err := bson.Unmarshal(in, &struct{}{})
180 c.Assert(err, IsNil)
181}
182
183func testDecodeError(c *C, in []byte, result interface{}) {
184 err := bson.Unmarshal(in, result)
185 c.Assert(err, Not(IsNil))
186}
187
188{{range .}}
189{{range .Valid}}
190func (s *S) Test{{.Name}}(c *C) {
191 b, err := hex.DecodeString("{{.Bson}}")
192 c.Assert(err, IsNil)
193
194 {{if .CanonicalBson}}
195 cb, err := hex.DecodeString("{{.CanonicalBson}}")
196 c.Assert(err, IsNil)
197 {{else}}
198 cb := b
199 {{end}}
200
201 var resultD bson.D
202 testValid(c, b, cb, &resultD)
203 {{if .StructTest}}var resultS struct {
204 Element {{.TestDef.GoType}} ` + "`bson:\"{{.TestDef.TestKey}}\"`" + `
205 }
206 testValid(c, b, cb, &resultS){{end}}
207
208 testDecodeSkip(c, b)
209}
210{{end}}
211
212{{range .DecodeErrors}}
213func (s *S) Test{{.Name}}(c *C) {
214 b, err := hex.DecodeString("{{.Bson}}")
215 c.Assert(err, IsNil)
216
217 var resultD bson.D
218 testDecodeError(c, b, &resultD)
219}
220{{end}}
221{{end}}
222`
223 tmpl, err := template.New("").Parse(content)
224 if err != nil {
225 return nil, err
226 }
227 return tmpl, nil
228}
229
230func cleanupFuncName(name string) string {
231 return strings.Map(func(r rune) rune {
232 if (r >= 48 && r <= 57) || (r >= 65 && r <= 90) || (r >= 97 && r <= 122) {
233 return r
234 }
235 return '_'
236 }, name)
237}
238
239type testDef struct {
240 Description string `json:"description"`
241 BsonType string `json:"bson_type"`
242 TestKey string `json:"test_key"`
243 Valid []*valid `json:"valid"`
244 DecodeErrors []*decodeError `json:"decodeErrors"`
245 Deprecated bool `json:"deprecated"`
246}
247
248func (t *testDef) GoType() string {
249 switch t.BsonType {
250 case "0x01":
251 return "float64"
252 case "0x02":
253 return "string"
254 case "0x03":
255 return "bson.D"
256 case "0x04":
257 return "[]interface{}"
258 case "0x05":
259 return "[]byte"
260 case "0x07":
261 return "bson.ObjectId"
262 case "0x08":
263 return "bool"
264 case "0x09":
265 return "time.Time"
266 case "0x0E":
267 return "string"
268 case "0x10":
269 return "int32"
270 case "0x12":
271 return "int64"
272 case "0x13":
273 return "bson.Decimal"
274 default:
275 return "interface{}"
276 }
277}
278
279type valid struct {
280 Description string `json:"description"`
281 Bson string `json:"bson"`
282 CanonicalBson string `json:"canonical_bson"`
283
284 Name string
285 StructTest bool
286 TestDef *testDef
287}
288
289type decodeError struct {
290 Description string `json:"description"`
291 Bson string `json:"bson"`
292
293 Name string
294}