2016-08-29 13:00:18 +01:00
|
|
|
package moq
|
|
|
|
|
|
|
|
import (
|
2016-09-21 21:39:26 +01:00
|
|
|
"errors"
|
2016-08-29 13:00:18 +01:00
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"go/importer"
|
|
|
|
"go/parser"
|
|
|
|
"go/token"
|
|
|
|
"go/types"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"strings"
|
2017-03-07 14:24:56 +01:00
|
|
|
"text/template"
|
2016-08-29 13:00:18 +01:00
|
|
|
)
|
|
|
|
|
2016-08-29 15:27:09 +01:00
|
|
|
// Mocker can generate mock structs.
|
2016-08-29 13:00:18 +01:00
|
|
|
type Mocker struct {
|
2016-09-21 21:39:26 +01:00
|
|
|
src string
|
|
|
|
tmpl *template.Template
|
|
|
|
fset *token.FileSet
|
|
|
|
pkgs map[string]*ast.Package
|
|
|
|
pkgName string
|
2016-08-29 13:00:18 +01:00
|
|
|
}
|
|
|
|
|
2016-08-29 15:27:09 +01:00
|
|
|
// New makes a new Mocker for the specified package directory.
|
2016-09-21 21:39:26 +01:00
|
|
|
func New(src, packageName string) (*Mocker, error) {
|
2016-08-29 13:00:18 +01:00
|
|
|
fset := token.NewFileSet()
|
|
|
|
noTestFiles := func(i os.FileInfo) bool {
|
|
|
|
return !strings.HasSuffix(i.Name(), "_test.go")
|
|
|
|
}
|
|
|
|
pkgs, err := parser.ParseDir(fset, src, noTestFiles, parser.SpuriousErrors)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-09-21 21:39:26 +01:00
|
|
|
if len(packageName) == 0 {
|
|
|
|
for pkgName := range pkgs {
|
|
|
|
if strings.Contains(pkgName, "_test") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
packageName = pkgName
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(packageName) == 0 {
|
|
|
|
return nil, errors.New("failed to determine package name")
|
|
|
|
}
|
2016-08-29 13:00:18 +01:00
|
|
|
tmpl, err := template.New("moq").Parse(moqTemplate)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &Mocker{
|
2016-09-21 21:39:26 +01:00
|
|
|
src: src,
|
|
|
|
tmpl: tmpl,
|
|
|
|
fset: fset,
|
|
|
|
pkgs: pkgs,
|
|
|
|
pkgName: packageName,
|
2016-08-29 13:00:18 +01:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mock generates a mock for the specified interface name.
|
|
|
|
func (m *Mocker) Mock(w io.Writer, name ...string) error {
|
2016-09-21 21:49:12 +01:00
|
|
|
if len(name) == 0 {
|
|
|
|
return errors.New("must specify one interface")
|
|
|
|
}
|
2016-08-29 13:00:18 +01:00
|
|
|
var objs []*obj
|
|
|
|
for _, pkg := range m.pkgs {
|
|
|
|
i := 0
|
|
|
|
files := make([]*ast.File, len(pkg.Files))
|
|
|
|
for _, file := range pkg.Files {
|
|
|
|
files[i] = file
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
conf := types.Config{Importer: importer.Default()}
|
|
|
|
tpkg, err := conf.Check(m.src, m.fset, files, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, n := range name {
|
|
|
|
iface := tpkg.Scope().Lookup(n)
|
2016-09-21 21:49:12 +01:00
|
|
|
if iface == nil {
|
|
|
|
return fmt.Errorf("cannot find interface %s", n)
|
|
|
|
}
|
2016-08-29 13:00:18 +01:00
|
|
|
if !types.IsInterface(iface.Type()) {
|
|
|
|
return fmt.Errorf("%s (%s) not an interface", n, iface.Type().String())
|
|
|
|
}
|
|
|
|
iiface := iface.Type().Underlying().(*types.Interface).Complete()
|
|
|
|
obj := &obj{
|
2016-08-29 13:20:37 +01:00
|
|
|
InterfaceName: n,
|
2016-08-29 13:00:18 +01:00
|
|
|
}
|
|
|
|
for i := 0; i < iiface.NumMethods(); i++ {
|
|
|
|
meth := iiface.Method(i)
|
|
|
|
sig := meth.Type().(*types.Signature)
|
|
|
|
method := &method{
|
|
|
|
Name: meth.Name(),
|
|
|
|
}
|
|
|
|
obj.Methods = append(obj.Methods, method)
|
2016-10-06 11:44:32 +01:00
|
|
|
method.Params = m.extractArgs(sig, sig.Params(), "in%d")
|
|
|
|
method.Returns = m.extractArgs(sig, sig.Results(), "out%d")
|
2016-08-29 13:00:18 +01:00
|
|
|
}
|
|
|
|
objs = append(objs, obj)
|
|
|
|
}
|
|
|
|
}
|
2016-09-21 21:39:26 +01:00
|
|
|
err := m.tmpl.Execute(w, struct {
|
|
|
|
PackageName string
|
|
|
|
Objs []*obj
|
|
|
|
}{
|
|
|
|
PackageName: m.pkgName,
|
|
|
|
Objs: objs,
|
|
|
|
})
|
2016-08-29 13:00:18 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-09-21 21:39:26 +01:00
|
|
|
func (m *Mocker) packageQualifier(pkg *types.Package) string {
|
|
|
|
if m.pkgName == pkg.Name() {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return pkg.Name()
|
|
|
|
}
|
|
|
|
|
2016-10-06 11:44:32 +01:00
|
|
|
func (m *Mocker) extractArgs(sig *types.Signature, list *types.Tuple, nameFormat string) []*param {
|
2016-08-29 13:00:18 +01:00
|
|
|
var params []*param
|
2016-10-06 11:44:32 +01:00
|
|
|
listLen := list.Len()
|
|
|
|
for ii := 0; ii < listLen; ii++ {
|
2016-08-29 13:00:18 +01:00
|
|
|
p := list.At(ii)
|
|
|
|
name := p.Name()
|
|
|
|
if name == "" {
|
|
|
|
name = fmt.Sprintf(nameFormat, ii+1)
|
|
|
|
}
|
2016-09-21 21:39:26 +01:00
|
|
|
typename := types.TypeString(p.Type(), m.packageQualifier)
|
2016-10-06 11:44:32 +01:00
|
|
|
// check for final variadic argument
|
2016-10-06 11:58:39 +01:00
|
|
|
variadic := sig.Variadic() && ii == listLen-1 && typename[0:2] == "[]"
|
2016-08-29 13:00:18 +01:00
|
|
|
param := ¶m{
|
2016-10-06 11:58:39 +01:00
|
|
|
Name: name,
|
|
|
|
Type: typename,
|
|
|
|
Variadic: variadic,
|
2016-08-29 13:00:18 +01:00
|
|
|
}
|
|
|
|
params = append(params, param)
|
|
|
|
}
|
|
|
|
return params
|
|
|
|
}
|
|
|
|
|
|
|
|
type obj struct {
|
2016-08-29 13:20:37 +01:00
|
|
|
InterfaceName string
|
|
|
|
Methods []*method
|
2016-08-29 13:00:18 +01:00
|
|
|
}
|
|
|
|
type method struct {
|
|
|
|
Name string
|
|
|
|
Params []*param
|
|
|
|
Returns []*param
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *method) Arglist() string {
|
|
|
|
params := make([]string, len(m.Params))
|
|
|
|
for i, p := range m.Params {
|
|
|
|
params[i] = p.String()
|
|
|
|
}
|
|
|
|
return strings.Join(params, ", ")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *method) ArgCallList() string {
|
|
|
|
params := make([]string, len(m.Params))
|
|
|
|
for i, p := range m.Params {
|
2016-10-06 11:58:39 +01:00
|
|
|
params[i] = p.CallName()
|
2016-08-29 13:00:18 +01:00
|
|
|
}
|
|
|
|
return strings.Join(params, ", ")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *method) ReturnArglist() string {
|
|
|
|
params := make([]string, len(m.Returns))
|
|
|
|
for i, p := range m.Returns {
|
|
|
|
params[i] = p.TypeString()
|
|
|
|
}
|
|
|
|
if len(m.Returns) > 1 {
|
|
|
|
return fmt.Sprintf("(%s)", strings.Join(params, ", "))
|
|
|
|
}
|
|
|
|
return strings.Join(params, ", ")
|
|
|
|
}
|
|
|
|
|
|
|
|
type param struct {
|
2016-10-06 11:58:39 +01:00
|
|
|
Name string
|
|
|
|
Type string
|
|
|
|
Variadic bool
|
2016-08-29 13:00:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p param) String() string {
|
|
|
|
return fmt.Sprintf("%s %s", p.Name, p.TypeString())
|
|
|
|
}
|
|
|
|
|
2016-10-06 11:58:39 +01:00
|
|
|
func (p param) CallName() string {
|
|
|
|
if p.Variadic {
|
|
|
|
return p.Name + "..."
|
|
|
|
}
|
|
|
|
return p.Name
|
|
|
|
}
|
|
|
|
|
2016-08-29 13:00:18 +01:00
|
|
|
func (p param) TypeString() string {
|
2016-10-06 11:58:39 +01:00
|
|
|
if p.Variadic {
|
|
|
|
return "..." + p.Type[2:]
|
|
|
|
}
|
2016-08-29 13:00:18 +01:00
|
|
|
return p.Type
|
|
|
|
}
|
|
|
|
|
2016-09-21 21:55:13 +01:00
|
|
|
var moqTemplate = `package {{.PackageName}}
|
2016-08-29 13:00:18 +01:00
|
|
|
|
|
|
|
// AUTOGENERATED BY MOQ
|
|
|
|
// github.com/matryer/moq
|
2016-08-29 13:20:37 +01:00
|
|
|
{{ range $i, $obj := .Objs }}
|
2016-08-29 13:22:52 +01:00
|
|
|
// {{.InterfaceName}}Mock is a mock implementation of {{.InterfaceName}}.
|
2016-08-29 13:09:34 +01:00
|
|
|
//
|
2016-08-29 13:20:37 +01:00
|
|
|
// func TestSomethingThatUses{{.InterfaceName}}(t *testing.T) {
|
2016-08-29 13:09:34 +01:00
|
|
|
//
|
2016-08-29 13:20:37 +01:00
|
|
|
// // make and configure a mocked {{.InterfaceName}}
|
|
|
|
// mocked{{.InterfaceName}} := &{{.InterfaceName}}Mock{ {{ range .Methods }}
|
|
|
|
// {{.Name}}Func: func({{ .Arglist }}) {{.ReturnArglist}} {
|
2017-07-04 17:48:32 -06:00
|
|
|
// panic("TODO: mock out the {{.Name}} method")
|
2016-08-29 13:20:37 +01:00
|
|
|
// },{{- end }}
|
2016-08-29 13:09:34 +01:00
|
|
|
// }
|
|
|
|
//
|
2016-08-29 13:20:37 +01:00
|
|
|
// // TODO: use mocked{{.InterfaceName}} in code that requires {{.InterfaceName}}
|
2017-07-04 18:09:47 -06:00
|
|
|
// // and then make assertions.
|
|
|
|
//
|
2016-08-29 13:09:34 +01:00
|
|
|
// }
|
2016-08-29 13:20:37 +01:00
|
|
|
type {{.InterfaceName}}Mock struct {
|
|
|
|
{{- range .Methods }}
|
2017-07-04 17:48:32 -06:00
|
|
|
// {{.Name}}Func mocks the {{.Name}} method.
|
2016-08-29 13:00:18 +01:00
|
|
|
{{.Name}}Func func({{ .Arglist }}) {{.ReturnArglist}}
|
2017-07-04 18:09:47 -06:00
|
|
|
{{ end }}
|
|
|
|
// CallsTo gets counters for each of the methods indicating
|
|
|
|
// how many times each one was called.
|
|
|
|
CallsTo struct {
|
|
|
|
{{- range .Methods }}
|
|
|
|
// {{ .Name }} holds the number of calls to the {{.Name}} method.
|
|
|
|
{{ .Name }} uint64
|
2016-08-29 13:20:37 +01:00
|
|
|
{{- end }}
|
2017-07-04 18:09:47 -06:00
|
|
|
}
|
2016-08-29 13:20:37 +01:00
|
|
|
}
|
2016-08-29 13:00:18 +01:00
|
|
|
{{ range .Methods }}
|
2016-08-29 13:20:37 +01:00
|
|
|
// {{.Name}} calls {{.Name}}Func.
|
|
|
|
func (mock *{{$obj.InterfaceName}}Mock) {{.Name}}({{.Arglist}}) {{.ReturnArglist}} {
|
2016-10-05 21:39:23 +01:00
|
|
|
if mock.{{.Name}}Func == nil {
|
2016-11-26 11:24:53 -08:00
|
|
|
panic("moq: {{$obj.InterfaceName}}Mock.{{.Name}}Func is nil but was just called")
|
2016-08-29 13:00:18 +01:00
|
|
|
}
|
2017-07-04 18:09:47 -06:00
|
|
|
atomic.AddUint64(&mock.CallsTo.{{.Name}}, 1) // count this
|
2017-07-04 17:48:32 -06:00
|
|
|
{{- if .ReturnArglist }}
|
2016-08-29 13:20:37 +01:00
|
|
|
return mock.{{.Name}}Func({{.ArgCallList}})
|
2017-07-04 17:48:32 -06:00
|
|
|
{{- else }}
|
2017-03-07 12:56:15 +00:00
|
|
|
mock.{{.Name}}Func({{.ArgCallList}})
|
2017-07-04 17:48:32 -06:00
|
|
|
{{- end }}
|
2016-08-29 13:00:18 +01:00
|
|
|
}
|
2016-09-21 21:55:13 +01:00
|
|
|
{{ end -}}
|
|
|
|
{{ end -}}`
|