From cefa7d2f09f77b15f7d2ab56b45816da936c48d3 Mon Sep 17 00:00:00 2001 From: Dave MacLachlan Date: Tue, 26 May 2020 12:49:34 -0700 Subject: [PATCH] Add support for having warnings vs errors A basic framework for dealing with issues that users may want to treat as either warnings or errors. It supports setting a default from compile time, modifying it at runtime using an environment variable, and push/popping states off of a stack. It also changes some exceptions that were being thrown to issues specifically because those exceptions are making it very difficult for me to transition my codebase from OCMock 3.4.1 to OCMock 3.6, and I know it will also be an issue for https://github.com/TextureGroup/Texture moving to OCMock 3.6. --- OCMock.podspec | 2 +- Source/OCMock.xcodeproj/project.pbxproj | 30 ++++ Source/OCMock/OCMIssueReporter.h | 61 +++++++ Source/OCMock/OCMIssueReporter.m | 166 ++++++++++++++++++ Source/OCMock/OCMock.h | 1 + Source/OCMock/OCMockObject.m | 24 ++- Source/OCMockTests/OCMIssueReporterTests.m | 105 +++++++++++ .../OCMockObjectClassMethodMockingTests.m | 5 +- .../OCMockTests/OCMockObjectInternalTests.m | 7 +- Source/OCMockTests/OCMockObjectTests.m | 4 +- .../OCMockObjectVerifyAfterRunTests.m | 4 +- 11 files changed, 398 insertions(+), 11 deletions(-) create mode 100644 Source/OCMock/OCMIssueReporter.h create mode 100644 Source/OCMock/OCMIssueReporter.m create mode 100644 Source/OCMockTests/OCMIssueReporterTests.m diff --git a/OCMock.podspec b/OCMock.podspec index 109cdb66..0106c088 100644 --- a/OCMock.podspec +++ b/OCMock.podspec @@ -33,7 +33,7 @@ Pod::Spec.new do |s| "OCMLocation.h", "OCMMacroState.h", "OCMRecorder.h", "OCMStubRecorder.h", "NSNotificationCenter+OCMAdditions.h", "OCMFunctions.h", "OCMVerifier.h", "OCMQuantifier.h", - "OCMockMacros.h" + "OCMockMacros.h", "OCMIssueReporter.h" ] .map { |file| "Source/OCMock/" + file } end diff --git a/Source/OCMock.xcodeproj/project.pbxproj b/Source/OCMock.xcodeproj/project.pbxproj index 1056a514..1c53f54d 100644 --- a/Source/OCMock.xcodeproj/project.pbxproj +++ b/Source/OCMock.xcodeproj/project.pbxproj @@ -294,6 +294,18 @@ 8BF7402124772B0600B9A52C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03565A1D18F05626003AE91E /* XCTest.framework */; }; 8BF7402224772B0800B9A52C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03565A1D18F05626003AE91E /* XCTest.framework */; }; 8BF7402324772B0800B9A52C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03565A1D18F05626003AE91E /* XCTest.framework */; }; + 8B911C8C247B97A500A3581F /* OCMIssueReporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B911C8B247B97A500A3581F /* OCMIssueReporterTests.m */; }; + 8B911C8D247B97A500A3581F /* OCMIssueReporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B911C8B247B97A500A3581F /* OCMIssueReporterTests.m */; }; + 8BF74028247AE3F100B9A52C /* OCMIssueReporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF74026247AE3F100B9A52C /* OCMIssueReporter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8BF74029247AE3F100B9A52C /* OCMIssueReporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF74026247AE3F100B9A52C /* OCMIssueReporter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8BF7402A247AE3F100B9A52C /* OCMIssueReporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF74026247AE3F100B9A52C /* OCMIssueReporter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8BF7402B247AE3F100B9A52C /* OCMIssueReporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF74026247AE3F100B9A52C /* OCMIssueReporter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8BF7402C247AE3F100B9A52C /* OCMIssueReporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BF74026247AE3F100B9A52C /* OCMIssueReporter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8BF7402D247AE3F100B9A52C /* OCMIssueReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF74027247AE3F100B9A52C /* OCMIssueReporter.m */; }; + 8BF7402E247AE3F100B9A52C /* OCMIssueReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF74027247AE3F100B9A52C /* OCMIssueReporter.m */; }; + 8BF7402F247AE3F100B9A52C /* OCMIssueReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF74027247AE3F100B9A52C /* OCMIssueReporter.m */; }; + 8BF74030247AE3F100B9A52C /* OCMIssueReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF74027247AE3F100B9A52C /* OCMIssueReporter.m */; }; + 8BF74031247AE3F100B9A52C /* OCMIssueReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF74027247AE3F100B9A52C /* OCMIssueReporter.m */; }; 8DE97C5522B43EE60098C63F /* OCMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B3159E146333BF0052CD09 /* OCMockObject.m */; }; 8DE97C5622B43EE60098C63F /* OCClassMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B3158C146333BF0052CD09 /* OCClassMockObject.m */; }; 8DE97C5722B43EE60098C63F /* OCPartialMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B315AA146333BF0052CD09 /* OCPartialMockObject.m */; }; @@ -588,7 +600,10 @@ 8B11D4B62448E2E900247BE2 /* OCMCPlusPlus98Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = OCMCPlusPlus98Tests.mm; sourceTree = ""; }; 8B11D4B92448E53600247BE2 /* OCMCPlusPlus11Tests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = OCMCPlusPlus11Tests.mm; sourceTree = ""; }; 8B3786A724E5BD5600FD1B5B /* OCMFunctionsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OCMFunctionsTests.m; sourceTree = ""; }; + 8B911C8B247B97A500A3581F /* OCMIssueReporterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCMIssueReporterTests.m; sourceTree = ""; }; 8BF73E52246CA75E00B9A52C /* OCMNoEscapeBlockTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCMNoEscapeBlockTests.m; sourceTree = ""; }; + 8BF74026247AE3F100B9A52C /* OCMIssueReporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMIssueReporter.h; sourceTree = ""; }; + 8BF74027247AE3F100B9A52C /* OCMIssueReporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCMIssueReporter.m; sourceTree = ""; }; 8DE97CA022B43EE60098C63F /* OCMock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OCMock.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A02926811CA0725A00594AAF /* TestObjects.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = TestObjects.xcdatamodel; sourceTree = ""; }; D31108AD1828DB8700737925 /* OCMockLibTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OCMockLibTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -769,6 +784,7 @@ 03B316211463350E0052CD09 /* OCMConstraintTests.m */, 8B11D4B62448E2E900247BE2 /* OCMCPlusPlus98Tests.mm */, 8B11D4B92448E53600247BE2 /* OCMCPlusPlus11Tests.mm */, + 8B911C8B247B97A500A3581F /* OCMIssueReporterTests.m */, 2FA28EDBF243639C57F88A1B /* OCMArgTests.m */, 036865631D3571A8005E6BEE /* OCMQuantifierTests.m */, 03B316291463350E0052CD09 /* OCObserverMockObjectTests.m */, @@ -816,6 +832,8 @@ 037ECD5618FB0D2E00AF0E4C /* Helper */ = { isa = PBXGroup; children = ( + 8BF74026247AE3F100B9A52C /* OCMIssueReporter.h */, + 8BF74027247AE3F100B9A52C /* OCMIssueReporter.m */, 03E98D4918F308B400522D42 /* OCMLocation.h */, 03E98D4A18F308B400522D42 /* OCMLocation.m */, 2FA28006D043CBDBBAEF6E3F /* OCMMacroState.h */, @@ -996,6 +1014,7 @@ 0368656D1D357317005E6BEE /* OCMQuantifier.h in Headers */, 3C0FF06A1BAA3FD10021AD20 /* OCMFunctionsPrivate.h in Headers */, 03B31613146333C00052CD09 /* OCProtocolMockObject.h in Headers */, + 8BF74028247AE3F100B9A52C /* OCMIssueReporter.h in Headers */, 2FA287ACE547BB41937BDEC3 /* NSObject+OCMAdditions.h in Headers */, 2FA28641AAD0AC2F876C9E48 /* OCMInvocationMatcher.h in Headers */, 0322DA6919118B4600CACAF1 /* OCMVerifier.h in Headers */, @@ -1039,6 +1058,7 @@ 03B315FB146333C00052CD09 /* OCMRealObjectForwarder.h in Headers */, 03DCED6F183406DA0059089E /* NSObject+OCMAdditions.h in Headers */, 0368656E1D357318005E6BEE /* OCMQuantifier.h in Headers */, + 8BF74029247AE3F100B9A52C /* OCMIssueReporter.h in Headers */, 817EB1661BD7674D0047E85A /* OCMFunctionsPrivate.h in Headers */, 03B31605146333C00052CD09 /* OCObserverMockObject.h in Headers */, 03B3160A146333C00052CD09 /* OCPartialMockObject.h in Headers */, @@ -1094,6 +1114,7 @@ 817EB15B1BD765130047E85A /* OCMFunctions.h in Headers */, 817EB15C1BD765130047E85A /* OCMBlockArgCaller.h in Headers */, 033AB1FD24F046C7002014AE /* OCMockMacros.h in Headers */, + 8BF7402B247AE3F100B9A52C /* OCMIssueReporter.h in Headers */, 817EB15D1BD765130047E85A /* OCMArgAction.h in Headers */, 2FA28806443827E286F12F6F /* OCMNonRetainingObjectReturnValueProvider.h in Headers */, ); @@ -1130,6 +1151,7 @@ 8DE97C9122B43EE60098C63F /* OCMFunctionsPrivate.h in Headers */, 8DE97C9222B43EE60098C63F /* OCObserverMockObject.h in Headers */, 8DE97C9322B43EE60098C63F /* OCMObserverRecorder.h in Headers */, + 8BF7402C247AE3F100B9A52C /* OCMIssueReporter.h in Headers */, 8DE97C9422B43EE60098C63F /* OCMPassByRefSetter.h in Headers */, 8DE97C9522B43EE60098C63F /* NSInvocation+OCMAdditions.h in Headers */, 8DE97C9622B43EE60098C63F /* NSMethodSignature+OCMAdditions.h in Headers */, @@ -1184,6 +1206,7 @@ F0B9514A1B00810C00942C38 /* OCMFunctions.h in Headers */, 2FA28B7BDB3319A499E90525 /* OCMBlockArgCaller.h in Headers */, 033AB1FC24F046C7002014AE /* OCMockMacros.h in Headers */, + 8BF7402A247AE3F100B9A52C /* OCMIssueReporter.h in Headers */, 2FA280E60213BA09F007C173 /* OCMArgAction.h in Headers */, 2FA28AFBD67EAB9DD1F23BF5 /* OCMNonRetainingObjectReturnValueProvider.h in Headers */, ); @@ -1440,6 +1463,7 @@ 03B315B6146333BF0052CD09 /* NSMethodSignature+OCMAdditions.m in Sources */, 03618D85195B553400389166 /* OCMRecorder.m in Sources */, 03C7BF0C195DA2F200A545DD /* OCMInvocationStub.m in Sources */, + 8BF7402D247AE3F100B9A52C /* OCMIssueReporter.m in Sources */, 03B315BB146333BF0052CD09 /* NSNotificationCenter+OCMAdditions.m in Sources */, 0322DA6B19118B4600CACAF1 /* OCMVerifier.m in Sources */, 03B315C0146333BF0052CD09 /* OCClassMockObject.m in Sources */, @@ -1482,6 +1506,7 @@ 03B315B8146333BF0052CD09 /* NSMethodSignature+OCMAdditions.m in Sources */, 03618D86195B553400389166 /* OCMRecorder.m in Sources */, 03C7BF0D195DA2F200A545DD /* OCMInvocationStub.m in Sources */, + 8BF7402E247AE3F100B9A52C /* OCMIssueReporter.m in Sources */, 03B315BD146333BF0052CD09 /* NSNotificationCenter+OCMAdditions.m in Sources */, 0322DA6C19118B4600CACAF1 /* OCMVerifier.m in Sources */, 03B315C2146333BF0052CD09 /* OCClassMockObject.m in Sources */, @@ -1529,6 +1554,7 @@ 03E98D5018F310EE00522D42 /* OCMockObjectMacroTests.m in Sources */, 03565A4A18F05721003AE91E /* OCObserverMockObjectTests.m in Sources */, 03565A4318F05721003AE91E /* OCMockObjectClassMethodMockingTests.m in Sources */, + 8B911C8C247B97A500A3581F /* OCMIssueReporterTests.m in Sources */, 03565A4918F05721003AE91E /* OCMArgTests.m in Sources */, 036865641D3571A8005E6BEE /* OCMQuantifierTests.m in Sources */, 0322DA65191188D100CACAF1 /* OCMockObjectVerifyAfterRunTests.m in Sources */, @@ -1556,6 +1582,7 @@ 817EB1181BD765130047E85A /* OCClassMockObject.m in Sources */, 817EB1191BD765130047E85A /* OCPartialMockObject.m in Sources */, 817EB11A1BD765130047E85A /* OCProtocolMockObject.m in Sources */, + 8BF74030247AE3F100B9A52C /* OCMIssueReporter.m in Sources */, 817EB11B1BD765130047E85A /* OCMRecorder.m in Sources */, 817EB11C1BD765130047E85A /* OCMStubRecorder.m in Sources */, 817EB11D1BD765130047E85A /* OCMExpectationRecorder.m in Sources */, @@ -1598,6 +1625,7 @@ 8DE97C5622B43EE60098C63F /* OCClassMockObject.m in Sources */, 8DE97C5722B43EE60098C63F /* OCPartialMockObject.m in Sources */, 8DE97C5822B43EE60098C63F /* OCProtocolMockObject.m in Sources */, + 8BF74031247AE3F100B9A52C /* OCMIssueReporter.m in Sources */, 8DE97C5922B43EE60098C63F /* OCMRecorder.m in Sources */, 03A1CC9F23F8A045005ADA04 /* OCMQuantifier.m in Sources */, 8DE97C5A22B43EE60098C63F /* OCMStubRecorder.m in Sources */, @@ -1645,6 +1673,7 @@ D31108C51828DBD600737925 /* OCMockObjectClassMethodMockingTests.m in Sources */, D31108C71828DBD600737925 /* OCMConstraintTests.m in Sources */, D31108C91828DBD600737925 /* OCObserverMockObjectTests.m in Sources */, + 8B911C8D247B97A500A3581F /* OCMIssueReporterTests.m in Sources */, 037ECD5518FAD84100AF0E4C /* OCMInvocationMatcherTests.m in Sources */, D31108C41828DBD600737925 /* OCMockObjectPartialMocksTests.m in Sources */, D31108C81828DBD600737925 /* OCMStubRecorderTests.m in Sources */, @@ -1672,6 +1701,7 @@ F0B9510D1B0080EC00942C38 /* OCClassMockObject.m in Sources */, F0B9510E1B0080EC00942C38 /* OCPartialMockObject.m in Sources */, F0B9510F1B0080EC00942C38 /* OCProtocolMockObject.m in Sources */, + 8BF7402F247AE3F100B9A52C /* OCMIssueReporter.m in Sources */, F0B951101B0080EC00942C38 /* OCMRecorder.m in Sources */, F0B951111B0080EC00942C38 /* OCMStubRecorder.m in Sources */, F0B951121B0080EC00942C38 /* OCMExpectationRecorder.m in Sources */, diff --git a/Source/OCMock/OCMIssueReporter.h b/Source/OCMock/OCMIssueReporter.h new file mode 100644 index 00000000..ebf22c2d --- /dev/null +++ b/Source/OCMock/OCMIssueReporter.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2014-2020 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import "OCMFunctions.h" + +// Use this define to be able to control the default issue treatment from a build. +// It can be overridden by using `OCMIssueTreatmentDefaultEnvironmentVariable`. +#ifndef OCMIssueTreatmentDefault +#define OCMIssueTreatmentDefault OCMIssueTreatmentWarnings +#endif + +// Use this to control the default issue treatment through the environment. +// It can be set to 0 for warnings or 1 for errors. +#define OCMIssueTreatmentDefaultEnvironmentVariable @"OCMIssueTreatmentDefault" + +// The name of NSExceptions thrown by default when an issue it treated as an error. +OCMOCK_EXTERN NSExceptionName const OCMIssueException; + +typedef NS_ENUM(NSUInteger, OCMIssueTreatment) +{ + // Warnings are printed to stderr. + OCMIssueTreatmentWarnings = 0, + // Errors are thrown an NSExceptions. + OCMIssueTreatmentErrors, +}; + +@interface OCMIssueReporter : NSObject +{ + NSMutableArray *issueTreatmentStack; +} + ++ (instancetype)defaultReporter; + +- (void)reportIssueInFile:(const char *)file line:(NSUInteger)line format:(NSString *)format, ... NS_FORMAT_FUNCTION(3,4); +- (void)reportIssueInFilev:(const char *)file line:(NSUInteger)line format:(NSString *)format arguments:(va_list)args NS_FORMAT_FUNCTION(3,0); +- (void)reportIssueInFile:(const char *)file line:(NSUInteger)line exceptionName:(NSExceptionName)name reason:(NSString *)reason; + +// Pushes/Pops an issue treatment on the stack. Push and Pop can only be called from the main thread and must be balanced. +- (void)pushIssueTreatment:(OCMIssueTreatment)treatment; +- (void)popIssueTreatment; + +// The current issue treatment on the top of the stack. +- (OCMIssueTreatment)issueTreatment; + +@end + +#define OCM_REPORT_ISSUE(_format, ...) ([[OCMIssueReporter defaultReporter] reportIssueInFile:__FILE__ line:__LINE__ format:_format, __VA_ARGS__ ]) diff --git a/Source/OCMock/OCMIssueReporter.m b/Source/OCMock/OCMIssueReporter.m new file mode 100644 index 00000000..5009ed60 --- /dev/null +++ b/Source/OCMock/OCMIssueReporter.m @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2014-2020 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import "OCMIssueReporter.h" + +NSExceptionName const OCMIssueException = @"OCMIssueException"; + +@implementation OCMIssueReporter + ++ (instancetype)defaultReporter +{ + static dispatch_once_t onceToken; + static OCMIssueReporter *defaultReporter; + dispatch_once(&onceToken, ^{ + defaultReporter = [[OCMIssueReporter alloc] init]; + atexit_b(^{ + if([defaultReporter->issueTreatmentStack count] != 1) + { + [NSException raise:NSInternalInconsistencyException format:@"Unmatched push/pops on OCMIssueRecorder"]; + } + }); + }); + return defaultReporter; +} + +- (instancetype)init +{ + if((self = [super init])) + { + NSDictionary *environment = [[NSProcessInfo processInfo] environment]; + OCMIssueTreatment value; + NSString *stringValue = [environment objectForKey:OCMIssueTreatmentDefaultEnvironmentVariable]; + if(stringValue) + { + value = [stringValue integerValue]; + } + else + { + value = OCMIssueTreatmentDefault; + } + if(value > OCMIssueTreatmentErrors) + { + [NSException raise:NSInvalidArgumentException format:@"OCMIssueReporter has invalid default issue treatment: %d", (int)value]; + } + issueTreatmentStack = [[NSMutableArray alloc] init]; + [issueTreatmentStack addObject:[NSNumber numberWithInteger:value]]; + } + return self; +} + +- (void)dealloc +{ + [issueTreatmentStack release]; + [super dealloc]; +} + +- (void)reportIssueInFile:(const char *)file line:(NSUInteger)line format:(NSString *)format, ... +{ + va_list arguments; + va_start(arguments, format); + [self reportIssueInFilev:file line:line format:format arguments:arguments]; + va_end(arguments); +} + +- (void)reportIssueInFilev:(const char *)file line:(NSUInteger)line format:(NSString *)format arguments:(va_list)arguments +{ + NSString *description = [[NSString alloc] initWithFormat:format arguments:arguments]; + [self reportIssueInFile:file line:line exceptionName:OCMIssueException reason:description]; + [description release]; +} + +- (void)reportIssueInFile:(const char *)file line:(NSUInteger)line exceptionName:(NSExceptionName)name reason:(NSString *)reason +{ + OCMIssueTreatment treatment = [self issueTreatment]; + NSString *fullDescription; + NSString *type; + switch(treatment) + { + case OCMIssueTreatmentWarnings: + type = @"warning"; + break; + + case OCMIssueTreatmentErrors: + type = @"error"; + break; + + default: + [NSException raise:NSInternalInconsistencyException format:@"Unknown issue treatment: %d", (int)treatment]; + break; + } + if(file) + { + fullDescription = [[NSString alloc] initWithFormat:@"%s:%d:0: %@: %@", file, (int)line, type, reason]; + } + else + { + fullDescription = [[NSString alloc] initWithFormat:@"%@: %@", type, reason]; + } + switch(treatment) + { + case OCMIssueTreatmentWarnings: + fprintf(stderr, "%s\n", [fullDescription UTF8String]); + break; + + case OCMIssueTreatmentErrors: + [NSException raise:name format:@"%@", fullDescription]; + break; + + default: + [NSException raise:NSInternalInconsistencyException format:@"Unknown issue treatment: %d", (int)treatment]; + break; + } +} + +- (void)pushIssueTreatment:(OCMIssueTreatment)treatment +{ + if(![NSThread isMainThread]) + { + [NSException raise:NSInternalInconsistencyException format:@"pushIssueTreatment can only be called on main thread"]; + } + @synchronized(self) + { + [issueTreatmentStack addObject:[NSNumber numberWithInteger:treatment]]; + } +} + +- (void)popIssueTreatment +{ + if(![NSThread isMainThread]) + { + [NSException raise:NSInternalInconsistencyException format:@"popIssueTreatment can only be called on main thread"]; + } + + @synchronized(self) + { + if([issueTreatmentStack count] == 1) + { + [NSException raise:NSInternalInconsistencyException format:@"unbalanced calls to pushIssueTreatment/popIssueTreatment"]; + } + [issueTreatmentStack removeLastObject]; + } +} + +- (OCMIssueTreatment)issueTreatment +{ + OCMIssueTreatment treatment; + @synchronized(self) + { + treatment = [[issueTreatmentStack lastObject] integerValue]; + } + return treatment; +} +@end diff --git a/Source/OCMock/OCMock.h b/Source/OCMock/OCMock.h index f6365722..d7019705 100644 --- a/Source/OCMock/OCMock.h +++ b/Source/OCMock/OCMock.h @@ -26,3 +26,4 @@ #import #import #import +#import diff --git a/Source/OCMock/OCMockObject.m b/Source/OCMock/OCMockObject.m index ec652b5d..08f156c6 100644 --- a/Source/OCMock/OCMockObject.m +++ b/Source/OCMock/OCMockObject.m @@ -28,7 +28,7 @@ #import "OCObserverMockObject.h" #import "OCPartialMockObject.h" #import "OCProtocolMockObject.h" - +#import "OCMIssueReporter.h" @implementation OCMockObject @@ -131,7 +131,10 @@ - (NSString *)description - (void)addStub:(OCMInvocationStub *)aStub { - [self assertInvocationsArrayIsPresent]; + if(![self checkThatInvocationsArrayIsPresent]) + { + return; + } @synchronized(stubs) { [stubs addObject:aStub]; @@ -157,12 +160,15 @@ - (void)addExpectation:(OCMInvocationExpectation *)anExpectation } } -- (void)assertInvocationsArrayIsPresent +- (BOOL)checkThatInvocationsArrayIsPresent { if(invocations == nil) { - [NSException raise:NSInternalInconsistencyException format:@"** Cannot use mock object %@ at %p. This error usually occurs when a mock object is used after stopMocking has been called on it. In most cases it is not necessary to call stopMocking. If you know you have to, please make sure that the mock object is not used afterwards.", [self description], (void *)self]; + NSString *reason = [NSString stringWithFormat:@"** Cannot use mock object %@ at %p. This issue usually occurs when a mock object is used after stopMocking has been called on it. In most cases it is not necessary to call stopMocking. If you know you have to, please make sure that the mock object is not used afterwards.", [self description], (void *)self]; + [[OCMIssueReporter defaultReporter] reportIssueInFile:__FILE__ line:__LINE__ exceptionName:NSInternalInconsistencyException reason:reason]; + return NO; } + return YES; } - (void)addInvocation:(NSInvocation *)anInvocation @@ -313,7 +319,10 @@ - (void)verifyInvocation:(OCMInvocationMatcher *)matcher atLocation:(OCMLocation - (void)verifyInvocation:(OCMInvocationMatcher *)matcher withQuantifier:(OCMQuantifier *)quantifier atLocation:(OCMLocation *)location { NSUInteger count = 0; - [self assertInvocationsArrayIsPresent]; + if(![self checkThatInvocationsArrayIsPresent]) + { + return; + } @synchronized(invocations) { for(NSInvocation *invocation in invocations) @@ -403,7 +412,10 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation - (BOOL)handleInvocation:(NSInvocation *)anInvocation { - [self assertInvocationsArrayIsPresent]; + if(![self checkThatInvocationsArrayIsPresent]) + { + return NO; + } [self addInvocation:anInvocation]; OCMInvocationStub *stub = [self stubForInvocation:anInvocation]; diff --git a/Source/OCMockTests/OCMIssueReporterTests.m b/Source/OCMockTests/OCMIssueReporterTests.m new file mode 100644 index 00000000..6d2be91b --- /dev/null +++ b/Source/OCMockTests/OCMIssueReporterTests.m @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2013-2020 Erik Doernenburg and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use these files except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +#import +#import +#import "OCMIssueReporter.h" +#import "OCMock.h" +#import "OCClassMockObject.h" + +@interface OCMIssueReporterTests : XCTestCase +@end + + +@implementation OCMIssueReporterTests + +- (void)testPushPopOfIssueTreatment +{ + // Default Settings + OCMIssueReporter *reporter = [OCMIssueReporter defaultReporter]; + XCTAssertEqual([reporter issueTreatment], OCMIssueTreatmentWarnings); + [reporter pushIssueTreatment:OCMIssueTreatmentWarnings]; + XCTAssertEqual([reporter issueTreatment], OCMIssueTreatmentWarnings); + [reporter popIssueTreatment]; + XCTAssertEqual([reporter issueTreatment], OCMIssueTreatmentWarnings); + [reporter pushIssueTreatment:OCMIssueTreatmentErrors]; + [reporter pushIssueTreatment:OCMIssueTreatmentErrors]; + XCTAssertEqual([reporter issueTreatment], OCMIssueTreatmentErrors); + [reporter popIssueTreatment]; + XCTAssertEqual([reporter issueTreatment], OCMIssueTreatmentErrors); + [reporter popIssueTreatment]; + XCTAssertEqual([reporter issueTreatment], OCMIssueTreatmentWarnings); +} + +- (void)testPushPopOfIssueTreatmentOnThreadThrows +{ + XCTestExpectation *finishedThreadExpectation = [self expectationWithDescription:@"Waiting On Thread"]; + [NSThread detachNewThreadWithBlock:^{ + OCMIssueReporter *reporter = [OCMIssueReporter defaultReporter]; + XCTAssertThrowsSpecificNamed([reporter pushIssueTreatment:OCMIssueTreatmentErrors], NSException, NSInternalInconsistencyException); + XCTAssertThrowsSpecificNamed([reporter popIssueTreatment], NSException, NSInternalInconsistencyException); + [finishedThreadExpectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5 handler:nil]; +} + +- (void)testDefaultIssueReporterThrowsOnOverPop +{ + OCMIssueReporter *reporter = [OCMIssueReporter defaultReporter]; + XCTAssertThrowsSpecificNamed([reporter popIssueTreatment], NSException, NSInternalInconsistencyException); +} + +- (void)testRepectsEnvironmentSettings +{ + id processInfoMock = OCMClassMock([NSProcessInfo class]); + OCMStub([processInfoMock processInfo]).andReturn(processInfoMock); + OCMExpect([processInfoMock environment]).andReturn(@{OCMIssueTreatmentDefaultEnvironmentVariable: @(OCMIssueTreatmentErrors)}); + + OCMIssueReporter *reporter = [[OCMIssueReporter alloc] init]; + XCTAssertEqual([reporter issueTreatment], OCMIssueTreatmentErrors); +} + +- (void)testThrowsWithBadEnvironmentSettings +{ + id processInfoMock = OCMClassMock([NSProcessInfo class]); + OCMStub([processInfoMock processInfo]).andReturn(processInfoMock); + OCMExpect([processInfoMock environment]).andReturn(@{OCMIssueTreatmentDefaultEnvironmentVariable: @(5)}); + + XCTAssertThrowsSpecificNamed([[OCMIssueReporter alloc] init], NSException, NSInvalidArgumentException); + OCMVerifyAll(processInfoMock); +} + +- (void)testThrowsWhenIssuesAreErrors +{ + OCMIssueReporter *reporter = [OCMIssueReporter defaultReporter]; + [reporter pushIssueTreatment:OCMIssueTreatmentErrors]; + id stub = OCMClassMock([NSString class]); + [stub stopMocking]; + XCTAssertThrowsSpecificNamed([stub lowercaseString], NSException, NSInternalInconsistencyException); + [reporter popIssueTreatment]; +} + +- (void)testLogsWhenIssuesAreWarnings +{ + OCMIssueReporter *reporter = [OCMIssueReporter defaultReporter]; + [reporter pushIssueTreatment:OCMIssueTreatmentWarnings]; + id stub = OCMClassMock([NSString class]); + [stub stopMocking]; + XCTAssertNoThrow([stub lowercaseString]); + [reporter popIssueTreatment]; +} + +@end diff --git a/Source/OCMockTests/OCMockObjectClassMethodMockingTests.m b/Source/OCMockTests/OCMockObjectClassMethodMockingTests.m index cccfb0f9..aafa6657 100644 --- a/Source/OCMockTests/OCMockObjectClassMethodMockingTests.m +++ b/Source/OCMockTests/OCMockObjectClassMethodMockingTests.m @@ -18,7 +18,7 @@ #import "OCClassMockObject.h" #import "OCMock.h" #import "OCPartialMockObject.h" - +#import "OCMIssueReporter.h" #pragma mark Helper classes @@ -371,9 +371,12 @@ - (void)testArgumentsGetReleasedAfterStopMocking - (void)testThrowsWhenAttemptingToStubClassMethodOnStoppedMock { + OCMIssueReporter *reporter = [OCMIssueReporter defaultReporter]; + [reporter pushIssueTreatment:OCMIssueTreatmentErrors]; id mock = [OCClassMockObject mockForClass:[TestClassWithClassMethods class]]; [mock stopMocking]; XCTAssertThrowsSpecificNamed([[[mock stub] andReturn:@"hello"] foo], NSException, NSInternalInconsistencyException); + [reporter popIssueTreatment]; } @end diff --git a/Source/OCMockTests/OCMockObjectInternalTests.m b/Source/OCMockTests/OCMockObjectInternalTests.m index 4aa67dd2..3992e015 100644 --- a/Source/OCMockTests/OCMockObjectInternalTests.m +++ b/Source/OCMockTests/OCMockObjectInternalTests.m @@ -17,7 +17,6 @@ #import #import "OCMock.h" - #pragma mark Helper classes @interface TestClassForInternalTests : NSObject @@ -207,6 +206,8 @@ - (void)testArgumentsGetReleasedAfterStopMocking - (void)testRaisesWhenAttemptingToVerifyInvocationsAfterStopMocking { + OCMIssueReporter *reporter = [OCMIssueReporter defaultReporter]; + [reporter pushIssueTreatment:OCMIssueTreatmentErrors]; id mock = OCMClassMock([TestClassForInternalTests class]); [mock title]; @@ -222,10 +223,13 @@ - (void)testRaisesWhenAttemptingToVerifyInvocationsAfterStopMocking XCTAssertEqualObjects(ex.name, NSInternalInconsistencyException); XCTAssertTrue([ex.reason containsString:[mock description]]); } + [reporter popIssueTreatment]; } - (void)testRaisesWhenAttemptingToUseAfterStopMocking { + OCMIssueReporter *reporter = [OCMIssueReporter defaultReporter]; + [reporter pushIssueTreatment:OCMIssueTreatmentErrors]; id mock = OCMClassMock([TestClassForInternalTests class]); [mock stopMocking]; @@ -240,6 +244,7 @@ - (void)testRaisesWhenAttemptingToUseAfterStopMocking XCTAssertEqualObjects(ex.name, NSInternalInconsistencyException); XCTAssertTrue([ex.reason containsString:[mock description]]); } + [reporter popIssueTreatment]; } diff --git a/Source/OCMockTests/OCMockObjectTests.m b/Source/OCMockTests/OCMockObjectTests.m index 27543673..7345c3b7 100644 --- a/Source/OCMockTests/OCMockObjectTests.m +++ b/Source/OCMockTests/OCMockObjectTests.m @@ -17,7 +17,6 @@ #import #import "OCMock/OCMock.h" - #pragma mark Helper classes and protocols for testing @interface TestClassWithSelectorMethod : NSObject @@ -499,8 +498,11 @@ - (void)testBlocksAreNotConsideredNonObjectArguments - (void)testThrowsWhenAttemptingToStubMethodOnStoppedMock { + OCMIssueReporter *reporter = [OCMIssueReporter defaultReporter]; + [reporter pushIssueTreatment:OCMIssueTreatmentErrors]; [mock stopMocking]; XCTAssertThrowsSpecificNamed([[mock stub] rangeOfString:@"foo" options:0], NSException, NSInternalInconsistencyException); + [reporter popIssueTreatment]; } diff --git a/Source/OCMockTests/OCMockObjectVerifyAfterRunTests.m b/Source/OCMockTests/OCMockObjectVerifyAfterRunTests.m index 24f541d5..8740851d 100644 --- a/Source/OCMockTests/OCMockObjectVerifyAfterRunTests.m +++ b/Source/OCMockTests/OCMockObjectVerifyAfterRunTests.m @@ -17,7 +17,6 @@ #import #import "OCMock.h" - @interface TestBaseClassForVerifyAfterRun : NSObject + (NSString *)classMethod1; @@ -119,6 +118,8 @@ - (void)testThrowsWhenClassMethodWasNotInvoked - (void)testThrowsWhenVerificationIsAttemptedAfterStopMocking { + OCMIssueReporter *reporter = [OCMIssueReporter defaultReporter]; + [reporter pushIssueTreatment:OCMIssueTreatmentErrors]; id mock = [OCMockObject niceMockForClass:[TestBaseClassForVerifyAfterRun class]]; [TestBaseClassForVerifyAfterRun classMethod1]; @@ -134,6 +135,7 @@ - (void)testThrowsWhenVerificationIsAttemptedAfterStopMocking XCTAssertEqualObjects([e name], NSInternalInconsistencyException); XCTAssertTrue([[e reason] containsString:@"after stopMocking has been called"]); } + [reporter popIssueTreatment]; }