diff --git a/README.md b/README.md index 15ae328..226fa1a 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ import ( // during the development process but not otherwise depended on by built code. ``` -#### Step 2 - Add `go:generate` Directives +#### Step 2a - Add `go:generate` Directives You can add directives right next to your interface definitions (or not), in any `.go` file in your module. @@ -64,6 +64,41 @@ $ go generate ./... Writing `FakeMySpecialInterface` to `foofakes/fake_my_special_interface.go`... Done ``` +#### Step 2b - Add `counterfeiter:generate` Directives + +If you plan to have many directives in a single package, consider using this +option. You can add directives right next to your interface definitions +(or not), in any `.go` file in your module. + +```shell +$ cat myinterface.go +``` + +```go +package foo + +// You only need **one** of these per package! +//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate + +// You will add lots of directives like these in the same package... +//counterfeiter:generate . MySpecialInterface +type MySpecialInterface interface { + DoThings(string, uint64) (int, error) +} + +// Like this... +//counterfeiter:generate . MyOtherInterface +type MyOtherInterface interface { + DoOtherThings(string, uint64) (int, error) +} +``` + +```shell +$ go generate ./... +Writing `FakeMySpecialInterface` to `foofakes/fake_my_special_interface.go`... Done +Writing `FakeMyOtherInterface` to `foofakes/fake_my_other_interface.go`... Done +``` + #### Step 3 - Run `go generate` You can run `go generate` in the directory with your directive, or in the root of your module (to ensure you generate for all packages in your module): @@ -98,7 +133,7 @@ go run github.com/maxbrunsfeld/counterfeiter/v6 USAGE counterfeiter - [-o ] [-p] [--fake-name ] + [-generate] [-o ] [-p] [--fake-name ] [] [-] ``` @@ -112,7 +147,7 @@ $ counterfeiter USAGE counterfeiter - [-o ] [-p] [--fake-name ] + [-generate] [-o ] [-p] [--fake-name ] [] [-] ``` diff --git a/arguments/usage.go b/arguments/usage.go index a598b27..e2926b4 100644 --- a/arguments/usage.go +++ b/arguments/usage.go @@ -3,7 +3,7 @@ package arguments const usage = ` USAGE counterfeiter - [-o ] [-p] [--fake-name ] + [-generate>] [-o ] [-p] [--fake-name ] [] [-] ARGUMENTS @@ -26,6 +26,31 @@ ARGUMENTS Write code to standard out instead of to a file OPTIONS + -generate + Identify all //counterfeiter:generate directives in .go file in the + current working directory and generate fakes for them. You can pass + arguments as usual. + + NOTE: This is not the same as //go:generate directives + (used with the 'go generate' command), but it can be combined with + go generate by adding the following to a .go file: + + # runs counterfeiter in generate mode + //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate + + example: + Add the following to a .go file: + + //counterfeiter:generate . MyInterface + //counterfeiter:generate . MyOtherInterface + //counterfeiter:generate . MyThirdInterface + + # run counterfeiter + counterfeiter -generate + # writes "FakeMyInterface" to ./mypackagefakes/fake_my_interface.go + # writes "FakeMyOtherInterface" to ./mypackagefakes/fake_my_other_interface.go + # writes "FakeMyThirdInterface" to ./mypackagefakes/fake_my_third_interface.go + -o Path to the file or directory for the generated fakes. This also determines the package name that will be used. diff --git a/benchmark_test.go b/benchmark_test.go index b34524c..19aa792 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -10,7 +10,33 @@ import ( "github.com/maxbrunsfeld/counterfeiter/v6/generator" ) -func BenchmarkSingleRun(b *testing.B) { +func BenchmarkWithoutCache(b *testing.B) { + b.StopTimer() + workingDir, err := filepath.Abs(filepath.Join(".", "fixtures")) + if err != nil { + b.Fatal(err) + } + log.SetOutput(ioutil.Discard) + + args := &arguments.ParsedArguments{ + GenerateInterfaceAndShimFromPackageDirectory: false, + SourcePackageDir: workingDir, + PackagePath: workingDir, + OutputPath: filepath.Join(workingDir, "fixturesfakes", "fake_something.go"), + DestinationPackageName: "fixturesfakes", + InterfaceName: "Something", + FakeImplName: "FakeSomething", + PrintToStdOut: false, + } + + cache := &generator.FakeCache{} + b.StartTimer() + for i := 0; i < b.N; i++ { + doGenerate(workingDir, args, cache) + } +} + +func BenchmarkWithCache(b *testing.B) { b.StopTimer() workingDir, err := filepath.Abs(filepath.Join(".", "fixtures")) if err != nil { diff --git a/command/runner.go b/command/runner.go index fd71de7..5589785 100644 --- a/command/runner.go +++ b/command/runner.go @@ -12,9 +12,9 @@ import ( "strings" ) -func Detect(cwd string, args []string) ([]Invocation, error) { - if invokedByGoGenerate() { - return invocations(cwd, args) +func Detect(cwd string, args []string, generateMode bool) ([]Invocation, error) { + if generateMode || invokedByGoGenerate() { + return invocations(cwd, generateMode) } i, err := NewInvocation("", 0, args) if err != nil { @@ -45,7 +45,7 @@ func invokedByGoGenerate() bool { return os.Getenv("DOLLAR") == "$" } -func invocations(cwd string, args []string) ([]Invocation, error) { +func invocations(cwd string, generateMode bool) ([]Invocation, error) { var result []Invocation // Find all the go files pkg, err := build.ImportDir(cwd, build.IgnoreVendor) @@ -59,17 +59,24 @@ func invocations(cwd string, args []string) ([]Invocation, error) { gofiles = append(gofiles, pkg.TestGoFiles...) gofiles = append(gofiles, pkg.XTestGoFiles...) sort.Strings(gofiles) - // Find all the generate statements - line, err := strconv.Atoi(os.Getenv("GOLINE")) - if err != nil { - return nil, err + var line int + if !generateMode { + // generateMode means counterfeiter:generate, not go:generate + line, err = strconv.Atoi(os.Getenv("GOLINE")) + if err != nil { + return nil, err + } } + for i := range gofiles { - i, err := open(cwd, gofiles[i]) + i, err := open(cwd, gofiles[i], generateMode) if err != nil { return nil, err } result = append(result, i...) + if generateMode { + continue + } if len(result) > 0 && result[0].File != os.Getenv("GOFILE") { return nil, nil } @@ -81,9 +88,9 @@ func invocations(cwd string, args []string) ([]Invocation, error) { return result, nil } -var re = regexp.MustCompile(`(?mi)^//go:generate (?:go run github\.com/maxbrunsfeld/counterfeiter/v6|gobin -m -run github\.com/maxbrunsfeld/counterfeiter/v6|counterfeiter|counterfeiter.exe)\s+(.*)?\s*$`) +var re = regexp.MustCompile(`(?mi)^//(go:generate|counterfeiter:generate)\s*(?:go run github\.com/maxbrunsfeld/counterfeiter/v6|gobin -m -run github\.com/maxbrunsfeld/counterfeiter/v6|counterfeiter|counterfeiter.exe)?\s*(.*)?\s*$`) -func open(dir string, file string) ([]Invocation, error) { +func open(dir string, file string, generateMode bool) ([]Invocation, error) { str, err := ioutil.ReadFile(filepath.Join(dir, file)) if err != nil { return nil, err @@ -98,12 +105,21 @@ func open(dir string, file string) ([]Invocation, error) { if match == nil { continue } - - inv, err := NewInvocation(file, line, stringToArgs(match[1])) + inv, err := NewInvocation(file, line, stringToArgs(match[2])) if err != nil { return nil, err } - result = append(result, inv) + + if generateMode && match[1] == "counterfeiter:generate" { + result = append(result, inv) + } + + if !generateMode && match[1] == "go:generate" { + if len(inv.Args) == 2 && strings.EqualFold(strings.TrimSpace(inv.Args[1]), "-generate") { + continue + } + result = append(result, inv) + } } return result, nil diff --git a/command/runner_test.go b/command/runner_test.go index a87ef3a..e59d7d0 100644 --- a/command/runner_test.go +++ b/command/runner_test.go @@ -39,7 +39,7 @@ func testRunner(t *testing.T, when spec.G, it spec.S) { }) it("creates an invocation", func() { - i, err := command.Detect(filepath.Join(".", "..", "fixtures"), []string{"counterfeiter", ".", "AliasedInterface"}) + i, err := command.Detect(filepath.Join(".", "..", "fixtures"), []string{"counterfeiter", ".", "AliasedInterface"}, false) Expect(err).NotTo(HaveOccurred()) Expect(i).NotTo(BeNil()) Expect(i).To(HaveLen(1)) @@ -49,6 +49,28 @@ func testRunner(t *testing.T, when spec.G, it spec.S) { }) }) + when("counterfeiter is invoked in generate mode", func() { + it.Before(func() { + os.Unsetenv("DOLLAR") + os.Unsetenv("GOFILE") + os.Unsetenv("GOLINE") + os.Unsetenv("GOPACKAGE") + }) + + it("creates invocations", func() { + i, err := command.Detect(filepath.Join(".", "..", "fixtures"), []string{"counterfeiter", ".", "AliasedInterface"}, true) + Expect(err).NotTo(HaveOccurred()) + Expect(i).NotTo(BeNil()) + Expect(len(i)).To(Equal(17)) + Expect(i[0].File).To(Equal("aliased_interfaces.go")) + Expect(i[0].Line).To(Equal(7)) + Expect(i[0].Args).To(HaveLen(3)) + Expect(i[0].Args[0]).To(Equal("counterfeiter")) + Expect(i[0].Args[1]).To(Equal(".")) + Expect(i[0].Args[2]).To(Equal("AliasedInterface")) + }) + }) + when("counterfeiter has been invoked by go generate", func() { it.Before(func() { os.Setenv("DOLLAR", "$") @@ -57,11 +79,11 @@ func testRunner(t *testing.T, when spec.G, it spec.S) { os.Setenv("GOPACKAGE", "fixtures") }) - it("creates invocations", func() { - i, err := command.Detect(filepath.Join(".", "..", "fixtures"), []string{"counterfeiter", ".", "AliasedInterface"}) + it("creates invocations but does not include generate mode as an invocation", func() { + i, err := command.Detect(filepath.Join(".", "..", "fixtures"), []string{"counterfeiter", ".", "AliasedInterface"}, false) Expect(err).NotTo(HaveOccurred()) Expect(i).NotTo(BeNil()) - Expect(len(i)).To(BeNumerically(">", 10)) + Expect(len(i)).To(Equal(1)) Expect(i[0].File).To(Equal("aliased_interfaces.go")) Expect(i[0].Line).To(Equal(5)) Expect(i[0].Args).To(HaveLen(3)) @@ -76,7 +98,7 @@ func testRunner(t *testing.T, when spec.G, it spec.S) { }) it("has no invocations", func() { - i, err := command.Detect(filepath.Join(".", "..", "fixtures"), []string{"counterfeiter", ".", "SomeOtherInterface"}) + i, err := command.Detect(filepath.Join(".", "..", "fixtures"), []string{"counterfeiter", ".", "AliasedInterface"}, false) Expect(err).NotTo(HaveOccurred()) Expect(i).To(HaveLen(0)) }) @@ -88,7 +110,7 @@ func testRunner(t *testing.T, when spec.G, it spec.S) { }) it("has no invocations", func() { - i, err := command.Detect(filepath.Join(".", "..", "fixtures"), []string{"counterfeiter", ".", "SomeOtherInterface"}) + i, err := command.Detect(filepath.Join(".", "..", "fixtures"), []string{"counterfeiter", ".", "AliasedInterface"}, false) Expect(err).NotTo(HaveOccurred()) Expect(i).To(HaveLen(0)) }) diff --git a/fixtures/aliased_interfaces.go b/fixtures/aliased_interfaces.go index a8e4cf8..0dc1111 100644 --- a/fixtures/aliased_interfaces.go +++ b/fixtures/aliased_interfaces.go @@ -3,6 +3,8 @@ package fixtures import alias "github.com/maxbrunsfeld/counterfeiter/v6/fixtures/another_package" //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . AliasedInterface +//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate +//counterfeiter:generate . AliasedInterface // AliasedInterface is an interface that embeds an interface in an aliased package. type AliasedInterface interface { diff --git a/fixtures/compound_return.go b/fixtures/compound_return.go index c5033c4..738f6d0 100644 --- a/fixtures/compound_return.go +++ b/fixtures/compound_return.go @@ -1,6 +1,6 @@ package fixtures -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . SomethingElse +//counterfeiter:generate . SomethingElse type SomethingElse interface { ReturnStuff() (a, b int) } diff --git a/fixtures/dot_imports.go b/fixtures/dot_imports.go index f4bc713..40571fe 100644 --- a/fixtures/dot_imports.go +++ b/fixtures/dot_imports.go @@ -6,7 +6,7 @@ import ( . "os" ) -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . DotImports +//counterfeiter:generate . DotImports type DotImports interface { DoThings(io.Writer, *File) *http.Client } diff --git a/fixtures/dup_packages/alias.go b/fixtures/dup_packages/alias.go index 74b5dfc..379f0d4 100644 --- a/fixtures/dup_packages/alias.go +++ b/fixtures/dup_packages/alias.go @@ -6,7 +6,8 @@ import ( "github.com/maxbrunsfeld/counterfeiter/v6/fixtures/dup_packages/b/foo" ) -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . AliasV1 +//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate +//counterfeiter:generate . AliasV1 type AliasV1 interface { a.A afoo.I diff --git a/fixtures/dup_packages/dupA.go b/fixtures/dup_packages/dupA.go index df08ab0..8b1b34a 100644 --- a/fixtures/dup_packages/dupA.go +++ b/fixtures/dup_packages/dupA.go @@ -2,7 +2,7 @@ package dup_packages // import "github.com/maxbrunsfeld/counterfeiter/v6/fixture import "github.com/maxbrunsfeld/counterfeiter/v6/fixtures/dup_packages/a/foo" -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . DupA +//counterfeiter:generate . DupA type DupA interface { A() foo.S } diff --git a/fixtures/dup_packages/dupAB.go b/fixtures/dup_packages/dupAB.go index d080d24..7d6a112 100644 --- a/fixtures/dup_packages/dupAB.go +++ b/fixtures/dup_packages/dupAB.go @@ -1,6 +1,6 @@ package dup_packages // import "github.com/maxbrunsfeld/counterfeiter/v6/fixtures/dup_packages" -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . DupAB +//counterfeiter:generate . DupAB type DupAB interface { DupA DupB diff --git a/fixtures/dup_packages/dupB.go b/fixtures/dup_packages/dupB.go index 4c3a122..88942d0 100644 --- a/fixtures/dup_packages/dupB.go +++ b/fixtures/dup_packages/dupB.go @@ -2,7 +2,7 @@ package dup_packages // import "github.com/maxbrunsfeld/counterfeiter/v6/fixture import "github.com/maxbrunsfeld/counterfeiter/v6/fixtures/dup_packages/b/foo" -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . DupB +//counterfeiter:generate . DupB type DupB interface { B() foo.S } diff --git a/fixtures/dup_packages/dup_packagenames.go b/fixtures/dup_packages/dup_packagenames.go index bf7f6ae..2b22dc9 100644 --- a/fixtures/dup_packages/dup_packagenames.go +++ b/fixtures/dup_packages/dup_packagenames.go @@ -5,7 +5,7 @@ import ( bfoo "github.com/maxbrunsfeld/counterfeiter/v6/fixtures/dup_packages/b/foo" ) -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . AB +//counterfeiter:generate . AB type AB interface { A() foo.S foo.I diff --git a/fixtures/dup_packages/go.mod b/fixtures/dup_packages/go.mod index 6efd7dc..e5fc549 100644 --- a/fixtures/dup_packages/go.mod +++ b/fixtures/dup_packages/go.mod @@ -1 +1,3 @@ module github.com/maxbrunsfeld/counterfeiter/v6/fixtures/dup_packages + +go 1.12 diff --git a/fixtures/embeds_interfaces.go b/fixtures/embeds_interfaces.go index ac33190..2b66dee 100644 --- a/fixtures/embeds_interfaces.go +++ b/fixtures/embeds_interfaces.go @@ -6,7 +6,7 @@ import ( "github.com/maxbrunsfeld/counterfeiter/v6/fixtures/another_package" ) -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . EmbedsInterfaces +//counterfeiter:generate . EmbedsInterfaces type EmbedsInterfaces interface { http.Handler another_package.AnotherInterface diff --git a/fixtures/has_imports.go b/fixtures/has_imports.go index 8fe146d..9097e2a 100644 --- a/fixtures/has_imports.go +++ b/fixtures/has_imports.go @@ -6,7 +6,7 @@ import ( some_alias "os" ) -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . HasImports +//counterfeiter:generate . HasImports type HasImports interface { DoThings(io.Writer, *some_alias.File) *http.Client } diff --git a/fixtures/has_other_types.go b/fixtures/has_other_types.go index 89011ba..4001743 100644 --- a/fixtures/has_other_types.go +++ b/fixtures/has_other_types.go @@ -1,6 +1,6 @@ package fixtures -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . HasOtherTypes +//counterfeiter:generate . HasOtherTypes type HasOtherTypes interface { GetThing(SomeString) SomeFunc } diff --git a/fixtures/has_var_args.go b/fixtures/has_var_args.go index 8c1cba8..8d9e13f 100644 --- a/fixtures/has_var_args.go +++ b/fixtures/has_var_args.go @@ -1,12 +1,12 @@ package fixtures -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . HasVarArgs +//counterfeiter:generate . HasVarArgs type HasVarArgs interface { DoThings(int, ...string) int DoMoreThings(int, int, ...string) int } -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . HasVarArgsWithLocalTypes +//counterfeiter:generate . HasVarArgsWithLocalTypes type HasVarArgsWithLocalTypes interface { DoThings(...LocalType) } diff --git a/fixtures/imports_go_hyphen_package.go b/fixtures/imports_go_hyphen_package.go index e058533..8a2a8ac 100644 --- a/fixtures/imports_go_hyphen_package.go +++ b/fixtures/imports_go_hyphen_package.go @@ -4,7 +4,7 @@ import ( "github.com/maxbrunsfeld/counterfeiter/v6/fixtures/go-hyphenpackage" ) -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . ImportsGoHyphenPackage +//counterfeiter:generate . ImportsGoHyphenPackage type ImportsGoHyphenPackage interface { UseHyphenType(hyphenpackage.HyphenType) } diff --git a/fixtures/multiple_interfaces.go b/fixtures/multiple_interfaces.go index 71c5624..212cf7d 100644 --- a/fixtures/multiple_interfaces.go +++ b/fixtures/multiple_interfaces.go @@ -1,11 +1,11 @@ package fixtures -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . FirstInterface +//counterfeiter:generate . FirstInterface type FirstInterface interface { DoThings() } -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . SecondInterface +//counterfeiter:generate . SecondInterface type SecondInterface interface { EmbeddedMethod() string } diff --git a/fixtures/reuses_arg_types.go b/fixtures/reuses_arg_types.go index ac5b60b..a9f59b8 100644 --- a/fixtures/reuses_arg_types.go +++ b/fixtures/reuses_arg_types.go @@ -1,6 +1,6 @@ package fixtures -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . ReusesArgTypes +//counterfeiter:generate . ReusesArgTypes type ReusesArgTypes interface { DoThings(x, y string) } diff --git a/fixtures/something.go b/fixtures/something.go index 926ee4b..98f106c 100644 --- a/fixtures/something.go +++ b/fixtures/something.go @@ -1,6 +1,6 @@ package fixtures -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . Something +//counterfeiter:generate . Something type Something interface { DoThings(string, uint64) (int, error) DoNothing() diff --git a/fixtures/something_remote.go b/fixtures/something_remote.go index 05daecb..f8f7ef9 100644 --- a/fixtures/something_remote.go +++ b/fixtures/something_remote.go @@ -2,7 +2,7 @@ package fixtures import the_aliased_package "github.com/maxbrunsfeld/counterfeiter/v6/fixtures/aliased_package" -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . SomethingWithForeignInterface +//counterfeiter:generate . SomethingWithForeignInterface // SomethingWithForeignInterface is an interface that embeds a foreign interface. type SomethingWithForeignInterface interface { diff --git a/fixtures/typed_function.go b/fixtures/typed_function.go index cbe79af..980a8d3 100644 --- a/fixtures/typed_function.go +++ b/fixtures/typed_function.go @@ -1,4 +1,4 @@ package fixtures -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . SomethingFactory +//counterfeiter:generate . SomethingFactory type SomethingFactory func(string, map[string]interface{}) string diff --git a/fixtures/unexported.go b/fixtures/unexported.go index ad16481..74fd61b 100644 --- a/fixtures/unexported.go +++ b/fixtures/unexported.go @@ -1,9 +1,9 @@ package fixtures -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . unexportedFunc +//counterfeiter:generate . unexportedFunc type unexportedFunc func(string, map[string]interface{}) string -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . unexportedInterface +//counterfeiter:generate . unexportedInterface type unexportedInterface interface { Method(string, map[string]interface{}) string } diff --git a/main.go b/main.go index 065c833..5beb222 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "errors" + "flag" "fmt" "go/format" "io/ioutil" @@ -59,7 +60,17 @@ func run() error { cache = &generator.Cache{} } var invocations []command.Invocation - invocations, err = command.Detect(cwd, os.Args) + fs := flag.NewFlagSet("counterfeiter", flag.ContinueOnError) + generateFlag := fs.Bool( + "generate", + false, + "Identify all //counterfeiter:generate directives in the current working directory and generate fakes for them", + ) + err = fs.Parse(os.Args[1:]) + if err != nil { + return err + } + invocations, err = command.Detect(cwd, os.Args, *generateFlag) if err != nil { return err }