1
0

Add logging for invalid model errors (#126)

Add logging for invalid model errors

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/126
This commit is contained in:
konrad
2020-01-26 19:40:23 +00:00
parent fc65052ba0
commit a464d1760c
167 changed files with 18301 additions and 7389 deletions

View File

@ -0,0 +1,313 @@
// Package goparse contains logic for parsing Go files. Specifically it parses
// source and test files into domain models for generating tests.
package goparser
import (
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"strings"
"github.com/cweill/gotests/internal/models"
)
// ErrEmptyFile represents an empty file error.
var ErrEmptyFile = errors.New("file is empty")
// Result representats a parsed Go file.
type Result struct {
// The package name and imports of a Go file.
Header *models.Header
// All the functions and methods in a Go file.
Funcs []*models.Function
}
// Parser can parse Go files.
type Parser struct {
// The importer to resolve packages from import paths.
Importer types.Importer
}
// Parse parses a given Go file at srcPath, along any files that share the same
// package, into a domain model for generating tests.
func (p *Parser) Parse(srcPath string, files []models.Path) (*Result, error) {
b, err := p.readFile(srcPath)
if err != nil {
return nil, err
}
fset := token.NewFileSet()
f, err := p.parseFile(fset, srcPath)
if err != nil {
return nil, err
}
fs, err := p.parseFiles(fset, f, files)
if err != nil {
return nil, err
}
return &Result{
Header: &models.Header{
Comments: parsePkgComment(f, f.Package),
Package: f.Name.String(),
Imports: parseImports(f.Imports),
Code: goCode(b, f),
},
Funcs: p.parseFunctions(fset, f, fs),
}, nil
}
func (p *Parser) readFile(srcPath string) ([]byte, error) {
b, err := ioutil.ReadFile(srcPath)
if err != nil {
return nil, fmt.Errorf("ioutil.ReadFile: %v", err)
}
if len(b) == 0 {
return nil, ErrEmptyFile
}
return b, nil
}
func (p *Parser) parseFile(fset *token.FileSet, srcPath string) (*ast.File, error) {
f, err := parser.ParseFile(fset, srcPath, nil, parser.ParseComments)
if err != nil {
return nil, fmt.Errorf("target parser.ParseFile(): %v", err)
}
return f, nil
}
func (p *Parser) parseFiles(fset *token.FileSet, f *ast.File, files []models.Path) ([]*ast.File, error) {
pkg := f.Name.String()
var fs []*ast.File
for _, file := range files {
ff, err := parser.ParseFile(fset, string(file), nil, 0)
if err != nil {
return nil, fmt.Errorf("other file parser.ParseFile: %v", err)
}
if name := ff.Name.String(); name != pkg {
continue
}
fs = append(fs, ff)
}
return fs, nil
}
func (p *Parser) parseFunctions(fset *token.FileSet, f *ast.File, fs []*ast.File) []*models.Function {
ul, el := p.parseTypes(fset, fs)
var funcs []*models.Function
for _, d := range f.Decls {
fDecl, ok := d.(*ast.FuncDecl)
if !ok {
continue
}
funcs = append(funcs, parseFunc(fDecl, ul, el))
}
return funcs
}
func (p *Parser) parseTypes(fset *token.FileSet, fs []*ast.File) (map[string]types.Type, map[*types.Struct]ast.Expr) {
conf := &types.Config{
Importer: p.Importer,
// Adding a NO-OP error function ignores errors and performs best-effort
// type checking. https://godoc.org/golang.org/x/tools/go/types#Config
Error: func(error) {},
}
ti := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
}
// Note: conf.Check can fail, but since Info is not required data, it's ok.
conf.Check("", fset, fs, ti)
ul := make(map[string]types.Type)
el := make(map[*types.Struct]ast.Expr)
for e, t := range ti.Types {
// Collect the underlying types.
ul[t.Type.String()] = t.Type.Underlying()
// Collect structs to determine the fields of a receiver.
if v, ok := t.Type.(*types.Struct); ok {
el[v] = e
}
}
return ul, el
}
func parsePkgComment(f *ast.File, pkgPos token.Pos) []string {
var comments []string
var count int
for _, comment := range f.Comments {
if comment.End() >= pkgPos {
break
}
for _, c := range comment.List {
count += len(c.Text) + 1 // +1 for '\n'
if count < int(c.End()) {
n := int(c.End()) - count - 1
comments = append(comments, strings.Repeat("\n", n))
count++ // for last of '\n'
}
comments = append(comments, c.Text)
}
}
if int(pkgPos)-count > 1 {
comments = append(comments, strings.Repeat("\n", int(pkgPos)-count-2))
}
return comments
}
// Returns the Go code below the imports block.
func goCode(b []byte, f *ast.File) []byte {
furthestPos := f.Name.End()
for _, node := range f.Imports {
if pos := node.End(); pos > furthestPos {
furthestPos = pos
}
}
if furthestPos < token.Pos(len(b)) {
furthestPos++
// Avoid wrong output on windows-encoded files
if b[furthestPos-2] == '\r' && b[furthestPos-1] == '\n' && furthestPos < token.Pos(len(b)) {
furthestPos++
}
}
return b[furthestPos:]
}
func parseFunc(fDecl *ast.FuncDecl, ul map[string]types.Type, el map[*types.Struct]ast.Expr) *models.Function {
f := &models.Function{
Name: fDecl.Name.String(),
IsExported: fDecl.Name.IsExported(),
Receiver: parseReceiver(fDecl.Recv, ul, el),
Parameters: parseFieldList(fDecl.Type.Params, ul),
}
fs := parseFieldList(fDecl.Type.Results, ul)
i := 0
for _, fi := range fs {
if fi.Type.String() == "error" {
f.ReturnsError = true
continue
}
fi.Index = i
f.Results = append(f.Results, fi)
i++
}
return f
}
func parseImports(imps []*ast.ImportSpec) []*models.Import {
var is []*models.Import
for _, imp := range imps {
var n string
if imp.Name != nil {
n = imp.Name.String()
}
is = append(is, &models.Import{
Name: n,
Path: imp.Path.Value,
})
}
return is
}
func parseReceiver(fl *ast.FieldList, ul map[string]types.Type, el map[*types.Struct]ast.Expr) *models.Receiver {
if fl == nil {
return nil
}
r := &models.Receiver{
Field: parseFieldList(fl, ul)[0],
}
t, ok := ul[r.Type.Value]
if !ok {
return r
}
s, ok := t.(*types.Struct)
if !ok {
return r
}
st, found := el[s]
if !found {
return r
}
r.Fields = append(r.Fields, parseFieldList(st.(*ast.StructType).Fields, ul)...)
for i, f := range r.Fields {
// https://github.com/cweill/gotests/issues/69
if i >= s.NumFields() {
break
}
f.Name = s.Field(i).Name()
}
return r
}
func parseFieldList(fl *ast.FieldList, ul map[string]types.Type) []*models.Field {
if fl == nil {
return nil
}
i := 0
var fs []*models.Field
for _, f := range fl.List {
for _, pf := range parseFields(f, ul) {
pf.Index = i
fs = append(fs, pf)
i++
}
}
return fs
}
func parseFields(f *ast.Field, ul map[string]types.Type) []*models.Field {
t := parseExpr(f.Type, ul)
if len(f.Names) == 0 {
return []*models.Field{{
Type: t,
}}
}
var fs []*models.Field
for _, n := range f.Names {
fs = append(fs, &models.Field{
Name: n.Name,
Type: t,
})
}
return fs
}
func parseExpr(e ast.Expr, ul map[string]types.Type) *models.Expression {
switch v := e.(type) {
case *ast.StarExpr:
val := types.ExprString(v.X)
return &models.Expression{
Value: val,
IsStar: true,
Underlying: underlying(val, ul),
}
case *ast.Ellipsis:
exp := parseExpr(v.Elt, ul)
return &models.Expression{
Value: exp.Value,
IsStar: exp.IsStar,
IsVariadic: true,
Underlying: underlying(exp.Value, ul),
}
default:
val := types.ExprString(e)
return &models.Expression{
Value: val,
Underlying: underlying(val, ul),
IsWriter: val == "io.Writer",
}
}
}
func underlying(val string, ul map[string]types.Type) string {
if ul[val] != nil {
return ul[val].String()
}
return ""
}

View File

@ -0,0 +1,54 @@
package input
import (
"fmt"
"path"
"path/filepath"
"os"
"github.com/cweill/gotests/internal/models"
)
// Returns all the Golang files for the given path. Ignores hidden files.
func Files(srcPath string) ([]models.Path, error) {
srcPath, err := filepath.Abs(srcPath)
if err != nil {
return nil, fmt.Errorf("filepath.Abs: %v\n", err)
}
var fi os.FileInfo
if fi, err = os.Stat(srcPath); err != nil {
return nil, fmt.Errorf("os.Stat: %v\n", err)
}
if fi.IsDir() {
return dirFiles(srcPath)
}
return file(srcPath)
}
func dirFiles(srcPath string) ([]models.Path, error) {
ps, err := filepath.Glob(path.Join(srcPath, "*.go"))
if err != nil {
return nil, fmt.Errorf("filepath.Glob: %v\n", err)
}
var srcPaths []models.Path
for _, p := range ps {
src := models.Path(p)
if isHiddenFile(p) || src.IsTestPath() {
continue
}
srcPaths = append(srcPaths, src)
}
return srcPaths, nil
}
func file(srcPath string) ([]models.Path, error) {
src := models.Path(srcPath)
if filepath.Ext(srcPath) != ".go" || isHiddenFile(srcPath) {
return nil, fmt.Errorf("no Go source files found at %v", srcPath)
}
return []models.Path{src}, nil
}
func isHiddenFile(path string) bool {
return []rune(filepath.Base(path))[0] == '.'
}

View File

@ -0,0 +1,172 @@
package models
import (
"strings"
"unicode"
)
type Expression struct {
Value string
IsStar bool
IsVariadic bool
IsWriter bool
Underlying string
}
func (e *Expression) String() string {
value := e.Value
if e.IsStar {
value = "*" + value
}
if e.IsVariadic {
return "[]" + value
}
return value
}
type Field struct {
Name string
Type *Expression
Index int
}
func (f *Field) IsWriter() bool {
return f.Type.IsWriter
}
func (f *Field) IsStruct() bool {
return strings.HasPrefix(f.Type.Underlying, "struct")
}
func (f *Field) IsBasicType() bool {
return isBasicType(f.Type.String()) || isBasicType(f.Type.Underlying)
}
func isBasicType(t string) bool {
switch t {
case "bool", "string", "int", "int8", "int16", "int32", "int64", "uint",
"uint8", "uint16", "uint32", "uint64", "uintptr", "byte", "rune",
"float32", "float64", "complex64", "complex128":
return true
default:
return false
}
}
func (f *Field) IsNamed() bool {
return f.Name != "" && f.Name != "_"
}
func (f *Field) ShortName() string {
return strings.ToLower(string([]rune(f.Type.Value)[0]))
}
type Receiver struct {
*Field
Fields []*Field
}
type Function struct {
Name string
IsExported bool
Receiver *Receiver
Parameters []*Field
Results []*Field
ReturnsError bool
}
func (f *Function) TestParameters() []*Field {
var ps []*Field
for _, p := range f.Parameters {
if p.IsWriter() {
continue
}
ps = append(ps, p)
}
return ps
}
func (f *Function) TestResults() []*Field {
var ps []*Field
ps = append(ps, f.Results...)
for _, p := range f.Parameters {
if !p.IsWriter() {
continue
}
ps = append(ps, &Field{
Name: p.Name,
Type: &Expression{
Value: "string",
IsWriter: true,
Underlying: "string",
},
Index: len(ps),
})
}
return ps
}
func (f *Function) ReturnsMultiple() bool {
return len(f.Results) > 1
}
func (f *Function) OnlyReturnsOneValue() bool {
return len(f.Results) == 1 && !f.ReturnsError
}
func (f *Function) OnlyReturnsError() bool {
return len(f.Results) == 0 && f.ReturnsError
}
func (f *Function) FullName() string {
var r string
if f.Receiver != nil {
r = f.Receiver.Type.Value
}
return strings.Title(r) + strings.Title(f.Name)
}
func (f *Function) TestName() string {
if strings.HasPrefix(f.Name, "Test") {
return f.Name
}
if f.Receiver != nil {
receiverType := f.Receiver.Type.Value
if unicode.IsLower([]rune(receiverType)[0]) {
receiverType = "_" + receiverType
}
return "Test" + receiverType + "_" + f.Name
}
if unicode.IsLower([]rune(f.Name)[0]) {
return "Test_" + f.Name
}
return "Test" + f.Name
}
func (f *Function) IsNaked() bool {
return f.Receiver == nil && len(f.Parameters) == 0 && len(f.Results) == 0
}
type Import struct {
Name, Path string
}
type Header struct {
Comments []string
Package string
Imports []*Import
Code []byte
}
type Path string
func (p Path) TestPath() string {
if !p.IsTestPath() {
return strings.TrimSuffix(string(p), ".go") + "_test.go"
}
return string(p)
}
func (p Path) IsTestPath() bool {
return strings.HasSuffix(string(p), "_test.go")
}

View File

@ -0,0 +1,65 @@
package output
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"golang.org/x/tools/imports"
"github.com/cweill/gotests/internal/models"
"github.com/cweill/gotests/internal/render"
)
type Options struct {
PrintInputs bool
Subtests bool
TemplateDir string
}
func Process(head *models.Header, funcs []*models.Function, opt *Options) ([]byte, error) {
if opt != nil && opt.TemplateDir != "" {
err := render.LoadCustomTemplates(opt.TemplateDir)
if err != nil {
return nil, fmt.Errorf("loading custom templates: %v", err)
}
}
tf, err := ioutil.TempFile("", "gotests_")
if err != nil {
return nil, fmt.Errorf("ioutil.TempFile: %v", err)
}
defer tf.Close()
defer os.Remove(tf.Name())
b := &bytes.Buffer{}
if err := writeTests(b, head, funcs, opt); err != nil {
return nil, err
}
out, err := imports.Process(tf.Name(), b.Bytes(), nil)
if err != nil {
return nil, fmt.Errorf("imports.Process: %v", err)
}
return out, nil
}
func IsFileExist(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
func writeTests(w io.Writer, head *models.Header, funcs []*models.Function, opt *Options) error {
b := bufio.NewWriter(w)
if err := render.Header(b, head); err != nil {
return fmt.Errorf("render.Header: %v", err)
}
for _, fun := range funcs {
if err := render.TestFunction(b, fun, opt.PrintInputs, opt.Subtests); err != nil {
return fmt.Errorf("render.TestFunction: %v", err)
}
}
return b.Flush()
}

View File

@ -0,0 +1,7 @@
bindata.go must be generated with https://github.com/jteeuwen/go-bindata.
From the gotests root run `$ go generate ./...`.
Or run `$ go-bindata -pkg=bindata -o "internal/render/bindata/bindata.go" internal/render/templates/`.
During development run `$ go-bindata -pkg=bindata -o "internal/render/bindata/bindata.go" -debug internal/render/templates/` instead.

View File

@ -0,0 +1,323 @@
// Code generated by "esc -o bindata/esc.go -pkg=bindata templates"; DO NOT EDIT.
package bindata
import (
"bytes"
"compress/gzip"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"sync"
"time"
)
type _escLocalFS struct{}
var _escLocal _escLocalFS
type _escStaticFS struct{}
var _escStatic _escStaticFS
type _escDirectory struct {
fs http.FileSystem
name string
}
type _escFile struct {
compressed string
size int64
modtime int64
local string
isDir bool
once sync.Once
data []byte
name string
}
func (_escLocalFS) Open(name string) (http.File, error) {
f, present := _escData[path.Clean(name)]
if !present {
return nil, os.ErrNotExist
}
return os.Open(f.local)
}
func (_escStaticFS) prepare(name string) (*_escFile, error) {
f, present := _escData[path.Clean(name)]
if !present {
return nil, os.ErrNotExist
}
var err error
f.once.Do(func() {
f.name = path.Base(name)
if f.size == 0 {
return
}
var gr *gzip.Reader
b64 := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(f.compressed))
gr, err = gzip.NewReader(b64)
if err != nil {
return
}
f.data, err = ioutil.ReadAll(gr)
})
if err != nil {
return nil, err
}
return f, nil
}
func (fs _escStaticFS) Open(name string) (http.File, error) {
f, err := fs.prepare(name)
if err != nil {
return nil, err
}
return f.File()
}
func (dir _escDirectory) Open(name string) (http.File, error) {
return dir.fs.Open(dir.name + name)
}
func (f *_escFile) File() (http.File, error) {
type httpFile struct {
*bytes.Reader
*_escFile
}
return &httpFile{
Reader: bytes.NewReader(f.data),
_escFile: f,
}, nil
}
func (f *_escFile) Close() error {
return nil
}
func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) {
if !f.isDir {
return nil, fmt.Errorf(" escFile.Readdir: '%s' is not directory", f.name)
}
fis, ok := _escDirs[f.local]
if !ok {
return nil, fmt.Errorf(" escFile.Readdir: '%s' is directory, but we have no info about content of this dir, local=%s", f.name, f.local)
}
limit := count
if count <= 0 || limit > len(fis) {
limit = len(fis)
}
if len(fis) == 0 && count > 0 {
return nil, io.EOF
}
return []os.FileInfo(fis[0:limit]), nil
}
func (f *_escFile) Stat() (os.FileInfo, error) {
return f, nil
}
func (f *_escFile) Name() string {
return f.name
}
func (f *_escFile) Size() int64 {
return f.size
}
func (f *_escFile) Mode() os.FileMode {
return 0
}
func (f *_escFile) ModTime() time.Time {
return time.Unix(f.modtime, 0)
}
func (f *_escFile) IsDir() bool {
return f.isDir
}
func (f *_escFile) Sys() interface{} {
return f
}
// FS returns a http.Filesystem for the embedded assets. If useLocal is true,
// the filesystem's contents are instead used.
func FS(useLocal bool) http.FileSystem {
if useLocal {
return _escLocal
}
return _escStatic
}
// Dir returns a http.Filesystem for the embedded assets on a given prefix dir.
// If useLocal is true, the filesystem's contents are instead used.
func Dir(useLocal bool, name string) http.FileSystem {
if useLocal {
return _escDirectory{fs: _escLocal, name: name}
}
return _escDirectory{fs: _escStatic, name: name}
}
// FSByte returns the named file from the embedded assets. If useLocal is
// true, the filesystem's contents are instead used.
func FSByte(useLocal bool, name string) ([]byte, error) {
if useLocal {
f, err := _escLocal.Open(name)
if err != nil {
return nil, err
}
b, err := ioutil.ReadAll(f)
_ = f.Close()
return b, err
}
f, err := _escStatic.prepare(name)
if err != nil {
return nil, err
}
return f.data, nil
}
// FSMustByte is the same as FSByte, but panics if name is not present.
func FSMustByte(useLocal bool, name string) []byte {
b, err := FSByte(useLocal, name)
if err != nil {
panic(err)
}
return b
}
// FSString is the string version of FSByte.
func FSString(useLocal bool, name string) (string, error) {
b, err := FSByte(useLocal, name)
return string(b), err
}
// FSMustString is the string version of FSMustByte.
func FSMustString(useLocal bool, name string) string {
return string(FSMustByte(useLocal, name))
}
var _escData = map[string]*_escFile{
"/templates/call.tmpl": {
name: "call.tmpl",
local: "templates/call.tmpl",
size: 241,
modtime: 1540446832,
compressed: `
H4sIAAAAAAAC/0SOQWrDQAxFryKMFy0YHaDQA3hTSlvatRjLrsCeFo2SEITuHsY4mdWHP2/el/vEs2SG
LtG6dhHuF7FfwA9OLGfW2sgM+c8Ax/JpekoWYYbunKf6eicBI1qLb7RxxJO7Ul4Yehmg5xVeXgHfSWlj
Yy2HvZeIAR5/296PitUbzJB0KU2/K+riTuPX9Z9xLN+kQpOkCMTG7vF85C0AAP//ZQi8iPEAAAA=
`,
},
"/templates/function.tmpl": {
name: "function.tmpl",
local: "templates/function.tmpl",
size: 2392,
modtime: 1540483824,
compressed: `
H4sIAAAAAAAC/7RWTW/jNhA9S79i1sgupMJh7g58aJC06KFx4QTNoSgKRh65RGnKJUcJDIL/vSBFfVpO
c9kcImlIznvz5g0Ta3dYCoWwKGtVkKjUwrnU2mu4KmG1BuZcmvolsJY9o6FHfkDnMoIfCA0JtWfPOdg0
8UfeBf0NbIsFijfUzqVJCIsS2C/miXRdUAh20Z8Eyp1pYgmdjghliIAJm33euFtztcfJgcTa8O1JBnqn
I8YlfwTVLn51mG1o8D559ax8mb9xzQ9IqANYoMb1fkRsQOv8RAAMoTN2A8QxvhfUeNH/+HMAo/gBPaxQ
+3h4RuaWO1e7XuuJXFHa5tEpIk2vWZvyXNAL4n0gWZIEvfyvmTMD3bZoakmmxXnhij6SrIPcItVamQet
q6jBO1f0oDW8VpWc6OyFvLmB5839ZgU/7nbgtYaCGzQstKGsNFgrSsgqDeypfm2akamKvKCP/B/c5blz
8NcSiHyTrA3JYynNdptC/GlZdpmcI7atVUbEfEeX4IdqOkYQOcN1L/tcty+M1VkPA83Qn9MRw2aunfsW
qUeF2e9c1uicbVNcmLbEWtZM/wqIWOMjNpjBZZ+gn71kZiDPPiLezAi1Zb5oQV31o9FareHb64nQsLu6
LFHbzwDGUWnau1HyNHRTfh7fKAwq5dAxIzwcJSeEhW4cvICrMvi2Xym4lE34EosZGyeijF2bEnMOUOum
q3Mgt50pM7/vyxqUkLl/ErF2OmKbiYWUZbYY5jqgMXyPsRT0O2ANX9+W0B7/+rZYjuCFOtZd8aj1cgCW
945ob4nRuIe1yZjoULC1zc1UVIqEqjEWNuewDy11DnnJU0H1nyvqB6fzGHsK12+W3w62NKoOL6zed9Jg
xLjjRhSDP0xdc6/KOX/5oRxxGOoshcJpoz/N5zvhf9FYSiyI3SMeH/6tucy6DMsxoXzIqOveZ3zYEo5k
f60liaMckY18eq/+j1Evkrz8D8PEp+ALGl7XLnVp2vr0vwAAAP//X+Qs81gJAAA=
`,
},
"/templates/header.tmpl": {
name: "header.tmpl",
local: "templates/header.tmpl",
size: 142,
modtime: 1540481163,
compressed: `
H4sIAAAAAAAC/0TMMQ7CMAyF4d2nsDrBQC7BxIK4gkUebYXiViGb9e6OlAi6/bL1voiM1+rQaYFl1ImU
iGo+Q9N1KwXePmRE6g941gspuz3fNkMj0mMkKbKWfatNT4dw65cB3K2AHJO2/DhSzv/6BgAA///GzMM9
jgAAAA==
`,
},
"/templates/inline.tmpl": {
name: "inline.tmpl",
local: "templates/inline.tmpl",
size: 49,
modtime: 1540446006,
compressed: `
H4sIAAAAAAAC/6quTklNy8xLVVDKzMvJzEtVqq1VqK4uSc0tyEksSVVQSk7MyVFS0AOLpual1NYCAgAA
//+q60H/MQAAAA==
`,
},
"/templates/inputs.tmpl": {
name: "inputs.tmpl",
local: "templates/inputs.tmpl",
size: 152,
modtime: 1540446821,
compressed: `
H4sIAAAAAAAC/0yNMQoCQQxFrxKWLSUHEDyAneAJIptZptgomWz1yd1lRoupEh7/vw9sWqopLdU+Z7Ql
E1gLXW/E/a2F7B3Ez/MV2qJlRrDJoRcC1LZ/Zi388GpxH5IOXWzXwcXl0FD/dcX3xsCgfWLyzOcbAAD/
/468z9qYAAAA
`,
},
"/templates/message.tmpl": {
name: "message.tmpl",
local: "templates/message.tmpl",
size: 201,
modtime: 1540446006,
compressed: `
H4sIAAAAAAAC/zyN4WqDQBCE//sUiyi0oPsAhT5A/xRpS/9f4mgW9GLuTkNY9t2DB/HXDDPDN6o9BvGg
ckaMbkRJrVmhKgP5ayL+XU8JMUWz+sakCt+bqd4lXYh/cIZsCHvCf48F/O+mFWZ8DPnbzTB7y0Tugvj0
5Zd1B6oG50dQJQ1VmOjjk7hzwc1ICLmXgSoxa16/9XZws7wXqi1l+wwAAP//kC65UskAAAA=
`,
},
"/templates/results.tmpl": {
name: "results.tmpl",
local: "templates/results.tmpl",
size: 168,
modtime: 1540446006,
compressed: `
H4sIAAAAAAAC/1yNTQrCQAyFr/Iosyw9gOBS3HsDoRkJlAy8ma5C7i6pRcFVfr4vee6rVDXBROn7NvoU
AXc+7SUoOqPIhssVy+ODI9y1omjEDHexNTf3NrBkc85a82DstH4jG1MW8uQ4hMbv0385A3/uUd8BAAD/
/7BPz2GoAAAA
`,
},
"/templates": {
name: "templates",
local: `templates`,
isDir: true,
},
}
var _escDirs = map[string][]os.FileInfo{
"templates": {
_escData["/templates/call.tmpl"],
_escData["/templates/function.tmpl"],
_escData["/templates/header.tmpl"],
_escData["/templates/inline.tmpl"],
_escData["/templates/inputs.tmpl"],
_escData["/templates/message.tmpl"],
_escData["/templates/results.tmpl"],
},
}

View File

@ -0,0 +1,23 @@
// Package bindata
// Helper with wrapper for backward compatibility with go-bindata
// used only AssetNames func, because only this func has no analog on esc
//
// the reason for changing go-bindata to esc - is:
// `go-bindata creator deleted their @github account and someone else created a new account with the same name.`
//
// https://github.com/jteeuwen/go-bindata/issues/5
// https://twitter.com/francesc/status/961249107020001280
//
// After research some of alternatives - `esc` - is looks like one of the best choice
// https://tech.townsourced.com/post/embedding-static-files-in-go/
package bindata
// AssetNames returns the names of the assets. (for compatible with go-bindata)
func AssetNames() []string {
names := make([]string, 0, len(_escData))
for name := range _escData {
names = append(names, name)
}
return names
}

View File

@ -0,0 +1,135 @@
package render
//go:generate esc -o bindata/esc.go -pkg=bindata templates
import (
"fmt"
"io"
"io/ioutil"
"path"
"strings"
"text/template"
"github.com/cweill/gotests/internal/models"
"github.com/cweill/gotests/internal/render/bindata"
)
const name = "name"
var (
tmpls *template.Template
)
func init() {
initEmptyTmpls()
for _, name := range bindata.AssetNames() {
tmpls = template.Must(tmpls.Parse(bindata.FSMustString(false, name)))
}
}
// LoadCustomTemplates allows to load in custom templates from a specified path.
func LoadCustomTemplates(dir string) error {
initEmptyTmpls()
files, err := ioutil.ReadDir(dir)
if err != nil {
return fmt.Errorf("ioutil.ReadDir: %v", err)
}
templateFiles := []string{}
for _, f := range files {
templateFiles = append(templateFiles, path.Join(dir, f.Name()))
}
tmpls, err = tmpls.ParseFiles(templateFiles...)
if err != nil {
return fmt.Errorf("tmpls.ParseFiles: %v", err)
}
return nil
}
func initEmptyTmpls() {
tmpls = template.New("render").Funcs(map[string]interface{}{
"Field": fieldName,
"Receiver": receiverName,
"Param": parameterName,
"Want": wantName,
"Got": gotName,
})
}
func fieldName(f *models.Field) string {
var n string
if f.IsNamed() {
n = f.Name
} else {
n = f.Type.String()
}
return n
}
func receiverName(f *models.Receiver) string {
var n string
if f.IsNamed() {
n = f.Name
} else {
n = f.ShortName()
}
if n == "name" {
// Avoid conflict with test struct's "name" field.
n = "n"
}
return n
}
func parameterName(f *models.Field) string {
var n string
if f.IsNamed() {
n = f.Name
} else {
n = fmt.Sprintf("in%v", f.Index)
}
return n
}
func wantName(f *models.Field) string {
var n string
if f.IsNamed() {
n = "want" + strings.Title(f.Name)
} else if f.Index == 0 {
n = "want"
} else {
n = fmt.Sprintf("want%v", f.Index)
}
return n
}
func gotName(f *models.Field) string {
var n string
if f.IsNamed() {
n = "got" + strings.Title(f.Name)
} else if f.Index == 0 {
n = "got"
} else {
n = fmt.Sprintf("got%v", f.Index)
}
return n
}
func Header(w io.Writer, h *models.Header) error {
if err := tmpls.ExecuteTemplate(w, "header", h); err != nil {
return err
}
_, err := w.Write(h.Code)
return err
}
func TestFunction(w io.Writer, f *models.Function, printInputs bool, subtests bool) error {
return tmpls.ExecuteTemplate(w, "function", struct {
*models.Function
PrintInputs bool
Subtests bool
}{
Function: f,
PrintInputs: printInputs,
Subtests: subtests,
})
}