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