// +build ignore package main import ( "bytes" "fmt" "go/format" "html/template" "io/ioutil" "log" "path/filepath" "strings" "github.com/globalsign/mgo/internal/json" ) func main() { log.SetFlags(0) log.SetPrefix(name + ": ") var g Generator fmt.Fprintf(&g, "// Code generated by \"%s.go\"; DO NOT EDIT\n\n", name) src := g.generate() err := ioutil.WriteFile(fmt.Sprintf("%s.go", strings.TrimSuffix(name, "_generator")), src, 0644) if err != nil { log.Fatalf("writing output: %s", err) } } // Generator holds the state of the analysis. Primarily used to buffer // the output for format.Source. type Generator struct { bytes.Buffer // Accumulated output. } // format returns the gofmt-ed contents of the Generator's buffer. func (g *Generator) format() []byte { src, err := format.Source(g.Bytes()) if err != nil { // Should never happen, but can arise when developing this code. // The user can compile the output to see the error. log.Printf("warning: internal error: invalid Go generated: %s", err) log.Printf("warning: compile the package to analyze the error") return g.Bytes() } return src } // EVERYTHING ABOVE IS CONSTANT BETWEEN THE GENERATORS const name = "bson_corpus_spec_test_generator" func (g *Generator) generate() []byte { testFiles, err := filepath.Glob("./specdata/specifications/source/bson-corpus/tests/*.json") if err != nil { log.Fatalf("error reading bson-corpus files: %s", err) } tests, err := g.loadTests(testFiles) if err != nil { log.Fatalf("error loading tests: %s", err) } tmpl, err := g.getTemplate() if err != nil { log.Fatalf("error loading template: %s", err) } tmpl.Execute(&g.Buffer, tests) return g.format() } func (g *Generator) loadTests(filenames []string) ([]*testDef, error) { var tests []*testDef for _, filename := range filenames { test, err := g.loadTest(filename) if err != nil { return nil, err } tests = append(tests, test) } return tests, nil } func (g *Generator) loadTest(filename string) (*testDef, error) { content, err := ioutil.ReadFile(filename) if err != nil { return nil, err } var testDef testDef err = json.Unmarshal(content, &testDef) if err != nil { return nil, err } names := make(map[string]struct{}) for i := len(testDef.Valid) - 1; i >= 0; i-- { if testDef.BsonType == "0x05" && testDef.Valid[i].Description == "subtype 0x02" { testDef.Valid = append(testDef.Valid[:i], testDef.Valid[i+1:]...) continue } name := cleanupFuncName(testDef.Description + "_" + testDef.Valid[i].Description) nameIdx := name j := 1 for { if _, ok := names[nameIdx]; !ok { break } nameIdx = fmt.Sprintf("%s_%d", name, j) } names[nameIdx] = struct{}{} testDef.Valid[i].TestDef = &testDef testDef.Valid[i].Name = nameIdx testDef.Valid[i].StructTest = testDef.TestKey != "" && (testDef.BsonType != "0x05" || strings.Contains(testDef.Valid[i].Description, "0x00")) && !testDef.Deprecated } for i := len(testDef.DecodeErrors) - 1; i >= 0; i-- { if strings.Contains(testDef.DecodeErrors[i].Description, "UTF-8") { testDef.DecodeErrors = append(testDef.DecodeErrors[:i], testDef.DecodeErrors[i+1:]...) continue } name := cleanupFuncName(testDef.Description + "_" + testDef.DecodeErrors[i].Description) nameIdx := name j := 1 for { if _, ok := names[nameIdx]; !ok { break } nameIdx = fmt.Sprintf("%s_%d", name, j) } names[nameIdx] = struct{}{} testDef.DecodeErrors[i].Name = nameIdx } return &testDef, nil } func (g *Generator) getTemplate() (*template.Template, error) { content := `package bson_test import ( "encoding/hex" "time" . "gopkg.in/check.v1" "github.com/globalsign/mgo/bson" ) func testValid(c *C, in []byte, expected []byte, result interface{}) { err := bson.Unmarshal(in, result) c.Assert(err, IsNil) out, err := bson.Marshal(result) c.Assert(err, IsNil) c.Assert(string(expected), Equals, string(out), Commentf("roundtrip failed for %T, expected '%x' but got '%x'", result, expected, out)) } func testDecodeSkip(c *C, in []byte) { err := bson.Unmarshal(in, &struct{}{}) c.Assert(err, IsNil) } func testDecodeError(c *C, in []byte, result interface{}) { err := bson.Unmarshal(in, result) c.Assert(err, Not(IsNil)) } {{range .}} {{range .Valid}} func (s *S) Test{{.Name}}(c *C) { b, err := hex.DecodeString("{{.Bson}}") c.Assert(err, IsNil) {{if .CanonicalBson}} cb, err := hex.DecodeString("{{.CanonicalBson}}") c.Assert(err, IsNil) {{else}} cb := b {{end}} var resultD bson.D testValid(c, b, cb, &resultD) {{if .StructTest}}var resultS struct { Element {{.TestDef.GoType}} ` + "`bson:\"{{.TestDef.TestKey}}\"`" + ` } testValid(c, b, cb, &resultS){{end}} testDecodeSkip(c, b) } {{end}} {{range .DecodeErrors}} func (s *S) Test{{.Name}}(c *C) { b, err := hex.DecodeString("{{.Bson}}") c.Assert(err, IsNil) var resultD bson.D testDecodeError(c, b, &resultD) } {{end}} {{end}} ` tmpl, err := template.New("").Parse(content) if err != nil { return nil, err } return tmpl, nil } func cleanupFuncName(name string) string { return strings.Map(func(r rune) rune { if (r >= 48 && r <= 57) || (r >= 65 && r <= 90) || (r >= 97 && r <= 122) { return r } return '_' }, name) } type testDef struct { Description string `json:"description"` BsonType string `json:"bson_type"` TestKey string `json:"test_key"` Valid []*valid `json:"valid"` DecodeErrors []*decodeError `json:"decodeErrors"` Deprecated bool `json:"deprecated"` } func (t *testDef) GoType() string { switch t.BsonType { case "0x01": return "float64" case "0x02": return "string" case "0x03": return "bson.D" case "0x04": return "[]interface{}" case "0x05": return "[]byte" case "0x07": return "bson.ObjectId" case "0x08": return "bool" case "0x09": return "time.Time" case "0x0E": return "string" case "0x10": return "int32" case "0x12": return "int64" case "0x13": return "bson.Decimal" default: return "interface{}" } } type valid struct { Description string `json:"description"` Bson string `json:"bson"` CanonicalBson string `json:"canonical_bson"` Name string StructTest bool TestDef *testDef } type decodeError struct { Description string `json:"description"` Bson string `json:"bson"` Name string }