1// +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}
295