maxbrunsfeld / counterfeiter Goto Github PK
View Code? Open in Web Editor NEWA tool for generating self-contained, type-safe test doubles in go
License: MIT License
A tool for generating self-contained, type-safe test doubles in go
License: MIT License
I was using counterfeiter and looking at some of the fakes it generated and something jumped out at me as being kind of odd. Is there a particular reason why it generates this line at the end of each fake?
var _ Something = new(FakeSomething)
Fakes save the arguments to calls in a pretty straightforward way: value types are copied, and references to reference types are copied. This works fine for value types, and for reference types in the great majority of cases. However, it falls down a bit where the argument is a reference type which is subsequently mutated, and where you care about its value at the moment of the call, rather than its identity. For example, a byte slice being used as a buffer while working through a stream, or a bytes.Buffer (I struggle to think of any more examples!).
By way of example, this morning i was counterfeiting an interface with this method:
PutBlock(container, name, blockID string, chunk []byte) error
I was testing code that 'put' several different 'blocks', one after the other. Let's say that i was uploading the alphabet in chunks of three letters. One test looked like:
_, _, _, chunk := blobClient.PutBlockArgsForCall(0)
Expect(chunk).To(Equal([]byte{'a', 'b', 'c'}))
This failed; according to the fake, the chunk was {'y', 'z'}. That's because the fake saved the pointer to the slice, and the slice was subsequently filled with more data. It would have been really useful if the fake had somehow saved the {'a', 'b', 'c'} that was in the slice when it was passed.
I ended up modifying the fake by hand to save a copy of the chunk:
copiedChunk := make([]byte, len(chunk))
copy(copiedChunk, chunk)
// ...
fake.putBlockArgsForCall = append(fake.putBlockArgsForCall, struct {
container string
name string
blockID string
chunk []byte
}{container, name, blockID, copiedChunk})
Of course, this will be wiped away if i ever regenerate the fakes.
It would be great if there was a way to fix this properly. Capturing all byte slices as copies would be a breaking change, so perhaps a bad idea. But could there be some way to specify it?
when writing the counterfeit to the same package, the source package is still imported and used in the generated code leading to import cycle not allowed
errors.
package cycle
type Data string
//go:generate counterfeiter -o tester.go . Tester
type Tester interface {
Test() Data
}
[cryptix@higgs ~/go/src/github.com/cryptix/exp/cycle:master] go generate
Wrote `FakeTester` to `tester.go`
[cryptix@higgs ~/go/src/github.com/cryptix/exp/cycle:master] go test
can't load package: import cycle not allowed
package github.com/cryptix/exp/cycle
imports github.com/cryptix/exp/cycle
import cycle not allowed
package github.com/cryptix/exp/cycle
imports github.com/cryptix/exp/cycle
[cryptix@higgs ~/go/src/github.com/cryptix/exp/cycle:master]
Often times we want to use interfaces that exist in the stdlib
Since counterfeiter requires you to point it at the source file which has the interface definition, this is pretty much impossible to do programatically, especially in go:generate comments
This would be a huge win.
Counterfeiter fails to generate fakes for the following interface types.
import alias "github.com/..../something"
type MyInterface interface {
alias.ImportedInterface
}
If you change the findImportPath
to the following piece of code, the problem is half way fixed.
func findImportPath(importSpecs []*ast.ImportSpec, alias string) string {
for _, spec := range importSpecs {
importPath := strings.Trim(spec.Path.Value, `"`)
if path.Base(importPath) == alias {
return importPath
}
if (spec.Name != nil) && (spec.Name.Name == alias) {
return importPath
}
}
return ""
}
However, imports are still messed up. I think the import section in the generated fake has the alias left in front, whereas the parameters in the methods of the aliased interface (as they are copy-pasted from the interface declaration file) don't have any alias. During goimport
the import is considered unused and removed resulting in the fake not compiling.
import (
"net/http"
"sync"
"github.com/GoogleCloudPlatform/kubernetes/pkg/probe/http"
)
In this case, both are required, but one of them needs an alias.
I thought this used to work, so it must be a regression.
test/potato.go:
package test
import (
git "github.com/libgit2/git2go"
)
type Potato interface {
Tomato(git.Oid)
}
after running counterfeiter . Potato
test/testfakes/fake_potato.go:
// This file was generated by counterfeiter
package testfakes
import (
"counterfeiter-bug-poc/test"
"sync"
)
type FakePotato struct {
TomatoStub func(git2go.Oid)
tomatoMutex sync.RWMutex
tomatoArgsForCall []struct {
arg1 git2go.Oid
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakePotato) Tomato(arg1 git2go.Oid) {
fake.tomatoMutex.Lock()
fake.tomatoArgsForCall = append(fake.tomatoArgsForCall, struct {
arg1 git2go.Oid
}{arg1})
fake.recordInvocation("Tomato", []interface{}{arg1})
fake.tomatoMutex.Unlock()
if fake.TomatoStub != nil {
fake.TomatoStub(arg1)
}
}
func (fake *FakePotato) TomatoCallCount() int {
fake.tomatoMutex.RLock()
defer fake.tomatoMutex.RUnlock()
return len(fake.tomatoArgsForCall)
}
func (fake *FakePotato) TomatoArgsForCall(i int) git2go.Oid {
fake.tomatoMutex.RLock()
defer fake.tomatoMutex.RUnlock()
return fake.tomatoArgsForCall[i].arg1
}
func (fake *FakePotato) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.tomatoMutex.RLock()
defer fake.tomatoMutex.RUnlock()
return fake.invocations
}
func (fake *FakePotato) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}
var _ test.Potato = new(FakePotato)
The test script, as run by Travis, runs the tests like this:
go test -race -v . ./arguments ./integration
This does not run the tests in the generator or locator packages. Should it?
The counterfeiter CLI allows you to specify -o
and --fake-name
flags. But it should also allow you to define the package name and path that is generated, for example:
$ counterfeiter github.com/my/package Something -p github.com/my/package/tests/fakes
Wrote `FakeSomething` to `github.com/my/package/tests/fakes/fake_something.go`
An additional complexity here is that I would like to be able to export the fakes to the same package as the interface definition. Reason: currently it is not possible to use counterfeiter-generated fakes in the package defining the interface (e.g. in unit tests) because this results in a cyclic dependency; allowing fakes to reside in the same package fixes this issue.
$ counterfeiter github.com/my/package Something -p github.com/my/package
Wrote `FakeSomething` to `github.com/my/package/fake_something.go`
with:
package metrics
import (
"os"
"time"
"github.com/cloudfoundry-incubator/executor/api"
"github.com/cloudfoundry-incubator/executor/registry"
"github.com/cloudfoundry/dropsonde/autowire/metrics"
)
type Source interface {
CurrentCapacity() registry.Capacity
TotalCapacity() registry.Capacity
GetAllContainers() []api.Container
}
type Reporter struct {
Source Source
Interval time.Duration
}
func (reporter *Reporter) Run(signals <-chan os.Signal, ready chan<- struct{}) error {
close(ready)
for {
metrics.SendValue("capacity.memory", 128, "Metric")
}
}
running:
counterfeiter . Source
results in:
// This file was generated by counterfeiter
package fakes
import (
"sync"
"github.com/cloudfoundry-incubator/executor/api"
"github.com/cloudfoundry-incubator/executor/metrics"
"github.com/cloudfoundry-incubator/executor/registry"
"github.com/cloudfoundry/dropsonde/autowire/metrics"
)
type FakeSource struct {
CurrentCapacityStub func() registry.Capacity
currentCapacityMutex sync.RWMutex
currentCapacityArgsForCall []struct{}
currentCapacityReturns struct {
result1 registry.Capacity
}
TotalCapacityStub func() registry.Capacity
totalCapacityMutex sync.RWMutex
totalCapacityArgsForCall []struct{}
totalCapacityReturns struct {
result1 registry.Capacity
}
GetAllContainersStub func() []api.Container
getAllContainersMutex sync.RWMutex
getAllContainersArgsForCall []struct{}
getAllContainersReturns struct {
result1 []api.Container
}
}
func (fake *FakeSource) CurrentCapacity() registry.Capacity {
fake.currentCapacityMutex.Lock()
defer fake.currentCapacityMutex.Unlock()
fake.currentCapacityArgsForCall = append(fake.currentCapacityArgsForCall, struct{}{})
if fake.CurrentCapacityStub != nil {
return fake.CurrentCapacityStub()
} else {
return fake.currentCapacityReturns.result1
}
}
func (fake *FakeSource) CurrentCapacityCallCount() int {
fake.currentCapacityMutex.RLock()
defer fake.currentCapacityMutex.RUnlock()
return len(fake.currentCapacityArgsForCall)
}
func (fake *FakeSource) CurrentCapacityReturns(result1 registry.Capacity) {
fake.currentCapacityReturns = struct {
result1 registry.Capacity
}{result1}
}
func (fake *FakeSource) TotalCapacity() registry.Capacity {
fake.totalCapacityMutex.Lock()
defer fake.totalCapacityMutex.Unlock()
fake.totalCapacityArgsForCall = append(fake.totalCapacityArgsForCall, struct{}{})
if fake.TotalCapacityStub != nil {
return fake.TotalCapacityStub()
} else {
return fake.totalCapacityReturns.result1
}
}
func (fake *FakeSource) TotalCapacityCallCount() int {
fake.totalCapacityMutex.RLock()
defer fake.totalCapacityMutex.RUnlock()
return len(fake.totalCapacityArgsForCall)
}
func (fake *FakeSource) TotalCapacityReturns(result1 registry.Capacity) {
fake.totalCapacityReturns = struct {
result1 registry.Capacity
}{result1}
}
func (fake *FakeSource) GetAllContainers() []api.Container {
fake.getAllContainersMutex.Lock()
defer fake.getAllContainersMutex.Unlock()
fake.getAllContainersArgsForCall = append(fake.getAllContainersArgsForCall, struct{}{})
if fake.GetAllContainersStub != nil {
return fake.GetAllContainersStub()
} else {
return fake.getAllContainersReturns.result1
}
}
func (fake *FakeSource) GetAllContainersCallCount() int {
fake.getAllContainersMutex.RLock()
defer fake.getAllContainersMutex.RUnlock()
return len(fake.getAllContainersArgsForCall)
}
func (fake *FakeSource) GetAllContainersReturns(result1 []api.Container) {
fake.getAllContainersReturns = struct {
result1 []api.Container
}{result1}
}
var _ metrics.Source = new(FakeSource)
it should probably not
We tried to generate a fake for an interface that looked like this:
type SecurityGroupSpaceBinder interface {
BindSpace(securityGroupGuid, spaceGuid string) error
Counterfeiter generated this fake:
type FakeSecurityGroupSpaceBinder struct {
BindSpaceStub func(securityGroupGuid, spaceGuid string) error
bindSpaceMutex sync.RWMutex
bindSpaceArgsForCall []struct {
arg1 string
}
bindSpaceReturns struct {
result1 error
}
}
The problem being that it expects only a single string argument instead of two. It's easy enough to work around this by always specifying the type for each arg, but I thought it would be worth sharing this bug.
Can you mock something remote? So for example:
counterfeiter github.com/dancannon/gorethin WriteResponse
Before the source code is passed to goimports, counterfeiter generates this import into fixturesfakes/fake_imports_go_hyphen_package.go (from https://github.com/maxbrunsfeld/counterfeiter/blob/b8c427d36da1fb0067515646ab4063195f8f89e0/fixtures/imports_go_hyphen_package.go):
go_hyphenpackage "github.com/maxbrunsfeld/counterfeiter/fixtures/go-hyphenpackage"
However, that's not how the package is used in the code. It's used as hyphenpackage
, so goimports removes this unused import, and inserts a new one. Since I have a fork, and the fork's name is shorter, it selects the fixture from my fork.
When counterfeiting the following interface, with imports:
import (
semver "github.com/cppforlife/go-semi-semantic/version"
boshrel "github.com/cloudfoundry/bosh-cli/release"
)
type ReleaseDir interface {
BuildRelease(name string, version semver.Version, force bool) (boshrel.Release, error)
FinalizeRelease(release boshrel.Release, force bool) error
}
Counterfeiter generates functions that look like the following:
func (fake *FakeReleaseDir) FinalizeRelease(release release.Release, force bool) error {
fake.finalizeReleaseMutex.Lock()
fake.finalizeReleaseArgsForCall = append(fake.finalizeReleaseArgsForCall, struct {
// WARNING, WARNING, AMBIGUOUS REFERENCE!
release release.Release
force bool
}{release, force})
fake.recordInvocation("FinalizeRelease", []interface{}{release, force})
fake.finalizeReleaseMutex.Unlock()
if fake.FinalizeReleaseStub != nil {
return fake.FinalizeReleaseStub(release, force)
} else {
return fake.finalizeReleaseReturns.result1
}
}
and
func (fake *FakeReleaseDir) BuildRelease(name string, version version.Version, force bool) (release.Release, error) {
fake.buildReleaseMutex.Lock()
fake.buildReleaseArgsForCall = append(fake.buildReleaseArgsForCall, struct {
name string
// WARNING, WARNING, AMBIGUOUS REFERENCE!
version version.Version
force bool
}{name, version, force})
fake.recordInvocation("BuildRelease", []interface{}{name, version, force})
fake.buildReleaseMutex.Unlock()
if fake.BuildReleaseStub != nil {
return fake.BuildReleaseStub(name, version, force)
} else {
return fake.buildReleaseReturns.result1, fake.buildReleaseReturns.result2
}
}
Notice the ambiguous use of version
and release
, inside the method it is unclear whether release.Release
is a reference to a package-namespaced type or a data-member of the method parameter release
Love,
Zak
hi,
i think it's cleaner to just output error messages and discard logs ala Wrote A to a.go
.
It should be self evident by the exit code being zero that everything went fine.
Given a type aliased function such as
type RequestFactory func(api.Filter, map[string]interface{}) (*http.Request, error)
a valid counterfeit/spy can be generated that looks like
package fakes
import (
"net/http"
"sync"
"github.com/cloudfoundry-incubator/diego-enabler/api"
"github.com/cloudfoundry-incubator/diego-enabler/commands"
)
type FakeRequestFactory struct {
Stub func(api.Filter, map[string]interface{}) (*http.Request, error)
mutex sync.RWMutex
argsForCall []struct {
arg1 api.Filter
arg2 map[string]interface{}
}
returns struct {
result1 *http.Request
result2 error
}
}
func (fake *FakeRequestFactory) Spy(arg1 api.Filter, arg2 map[string]interface{}) (*http.Request, error) {
fake.mutex.Lock()
fake.argsForCall = append(fake.argsForCall, struct {
arg1 api.Filter
arg2 map[string]interface{}
}{arg1, arg2})
fake.mutex.Unlock()
if fake.Stub != nil {
return fake.Stub(arg1, arg2)
} else {
return fake.returns.result1, fake.returns.result2
}
}
func (fake *FakeRequestFactory) CallCount() int {
fake.mutex.RLock()
defer fake.mutex.RUnlock()
return len(fake.argsForCall)
}
func (fake *FakeRequestFactory) ArgsForCall(i int) (api.Filter, map[string]interface{}) {
fake.mutex.RLock()
defer fake.mutex.RUnlock()
return fake.argsForCall[i].arg1, fake.argsForCall[i].arg2
}
func (fake *FakeRequestFactory) Returns(result1 *http.Request, result2 error) {
fake.Stub = nil
fake.returns = struct {
result1 *http.Request
result2 error
}{result1, result2}
}
var _ commands.RequestFactory = new(FakeRequestFactory).Spy
This spy can be used in a very similar way as other spies:
var fakeRequestFactory *fakes.FakeRequestFactory
BeforeEach(func() {
fakeRequestFactory = new(fakes.FakeRequestFactory)
})
It("does stuff", func() {
fakeRequestFactory.Returns(new(http.Request), errors.New("BOOM"))
results := someFuncWhichUsesIt(fakeRequestFactory.Spy)
Expect(fakeRequestFactory.CallCount()).To(Equal(1))
})
I have a package called md5sum
with an interface FileSummer
. When I run counterfeiter on that, it creates md5sum/md5sumfakes/fake_file_summer.go
but the package inside that generated file is mdsum
- counterfeiter dropped the 5
from the package name.
cc @tjarratt
If publish new version or tag a new release, remove the fix directory so tab completion works better.
For CLI tools it can be helpful to have the go get command at the top of the readme.
This helps users not have to lookup the location of main.go (some projects nest it inside the cmd directory).
Package 'foo' not found on GOPATH
This is mainly an issue when using gvm and symbolically linking a directory back to $GOPATH/src
using gvm linkthis
.
My workaround is to append a directory to the GOPATH
such that the check is satisfied e.g.:
GOPATH=$GOPATH:../../../..
Here's some sample output from a fake logger (let me know if you need more info but it should be clear enough to highlight the issue):
fakes/fake_logger.go:1:1: package comment should be of the form "Package fakes ..."
fakes/fake_logger.go:9:6: exported type FakeLogger should have comment or be unexported
fakes/fake_logger.go:17:1: exported method FakeLogger.LogLine should have comment or be unexported
fakes/fake_logger.go:28:1: exported method FakeLogger.LogLineCallCount should have comment or be unexported
fakes/fake_logger.go:34:1: exported method FakeLogger.LogLineArgsForCall should have comment or be unexported
package race_test
import (
"github.com/yourcode/yourcodefakes"
. "github.com/onsi/ginkgo"
)
var _ = Describe("data race", func() {
It("has a race condition", func() {
myHandler := new(yourcodefakes.FakeHandler)
go func() {
for {
myHandler.SomeMethod()
}
}()
myHandler.SomeMethodStub = func() string {
return "some string"
}
})
})
Is there a preferred way to change the stub without causing a race condition? We experimented with exposing the mutex for SomeMethod
by manually modifying the generated counterfeiter fakes, and locking when reassigning SomeMethodStub
, eg:
myHandler.SomeMethodMutex.Lock()
myHandler.SomeMethodStub = func() string {
return "some string"
}
myHandler.SomeMethodMutex.Unlock()
This fixes the race condition but obviously this requires editing the fake (non-ideal).
If we hid the re-assignment of the stub into a method, we could Lock/Unlock it in that function instead.
// This file was generated by counterfeiter
package yourcodefakes
import (
"sync"
"yourcode"
)
type FakeHandler struct {
someMethodStub func() string
someMethodMutex sync.RWMutex
someMethodArgsForCall []struct{}
someMethodReturns struct {
result1 string
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}
func (fake *FakeHandler) SomeMethod() string {
fake.someMethodMutex.Lock()
fake.someMethodArgsForCall = append(fake.someMethodArgsForCall, struct{}{})
fake.recordInvocation("SomeMethod", []interface{}{})
fake.someMethodMutex.Unlock()
if fake.someMethodStub != nil {
return fake.someMethodStub()
} else {
return fake.someMethodReturns.result1
}
}
func (fake *FakeHandler) SomeMethodCalls(stub func() string) {
fake.someMethodMutex.Lock()
defer fake.someMethodMutex.Unlock()
fake.someMethodStub = stub
}
...
Reproduction:
go get -u github.com/blevesearch/bleve/...
//go:generate counterfeiter -o ./fake_bleve_index.go "github.com/blevesearch/bleve.Index"
export GOPATH="$HOME/go"
When trying to generate fakes for interface that has a method that looks like this:
type Hello interface{
SayHello() (a, b string)
}
The output is the following:
type FakeHello struct {
SayHelloStub func() (a, b string)
sayHelloMutex sync.RWMutex
sayHelloArgsForCall []struct{}
sayHelloReturns struct {
result1 string
}
}
func (fake *FakeHello) SayHello() (a, b string) {
fake.sayHelloMutex.Lock()
fake.sayHelloArgsForCall = append(fake.sayHelloArgsForCall, struct{}{})
fake.sayHelloMutex.Unlock()
if fake.SayHelloStub != nil {
return fake.SayHelloStub()
} else {
return fake.sayHelloReturns.result1
}
}
func (fake *FakeHello) SayHelloCallCount() int {
fake.sayHelloMutex.RLock()
defer fake.sayHelloMutex.RUnlock()
return len(fake.sayHelloArgsForCall)
}
func (fake *FakeHello) SayHelloReturns(result1 string) {
fake.SayHelloStub = nil
fake.sayHelloReturns = struct {
result1 string
}{result1}
}
The second return value is missing from the sayHelloReturns
struct, so the fake is not implementing the Hello interface.
This doesn't work, it ignores the -o option:
counterfeiter -o fakeSomething.go some/imported/package.Something
The problem is that this line is in the if branch: https://github.com/maxbrunsfeld/counterfeiter/blob/master/arguments/parser.go#L51
Is there a reason for this? I moved this line outside the if, and it seems to work fine.
Edit: there's already a pull request for this: #67
In #33 i changed how slices are handled in fakes: they are copied, rather than stored by reference.
However, i forgot about nil. Because of the unique way Go represents slices, a nil pointer to a slice looks quite like an empty slice, and my copying code doesn't distinguish between them. Passing a nil to a fake leads to an empty slice being stored.
Sorry about this. I'll try to PR a fix for this later on today.
I am unable to run counterfeiter on Windows. I tryed the same command on MacOS and I'm getting no error.
counterfeiter-test> counterfeiter services/io_services.go IoService
format.Node internal error (5:2: invalid import path: "github.com\\xcomponent\\counterfeiter-test\\services")
I tried running from a vanilla project with a single interface and I'm getting the same result. Any additional information could help you out ?
When embedding interfaces, counterfeiter resolves the package path to packages in the GOPATH for vendored libraries. The expected behaviour would be that it resolves these package paths to vendor first before looking in the GOPATH. This can lead to subtle issues like the wrong fake being generated for interfaces with embedded interfaces from vendored libraries because another version of the package happens to be installed in the GOPATH as well.
I've reproduced the issue here: github.com/beatrichartz/counterfeiter-vendor-issue
I have an interface:
import "google.golang.org/api/storage/v1"
type StorageService interface {
Buckets(string) (*storage.Buckets, error)
}
counterfeiter panics with
$ counterfeiter . StorageService
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x1239a8]
goroutine 1 [running]:
panic(0x199d00, 0xc42000e0d0)
/usr/local/Cellar/go/1.7.1/libexec/src/runtime/panic.go:500 +0x1a1
github.com/maxbrunsfeld/counterfeiter/astutil.InjectAlias.func1(0x406420, 0xc4200a6a20, 0xc41fffa401)
/Users/pivotal/go/src/github.com/maxbrunsfeld/counterfeiter/astutil/mutator.go:11 +0x988
go/ast.inspector.Visit(0xc42023cf60, 0x406420, 0xc4200a6a20, 0x2f56e0, 0xc42023cf60)
/usr/local/Cellar/go/1.7.1/libexec/src/go/ast/walk.go:373 +0x3a
go/ast.Walk(0x2f56e0, 0xc42023cf60, 0x406420, 0xc4200a6a20)
/usr/local/Cellar/go/1.7.1/libexec/src/go/ast/walk.go:52 +0x63
go/ast.Walk(0x2f56e0, 0xc42023cf60, 0x2f5e20, 0xc42009ab80)
/usr/local/Cellar/go/1.7.1/libexec/src/go/ast/walk.go:74 +0xd22
go/ast.Walk(0x2f56e0, 0xc42023cf60, 0x2f5e60, 0xc42007ecf0)
/usr/local/Cellar/go/1.7.1/libexec/src/go/ast/walk.go:84 +0x152
go/ast.Walk(0x2f56e0, 0xc42023cf60, 0x2f5ee0, 0xc4200a6aa0)
/usr/local/Cellar/go/1.7.1/libexec/src/go/ast/walk.go:168 +0x22df
go/ast.Inspect(0x2f5ee0, 0xc4200a6aa0, 0xc42023cf60)
/usr/local/Cellar/go/1.7.1/libexec/src/go/ast/walk.go:385 +0x4b
github.com/maxbrunsfeld/counterfeiter/astutil.InjectAlias(0xc4200a6aa0, 0xc42007fe90, 0xc42007ff20)
/Users/pivotal/go/src/github.com/maxbrunsfeld/counterfeiter/astutil/mutator.go:23 +0x92
github.com/maxbrunsfeld/counterfeiter/generator.CodeGenerator.fixup(0x7fff5fbff788, 0xe, 0xc42007b760, 0x1, 0x1, 0xc42009a816, 0x27, 0xc42007a750, 0x3, 0x1, ...)
/Users/pivotal/go/src/github.com/maxbrunsfeld/counterfeiter/generator/generator.go:191 +0x80
github.com/maxbrunsfeld/counterfeiter/generator.CodeGenerator.buildASTForFake(0x7fff5fbff788, 0xe, 0xc42007b760, 0x1, 0x1, 0xc42009a816, 0x27, 0xc42007a750, 0x3, 0x1, ...)
/Users/pivotal/go/src/github.com/maxbrunsfeld/counterfeiter/generator/generator.go:61 +0x1a3
github.com/maxbrunsfeld/counterfeiter/generator.CodeGenerator.GenerateFake(0x7fff5fbff788, 0xe, 0xc42007b760, 0x1, 0x1, 0xc42009a816, 0x27, 0xc42007a750, 0x3, 0x1, ...)
/Users/pivotal/go/src/github.com/maxbrunsfeld/counterfeiter/generator/generator.go:29 +0xc2
main.main()
/Users/pivotal/go/src/github.com/maxbrunsfeld/counterfeiter/main.go:57 +0x3f9
The issue is fixed when I alias the import:
import storage "google.golang.org/api/storage/v1"
type StorageService interface {
Buckets(string) (*storage.Buckets, error)
}
type Registry interface {
ListComponents(ctx api.Context, labels labels.Selector, fields fields.Selector) (*api.ComponentList, error)
...
}
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
)
...
func (fake *MockRegistry) ListComponents(ctx api.Context, labels labels.Selector, fields fields.Selector) (*api.ComponentList, error) {
fake.listComponentsMutex.Lock()
fake.listComponentsArgsForCall = append(fake.listComponentsArgsForCall, struct {
ctx api.Context
labels labels.Selector
fields fields.Selector
}{ctx, labels, fields})
fake.listComponentsMutex.Unlock()
if fake.ListComponentsStub != nil {
return fake.ListComponentsStub(ctx, labels, fields)
} else {
return fake.listComponentsReturns.result1, fake.listComponentsReturns.result2
}
}
See labels labels.Selector
.
In this case, labels is the variable name used by the interface definition, which shadows the import. Shadowing is technically fine for usage, as long as the function using it doesn't need to access the import with the same name. Tho it may not pass linting...
The golang convention of having your package name match your directory name is really convenient, but you can also create packages whose name does not match their directory name. In this case, it seems like counterfeiter
can't find the package.
Example:
> find .
imDoinYouAFavor
imDoinYouAFavor/Jake.go
imDoinYouAFavor/Jake.go
package forgetAboutItJake
type ItsChinaTown interface{}
> counterfeiter ./imDoinYouAFavor/Jake.go ItsChinaTown
Couldn't find package 'imDoinYouAFavor' in directory
> echo $?
1
I also tried to use the package name instead of the dirname but that didn't seem to work either.
I have a package 'bar' defined in a project/foo-bar/bar.go
When trying to run counterfeiter
$ counterfeiter project/foo-bar Bar
I get format.Node internal error (1:12: expected ':' , found '-')
This comes from line 59 in this file https://golang.org/src/go/format/format.go
I believe this is caused by the way the fake package name is created by parsing directories and package name.
The commit that introduced this behavior is 9db5229
I plan on digging into it this weekend and making a PR, but feel free to beat me to it.
We invoked scripts/test.sh
on a Windows 10 64-bit machine.
scripts/test.sh
Generating fakes used by tests...
Path 'C:\Users\saint\go\src\github.com\maxbrunsfeld\counterfeiter\arguments' is not on GOPATH
Path 'C:\Users\saint\AppData\Local\Temp\symlinked_fixtures' is not on GOPATH
exit status 1
Our GOPATH:
echo %GOPATH%
C:\Users\saint\go
We believe that error is triggered on this line, which mistakenly splits the GOPATH on :
, not realizing that in Windows, the :
is part of the path (e.g C:\
) and not necessarily a separator.
We are using go
1.8:
go version
go version go1.8 windows/amd64
I've been using counterfeiter a lot lately. One thing I notice is that once I generate fakes for two or more packages I'm likely to want to use fakes from multiple packages in a single test. This often means I have to alias my imports...
import (
"net/http"
"os"
dbFakes "github.com/tjarratt/foo/database/fakes"
httpFakes "github.com/tjarratt/foo/http/fakes"
)
This kind of sucks because existing tooling doesn't have a great answer to aliased packages (I'm looking at you, vim, emacs, Intelli-J and Sublime), and you constantly have to remember if you can use the fakes
package name as is, or if you need to alias it.
What if (hear me out here), we followed the standard library model, and put our fakes into a "__fakes" package? For example, "net/http" has a "net/http/httptest" package that exposes some convenient test helpers. I believe that pivotal-golang/lager does this as well. This pattern seems to be emergent, and convenient since the _fakes
package name is much less likely to collide, but still captures the same spirit of the standard library.
For existing code using the fakes
package, we could write a "go fix" script that automatically rewrites your code and puts the source files in the (new) correct package. That should be a relatively painless way of dealing with the breakage for current users. (Tip of the hat to @robdimsdale for suggesting this).
I would like to issue a PR to counterfeiter that changes fakes
to xyzfakes
where xyz
is the name of the package being faked.
Thoughts? Concerns? This is a breaking change, so I wanted to put this out here and solicit feedback first.
... especially in light of breaking changes like #28.
Helpful for ensuring team doesn't get out of sync on tool versions.
The help manual for counterfeiter prints:
OPTIONS
-o
Path to the file or directory for the generated fakes.
This also determines the package name that will be used.
By default, the generated fakes will be generated in
the package "xyzfakes" which is nested in package "xyz",
where "xyz" is the name of referenced package.
I tried using -o
without success. Here's what I did:
I have an interface in packageA
named MyInterface
.
From the root of my project, I run:
counterfeiter -o ./fakes MyInterface ./packageA
And the output is:
No such file or directory ''
I tried many other variations and couldn't get any to work. Suggestions?
Firstly, why is the output of the counterfeiting redirected to /dev/null? At one point, i broke counterfeiter so that it panicked when run, but because the output is thrown away, the test script gave me no clue as to what was going on. I think it would be more useful to let the output go to the console. It's a bit more verbose, but when counterfeiting works, only one line per package.
Secondly, the last line in the script cleans up arguments/argumentsfakes, but this is never created. Should it be, or can this line be removed?
Thirdly, the tests don't use the generated fakes. Rather, they use the checked-in fakes. Is this deliberate, and if so, is this wise?
If it is deliberate, then the test script is really doing two completely separate things: on the one hand, generating and compiling a set of fakes, which is a kind of smoke test, and on the other, running the tests. I think it would be helpful to rearrange the test script slightly to make this clearer; at present, it looks like the counterfeiting is setup for the tests, which is not the case.
If this is not deliberate, and the test script is supposed to be regenerating the checked-in fakes, then let's fix it!
Personally, i would suggest that the test script should regenerate the checked-in fakes. The way testing works at the moment is a bit weird; if i change the counterfeiting code, it's not enough to re-run the tests, or even the test script; i have to manually regenerate the fakes, and then run the tests. This seems scandalously error-prone - if i forget to re-generate the fakes, i can check in broken code, but have the build pass.
I'm happy to PR an updated script which fixes any of these problems.
Hi,
I think using the import path would be handier than the filesystem path to locate the interface.
It can be looked up converted to a fs path fairly easy by using the go/build package.
// findRelImport tries to find the import string in the $GOPATH
// and constructs a filepath relative to the current wroking directory
func findRelImport(imp string) string {
p, err := build.Default.Import(imp, "", build.FindOnly)
checkPanic(err)
cwd, err := os.Getwd()
checkPanic(err)
p.Dir, err = filepath.Rel(cwd, p.Dir)
checkPanic(err)
return p.Dir
}
Having this made moving and renaming packages much more comfortable with other tools but I guess we should have both options for compatibility and flexibility.
I can whip up a preliminary PR if you agree.
If your GOPATH contains multiple directories the order in which they appear seems to matter. I'm running this in a docker container (golang:latest) with my GOPATH=/root/go:/usergo
and then trying to run counterfeiter on a file in /usergo
(usergo is a mounted volume from the host machine). I get a Could not find interface
error from counterfeiter. But if I follow the exact same steps with my GOPATH set to /usergo:/root/go
then everything works fine.
I can't reproduce this on my local machine but this behavior is consistent on golang:latest
The fake seems to just copy all the import statements from the real file. We have a member of a value object in the real file that required an import, but which is not used anywhere in the fake. So we can't build after generating a fake for one of these files because the fake has an unused import we have to remove manually.
I am seeing a race condition sporadically when calling different methods in parallel. It only happens when different methods are called, since calling the same method in parallel leads to the method mutex being invoked, and preventing race conditions.
An example test case (nested within the existing when two methods are called at the same time describe block):
It("records its calls without race conditions", func(done Done) {
go fake.DoNothing()
go fake.DoThings("abc", 1)
go fake.DoASlice([]byte{})
Eventually(len(fake.Invocations()["DoNothing"]), 1.0).Should(Equal(1))
Eventually(len(fake.Invocations()["DoThings"]), 1.0).Should(Equal(1))
Eventually(len(fake.Invocations()["DoASlice"]), 1.0).Should(Equal(1))
close(done)
})
Leads to output like:
WARNING: DATA RACE
Write by goroutine 17:
github.com/maxbrunsfeld/counterfeiter/fixtures/fixturesfakes.(*FakeSomething).DoThings()
/Users/bazza/go/src/github.com/maxbrunsfeld/counterfeiter/fixtures/fixturesfakes/fake_something.go:43 +0x297
Previous read by goroutine 15:
github.com/maxbrunsfeld/counterfeiter_test.glob.func1.14.2()
/Users/bazza/go/src/github.com/maxbrunsfeld/counterfeiter/counterfeiter_test.go:195 +0x197
(and a lot more redacted for the sake of brevity)
Attempting to sleuth a little, it seems like this was probably a result of trying to record invocations. The counterfeit method for DoASlice for instance looks like this:
func (fake *FakeSomething) DoASlice(arg1 []byte) {
var arg1Copy []byte
if arg1 != nil {
arg1Copy = make([]byte, len(arg1))
copy(arg1Copy, arg1)
}
fake.doASliceMutex.Lock()
fake.doASliceArgsForCall = append(fake.doASliceArgsForCall, struct {
arg1 []byte
}{arg1Copy})
fake.guard("DoASlice")
fake.invocations["DoASlice"] = append(fake.invocations["DoASlice"], []interface{}{arg1Copy})
fake.doASliceMutex.Unlock()
if fake.DoASliceStub != nil {
fake.DoASliceStub(arg1)
}
}
Note that there is a mutex for the specific DoASlice method, but not for the call to fake.guard
and fake.invocations["DoASlice"]
which both mutate the fake.invocations
field. If calls were made to multiple methods on the same fake, they would both mutate the fake.invocations
field at the same time.
Unfortunately I can't get the test case to be 100% deterministic, since one goroutine might complete before the other starts. However, running 3 methods in parallel (as in the above test case) seems to be more consistent than just 2.
Say I have
package foo
type Foo interface {
Eat(chocolate Bar)
}
type Bar struct {
Filling string
}
the generated fake will look something like:
package foo_fake
type FakeFoo struct {
<snip>
}
func (f *FakeFoo) Eat(arg1 Bar) {
<snip>
}
That Bar
in Eat
won't work -- it needs to import foo
and Eat(arg1 foo.Bar)
When you have a vendored library in your project and you use a fully qualified import path to generate a fake, counterfeiter won't search for it in the vendored directory.
E.g.:
mkdir -p vendor/foo/bar/baz/
echo "package baz\ntype Baz interface {\nBaz() string\n}\n" > vendor/foo/bar/baz/baz.go
counterfeiter foo/bar/baz.Baz
Package 'foo/bar/baz' not found on GOPATH
Can we run go fmt
on the outputted files after creation?
This causes an import cycle:
package counterfeit
//go:generate counterfeiter . Fooer
type Fooer interface {
Foo() bool
}
func Bar(f Fooer) string {
if f.Foo() {
return "foo"
} else {
return "bar"
}
}
package counterfeit
import (
"temp/counterfeit/counterfeitfakes"
"testing"
)
func TestBar(t *testing.T) {
fake := &counterfeitfakes.FakeFooer{}
Bar(fake)
}
This is caused by the interface check at the bottom of fake_fooer.go:
var _ counterfeit.Fooer = new(FakeFooer)
One solution is to change the test file's package to counterfeit_tests
, but that doesn't work if I want to test unexported functions.
#8 is similar, but that one is solvable by renaming the import. This isn't.
Is this check really necessary though? If you try to use the fake in a test, and it no longer satisfies the interface, it'll fail to compile anyway (well, except if you're doing something tricky with interface{}).
Could you maybe add an option to disable this check?
Running the test suit with ./scripts/test.sh works the first time but hangs on race condition the second time.
I was able to rerun the tests after running git clean -fx
I was trying to figure out if I had the latest version of counterfeiter and was imaging that I could run something along the lines of
counterfeiter -v
Maybe I get a git sha back of the latest commit? A friendly version number would be nice but maybe I am asking too much?
Generating fakes for any interface including src-d/go-git/CloneOptions as a method parameter results in the following nil pointer dereference:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x52c3b2]
goroutine 1 [running]:
github.com/maxbrunsfeld/counterfeiter/astutil.InjectAlias.func1(0x70be00, 0xc420156880, 0x70bf01)
/go/src/github.com/maxbrunsfeld/counterfeiter/astutil/mutator.go:9 +0x132
go/ast.inspector.Visit(0xc420162340, 0x70be00, 0xc420156880, 0x70b700, 0xc420162340)
/usr/local/go/src/go/ast/walk.go:373 +0x3a
go/ast.Walk(0x70b700, 0xc420162340, 0x70be00, 0xc420156880)
/usr/local/go/src/go/ast/walk.go:52 +0x66
go/ast.Walk(0x70b700, 0xc420162340, 0x70be40, 0xc420158570)
/usr/local/go/src/go/ast/walk.go:84 +0x156
go/ast.Walk(0x70b700, 0xc420162340, 0x70bec0, 0xc4201558c0)
/usr/local/go/src/go/ast/walk.go:165 +0x2473
go/ast.Inspect(0x70bec0, 0xc4201558c0, 0xc420162340)
/usr/local/go/src/go/ast/walk.go:385 +0x4b
github.com/maxbrunsfeld/counterfeiter/astutil.InjectAlias(0xc4201558c0, 0xc420158c00, 0xc420158c90)
/go/src/github.com/maxbrunsfeld/counterfeiter/astutil/mutator.go:23 +0x94
github.com/maxbrunsfeld/counterfeiter/generator.CodeGenerator.fixup(0x7ffd9dd7ce28, 0x4, 0xc420164030, 0x1, 0x1, 0xc42004a708, 0x29, 0xc420012970, 0x4, 0x1, ...)
/go/src/github.com/maxbrunsfeld/counterfeiter/generator/generator.go:202 +0x80
github.com/maxbrunsfeld/counterfeiter/generator.CodeGenerator.buildASTForFake(0x7ffd9dd7ce28, 0x4, 0xc420164030, 0x1, 0x1, 0xc42004a708, 0x29, 0xc420012970, 0x4, 0x1, ...)
/go/src/github.com/maxbrunsfeld/counterfeiter/generator/generator.go:61 +0x199
github.com/maxbrunsfeld/counterfeiter/generator.CodeGenerator.GenerateFake(0x7ffd9dd7ce28, 0x4, 0xc420164030, 0x1, 0x1, 0xc42004a708, 0x29, 0xc420012970, 0x4, 0x1, ...)
/go/src/github.com/maxbrunsfeld/counterfeiter/generator/generator.go:29 +0xa2
main.generateFake(0x7ffd9dd7ce28, 0x4, 0xc42004a700, 0x31, 0x0, 0x0, 0xc42004a800, 0x39, 0xc42004a82d, 0x4, ...)
/go/src/github.com/maxbrunsfeld/counterfeiter/main.go:64 +0x1dc
main.main()
/go/src/github.com/maxbrunsfeld/counterfeiter/main.go:43 +0x29f
util/vgit/vGit.go:3: running "counterfeiter": exit status 2
With these interfaces:
type A interface {
Foo() int
}
type B interface {
A
}
I get an error like:
garden(master*): counterfeiter warden/ Backend
panic: interface conversion: ast.Expr is *ast.Ident, not *ast.FuncType
goroutine 16 [running]:
runtime.panic(0x1a9d00, 0x20856e140)
/usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/panic.c:279 +0xf5
github.com/maxbrunsfeld/counterfeiter/generator.CodeGenerator.typeDecl(0x7fff5fbff9f6, 0x7, 0x208412472, 0xb, 0x2083e0a63, 0x5, 0x208426400, 0x2084186f0, 0x1, 0x1, ...)
/Users/Alex/go/src/github.com/maxbrunsfeld/counterfeiter/generator/generator.go:108 +0x10f
github.com/maxbrunsfeld/counterfeiter/generator.CodeGenerator.sourceFile(0x7fff5fbff9f6, 0x7, 0x208412472, 0xb, 0x2083e0a63, 0x5, 0x208426400, 0x2084186f0, 0x1, 0x1, ...)
/Users/Alex/go/src/github.com/maxbrunsfeld/counterfeiter/generator/generator.go:38 +0x90
github.com/maxbrunsfeld/counterfeiter/generator.CodeGenerator.GenerateFake(0x7fff5fbff9f6, 0x7, 0x208412472, 0xb, 0x2083e0a63, 0x5, 0x208426400, 0x2084186f0, 0x1, 0x1, ...)
/Users/Alex/go/src/github.com/maxbrunsfeld/counterfeiter/generator/generator.go:26 +0xca
main.main()
/Users/Alex/go/src/github.com/maxbrunsfeld/counterfeiter/main.go:82 +0x5f1
goroutine 17 [runnable]:
runtime.MHeap_Scavenger()
/usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/mheap.c:507
runtime.goexit()
/usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:1445
goroutine 18 [runnable]:
bgsweep()
/usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/mgc0.c:1976
runtime.goexit()
/usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:1445
goroutine 19 [runnable]:
runfinq()
/usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/mgc0.c:2606
runtime.goexit()
/usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:1445
I suppose it should 'flatten' the interface and bring in all of B's methods as well.
I'm evaluating Counterfeiter as compared to mockery, mockgen, etc. Something I noticed is that this invocation:
$ counterfeiter . XYZClient - > /tmp/z
Produces a file ending with a diagnostic message:
$ tail -3 /tmp/z
var _ XYZClient = new(FakeXYZClient)
Wrote `FakeXYZClient` to `xyzfakes/fake_xyzclient.go`
Probably this message (and any other similar) should go to stderr
instead.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.