Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get call count in thread safe way #965

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

Conversation

nbaztec
Copy link
Contributor

@nbaztec nbaztec commented Jun 16, 2020

Summary

Allows returning number of calls to a specific method in a thread safe way.

Changes

Adds a new method on the Mock object that returns the call count.

Motivation

There was a need to get the call count information on a given mock. Accessing the Calls member leads to a race condition as the lock is neglected.

Example usage (if applicable)

type client interface {
	Do(x int)
}

type owner struct {
	c client
}

func (o owner) Start() chan struct{} {
	done := make(chan struct{})
	go func() {
		for {
			select {
			case <-done:
				return
			case <-time.After(2 * time.Millisecond):
				o.c.Do(1)
			}
		}
	}()

	return done
}

type clientMock struct {
	mock.Mock
}

func (m *clientMock) Do(x int) {
	m.Called(x)
}

func TestOwnerCloses(t *testing.T) {
	m := &clientMock{}
	m.On("Do", mock.AnythingOfType("int"))
	defer m.AssertExpectations(t)

	o := owner{c: m}
	done := o.Start()
	time.Sleep(5*time.Millisecond)	// wait for some time, the client gets called finite amount of times
	close(done)
	time.Sleep(2*time.Millisecond)	// allow closing

	expected := m.NumberOfCalls("Do")
	time.Sleep(10*time.Millisecond)	// test if the client was called during the time
	actual := m.NumberOfCalls("Do")

	assert.Equal(t, expected, actual)
}

Related issues

Closes #964

m.mutex.Lock()
defer m.mutex.Unlock()
var actualCalls int
for _, call := range m.calls() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the mutex locking right before the start of the critical section (i.e. before this line)

// NumberOfCalls returns the number of times a method was called
func (m *Mock) NumberOfCalls(methodName string) int {
m.mutex.Lock()
defer m.mutex.Unlock()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it not make sense to move this unlock to right after the critical section (i.e. between lines 590,591)? My thought is that it being explicitly there will help the critical section unnecessarily ballooning by accident.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd ideally go with defer Unlock since it's more "failsafe" in case the function body panics, however in this case, I don't think the critical section will panic at all. As such the result is identical the Unlock can be moved to end. Though, as long as we're also aware of the fact that any newly introduced code prone to panic will introduce a potential bug in future.

thoughts?

@boyan-soubachov
Copy link
Collaborator

boyan-soubachov commented Jul 27, 2020 via email

mvdkleijn
mvdkleijn previously approved these changes Jul 31, 2020
Copy link
Contributor

@mvdkleijn mvdkleijn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mvdkleijn
Copy link
Contributor

@boyan-soubachov If it looks good to you too, feel free to merge

@@ -1476,6 +1476,17 @@ func Test_MockReturnAndCalledConcurrent(t *testing.T) {
wg.Wait()
}

func Test_Mock_NumberOfCalls(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test which covers the issue you fixed. I would imagine it would fail when run with go test -race and without the fix you've added in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, added the test to demonstrate data race.

Commenting out the mutex yields a data race with go test -v -race -run Test_Mock_NumberOfCallsIsThreadSafe ./...

@boyan-soubachov
Copy link
Collaborator

@boyan-soubachov If it looks good to you too, feel free to merge

The code looks good to me but I think we should have a test that demonstrates the issue (without the fix) and how it's fixed.

Copy link
Collaborator

@boyan-soubachov boyan-soubachov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your time and effort. @mvdkleijn , thoughts?

Copy link
Contributor

@mvdkleijn mvdkleijn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@nbaztec
Copy link
Contributor Author

nbaztec commented Nov 9, 2020

Is this safe to get merged?

@ubaltaci
Copy link

Can we merge this? It seems impl agreed and tests there, also NumberOfCalls method itself should be nice addition to API?

@brackendawson brackendawson added this to the v1.9.0 milestone Feb 23, 2024
@@ -578,6 +578,19 @@ func (m *Mock) IsMethodCallable(t TestingT, methodName string, arguments ...inte
return false
}

// NumberOfCalls returns the number of times a method was called
func (m *Mock) NumberOfCalls(methodName string) int {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not actually NumberOfCalls, it's more like NumberOfCallsWithMethodName. This PR is useful, but do we have to add another method here for people to count calls with certain arguments, or number of calls regardless of method name?

Perhaps something like I mentioned in #1128 is more useful?

@brackendawson brackendawson modified the milestones: v1.9.0, v1.10 Feb 28, 2024
@dolmen dolmen added bug pkg-mock Any issues related to Mock labels Mar 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug pkg-mock Any issues related to Mock
Projects
None yet
Development

Successfully merging this pull request may close these issues.

mock - get call count/list in thread safe way
6 participants