aboutsummaryrefslogtreecommitdiff
path: root/UnitTesting
diff options
context:
space:
mode:
authorGravatar gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2010-04-14 17:08:30 +0000
committerGravatar gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2010-04-14 17:08:30 +0000
commit30ac0ff87b594d7bca3a5cc2789ca415840ea0b1 (patch)
tree8057e5c0112ba796b93082b903d54b3aed0b9e47 /UnitTesting
parentd2a270d64ee1dd3b0d719359170e2d6511fcb669 (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.h83
-rw-r--r--UnitTesting/GTMUnitTestingUtilities.m94
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