aboutsummaryrefslogtreecommitdiff
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
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)
-rw-r--r--AppKit/GTMCarbonEventTest.m31
-rw-r--r--AppKit/GTMNSAnimatablePropertyContainerTest.h4
-rw-r--r--AppKit/GTMNSAnimatablePropertyContainerTest.m32
-rw-r--r--UnitTesting/GTMUnitTestingUtilities.h83
-rw-r--r--UnitTesting/GTMUnitTestingUtilities.m94
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