diff --git a/Classes/BITCrashManager.m b/Classes/BITCrashManager.m index f8c4411baa..1a2bccd490 100644 --- a/Classes/BITCrashManager.m +++ b/Classes/BITCrashManager.m @@ -32,7 +32,6 @@ #if HOCKEYSDK_FEATURE_CRASH_REPORTER -#import #import #import @@ -83,9 +82,6 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus"; BOOL _sendingInProgress; BOOL _isSetup; - BITPLCrashReporter *_plCrashReporter; - NSUncaughtExceptionHandler *_exceptionHandler; - id _appDidBecomeActiveObserver; id _networkDidBecomeReachableObserver; } @@ -433,7 +429,7 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus"; - (void) handleCrashReport { NSError *error = NULL; - if (!_plCrashReporter) return; + if (!self.plCrashReporter) return; [self loadSettings]; @@ -445,7 +441,7 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus"; [self saveSettings]; // Try loading the crash report - NSData *crashData = [[NSData alloc] initWithData:[_plCrashReporter loadPendingCrashReportDataAndReturnError: &error]]; + NSData *crashData = [[NSData alloc] initWithData:[self.plCrashReporter loadPendingCrashReportDataAndReturnError: &error]]; NSString *cacheFilename = [NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate]]; @@ -500,7 +496,7 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus"; [self saveSettings]; - [_plCrashReporter purgePendingCrashReport]; + [self.plCrashReporter purgePendingCrashReport]; } /** @@ -509,7 +505,7 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus"; * @return `YES` if there are crash reports pending that are not approved, `NO` otherwise */ - (BOOL)hasNonApprovedCrashReports { - if (!_approvedCrashReports || [_approvedCrashReports count] == 0) return YES; + if ((!_approvedCrashReports || [_approvedCrashReports count] == 0) && [_crashFiles count] > 0) return YES; for (NSUInteger i=0; i < [_crashFiles count]; i++) { NSString *filename = [_crashFiles objectAtIndex:i]; @@ -582,13 +578,13 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus"; BITHockeyLog(@"INFO: Start delayed CrashManager processing"); // was our own exception handler successfully added? - if (_exceptionHandler) { + if (self.exceptionHandler) { // get the current top level error handler NSUncaughtExceptionHandler *currentHandler = NSGetUncaughtExceptionHandler(); // If the top level error handler differs from our own, then at least another one was added. // This could cause exception crashes not to be reported to HockeyApp. See log message for details. - if (_exceptionHandler != currentHandler) { + if (self.exceptionHandler != currentHandler) { BITHockeyLog(@"[HockeySDK] WARNING: Another exception handler was added. If this invokes any kind exit() after processing the exception, which causes any subsequent error handler not to be invoked, these crashes will NOT be reported to HockeyApp!"); } } @@ -653,10 +649,10 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus"; } BITPLCrashReporterConfig *config = [[BITPLCrashReporterConfig alloc] initWithSignalHandlerType: signalHandlerType symbolicationStrategy: PLCrashReporterSymbolicationStrategyAll]; - _plCrashReporter = [[BITPLCrashReporter alloc] initWithConfiguration: config]; + self.plCrashReporter = [[BITPLCrashReporter alloc] initWithConfiguration: config]; // Check if we previously crashed - if ([_plCrashReporter hasPendingCrashReport]) { + if ([self.plCrashReporter hasPendingCrashReport]) { _didCrashInLastSession = YES; [self handleCrashReport]; } @@ -694,7 +690,7 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus"; NSError *error = NULL; // Enable the Crash Reporter - if (![_plCrashReporter enableCrashReporterAndReturnError: &error]) + if (![self.plCrashReporter enableCrashReporterAndReturnError: &error]) NSLog(@"[HockeySDK] WARNING: Could not enable crash reporter: %@", [error localizedDescription]); // get the new current top level error handler, which should now be the one from PLCrashReporter @@ -702,7 +698,7 @@ NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus"; // do we have a new top level error handler? then we were successful if (currentHandler && currentHandler != initialHandler) { - _exceptionHandler = currentHandler; + self.exceptionHandler = currentHandler; BITHockeyLog(@"INFO: Exception handler successfully initialized."); } else { diff --git a/Classes/BITCrashManagerPrivate.h b/Classes/BITCrashManagerPrivate.h index 877168c3f2..6bd5e70298 100644 --- a/Classes/BITCrashManagerPrivate.h +++ b/Classes/BITCrashManagerPrivate.h @@ -30,11 +30,18 @@ #import "HockeySDK.h" #if HOCKEYSDK_FEATURE_CRASH_REPORTER -#if HOCKEYSDK_FEATURE_AUTHENTICATOR + +#import @interface BITCrashManager () { } +@property (nonatomic) NSUncaughtExceptionHandler *exceptionHandler; + +@property (nonatomic, strong) BITPLCrashReporter *plCrashReporter; + +#if HOCKEYSDK_FEATURE_AUTHENTICATOR + // Only set via BITAuthenticator @property (nonatomic, strong) NSString *installationIdentification; @@ -44,7 +51,17 @@ // Only set via BITAuthenticator @property (nonatomic) BOOL installationIdentified; +#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ + +- (NSString *)userIDForCrashReport; +- (NSString *)userEmailForCrashReport; +- (NSString *)userNameForCrashReport; + +- (BOOL)hasPendingCrashReport; +- (BOOL)hasNonApprovedCrashReports; +- (void)invokeDelayedProcessing; + @end -#endif /* HOCKEYSDK_FEATURE_AUTHENTICATOR */ + #endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */ diff --git a/Support/HockeySDK.xcodeproj/project.pbxproj b/Support/HockeySDK.xcodeproj/project.pbxproj index e54283b4be..b2143d1d00 100644 --- a/Support/HockeySDK.xcodeproj/project.pbxproj +++ b/Support/HockeySDK.xcodeproj/project.pbxproj @@ -127,6 +127,7 @@ 1EF95CA6162CB037000AE3AD /* BITFeedbackActivity.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EF95CA4162CB036000AE3AD /* BITFeedbackActivity.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1EF95CA7162CB037000AE3AD /* BITFeedbackActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EF95CA5162CB036000AE3AD /* BITFeedbackActivity.m */; }; 1EF95CAA162CB314000AE3AD /* BITFeedbackComposeViewControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EF95CA9162CB313000AE3AD /* BITFeedbackComposeViewControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1EFF03E517F2485500A5F13C /* BITCrashManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EFF03E417F2485500A5F13C /* BITCrashManagerTests.m */; }; E405266217A2AD300096359C /* BITFeedbackManagerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E405266117A2AD300096359C /* BITFeedbackManagerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; E40E0B0917DA19DC005E38C1 /* BITHockeyAppClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E40E0B0817DA19DC005E38C1 /* BITHockeyAppClientTests.m */; }; E40E0B0C17DA1AFF005E38C1 /* BITHockeyAppClient.h in Headers */ = {isa = PBXBuildFile; fileRef = E40E0B0A17DA1AFF005E38C1 /* BITHockeyAppClient.h */; }; @@ -280,6 +281,7 @@ 1EF95CA5162CB036000AE3AD /* BITFeedbackActivity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITFeedbackActivity.m; sourceTree = ""; }; 1EF95CA9162CB313000AE3AD /* BITFeedbackComposeViewControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITFeedbackComposeViewControllerDelegate.h; sourceTree = ""; }; 1EFF03D717F20F8300A5F13C /* BITCrashManagerPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BITCrashManagerPrivate.h; sourceTree = ""; }; + 1EFF03E417F2485500A5F13C /* BITCrashManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BITCrashManagerTests.m; sourceTree = ""; }; BEE0207C16C5107E004426EA /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/HockeySDK.strings; sourceTree = ""; }; E400561D148D79B500EB22B9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; E405266117A2AD300096359C /* BITFeedbackManagerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BITFeedbackManagerDelegate.h; sourceTree = ""; }; @@ -367,6 +369,7 @@ 1E5A459716F0DFC200B55C04 /* Supporting Files */, 1E5A459D16F0DFC200B55C04 /* BITStoreUpdateManagerTests.m */, E48A3DEE17B3EFF100924C3D /* BITAuthenticatorTests.m */, + 1EFF03E417F2485500A5F13C /* BITCrashManagerTests.m */, E40E0B0817DA19DC005E38C1 /* BITHockeyAppClientTests.m */, E4507E4217F0658F00171A0D /* BITKeychainUtilsTests.m */, ); @@ -894,6 +897,7 @@ buildActionMask = 2147483647; files = ( 1E5A459E16F0DFC200B55C04 /* BITStoreUpdateManagerTests.m in Sources */, + 1EFF03E517F2485500A5F13C /* BITCrashManagerTests.m in Sources */, E40E0B0917DA19DC005E38C1 /* BITHockeyAppClientTests.m in Sources */, E48A3DEF17B3EFF100924C3D /* BITAuthenticatorTests.m in Sources */, 1EA1170716F53B91001C015C /* BITTestHelper.m in Sources */, diff --git a/Support/HockeySDKTests/BITCrashManagerTests.m b/Support/HockeySDKTests/BITCrashManagerTests.m new file mode 100644 index 0000000000..5a998424a5 --- /dev/null +++ b/Support/HockeySDKTests/BITCrashManagerTests.m @@ -0,0 +1,176 @@ +// +// BITCrashManagerTests.m +// HockeySDK +// +// Created by Andreas Linde on 25.09.13. +// +// + +#import + +#define HC_SHORTHAND +#import + +#define MOCKITO_SHORTHAND +#import + +#import "HockeySDK.h" +#import "BITCrashManager.h" +#import "BITCrashManagerPrivate.h" +#import "BITHockeyBaseManager.h" +#import "BITHockeyBaseManagerPrivate.h" +#import "BITHockeyManagerPrivate.h" + +#import "BITTestHelper.h" + + +@interface BITCrashManagerTests : SenTestCase + +@end + + +@implementation BITCrashManagerTests { + BITCrashManager *_sut; + BOOL _startManagerInitialized; +} + +- (void)setUp { + [super setUp]; + + _startManagerInitialized = NO; + _sut = [[BITCrashManager alloc] initWithAppIdentifier:nil isAppStoreEnvironment:NO]; +} + +- (void)tearDown { +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wimplicit" + __gcov_flush(); +# pragma clang diagnostic pop + + _sut = nil; + [super tearDown]; +} + +#pragma mark - Private + +- (void)startManager { + [_sut startManager]; + [NSObject cancelPreviousPerformRequestsWithTarget:_sut selector:@selector(invokeDelayedProcessing) object:nil]; + _startManagerInitialized = YES; +} + +- (void)startManagerDisabled { + _sut.crashManagerStatus = BITCrashManagerStatusDisabled; + if (_startManagerInitialized) return; + [self startManager]; +} + +- (void)startManagerAutoSend { + _sut.crashManagerStatus = BITCrashManagerStatusAutoSend; + if (_startManagerInitialized) return; + [self startManager]; +} + + +#pragma mark - Setup Tests + +- (void)testThatItInstantiates { + STAssertNotNil(_sut, @"Should be there"); +} + + +#pragma mark - Persistence tests + + +#pragma mark - Helper + +- (void)testUserIDForCrashReport { + BITHockeyManager *hm = [BITHockeyManager sharedHockeyManager]; + id delegateMock = mockProtocol(@protocol(BITHockeyManagerDelegate)); + hm.delegate = delegateMock; + _sut.delegate = delegateMock; + + NSString *result = [_sut userIDForCrashReport]; + + assertThat(result, notNilValue()); + + [verifyCount(delegateMock, times(1)) userIDForHockeyManager:hm componentManager:_sut]; +} + +- (void)testUserNameForCrashReport { + BITHockeyManager *hm = [BITHockeyManager sharedHockeyManager]; + id delegateMock = mockProtocol(@protocol(BITHockeyManagerDelegate)); + hm.delegate = delegateMock; + _sut.delegate = delegateMock; + + NSString *result = [_sut userNameForCrashReport]; + + assertThat(result, notNilValue()); + + [verifyCount(delegateMock, times(1)) userNameForHockeyManager:hm componentManager:_sut]; +} + +- (void)testUserEmailForCrashReport { + BITHockeyManager *hm = [BITHockeyManager sharedHockeyManager]; + id delegateMock = mockProtocol(@protocol(BITHockeyManagerDelegate)); + hm.delegate = delegateMock; + _sut.delegate = delegateMock; + + NSString *result = [_sut userEmailForCrashReport]; + + assertThat(result, notNilValue()); + + [verifyCount(delegateMock, times(1)) userEmailForHockeyManager:hm componentManager:_sut]; +} + + +#pragma mark - Debugger + +/** + * We are running this usually witin Xcode + * TODO: what to do if we do run this e.g. on Jenkins or Xcode bots ? + */ +- (void)testIsDebuggerAttached { + assertThatBool([_sut isDebuggerAttached], equalToBool(YES)); +} + + +#pragma mark - Helper + +- (void)testHasPendingCrashReports { + _sut.crashManagerStatus = BITCrashManagerStatusAutoSend; + + // No files + assertThatBool([_sut hasPendingCrashReport], equalToBool(NO)); + assertThatBool([_sut hasNonApprovedCrashReports], equalToBool(NO)); + + // TODO: add some test files +} + + +#pragma mark - StartManager + +- (void)testStartManagerWithModuleDisabled { + [self startManagerDisabled]; + + assertThat(_sut.plCrashReporter, equalTo(nil)); +} + +- (void)testStartManagerWithAutoSend { + [self startManagerAutoSend]; + + assertThat(_sut.plCrashReporter, notNilValue()); + + // When running from the debugger this is always nil and not the exception handler from PLCR + NSUncaughtExceptionHandler *currentHandler = NSGetUncaughtExceptionHandler(); + + BOOL result = (_sut.exceptionHandler == currentHandler); + + assertThatBool(result, equalToBool(YES)); + + +// [_sut invokeDelayedProcessing]; + +} + +@end