Skip to content

mock: Deadlock if mocking String() #643

@lily-mara

Description

@lily-mara

This is a fairly contrived example, I admit. But this was a real head-scratcher in the wild.

The following code will never complete.

package main

import (
	"fmt"

	"github.com/stretchr/testify/mock"
)

type K struct {
	mock.Mock
}

func (f *K) String() string {
	return f.Called().Get(0).(string)
}

func (f *K) Foo(b B) string {
	return f.Called(b).Get(0).(string)
}

type B struct {
	Kay *K
}

func main() {
	k := &K{}
	b := B{
		Kay: k,
	}

	k.On("String").Return("string-return")
	k.On("Foo", b).Return("foo-return")

	fmt.Printf(k.Foo(b))
}

This fails because Mock.Called calls Mock.MethodCalled, which locks the Mutex, and eventually calls Arguments.Diff. When Arguments.Diff calls fmt.Sprintf, fmt.Sprintf sees the Kay field of B. Since K implements fmt.Stringer, fmt.Sprintf calls K.String. Since this is a mocked function, K.String jumps into Mock.Called, which calls Mock.MethodCalled which waits for the still-locked mutex to unlock, which never happens, because the lock is further up the call chain.

I don't know if this should be considered a bug or not, but it certainly was unexpected, and took a lot of digging to figure out.

Metadata

Metadata

Assignees

Labels

bugpkg-mockAny issues related to Mock

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions