I'm part way to understanding what is going on. I've compiled myself a debug library of OCMock
so that I can understand where the crash is occurring.
Here's what I've found.
In my original test I call andReturn:
to set a return expectation value:
NSMutableString *copy = [@"foo" mutableCopy];
[(NSString *) [[(id) string expect] andReturn:copy] mutableCopy];
This in turn calls OCMReturnValueProvider
to store a copy
so that it can returned at the appropriate time:
@implementation OCMReturnValueProvider
- (id)initWithValue:(id)aValue
{
self = [super init];
returnValue = [aValue retain];
return self;
}
At this point the debugger says that aValue
is of type __NSCFString
. (Alarm bells ring in my head; isn't that a toll free bridge to an underlying string? Not a reference to the NSMutableString
)
Next the test completes and passes.
However, the problem now occurs when the OCMReturnValueProvider
is dealloc
'd.
@implementation OCMReturnValueProvider
- (void)dealloc
{
[returnValue release];
[super dealloc];
}
The crash happens when [returnValue release]
is called; the OCMReturnValueProvider
is trying to release the __NSCFString
that it retain
ed earlier.
Next, I've switch on NSZombie debugging, which is revealing:
2013-03-12 20:58:19.654 UnitTests[16667:c07] TestItClass/test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned
2013-03-12 20:58:21.778 UnitTests[16667:c07] Re-running: TestItClass/test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned <GHTest: 0x4afc5fd0>
2013-03-12 20:58:21.780 UnitTests[16667:c07] *** -[CFString release]: message sent to deallocated instance 0x4b0b1fe0
The malloc-history (Find Zombie instrument) is helping shed some light on it:
Category Event Type Ref Ct Responsible Caller
CFString (mutable) Malloc 1 -[TestItClass test_2_mutable...]
CFString (mutable) Retain 2 -[OCMReturnValueProvider initWithValue:]
CFString (mutable) Retain 3 -[TestItClass test_2_mutable...]
CFString (mutable) Retain 4 -[TestItClass test_2_mutable...]
CFString (mutable) Release 3 -[TestItClass test_2_mutable...]
CFString (mutable) Release 2 -[TestItClass test_2_mutable...]
CFString (mutable) Release 1 -[TestItClass test_2_mutable...]
CFString (mutable) Release 0 -[TestItClass test_2_mutable...]
CFString (mutable) Zombie -1 -[OCMReturnValueProvider dealloc]
So something in the test class is causing more releases than retains. Why's that happening? Strange!