|
|
package main
import ( "fmt" "go/ast" "go/parser" "go/token" "io/ioutil" "os" "path/filepath" "reflect" "sort" "testing" "text/template"
"github.com/tinylib/msgp/gen" )
// When stuff's going wrong, you'll be glad this is here!
const debugTemp = false
// Ensure that consistent identifiers are generated on a per-method basis by msgp.
//
// Also ensure that no duplicate identifiers appear in a method.
//
// structs are currently processed alphabetically by msgp. this test relies on
// that property.
//
func TestIssue185Idents(t *testing.T) { var identCases = []struct { tpl *template.Template expectedChanged []string }{ {tpl: issue185IdentsTpl, expectedChanged: []string{"Test1"}}, {tpl: issue185ComplexIdentsTpl, expectedChanged: []string{"Test2"}}, }
methods := []string{"DecodeMsg", "EncodeMsg", "Msgsize", "MarshalMsg", "UnmarshalMsg"}
for idx, identCase := range identCases { // generate the code, extract the generated variable names, mapped to function name
var tplData issue185TplData varsBefore, err := loadVars(identCase.tpl, tplData) if err != nil { t.Fatalf("%d: could not extract before vars: %v", idx, err) }
// regenerate the code with extra field(s), extract the generated variable
// names, mapped to function name
tplData.Extra = true varsAfter, err := loadVars(identCase.tpl, tplData) if err != nil { t.Fatalf("%d: could not extract after vars: %v", idx, err) }
// ensure that all declared variable names inside each of the methods we
// expect to change have actually changed
for _, stct := range identCase.expectedChanged { for _, method := range methods { fn := fmt.Sprintf("%s.%s", stct, method)
bv, av := varsBefore.Value(fn), varsAfter.Value(fn) if len(bv) > 0 && len(av) > 0 && reflect.DeepEqual(bv, av) { t.Fatalf("%d vars identical! expected vars to change for %s", idx, fn) } delete(varsBefore, fn) delete(varsAfter, fn) } }
// all of the remaining keys should not have changed
for bmethod, bvars := range varsBefore { avars := varsAfter.Value(bmethod)
if !reflect.DeepEqual(bvars, avars) { t.Fatalf("%d: vars changed! expected vars identical for %s", idx, bmethod) } delete(varsBefore, bmethod) delete(varsAfter, bmethod) }
if len(varsBefore) > 0 || len(varsAfter) > 0 { t.Fatalf("%d: unexpected methods remaining", idx) } } }
type issue185TplData struct { Extra bool }
func TestIssue185Overlap(t *testing.T) { var overlapCases = []struct { tpl *template.Template data issue185TplData }{ {tpl: issue185IdentsTpl, data: issue185TplData{Extra: false}}, {tpl: issue185IdentsTpl, data: issue185TplData{Extra: true}}, {tpl: issue185ComplexIdentsTpl, data: issue185TplData{Extra: false}}, {tpl: issue185ComplexIdentsTpl, data: issue185TplData{Extra: true}}, }
for idx, o := range overlapCases { // regenerate the code with extra field(s), extract the generated variable
// names, mapped to function name
mvars, err := loadVars(o.tpl, o.data) if err != nil { t.Fatalf("%d: could not extract after vars: %v", idx, err) }
identCnt := 0 for fn, vars := range mvars { sort.Strings(vars)
// Loose sanity check to make sure the tests expectations aren't broken.
// If the prefix ever changes, this needs to change.
for _, v := range vars { if v[0] == 'z' { identCnt++ } }
for i := 0; i < len(vars)-1; i++ { if vars[i] == vars[i+1] { t.Fatalf("%d: duplicate var %s in function %s", idx, vars[i], fn) } } }
// one last sanity check: if there aren't any vars that start with 'z',
// this test's expectations are unsatisfiable.
if identCnt == 0 { t.Fatalf("%d: no generated identifiers found", idx) } } }
func loadVars(tpl *template.Template, tplData interface{}) (vars extractedVars, err error) { tempDir, err := ioutil.TempDir("", "msgp-") if err != nil { err = fmt.Errorf("could not create temp dir: %v", err) return }
if !debugTemp { defer os.RemoveAll(tempDir) } else { fmt.Println(tempDir) } tfile := filepath.Join(tempDir, "msg.go") genFile := newFilename(tfile, "")
if err = goGenerateTpl(tempDir, tfile, tpl, tplData); err != nil { err = fmt.Errorf("could not generate code: %v", err) return }
vars, err = extractVars(genFile) if err != nil { err = fmt.Errorf("could not extract after vars: %v", err) return }
return }
type varVisitor struct { vars []string fset *token.FileSet }
func (v *varVisitor) Visit(node ast.Node) (w ast.Visitor) { gen, ok := node.(*ast.GenDecl) if !ok { return v } for _, spec := range gen.Specs { if vspec, ok := spec.(*ast.ValueSpec); ok { for _, n := range vspec.Names { v.vars = append(v.vars, n.Name) } } } return v }
type extractedVars map[string][]string
func (e extractedVars) Value(key string) []string { if v, ok := e[key]; ok { return v } panic(fmt.Errorf("unknown key %s", key)) }
func extractVars(file string) (extractedVars, error) { fset := token.NewFileSet()
f, err := parser.ParseFile(fset, file, nil, 0) if err != nil { return nil, err }
vars := make(map[string][]string) for _, d := range f.Decls { switch d := d.(type) { case *ast.FuncDecl: sn := "" switch rt := d.Recv.List[0].Type.(type) { case *ast.Ident: sn = rt.Name case *ast.StarExpr: sn = rt.X.(*ast.Ident).Name default: panic("unknown receiver type") }
key := fmt.Sprintf("%s.%s", sn, d.Name.Name) vis := &varVisitor{fset: fset} ast.Walk(vis, d.Body) vars[key] = vis.vars } } return vars, nil }
func goGenerateTpl(cwd, tfile string, tpl *template.Template, tplData interface{}) error { outf, err := os.OpenFile(tfile, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0600) if err != nil { return err } defer outf.Close()
if err := tpl.Execute(outf, tplData); err != nil { return err }
mode := gen.Encode | gen.Decode | gen.Size | gen.Marshal | gen.Unmarshal
return Run(tfile, mode, false) }
var issue185IdentsTpl = template.Must(template.New("").Parse(` package issue185
//go:generate msgp
type Test1 struct { Foo string Bar string {{ if .Extra }}Baz []string{{ end }} Qux string }
type Test2 struct { Foo string Bar string Baz string } `))
var issue185ComplexIdentsTpl = template.Must(template.New("").Parse(` package issue185
//go:generate msgp
type Test1 struct { Foo string Bar string Baz string }
type Test2 struct { Foo string Bar string Baz []string Qux map[string]string Yep map[string]map[string]string Quack struct { Quack struct { Quack struct { {{ if .Extra }}Extra []string{{ end }} Quack string } } } Nup struct { Foo string Bar string Baz []string Qux map[string]string Yep map[string]map[string]string } Ding struct { Dong struct { Dung struct { Thing string } } } }
type Test3 struct { Foo string Bar string Baz string } `))
|