Refactor names and tests in preperation for middleware addition

This commit is contained in:
Adam Ryman 2016-11-28 03:39:34 -08:00
Родитель 5924b77e11
Коммит 926895c623
6 изменённых файлов: 187 добавлений и 209 удалений

Просмотреть файл

@ -2,13 +2,10 @@
package generator
import (
"bytes"
"go/format"
"io"
"io/ioutil"
"os"
"strings"
"text/template"
log "github.com/Sirupsen/logrus"
"github.com/pkg/errors"
@ -21,32 +18,26 @@ import (
"github.com/TuneLab/go-truss/truss"
)
func init() {
log.SetLevel(log.InfoLevel)
log.SetOutput(os.Stderr)
log.SetFormatter(&log.TextFormatter{
ForceColors: true,
})
}
// GenerateGokit returns a gokit service generated from a service definition (svcdef),
// the package to the root of the generated service goPackage, the package
// to the .pb.go service struct files (goPBPackage) and any prevously generated files.
func GenerateGokit(sd *svcdef.Svcdef, conf gengokit.Config) ([]truss.NamedReadWriter, error) {
te, err := gengokit.NewTemplateExecutor(sd, conf)
executor, err := gengokit.NewExecutor(sd, conf)
if err != nil {
return nil, errors.Wrap(err, "cannot create template executor")
}
fpm := make(map[string]io.Reader, len(conf.PreviousFiles))
prevFiles := make(map[string]io.Reader, len(conf.PreviousFiles))
for _, f := range conf.PreviousFiles {
fpm[f.Name()] = f
prevFiles[f.Name()] = f
}
var codeGenFiles []truss.NamedReadWriter
for _, templFP := range templFiles.AssetNames() {
file, err := generateResponseFile(templFP, te, fpm)
actualFP := templatePathToActual(templFP, sd.PkgName)
prev := prevFiles[actualFP]
file, err := generateResponseFile(executor, prev, templFP)
if err != nil {
return nil, errors.Wrap(err, "cannot render template")
}
@ -62,32 +53,28 @@ func GenerateGokit(sd *svcdef.Svcdef, conf gengokit.Config) ([]truss.NamedReadWr
// generateResponseFile contains logic to choose how to render a template file
// based on path and if that file was generated previously. It accepts a
// template path to render, a templateExecutor to apply to the template, and a
// map of paths to files for the previous generation. It returns a
// truss.NamedReadWriter representing the generated file
func generateResponseFile(templFP string, te *gengokit.TemplateExecutor, prevGenMap map[string]io.Reader) (truss.NamedReadWriter, error) {
// template path to render, a executor to apply to the template,
// and . It returns a truss.NamedReadWriter representing the generated file.
func generateResponseFile(executor *gengokit.Executor, file io.Reader, templFP string) (truss.NamedReadWriter, error) {
var genCode io.Reader
var err error
// Get the actual path to the file rather than the template file path
actualFP := templatePathToActual(templFP, te.PackageName)
// If we are rendering the server and or the client
if templFP == "NAME-service/handlers/server/server_handler.gotemplate" {
file := prevGenMap[actualFP]
h, err := handler.New(te.Service, file, te.PackageName)
actualFP := templatePathToActual(templFP, executor.PackageName)
switch templFP {
case handler.ServerFile:
h, err := handler.New(executor.Service, file, executor.PackageName)
if err != nil {
return nil, errors.Wrapf(err, "cannot parse previous handler: %q", actualFP)
}
if genCode, err = h.Render(templFP, te); err != nil {
return nil, errors.Wrap(err, "cannot render template")
if genCode, err = h.Render(templFP, executor); err != nil {
return nil, errors.Wrapf(err, "cannot render template: %s", templFP)
}
}
// if no code has been generated just apply the template
if genCode == nil {
if genCode, err = applyTemplateFromPath(templFP, te); err != nil {
return nil, errors.Wrap(err, "cannot render template")
default:
if genCode, err = applyTemplateFromPath(templFP, executor); err != nil {
return nil, errors.Wrapf(err, "cannot render template: %s", templFP)
}
}
@ -125,30 +112,13 @@ func templatePathToActual(templFilePath, packageName string) string {
}
// applyTemplateFromPath calls applyTemplate with the template at templFilePath
func applyTemplateFromPath(templFilePath string, executor *gengokit.TemplateExecutor) (io.Reader, error) {
templBytes, err := templFiles.Asset(templFilePath)
func applyTemplateFromPath(templFP string, executor *gengokit.Executor) (io.Reader, error) {
templBytes, err := templFiles.Asset(templFP)
if err != nil {
return nil, errors.Wrapf(err, "unable to find template file: %v", templFilePath)
return nil, errors.Wrapf(err, "unable to find template file: %v", templFP)
}
return applyTemplate(templBytes, templFilePath, executor)
}
func applyTemplate(templBytes []byte, templName string, executor *gengokit.TemplateExecutor) (io.Reader, error) {
templateString := string(templBytes)
codeTemplate, err := template.New(templName).Funcs(executor.FuncMap).Parse(templateString)
if err != nil {
return nil, errors.Wrap(err, "cannot create template")
}
outputBuffer := bytes.NewBuffer(nil)
err = codeTemplate.Execute(outputBuffer, executor)
if err != nil {
return nil, errors.Wrap(err, "template error")
}
return outputBuffer, nil
return executor.ApplyTemplate(string(templBytes), templFP)
}
// formatCode takes a string representing golang code and attempts to return a
@ -164,5 +134,4 @@ func formatCode(code []byte) []byte {
}
return formatted
}

Просмотреть файл

@ -1,7 +1,6 @@
package generator
import (
"bytes"
"go/format"
"io"
"io/ioutil"
@ -29,6 +28,7 @@ func init() {
func init() {
log.SetLevel(log.DebugLevel)
_ = errors.Wrap
}
func TestTemplatePathToActual(t *testing.T) {
@ -85,7 +85,7 @@ func TestApplyTemplateFromPath(t *testing.T) {
PBPackage: "github.com/TuneLab/go-truss/gengokit/general-service",
}
te, err := gengokit.NewTemplateExecutor(sd, conf)
te, err := gengokit.NewExecutor(sd, conf)
if err != nil {
t.Fatal(err)
}
@ -100,11 +100,10 @@ func TestApplyTemplateFromPath(t *testing.T) {
t.Fatal(err)
}
_, err = testFormat(endCode)
_, err = testFormat(string(endCode))
if err != nil {
t.Fatal(err)
}
}
func svcMethodsNames(methods []*svcdef.ServiceMethod) []string {
@ -116,7 +115,7 @@ func svcMethodsNames(methods []*svcdef.ServiceMethod) []string {
return mNames
}
func stringToTemplateExector(def, importPath string) (*gengokit.TemplateExecutor, error) {
func stringToTemplateExector(def, importPath string) (*gengokit.Executor, error) {
sd, err := svcdef.NewFromString(def, gopath)
if err != nil {
return nil, err
@ -127,13 +126,12 @@ func stringToTemplateExector(def, importPath string) (*gengokit.TemplateExecutor
PBPackage: importPath,
}
te, err := gengokit.NewTemplateExecutor(sd, conf)
te, err := gengokit.NewExecutor(sd, conf)
if err != nil {
return nil, err
}
return te, nil
}
func TestAllTemplates(t *testing.T) {
@ -207,17 +205,7 @@ func TestAllTemplates(t *testing.T) {
}
`
sd, err := svcdef.NewFromString(def, gopath)
if err != nil {
t.Fatal(err)
}
conf := gengokit.Config{
GoPackage: "github.com/TuneLab/go-truss/gengokit",
PBPackage: "github.com/TuneLab/go-truss/gengokit/general-service",
}
te, err := gengokit.NewTemplateExecutor(sd, conf)
sd1, err := svcdef.NewFromString(def, gopath)
if err != nil {
t.Fatal(err)
}
@ -227,54 +215,60 @@ func TestAllTemplates(t *testing.T) {
t.Fatal(err)
}
te2, err := gengokit.NewTemplateExecutor(sd2, conf)
conf := gengokit.Config{
GoPackage: "github.com/TuneLab/go-truss/gengokit",
PBPackage: "github.com/TuneLab/go-truss/gengokit/general-service",
}
executor1, err := gengokit.NewExecutor(sd1, conf)
if err != nil {
t.Fatal(err)
}
executor2, err := gengokit.NewExecutor(sd2, conf)
if err != nil {
t.Fatal(err)
}
for _, templFP := range templateFileAssets.AssetNames() {
// skip the partial templates
if filepath.Ext(templFP) != ".gotemplate" {
continue
}
prevGenMap := make(map[string]io.Reader)
var prev io.Reader
firstCode, err := testGenerateResponseFile(templFP, te, prevGenMap)
firstCode, err := testGenerateResponseFile(executor1, prev, templFP)
if err != nil {
t.Fatalf("%v failed to format on first generation\n\nERROR:\n\n%v\n\nCODE:\n\n%v", templFP, err.Error(), string(firstCode))
t.Fatalf("%s failed to format on first generation\n\nERROR:\n\n%s\n\nCODE:\n\n%s", templFP, err.Error(), firstCode)
}
// store the file to act to pass back to testGenerateResponseFile for second generation
prevGenMap[templatePathToActual(templFP, te.PackageName)] = bytes.NewReader(firstCode)
// store the file to pass back to testGenerateResponseFile for second generation
prev = strings.NewReader(firstCode)
secondCode, err := testGenerateResponseFile(templFP, te, prevGenMap)
secondCode, err := testGenerateResponseFile(executor1, prev, templFP)
if err != nil {
t.Fatalf("%v failed to format on second identical generation\n\nERROR: %v\nCODE:\n\n%v",
templFP, err.Error(), string(secondCode))
t.Fatalf("%s failed to format on second identical generation\n\nERROR: %s\nCODE:\n\n%s",
templFP, err.Error(), secondCode)
}
if bytes.Compare(firstCode, secondCode) != 0 {
t.Fatal("Generated code differs after regeneration with same definition\n" + gentesthelper.DiffStrings(string(firstCode), string(secondCode)))
if firstCode != secondCode {
t.Fatal("Generated code differs after regeneration with same definition\n" + diff(firstCode, secondCode))
}
// store the file to act to pass back to testGenerateResponseFile for third generation
prevGenMap[templatePathToActual(templFP, te.PackageName)] = bytes.NewReader(secondCode)
// store the file to pass back to testGenerateResponseFile for third generation
prev = strings.NewReader(secondCode)
// pass in templateExecutor created from def2
addRPCCode, err := testGenerateResponseFile(templFP, te2, prevGenMap)
// pass in Executor created from def2
addRPCCode, err := testGenerateResponseFile(executor2, prev, templFP)
if err != nil {
t.Fatalf("%v failed to format on third generation with 1 rpc added\n\nERROR: %v\nCODE:\n\n%v",
templFP, err.Error(), string(addRPCCode))
t.Fatalf("%s failed to format on third generation with 1 rpc added\n\nERROR: %s\nCODE:\n\n%s",
templFP, err.Error(), addRPCCode)
}
// store the file to act to pass back to testGenerateResponseFile for forth generation
prevGenMap[templatePathToActual(templFP, te.PackageName)] = bytes.NewReader(addRPCCode)
// store the file to pass back to testGenerateResponseFile for forth generation
prev = strings.NewReader(addRPCCode)
// pass in templateExecutor create from def1
_, err = testGenerateResponseFile(templFP, te, prevGenMap)
// pass in Executor create from def1
_, err = testGenerateResponseFile(executor1, prev, templFP)
if err != nil {
t.Fatalf("%v failed to format on forth generation with 1 rpc removed\n\nERROR: %v\nCODE:\n\n%v",
templFP, err.Error(), string(addRPCCode))
t.Fatalf("%s failed to format on forth generation with 1 rpc removed\n\nERROR: %s\nCODE:\n\n%s",
templFP, err.Error(), addRPCCode)
}
}
}
@ -346,82 +340,79 @@ func TestUpdateMethods(t *testing.T) {
PBPackage: "github.com/TuneLab/go-truss/gengokit/general-service",
}
te, err := gengokit.NewTemplateExecutor(sd, conf)
te, err := gengokit.NewExecutor(sd, conf)
if err != nil {
t.Fatal(err)
}
testHandlerGeneration := func(templPath string) {
svc.Methods = []*svcdef.ServiceMethod{allMethods[0]}
firstBytes, err := testGenerateResponseFile(templPath, te, nil)
if err != nil {
t.Fatal(err)
}
firstCode := strings.TrimSpace(string(firstBytes))
templPath := handler.ServerFile
secondCode, err := renderService(svc, firstCode, te, templPath)
if err != nil {
t.Fatal(err)
}
svc.Methods = []*svcdef.ServiceMethod{allMethods[0]}
if strings.Compare(firstCode, secondCode) != 0 {
t.Fatal("Generated code differs after regenerated with same definition\n" +
templPath + "\n" +
diff(firstCode, secondCode))
}
svc.Methods = append(svc.Methods, allMethods[1])
thirdCode, err := renderService(svc, secondCode, te, templPath)
if err != nil {
t.Fatal(err)
}
if strings.Compare(secondCode, thirdCode) != -1 {
t.Fatal("Generated code not longer after regenerated with additional service method\n" +
templPath + "\n" +
diff(secondCode, thirdCode))
}
// remove the first one rpc
svc.Methods = svc.Methods[1:]
forthCode, err := renderService(svc, thirdCode, te, templPath)
if err != nil {
t.Fatal(err)
}
if strings.Compare(thirdCode, forthCode) != 1 {
t.Fatal("Generated code not shorter after regenerated with fewer service method\n" +
templPath + "\n" +
diff(secondCode, thirdCode))
}
svc.Methods = allMethods
fifthCode, err := renderService(svc, forthCode, te, templPath)
if err != nil {
t.Fatal(err)
}
if strings.Compare(forthCode, fifthCode) != -1 {
t.Fatal("Generated code not longer after regenerated with additional service method\n" +
templPath + "\n" +
diff(secondCode, thirdCode))
}
firstCode, err := renderService(svc, "", te, templPath)
if err != nil {
t.Fatal(err)
}
secondCode, err := renderService(svc, firstCode, te, templPath)
if err != nil {
t.Fatal(err)
}
if strings.Compare(firstCode, secondCode) != 0 {
t.Fatal("Generated code differs after regenerated with same definition\n" +
templPath + "\n" +
diff(firstCode, secondCode))
}
svc.Methods = append(svc.Methods, allMethods[1])
thirdCode, err := renderService(svc, secondCode, te, templPath)
if err != nil {
t.Fatal(err)
}
if thirdCode <= secondCode {
t.Fatal("Generated code not longer after regenerated with additional service method\n" +
templPath + "\n" +
diff(secondCode, thirdCode))
}
// remove the first one rpc
svc.Methods = svc.Methods[1:]
forthCode, err := renderService(svc, thirdCode, te, templPath)
if err != nil {
t.Fatal(err)
}
if forthCode >= thirdCode {
t.Fatal("Generated code not shorter after regenerated with fewer service method\n" +
templPath + "\n" +
diff(thirdCode, forthCode))
}
svc.Methods = allMethods
fifthCode, err := renderService(svc, forthCode, te, templPath)
if err != nil {
t.Fatal(err)
}
if fifthCode <= forthCode {
t.Fatal("Generated code not longer after regenerated with additional service method\n" +
templPath + "\n" +
diff(forthCode, fifthCode))
}
testHandlerGeneration("NAME-service/handlers/server/server_handler.gotemplate")
}
func diff(a, b string) string {
return gentesthelper.DiffStrings(
a,
b,
)
}
func renderService(svc *svcdef.Service, prev string, te *gengokit.Executor, templPath string) (string, error) {
var prevFile io.Reader
if prev != "" {
prevFile = strings.NewReader(prev)
}
func renderService(svc *svcdef.Service, prev string, te *gengokit.TemplateExecutor, templPath string) (string, error) {
h, err := handler.New(svc, strings.NewReader(prev), te.PackageName)
h, err := handler.New(svc, prevFile, te.PackageName)
if err != nil {
return "", err
}
@ -436,33 +427,40 @@ func renderService(svc *svcdef.Service, prev string, te *gengokit.TemplateExecut
return "", err
}
nextBytes, err = testFormat(nextBytes)
nextCode, err := testFormat(string(nextBytes))
if err != nil {
return "", errors.Wrap(err, "cannot format")
}
nextCode := strings.TrimSpace(string(nextBytes))
nextCode = strings.TrimSpace(nextCode)
return nextCode, nil
}
func testGenerateResponseFile(templFP string, te *gengokit.TemplateExecutor, prevGenMap map[string]io.Reader) ([]byte, error) {
func diff(a, b string) string {
return gentesthelper.DiffStrings(
a,
b,
)
}
func testGenerateResponseFile(executor *gengokit.Executor, prev io.Reader, templFP string) (string, error) {
// apply server_handler.go template
code, err := generateResponseFile(templFP, te, prevGenMap)
code, err := generateResponseFile(executor, prev, templFP)
if err != nil {
return nil, err
return "", err
}
// read the code off the io.Reader
codeBytes, err := ioutil.ReadAll(code)
if err != nil {
return nil, err
return "", err
}
// format the code
formatted, err := testFormat(codeBytes)
formatted, err := testFormat(string(codeBytes))
if err != nil {
return codeBytes, err
return string(codeBytes), err
}
return formatted, nil
@ -470,12 +468,12 @@ func testGenerateResponseFile(templFP string, te *gengokit.TemplateExecutor, pre
// testFormat takes a string representing golang code and attempts to return a
// formated copy of that code.
func testFormat(code []byte) ([]byte, error) {
formatted, err := format.Source(code)
func testFormat(code string) (string, error) {
formatted, err := format.Source([]byte(code))
if err != nil {
return code, err
}
return formatted, nil
return string(formatted), nil
}

Просмотреть файл

@ -1,11 +1,13 @@
package gengokit
import (
"bytes"
"io"
"strings"
"text/template"
generatego "github.com/golang/protobuf/protoc-gen-go/generator"
"github.com/pkg/errors"
"github.com/TuneLab/go-truss/gengokit/clientarggen"
"github.com/TuneLab/go-truss/gengokit/httptransport"
@ -14,7 +16,7 @@ import (
)
type Renderable interface {
Render(string, *TemplateExecutor) (io.Reader, error)
Render(string, *Executor) (io.Reader, error)
}
type Config struct {
@ -24,9 +26,9 @@ type Config struct {
PreviousFiles []truss.NamedReadWriter
}
// templateExecutor is passed to templates as the executing struct; its fields
// Executor is passed to templates as the executing struct; its fields
// and methods are used to modify the template
type TemplateExecutor struct {
type Executor struct {
// import path for the directory containing the definition .proto files
ImportPath string
// import path for .pb.go files containing service structs
@ -41,12 +43,12 @@ type TemplateExecutor struct {
FuncMap template.FuncMap
}
func NewTemplateExecutor(sd *svcdef.Svcdef, conf Config) (*TemplateExecutor, error) {
func NewExecutor(sd *svcdef.Svcdef, conf Config) (*Executor, error) {
funcMap := template.FuncMap{
"ToLower": strings.ToLower,
"GoName": generatego.CamelCase,
}
return &TemplateExecutor{
return &Executor{
ImportPath: conf.GoPackage,
PBImportPath: conf.PBPackage,
PackageName: sd.PkgName,
@ -56,3 +58,25 @@ func NewTemplateExecutor(sd *svcdef.Svcdef, conf Config) (*TemplateExecutor, err
FuncMap: funcMap,
}, nil
}
// ApplyTemplate applies the passed template with the Executor
func (e *Executor) ApplyTemplate(templ string, templName string) (io.Reader, error) {
return ApplyTemplate(templ, templName, e, e.FuncMap)
}
// ApplyTemplate is a helper methods that packages can call to render a
// template with any executor and func map
func ApplyTemplate(templ string, templName string, executor interface{}, funcMap template.FuncMap) (io.Reader, error) {
codeTemplate, err := template.New(templName).Funcs(funcMap).Parse(templ)
if err != nil {
return nil, errors.Wrap(err, "cannot create template")
}
outputBuffer := bytes.NewBuffer(nil)
err = codeTemplate.Execute(outputBuffer, executor)
if err != nil {
return nil, errors.Wrap(err, "template error")
}
return outputBuffer, nil
}

Просмотреть файл

@ -14,7 +14,7 @@ func init() {
gopath = filepath.SplitList(os.Getenv("GOPATH"))
}
func TestNewTemplateExecutor(t *testing.T) {
func TestNewExecutor(t *testing.T) {
const def = `
syntax = "proto3";
@ -54,7 +54,7 @@ func TestNewTemplateExecutor(t *testing.T) {
PBPackage: "github.com/TuneLab/go-truss/gengokit/general-service",
}
te, err := NewTemplateExecutor(sd, conf)
te, err := NewExecutor(sd, conf)
if err != nil {
t.Fatal(err)
}

Просмотреть файл

@ -23,7 +23,9 @@ import (
// NewService is an exported func that creates a new service
// it will not be defined in the service definition but is required
const ignoredFunc = "NewService"
const serverTemplPath = "NAME-service/handlers/server/server_handler.gotemplate"
// ServerFile is the path to the service handelr file that handler will render
const ServerFile = "NAME-service/handlers/server/server_handler.gotemplate"
// New returns a truss.Renderable capable of updating server handlers.
// New should be passed the previous version of the server handler to parse.
@ -74,23 +76,23 @@ type handlerExecutor struct {
// Render returns a go code server handler that has functions for all
// ServiceMethods in the service definition.
func (h *handler) Render(f string, te *gengokit.TemplateExecutor) (io.Reader, error) {
if f != serverTemplPath {
return nil, errors.Errorf("cannot render unknown file: %q", f)
func (h *handler) Render(alias string, executor *gengokit.Executor) (io.Reader, error) {
if alias != ServerFile {
return nil, errors.Errorf("cannot render unknown file: %q", alias)
}
if h.ast == nil {
return applyServerTempl(te)
return applyServerTempl(executor)
}
// Remove exported methods not defined in service definition
// and remove methods defined in the previous file from methodMap
log.WithField("Service Methods", len(h.mMap)).Debug("Before prune")
h.ast.Decls = h.mMap.pruneDecls(h.ast.Decls, te.PackageName)
h.ast.Decls = h.mMap.pruneDecls(h.ast.Decls, executor.PackageName)
log.WithField("Service Methods", len(h.mMap)).Debug("After prune")
// create a new executor, and add all methods not defined in the previous file
ex := handlerExecutor{
PackageName: te.PackageName,
PackageName: executor.PackageName,
}
// If there are no methods to template then exit early
@ -283,30 +285,15 @@ func exprString(e ast.Expr) string {
return ""
}
func applyServerTempl(exec *gengokit.TemplateExecutor) (io.Reader, error) {
func applyServerTempl(exec *gengokit.Executor) (io.Reader, error) {
log.Debug("Rendering handler for the first time")
return applyTemplate(serverTempl, "ServerTempl", exec)
return exec.ApplyTemplate(serverTempl, "ServerTempl")
}
func applyServerMethsTempl(exec handlerExecutor) (io.Reader, error) {
return applyTemplate(serverMethsTempl, "ServerMethsTempl", exec)
}
func applyTemplate(templ string, templName string, exec interface{}) (io.Reader, error) {
funcMap := template.FuncMap{
"ToLower": strings.ToLower,
"GoName": generatego.CamelCase,
}
codeTemplate, err := template.New(templName).Funcs(funcMap).Parse(templ)
if err != nil {
return nil, errors.Wrap(err, "cannot create template")
}
outputBuffer := bytes.NewBuffer(nil)
err = codeTemplate.Execute(outputBuffer, exec)
if err != nil {
return nil, errors.Wrap(err, "template error")
}
return outputBuffer, nil
return gengokit.ApplyTemplate(serverMethsTempl, "ServerMethsTempl", exec, funcMap)
}

Просмотреть файл

@ -125,7 +125,7 @@ func TestApplyServerTempl(t *testing.T) {
if err != nil {
t.Fatal(err)
}
te, err := gengokit.NewTemplateExecutor(sd, conf)
te, err := gengokit.NewExecutor(sd, conf)
gen, err := applyServerTempl(te)
genBytes, err := ioutil.ReadAll(gen)