diff options
author | gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2010-04-14 17:08:30 +0000 |
---|---|---|
committer | gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2010-04-14 17:08:30 +0000 |
commit | 30ac0ff87b594d7bca3a5cc2789ca415840ea0b1 (patch) | |
tree | 8057e5c0112ba796b93082b903d54b3aed0b9e47 | |
parent | d2a270d64ee1dd3b0d719359170e2d6511fcb669 (diff) |
[Author: dmaclach]
Add utility code to make it faster, easier and more robust to spin runloops during tests.
R=thomasvl
DELTA=186 (140 added, 19 deleted, 27 changed)
-rw-r--r-- | AppKit/GTMCarbonEventTest.m | 31 | ||||
-rw-r--r-- | AppKit/GTMNSAnimatablePropertyContainerTest.h | 4 | ||||
-rw-r--r-- | AppKit/GTMNSAnimatablePropertyContainerTest.m | 32 | ||||
-rw-r--r-- | UnitTesting/GTMUnitTestingUtilities.h | 83 | ||||
-rw-r--r-- | UnitTesting/GTMUnitTestingUtilities.m | 94 |
5 files changed, 185 insertions, 59 deletions
diff --git a/AppKit/GTMCarbonEventTest.m b/AppKit/GTMCarbonEventTest.m index ff7fca5..81acbd0 100644 --- a/AppKit/GTMCarbonEventTest.m +++ b/AppKit/GTMCarbonEventTest.m @@ -41,7 +41,7 @@ @interface GTMCarbonEventDispatcherHandlerTest : GTMTestCase { @private - BOOL hotKeyHit_; + GTMUnitTestingBooleanRunLoopContext *hotKeyHit_; } @end @@ -258,6 +258,15 @@ extern EventTargetRef GetApplicationEventTarget(void); @implementation GTMCarbonEventDispatcherHandlerTest +- (void)setUp { + hotKeyHit_ = [[GTMUnitTestingBooleanRunLoopContext alloc] init]; +} + +- (void)tearDown { + [hotKeyHit_ release]; + hotKeyHit_ = nil; +} + - (void)testEventHandler { GTMCarbonEventDispatcherHandler *dispatcher = [GTMCarbonEventDispatcherHandler sharedEventDispatcherHandler]; @@ -265,11 +274,11 @@ extern EventTargetRef GetApplicationEventTarget(void); } - (void)hitHotKey:(id)sender { - hotKeyHit_ = YES; - [NSApp stop:self]; + [hotKeyHit_ setShouldStop:YES]; } - (void)hitExceptionalHotKey:(id)sender { + [hotKeyHit_ setShouldStop:YES]; [NSException raise:@"foo" format:@"bar"]; } @@ -299,16 +308,12 @@ extern EventTargetRef GetApplicationEventTarget(void); whenPressed:YES]; STAssertNotNULL(hotKey, @"Unable to create hotkey"); - hotKeyHit_ = NO; - // Post the hotkey combo to the event queue. If everything is working // correctly hitHotKey: should get called, and hotKeyHit_ will be set for // us. We run the event loop for a set amount of time waiting for this to // happen. [GTMUnitTestingUtilities postTypeCharacterEvent:'g' modifiers:keyMods]; - NSDate* future = [NSDate dateWithTimeIntervalSinceNow:1.0f]; - [GTMUnitTestingUtilities runUntilDate:future]; - STAssertTrue(hotKeyHit_, @"Hot key never got fired."); + STAssertTrue([NSApp gtm_runUpToSixtySecondsWithContext:hotKeyHit_], nil); [dispatcher unregisterHotKey:hotKey]; } } @@ -330,13 +335,13 @@ extern EventTargetRef GetApplicationEventTarget(void); whenPressed:YES]; STAssertTrue(hotKey != nil, @"Unable to create hotkey"); - // Post the hotkey combo to the event queue. If everything is working correctly - // hitHotKey: should get called, and hotKeyHit_ will be set for us. - // We run the event loop for a set amount of time waiting for this to happen. + // Post the hotkey combo to the event queue. If everything is working + // correctly hitHotKey: should get called, and hotKeyHit_ will be set for + // us. We run the event loop for a set amount of time waiting for this to + // happen. [GTMUnitTestingUtilities postTypeCharacterEvent:'g' modifiers:keyMods]; - NSDate* future = [NSDate dateWithTimeIntervalSinceNow:1.0f]; [GTMUnitTestDevLog expectString:@"Exception fired in hotkey: foo (bar)"]; - [GTMUnitTestingUtilities runUntilDate:future]; + STAssertTrue([NSApp gtm_runUpToSixtySecondsWithContext:hotKeyHit_], nil); [dispatcher unregisterHotKey:hotKey]; } } diff --git a/AppKit/GTMNSAnimatablePropertyContainerTest.h b/AppKit/GTMNSAnimatablePropertyContainerTest.h index ebfd323..f873601 100644 --- a/AppKit/GTMNSAnimatablePropertyContainerTest.h +++ b/AppKit/GTMNSAnimatablePropertyContainerTest.h @@ -21,6 +21,8 @@ #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 +@class GTMUnitTestingBooleanRunLoopContext; + @interface GTMNSAnimatablePropertyContainerWindow : NSWindow @end @@ -44,7 +46,7 @@ @interface GTMNSAnimatablePropertyContainerTest : GTMTestCase { @private GTMNSAnimatablePropertyContainerWindowController *windowController_; - BOOL timerCalled_; + GTMUnitTestingBooleanRunLoopContext *timerCalled_; } @end diff --git a/AppKit/GTMNSAnimatablePropertyContainerTest.m b/AppKit/GTMNSAnimatablePropertyContainerTest.m index 0ce89d2..9a1d497 100644 --- a/AppKit/GTMNSAnimatablePropertyContainerTest.m +++ b/AppKit/GTMNSAnimatablePropertyContainerTest.m @@ -19,6 +19,7 @@ #import "GTMNSAnimatablePropertyContainerTest.h" #import "GTMNSAnimatablePropertyContainer.h" #import "GTMTypeCasting.h" +#import "GTMUnitTestingUtilities.h" #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 @@ -80,23 +81,26 @@ STAssertNotNil(windowController_, nil); NSWindow *window = [windowController_ window]; STAssertNotNil(window, nil); + timerCalled_ = [[GTMUnitTestingBooleanRunLoopContext alloc] init]; } - (void)tearDown { [windowController_ close]; windowController_ = nil; + [timerCalled_ release]; + timerCalled_ = nil; } - (void)windowAlphaValueStopper:(NSTimer *)timer { NSWindow *window = GTM_DYNAMIC_CAST(NSWindow, [timer userInfo]); - timerCalled_ = YES; + [timerCalled_ setShouldStop:YES]; [[window gtm_animatorStopper] setAlphaValue:0.25]; STAssertEquals([window alphaValue], (CGFloat)0.25, nil); } - (void)windowFrameStopper:(NSTimer *)timer { NSWindow *window = GTM_DYNAMIC_CAST(NSWindow, [timer userInfo]); - timerCalled_ = YES; + [timerCalled_ setShouldStop:YES]; [[window gtm_animatorStopper] setFrame:NSMakeRect(300, 300, 150, 150) display:YES]; STAssertEquals([window frame], NSMakeRect(300, 300, 150, 150), nil); @@ -104,14 +108,14 @@ - (void)nonLayerFrameStopper:(NSTimer *)timer { NSView *view = GTM_DYNAMIC_CAST(NSView, [timer userInfo]); - timerCalled_ = YES; + [timerCalled_ setShouldStop:YES]; [[view gtm_animatorStopper] setFrame:NSMakeRect(200, 200, 200, 200)]; STAssertEquals([view frame], NSMakeRect(200, 200, 200, 200), nil); } - (void)layerFrameStopper:(NSTimer *)timer { NSView *view = GTM_DYNAMIC_CAST(NSView, [timer userInfo]); - timerCalled_ = YES; + [timerCalled_ setShouldStop:YES]; [[view gtm_animatorStopper] setFrame:NSMakeRect(200, 200, 200, 200)]; STAssertEquals([view frame], NSMakeRect(200, 200, 200, 200), nil); } @@ -122,7 +126,7 @@ // Test Alpha NSWindow *window = [windowController_ window]; [window setAlphaValue:1.0]; - timerCalled_ = NO; + [timerCalled_ setShouldStop:NO]; [NSAnimationContext beginGrouping]; NSAnimationContext *currentContext = [NSAnimationContext currentContext]; [currentContext setDuration:2]; @@ -133,13 +137,12 @@ selector:@selector(windowAlphaValueStopper:) userInfo:window repeats:NO]; - [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; - STAssertTrue(timerCalled_, nil); + STAssertTrue([runLoop gtm_runUpToSixtySecondsWithContext:timerCalled_], nil); STAssertEquals([window alphaValue], (CGFloat)0.25, nil); // Test Frame [window setFrame:NSMakeRect(100, 100, 100, 100) display:YES]; - timerCalled_ = NO; + [timerCalled_ setShouldStop:NO]; [NSAnimationContext beginGrouping]; currentContext = [NSAnimationContext currentContext]; [currentContext setDuration:2]; @@ -150,8 +153,7 @@ selector:@selector(windowFrameStopper:) userInfo:window repeats:NO]; - [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; - STAssertTrue(timerCalled_, nil); + STAssertTrue([runLoop gtm_runUpToSixtySecondsWithContext:timerCalled_], nil); STAssertEquals([window frame], NSMakeRect(300, 300, 150, 150), nil); // Test non-animation value @@ -171,7 +173,7 @@ // Test frame [nonLayerBox setFrame:NSMakeRect(50, 50, 50, 50)]; - timerCalled_ = NO; + [timerCalled_ setShouldStop:NO]; [NSAnimationContext beginGrouping]; NSAnimationContext *currentContext = [NSAnimationContext currentContext]; [currentContext setDuration:2]; @@ -182,8 +184,7 @@ selector:@selector(nonLayerFrameStopper:) userInfo:nonLayerBox repeats:NO]; - [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; - STAssertTrue(timerCalled_, nil); + STAssertTrue([runLoop gtm_runUpToSixtySecondsWithContext:timerCalled_], nil); STAssertEquals([nonLayerBox frame], NSMakeRect(200, 200, 200, 200), nil); // Test non-animation value @@ -204,7 +205,7 @@ // Test frame [layerBox setFrame:NSMakeRect(50, 50, 50, 50)]; - timerCalled_ = NO; + [timerCalled_ setShouldStop:NO]; [NSAnimationContext beginGrouping]; NSAnimationContext *currentContext = [NSAnimationContext currentContext]; [currentContext setDuration:2]; @@ -215,8 +216,7 @@ selector:@selector(layerFrameStopper:) userInfo:layerBox repeats:NO]; - [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; - STAssertTrue(timerCalled_, nil); + STAssertTrue([runLoop gtm_runUpToSixtySecondsWithContext:timerCalled_], nil); STAssertEquals([layerBox frame], NSMakeRect(200, 200, 200, 200), nil); // Test non-animation value diff --git a/UnitTesting/GTMUnitTestingUtilities.h b/UnitTesting/GTMUnitTestingUtilities.h index 128a4c4..d47c1ec 100644 --- a/UnitTesting/GTMUnitTestingUtilities.h +++ b/UnitTesting/GTMUnitTestingUtilities.h @@ -19,6 +19,33 @@ #import <Foundation/Foundation.h> #import <objc/objc.h> +// Many tests need to spin the runloop and wait for an event to happen. This is +// often done by calling: +// NSDate* next = [NSDate dateWithTimeIntervalSinceNow:resolution]; +// [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode +// beforeDate:next]; +// where |resolution| is a guess at how long it will take for the event to +// happen. There are two major problems with this approach: +// a) By guessing we force the test to take at least |resolution| time. +// b) It makes for flaky tests in that sometimes this guess isn't good, and the +// test takes slightly longer than |resolution| time causing the test to post +// a possibly false-negative failure. +// To make timing callback tests easier use this class and the +// GTMUnitTestingAdditions additions to NSRunLoop and NSApplication. +// Your call would look something like this: +// id<GTMUnitTestingRunLoopContext> context = [self getMeAContext]; +// [[NSRunLoop currentRunLoop] gtm_runUpToSixtySecondsWithContext:context]; +// Then in some callback method within your test you would call +// [context setShouldStop:YES]; +// Internally gtm_runUpToSixtySecondsWithContext will poll the runloop really +// quickly to keep test times down to a minimum, but will stop after a good time +// interval (in this case 60 seconds) for failures. +@protocol GTMUnitTestingRunLoopContext +// Return YES if the NSRunLoop (or equivalent) that this context applies to +// should stop as soon as possible. +- (BOOL)shouldStop; +@end + // Collection of utilities for unit testing @interface GTMUnitTestingUtilities : NSObject @@ -76,16 +103,52 @@ + (void)postTypeCharacterEvent:(CGCharCode)keyChar modifiers:(UInt32)cocoaModifiers; -// Runs the event loop in NSDefaultRunLoopMode until date. Can be useful for -// testing user interface responses in a controlled timed event loop. For most -// uses using: -// [[NSRunLoop currentRunLoop] runUntilDate:date] -// will do. The only reason you would want to use this is if you were -// using the postKeyEvent:character:modifiers to send events and wanted to -// receive user input. -// Arguments: -// date - end of execution time -+ (void)runUntilDate:(NSDate*)date; +@end +// An implementation of the GTMUnitTestingRunLoopContext that is a simple +// BOOL flag. See GTMUnitTestingRunLoopContext documentation. +@interface GTMUnitTestingBooleanRunLoopContext : NSObject <GTMUnitTestingRunLoopContext> { + @private + BOOL shouldStop_; +} ++ (id)context; +- (BOOL)shouldStop; +- (void)setShouldStop:(BOOL)stop; @end +// Some category methods to simplify spinning the runloops in such a way as +// to make tests less flaky, but have them complete as fast as possible. +@interface NSRunLoop (GTMUnitTestingAdditions) +// Will spin the runloop in mode until date in mode until the runloop returns +// because all sources have been removed or the current date is greater than +// |date| or [context shouldStop] returns YES. +// Return YES if the runloop was stopped because [context shouldStop] returned +// YES. +- (BOOL)gtm_runUntilDate:(NSDate *)date + mode:(NSString *)mode + context:(id<GTMUnitTestingRunLoopContext>)context; + +// Calls -gtm_runUntilDate:mode:context: with mode set to NSDefaultRunLoopMode. +- (BOOL)gtm_runUntilDate:(NSDate *)date + context:(id<GTMUnitTestingRunLoopContext>)context; + +// Calls -gtm_runUntilDate:mode:context: with mode set to NSDefaultRunLoopMode, +// and the timeout date set to 60 seconds. +- (BOOL)gtm_runUpToSixtySecondsWithContext:(id<GTMUnitTestingRunLoopContext>)context; + +@end + +// Some category methods to simplify spinning the runloops in such a way as +// to make tests less flaky, but have them complete as fast as possible. +@interface NSApplication (GTMUnitTestingAdditions) +// Has NSApplication call nextEventMatchingMask repeatedly until +// [context shouldStop] returns YES or it returns nil because the current date +// is greater than |date|. +// Return YES if the runloop was stopped because [context shouldStop] returned +// YES. +- (BOOL)gtm_runUntilDate:(NSDate *)date + context:(id<GTMUnitTestingRunLoopContext>)context; + +// Calls -gtm_runUntilDate:context: with the timeout date set to 60 seconds. +- (BOOL)gtm_runUpToSixtySecondsWithContext:(id<GTMUnitTestingRunLoopContext>)context; +@end diff --git a/UnitTesting/GTMUnitTestingUtilities.m b/UnitTesting/GTMUnitTestingUtilities.m index 5ce4219..2bd7b88 100644 --- a/UnitTesting/GTMUnitTestingUtilities.m +++ b/UnitTesting/GTMUnitTestingUtilities.m @@ -168,25 +168,6 @@ CantWorkWithScreenSaver: [self postKeyEvent:NSKeyUp character:keyChar modifiers:cocoaModifiers]; } -// Runs the event loop in NSDefaultRunLoopMode until date. Can be useful for -// testing user interface responses in a controlled timed event loop. For most -// uses using: -// [[NSRunLoop currentRunLoop] runUntilDate:date] -// will do. The only reason you would want to use this is if you were -// using the postKeyEvent:character:modifiers to send events and wanted to -// receive user input. -// Arguments: -// date - end of execution time -+ (void)runUntilDate:(NSDate*)date { - NSEvent *event; - while ((event = [NSApp nextEventMatchingMask:NSAnyEventMask - untilDate:date - inMode:NSDefaultRunLoopMode - dequeue:YES])) { - [NSApp sendEvent:event]; - } -} - @end BOOL GTMAreCMProfilesEqual(CMProfileRef a, CMProfileRef b) { @@ -333,3 +314,78 @@ static CGKeyCode GTMKeyCodeForCharCode(CGCharCode charCode) { return outCode; } +@implementation GTMUnitTestingBooleanRunLoopContext + ++ (id)context { + return [[[GTMUnitTestingBooleanRunLoopContext alloc] init] autorelease]; +} + +- (BOOL)shouldStop { + return shouldStop_; +} + +- (void)setShouldStop:(BOOL)stop { + shouldStop_ = stop; +} + +@end + +@implementation NSRunLoop (GTMUnitTestingAdditions) + +- (BOOL)gtm_runUpToSixtySecondsWithContext:(id<GTMUnitTestingRunLoopContext>)context { + return [self gtm_runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60] + context:context]; +} + +- (BOOL)gtm_runUntilDate:(NSDate *)date + context:(id<GTMUnitTestingRunLoopContext>)context { + return [self gtm_runUntilDate:date mode:NSDefaultRunLoopMode context:context]; +} + +- (BOOL)gtm_runUntilDate:(NSDate *)date + mode:(NSString *)mode + context:(id<GTMUnitTestingRunLoopContext>)context { + BOOL contextShouldStop = NO; + NSRunLoop *rl = [NSRunLoop currentRunLoop]; + while (1) { + contextShouldStop = [context shouldStop]; + if (contextShouldStop) break; + NSDate* next = [[NSDate alloc] initWithTimeIntervalSinceNow:0.01]; + if (!([rl runMode:mode beforeDate:next])) break; + if ([next compare:date] == NSOrderedDescending) break; + [next release]; + } + return contextShouldStop; +} + +@end + +@implementation NSApplication (GTMUnitTestingAdditions) + +- (BOOL)gtm_runUntilDate:(NSDate *)date + context:(id<GTMUnitTestingRunLoopContext>)context { + BOOL contextShouldStop = NO; + while (1) { + contextShouldStop = [context shouldStop]; + if (contextShouldStop) break; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:date + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (!event) { + [pool drain]; + break; + } + [NSApp sendEvent:event]; + [pool drain]; + } + return contextShouldStop; +} + +- (BOOL)gtm_runUpToSixtySecondsWithContext:(id<GTMUnitTestingRunLoopContext>)context { + return [self gtm_runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60] + context:context]; +} + +@end |