diff --git a/example/mockpersonstore_test.go b/example/mockpersonstore_test.go index a82d0a8..70911cd 100755 --- a/example/mockpersonstore_test.go +++ b/example/mockpersonstore_test.go @@ -14,22 +14,22 @@ var _ PersonStore = &PersonStoreMock{} // PersonStoreMock is a mock implementation of PersonStore. // -// func TestSomethingThatUsesPersonStore(t *testing.T) { +// func TestSomethingThatUsesPersonStore(t *testing.T) { // -// // make and configure a mocked PersonStore -// mockedPersonStore := &PersonStoreMock{ -// CreateFunc: func(ctx context.Context, person *Person, confirm bool) error { -// panic("mock out the Create method") -// }, -// GetFunc: func(ctx context.Context, id string) (*Person, error) { -// panic("mock out the Get method") -// }, -// } +// // make and configure a mocked PersonStore +// mockedPersonStore := &PersonStoreMock{ +// CreateFunc: func(ctx context.Context, person *Person, confirm bool) error { +// panic("mock out the Create method") +// }, +// GetFunc: func(ctx context.Context, id string) (*Person, error) { +// panic("mock out the Get method") +// }, +// } // -// // use mockedPersonStore in code that requires PersonStore -// // and then make assertions. +// // use mockedPersonStore in code that requires PersonStore +// // and then make assertions. // -// } +// } type PersonStoreMock struct { // CreateFunc mocks the Create method. CreateFunc func(ctx context.Context, person *Person, confirm bool) error diff --git a/generate/generated.go b/generate/generated.go index 04f2ed3..26bf81b 100644 --- a/generate/generated.go +++ b/generate/generated.go @@ -13,25 +13,25 @@ var _ MyInterface = &MyInterfaceMock{} // MyInterfaceMock is a mock implementation of MyInterface. // -// func TestSomethingThatUsesMyInterface(t *testing.T) { +// func TestSomethingThatUsesMyInterface(t *testing.T) { // -// // make and configure a mocked MyInterface -// mockedMyInterface := &MyInterfaceMock{ -// OneFunc: func() bool { -// panic("mock out the One method") -// }, -// ThreeFunc: func() string { -// panic("mock out the Three method") -// }, -// TwoFunc: func() int { -// panic("mock out the Two method") -// }, -// } +// // make and configure a mocked MyInterface +// mockedMyInterface := &MyInterfaceMock{ +// OneFunc: func() bool { +// panic("mock out the One method") +// }, +// ThreeFunc: func() string { +// panic("mock out the Three method") +// }, +// TwoFunc: func() int { +// panic("mock out the Two method") +// }, +// } // -// // use mockedMyInterface in code that requires MyInterface -// // and then make assertions. +// // use mockedMyInterface in code that requires MyInterface +// // and then make assertions. // -// } +// } type MyInterfaceMock struct { // OneFunc mocks the One method. OneFunc func() bool diff --git a/internal/registry/method_scope.go b/internal/registry/method_scope.go new file mode 100644 index 0000000..59cc312 --- /dev/null +++ b/internal/registry/method_scope.go @@ -0,0 +1,155 @@ +package registry + +import ( + "go/types" + "strconv" +) + +// MethodScope is the sub-registry for allocating variables present in +// the method scope. +// +// It should be created using a registry instance. +type MethodScope struct { + registry *Registry + moqPkgPath string + + vars []*Var + conflicted map[string]bool +} + +// AddVar allocates a variable instance and adds it to the method scope. +// +// Variables names are generated if required and are ensured to be +// without conflict with other variables and imported packages. It also +// adds the relevant imports to the registry for each added variable. +func (m *MethodScope) AddVar(vr *types.Var, suffix string) *Var { + name := vr.Name() + if name == "" || name == "_" { + name = generateVarName(vr.Type()) + } + + name += suffix + + switch name { + case "mock", "callInfo", "break", "default", "func", "interface", "select", "case", "defer", "go", "map", "struct", + "chan", "else", "goto", "package", "switch", "const", "fallthrough", "if", "range", "type", "continue", "for", + "import", "return", "var": + name += "MoqParam" + } + + if _, ok := m.searchVar(name); ok || m.conflicted[name] { + return m.addDisambiguatedVar(vr, name) + } + + return m.addVar(vr, name) +} + +func (m *MethodScope) addDisambiguatedVar(vr *types.Var, suggested string) *Var { + n := 1 + for { + // Keep incrementing the suffix until we find a name which is unused. + if _, ok := m.searchVar(suggested + strconv.Itoa(n)); !ok { + break + } + n++ + } + + name := suggested + strconv.Itoa(n) + if n == 1 { + conflict, _ := m.searchVar(suggested) + conflict.Name += "1" + name = suggested + "2" + m.conflicted[suggested] = true + } + + return m.addVar(vr, name) +} + +func (m *MethodScope) addVar(vr *types.Var, name string) *Var { + imports := make(map[string]*Package) + m.populateImports(vr.Type(), imports) + + v := Var{ + vr: vr, + imports: imports, + moqPkgPath: m.moqPkgPath, + Name: name, + } + m.vars = append(m.vars, &v) + m.resolveImportVarConflicts(&v) + return &v +} + +func (m MethodScope) searchVar(name string) (*Var, bool) { + for _, v := range m.vars { + if v.Name == name { + return v, true + } + } + + return nil, false +} + +// populateImports extracts all the package imports for a given type +// recursively. The imported packages by a single type can be more than +// one (ex: map[a.Type]b.Type). +func (m MethodScope) populateImports(t types.Type, imports map[string]*Package) { + switch t := t.(type) { + case *types.Named: + if pkg := t.Obj().Pkg(); pkg != nil { + imports[stripVendorPath(pkg.Path())] = m.registry.AddImport(pkg) + } + + case *types.Array: + m.populateImports(t.Elem(), imports) + + case *types.Slice: + m.populateImports(t.Elem(), imports) + + case *types.Signature: + for i := 0; i < t.Params().Len(); i++ { + m.populateImports(t.Params().At(i).Type(), imports) + } + for i := 0; i < t.Results().Len(); i++ { + m.populateImports(t.Results().At(i).Type(), imports) + } + + case *types.Map: + m.populateImports(t.Key(), imports) + m.populateImports(t.Elem(), imports) + + case *types.Chan: + m.populateImports(t.Elem(), imports) + + case *types.Pointer: + m.populateImports(t.Elem(), imports) + + case *types.Struct: // anonymous struct + for i := 0; i < t.NumFields(); i++ { + m.populateImports(t.Field(i).Type(), imports) + } + + case *types.Interface: // anonymous interface + for i := 0; i < t.NumExplicitMethods(); i++ { + m.populateImports(t.ExplicitMethod(i).Type(), imports) + } + for i := 0; i < t.NumEmbeddeds(); i++ { + m.populateImports(t.EmbeddedType(i), imports) + } + } +} + +func (m MethodScope) resolveImportVarConflicts(v *Var) { + // Ensure that the newly added var does not conflict with a package import + // which was added earlier. + if _, ok := m.registry.searchImport(v.Name); ok { + v.Name += "MoqParam" + } + // Ensure that all the newly added imports do not conflict with any of the + // existing vars. + for _, imprt := range v.imports { + if v, ok := m.searchVar(imprt.Qualifier()); ok { + v.Name += "MoqParam" + } + } +} diff --git a/internal/registry/package.go b/internal/registry/package.go new file mode 100644 index 0000000..3768242 --- /dev/null +++ b/internal/registry/package.go @@ -0,0 +1,93 @@ +package registry + +import ( + "go/types" + "path" + "strings" +) + +// Package represents an imported package. +type Package struct { + pkg *types.Package + + Alias string +} + +// NewPackage creates a new instance of Package. +func NewPackage(pkg *types.Package) *Package { return &Package{pkg: pkg} } + +// Qualifier returns the qualifier which must be used to refer to types +// declared in the package. +func (p *Package) Qualifier() string { + if p == nil { + return "" + } + + if p.Alias != "" { + return p.Alias + } + + return p.pkg.Name() +} + +// Path is the full package import path (without vendor). +func (p *Package) Path() string { + if p == nil { + return "" + } + + return stripVendorPath(p.pkg.Path()) +} + +var replacer = strings.NewReplacer( + "go-", "", + "-go", "", + "-", "", + "_", "", + ".", "", + "@", "", + "+", "", + "~", "", +) + +// uniqueName generates a unique name for a package by concatenating +// path components. The generated name is guaranteed to unique with an +// appropriate level because the full package import paths themselves +// are unique. +func (p Package) uniqueName(lvl int) string { + pp := strings.Split(p.Path(), "/") + reverse(pp) + + var name string + for i := 0; i < min(len(pp), lvl+1); i++ { + name = strings.ToLower(replacer.Replace(pp[i])) + name + } + + return name +} + +// stripVendorPath strips the vendor dir prefix from a package path. +// For example we might encounter an absolute path like +// github.com/foo/bar/vendor/github.com/pkg/errors which is resolved +// to github.com/pkg/errors. +func stripVendorPath(p string) string { + parts := strings.Split(p, "/vendor/") + if len(parts) == 1 { + return p + } + return strings.TrimLeft(path.Join(parts[1:]...), "/") +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func reverse(a []string) { + for i := len(a)/2 - 1; i >= 0; i-- { + opp := len(a) - 1 - i + a[i], a[opp] = a[opp], a[i] + } +} diff --git a/internal/registry/registry.go b/internal/registry/registry.go new file mode 100644 index 0000000..e017183 --- /dev/null +++ b/internal/registry/registry.go @@ -0,0 +1,190 @@ +package registry + +import ( + "errors" + "fmt" + "go/types" + "path/filepath" + "sort" + "strings" + + "golang.org/x/tools/go/packages" +) + +// Registry encapsulates types information for the source and mock +// destination package. For the mock package, it tracks the list of +// imports and ensures there are no conflicts in the imported package +// qualifiers. +type Registry struct { + srcPkg *packages.Package + moqPkgPath string + aliases map[string]string + imports map[string]*Package +} + +// New loads the source package info and returns a new instance of +// Registry. +func New(srcDir, moqPkg string) (*Registry, error) { + srcPkg, err := pkgInfoFromPath( + srcDir, packages.NeedName|packages.NeedSyntax|packages.NeedTypes|packages.NeedTypesInfo, + ) + if err != nil { + return nil, fmt.Errorf("couldn't load source package: %s", err) + } + + return &Registry{ + srcPkg: srcPkg, + moqPkgPath: findPkgPath(moqPkg, srcPkg), + aliases: parseImportsAliases(srcPkg), + imports: make(map[string]*Package), + }, nil +} + +// SrcPkg returns the types info for the source package. +func (r Registry) SrcPkg() *types.Package { + return r.srcPkg.Types +} + +// SrcPkgName returns the name of the source package. +func (r Registry) SrcPkgName() string { + return r.srcPkg.Name +} + +// LookupInterface returns the underlying interface definition of the +// given interface name. +func (r Registry) LookupInterface(name string) (*types.Interface, error) { + obj := r.SrcPkg().Scope().Lookup(name) + if obj == nil { + return nil, fmt.Errorf("interface not found: %s", name) + } + + if !types.IsInterface(obj.Type()) { + return nil, fmt.Errorf("%s (%s) is not an interface", name, obj.Type()) + } + + return obj.Type().Underlying().(*types.Interface).Complete(), nil +} + +// MethodScope returns a new MethodScope. +func (r *Registry) MethodScope() *MethodScope { + return &MethodScope{ + registry: r, + moqPkgPath: r.moqPkgPath, + conflicted: map[string]bool{}, + } +} + +// AddImport adds the given package to the set of imports. It generates a +// suitable alias if there are any conflicts with previously imported +// packages. +func (r *Registry) AddImport(pkg *types.Package) *Package { + path := stripVendorPath(pkg.Path()) + if path == r.moqPkgPath { + return nil + } + + if imprt, ok := r.imports[path]; ok { + return imprt + } + + imprt := Package{pkg: pkg, Alias: r.aliases[path]} + + if conflict, ok := r.searchImport(imprt.Qualifier()); ok { + resolveImportConflict(&imprt, conflict, 0) + } + + r.imports[path] = &imprt + return &imprt +} + +// Imports returns the list of imported packages. The list is sorted by +// path. +func (r Registry) Imports() []*Package { + imports := make([]*Package, 0, len(r.imports)) + for _, imprt := range r.imports { + imports = append(imports, imprt) + } + sort.Slice(imports, func(i, j int) bool { + return imports[i].Path() < imports[j].Path() + }) + return imports +} + +func (r Registry) searchImport(name string) (*Package, bool) { + for _, imprt := range r.imports { + if imprt.Qualifier() == name { + return imprt, true + } + } + + return nil, false +} + +func pkgInfoFromPath(srcDir string, mode packages.LoadMode) (*packages.Package, error) { + pkgs, err := packages.Load(&packages.Config{ + Mode: mode, + Dir: srcDir, + }) + if err != nil { + return nil, err + } + if len(pkgs) == 0 { + return nil, errors.New("package not found") + } + if len(pkgs) > 1 { + return nil, errors.New("found more than one package") + } + if errs := pkgs[0].Errors; len(errs) != 0 { + if len(errs) == 1 { + return nil, errs[0] + } + return nil, fmt.Errorf("%s (and %d more errors)", errs[0], len(errs)-1) + } + return pkgs[0], nil +} + +func findPkgPath(pkgInputVal string, srcPkg *packages.Package) string { + if pkgInputVal == "" { + return srcPkg.PkgPath + } + if pkgInDir(srcPkg.PkgPath, pkgInputVal) { + return srcPkg.PkgPath + } + subdirectoryPath := filepath.Join(srcPkg.PkgPath, pkgInputVal) + if pkgInDir(subdirectoryPath, pkgInputVal) { + return subdirectoryPath + } + return "" +} + +func pkgInDir(pkgName, dir string) bool { + currentPkg, err := pkgInfoFromPath(dir, packages.NeedName) + if err != nil { + return false + } + return currentPkg.Name == pkgName || currentPkg.Name+"_test" == pkgName +} + +func parseImportsAliases(pkg *packages.Package) map[string]string { + aliases := make(map[string]string) + for _, syntax := range pkg.Syntax { + for _, imprt := range syntax.Imports { + if imprt.Name != nil && imprt.Name.Name != "." { + aliases[strings.Trim(imprt.Path.Value, `"`)] = imprt.Name.Name + } + } + } + return aliases +} + +// resolveImportConflict generates and assigns a unique alias for +// packages with conflicting qualifiers. +func resolveImportConflict(a, b *Package, lvl int) { + u1, u2 := a.uniqueName(lvl), b.uniqueName(lvl) + if u1 != u2 { + a.Alias, b.Alias = u1, u2 + return + } + + resolveImportConflict(a, b, lvl+1) +} diff --git a/internal/registry/var.go b/internal/registry/var.go new file mode 100644 index 0000000..f311735 --- /dev/null +++ b/internal/registry/var.go @@ -0,0 +1,123 @@ +package registry + +import ( + "go/types" + "strings" +) + +// Var represents a method variable/parameter. +// +// It should be created using a method scope instance. +type Var struct { + vr *types.Var + imports map[string]*Package + moqPkgPath string + + Name string +} + +// IsSlice returns whether the type (or the underlying type) is a slice. +func (v Var) IsSlice() bool { + _, ok := v.vr.Type().Underlying().(*types.Slice) + return ok +} + +// TypeString returns the variable type with the package qualifier in the +// format 'pkg.Type'. +func (v Var) TypeString() string { + return types.TypeString(v.vr.Type(), v.packageQualifier) +} + +// packageQualifier is a types.Qualifier. +func (v Var) packageQualifier(pkg *types.Package) string { + path := stripVendorPath(pkg.Path()) + if v.moqPkgPath != "" && v.moqPkgPath == path { + return "" + } + + return v.imports[path].Qualifier() +} + +// generateVarName generates a name for the variable using the type +// information. +// +// Examples: +// - string -> s +// - int -> n +// - chan int -> intCh +// - []a.MyType -> myTypes +// - map[string]int -> stringToInt +// - error -> err +// - a.MyType -> myType +func generateVarName(t types.Type) string { + nestedType := func(t types.Type) string { + if t, ok := t.(*types.Basic); ok { + return deCapitalise(t.String()) + } + return generateVarName(t) + } + + switch t := t.(type) { + case *types.Named: + if t.Obj().Name() == "error" { + return "err" + } + + name := deCapitalise(t.Obj().Name()) + if name == t.Obj().Name() { + name += "MoqParam" + } + + return name + + case *types.Basic: + return basicTypeVarName(t) + + case *types.Array: + return nestedType(t.Elem()) + "s" + + case *types.Slice: + return nestedType(t.Elem()) + "s" + + case *types.Struct: // anonymous struct + return "val" + + case *types.Pointer: + return generateVarName(t.Elem()) + + case *types.Signature: + return "fn" + + case *types.Interface: // anonymous interface + return "ifaceVal" + + case *types.Map: + return nestedType(t.Key()) + "To" + capitalise(nestedType(t.Elem())) + + case *types.Chan: + return nestedType(t.Elem()) + "Ch" + } + + return "v" +} + +func basicTypeVarName(b *types.Basic) string { + switch b.Info() { + case types.IsBoolean: + return "b" + + case types.IsInteger: + return "n" + + case types.IsFloat: + return "f" + + case types.IsString: + return "s" + } + + return "v" +} + +func capitalise(s string) string { return strings.ToUpper(s[:1]) + s[1:] } +func deCapitalise(s string) string { return strings.ToLower(s[:1]) + s[1:] } diff --git a/internal/template/template.go b/internal/template/template.go new file mode 100644 index 0000000..22bb8e8 --- /dev/null +++ b/internal/template/template.go @@ -0,0 +1,190 @@ +package template + +import ( + "io" + "strings" + "text/template" + + "github.com/matryer/moq/internal/registry" +) + +// Template is the Moq template. It is capable of generating the Moq +// implementation for the given template.Data. +type Template struct { + tmpl *template.Template +} + +// New returns a new instance of Template. +func New() (Template, error) { + tmpl, err := template.New("moq").Funcs(templateFuncs).Parse(moqTemplate) + if err != nil { + return Template{}, err + } + + return Template{tmpl: tmpl}, nil +} + +// Execute generates and writes the Moq implementation for the given +// data. +func (t Template) Execute(w io.Writer, data Data) error { + return t.tmpl.Execute(w, data) +} + +// moqTemplate is the template for mocked code. +// language=GoTemplate +var moqTemplate = `// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package {{.PkgName}} + +import ( +{{- range .Imports}} + {{. | ImportStatement}} +{{- end}} +) + +{{range $i, $mock := .Mocks -}} + +{{- if not $.SkipEnsure -}} +// Ensure, that {{.MockName}} does implement {{$.SrcPkgQualifier}}{{.InterfaceName}}. +// If this is not the case, regenerate this file with moq. +var _ {{$.SrcPkgQualifier}}{{.InterfaceName}} = &{{.MockName}}{} +{{- end}} + +// {{.MockName}} is a mock implementation of {{$.SrcPkgQualifier}}{{.InterfaceName}}. +// +// func TestSomethingThatUses{{.InterfaceName}}(t *testing.T) { +// +// // make and configure a mocked {{$.SrcPkgQualifier}}{{.InterfaceName}} +// mocked{{.InterfaceName}} := &{{.MockName}}{ + {{- range .Methods}} +// {{.Name}}Func: func({{.ArgList}}) {{.ReturnArgTypeList}} { +// panic("mock out the {{.Name}} method") +// }, + {{- end}} +// } +// +// // use mocked{{.InterfaceName}} in code that requires {{$.SrcPkgQualifier}}{{.InterfaceName}} +// // and then make assertions. +// +// } +type {{.MockName}} struct { +{{- range .Methods}} + // {{.Name}}Func mocks the {{.Name}} method. + {{.Name}}Func func({{.ArgList}}) {{.ReturnArgTypeList}} +{{end}} + // calls tracks calls to the methods. + calls struct { +{{- range .Methods}} + // {{.Name}} holds details about calls to the {{.Name}} method. + {{.Name}} []struct { + {{- range .Params}} + // {{.Name | Exported}} is the {{.Name}} argument value. + {{.Name | Exported}} {{.TypeString}} + {{- end}} + } +{{- end}} + } +{{- range .Methods}} + lock{{.Name}} {{$.Imports | SyncPkgQualifier}}.RWMutex +{{- end}} +} +{{range .Methods}} +// {{.Name}} calls {{.Name}}Func. +func (mock *{{$mock.MockName}}) {{.Name}}({{.ArgList}}) {{.ReturnArgTypeList}} { +{{- if not $.StubImpl}} + if mock.{{.Name}}Func == nil { + panic("{{$mock.MockName}}.{{.Name}}Func: method is nil but {{$mock.InterfaceName}}.{{.Name}} was just called") + } +{{- end}} + callInfo := struct { + {{- range .Params}} + {{.Name | Exported}} {{.TypeString}} + {{- end}} + }{ + {{- range .Params}} + {{.Name | Exported}}: {{.Name}}, + {{- end}} + } + mock.lock{{.Name}}.Lock() + mock.calls.{{.Name}} = append(mock.calls.{{.Name}}, callInfo) + mock.lock{{.Name}}.Unlock() +{{- if .Returns}} + {{- if $.StubImpl}} + if mock.{{.Name}}Func == nil { + var ( + {{- range .Returns}} + {{.Name}} {{.TypeString}} + {{- end}} + ) + return {{.ReturnArgNameList}} + } + {{- end}} + return mock.{{.Name}}Func({{.ArgCallList}}) +{{- else}} + {{- if $.StubImpl}} + if mock.{{.Name}}Func == nil { + return + } + {{- end}} + mock.{{.Name}}Func({{.ArgCallList}}) +{{- end}} +} + +// {{.Name}}Calls gets all the calls that were made to {{.Name}}. +// Check the length with: +// len(mocked{{$mock.InterfaceName}}.{{.Name}}Calls()) +func (mock *{{$mock.MockName}}) {{.Name}}Calls() []struct { + {{- range .Params}} + {{.Name | Exported}} {{.TypeString}} + {{- end}} + } { + var calls []struct { + {{- range .Params}} + {{.Name | Exported}} {{.TypeString}} + {{- end}} + } + mock.lock{{.Name}}.RLock() + calls = mock.calls.{{.Name}} + mock.lock{{.Name}}.RUnlock() + return calls +} +{{end -}} +{{end -}}` + +// This list comes from the golint codebase. Golint will complain about any of +// these being mixed-case, like "Id" instead of "ID". +var golintInitialisms = []string{ + "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "LHS", + "QPS", "RAM", "RHS", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "UID", "UUID", "URI", + "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", +} + +var templateFuncs = template.FuncMap{ + "ImportStatement": func(imprt *registry.Package) string { + if imprt.Alias == "" { + return `"` + imprt.Path() + `"` + } + return imprt.Alias + ` "` + imprt.Path() + `"` + }, + "SyncPkgQualifier": func(imports []*registry.Package) string { + for _, imprt := range imports { + if imprt.Path() == "sync" { + return imprt.Qualifier() + } + } + + return "sync" + }, + "Exported": func(s string) string { + if s == "" { + return "" + } + for _, initialism := range golintInitialisms { + if strings.ToUpper(s) == initialism { + return initialism + } + } + return strings.ToUpper(s[0:1]) + s[1:] + }, +} diff --git a/internal/template/template_data.go b/internal/template/template_data.go new file mode 100644 index 0000000..0a95bb5 --- /dev/null +++ b/internal/template/template_data.go @@ -0,0 +1,125 @@ +package template + +import ( + "fmt" + "strings" + + "github.com/matryer/moq/internal/registry" +) + +// Data is the template data used to render the Moq template. +type Data struct { + PkgName string + SrcPkgQualifier string + Imports []*registry.Package + Mocks []MockData + StubImpl bool + SkipEnsure bool +} + +// MocksSomeMethod returns true of any one of the Mocks has at least 1 +// method. +func (d Data) MocksSomeMethod() bool { + for _, m := range d.Mocks { + if len(m.Methods) > 0 { + return true + } + } + + return false +} + +// MockData is the data used to generate a mock for some interface. +type MockData struct { + InterfaceName string + MockName string + Methods []MethodData +} + +// MethodData is the data which represents a method on some interface. +type MethodData struct { + Name string + Params []ParamData + Returns []ParamData +} + +// ArgList is the string representation of method parameters, ex: +// 's string, n int, foo bar.Baz'. +func (m MethodData) ArgList() string { + params := make([]string, len(m.Params)) + for i, p := range m.Params { + params[i] = p.MethodArg() + } + return strings.Join(params, ", ") +} + +// ArgCallList is the string representation of method call parameters, +// ex: 's, n, foo'. In case of a last variadic parameter, it will be of +// the format 's, n, foos...' +func (m MethodData) ArgCallList() string { + params := make([]string, len(m.Params)) + for i, p := range m.Params { + params[i] = p.CallName() + } + return strings.Join(params, ", ") +} + +// ReturnArgTypeList is the string representation of method return +// types, ex: 'bar.Baz', '(string, error)'. +func (m MethodData) ReturnArgTypeList() 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, ", ") +} + +// ReturnArgNameList is the string representation of values being +// returned from the method, ex: 'foo', 's, err'. +func (m MethodData) ReturnArgNameList() string { + params := make([]string, len(m.Returns)) + for i, p := range m.Returns { + params[i] = p.Name() + } + return strings.Join(params, ", ") +} + +// ParamData is the data which represents a parameter to some method of +// an interface. +type ParamData struct { + Var *registry.Var + Variadic bool +} + +// Name returns the name of the parameter. +func (p ParamData) Name() string { + return p.Var.Name +} + +// MethodArg is the representation of the parameter in the function +// signature, ex: 'name a.Type'. +func (p ParamData) MethodArg() string { + if p.Variadic { + return fmt.Sprintf("%s ...%s", p.Name(), p.TypeString()[2:]) + } + return fmt.Sprintf("%s %s", p.Name(), p.TypeString()) +} + +// CallName returns the string representation of the parameter to be +// used for a method call. For a variadic paramter, it will be of the +// format 'foos...'. +func (p ParamData) CallName() string { + if p.Variadic { + return p.Name() + "..." + } + return p.Name() +} + +// TypeString returns the string representation of the type of the +// parameter. +func (p ParamData) TypeString() string { + return p.Var.TypeString() +} diff --git a/internal/template/template_test.go b/internal/template/template_test.go new file mode 100644 index 0000000..e977d48 --- /dev/null +++ b/internal/template/template_test.go @@ -0,0 +1,55 @@ +package template + +import ( + "go/types" + "testing" + + "github.com/matryer/moq/internal/registry" +) + +func TestTemplateFuncs(t *testing.T) { + t.Run("Exported", func(t *testing.T) { + f := templateFuncs["Exported"].(func(string) string) + if f("") != "" { + t.Errorf("Exported(...) want: ``; got: `%s`", f("")) + } + if f("var") != "Var" { + t.Errorf("Exported(...) want: `Var`; got: `%s`", f("var")) + } + }) + + t.Run("ImportStatement", func(t *testing.T) { + f := templateFuncs["ImportStatement"].(func(*registry.Package) string) + pkg := registry.NewPackage(types.NewPackage("xyz", "xyz")) + if f(pkg) != `"xyz"` { + t.Errorf("ImportStatement(...): want: `\"xyz\"`; got: `%s`", f(pkg)) + } + + pkg.Alias = "x" + if f(pkg) != `x "xyz"` { + t.Errorf("ImportStatement(...): want: `x \"xyz\"`; got: `%s`", f(pkg)) + } + }) + + t.Run("SyncPkgQualifier", func(t *testing.T) { + f := templateFuncs["SyncPkgQualifier"].(func([]*registry.Package) string) + if f(nil) != "sync" { + t.Errorf("SyncPkgQualifier(...): want: `sync`; got: `%s`", f(nil)) + } + imports := []*registry.Package{ + registry.NewPackage(types.NewPackage("sync", "sync")), + registry.NewPackage(types.NewPackage("github.com/some/module", "module")), + } + if f(imports) != "sync" { + t.Errorf("SyncPkgQualifier(...): want: `sync`; got: `%s`", f(imports)) + } + + syncPkg := registry.NewPackage(types.NewPackage("sync", "sync")) + syncPkg.Alias = "stdsync" + otherSyncPkg := registry.NewPackage(types.NewPackage("github.com/someother/sync", "sync")) + imports = []*registry.Package{otherSyncPkg, syncPkg} + if f(imports) != "stdsync" { + t.Errorf("SyncPkgQualifier(...): want: `stdsync`; got: `%s`", f(imports)) + } + }) +} diff --git a/pkg/moq/formatter.go b/pkg/moq/formatter.go index 61d8c31..6154561 100644 --- a/pkg/moq/formatter.go +++ b/pkg/moq/formatter.go @@ -29,5 +29,3 @@ func gofmt(src []byte) ([]byte, error) { return formatted, nil } - -func noopFmt(src []byte) ([]byte, error) { return src, nil } diff --git a/pkg/moq/golint_initialisms.go b/pkg/moq/golint_initialisms.go deleted file mode 100644 index 1253f87..0000000 --- a/pkg/moq/golint_initialisms.go +++ /dev/null @@ -1,44 +0,0 @@ -package moq - -// This list comes from the golint codebase. Golint will complain about any of -// these being mixed-case, like "Id" instead of "ID". -var golintInitialisms = []string{ - "ACL", - "API", - "ASCII", - "CPU", - "CSS", - "DNS", - "EOF", - "GUID", - "HTML", - "HTTP", - "HTTPS", - "ID", - "IP", - "JSON", - "LHS", - "QPS", - "RAM", - "RHS", - "RPC", - "SLA", - "SMTP", - "SQL", - "SSH", - "TCP", - "TLS", - "TTL", - "UDP", - "UI", - "UID", - "UUID", - "URI", - "URL", - "UTF8", - "VM", - "XML", - "XMPP", - "XSRF", - "XSS", -} diff --git a/pkg/moq/moq.go b/pkg/moq/moq.go index cc07fd1..3029a0c 100644 --- a/pkg/moq/moq.go +++ b/pkg/moq/moq.go @@ -3,31 +3,20 @@ package moq import ( "bytes" "errors" - "fmt" - "go/build" "go/types" "io" - "os" - "path" - "path/filepath" - "strconv" "strings" - "text/template" - "golang.org/x/tools/go/packages" + "github.com/matryer/moq/internal/registry" + "github.com/matryer/moq/internal/template" ) // Mocker can generate mock structs. type Mocker struct { - srcPkg *packages.Package - tmpl *template.Template - pkgName string - pkgPath string - fmter func(src []byte) ([]byte, error) - stubImpl bool - skipEnsure bool + cfg Config - imports map[string]bool + registry *registry.Registry + tmpl template.Template } // Config specifies details about how interfaces should be mocked. @@ -41,331 +30,142 @@ type Config struct { } // New makes a new Mocker for the specified package directory. -func New(conf Config) (*Mocker, error) { - srcPkg, err := pkgInfoFromPath(conf.SrcDir, packages.NeedName|packages.NeedTypes|packages.NeedTypesInfo) +func New(cfg Config) (*Mocker, error) { + reg, err := registry.New(cfg.SrcDir, cfg.PkgName) if err != nil { - return nil, fmt.Errorf("couldn't load source package: %s", err) - } - - pkgName := conf.PkgName - if pkgName == "" { - pkgName = srcPkg.Name - } - - pkgPath, err := findPkgPath(conf.PkgName, srcPkg) - if err != nil { - return nil, fmt.Errorf("couldn't load mock package: %s", err) + return nil, err } - tmpl, err := template.New("moq").Funcs(templateFuncs).Parse(moqTemplate) + tmpl, err := template.New() if err != nil { return nil, err } - fmter := gofmt - switch conf.Formatter { - case "goimports": - fmter = goimports - case "noop": - fmter = noopFmt - } - return &Mocker{ - tmpl: tmpl, - srcPkg: srcPkg, - pkgName: pkgName, - pkgPath: pkgPath, - fmter: fmter, - stubImpl: conf.StubImpl, - skipEnsure: conf.SkipEnsure, - imports: make(map[string]bool), + cfg: cfg, + registry: reg, + tmpl: tmpl, }, nil } -func findPkgPath(pkgInputVal string, srcPkg *packages.Package) (string, error) { - if pkgInputVal == "" { - return srcPkg.PkgPath, nil - } - if pkgInDir(".", pkgInputVal) { - return ".", nil - } - if pkgInDir(srcPkg.PkgPath, pkgInputVal) { - return srcPkg.PkgPath, nil - } - subdirectoryPath := filepath.Join(srcPkg.PkgPath, pkgInputVal) - if pkgInDir(subdirectoryPath, pkgInputVal) { - return subdirectoryPath, nil - } - return "", nil -} - -func pkgInDir(pkgName, dir string) bool { - currentPkg, err := pkgInfoFromPath(dir, packages.NeedName) - if err != nil { - return false - } - return currentPkg.Name == pkgName || currentPkg.Name+"_test" == pkgName -} - // Mock generates a mock for the specified interface name. -func (m *Mocker) Mock(w io.Writer, names ...string) error { - if len(names) == 0 { +func (m *Mocker) Mock(w io.Writer, namePairs ...string) error { + if len(namePairs) == 0 { return errors.New("must specify one interface") } - doc := doc{ - PackageName: m.pkgName, - Imports: moqImports, - StubImpl: m.stubImpl, - SkipEnsure: m.skipEnsure, - } - - mocksMethods := false - - tpkg := m.srcPkg.Types - for _, name := range names { - n, mockName := parseInterfaceName(name) - iface := tpkg.Scope().Lookup(n) - if iface == nil { - return fmt.Errorf("cannot find interface %s", n) + mocks := make([]template.MockData, len(namePairs)) + for i, np := range namePairs { + name, mockName := parseInterfaceName(np) + iface, err := m.registry.LookupInterface(name) + if err != nil { + return err } - if !types.IsInterface(iface.Type()) { - return fmt.Errorf("%s (%s) not an interface", n, iface.Type().String()) + + methods := make([]template.MethodData, iface.NumMethods()) + for j := 0; j < iface.NumMethods(); j++ { + methods[j] = m.methodData(iface.Method(j)) } - iiface := iface.Type().Underlying().(*types.Interface).Complete() - obj := obj{ - InterfaceName: n, + + mocks[i] = template.MockData{ + InterfaceName: name, MockName: mockName, + Methods: methods, } - for i := 0; i < iiface.NumMethods(); i++ { - mocksMethods = true - meth := iiface.Method(i) - sig := meth.Type().(*types.Signature) - method := &method{ - Name: meth.Name(), - } - obj.Methods = append(obj.Methods, method) - method.Params, method.Returns = m.extractArgs(sig) - } - doc.Objects = append(doc.Objects, obj) } - if mocksMethods { - doc.Imports = append(doc.Imports, "sync") + data := template.Data{ + PkgName: m.mockPkgName(), + Mocks: mocks, + StubImpl: m.cfg.StubImpl, + SkipEnsure: m.cfg.SkipEnsure, } - for pkgToImport := range m.imports { - doc.Imports = append(doc.Imports, stripVendorPath(pkgToImport)) + if data.MocksSomeMethod() { + m.registry.AddImport(types.NewPackage("sync", "sync")) } - - if tpkg.Name() != m.pkgName { - doc.SourcePackagePrefix = tpkg.Name() + "." - doc.Imports = append(doc.Imports, stripVendorPath(tpkg.Path())) + if m.registry.SrcPkgName() != m.mockPkgName() { + data.SrcPkgQualifier = m.registry.SrcPkgName() + "." + if !m.cfg.SkipEnsure { + imprt := m.registry.AddImport(m.registry.SrcPkg()) + data.SrcPkgQualifier = imprt.Qualifier() + "." + } } + data.Imports = m.registry.Imports() + var buf bytes.Buffer - err := m.tmpl.Execute(&buf, doc) - if err != nil { + if err := m.tmpl.Execute(&buf, data); err != nil { return err } - formatted, err := m.fmter(buf.Bytes()) + + formatted, err := m.format(buf.Bytes()) if err != nil { return err } + if _, err := w.Write(formatted); err != nil { return err } return nil } -func (m *Mocker) packageQualifier(pkg *types.Package) string { - if m.pkgPath != "" && m.pkgPath == pkg.Path() { - return "" - } - path := pkg.Path() - if pkg.Path() == "." { - wd, err := os.Getwd() - if err == nil { - path = stripGopath(wd) - } - } - m.imports[path] = true - return pkg.Name() -} - -func (m *Mocker) extractArgs(sig *types.Signature) (params, results []*param) { - pp := sig.Params() - for i := 0; i < pp.Len(); i++ { - p := m.buildParam(pp.At(i), "in"+strconv.Itoa(i+1)) - // check for final variadic argument - p.Variadic = sig.Variadic() && i == pp.Len()-1 && p.Type[0:2] == "[]" - params = append(params, p) - } +func (m *Mocker) methodData(f *types.Func) template.MethodData { + sig := f.Type().(*types.Signature) - rr := sig.Results() - for i := 0; i < rr.Len(); i++ { - results = append(results, m.buildParam(rr.At(i), "out"+strconv.Itoa(i+1))) - } - - return -} - -func (m *Mocker) buildParam(v *types.Var, fallbackName string) *param { - name := v.Name() - if name == "" || name == "_" { - name = fallbackName - } - typ := types.TypeString(v.Type(), m.packageQualifier) - return ¶m{Name: name, Type: typ} -} - -func pkgInfoFromPath(srcDir string, mode packages.LoadMode) (*packages.Package, error) { - pkgs, err := packages.Load(&packages.Config{ - Mode: mode, - Dir: srcDir, - }) - if err != nil { - return nil, err - } - if len(pkgs) == 0 { - return nil, errors.New("No packages found") - } - if len(pkgs) > 1 { - return nil, errors.New("More than one package was found") - } - if errs := pkgs[0].Errors; len(errs) != 0 { - if len(errs) == 1 { - return nil, errs[0] + scope := m.registry.MethodScope() + n := sig.Params().Len() + params := make([]template.ParamData, n) + for i := 0; i < n; i++ { + p := template.ParamData{ + Var: scope.AddVar(sig.Params().At(i), ""), } - return nil, fmt.Errorf("%s (and %d more errors)", errs[0], len(errs)-1) - } - return pkgs[0], nil -} - -func parseInterfaceName(name string) (ifaceName, mockName string) { - parts := strings.SplitN(name, ":", 2) - ifaceName = parts[0] - mockName = ifaceName + "Mock" - if len(parts) == 2 { - mockName = parts[1] - } - return -} - -type doc struct { - PackageName string - SourcePackagePrefix string - Objects []obj - Imports []string - StubImpl bool - SkipEnsure bool -} - -type obj struct { - InterfaceName string - MockName string - Methods []*method -} -type method struct { - Name string - Params []*param - Returns []*param -} + p.Variadic = sig.Variadic() && i == n-1 && p.Var.IsSlice() // check for final variadic argument -func (m *method) Arglist() string { - params := make([]string, len(m.Params)) - for i, p := range m.Params { - params[i] = p.String() + params[i] = p } - return strings.Join(params, ", ") -} -func (m *method) ArgCallList() string { - params := make([]string, len(m.Params)) - for i, p := range m.Params { - params[i] = p.CallName() + n = sig.Results().Len() + results := make([]template.ParamData, n) + for i := 0; i < n; i++ { + results[i] = template.ParamData{ + Var: scope.AddVar(sig.Results().At(i), "Out"), + } } - return strings.Join(params, ", ") -} -func (m *method) ReturnArgTypeList() 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 template.MethodData{ + Name: f.Name(), + Params: params, + Returns: results, } - return strings.Join(params, ", ") } -func (m *method) ReturnArgNameList() string { - params := make([]string, len(m.Returns)) - for i, p := range m.Returns { - params[i] = p.Name +func (m *Mocker) mockPkgName() string { + if m.cfg.PkgName != "" { + return m.cfg.PkgName } - return strings.Join(params, ", ") -} - -type param struct { - Name string - Type string - Variadic bool -} -func (p param) String() string { - return fmt.Sprintf("%s %s", p.Name, p.TypeString()) + return m.registry.SrcPkgName() } -func (p param) CallName() string { - if p.Variadic { - return p.Name + "..." - } - return p.Name -} +func (m *Mocker) format(src []byte) ([]byte, error) { + switch m.cfg.Formatter { + case "goimports": + return goimports(src) -func (p param) TypeString() string { - if p.Variadic { - return "..." + p.Type[2:] + case "noop": + return src, nil } - return p.Type -} -var templateFuncs = template.FuncMap{ - "Exported": func(s string) string { - if s == "" { - return "" - } - for _, initialism := range golintInitialisms { - if strings.ToUpper(s) == initialism { - return initialism - } - } - return strings.ToUpper(s[0:1]) + s[1:] - }, + return gofmt(src) } -// stripVendorPath strips the vendor dir prefix from a package path. -// For example we might encounter an absolute path like -// github.com/foo/bar/vendor/github.com/pkg/errors which is resolved -// to github.com/pkg/errors. -func stripVendorPath(p string) string { - parts := strings.Split(p, "/vendor/") - if len(parts) == 1 { - return p +func parseInterfaceName(namePair string) (ifaceName, mockName string) { + parts := strings.SplitN(namePair, ":", 2) + if len(parts) == 2 { + return parts[0], parts[1] } - return strings.TrimLeft(path.Join(parts[1:]...), "/") -} -// stripGopath takes the directory to a package and removes the -// $GOPATH/src path to get the canonical package name. -func stripGopath(p string) string { - for _, srcDir := range build.Default.SrcDirs() { - rel, err := filepath.Rel(srcDir, p) - if err != nil || strings.HasPrefix(rel, "..") { - continue - } - return filepath.ToSlash(rel) - } - return p + ifaceName = parts[0] + return ifaceName, ifaceName + "Mock" } diff --git a/pkg/moq/moq_test.go b/pkg/moq/moq_test.go index f052ab8..3878ce3 100644 --- a/pkg/moq/moq_test.go +++ b/pkg/moq/moq_test.go @@ -246,45 +246,6 @@ func TestVariadicArguments(t *testing.T) { } } -// TestSliceResult tests to ensure slice return data type works as -// expected. -// see https://github.com/matryer/moq/issues/124 -func TestSliceResult(t *testing.T) { - m, err := New(Config{SrcDir: "testpackages/variadic"}) - if err != nil { - t.Fatalf("moq.New: %s", err) - } - - var buf bytes.Buffer - if err = m.Mock(&buf, "Echoer"); err != nil { - t.Errorf("m.Mock: %s", err) - } - - golden := filepath.Join("testpackages/variadic", "echoer.golden.go") - if err := matchGoldenFile(golden, buf.Bytes()); err != nil { - t.Errorf("check golden file: %s", err) - } -} - -// TestBlankID tests generation of mock where a method on the interface -// uses a blank identifier. -// See https://github.com/matryer/moq/issues/70 -func TestBlankID(t *testing.T) { - m, err := New(Config{SrcDir: "testpackages/blankid"}) - if err != nil { - t.Fatalf("moq.New: %s", err) - } - - var buf bytes.Buffer - if err = m.Mock(&buf, "Swallower"); err != nil { - t.Errorf("m.Mock: %s", err) - } - golden := filepath.Join("testpackages/blankid", "swallower.golden.go") - if err := matchGoldenFile(golden, buf.Bytes()); err != nil { - t.Errorf("check golden file: %s", err) - } -} - func TestNothingToReturn(t *testing.T) { m, err := New(Config{SrcDir: "testpackages/example"}) if err != nil { @@ -310,23 +271,6 @@ func TestNothingToReturn(t *testing.T) { } } -func TestChannelNames(t *testing.T) { - m, err := New(Config{SrcDir: "testpackages/channels", StubImpl: true}) - if err != nil { - t.Fatalf("moq.New: %s", err) - } - - var buf bytes.Buffer - if err = m.Mock(&buf, "Queuer"); err != nil { - t.Errorf("m.Mock: %s", err) - } - - golden := filepath.Join("testpackages/channels", "queuer_moq.golden.go") - if err := matchGoldenFile(golden, buf.Bytes()); err != nil { - t.Errorf("check golden file: %s", err) - } -} - func TestImports(t *testing.T) { m, err := New(Config{SrcDir: "testpackages/imports/two"}) if err != nil { @@ -352,6 +296,101 @@ func TestImports(t *testing.T) { } } +func TestMockGolden(t *testing.T) { + cases := []struct { + name string + cfg Config + interfaces []string + goldenFile string + }{ + { + // Tests to ensure slice return data type works as expected. + // See https://github.com/matryer/moq/issues/124 + name: "SliceResult", + cfg: Config{SrcDir: "testpackages/variadic"}, + interfaces: []string{"Echoer"}, + goldenFile: filepath.Join("testpackages/variadic", "echoer.golden.go"), + }, + { + // Tests generation of mock where a method on the interface uses a + // blank identifier. + // See https://github.com/matryer/moq/issues/70 + name: "BlankID", + cfg: Config{SrcDir: "testpackages/blankid"}, + interfaces: []string{"Swallower"}, + goldenFile: filepath.Join("testpackages/blankid", "swallower.golden.go"), + }, + { + name: "ChannelNames", + cfg: Config{SrcDir: "testpackages/channels", StubImpl: true}, + interfaces: []string{"Queuer"}, + goldenFile: filepath.Join("testpackages/channels", "queuer_moq.golden.go"), + }, + { + // Tests generation of mock when the interface imports a different + // package by the same name as it's own. + // See https://github.com/matryer/moq/issues/94 + name: "PkgShadow", + cfg: Config{SrcDir: "testpackages/shadow/http", PkgName: "mock"}, + interfaces: []string{"Thing"}, + goldenFile: filepath.Join("testpackages/shadow/mock", "thing_moq.golden.go"), + }, + { + // Tests generation of mock when a method parameter shadows an + // imported package name. + name: "ParamShadow", + cfg: Config{SrcDir: "testpackages/shadow"}, + interfaces: []string{"Shadower"}, + goldenFile: filepath.Join("testpackages/shadow", "shadower_moq.golden.go"), + }, + { + name: "ImportAlias", + cfg: Config{SrcDir: "testpackages/importalias"}, + interfaces: []string{"MiddleMan"}, + goldenFile: filepath.Join("testpackages/importalias", "middleman_moq.golden.go"), + }, + { + // Tests conflict resolution for generated names of method + // parameters. + name: "ParamNameConflict", + cfg: Config{SrcDir: "testpackages/paramconflict"}, + interfaces: []string{"Interface"}, + goldenFile: filepath.Join("testpackages/paramconflict", "iface_moq.golden.go"), + }, + { + // Tests generation of names for unnamed method parameters. + name: "GenerateParamNames", + cfg: Config{SrcDir: "testpackages/genparamname"}, + interfaces: []string{"Interface"}, + goldenFile: filepath.Join("testpackages/genparamname", "iface_moq.golden.go"), + }, + { + name: "SyncImport", + cfg: Config{SrcDir: "testpackages/syncimport"}, + interfaces: []string{"Syncer"}, + goldenFile: filepath.Join("testpackages/syncimport", "syncer_moq.golden.go"), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + m, err := New(tc.cfg) + if err != nil { + t.Fatalf("moq.New: %s", err) + } + + var buf bytes.Buffer + if err = m.Mock(&buf, tc.interfaces...); err != nil { + t.Errorf("m.Mock: %s", err) + return + } + + if err := matchGoldenFile(tc.goldenFile, buf.Bytes()); err != nil { + t.Errorf("check golden file: %s", err) + } + }) + } +} + func TestFormatter(t *testing.T) { cases := []struct { name string @@ -419,13 +458,6 @@ func matchGoldenFile(goldenFile string, actual []byte) error { return nil } -func TestTemplateFuncs(t *testing.T) { - fn := templateFuncs["Exported"].(func(string) string) - if fn("var") != "Var" { - t.Errorf("exported didn't work: %s", fn("var")) - } -} - func TestVendoredPackages(t *testing.T) { m, err := New(Config{SrcDir: "testpackages/vendoring/user"}) if err != nil { @@ -601,6 +633,41 @@ func TestParseError(t *testing.T) { } } +func TestMockError(t *testing.T) { + m, err := New(Config{SrcDir: "testpackages/example"}) + if err != nil { + t.Fatalf("moq.New: %s", err) + } + cases := []struct { + name string + namePair string + wantErr string + }{ + { + name: "TypeNotFound", + namePair: "DoesNotExist", + wantErr: "interface not found: DoesNotExist", + }, + { + name: "UnexpectedType", + namePair: "Person", + wantErr: "Person (github.com/matryer/moq/pkg/moq/testpackages/example.Person) is not an interface", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := m.Mock(ioutil.Discard, tc.namePair) + if err == nil { + t.Errorf("expected error but got nil") + return + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("unexpected error: %s", err.Error()) + } + }) + } +} + // normalize normalizes \r\n (windows) and \r (mac) // into \n (unix) func normalize(d []byte) []byte { diff --git a/pkg/moq/template.go b/pkg/moq/template.go deleted file mode 100644 index fae942e..0000000 --- a/pkg/moq/template.go +++ /dev/null @@ -1,127 +0,0 @@ -package moq - -// moqImports are the imports all moq files get. -var moqImports = []string{} - -// moqTemplate is the template for mocked code. -// language=GoTemplate -var moqTemplate = `// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package {{.PackageName}} -{{- $sourcePackagePrefix := .SourcePackagePrefix}} -{{- $stubImpl := .StubImpl}} -{{- $skipEnsure := .SkipEnsure}} - -import ( -{{- range .Imports }} - "{{.}}" -{{- end }} -) - -{{ range $i, $obj := .Objects -}} - -{{- if not $skipEnsure -}} -// Ensure, that {{.MockName}} does implement {{$sourcePackagePrefix}}{{.InterfaceName}}. -// If this is not the case, regenerate this file with moq. -var _ {{$sourcePackagePrefix}}{{.InterfaceName}} = &{{.MockName}}{} -{{- end }} - -// {{.MockName}} is a mock implementation of {{$sourcePackagePrefix}}{{.InterfaceName}}. -// -// func TestSomethingThatUses{{.InterfaceName}}(t *testing.T) { -// -// // make and configure a mocked {{$sourcePackagePrefix}}{{.InterfaceName}} -// mocked{{.InterfaceName}} := &{{.MockName}}{ {{ range .Methods }} -// {{.Name}}Func: func({{ .Arglist }}) {{.ReturnArgTypeList}} { -// panic("mock out the {{.Name}} method") -// },{{- end }} -// } -// -// // use mocked{{.InterfaceName}} in code that requires {{$sourcePackagePrefix}}{{.InterfaceName}} -// // and then make assertions. -// -// } -type {{.MockName}} struct { -{{- range .Methods }} - // {{.Name}}Func mocks the {{.Name}} method. - {{.Name}}Func func({{ .Arglist }}) {{.ReturnArgTypeList}} -{{ end }} - // calls tracks calls to the methods. - calls struct { -{{- range .Methods }} - // {{ .Name }} holds details about calls to the {{.Name}} method. - {{ .Name }} []struct { - {{- range .Params }} - // {{ .Name | Exported }} is the {{ .Name }} argument value. - {{ .Name | Exported }} {{ .Type }} - {{- end }} - } -{{- end }} - } -{{- range .Methods }} - lock{{.Name}} sync.RWMutex -{{- end }} -} -{{ range .Methods }} -// {{.Name}} calls {{.Name}}Func. -func (mock *{{$obj.MockName}}) {{.Name}}({{.Arglist}}) {{.ReturnArgTypeList}} { -{{- if not $stubImpl }} - if mock.{{.Name}}Func == nil { - panic("{{$obj.MockName}}.{{.Name}}Func: method is nil but {{$obj.InterfaceName}}.{{.Name}} was just called") - } -{{- end }} - callInfo := struct { - {{- range .Params }} - {{ .Name | Exported }} {{ .Type }} - {{- end }} - }{ - {{- range .Params }} - {{ .Name | Exported }}: {{ .Name }}, - {{- end }} - } - mock.lock{{.Name}}.Lock() - mock.calls.{{.Name}} = append(mock.calls.{{.Name}}, callInfo) - mock.lock{{.Name}}.Unlock() -{{- if .Returns }} - {{- if $stubImpl }} - if mock.{{.Name}}Func == nil { - var ( - {{- range .Returns }} - {{.Name}} {{.Type}} - {{- end }} - ) - return {{.ReturnArgNameList}} - } - {{- end }} - return mock.{{.Name}}Func({{.ArgCallList}}) -{{- else }} - {{- if $stubImpl }} - if mock.{{.Name}}Func == nil { - return - } - {{- end }} - mock.{{.Name}}Func({{.ArgCallList}}) -{{- end }} -} - -// {{.Name}}Calls gets all the calls that were made to {{.Name}}. -// Check the length with: -// len(mocked{{$obj.InterfaceName}}.{{.Name}}Calls()) -func (mock *{{$obj.MockName}}) {{.Name}}Calls() []struct { - {{- range .Params }} - {{ .Name | Exported }} {{ .Type }} - {{- end }} - } { - var calls []struct { - {{- range .Params }} - {{ .Name | Exported }} {{ .Type }} - {{- end }} - } - mock.lock{{.Name}}.RLock() - calls = mock.calls.{{.Name}} - mock.lock{{.Name}}.RUnlock() - return calls -} -{{ end -}} -{{ end -}}` diff --git a/pkg/moq/testpackages/blankid/swallower.golden.go b/pkg/moq/testpackages/blankid/swallower.golden.go index ecdcfa6..3a6a49f 100644 --- a/pkg/moq/testpackages/blankid/swallower.golden.go +++ b/pkg/moq/testpackages/blankid/swallower.golden.go @@ -13,58 +13,58 @@ var _ Swallower = &SwallowerMock{} // SwallowerMock is a mock implementation of Swallower. // -// func TestSomethingThatUsesSwallower(t *testing.T) { +// func TestSomethingThatUsesSwallower(t *testing.T) { // -// // make and configure a mocked Swallower -// mockedSwallower := &SwallowerMock{ -// SwallowFunc: func(in1 string) { -// panic("mock out the Swallow method") -// }, -// } +// // make and configure a mocked Swallower +// mockedSwallower := &SwallowerMock{ +// SwallowFunc: func(s string) { +// panic("mock out the Swallow method") +// }, +// } // -// // use mockedSwallower in code that requires Swallower -// // and then make assertions. +// // use mockedSwallower in code that requires Swallower +// // and then make assertions. // -// } +// } type SwallowerMock struct { // SwallowFunc mocks the Swallow method. - SwallowFunc func(in1 string) + SwallowFunc func(s string) // calls tracks calls to the methods. calls struct { // Swallow holds details about calls to the Swallow method. Swallow []struct { - // In1 is the in1 argument value. - In1 string + // S is the s argument value. + S string } } lockSwallow sync.RWMutex } // Swallow calls SwallowFunc. -func (mock *SwallowerMock) Swallow(in1 string) { +func (mock *SwallowerMock) Swallow(s string) { if mock.SwallowFunc == nil { panic("SwallowerMock.SwallowFunc: method is nil but Swallower.Swallow was just called") } callInfo := struct { - In1 string + S string }{ - In1: in1, + S: s, } mock.lockSwallow.Lock() mock.calls.Swallow = append(mock.calls.Swallow, callInfo) mock.lockSwallow.Unlock() - mock.SwallowFunc(in1) + mock.SwallowFunc(s) } // SwallowCalls gets all the calls that were made to Swallow. // Check the length with: // len(mockedSwallower.SwallowCalls()) func (mock *SwallowerMock) SwallowCalls() []struct { - In1 string + S string } { var calls []struct { - In1 string + S string } mock.lockSwallow.RLock() calls = mock.calls.Swallow diff --git a/pkg/moq/testpackages/channels/queuer_moq.golden.go b/pkg/moq/testpackages/channels/queuer_moq.golden.go index 4d71ec6..51c93b7 100644 --- a/pkg/moq/testpackages/channels/queuer_moq.golden.go +++ b/pkg/moq/testpackages/channels/queuer_moq.golden.go @@ -13,22 +13,22 @@ var _ Queuer = &QueuerMock{} // QueuerMock is a mock implementation of Queuer. // -// func TestSomethingThatUsesQueuer(t *testing.T) { +// func TestSomethingThatUsesQueuer(t *testing.T) { // -// // make and configure a mocked Queuer -// mockedQueuer := &QueuerMock{ -// SubFunc: func(topic string) (<-chan Queue, error) { -// panic("mock out the Sub method") -// }, -// UnsubFunc: func(topic string) { -// panic("mock out the Unsub method") -// }, -// } +// // make and configure a mocked Queuer +// mockedQueuer := &QueuerMock{ +// SubFunc: func(topic string) (<-chan Queue, error) { +// panic("mock out the Sub method") +// }, +// UnsubFunc: func(topic string) { +// panic("mock out the Unsub method") +// }, +// } // -// // use mockedQueuer in code that requires Queuer -// // and then make assertions. +// // use mockedQueuer in code that requires Queuer +// // and then make assertions. // -// } +// } type QueuerMock struct { // SubFunc mocks the Sub method. SubFunc func(topic string) (<-chan Queue, error) @@ -65,10 +65,10 @@ func (mock *QueuerMock) Sub(topic string) (<-chan Queue, error) { mock.lockSub.Unlock() if mock.SubFunc == nil { var ( - out1 <-chan Queue - out2 error + queueChOut <-chan Queue + errOut error ) - return out1, out2 + return queueChOut, errOut } return mock.SubFunc(topic) } diff --git a/pkg/moq/testpackages/dotimport/service_moq_test.go b/pkg/moq/testpackages/dotimport/service_moq_test.go index a5d2a34..d2855c5 100755 --- a/pkg/moq/testpackages/dotimport/service_moq_test.go +++ b/pkg/moq/testpackages/dotimport/service_moq_test.go @@ -8,29 +8,25 @@ import ( "sync" ) -var ( - lockServiceMockUser sync.RWMutex -) - // Ensure, that ServiceMock does implement dotimport.Service. // If this is not the case, regenerate this file with moq. var _ dotimport.Service = &ServiceMock{} // ServiceMock is a mock implementation of dotimport.Service. // -// func TestSomethingThatUsesService(t *testing.T) { +// func TestSomethingThatUsesService(t *testing.T) { // -// // make and configure a mocked dotimport.Service -// mockedService := &ServiceMock{ -// UserFunc: func(ID string) (dotimport.User, error) { -// panic("mock out the User method") -// }, -// } +// // make and configure a mocked dotimport.Service +// mockedService := &ServiceMock{ +// UserFunc: func(ID string) (dotimport.User, error) { +// panic("mock out the User method") +// }, +// } // -// // use mockedService in code that requires dotimport.Service -// // and then make assertions. +// // use mockedService in code that requires dotimport.Service +// // and then make assertions. // -// } +// } type ServiceMock struct { // UserFunc mocks the User method. UserFunc func(ID string) (dotimport.User, error) @@ -43,6 +39,7 @@ type ServiceMock struct { ID string } } + lockUser sync.RWMutex } // User calls UserFunc. @@ -55,9 +52,9 @@ func (mock *ServiceMock) User(ID string) (dotimport.User, error) { }{ ID: ID, } - lockServiceMockUser.Lock() + mock.lockUser.Lock() mock.calls.User = append(mock.calls.User, callInfo) - lockServiceMockUser.Unlock() + mock.lockUser.Unlock() return mock.UserFunc(ID) } @@ -70,8 +67,8 @@ func (mock *ServiceMock) UserCalls() []struct { var calls []struct { ID string } - lockServiceMockUser.RLock() + mock.lockUser.RLock() calls = mock.calls.User - lockServiceMockUser.RUnlock() + mock.lockUser.RUnlock() return calls } diff --git a/pkg/moq/testpackages/genparamname/iface.go b/pkg/moq/testpackages/genparamname/iface.go new file mode 100644 index 0000000..2ceaf45 --- /dev/null +++ b/pkg/moq/testpackages/genparamname/iface.go @@ -0,0 +1,34 @@ +package genparamname + +import ( + "database/sql" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/http/httputil" + "net/url" +) + +type ( + Go func() + myType struct{} +) + +type Interface interface { + Method( + *myType, + [3]json.Number, + []byte, + map[sql.NullString]io.Reader, + func(conn net.Conn), + Go, + chan *httputil.BufferPool, + struct{ URL *url.URL }, + interface { + fmt.Stringer + CookieJar() http.CookieJar + }, + ) +} diff --git a/pkg/moq/testpackages/genparamname/iface_moq.golden.go b/pkg/moq/testpackages/genparamname/iface_moq.golden.go new file mode 100644 index 0000000..0a12746 --- /dev/null +++ b/pkg/moq/testpackages/genparamname/iface_moq.golden.go @@ -0,0 +1,147 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package genparamname + +import ( + "database/sql" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/http/httputil" + "net/url" + "sync" +) + +// Ensure, that InterfaceMock does implement Interface. +// If this is not the case, regenerate this file with moq. +var _ Interface = &InterfaceMock{} + +// InterfaceMock is a mock implementation of Interface. +// +// func TestSomethingThatUsesInterface(t *testing.T) { +// +// // make and configure a mocked Interface +// mockedInterface := &InterfaceMock{ +// MethodFunc: func(myTypeMoqParam *myType, numbers [3]json.Number, bytes []byte, nullStringToReader map[sql.NullString]io.Reader, fn func(conn net.Conn), goMoqParam Go, bufferPoolCh chan *httputil.BufferPool, val struct{URL *url.URL}, ifaceVal interface{CookieJar() http.CookieJar; fmt.Stringer}) { +// panic("mock out the Method method") +// }, +// } +// +// // use mockedInterface in code that requires Interface +// // and then make assertions. +// +// } +type InterfaceMock struct { + // MethodFunc mocks the Method method. + MethodFunc func(myTypeMoqParam *myType, numbers [3]json.Number, bytes []byte, nullStringToReader map[sql.NullString]io.Reader, fn func(conn net.Conn), goMoqParam Go, bufferPoolCh chan *httputil.BufferPool, val struct{ URL *url.URL }, ifaceVal interface { + CookieJar() http.CookieJar + fmt.Stringer + }) + + // calls tracks calls to the methods. + calls struct { + // Method holds details about calls to the Method method. + Method []struct { + // MyTypeMoqParam is the myTypeMoqParam argument value. + MyTypeMoqParam *myType + // Numbers is the numbers argument value. + Numbers [3]json.Number + // Bytes is the bytes argument value. + Bytes []byte + // NullStringToReader is the nullStringToReader argument value. + NullStringToReader map[sql.NullString]io.Reader + // Fn is the fn argument value. + Fn func(conn net.Conn) + // GoMoqParam is the goMoqParam argument value. + GoMoqParam Go + // BufferPoolCh is the bufferPoolCh argument value. + BufferPoolCh chan *httputil.BufferPool + // Val is the val argument value. + Val struct{ URL *url.URL } + // IfaceVal is the ifaceVal argument value. + IfaceVal interface { + CookieJar() http.CookieJar + fmt.Stringer + } + } + } + lockMethod sync.RWMutex +} + +// Method calls MethodFunc. +func (mock *InterfaceMock) Method(myTypeMoqParam *myType, numbers [3]json.Number, bytes []byte, nullStringToReader map[sql.NullString]io.Reader, fn func(conn net.Conn), goMoqParam Go, bufferPoolCh chan *httputil.BufferPool, val struct{ URL *url.URL }, ifaceVal interface { + CookieJar() http.CookieJar + fmt.Stringer +}) { + if mock.MethodFunc == nil { + panic("InterfaceMock.MethodFunc: method is nil but Interface.Method was just called") + } + callInfo := struct { + MyTypeMoqParam *myType + Numbers [3]json.Number + Bytes []byte + NullStringToReader map[sql.NullString]io.Reader + Fn func(conn net.Conn) + GoMoqParam Go + BufferPoolCh chan *httputil.BufferPool + Val struct{ URL *url.URL } + IfaceVal interface { + CookieJar() http.CookieJar + fmt.Stringer + } + }{ + MyTypeMoqParam: myTypeMoqParam, + Numbers: numbers, + Bytes: bytes, + NullStringToReader: nullStringToReader, + Fn: fn, + GoMoqParam: goMoqParam, + BufferPoolCh: bufferPoolCh, + Val: val, + IfaceVal: ifaceVal, + } + mock.lockMethod.Lock() + mock.calls.Method = append(mock.calls.Method, callInfo) + mock.lockMethod.Unlock() + mock.MethodFunc(myTypeMoqParam, numbers, bytes, nullStringToReader, fn, goMoqParam, bufferPoolCh, val, ifaceVal) +} + +// MethodCalls gets all the calls that were made to Method. +// Check the length with: +// len(mockedInterface.MethodCalls()) +func (mock *InterfaceMock) MethodCalls() []struct { + MyTypeMoqParam *myType + Numbers [3]json.Number + Bytes []byte + NullStringToReader map[sql.NullString]io.Reader + Fn func(conn net.Conn) + GoMoqParam Go + BufferPoolCh chan *httputil.BufferPool + Val struct{ URL *url.URL } + IfaceVal interface { + CookieJar() http.CookieJar + fmt.Stringer + } +} { + var calls []struct { + MyTypeMoqParam *myType + Numbers [3]json.Number + Bytes []byte + NullStringToReader map[sql.NullString]io.Reader + Fn func(conn net.Conn) + GoMoqParam Go + BufferPoolCh chan *httputil.BufferPool + Val struct{ URL *url.URL } + IfaceVal interface { + CookieJar() http.CookieJar + fmt.Stringer + } + } + mock.lockMethod.RLock() + calls = mock.calls.Method + mock.lockMethod.RUnlock() + return calls +} diff --git a/pkg/moq/testpackages/gogenvendoring/user/user_moq_test.go b/pkg/moq/testpackages/gogenvendoring/user/user_moq_test.go index a9440cf..5b6fd26 100644 --- a/pkg/moq/testpackages/gogenvendoring/user/user_moq_test.go +++ b/pkg/moq/testpackages/gogenvendoring/user/user_moq_test.go @@ -14,58 +14,58 @@ var _ Service = &ServiceMock{} // ServiceMock is a mock implementation of Service. // -// func TestSomethingThatUsesService(t *testing.T) { +// func TestSomethingThatUsesService(t *testing.T) { // -// // make and configure a mocked Service -// mockedService := &ServiceMock{ -// DoSomethingFunc: func(in1 somerepo.SomeType) error { -// panic("mock out the DoSomething method") -// }, -// } +// // make and configure a mocked Service +// mockedService := &ServiceMock{ +// DoSomethingFunc: func(someType somerepo.SomeType) error { +// panic("mock out the DoSomething method") +// }, +// } // -// // use mockedService in code that requires Service -// // and then make assertions. +// // use mockedService in code that requires Service +// // and then make assertions. // -// } +// } type ServiceMock struct { // DoSomethingFunc mocks the DoSomething method. - DoSomethingFunc func(in1 somerepo.SomeType) error + DoSomethingFunc func(someType somerepo.SomeType) error // calls tracks calls to the methods. calls struct { // DoSomething holds details about calls to the DoSomething method. DoSomething []struct { - // In1 is the in1 argument value. - In1 somerepo.SomeType + // SomeType is the someType argument value. + SomeType somerepo.SomeType } } lockDoSomething sync.RWMutex } // DoSomething calls DoSomethingFunc. -func (mock *ServiceMock) DoSomething(in1 somerepo.SomeType) error { +func (mock *ServiceMock) DoSomething(someType somerepo.SomeType) error { if mock.DoSomethingFunc == nil { panic("ServiceMock.DoSomethingFunc: method is nil but Service.DoSomething was just called") } callInfo := struct { - In1 somerepo.SomeType + SomeType somerepo.SomeType }{ - In1: in1, + SomeType: someType, } mock.lockDoSomething.Lock() mock.calls.DoSomething = append(mock.calls.DoSomething, callInfo) mock.lockDoSomething.Unlock() - return mock.DoSomethingFunc(in1) + return mock.DoSomethingFunc(someType) } // DoSomethingCalls gets all the calls that were made to DoSomething. // Check the length with: // len(mockedService.DoSomethingCalls()) func (mock *ServiceMock) DoSomethingCalls() []struct { - In1 somerepo.SomeType + SomeType somerepo.SomeType } { var calls []struct { - In1 somerepo.SomeType + SomeType somerepo.SomeType } mock.lockDoSomething.RLock() calls = mock.calls.DoSomething diff --git a/pkg/moq/testpackages/importalias/middleman.go b/pkg/moq/testpackages/importalias/middleman.go new file mode 100644 index 0000000..831394f --- /dev/null +++ b/pkg/moq/testpackages/importalias/middleman.go @@ -0,0 +1,10 @@ +package importalias + +import ( + srcclient "github.com/matryer/moq/pkg/moq/testpackages/importalias/source/client" + tgtclient "github.com/matryer/moq/pkg/moq/testpackages/importalias/target/client" +) + +type MiddleMan interface { + Connect(src srcclient.Client, tgt tgtclient.Client) +} diff --git a/pkg/moq/testpackages/importalias/middleman_moq.golden.go b/pkg/moq/testpackages/importalias/middleman_moq.golden.go new file mode 100644 index 0000000..dfc91b1 --- /dev/null +++ b/pkg/moq/testpackages/importalias/middleman_moq.golden.go @@ -0,0 +1,81 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package importalias + +import ( + srcclient "github.com/matryer/moq/pkg/moq/testpackages/importalias/source/client" + tgtclient "github.com/matryer/moq/pkg/moq/testpackages/importalias/target/client" + "sync" +) + +// Ensure, that MiddleManMock does implement MiddleMan. +// If this is not the case, regenerate this file with moq. +var _ MiddleMan = &MiddleManMock{} + +// MiddleManMock is a mock implementation of MiddleMan. +// +// func TestSomethingThatUsesMiddleMan(t *testing.T) { +// +// // make and configure a mocked MiddleMan +// mockedMiddleMan := &MiddleManMock{ +// ConnectFunc: func(src srcclient.Client, tgt tgtclient.Client) { +// panic("mock out the Connect method") +// }, +// } +// +// // use mockedMiddleMan in code that requires MiddleMan +// // and then make assertions. +// +// } +type MiddleManMock struct { + // ConnectFunc mocks the Connect method. + ConnectFunc func(src srcclient.Client, tgt tgtclient.Client) + + // calls tracks calls to the methods. + calls struct { + // Connect holds details about calls to the Connect method. + Connect []struct { + // Src is the src argument value. + Src srcclient.Client + // Tgt is the tgt argument value. + Tgt tgtclient.Client + } + } + lockConnect sync.RWMutex +} + +// Connect calls ConnectFunc. +func (mock *MiddleManMock) Connect(src srcclient.Client, tgt tgtclient.Client) { + if mock.ConnectFunc == nil { + panic("MiddleManMock.ConnectFunc: method is nil but MiddleMan.Connect was just called") + } + callInfo := struct { + Src srcclient.Client + Tgt tgtclient.Client + }{ + Src: src, + Tgt: tgt, + } + mock.lockConnect.Lock() + mock.calls.Connect = append(mock.calls.Connect, callInfo) + mock.lockConnect.Unlock() + mock.ConnectFunc(src, tgt) +} + +// ConnectCalls gets all the calls that were made to Connect. +// Check the length with: +// len(mockedMiddleMan.ConnectCalls()) +func (mock *MiddleManMock) ConnectCalls() []struct { + Src srcclient.Client + Tgt tgtclient.Client +} { + var calls []struct { + Src srcclient.Client + Tgt tgtclient.Client + } + mock.lockConnect.RLock() + calls = mock.calls.Connect + mock.lockConnect.RUnlock() + return calls +} diff --git a/pkg/moq/testpackages/importalias/source/client/src_client.go b/pkg/moq/testpackages/importalias/source/client/src_client.go new file mode 100644 index 0000000..54cf8ad --- /dev/null +++ b/pkg/moq/testpackages/importalias/source/client/src_client.go @@ -0,0 +1,3 @@ +package client + +type Client struct{} diff --git a/pkg/moq/testpackages/importalias/target/client/tgt_client.go b/pkg/moq/testpackages/importalias/target/client/tgt_client.go new file mode 100644 index 0000000..54cf8ad --- /dev/null +++ b/pkg/moq/testpackages/importalias/target/client/tgt_client.go @@ -0,0 +1,3 @@ +package client + +type Client struct{} diff --git a/pkg/moq/testpackages/imports/two/gofmt.golden.go b/pkg/moq/testpackages/imports/two/gofmt.golden.go index 36312c4..2f1bad0 100644 --- a/pkg/moq/testpackages/imports/two/gofmt.golden.go +++ b/pkg/moq/testpackages/imports/two/gofmt.golden.go @@ -14,22 +14,22 @@ var _ DoSomething = &gofmtMock{} // gofmtMock is a mock implementation of DoSomething. // -// func TestSomethingThatUsesDoSomething(t *testing.T) { +// func TestSomethingThatUsesDoSomething(t *testing.T) { // -// // make and configure a mocked DoSomething -// mockedDoSomething := &gofmtMock{ -// AnotherFunc: func(thing one.Thing) error { -// panic("mock out the Another method") -// }, -// DoFunc: func(thing one.Thing) error { -// panic("mock out the Do method") -// }, -// } +// // make and configure a mocked DoSomething +// mockedDoSomething := &gofmtMock{ +// AnotherFunc: func(thing one.Thing) error { +// panic("mock out the Another method") +// }, +// DoFunc: func(thing one.Thing) error { +// panic("mock out the Do method") +// }, +// } // -// // use mockedDoSomething in code that requires DoSomething -// // and then make assertions. +// // use mockedDoSomething in code that requires DoSomething +// // and then make assertions. // -// } +// } type gofmtMock struct { // AnotherFunc mocks the Another method. AnotherFunc func(thing one.Thing) error diff --git a/pkg/moq/testpackages/imports/two/goimports.golden.go b/pkg/moq/testpackages/imports/two/goimports.golden.go index 989e7c7..e577776 100644 --- a/pkg/moq/testpackages/imports/two/goimports.golden.go +++ b/pkg/moq/testpackages/imports/two/goimports.golden.go @@ -15,22 +15,22 @@ var _ DoSomething = &goimportsMock{} // goimportsMock is a mock implementation of DoSomething. // -// func TestSomethingThatUsesDoSomething(t *testing.T) { +// func TestSomethingThatUsesDoSomething(t *testing.T) { // -// // make and configure a mocked DoSomething -// mockedDoSomething := &goimportsMock{ -// AnotherFunc: func(thing one.Thing) error { -// panic("mock out the Another method") -// }, -// DoFunc: func(thing one.Thing) error { -// panic("mock out the Do method") -// }, -// } +// // make and configure a mocked DoSomething +// mockedDoSomething := &goimportsMock{ +// AnotherFunc: func(thing one.Thing) error { +// panic("mock out the Another method") +// }, +// DoFunc: func(thing one.Thing) error { +// panic("mock out the Do method") +// }, +// } // -// // use mockedDoSomething in code that requires DoSomething -// // and then make assertions. +// // use mockedDoSomething in code that requires DoSomething +// // and then make assertions. // -// } +// } type goimportsMock struct { // AnotherFunc mocks the Another method. AnotherFunc func(thing one.Thing) error diff --git a/pkg/moq/testpackages/imports/two/noop.golden.go b/pkg/moq/testpackages/imports/two/noop.golden.go index 4decf10..c4ed29d 100644 --- a/pkg/moq/testpackages/imports/two/noop.golden.go +++ b/pkg/moq/testpackages/imports/two/noop.golden.go @@ -4,8 +4,8 @@ package two import ( - "sync" "github.com/matryer/moq/pkg/moq/testpackages/imports/one" + "sync" ) // Ensure, that noopMock does implement DoSomething. @@ -14,22 +14,22 @@ var _ DoSomething = &noopMock{} // noopMock is a mock implementation of DoSomething. // -// func TestSomethingThatUsesDoSomething(t *testing.T) { +// func TestSomethingThatUsesDoSomething(t *testing.T) { // -// // make and configure a mocked DoSomething -// mockedDoSomething := &noopMock{ -// AnotherFunc: func(thing one.Thing) error { -// panic("mock out the Another method") -// }, -// DoFunc: func(thing one.Thing) error { -// panic("mock out the Do method") -// }, -// } +// // make and configure a mocked DoSomething +// mockedDoSomething := &noopMock{ +// AnotherFunc: func(thing one.Thing) error { +// panic("mock out the Another method") +// }, +// DoFunc: func(thing one.Thing) error { +// panic("mock out the Do method") +// }, +// } // -// // use mockedDoSomething in code that requires DoSomething -// // and then make assertions. +// // use mockedDoSomething in code that requires DoSomething +// // and then make assertions. // -// } +// } type noopMock struct { // AnotherFunc mocks the Another method. AnotherFunc func(thing one.Thing) error diff --git a/pkg/moq/testpackages/paramconflict/iface.go b/pkg/moq/testpackages/paramconflict/iface.go new file mode 100644 index 0000000..3432b85 --- /dev/null +++ b/pkg/moq/testpackages/paramconflict/iface.go @@ -0,0 +1,5 @@ +package paramconflict + +type Interface interface { + Method(string, bool, string, bool, int, int32, int64, float32, float64) +} diff --git a/pkg/moq/testpackages/paramconflict/iface_moq.golden.go b/pkg/moq/testpackages/paramconflict/iface_moq.golden.go new file mode 100644 index 0000000..d0bd23d --- /dev/null +++ b/pkg/moq/testpackages/paramconflict/iface_moq.golden.go @@ -0,0 +1,121 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package paramconflict + +import ( + "sync" +) + +// Ensure, that InterfaceMock does implement Interface. +// If this is not the case, regenerate this file with moq. +var _ Interface = &InterfaceMock{} + +// InterfaceMock is a mock implementation of Interface. +// +// func TestSomethingThatUsesInterface(t *testing.T) { +// +// // make and configure a mocked Interface +// mockedInterface := &InterfaceMock{ +// MethodFunc: func(s1 string, b1 bool, s2 string, b2 bool, n1 int, n2 int32, n3 int64, f1 float32, f2 float64) { +// panic("mock out the Method method") +// }, +// } +// +// // use mockedInterface in code that requires Interface +// // and then make assertions. +// +// } +type InterfaceMock struct { + // MethodFunc mocks the Method method. + MethodFunc func(s1 string, b1 bool, s2 string, b2 bool, n1 int, n2 int32, n3 int64, f1 float32, f2 float64) + + // calls tracks calls to the methods. + calls struct { + // Method holds details about calls to the Method method. + Method []struct { + // S1 is the s1 argument value. + S1 string + // B1 is the b1 argument value. + B1 bool + // S2 is the s2 argument value. + S2 string + // B2 is the b2 argument value. + B2 bool + // N1 is the n1 argument value. + N1 int + // N2 is the n2 argument value. + N2 int32 + // N3 is the n3 argument value. + N3 int64 + // F1 is the f1 argument value. + F1 float32 + // F2 is the f2 argument value. + F2 float64 + } + } + lockMethod sync.RWMutex +} + +// Method calls MethodFunc. +func (mock *InterfaceMock) Method(s1 string, b1 bool, s2 string, b2 bool, n1 int, n2 int32, n3 int64, f1 float32, f2 float64) { + if mock.MethodFunc == nil { + panic("InterfaceMock.MethodFunc: method is nil but Interface.Method was just called") + } + callInfo := struct { + S1 string + B1 bool + S2 string + B2 bool + N1 int + N2 int32 + N3 int64 + F1 float32 + F2 float64 + }{ + S1: s1, + B1: b1, + S2: s2, + B2: b2, + N1: n1, + N2: n2, + N3: n3, + F1: f1, + F2: f2, + } + mock.lockMethod.Lock() + mock.calls.Method = append(mock.calls.Method, callInfo) + mock.lockMethod.Unlock() + mock.MethodFunc(s1, b1, s2, b2, n1, n2, n3, f1, f2) +} + +// MethodCalls gets all the calls that were made to Method. +// Check the length with: +// len(mockedInterface.MethodCalls()) +func (mock *InterfaceMock) MethodCalls() []struct { + S1 string + B1 bool + S2 string + B2 bool + N1 int + N2 int32 + N3 int64 + F1 float32 + F2 float64 +} { + var calls []struct { + S1 string + B1 bool + S2 string + B2 bool + N1 int + N2 int32 + N3 int64 + F1 float32 + F2 float64 + } + mock.lockMethod.RLock() + calls = mock.calls.Method + mock.lockMethod.RUnlock() + return calls +} diff --git a/pkg/moq/testpackages/shadow/http/thing.go b/pkg/moq/testpackages/shadow/http/thing.go new file mode 100644 index 0000000..5d4c621 --- /dev/null +++ b/pkg/moq/testpackages/shadow/http/thing.go @@ -0,0 +1,7 @@ +package http + +import "net/http" + +type Thing interface { + Blah(w http.ResponseWriter, r *http.Request) +} diff --git a/pkg/moq/testpackages/shadow/mock/thing_moq.golden.go b/pkg/moq/testpackages/shadow/mock/thing_moq.golden.go new file mode 100644 index 0000000..d66433a --- /dev/null +++ b/pkg/moq/testpackages/shadow/mock/thing_moq.golden.go @@ -0,0 +1,81 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package mock + +import ( + shadowhttp "github.com/matryer/moq/pkg/moq/testpackages/shadow/http" + nethttp "net/http" + "sync" +) + +// Ensure, that ThingMock does implement shadowhttp.Thing. +// If this is not the case, regenerate this file with moq. +var _ shadowhttp.Thing = &ThingMock{} + +// ThingMock is a mock implementation of shadowhttp.Thing. +// +// func TestSomethingThatUsesThing(t *testing.T) { +// +// // make and configure a mocked shadowhttp.Thing +// mockedThing := &ThingMock{ +// BlahFunc: func(w nethttp.ResponseWriter, r *nethttp.Request) { +// panic("mock out the Blah method") +// }, +// } +// +// // use mockedThing in code that requires shadowhttp.Thing +// // and then make assertions. +// +// } +type ThingMock struct { + // BlahFunc mocks the Blah method. + BlahFunc func(w nethttp.ResponseWriter, r *nethttp.Request) + + // calls tracks calls to the methods. + calls struct { + // Blah holds details about calls to the Blah method. + Blah []struct { + // W is the w argument value. + W nethttp.ResponseWriter + // R is the r argument value. + R *nethttp.Request + } + } + lockBlah sync.RWMutex +} + +// Blah calls BlahFunc. +func (mock *ThingMock) Blah(w nethttp.ResponseWriter, r *nethttp.Request) { + if mock.BlahFunc == nil { + panic("ThingMock.BlahFunc: method is nil but Thing.Blah was just called") + } + callInfo := struct { + W nethttp.ResponseWriter + R *nethttp.Request + }{ + W: w, + R: r, + } + mock.lockBlah.Lock() + mock.calls.Blah = append(mock.calls.Blah, callInfo) + mock.lockBlah.Unlock() + mock.BlahFunc(w, r) +} + +// BlahCalls gets all the calls that were made to Blah. +// Check the length with: +// len(mockedThing.BlahCalls()) +func (mock *ThingMock) BlahCalls() []struct { + W nethttp.ResponseWriter + R *nethttp.Request +} { + var calls []struct { + W nethttp.ResponseWriter + R *nethttp.Request + } + mock.lockBlah.RLock() + calls = mock.calls.Blah + mock.lockBlah.RUnlock() + return calls +} diff --git a/pkg/moq/testpackages/shadow/shadower.go b/pkg/moq/testpackages/shadow/shadower.go new file mode 100644 index 0000000..4af0137 --- /dev/null +++ b/pkg/moq/testpackages/shadow/shadower.go @@ -0,0 +1,14 @@ +package shadow + +import ( + "io" + "net/http" +) + +// Shadower is an interface, with a method, with a parameter whose name +// shadows an import name. +type Shadower interface { + Shadow(io io.Reader) + ShadowTwo(r io.Reader, io interface{}) + ShadowThree(http interface{}, srv *http.Server) +} diff --git a/pkg/moq/testpackages/shadow/shadower_moq.golden.go b/pkg/moq/testpackages/shadow/shadower_moq.golden.go new file mode 100644 index 0000000..18a35e1 --- /dev/null +++ b/pkg/moq/testpackages/shadow/shadower_moq.golden.go @@ -0,0 +1,173 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package shadow + +import ( + "io" + "net/http" + "sync" +) + +// Ensure, that ShadowerMock does implement Shadower. +// If this is not the case, regenerate this file with moq. +var _ Shadower = &ShadowerMock{} + +// ShadowerMock is a mock implementation of Shadower. +// +// func TestSomethingThatUsesShadower(t *testing.T) { +// +// // make and configure a mocked Shadower +// mockedShadower := &ShadowerMock{ +// ShadowFunc: func(ioMoqParam io.Reader) { +// panic("mock out the Shadow method") +// }, +// ShadowThreeFunc: func(httpMoqParam interface{}, srv *http.Server) { +// panic("mock out the ShadowThree method") +// }, +// ShadowTwoFunc: func(r io.Reader, ioMoqParam interface{}) { +// panic("mock out the ShadowTwo method") +// }, +// } +// +// // use mockedShadower in code that requires Shadower +// // and then make assertions. +// +// } +type ShadowerMock struct { + // ShadowFunc mocks the Shadow method. + ShadowFunc func(ioMoqParam io.Reader) + + // ShadowThreeFunc mocks the ShadowThree method. + ShadowThreeFunc func(httpMoqParam interface{}, srv *http.Server) + + // ShadowTwoFunc mocks the ShadowTwo method. + ShadowTwoFunc func(r io.Reader, ioMoqParam interface{}) + + // calls tracks calls to the methods. + calls struct { + // Shadow holds details about calls to the Shadow method. + Shadow []struct { + // IoMoqParam is the ioMoqParam argument value. + IoMoqParam io.Reader + } + // ShadowThree holds details about calls to the ShadowThree method. + ShadowThree []struct { + // HttpMoqParam is the httpMoqParam argument value. + HttpMoqParam interface{} + // Srv is the srv argument value. + Srv *http.Server + } + // ShadowTwo holds details about calls to the ShadowTwo method. + ShadowTwo []struct { + // R is the r argument value. + R io.Reader + // IoMoqParam is the ioMoqParam argument value. + IoMoqParam interface{} + } + } + lockShadow sync.RWMutex + lockShadowThree sync.RWMutex + lockShadowTwo sync.RWMutex +} + +// Shadow calls ShadowFunc. +func (mock *ShadowerMock) Shadow(ioMoqParam io.Reader) { + if mock.ShadowFunc == nil { + panic("ShadowerMock.ShadowFunc: method is nil but Shadower.Shadow was just called") + } + callInfo := struct { + IoMoqParam io.Reader + }{ + IoMoqParam: ioMoqParam, + } + mock.lockShadow.Lock() + mock.calls.Shadow = append(mock.calls.Shadow, callInfo) + mock.lockShadow.Unlock() + mock.ShadowFunc(ioMoqParam) +} + +// ShadowCalls gets all the calls that were made to Shadow. +// Check the length with: +// len(mockedShadower.ShadowCalls()) +func (mock *ShadowerMock) ShadowCalls() []struct { + IoMoqParam io.Reader +} { + var calls []struct { + IoMoqParam io.Reader + } + mock.lockShadow.RLock() + calls = mock.calls.Shadow + mock.lockShadow.RUnlock() + return calls +} + +// ShadowThree calls ShadowThreeFunc. +func (mock *ShadowerMock) ShadowThree(httpMoqParam interface{}, srv *http.Server) { + if mock.ShadowThreeFunc == nil { + panic("ShadowerMock.ShadowThreeFunc: method is nil but Shadower.ShadowThree was just called") + } + callInfo := struct { + HttpMoqParam interface{} + Srv *http.Server + }{ + HttpMoqParam: httpMoqParam, + Srv: srv, + } + mock.lockShadowThree.Lock() + mock.calls.ShadowThree = append(mock.calls.ShadowThree, callInfo) + mock.lockShadowThree.Unlock() + mock.ShadowThreeFunc(httpMoqParam, srv) +} + +// ShadowThreeCalls gets all the calls that were made to ShadowThree. +// Check the length with: +// len(mockedShadower.ShadowThreeCalls()) +func (mock *ShadowerMock) ShadowThreeCalls() []struct { + HttpMoqParam interface{} + Srv *http.Server +} { + var calls []struct { + HttpMoqParam interface{} + Srv *http.Server + } + mock.lockShadowThree.RLock() + calls = mock.calls.ShadowThree + mock.lockShadowThree.RUnlock() + return calls +} + +// ShadowTwo calls ShadowTwoFunc. +func (mock *ShadowerMock) ShadowTwo(r io.Reader, ioMoqParam interface{}) { + if mock.ShadowTwoFunc == nil { + panic("ShadowerMock.ShadowTwoFunc: method is nil but Shadower.ShadowTwo was just called") + } + callInfo := struct { + R io.Reader + IoMoqParam interface{} + }{ + R: r, + IoMoqParam: ioMoqParam, + } + mock.lockShadowTwo.Lock() + mock.calls.ShadowTwo = append(mock.calls.ShadowTwo, callInfo) + mock.lockShadowTwo.Unlock() + mock.ShadowTwoFunc(r, ioMoqParam) +} + +// ShadowTwoCalls gets all the calls that were made to ShadowTwo. +// Check the length with: +// len(mockedShadower.ShadowTwoCalls()) +func (mock *ShadowerMock) ShadowTwoCalls() []struct { + R io.Reader + IoMoqParam interface{} +} { + var calls []struct { + R io.Reader + IoMoqParam interface{} + } + mock.lockShadowTwo.RLock() + calls = mock.calls.ShadowTwo + mock.lockShadowTwo.RUnlock() + return calls +} diff --git a/pkg/moq/testpackages/syncimport/sync/thing.go b/pkg/moq/testpackages/syncimport/sync/thing.go new file mode 100644 index 0000000..5af77a9 --- /dev/null +++ b/pkg/moq/testpackages/syncimport/sync/thing.go @@ -0,0 +1,3 @@ +package sync + +type Thing string diff --git a/pkg/moq/testpackages/syncimport/syncer.go b/pkg/moq/testpackages/syncimport/syncer.go new file mode 100644 index 0000000..5094284 --- /dev/null +++ b/pkg/moq/testpackages/syncimport/syncer.go @@ -0,0 +1,11 @@ +package syncimport + +import ( + stdsync "sync" + + "github.com/matryer/moq/pkg/moq/testpackages/syncimport/sync" +) + +type Syncer interface { + Blah(s sync.Thing, wg *stdsync.WaitGroup) +} diff --git a/pkg/moq/testpackages/syncimport/syncer_moq.golden.go b/pkg/moq/testpackages/syncimport/syncer_moq.golden.go new file mode 100644 index 0000000..8609800 --- /dev/null +++ b/pkg/moq/testpackages/syncimport/syncer_moq.golden.go @@ -0,0 +1,80 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package syncimport + +import ( + "github.com/matryer/moq/pkg/moq/testpackages/syncimport/sync" + stdsync "sync" +) + +// Ensure, that SyncerMock does implement Syncer. +// If this is not the case, regenerate this file with moq. +var _ Syncer = &SyncerMock{} + +// SyncerMock is a mock implementation of Syncer. +// +// func TestSomethingThatUsesSyncer(t *testing.T) { +// +// // make and configure a mocked Syncer +// mockedSyncer := &SyncerMock{ +// BlahFunc: func(s sync.Thing, wg *stdsync.WaitGroup) { +// panic("mock out the Blah method") +// }, +// } +// +// // use mockedSyncer in code that requires Syncer +// // and then make assertions. +// +// } +type SyncerMock struct { + // BlahFunc mocks the Blah method. + BlahFunc func(s sync.Thing, wg *stdsync.WaitGroup) + + // calls tracks calls to the methods. + calls struct { + // Blah holds details about calls to the Blah method. + Blah []struct { + // S is the s argument value. + S sync.Thing + // Wg is the wg argument value. + Wg *stdsync.WaitGroup + } + } + lockBlah stdsync.RWMutex +} + +// Blah calls BlahFunc. +func (mock *SyncerMock) Blah(s sync.Thing, wg *stdsync.WaitGroup) { + if mock.BlahFunc == nil { + panic("SyncerMock.BlahFunc: method is nil but Syncer.Blah was just called") + } + callInfo := struct { + S sync.Thing + Wg *stdsync.WaitGroup + }{ + S: s, + Wg: wg, + } + mock.lockBlah.Lock() + mock.calls.Blah = append(mock.calls.Blah, callInfo) + mock.lockBlah.Unlock() + mock.BlahFunc(s, wg) +} + +// BlahCalls gets all the calls that were made to Blah. +// Check the length with: +// len(mockedSyncer.BlahCalls()) +func (mock *SyncerMock) BlahCalls() []struct { + S sync.Thing + Wg *stdsync.WaitGroup +} { + var calls []struct { + S sync.Thing + Wg *stdsync.WaitGroup + } + mock.lockBlah.RLock() + calls = mock.calls.Blah + mock.lockBlah.RUnlock() + return calls +} diff --git a/pkg/moq/testpackages/variadic/echoer.golden.go b/pkg/moq/testpackages/variadic/echoer.golden.go index df7f8e7..7fe4c86 100644 --- a/pkg/moq/testpackages/variadic/echoer.golden.go +++ b/pkg/moq/testpackages/variadic/echoer.golden.go @@ -13,19 +13,19 @@ var _ Echoer = &EchoerMock{} // EchoerMock is a mock implementation of Echoer. // -// func TestSomethingThatUsesEchoer(t *testing.T) { +// func TestSomethingThatUsesEchoer(t *testing.T) { // -// // make and configure a mocked Echoer -// mockedEchoer := &EchoerMock{ -// EchoFunc: func(ss ...string) []string { -// panic("mock out the Echo method") -// }, -// } +// // make and configure a mocked Echoer +// mockedEchoer := &EchoerMock{ +// EchoFunc: func(ss ...string) []string { +// panic("mock out the Echo method") +// }, +// } // -// // use mockedEchoer in code that requires Echoer -// // and then make assertions. +// // use mockedEchoer in code that requires Echoer +// // and then make assertions. // -// } +// } type EchoerMock struct { // EchoFunc mocks the Echo method. EchoFunc func(ss ...string) []string