-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Description
Testify mocks are currently prone to data races when supplied a pointer argument (that may be concurrently updated elsewhere).
In several instances of our code, we have started seeing data races when running go test -race
that originate from testify code. More specifically these data races occur whenever a mock pointer argument is concurrently modified. The reason being that Arguments.Diff
uses the %v
format specifier to get a presentable string for the argument. This also traverses the pointed-to data structure, which would lead to the data race.
We did not see these issues earlier, but they started appearing lately after we upgraded to go 1.22. Not sure if that is a coincidence, but maybe performance improved in such a way that these data races suddenly started surfacing.
We need to test our code for race conditions with go test -race
, and we cannot have testify be the offender of data races.
A PR to resolve the issue is suggested in #1598.
(It should be noted that it is heavily inspired by #1229, but since that PR hasn't seen any updates in over a year I decided to start fresh).
Step To Reproduce
See test case in suggested fix PR: #1598
Expected behavior
The unit test in #1598 passes when running with go test -race
.
Actual behavior
The unit test fails with a data race:
$ go test -race ./...
ok github.com/stretchr/testify (cached)
ok github.com/stretchr/testify/assert (cached)
ok github.com/stretchr/testify/assert/internal/unsafetests (cached)
? github.com/stretchr/testify/http [no test files]
==================
WARNING: DATA RACE
Write at 0x00c000591f30 by goroutine 127:
github.com/stretchr/testify/mock.Test_CallMockWithConcurrentlyModifiedPointerArg.func1()
/home/peterg/dev/git/testify/mock/mock_test.go:1938 +0x84
Previous read at 0x00c000591f30 by goroutine 126:
reflect.typedmemmove()
/opt/go/src/runtime/mbarrier.go:203 +0x0
reflect.packEface()
/opt/go/src/reflect/value.go:135 +0xc5
reflect.valueInterface()
/opt/go/src/reflect/value.go:1526 +0x179
reflect.Value.Interface()
/opt/go/src/reflect/value.go:1496 +0xb4
fmt.(*pp).printValue()
/opt/go/src/fmt/print.go:769 +0xc5
fmt.(*pp).printValue()
/opt/go/src/fmt/print.go:921 +0x132a
fmt.(*pp).printArg()
/opt/go/src/fmt/print.go:759 +0xb84
fmt.(*pp).doPrintf()
/opt/go/src/fmt/print.go:1174 +0x10ce
fmt.Sprintf()
/opt/go/src/fmt/print.go:239 +0x5c
github.com/stretchr/testify/mock.Arguments.Diff()
/home/peterg/dev/git/testify/mock/mock.go:950 +0x1b2
github.com/stretchr/testify/mock.(*Mock).findExpectedCall()
/home/peterg/dev/git/testify/mock/mock.go:368 +0x147
github.com/stretchr/testify/mock.(*Mock).MethodCalled()
/home/peterg/dev/git/testify/mock/mock.go:476 +0xac
github.com/stretchr/testify/mock.(*Mock).Called()
/home/peterg/dev/git/testify/mock/mock.go:466 +0x195
github.com/stretchr/testify/mock.(*pointerArgMock).Question()
/home/peterg/dev/git/testify/mock/mock_test.go:1919 +0x8c
github.com/stretchr/testify/mock.Test_CallMockWithConcurrentlyModifiedPointerArg()
/home/peterg/dev/git/testify/mock/mock_test.go:1943 +0x28b
testing.tRunner()
/opt/go/src/testing/testing.go:1689 +0x21e
testing.(*T).Run.gowrap1()
/opt/go/src/testing/testing.go:1742 +0x44
Goroutine 127 (running) created at:
github.com/stretchr/testify/mock.Test_CallMockWithConcurrentlyModifiedPointerArg()
/home/peterg/dev/git/testify/mock/mock_test.go:1936 +0x27c
testing.tRunner()
/opt/go/src/testing/testing.go:1689 +0x21e
testing.(*T).Run.gowrap1()
/opt/go/src/testing/testing.go:1742 +0x44
Goroutine 126 (running) created at:
testing.(*T).Run()
/opt/go/src/testing/testing.go:1742 +0x825
testing.runTests.func1()
/opt/go/src/testing/testing.go:2161 +0x85
testing.tRunner()
/opt/go/src/testing/testing.go:1689 +0x21e
testing.runTests()
/opt/go/src/testing/testing.go:2159 +0x8be
testing.(*M).Run()
/opt/go/src/testing/testing.go:2027 +0xf17
main.main()
_testmain.go:267 +0x2bd
==================
--- FAIL: Test_CallMockWithConcurrentlyModifiedPointerArg (0.00s)
testing.go:1398: race detected during execution of test
FAIL
FAIL github.com/stretchr/testify/mock 0.053s
ok github.com/stretchr/testify/require (cached)
ok github.com/stretchr/testify/suite (cached)
FAIL