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 /UnitTesting | |
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)
Diffstat (limited to 'UnitTesting')
-rw-r--r-- | UnitTesting/GTMUnitTestingUtilities.h | 83 | ||||
-rw-r--r-- | UnitTesting/GTMUnitTestingUtilities.m | 94 |
2 files changed, 148 insertions, 29 deletions
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 |