From 15abd4b7cc100ef8153ca5d6127d4827fa2732b1 Mon Sep 17 00:00:00 2001 From: Dave MacLachlan Date: Thu, 28 May 2020 08:24:42 -0700 Subject: [PATCH] Add location recording to all macros. For large CI systems it can be costly to generate debug information. Recording location data for all of the stub/expect/reject macros makes it a lot easier to record/find failures. --- Source/OCMock/OCMMacroState.h | 6 +-- Source/OCMock/OCMMacroState.m | 36 ++++++++++------ Source/OCMock/OCMRecorder.h | 4 ++ Source/OCMock/OCMRecorder.m | 1 + Source/OCMock/OCMVerifier.h | 4 +- Source/OCMock/OCMVerifier.m | 3 +- Source/OCMock/OCMock.h | 6 +-- Source/OCMockTests/OCMockObjectMacroTests.m | 48 +++++++++++++++++++++ 8 files changed, 85 insertions(+), 23 deletions(-) diff --git a/Source/OCMock/OCMMacroState.h b/Source/OCMock/OCMMacroState.h index a5dda11a..48d623be 100644 --- a/Source/OCMock/OCMMacroState.h +++ b/Source/OCMock/OCMMacroState.h @@ -29,13 +29,13 @@ BOOL invocationDidThrow; } -+ (void)beginStubMacro; ++ (void)beginStubMacroAtLocation:(OCMLocation *)aLocation; + (OCMStubRecorder *)endStubMacro; -+ (void)beginExpectMacro; ++ (void)beginExpectMacroAtLocation:(OCMLocation *)aLocation; + (OCMStubRecorder *)endExpectMacro; -+ (void)beginRejectMacro; ++ (void)beginRejectMacroAtLocation:(OCMLocation *)aLocation; + (OCMStubRecorder *)endRejectMacro; + (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation; diff --git a/Source/OCMock/OCMMacroState.m b/Source/OCMock/OCMMacroState.m index 4cbe244d..8d67f54e 100644 --- a/Source/OCMock/OCMMacroState.m +++ b/Source/OCMock/OCMMacroState.m @@ -25,9 +25,10 @@ @implementation OCMMacroState #pragma mark Methods to begin/end macros -+ (void)beginStubMacro ++ (void)beginStubMacroAtLocation:(OCMLocation *)aLocation { OCMStubRecorder *recorder = [[[OCMStubRecorder alloc] init] autorelease]; + recorder.ocm_location = aLocation; OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder]; [NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState; [macroState release]; @@ -40,21 +41,31 @@ + (OCMStubRecorder *)endStubMacro OCMStubRecorder *recorder = [[(OCMStubRecorder *)[globalState recorder] retain] autorelease]; BOOL didThrow = [globalState invocationDidThrow]; [threadDictionary removeObjectForKey:OCMGlobalStateKey]; - if(didThrow == NO && [recorder didRecordInvocation] == NO) - { - [NSException raise:NSInternalInconsistencyException - format:@"Did not record an invocation in OCMStub/OCMExpect/OCMReject.\n" - @"Possible causes are:\n" - @"- The receiver is not a mock object.\n" - @"- The selector conflicts with a selector implemented by OCMStubRecorder/OCMExpectationRecorder."]; - } + if(didThrow == NO && [recorder didRecordInvocation] == NO) + { + OCMLocation *location = recorder.ocm_location; + NSString *explanation = @"Did not record an invocation in OCMStub/OCMExpect/OCMReject.\n" + @"Possible causes are:\n" + @"- The receiver is not a mock object.\n" + @"- The selector conflicts with a selector implemented by OCMStubRecorder/OCMExpectationRecorder."; + if(location != nil) + { + [NSException raise:NSInternalInconsistencyException + format:@"%@:%d :%@", [location file], (int)[location line], explanation]; + } + else + { + [NSException raise:NSInternalInconsistencyException format:@"%@", explanation]; + } + } return recorder; } -+ (void)beginExpectMacro ++ (void)beginExpectMacroAtLocation:(OCMLocation *)aLocation { OCMExpectationRecorder *recorder = [[[OCMExpectationRecorder alloc] init] autorelease]; + recorder.ocm_location = aLocation; OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder]; [NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState; [macroState release]; @@ -66,9 +77,10 @@ + (OCMStubRecorder *)endExpectMacro } -+ (void)beginRejectMacro ++ (void)beginRejectMacroAtLocation:(OCMLocation *)aLocation { OCMExpectationRecorder *recorder = [[[OCMExpectationRecorder alloc] init] autorelease]; + recorder.ocm_location = aLocation; OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder]; [NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState; [macroState release]; @@ -92,7 +104,7 @@ + (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation + (void)beginVerifyMacroAtLocation:(OCMLocation *)aLocation withQuantifier:(OCMQuantifier *)quantifier { OCMVerifier *recorder = [[[OCMVerifier alloc] init] autorelease]; - [recorder setLocation:aLocation]; + recorder.ocm_location = aLocation; [recorder setQuantifier:quantifier]; OCMMacroState *macroState = [[OCMMacroState alloc] initWithRecorder:recorder]; [NSThread currentThread].threadDictionary[OCMGlobalStateKey] = macroState; diff --git a/Source/OCMock/OCMRecorder.h b/Source/OCMock/OCMRecorder.h index b10d54df..31ba7de1 100644 --- a/Source/OCMock/OCMRecorder.h +++ b/Source/OCMock/OCMRecorder.h @@ -16,6 +16,8 @@ #import +#import "OCMLocation.h" + @class OCMockObject; @class OCMInvocationMatcher; @@ -28,6 +30,8 @@ BOOL shouldReturnMockFromInit; } +// Using `ocm_` prefix to minimize clashes with mocked objects using `location` as a property. +@property(retain) OCMLocation *ocm_location; - (instancetype)init; - (instancetype)initWithMockObject:(OCMockObject *)aMockObject; diff --git a/Source/OCMock/OCMRecorder.m b/Source/OCMock/OCMRecorder.m index a611db72..2bb03b61 100644 --- a/Source/OCMock/OCMRecorder.m +++ b/Source/OCMock/OCMRecorder.m @@ -51,6 +51,7 @@ - (void)setShouldReturnMockFromInit:(BOOL)flag - (void)dealloc { + [_ocm_location release]; [invocationMatcher release]; [super dealloc]; } diff --git a/Source/OCMock/OCMVerifier.h b/Source/OCMock/OCMVerifier.h index d3123d28..2be98323 100644 --- a/Source/OCMock/OCMVerifier.h +++ b/Source/OCMock/OCMVerifier.h @@ -16,13 +16,11 @@ #import "OCMRecorder.h" -@class OCMLocation; @class OCMQuantifier; @interface OCMVerifier : OCMRecorder -@property(strong) OCMLocation *location; -@property(strong) OCMQuantifier *quantifier; +@property(retain) OCMQuantifier *quantifier; - (instancetype)withQuantifier:(OCMQuantifier *)quantifier; diff --git a/Source/OCMock/OCMVerifier.m b/Source/OCMock/OCMVerifier.m index a96bb74b..201b263b 100644 --- a/Source/OCMock/OCMVerifier.m +++ b/Source/OCMock/OCMVerifier.m @@ -44,12 +44,11 @@ - (instancetype)withQuantifier:(OCMQuantifier *)quantifier - (void)forwardInvocation:(NSInvocation *)anInvocation { [super forwardInvocation:anInvocation]; - [mockObject verifyInvocation:invocationMatcher withQuantifier:self.quantifier atLocation:self.location]; + [mockObject verifyInvocation:invocationMatcher withQuantifier:self.quantifier atLocation:self.ocm_location]; } - (void)dealloc { - [_location release]; [_quantifier release]; [super dealloc]; } diff --git a/Source/OCMock/OCMock.h b/Source/OCMock/OCMock.h index 548e2147..c8b9491d 100644 --- a/Source/OCMock/OCMock.h +++ b/Source/OCMock/OCMock.h @@ -48,7 +48,7 @@ #define OCMStub(invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginStubMacro]; \ + [OCMMacroState beginStubMacroAtLocation:OCMMakeLocation(nil, __FILE__, __LINE__)]; \ OCMStubRecorder *recorder = nil; \ @try{ \ invocation; \ @@ -65,7 +65,7 @@ #define OCMExpect(invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginExpectMacro]; \ + [OCMMacroState beginExpectMacroAtLocation:OCMMakeLocation(nil, __FILE__, __LINE__)]; \ OCMStubRecorder *recorder = nil; \ @try{ \ invocation; \ @@ -82,7 +82,7 @@ #define OCMReject(invocation) \ ({ \ _OCMSilenceWarnings( \ - [OCMMacroState beginRejectMacro]; \ + [OCMMacroState beginRejectMacroAtLocation:OCMMakeLocation(nil, __FILE__, __LINE__)]; \ OCMStubRecorder *recorder = nil; \ @try{ \ invocation; \ diff --git a/Source/OCMockTests/OCMockObjectMacroTests.m b/Source/OCMockTests/OCMockObjectMacroTests.m index 959596cb..9e287944 100644 --- a/Source/OCMockTests/OCMockObjectMacroTests.m +++ b/Source/OCMockTests/OCMockObjectMacroTests.m @@ -422,6 +422,54 @@ - (void)testShouldHintAtPossibleReasonWhenVerifyingMethodThatCannotBeMocked } } +- (void)testStubExpectAndRejectShouldCaptureFileAndLineNumbers +{ + NSString *expectedFile = [NSString stringWithUTF8String:__FILE__]; + int expectedLine; + BOOL caughtException = NO; + id realObject = [NSMutableArray array]; + + @try + { + caughtException = NO; + expectedLine = __LINE__; OCMStub([realObject addObject:@"foo"]); + } + @catch (NSException *e) + { + XCTAssertTrue([[e reason] containsString:expectedFile], @"`%@` should contain `%@`", [e reason], expectedFile); + XCTAssertTrue([[e reason] containsString:[[NSNumber numberWithInt:expectedLine] stringValue]], @"`%@` should contain `%d`", [e reason], expectedLine); + caughtException = YES; + } + XCTAssertTrue(caughtException); + + @try + { + caughtException = NO; + expectedLine = __LINE__; OCMExpect([realObject addObject:@"foo"]); + } + @catch (NSException *e) + { + XCTAssertTrue([[e reason] containsString:expectedFile], @"`%@` should contain `%@`", [e reason], expectedFile); + XCTAssertTrue([[e reason] containsString:[[NSNumber numberWithInt:expectedLine] stringValue]], @"`%@` should contain `%d`", [e reason], expectedLine); + caughtException = YES; + } + XCTAssertTrue(caughtException); + + @try + { + caughtException = NO; + expectedLine = __LINE__; OCMReject([realObject addObject:@"foo"]); + } + @catch (NSException *e) + { + XCTAssertTrue([[e reason] containsString:expectedFile], @"`%@` should contain `%@`", [e reason], expectedFile); + XCTAssertTrue([[e reason] containsString:[[NSNumber numberWithInt:expectedLine] stringValue]], @"`%@` should contain `%d`", [e reason], expectedLine); + caughtException = YES; + } + XCTAssertTrue(caughtException); + +} + - (void)testCanExplicitlySelectClassMethodForStubs {