Closed aaronjwood closed 3 years ago
@aaronjwood Can you distill this down to a simpler test case? There are too many gaps for me to understand exactly what is going on here and where the problem might be.
I have the same problem. Easy way to reproduce it:
main.go:
package main
import "fmt"
import "time"
//go:generate go run -mod=mod github.com/golang/mock/mockgen -package main -destination=./mock_pub.go -source=main.go -build_flags=-mod=mod
type Publisher interface {
Publish(i int) error
}
type StdoutPublisher struct {
}
func (StdoutPublisher) Publish(i int) error {
fmt.Println(i)
return nil
}
func PublishInts(p Publisher, n int) {
for i := 0; i < n; i++ {
go p.Publish(i)
}
}
func main() {
p := StdoutPublisher{}
PublishInts(p, 2)
time.Sleep(100 * time.Millisecond)
}
main_test.go:
package main
import (
"testing"
"time"
"github.com/golang/mock/gomock"
)
func TestError1(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockPublisher(ctrl)
N := 2
for i := 0; i < N; i++ {
go m.EXPECT().Publish(gomock.Any()).
DoAndReturn(func(i int) error {
println(i)
return nil
}).Times(1)
}
PublishInts(m, N)
time.Sleep(100 * time.Millisecond)
}
my real code does not run m.EXPECT()
in separated gorutines and I am not able to reproduce this bug with simple code when m.EXPECT()
is run in the same gorutine. But in both cases top of stack traces are exactly the same:
WARNING: DATA RACE
Read at 0x00c000148348 by goroutine 10:
github.com/golang/mock/gomock.(*Call).exhausted()
/home/marigs/play/go/mock_error/vendor/github.com/golang/mock/gomock/call.go:422 +0x5cd
github.com/golang/mock/gomock.(*Call).matches()
/home/marigs/play/go/mock_error/vendor/github.com/golang/mock/gomock/call.go:527 +0x5e7
github.com/golang/mock/gomock.callSet.FindMatch()
/home/marigs/play/go/mock_error/vendor/github.com/golang/mock/gomock/callset.go:73 +0x24d
github.com/golang/mock/gomock.(*Controller).Call.func1()
/home/marigs/play/go/mock_error/vendor/github.com/golang/mock/gomock/controller.go:225 +0x1a9
github.com/golang/mock/gomock.(*Controller).Call()
/home/marigs/play/go/mock_error/vendor/github.com/golang/mock/gomock/controller.go:247 +0xdc
mock_error.(*MockPublisher).Publish()
/home/marigs/play/go/mock_error/mock_pub.go:39 +0x15d
Previous write at 0x00c000148348 by goroutine 8:
github.com/golang/mock/gomock.(*Call).Times()
/home/marigs/play/go/mock_error/vendor/github.com/golang/mock/gomock/call.go:340 +0xce
Is it right to say that gomock isn't thread safe at all? I took a quick look at call.go and saw no protection anywhere. Even the counters are not using CAS logic. Are users of this lib expected to handle safety around the various calls themselves?
There is mutex guarding with the controller. In general expects should be done in the main test goroutine but the mock can be passed around.
If I modify the example listed by marigs you can see this working:
func TestError1(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockPublisher(ctrl)
N := 2
for i := 0; i < N; i++ {
n := i
m.EXPECT().Publish(gomock.Any()).
DoAndReturn(func(i int) error {
println(n)
return nil
}).Times(2)
}
go PublishInts(m, N)
go PublishInts(m, N)
time.Sleep(100 * time.Millisecond)
}
Closing this issue for now. I believe everything should work as expected with proper usage. It might be good to open up an issue and document how asynchronous code should be tested/mocked though.
I am hitting a race in gomock that's very similar to this https://github.com/golang/mock/issues/369
It looks like the main difference is that I'm racing
Call()
withaddAction
where the other issue is racing withCall
andReturn
. Below is the test/code and the stack trace (had to mask out a few things):The meat:
The failures:
Go version:
go version go1.15.8 darwin/amd64
Gomock version:gomock v1.4.3