diff options
author | gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2010-04-15 17:31:09 +0000 |
---|---|---|
committer | gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2010-04-15 17:31:09 +0000 |
commit | 2ddb245fac99802e63742c21f7da0d499e9cecf6 (patch) | |
tree | 3eebbf1aeed8269c8b14de81e8b680e784b8709b /UnitTesting/GTMAppKitUnitTestingUtilities.m | |
parent | 30ac0ff87b594d7bca3a5cc2789ca415840ea0b1 (diff) |
[Author: dmaclach]
refactor GTMUnitTestingUtilities into GTMAppKitUnitTestingUtilties and GTMFoundationUnitTestingUtilities.
convert GTMSignalHandler over to using new runloop routines
fix bug in GTMSignalHandler API with GC where releasing it wasn't sufficient to stop it listening.
R=thomasvl
DELTA=1227 (638 added, 566 deleted, 23 changed)
Diffstat (limited to 'UnitTesting/GTMAppKitUnitTestingUtilities.m')
-rw-r--r-- | UnitTesting/GTMAppKitUnitTestingUtilities.m | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/UnitTesting/GTMAppKitUnitTestingUtilities.m b/UnitTesting/GTMAppKitUnitTestingUtilities.m new file mode 100644 index 0000000..551738e --- /dev/null +++ b/UnitTesting/GTMAppKitUnitTestingUtilities.m @@ -0,0 +1,326 @@ +// +// GTMAppKitUnitTestingUtilities.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMAppKitUnitTestingUtilities.h" +#import <AppKit/AppKit.h> +#include <signal.h> +#include <unistd.h> +#import "GTMDefines.h" +#import "GTMGarbageCollection.h" + +// The Users profile before we change it on them +static CMProfileRef gGTMCurrentColorProfile = NULL; + +// Compares two color profiles +static BOOL GTMAreCMProfilesEqual(CMProfileRef a, CMProfileRef b); +// Stores the user's color profile away, and changes over to generic. +static void GTMSetColorProfileToGenericRGB(); +// Restores the users profile. +static void GTMRestoreColorProfile(void); +// Signal handler to try and restore users profile. +static void GTMHandleCrashSignal(int signalNumber); + +static CGKeyCode GTMKeyCodeForCharCode(CGCharCode charCode); + +@implementation GTMAppKitUnitTestingUtilities + +// Sets up the user interface so that we can run consistent UI unittests on it. ++ (void)setUpForUIUnitTests { + // Give some names to undocumented defaults values + const NSInteger MediumFontSmoothing = 2; + const NSInteger BlueTintedAppearance = 1; + + // This sets up some basic values that we want as our defaults for doing pixel + // based user interface tests. These defaults only apply to the unit test app, + // except or the color profile which will be set system wide, and then + // restored when the tests complete. + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + // Scroll arrows together bottom + [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"]; + // Smallest font size to CG should perform antialiasing on + [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"]; + // Type of smoothing + [defaults setInteger:MediumFontSmoothing forKey:@"AppleFontSmoothing"]; + // Blue aqua + [defaults setInteger:BlueTintedAppearance forKey:@"AppleAquaColorVariant"]; + // Standard highlight colors + [defaults setObject:@"0.709800 0.835300 1.000000" + forKey:@"AppleHighlightColor"]; + [defaults setObject:@"0.500000 0.500000 0.500000" + forKey:@"AppleOtherHighlightColor"]; + // Use english plz + [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"]; + // How fast should we draw sheets. This speeds up the sheet tests considerably + [defaults setFloat:.001f forKey:@"NSWindowResizeTime"]; + // Switch over the screen profile to "generic rgb". This installs an + // atexit handler to return our profile back when we are done. + GTMSetColorProfileToGenericRGB(); +} + ++ (void)setUpForUIUnitTestsIfBeingTested { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + if ([GTMFoundationUnitTestingUtilities areWeBeingUnitTested]) { + [self setUpForUIUnitTests]; + } + [pool drain]; +} + ++ (BOOL)isScreenSaverActive { + BOOL answer = NO; + ProcessSerialNumber psn; + if (GetFrontProcess(&psn) == noErr) { + CFDictionaryRef cfProcessInfo + = ProcessInformationCopyDictionary(&psn, + kProcessDictionaryIncludeAllInformationMask); + NSDictionary *processInfo = GTMCFAutorelease(cfProcessInfo); + + NSString *bundlePath = [processInfo objectForKey:@"BundlePath"]; + // ScreenSaverEngine is the frontmost app if the screen saver is actually + // running Security Agent is the frontmost app if the "enter password" + // dialog is showing + NSString *bundleName = [bundlePath lastPathComponent]; + answer = ([bundleName isEqualToString:@"ScreenSaverEngine.app"] + || [bundleName isEqualToString:@"SecurityAgent.app"]); + } + return answer; +} + +// Allows for posting either a keydown or a keyup with all the modifiers being +// applied. Passing a 'g' with NSKeyDown and NSShiftKeyMask +// generates two events (a shift key key down and a 'g' key keydown). Make sure +// to balance this with a keyup, or things could get confused. Events get posted +// using the CGRemoteOperation events which means that it gets posted in the +// system event queue. Thus you can affect other applications if your app isn't +// the active app (or in some cases, such as hotkeys, even if it is). +// Arguments: +// type - Event type. Currently accepts NSKeyDown and NSKeyUp +// keyChar - character on the keyboard to type. Make sure it is lower case. +// If you need upper case, pass in the NSShiftKeyMask in the +// modifiers. i.e. to generate "G" pass in 'g' and NSShiftKeyMask. +// to generate "+" pass in '=' and NSShiftKeyMask. +// cocoaModifiers - an int made up of bit masks. Handles NSAlphaShiftKeyMask, +// NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask, and +// NSCommandKeyMask ++ (void)postKeyEvent:(NSEventType)type + character:(CGCharCode)keyChar + modifiers:(UInt32)cocoaModifiers { + require(![self isScreenSaverActive], CantWorkWithScreenSaver); + require(type == NSKeyDown || type == NSKeyUp, CantDoEvent); + CGKeyCode code = GTMKeyCodeForCharCode(keyChar); + verify(code != 256); + CGEventRef event = CGEventCreateKeyboardEvent(NULL, code, type == NSKeyDown); + require(event, CantCreateEvent); + CGEventSetFlags(event, cocoaModifiers); + CGEventPost(kCGSessionEventTap, event); + CFRelease(event); +CantCreateEvent: +CantDoEvent: +CantWorkWithScreenSaver: + return; +} + +// Syntactic sugar for posting a keydown immediately followed by a key up event +// which is often what you really want. +// Arguments: +// keyChar - character on the keyboard to type. Make sure it is lower case. +// If you need upper case, pass in the NSShiftKeyMask in the +// modifiers. i.e. to generate "G" pass in 'g' and NSShiftKeyMask. +// to generate "+" pass in '=' and NSShiftKeyMask. +// cocoaModifiers - an int made up of bit masks. Handles NSAlphaShiftKeyMask, +// NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask, and +// NSCommandKeyMask ++ (void)postTypeCharacterEvent:(CGCharCode)keyChar modifiers:(UInt32)cocoaModifiers { + [self postKeyEvent:NSKeyDown character:keyChar modifiers:cocoaModifiers]; + [self postKeyEvent:NSKeyUp character:keyChar modifiers:cocoaModifiers]; +} + +@end + +BOOL GTMAreCMProfilesEqual(CMProfileRef a, CMProfileRef b) { + BOOL equal = YES; + if (a != b) { + CMProfileMD5 aMD5; + CMProfileMD5 bMD5; + CMError aMD5Err = CMGetProfileMD5(a, aMD5); + CMError bMD5Err = CMGetProfileMD5(b, bMD5); + equal = (!aMD5Err && + !bMD5Err && + !memcmp(aMD5, bMD5, sizeof(CMProfileMD5))) ? YES : NO; + } + return equal; +} + +void GTMRestoreColorProfile(void) { + if (gGTMCurrentColorProfile) { + CGDirectDisplayID displayID = CGMainDisplayID(); + CMError error = CMSetProfileByAVID((UInt32)displayID, + gGTMCurrentColorProfile); + CMCloseProfile(gGTMCurrentColorProfile); + if (error) { + // COV_NF_START + // No way to force this case in a unittest. + _GTMDevLog(@"Failed to restore previous color profile! " + "You may need to open System Preferences : Displays : Color " + "and manually restore your color settings. (Error: %i)", error); + // COV_NF_END + } else { + _GTMDevLog(@"Color profile restored"); + } + gGTMCurrentColorProfile = NULL; + } +} + +void GTMHandleCrashSignal(int signalNumber) { + // Going down in flames, might as well try to restore the color profile + // anyways. + GTMRestoreColorProfile(); + // Go ahead and exit with the signal value relayed just incase. + _exit(signalNumber + 128); +} + +void GTMSetColorProfileToGenericRGB(void) { + NSColorSpace *genericSpace = [NSColorSpace genericRGBColorSpace]; + CMProfileRef genericProfile = (CMProfileRef)[genericSpace colorSyncProfile]; + CMProfileRef previousProfile; + CGDirectDisplayID displayID = CGMainDisplayID(); + CMError error = CMGetProfileByAVID((UInt32)displayID, &previousProfile); + if (error) { + // COV_NF_START + // No way to force this case in a unittest. + _GTMDevLog(@"Failed to get current color profile. " + "I will not be able to restore your current profile, thus I'm " + "not changing it. Many unit tests may fail as a result. (Error: %i)", + error); + return; + // COV_NF_END + } + if (GTMAreCMProfilesEqual(genericProfile, previousProfile)) { + CMCloseProfile(previousProfile); + return; + } + CFStringRef previousProfileName; + CFStringRef genericProfileName; + CMCopyProfileDescriptionString(previousProfile, &previousProfileName); + CMCopyProfileDescriptionString(genericProfile, &genericProfileName); + + _GTMDevLog(@"Temporarily changing your system color profile from \"%@\" to \"%@\".", + previousProfileName, genericProfileName); + _GTMDevLog(@"This allows the pixel-based unit-tests to have consistent color " + "values across all machines."); + _GTMDevLog(@"The colors on your screen will change for the duration of the testing."); + + + if ((error = CMSetProfileByAVID((UInt32)displayID, genericProfile))) { + // COV_NF_START + // No way to force this case in a unittest. + _GTMDevLog(@"Failed to set color profile to \"%@\"! Many unit tests will fail as " + "a result. (Error: %i)", genericProfileName, error); + // COV_NF_END + } else { + gGTMCurrentColorProfile = previousProfile; + atexit(GTMRestoreColorProfile); + // WebKit DRT and Chrome TestShell both use this trick. If the test is + // already crashing, might as well try restoring the color profile, and if + // it fails, it is no worse than crashing without having tried. + signal(SIGILL, GTMHandleCrashSignal); + signal(SIGTRAP, GTMHandleCrashSignal); + signal(SIGEMT, GTMHandleCrashSignal); + signal(SIGFPE, GTMHandleCrashSignal); + signal(SIGBUS, GTMHandleCrashSignal); + signal(SIGSEGV, GTMHandleCrashSignal); + signal(SIGSYS, GTMHandleCrashSignal); + signal(SIGPIPE, GTMHandleCrashSignal); + signal(SIGXCPU, GTMHandleCrashSignal); + signal(SIGXFSZ, GTMHandleCrashSignal); + } + CFRelease(previousProfileName); + CFRelease(genericProfileName); +} + +// Returns a virtual key code for a given charCode. Handles all of the +// NS*FunctionKeys as well. +static CGKeyCode GTMKeyCodeForCharCode(CGCharCode charCode) { + // character map taken from http://classicteck.com/rbarticles/mackeyboard.php + int characters[] = { + 'a', 's', 'd', 'f', 'h', 'g', 'z', 'x', 'c', 'v', 256, 'b', 'q', 'w', + 'e', 'r', 'y', 't', '1', '2', '3', '4', '6', '5', '=', '9', '7', '-', + '8', '0', ']', 'o', 'u', '[', 'i', 'p', '\n', 'l', 'j', '\'', 'k', ';', + '\\', ',', '/', 'n', 'm', '.', '\t', ' ', '`', '\b', 256, '\e' + }; + + // function key map taken from + // file:///Developer/ADC%20Reference%20Library/documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/Classes/NSEvent.html + int functionKeys[] = { + // NSUpArrowFunctionKey - NSF12FunctionKey + 126, 125, 123, 124, 122, 120, 99, 118, 96, 97, 98, 100, 101, 109, 103, 111, + // NSF13FunctionKey - NSF28FunctionKey + 105, 107, 113, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + // NSF29FunctionKey - NSScrollLockFunctionKey + 256, 256, 256, 256, 256, 256, 256, 256, 117, 115, 256, 119, 116, 121, 256, 256, + // NSPauseFunctionKey - NSPrevFunctionKey + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + // NSNextFunctionKey - NSModeSwitchFunctionKey + 256, 256, 256, 256, 256, 256, 114, 1 + }; + + CGKeyCode outCode = 0; + + // Look in the function keys + if (charCode >= NSUpArrowFunctionKey && charCode <= NSModeSwitchFunctionKey) { + outCode = functionKeys[charCode - NSUpArrowFunctionKey]; + } else { + // Look in our character map + for (size_t i = 0; i < (sizeof(characters) / sizeof (int)); i++) { + if (characters[i] == charCode) { + outCode = i; + break; + } + } + } + return outCode; +} + +@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 |