diff --git a/README.md b/README.md index e9b727d1..20b2575b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ mockery mockery provides the ability to easily generate mocks for golang interfaces. It removes the boilerplate coding required to use mocks. -[![Linux Build Status](https://travis-ci.org/vektra/mockery.png)](https://travis-ci.org/vektra/mockery) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/vektra/mockery)](https://ci.appveyor.com/project/vektra/mockery) [![GoDoc](https://godoc.org/github.com/vektra/mockery/mockery?status.svg)](https://godoc.org/github.com/vektra/mockery/mockery) [![Go Report Card](https://goreportcard.com/badge/github.com/vektra/mockery)](https://goreportcard.com/report/github.com/vektra/mockery) +[![Linux Build Status](https://travis-ci.org/vektra/mockery.png)](https://travis-ci.org/vektra/mockery) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/vektra/mockery)](https://ci.appveyor.com/project/vektra/mockery) [![GoDoc](https://godoc.org/github.com/vektra/mockery/mockery?status.svg)](https://godoc.org/github.com/vektra/mockery/mockery) [![Go Report Card](https://goreportcard.com/badge/github.com/vektra/mockery)](https://goreportcard.com/report/github.com/vektra/mockery) ### Installation @@ -144,8 +144,8 @@ For example, `panic: assert: arguments: Cannot call Get(0) because there are 0 a #### Notes -This approach should be used judiciously, as return values should generally -not depend on arguments in mocks; however, this approach can be helpful for +This approach should be used judiciously, as return values should generally +not depend on arguments in mocks; however, this approach can be helpful for situations like passthroughs or other test-only calculations. ### Name @@ -170,6 +170,12 @@ This option is only compatible with `-name`. The `-all` option implies `-recursi mockery always generates files with the package `mocks` to keep things clean and simple. You can control which mocks directory is used by using `-output`, which defaults to `./mocks`. +### In Package (-inpkg) and KeepTree (-keeptree) + +For some complex repositories, there could be multiple interfaces with the same name but in different packages. In that case, `-inpkg` allows generate the mocked interfaces directly in the package that it mocks. + +In the case you don't want to generate the mocks into the package but want to keep a similar structure, use the option `-keeptree`. + ## Casing mockery generates files using the casing of the original interface name. This diff --git a/cmd/mockery/mockery.go b/cmd/mockery/mockery.go index 6fc2bee8..fe6fc8f7 100644 --- a/cmd/mockery/mockery.go +++ b/cmd/mockery/mockery.go @@ -29,6 +29,8 @@ type Config struct { fProfile string fVersion bool quiet bool + fkeepTree bool + buildTags string } func main() { @@ -69,6 +71,10 @@ func main() { os.Exit(1) } + if config.fkeepTree { + config.fIP = false + } + if config.fProfile != "" { f, err := os.Create(config.fProfile) if err != nil { @@ -85,10 +91,12 @@ func main() { osp = &mockery.StdoutStreamProvider{} } else { osp = &mockery.FileOutputStreamProvider{ - BaseDir: config.fOutput, - InPackage: config.fIP, - TestOnly: config.fTO, - Case: config.fCase, + BaseDir: config.fOutput, + InPackage: config.fIP, + TestOnly: config.fTO, + Case: config.fCase, + KeepTree: config.fkeepTree, + KeepTreeOriginalDirectory: config.fDir, } } @@ -104,6 +112,7 @@ func main() { Recursive: recursive, Filter: filter, LimitOne: limitOne, + BuildTags: strings.Split(config.buildTags, " "), } generated := walker.Walk(visitor) @@ -133,6 +142,8 @@ func parseConfigFromArgs(args []string) Config { flagSet.StringVar(&config.fProfile, "cpuprofile", "", "write cpu profile to file") flagSet.BoolVar(&config.fVersion, "version", false, "prints the installed version of mockery") flagSet.BoolVar(&config.quiet, "quiet", false, "suppress output to stdout") + flagSet.BoolVar(&config.fkeepTree, "keeptree", false, "keep the tree structure of the original interface files into a different repository. Must be used with XX") + flagSet.StringVar(&config.buildTags, "tags", "", "space-separated list of additional build tags to use") flagSet.Parse(args[1:]) diff --git a/mockery/fixtures/buildtag/comment/custom2_iface.go b/mockery/fixtures/buildtag/comment/custom2_iface.go new file mode 100644 index 00000000..ef527fd4 --- /dev/null +++ b/mockery/fixtures/buildtag/comment/custom2_iface.go @@ -0,0 +1,7 @@ +// +build custom2 + +package comment + +type IfaceWithCustomBuildTagInComment interface { + Sprintf(format string, a ...interface{}) string +} diff --git a/mockery/fixtures/buildtag/comment/custom_iface.go b/mockery/fixtures/buildtag/comment/custom_iface.go new file mode 100644 index 00000000..313b9cef --- /dev/null +++ b/mockery/fixtures/buildtag/comment/custom_iface.go @@ -0,0 +1,7 @@ +// +build custom + +package comment + +type IfaceWithCustomBuildTagInComment interface { + Sprintf(format string, a ...interface{}) string +} diff --git a/mockery/fixtures/struct_value.go b/mockery/fixtures/struct_value.go new file mode 100644 index 00000000..b963772d --- /dev/null +++ b/mockery/fixtures/struct_value.go @@ -0,0 +1,7 @@ +package test + +type B struct{} + +type A interface { + Call() (B, error) +} diff --git a/mockery/generator.go b/mockery/generator.go index fe988f7a..978d43bc 100644 --- a/mockery/generator.go +++ b/mockery/generator.go @@ -267,14 +267,14 @@ func (g *Generator) GeneratePrologue(pkg string) { // GeneratePrologueNote adds a note after the prologue to the output // string. func (g *Generator) GeneratePrologueNote(note string) { - g.printf("// Code generated by mockery v%s\n", SemVer) + g.printf("// Code generated by mockery v%s. DO NOT EDIT.\n", SemVer) if note != "" { g.printf("\n") for _, n := range strings.Split(note, "\\n") { g.printf("// %s\n", n) } - g.printf("\n") } + g.printf("\n") } // ErrNotInterface is returned when the given type is not an interface diff --git a/mockery/generator_test.go b/mockery/generator_test.go index 2c22bfd6..198566bb 100644 --- a/mockery/generator_test.go +++ b/mockery/generator_test.go @@ -254,7 +254,7 @@ func (s *GeneratorSuite) TestGeneratorPrologueNote() { generator := s.getGenerator(testFile, "Requester", false) generator.GeneratePrologueNote("A\\nB") - expected := `// Code generated by mockery v1.0.0 + expected := `// Code generated by mockery v1.0.0. DO NOT EDIT. // A // B @@ -1042,6 +1042,39 @@ import test "github.com/vektra/mockery/mockery/fixtures" s.checkPrologueGeneration(generator, expected) } +func (s *GeneratorSuite) TestGeneratorForStructValueReturn() { + expected := `// A is an autogenerated mock type for the A type +type A struct { + mock.Mock +} + +// Call provides a mock function with given fields: +func (_m *A) Call() (test.B, error) { + ret := _m.Called() + + var r0 test.B + if rf, ok := ret.Get(0).(func() test.B); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(test.B) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} +` + s.checkGeneration( + filepath.Join(fixturePath, "struct_value.go"), "A", false, + expected, + ) +} + func TestGeneratorSuite(t *testing.T) { generatorSuite := new(GeneratorSuite) suite.Run(t, generatorSuite) diff --git a/mockery/outputter.go b/mockery/outputter.go index c96d7043..e0e90e10 100644 --- a/mockery/outputter.go +++ b/mockery/outputter.go @@ -23,10 +23,12 @@ func (this *StdoutStreamProvider) GetWriter(iface *Interface, pkg string) (io.Wr } type FileOutputStreamProvider struct { - BaseDir string - InPackage bool - TestOnly bool - Case string + BaseDir string + InPackage bool + TestOnly bool + Case string + KeepTree bool + KeepTreeOriginalDirectory string } func (this *FileOutputStreamProvider) GetWriter(iface *Interface, pkg string) (io.Writer, error, Cleanup) { @@ -37,7 +39,17 @@ func (this *FileOutputStreamProvider) GetWriter(iface *Interface, pkg string) (i caseName = this.underscoreCaseName(caseName) } - if this.InPackage { + if this.KeepTree { + absOriginalDir, err := filepath.Abs(this.KeepTreeOriginalDirectory) + if err != nil { + return nil, err, func() error { return nil } + } + relativePath := strings.TrimPrefix( + filepath.Join(filepath.Dir(iface.Path), this.filename(caseName)), + absOriginalDir) + path = filepath.Join(this.BaseDir, relativePath) + os.MkdirAll(filepath.Dir(path), 0755) + } else if this.InPackage { path = filepath.Join(filepath.Dir(iface.Path), this.filename(caseName)) } else { path = filepath.Join(this.BaseDir, this.filename(caseName)) @@ -50,7 +62,7 @@ func (this *FileOutputStreamProvider) GetWriter(iface *Interface, pkg string) (i return nil, err, func() error { return nil } } - fmt.Printf("Generating mock for: %s\n", iface.Name) + fmt.Printf("Generating mock for: %s in file: %s\n", iface.Name, path) return f, nil, func() error { return f.Close() } diff --git a/mockery/parse.go b/mockery/parse.go index e1c04c42..0a300d70 100644 --- a/mockery/parse.go +++ b/mockery/parse.go @@ -31,8 +31,8 @@ func NewParser() *Parser { // Initialize the build context (e.g. GOARCH/GOOS fields) so we can use it for respecting // build tags during Parse. - defaultBuildCtx := build.Default - conf.Build = &defaultBuildCtx + buildCtx := build.Default + conf.Build = &buildCtx return &Parser{ parserPackages: make([]*types.Package, 0), @@ -43,6 +43,10 @@ func NewParser() *Parser { } } +func (p *Parser) AddBuildTags(buildTags ...string) { + p.conf.Build.BuildTags = append(p.conf.Build.BuildTags, buildTags...) +} + func (p *Parser) Parse(path string) error { // To support relative paths to mock targets w/ vendor deps, we need to provide eventual @@ -57,11 +61,6 @@ func (p *Parser) Parse(path string) error { return err } - path, err = filepath.EvalSymlinks(path) - if err != nil { - return err - } - dir := filepath.Dir(path) files, err := ioutil.ReadDir(dir) diff --git a/mockery/parse_test.go b/mockery/parse_test.go index 160c1892..07a0083b 100644 --- a/mockery/parse_test.go +++ b/mockery/parse_test.go @@ -85,3 +85,22 @@ func TestBuildTagInComment(t *testing.T) { assert.Equal(t, 1, len(nodes)) assert.Equal(t, "IfaceWithBuildTagInComment", nodes[0].Name) } + +func TestCustomBuildTag(t *testing.T) { + parser := NewParser() + parser.AddBuildTags("custom") + + // Include two files that define the same interface, but with different + // build tags. Only one should be loaded. + err := parser.Parse(getFixturePath("buildtag", "comment", "custom_iface.go")) + assert.NoError(t, err) + err = parser.Parse(getFixturePath("buildtag", "comment", "custom2_iface.go")) + assert.NoError(t, err) + + err = parser.Load() + assert.NoError(t, err) // Expect "redeclared in this block" if tags aren't respected + + nodes := parser.Interfaces() + assert.Equal(t, 1, len(nodes)) + assert.Equal(t, "IfaceWithCustomBuildTagInComment", nodes[0].Name) +} diff --git a/mockery/walker.go b/mockery/walker.go index 4ab8e906..d67f8e11 100644 --- a/mockery/walker.go +++ b/mockery/walker.go @@ -15,6 +15,7 @@ type Walker struct { Recursive bool Filter *regexp.Regexp LimitOne bool + BuildTags []string } type WalkerVisitor interface { @@ -23,6 +24,7 @@ type WalkerVisitor interface { func (this *Walker) Walk(visitor WalkerVisitor) (generated bool) { parser := NewParser() + parser.AddBuildTags(this.BuildTags...) this.doWalk(parser, this.BaseDir, visitor) err := parser.Load() diff --git a/mockery/walker_test.go b/mockery/walker_test.go index 55055ec5..bc72301a 100644 --- a/mockery/walker_test.go +++ b/mockery/walker_test.go @@ -43,8 +43,8 @@ func TestWalkerHere(t *testing.T) { assert.True(t, len(gv.Interfaces) > 10) first := gv.Interfaces[0] - assert.Equal(t, "AsyncProducer", first.Name) - assert.Equal(t, getFixturePath("async.go"), first.Path) + assert.Equal(t, "A", first.Name) + assert.Equal(t, getFixturePath("struct_value.go"), first.Path) } func TestWalkerRegexp(t *testing.T) {