diff --git a/Classes/Core/KWBackgroundTask.h b/Classes/Core/KWBackgroundTask.h new file mode 100644 index 00000000..9a977c1b --- /dev/null +++ b/Classes/Core/KWBackgroundTask.h @@ -0,0 +1,38 @@ +// +// Licensed under the terms in License.txt +// +// Copyright 2010 Allen Ding. All rights reserved. +// + +#import + +// Used to suppress compiler warnings by +// casting receivers to this protocol +@protocol NSTask_KWWarningSuppressor + +- (void)setLaunchPath:(NSString *)path; +- (void)setArguments:(NSArray *)arguments; +- (void)setEnvironment:(NSDictionary *)dict; +- (void)setStandardOutput:(id)output; +- (void)setStandardError:(id)output; +- (void)launch; +- (void)waitUntilExit; + +@property (readonly) int terminationStatus; + +@end + +@interface KWBackgroundTask : NSObject + +@property (nonatomic, readonly) id task; +@property (nonatomic, readonly) NSPipe *standardOutput; +@property (nonatomic, readonly) NSPipe *standardError; +@property (nonatomic, readonly) NSString *command; +@property (nonatomic, readonly) NSArray *arguments; +@property (nonatomic, readonly) NSData *output; + +- (instancetype)initWithCommand:(NSString *)command arguments:(NSArray *)arguments; + +- (void)launchAndWaitForExit; + +@end diff --git a/Classes/Core/KWBackgroundTask.m b/Classes/Core/KWBackgroundTask.m new file mode 100644 index 00000000..db96070e --- /dev/null +++ b/Classes/Core/KWBackgroundTask.m @@ -0,0 +1,95 @@ +// +// Licensed under the terms in License.txt +// +// Copyright 2010 Allen Ding. All rights reserved. +// + +#import "KWBackgroundTask.h" + +NSString *const NSTaskDidTerminateNotification; + +static NSString *const KWTaskDidTerminateNotification = @"KWTaskDidTerminateNotification"; + +static NSString *const KWBackgroundTaskException = @"KWBackgroundTaskException"; + +@implementation KWBackgroundTask + +- (instancetype)initWithCommand:(NSString *)command arguments:(NSArray *)arguments { + if (self = [super init]) { + _command = command; + _arguments = arguments; + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTaskDidTerminateNotification object:nil]; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ `%@ %@`", [super description], self.command, [self.arguments componentsJoinedByString:@" "]]; +} + +// Run this task for 10 seconds +// if it times out raise an exception +- (void)launchAndWaitForExit { + CFRunLoopRef runLoop = [NSRunLoop currentRunLoop].getCFRunLoop; + __weak KWBackgroundTask *weakSelf = self; + CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + 10.0, 0, 0, 0, ^(CFRunLoopTimerRef timer) { + [NSException raise:KWBackgroundTaskException format:@"Task %@ timed out", weakSelf]; + CFRunLoopStop(runLoop); + }); + CFRunLoopAddTimer(runLoop, timer, kCFRunLoopDefaultMode); + + id taskObserver = [[NSNotificationCenter defaultCenter] addObserverForName:KWTaskDidTerminateNotification object:self queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + CFRunLoopStop(runLoop); + }]; + + [NSThread detachNewThreadSelector:@selector(launch) toTarget:self withObject:nil]; + CFRunLoopRun(); + CFRunLoopRemoveTimer(runLoop, timer, kCFRunLoopDefaultMode); + + [[NSNotificationCenter defaultCenter] removeObserver:taskObserver]; +} + +#pragma mark - Private + +- (void)launch { + __block id task = [[NSClassFromString(@"NSTask") alloc] init]; + [task setEnvironment:[NSDictionary dictionary]]; + [task setLaunchPath:_command]; + [task setArguments:_arguments]; + + NSPipe *standardOutput = [NSPipe pipe]; + [task setStandardOutput:standardOutput]; + + // Consume standard error but don't use it + NSPipe *standardError = [NSPipe pipe]; + [task setStandardError:standardError]; + + _task = task; + _standardError = standardError; + _standardOutput = standardOutput; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidTerminate:) name:NSTaskDidTerminateNotification object:task]; + + @try { + [_task launch]; + } @catch (NSException *exception) { + [NSException raise:KWBackgroundTaskException format:@"Task %@ failed to launch", self]; + } + CFRunLoopRun(); +} + +- (void)taskDidTerminate:(NSNotification *)note { + if ([_task terminationStatus] != 0) { + [NSException raise:KWBackgroundTaskException format:@"Task %@ terminated with non 0 exit code", self]; + } else { + _output = [[_standardOutput fileHandleForReading] readDataToEndOfFile]; + } + + [[NSNotificationCenter defaultCenter] postNotificationName:KWTaskDidTerminateNotification object:self]; + [NSThread exit]; +} + +@end diff --git a/Classes/Core/KWSymbolicator.m b/Classes/Core/KWSymbolicator.m index af53969e..21176fde 100644 --- a/Classes/Core/KWSymbolicator.m +++ b/Classes/Core/KWSymbolicator.m @@ -11,6 +11,7 @@ #import #import #import "KWSymbolicator.h" +#import "KWBackgroundTask.h" long kwCallerAddress (void){ #if !__arm__ @@ -29,123 +30,6 @@ long kwCallerAddress (void){ return 0; } -NSString *const NSTaskDidTerminateNotification; - -// Used to suppress compiler warnings by -// casting receivers to this protocol -@protocol NSTask_KWWarningSuppressor - -- (void)setLaunchPath:(NSString *)path; -- (void)setArguments:(NSArray *)arguments; -- (void)setEnvironment:(NSDictionary *)dict; -- (void)setStandardOutput:(id)output; -- (void)setStandardError:(id)output; -- (void)launch; -- (void)waitUntilExit; - -@property (readonly) int terminationStatus; - -@end - -static NSString *const KWTaskDidTerminateNotification = @"KWTaskDidTerminateNotification"; - -@interface KWBackgroundTask : NSObject - -@property (nonatomic, readonly) id task; -@property (nonatomic, readonly) NSPipe *standardOutput; -@property (nonatomic, readonly) NSPipe *standardError; -@property (nonatomic, readonly) NSString *command; -@property (nonatomic, readonly) NSArray *arguments; -@property (nonatomic, readonly) NSData *output; - -- (void)launchAndWaitForExit; - -@end - -static NSString *const KWBackgroundTaskException = @"KWBackgroundTaskException"; - -@implementation KWBackgroundTask - -- (instancetype)initWithCommand:(NSString *)command arguments:(NSArray *)arguments { - if (self = [super init]) { - _command = command; - _arguments = arguments; - } - return self; -} - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTaskDidTerminateNotification object:nil]; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"%@ `%@ %@`", [super description], self.command, [self.arguments componentsJoinedByString:@" "]]; -} - -// Run this task for 10 seconds -// if it times out raise an exception -- (void)launchAndWaitForExit { - CFRunLoopRef runLoop = [NSRunLoop currentRunLoop].getCFRunLoop; - __weak KWBackgroundTask *weakSelf = self; - CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + 10.0, 0, 0, 0, ^(CFRunLoopTimerRef timer) { - [NSException raise:KWBackgroundTaskException format:@"Task %@ timed out", weakSelf]; - CFRunLoopStop(runLoop); - }); - CFRunLoopAddTimer(runLoop, timer, kCFRunLoopDefaultMode); - - id taskObserver = [[NSNotificationCenter defaultCenter] addObserverForName:KWTaskDidTerminateNotification object:self queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { - CFRunLoopStop(runLoop); - }]; - - [NSThread detachNewThreadSelector:@selector(launch) toTarget:self withObject:nil]; - CFRunLoopRun(); - CFRunLoopRemoveTimer(runLoop, timer, kCFRunLoopDefaultMode); - - [[NSNotificationCenter defaultCenter] removeObserver:taskObserver]; -} - -#pragma mark - Private - -- (void)launch { - __block id task = [[NSClassFromString(@"NSTask") alloc] init]; - [task setEnvironment:[NSDictionary dictionary]]; - [task setLaunchPath:_command]; - [task setArguments:_arguments]; - - NSPipe *standardOutput = [NSPipe pipe]; - [task setStandardOutput:standardOutput]; - - // Consume standard error but don't use it - NSPipe *standardError = [NSPipe pipe]; - [task setStandardError:standardError]; - - _task = task; - _standardError = standardError; - _standardOutput = standardOutput; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidTerminate:) name:NSTaskDidTerminateNotification object:task]; - - @try { - [_task launch]; - } @catch (NSException *exception) { - [NSException raise:KWBackgroundTaskException format:@"Task %@ failed to launch", self]; - } - CFRunLoopRun(); -} - -- (void)taskDidTerminate:(NSNotification *)note { - if ([_task terminationStatus] != 0) { - [NSException raise:KWBackgroundTaskException format:@"Task %@ terminated with non 0 exit code", self]; - } else { - _output = [[_standardOutput fileHandleForReading] readDataToEndOfFile]; - } - - [[NSNotificationCenter defaultCenter] postNotificationName:KWTaskDidTerminateNotification object:self]; - [NSThread exit]; -} - -@end - @implementation KWCallSite (KWSymbolication) static void GetTestBundleExecutablePathSlide(NSString **executablePath, long *slide) { diff --git a/Kiwi.xcodeproj/project.pbxproj b/Kiwi.xcodeproj/project.pbxproj index 51178529..1ef4428f 100755 --- a/Kiwi.xcodeproj/project.pbxproj +++ b/Kiwi.xcodeproj/project.pbxproj @@ -568,6 +568,10 @@ C533F7D517462CAA000CAB02 /* KWSymbolicator.m in Sources */ = {isa = PBXBuildFile; fileRef = C533F7D217462CAA000CAB02 /* KWSymbolicator.m */; }; C533F7D617462CAA000CAB02 /* KWSymbolicator.m in Sources */ = {isa = PBXBuildFile; fileRef = C533F7D217462CAA000CAB02 /* KWSymbolicator.m */; }; C931D36E18AB2DEB005BC184 /* KWBeZeroMatcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C931D36D18AB2DEB005BC184 /* KWBeZeroMatcherTest.m */; }; + CE80E4441AF2528D00D2F0D6 /* KWBackgroundTask.h in Headers */ = {isa = PBXBuildFile; fileRef = CE80E4421AF2528D00D2F0D6 /* KWBackgroundTask.h */; }; + CE80E4451AF2528D00D2F0D6 /* KWBackgroundTask.h in Headers */ = {isa = PBXBuildFile; fileRef = CE80E4421AF2528D00D2F0D6 /* KWBackgroundTask.h */; }; + CE80E4461AF2528D00D2F0D6 /* KWBackgroundTask.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80E4431AF2528D00D2F0D6 /* KWBackgroundTask.m */; }; + CE80E4471AF2528D00D2F0D6 /* KWBackgroundTask.m in Sources */ = {isa = PBXBuildFile; fileRef = CE80E4431AF2528D00D2F0D6 /* KWBackgroundTask.m */; }; DA3EAD0818A7A1D700EBBF57 /* KWSharedExampleFunctionalTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3EAD0718A7A1D700EBBF57 /* KWSharedExampleFunctionalTest.m */; }; DA3EAD0A18A86E5600EBBF57 /* KWUserDefinedMatcherFunctionalTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3EAD0918A86E5600EBBF57 /* KWUserDefinedMatcherFunctionalTest.m */; }; DA9C69F8190CA6EE002C4DC0 /* NSNumber_KiwiAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA9C69F7190CA6EE002C4DC0 /* NSNumber_KiwiAdditionsTests.m */; }; @@ -965,6 +969,8 @@ C533F7D117462CAA000CAB02 /* KWSymbolicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KWSymbolicator.h; sourceTree = ""; }; C533F7D217462CAA000CAB02 /* KWSymbolicator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KWSymbolicator.m; sourceTree = ""; }; C931D36D18AB2DEB005BC184 /* KWBeZeroMatcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = KWBeZeroMatcherTest.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + CE80E4421AF2528D00D2F0D6 /* KWBackgroundTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KWBackgroundTask.h; sourceTree = ""; }; + CE80E4431AF2528D00D2F0D6 /* KWBackgroundTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KWBackgroundTask.m; sourceTree = ""; }; DA084D3F17E3804200592D5A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; DA084D4217E3804200592D5A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; DA3EAD0718A7A1D700EBBF57 /* KWSharedExampleFunctionalTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KWSharedExampleFunctionalTest.m; sourceTree = ""; }; @@ -1130,6 +1136,8 @@ 9F982C3D16A802920030A0B1 /* KiwiMacros.h */, 9F982C4316A802920030A0B1 /* KWAny.h */, 9F982C4416A802920030A0B1 /* KWAny.m */, + CE80E4421AF2528D00D2F0D6 /* KWBackgroundTask.h */, + CE80E4431AF2528D00D2F0D6 /* KWBackgroundTask.m */, 9F982C6116A802920030A0B1 /* KWBlock.h */, 9F982C6216A802920030A0B1 /* KWBlock.m */, 9F982C6716A802920030A0B1 /* KWCallSite.h */, @@ -1572,6 +1580,7 @@ 9F982DC316A802920030A0B1 /* KWObjCUtilities.h in Headers */, 9F982DC716A802920030A0B1 /* KWPendingNode.h in Headers */, 9F982DCB16A802920030A0B1 /* KWProbe.h in Headers */, + CE80E4451AF2528D00D2F0D6 /* KWBackgroundTask.h in Headers */, 9F982DCD16A802920030A0B1 /* KWProbePoller.h in Headers */, 9F982DD516A802920030A0B1 /* KWReceiveMatcher.h in Headers */, 9F982DD916A802920030A0B1 /* KWRegisterMatchersNode.h in Headers */, @@ -1655,6 +1664,7 @@ 9F982D6E16A802920030A0B1 /* KWExpectationType.h in Headers */, 9F982D7016A802920030A0B1 /* KWFailure.h in Headers */, 9F982D7416A802920030A0B1 /* KWFormatter.h in Headers */, + CE80E4441AF2528D00D2F0D6 /* KWBackgroundTask.h in Headers */, 9F982D7816A802920030A0B1 /* KWFutureObject.h in Headers */, 9F982D7C16A802920030A0B1 /* KWGenericMatcher.h in Headers */, 9F982D8016A802920030A0B1 /* KWGenericMatchingAdditions.h in Headers */, @@ -1979,6 +1989,7 @@ 9F982D3116A802920030A0B1 /* KWBlock.m in Sources */, 9F982D3516A802920030A0B1 /* KWBlockNode.m in Sources */, 9F982D3916A802920030A0B1 /* KWBlockRaiseMatcher.m in Sources */, + CE80E4471AF2528D00D2F0D6 /* KWBackgroundTask.m in Sources */, 9F982D3D16A802920030A0B1 /* KWCallSite.m in Sources */, 9F982D4116A802920030A0B1 /* KWCaptureSpy.m in Sources */, 9F982D4516A802920030A0B1 /* KWConformToProtocolMatcher.m in Sources */, @@ -2070,6 +2081,7 @@ 9F982D3016A802920030A0B1 /* KWBlock.m in Sources */, 9F982D3416A802920030A0B1 /* KWBlockNode.m in Sources */, 9F982D3816A802920030A0B1 /* KWBlockRaiseMatcher.m in Sources */, + CE80E4461AF2528D00D2F0D6 /* KWBackgroundTask.m in Sources */, 9F982D3C16A802920030A0B1 /* KWCallSite.m in Sources */, 9F982D4016A802920030A0B1 /* KWCaptureSpy.m in Sources */, 9F982D4416A802920030A0B1 /* KWConformToProtocolMatcher.m in Sources */,