Skip to content

Commit 2b00d33

Browse files
authored
Fix Call.Unset() panic (issue #1236) (#1250)
Unset changes len of a `ExpectedCalls` slice during iteration while using index from original slice, and under some conditions it tries to reslice element beyond of the slice boundaries. That causes panic. The proposed solution uses independent write index to count elements kept in output slice. Tests (the simplest and more complicated cases) and comments are included.
1 parent 9acc222 commit 2b00d33

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

mock/mock.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -218,16 +218,22 @@ func (c *Call) Unset() *Call {
218218

219219
foundMatchingCall := false
220220

221-
for i, call := range c.Parent.ExpectedCalls {
221+
// in-place filter slice for calls to be removed - iterate from 0'th to last skipping unnecessary ones
222+
var index int // write index
223+
for _, call := range c.Parent.ExpectedCalls {
222224
if call.Method == c.Method {
223225
_, diffCount := call.Arguments.Diff(c.Arguments)
224226
if diffCount == 0 {
225227
foundMatchingCall = true
226-
// Remove from ExpectedCalls
227-
c.Parent.ExpectedCalls = append(c.Parent.ExpectedCalls[:i], c.Parent.ExpectedCalls[i+1:]...)
228+
// Remove from ExpectedCalls - just skip it
229+
continue
228230
}
229231
}
232+
c.Parent.ExpectedCalls[index] = call
233+
index++
230234
}
235+
// trim slice up to last copied index
236+
c.Parent.ExpectedCalls = c.Parent.ExpectedCalls[:index]
231237

232238
if !foundMatchingCall {
233239
unlockOnce.Do(c.unlock)

mock/mock_test.go

+74
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,80 @@ func Test_Mock_UnsetIfAlreadyUnsetFails(t *testing.T) {
569569
assert.Equal(t, 0, len(mockedService.ExpectedCalls))
570570
}
571571

572+
func Test_Mock_UnsetByOnMethodSpec(t *testing.T) {
573+
// make a test impl object
574+
var mockedService = new(TestExampleImplementation)
575+
576+
mock1 := mockedService.
577+
On("TheExampleMethod", 1, 2, 3).
578+
Return(0, nil)
579+
580+
assert.Equal(t, 1, len(mockedService.ExpectedCalls))
581+
mock1.On("TheExampleMethod", 1, 2, 3).
582+
Return(0, nil).Unset()
583+
584+
assert.Equal(t, 0, len(mockedService.ExpectedCalls))
585+
586+
assert.Panics(t, func() {
587+
mock1.Unset()
588+
})
589+
590+
assert.Equal(t, 0, len(mockedService.ExpectedCalls))
591+
}
592+
593+
func Test_Mock_UnsetByOnMethodSpecAmongOthers(t *testing.T) {
594+
// make a test impl object
595+
var mockedService = new(TestExampleImplementation)
596+
597+
_, filename, line, _ := runtime.Caller(0)
598+
mock1 := mockedService.
599+
On("TheExampleMethod", 1, 2, 3).
600+
Return(0, nil).
601+
On("TheExampleMethodVariadic", 1, 2, 3, 4, 5).Once().
602+
Return(nil)
603+
mock1.
604+
On("TheExampleMethodFuncType", Anything).
605+
Return(nil)
606+
607+
assert.Equal(t, 3, len(mockedService.ExpectedCalls))
608+
mock1.On("TheExampleMethod", 1, 2, 3).
609+
Return(0, nil).Unset()
610+
611+
assert.Equal(t, 2, len(mockedService.ExpectedCalls))
612+
613+
expectedCalls := []*Call{
614+
{
615+
Parent: &mockedService.Mock,
616+
Method: "TheExampleMethodVariadic",
617+
Repeatability: 1,
618+
Arguments: []interface{}{1, 2, 3, 4, 5},
619+
ReturnArguments: []interface{}{nil},
620+
callerInfo: []string{fmt.Sprintf("%s:%d", filename, line+4)},
621+
},
622+
{
623+
Parent: &mockedService.Mock,
624+
Method: "TheExampleMethodFuncType",
625+
Arguments: []interface{}{Anything},
626+
ReturnArguments: []interface{}{nil},
627+
callerInfo: []string{fmt.Sprintf("%s:%d", filename, line+7)},
628+
},
629+
}
630+
631+
assert.Equal(t, 2, len(mockedService.ExpectedCalls))
632+
assert.Equal(t, expectedCalls, mockedService.ExpectedCalls)
633+
}
634+
635+
func Test_Mock_Unset_WithFuncPanics(t *testing.T) {
636+
// make a test impl object
637+
var mockedService = new(TestExampleImplementation)
638+
mock1 := mockedService.On("TheExampleMethod", 1)
639+
mock1.Arguments = append(mock1.Arguments, func(string) error { return nil })
640+
641+
assert.Panics(t, func() {
642+
mock1.Unset()
643+
})
644+
}
645+
572646
func Test_Mock_Return(t *testing.T) {
573647

574648
// make a test impl object

0 commit comments

Comments
 (0)