stretchr / testify

A toolkit with common assertions and mocks that plays nicely with the standard library
MIT License
22.5k stars 1.56k forks source link

mock: Diff is prone to data races #1597

Open petergardfjall opened 1 month ago

petergardfjall commented 1 month ago

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 https://github.com/stretchr/testify/pull/1598. (It should be noted that it is heavily inspired by https://github.com/stretchr/testify/pull/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: https://github.com/stretchr/testify/pull/1598

Expected behavior

The unit test in https://github.com/stretchr/testify/pull/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