Skip to content

Commit 75c8c15

Browse files
authored
fix(mock): fix race condition in mock.go file (#92)
close 91
1 parent e7faddd commit 75c8c15

File tree

3 files changed

+44
-18
lines changed

3 files changed

+44
-18
lines changed

mock.go

+33-7
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ type Mock interface {
3333
// Mocker implements a Mock capable interface providing
3434
// a default mock configuration used internally to store mocks.
3535
type Mocker struct {
36-
// disabled stores if the current mock is disabled.
37-
disabled bool
36+
// disabler stores a disabler for thread safety checking current mock is disabled
37+
disabler *disabler
3838

39-
// mutex stores the mock mutex for thread safity.
39+
// mutex stores the mock mutex for thread safety.
4040
mutex sync.Mutex
4141

4242
// matcher stores a Matcher capable instance to match the given http.Request.
@@ -49,10 +49,31 @@ type Mocker struct {
4949
response *Response
5050
}
5151

52+
type disabler struct {
53+
// disabled stores if the current mock is disabled.
54+
disabled bool
55+
56+
// mutex stores the disabler mutex for thread safety.
57+
mutex sync.RWMutex
58+
}
59+
60+
func (d *disabler) isDisabled() bool {
61+
d.mutex.RLock()
62+
defer d.mutex.RUnlock()
63+
return d.disabled
64+
}
65+
66+
func (d *disabler) Disable() {
67+
d.mutex.Lock()
68+
defer d.mutex.Unlock()
69+
d.disabled = true
70+
}
71+
5272
// NewMock creates a new HTTP mock based on the given request and response instances.
5373
// It's mostly used internally.
5474
func NewMock(req *Request, res *Response) *Mocker {
5575
mock := &Mocker{
76+
disabler: new(disabler),
5677
request: req,
5778
response: res,
5879
matcher: DefaultMatcher.Clone(),
@@ -65,15 +86,20 @@ func NewMock(req *Request, res *Response) *Mocker {
6586

6687
// Disable disables the current mock manually.
6788
func (m *Mocker) Disable() {
68-
m.disabled = true
89+
m.disabler.Disable()
6990
}
7091

7192
// Done returns true in case that the current mock
7293
// instance is disabled and therefore must be removed.
7394
func (m *Mocker) Done() bool {
95+
// prevent deadlock with m.mutex
96+
if m.disabler.isDisabled() {
97+
return true
98+
}
99+
74100
m.mutex.Lock()
75101
defer m.mutex.Unlock()
76-
return m.disabled || (!m.request.Persisted && m.request.Counter == 0)
102+
return !m.request.Persisted && m.request.Counter == 0
77103
}
78104

79105
// Request returns the Request instance
@@ -91,7 +117,7 @@ func (m *Mocker) Response() *Response {
91117
// Match matches the given http.Request with the current Request
92118
// mock expectation, returning true if matches.
93119
func (m *Mocker) Match(req *http.Request) (bool, error) {
94-
if m.disabled {
120+
if m.disabler.isDisabled() {
95121
return false, nil
96122
}
97123

@@ -141,6 +167,6 @@ func (m *Mocker) decrement() {
141167

142168
m.request.Counter--
143169
if m.request.Counter == 0 {
144-
m.disabled = true
170+
m.disabler.Disable()
145171
}
146172
}

mock_test.go

+10-10
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func TestNewMock(t *testing.T) {
1313
req := NewRequest()
1414
res := NewResponse()
1515
mock := NewMock(req, res)
16-
st.Expect(t, mock.disabled, false)
16+
st.Expect(t, mock.disabler.isDisabled(), false)
1717
st.Expect(t, len(mock.matcher.Get()), len(DefaultMatcher.Get()))
1818

1919
st.Expect(t, mock.Request(), req)
@@ -29,9 +29,9 @@ func TestMockDisable(t *testing.T) {
2929
res := NewResponse()
3030
mock := NewMock(req, res)
3131

32-
st.Expect(t, mock.disabled, false)
32+
st.Expect(t, mock.disabler.isDisabled(), false)
3333
mock.Disable()
34-
st.Expect(t, mock.disabled, true)
34+
st.Expect(t, mock.disabler.isDisabled(), true)
3535

3636
matches, err := mock.Match(&http.Request{})
3737
st.Expect(t, err, nil)
@@ -45,21 +45,21 @@ func TestMockDone(t *testing.T) {
4545
res := NewResponse()
4646

4747
mock := NewMock(req, res)
48-
st.Expect(t, mock.disabled, false)
48+
st.Expect(t, mock.disabler.isDisabled(), false)
4949
st.Expect(t, mock.Done(), false)
5050

5151
mock = NewMock(req, res)
52-
st.Expect(t, mock.disabled, false)
52+
st.Expect(t, mock.disabler.isDisabled(), false)
5353
mock.Disable()
5454
st.Expect(t, mock.Done(), true)
5555

5656
mock = NewMock(req, res)
57-
st.Expect(t, mock.disabled, false)
57+
st.Expect(t, mock.disabler.isDisabled(), false)
5858
mock.request.Counter = 0
5959
st.Expect(t, mock.Done(), true)
6060

6161
mock = NewMock(req, res)
62-
st.Expect(t, mock.disabled, false)
62+
st.Expect(t, mock.disabler.isDisabled(), false)
6363
mock.request.Persisted = true
6464
st.Expect(t, mock.Done(), false)
6565
}
@@ -79,7 +79,7 @@ func TestMockSetMatcher(t *testing.T) {
7979
})
8080
mock.SetMatcher(matcher)
8181
st.Expect(t, len(mock.matcher.Get()), 1)
82-
st.Expect(t, mock.disabled, false)
82+
st.Expect(t, mock.disabler.isDisabled(), false)
8383

8484
matches, err := mock.Match(&http.Request{})
8585
st.Expect(t, err, nil)
@@ -100,7 +100,7 @@ func TestMockAddMatcher(t *testing.T) {
100100
mock.AddMatcher(func(req *http.Request, ereq *Request) (bool, error) {
101101
return true, nil
102102
})
103-
st.Expect(t, mock.disabled, false)
103+
st.Expect(t, mock.disabler.isDisabled(), false)
104104
st.Expect(t, mock.matcher, matcher)
105105

106106
matches, err := mock.Match(&http.Request{})
@@ -127,7 +127,7 @@ func TestMockMatch(t *testing.T) {
127127
calls++
128128
return true, nil
129129
})
130-
st.Expect(t, mock.disabled, false)
130+
st.Expect(t, mock.disabler.isDisabled(), false)
131131
st.Expect(t, mock.matcher, matcher)
132132

133133
matches, err := mock.Match(&http.Request{})

response_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func TestResponseEnableNetworking(t *testing.T) {
146146

147147
func TestResponseDone(t *testing.T) {
148148
res := NewResponse()
149-
res.Mock = &Mocker{request: &Request{Counter: 1}}
149+
res.Mock = &Mocker{request: &Request{Counter: 1}, disabler: new(disabler)}
150150
st.Expect(t, res.Done(), false)
151151
res.Mock.Disable()
152152
st.Expect(t, res.Done(), true)

0 commit comments

Comments
 (0)