diff --git a/Source/OCMock/OCMockObject.m b/Source/OCMock/OCMockObject.m index d78cba44..7380e1bd 100644 --- a/Source/OCMock/OCMockObject.m +++ b/Source/OCMock/OCMockObject.m @@ -408,44 +408,40 @@ - (BOOL)handleInvocation:(NSInvocation *)anInvocation [self assertInvocationsArrayIsPresent]; [self addInvocation:anInvocation]; - OCMInvocationStub *stub = [self stubForInvocation:anInvocation]; - if(stub == nil) - return NO; + OCMInvocationStub *stub = nil; + @synchronized(stubs) + { + stub = [self stubForInvocation:anInvocation]; + if(stub == nil) + return NO; - // Retain the stub in case it ends up being removed because we still need it at the end for handleInvocation: - [stub retain]; + // Retain the stub in case it ends up being removed because we still need it at the end for handleInvocation: + [stub retain]; - BOOL removeStub = NO; - @synchronized(expectations) - { - if([expectations containsObject:stub]) + @synchronized(expectations) { - OCMInvocationExpectation *expectation = [self _nextExpectedInvocation]; - if(expectationOrderMatters && (expectation != stub)) + if([expectations containsObject:stub]) { - [NSException raise:NSInternalInconsistencyException - format:@"%@: unexpected method invoked: %@\n\texpected:\t%@", - [self description], [stub description], [[expectations objectAtIndex:0] description]]; - } + OCMInvocationExpectation *expectation = [self _nextExpectedInvocation]; + if(expectationOrderMatters && (expectation != stub)) + { + [NSException raise:NSInternalInconsistencyException + format:@"%@: unexpected method invoked: %@\n\texpected:\t%@", + [self description], [stub description], [[expectations objectAtIndex:0] description]]; + } - // We can't check isSatisfied yet, since the stub won't be satisfied until we call - // handleInvocation: since we'll still have the current expectation in the expectations array, which - // will cause an exception if expectationOrderMatters is YES and we're not ready for any future - // expected methods to be called yet - if(![(OCMInvocationExpectation *)stub isMatchAndReject]) - { - [expectations removeObject:stub]; - removeStub = YES; + // We can't check isSatisfied yet, since the stub won't be satisfied until we call + // handleInvocation: since we'll still have the current expectation in the expectations array, which + // will cause an exception if expectationOrderMatters is YES and we're not ready for any future + // expected methods to be called yet + if(![(OCMInvocationExpectation *)stub isMatchAndReject]) + { + [expectations removeObject:stub]; + [stubs removeObject:stub]; + } } } } - if(removeStub) - { - @synchronized(stubs) - { - [stubs removeObject:stub]; - } - } @try { diff --git a/Source/OCMockTests/OCMockObjectTests.m b/Source/OCMockTests/OCMockObjectTests.m index eb30a0c4..17505884 100644 --- a/Source/OCMockTests/OCMockObjectTests.m +++ b/Source/OCMockTests/OCMockObjectTests.m @@ -1156,4 +1156,23 @@ - (void)testTryingToCreateAnInstanceOfOCMockObjectRaisesAnException XCTAssertThrows([[OCMockObject alloc] init]); } +#pragma mark thread safety + +- (void)testExpectationsAreThreadSafe +{ + size_t concurrentTaskCount = 10000; + + for (size_t i = 0; i < concurrentTaskCount; i++) + { + [[mock expect] lowercaseString]; + } + + dispatch_apply(concurrentTaskCount, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^(size_t iteration) + { + [mock lowercaseString]; + }); + + [mock verify]; +} + @end