PageRenderTime 83ms CodeModel.GetById 9ms app.highlight 68ms RepoModel.GetById 1ms app.codeStats 0ms

/core/externals/update-engine/externals/google-toolbox-for-mac/UnitTesting/GTMAppKitUnitTestingUtilities.m

http://macfuse.googlecode.com/
Objective C | 326 lines | 205 code | 28 blank | 93 comment | 27 complexity | 8c26d6ca7301eb48c66bde32709a18b8 MD5 | raw file
  1//
  2//  GTMAppKitUnitTestingUtilities.m
  3//
  4//  Copyright 2006-2008 Google Inc.
  5//
  6//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  7//  use this file except in compliance with the License.  You may obtain a copy
  8//  of the License at
  9//
 10//  http://www.apache.org/licenses/LICENSE-2.0
 11//
 12//  Unless required by applicable law or agreed to in writing, software
 13//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 14//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 15//  License for the specific language governing permissions and limitations under
 16//  the License.
 17//
 18
 19#import "GTMAppKitUnitTestingUtilities.h"
 20#import <AppKit/AppKit.h>
 21#include <signal.h>
 22#include <unistd.h>
 23#import "GTMDefines.h"
 24
 25// The Users profile before we change it on them
 26static CMProfileRef gGTMCurrentColorProfile = NULL;
 27
 28// Compares two color profiles
 29static BOOL GTMAreCMProfilesEqual(CMProfileRef a, CMProfileRef b);
 30// Stores the user's color profile away, and changes over to generic.
 31static void GTMSetColorProfileToGenericRGB();
 32// Restores the users profile.
 33static void GTMRestoreColorProfile(void);
 34// Signal handler to try and restore users profile.
 35static void GTMHandleCrashSignal(int signalNumber);
 36
 37static CGKeyCode GTMKeyCodeForCharCode(CGCharCode charCode);
 38
 39@implementation GTMAppKitUnitTestingUtilities
 40
 41// Sets up the user interface so that we can run consistent UI unittests on it.
 42+ (void)setUpForUIUnitTests {
 43  // Give some names to undocumented defaults values
 44  const NSInteger MediumFontSmoothing = 2;
 45  const NSInteger BlueTintedAppearance = 1;
 46
 47  // This sets up some basic values that we want as our defaults for doing pixel
 48  // based user interface tests. These defaults only apply to the unit test app,
 49  // except or the color profile which will be set system wide, and then
 50  // restored when the tests complete.
 51  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
 52  // Scroll arrows together bottom
 53  [defaults setObject:@"DoubleMax" forKey:@"AppleScrollBarVariant"];
 54  // Smallest font size to CG should perform antialiasing on
 55  [defaults setInteger:4 forKey:@"AppleAntiAliasingThreshold"];
 56  // Type of smoothing
 57  [defaults setInteger:MediumFontSmoothing forKey:@"AppleFontSmoothing"];
 58  // Blue aqua
 59  [defaults setInteger:BlueTintedAppearance forKey:@"AppleAquaColorVariant"];
 60  // Standard highlight colors
 61  [defaults setObject:@"0.709800 0.835300 1.000000"
 62               forKey:@"AppleHighlightColor"];
 63  [defaults setObject:@"0.500000 0.500000 0.500000"
 64               forKey:@"AppleOtherHighlightColor"];
 65  // Use english plz
 66  [defaults setObject:[NSArray arrayWithObject:@"en"] forKey:@"AppleLanguages"];
 67  // How fast should we draw sheets. This speeds up the sheet tests considerably
 68  [defaults setFloat:.001f forKey:@"NSWindowResizeTime"];
 69  // Switch over the screen profile to "generic rgb". This installs an
 70  // atexit handler to return our profile back when we are done.
 71  GTMSetColorProfileToGenericRGB();
 72}
 73
 74+ (void)setUpForUIUnitTestsIfBeingTested {
 75  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 76  if ([GTMFoundationUnitTestingUtilities areWeBeingUnitTested]) {
 77    [self setUpForUIUnitTests];
 78  }
 79  [pool drain];
 80}
 81
 82+ (BOOL)isScreenSaverActive {
 83  BOOL answer = NO;
 84  ProcessSerialNumber psn;
 85  if (GetFrontProcess(&psn) == noErr) {
 86    CFDictionaryRef cfProcessInfo
 87      = ProcessInformationCopyDictionary(&psn,
 88                                         kProcessDictionaryIncludeAllInformationMask);
 89    NSDictionary *processInfo = GTMCFAutorelease(cfProcessInfo);
 90
 91    NSString *bundlePath = [processInfo objectForKey:@"BundlePath"];
 92    // ScreenSaverEngine is the frontmost app if the screen saver is actually
 93    // running Security Agent is the frontmost app if the "enter password"
 94    // dialog is showing
 95    NSString *bundleName = [bundlePath lastPathComponent];
 96    answer = ([bundleName isEqualToString:@"ScreenSaverEngine.app"]
 97              || [bundleName isEqualToString:@"SecurityAgent.app"]);
 98  }
 99  return answer;
100}
101
102// Allows for posting either a keydown or a keyup with all the modifiers being
103// applied. Passing a 'g' with NSKeyDown and NSShiftKeyMask
104// generates two events (a shift key key down and a 'g' key keydown). Make sure
105// to balance this with a keyup, or things could get confused. Events get posted
106// using the CGRemoteOperation events which means that it gets posted in the
107// system event queue. Thus you can affect other applications if your app isn't
108// the active app (or in some cases, such as hotkeys, even if it is).
109//  Arguments:
110//    type - Event type. Currently accepts NSKeyDown and NSKeyUp
111//    keyChar - character on the keyboard to type. Make sure it is lower case.
112//              If you need upper case, pass in the NSShiftKeyMask in the
113//              modifiers. i.e. to generate "G" pass in 'g' and NSShiftKeyMask.
114//              to generate "+" pass in '=' and NSShiftKeyMask.
115//    cocoaModifiers - an int made up of bit masks. Handles NSAlphaShiftKeyMask,
116//                    NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask, and
117//                    NSCommandKeyMask
118+ (void)postKeyEvent:(NSEventType)type
119           character:(CGCharCode)keyChar
120           modifiers:(UInt32)cocoaModifiers {
121  require(![self isScreenSaverActive], CantWorkWithScreenSaver);
122  require(type == NSKeyDown || type == NSKeyUp, CantDoEvent);
123  CGKeyCode code = GTMKeyCodeForCharCode(keyChar);
124  verify(code != 256);
125  CGEventRef event = CGEventCreateKeyboardEvent(NULL, code, type == NSKeyDown);
126  require(event, CantCreateEvent);
127  CGEventSetFlags(event, cocoaModifiers);
128  CGEventPost(kCGSessionEventTap, event);
129  CFRelease(event);
130CantCreateEvent:
131CantDoEvent:
132CantWorkWithScreenSaver:
133  return;
134}
135
136// Syntactic sugar for posting a keydown immediately followed by a key up event
137// which is often what you really want.
138//  Arguments:
139//    keyChar - character on the keyboard to type. Make sure it is lower case.
140//              If you need upper case, pass in the NSShiftKeyMask in the
141//              modifiers. i.e. to generate "G" pass in 'g' and NSShiftKeyMask.
142//              to generate "+" pass in '=' and NSShiftKeyMask.
143//    cocoaModifiers - an int made up of bit masks. Handles NSAlphaShiftKeyMask,
144//                    NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask, and
145//                    NSCommandKeyMask
146+ (void)postTypeCharacterEvent:(CGCharCode)keyChar modifiers:(UInt32)cocoaModifiers {
147  [self postKeyEvent:NSKeyDown character:keyChar modifiers:cocoaModifiers];
148  [self postKeyEvent:NSKeyUp character:keyChar modifiers:cocoaModifiers];
149}
150
151@end
152
153BOOL GTMAreCMProfilesEqual(CMProfileRef a, CMProfileRef b) {
154  BOOL equal = YES;
155  if (a != b) {
156    CMProfileMD5 aMD5;
157    CMProfileMD5 bMD5;
158    CMError aMD5Err = CMGetProfileMD5(a, aMD5);
159    CMError bMD5Err = CMGetProfileMD5(b, bMD5);
160    equal = (!aMD5Err &&
161             !bMD5Err &&
162             !memcmp(aMD5, bMD5, sizeof(CMProfileMD5))) ? YES : NO;
163  }
164  return equal;
165}
166
167void GTMRestoreColorProfile(void) {
168  if (gGTMCurrentColorProfile) {
169    CGDirectDisplayID displayID = CGMainDisplayID();
170    CMError error = CMSetProfileByAVID((UInt32)displayID,
171                                       gGTMCurrentColorProfile);
172    CMCloseProfile(gGTMCurrentColorProfile);
173    if (error) {
174      // COV_NF_START
175      // No way to force this case in a unittest.
176      _GTMDevLog(@"Failed to restore previous color profile! "
177                 @"You may need to open System Preferences : Displays : Color "
178                 @"and manually restore your color settings. (Error: %ld)",
179                 (long)error);
180      // COV_NF_END
181    } else {
182      _GTMDevLog(@"Color profile restored");
183    }
184    gGTMCurrentColorProfile = NULL;
185  }
186}
187
188void GTMHandleCrashSignal(int signalNumber) {
189  // Going down in flames, might as well try to restore the color profile
190  // anyways.
191  GTMRestoreColorProfile();
192  // Go ahead and exit with the signal value relayed just incase.
193  _exit(signalNumber + 128);
194}
195
196void GTMSetColorProfileToGenericRGB(void) {
197  NSColorSpace *genericSpace = [NSColorSpace genericRGBColorSpace];
198  CMProfileRef genericProfile = (CMProfileRef)[genericSpace colorSyncProfile];
199  CMProfileRef previousProfile;
200  CGDirectDisplayID displayID = CGMainDisplayID();
201  CMError error = CMGetProfileByAVID((UInt32)displayID, &previousProfile);
202  if (error) {
203    // COV_NF_START
204    // No way to force this case in a unittest.
205    _GTMDevLog(@"Failed to get current color profile. "
206               "I will not be able to restore your current profile, thus I'm "
207               "not changing it. Many unit tests may fail as a result. (Error: %li)",
208          (long)error);
209    return;
210    // COV_NF_END
211  }
212  if (GTMAreCMProfilesEqual(genericProfile, previousProfile)) {
213    CMCloseProfile(previousProfile);
214    return;
215  }
216  CFStringRef previousProfileName;
217  CFStringRef genericProfileName;
218  CMCopyProfileDescriptionString(previousProfile, &previousProfileName);
219  CMCopyProfileDescriptionString(genericProfile, &genericProfileName);
220
221  _GTMDevLog(@"Temporarily changing your system color profile from \"%@\" to \"%@\".",
222             previousProfileName, genericProfileName);
223  _GTMDevLog(@"This allows the pixel-based unit-tests to have consistent color "
224             "values across all machines.");
225  _GTMDevLog(@"The colors on your screen will change for the duration of the testing.");
226
227
228  if ((error = CMSetProfileByAVID((UInt32)displayID, genericProfile))) {
229    // COV_NF_START
230    // No way to force this case in a unittest.
231    _GTMDevLog(@"Failed to set color profile to \"%@\"! Many unit tests will fail as "
232               "a result.  (Error: %li)", genericProfileName, (long)error);
233    // COV_NF_END
234  } else {
235    gGTMCurrentColorProfile = previousProfile;
236    atexit(GTMRestoreColorProfile);
237    // WebKit DRT and Chrome TestShell both use this trick. If the test is
238    // already crashing, might as well try restoring the color profile, and if
239    // it fails, it is no worse than crashing without having tried.
240    signal(SIGILL, GTMHandleCrashSignal);
241    signal(SIGTRAP, GTMHandleCrashSignal);
242    signal(SIGEMT, GTMHandleCrashSignal);
243    signal(SIGFPE, GTMHandleCrashSignal);
244    signal(SIGBUS, GTMHandleCrashSignal);
245    signal(SIGSEGV, GTMHandleCrashSignal);
246    signal(SIGSYS, GTMHandleCrashSignal);
247    signal(SIGPIPE, GTMHandleCrashSignal);
248    signal(SIGXCPU, GTMHandleCrashSignal);
249    signal(SIGXFSZ, GTMHandleCrashSignal);
250  }
251  CFRelease(previousProfileName);
252  CFRelease(genericProfileName);
253}
254
255// Returns a virtual key code for a given charCode. Handles all of the
256// NS*FunctionKeys as well.
257static CGKeyCode GTMKeyCodeForCharCode(CGCharCode charCode) {
258  // character map taken from http://classicteck.com/rbarticles/mackeyboard.php
259  int characters[] = {
260    'a', 's', 'd', 'f', 'h', 'g', 'z', 'x', 'c', 'v', 256, 'b', 'q', 'w',
261    'e', 'r', 'y', 't', '1', '2', '3', '4', '6', '5', '=', '9', '7', '-',
262    '8', '0', ']', 'o', 'u', '[', 'i', 'p', '\n', 'l', 'j', '\'', 'k', ';',
263    '\\', ',', '/', 'n', 'm', '.', '\t', ' ', '`', '\b', 256, '\e'
264  };
265
266  // function key map taken from
267  // file:///Developer/ADC%20Reference%20Library/documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/Classes/NSEvent.html
268  int functionKeys[] = {
269    // NSUpArrowFunctionKey - NSF12FunctionKey
270    126, 125, 123, 124, 122, 120, 99, 118, 96, 97, 98, 100, 101, 109, 103, 111,
271    // NSF13FunctionKey - NSF28FunctionKey
272    105, 107, 113, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
273    // NSF29FunctionKey - NSScrollLockFunctionKey
274    256, 256, 256, 256, 256, 256, 256, 256, 117, 115, 256, 119, 116, 121, 256, 256,
275    // NSPauseFunctionKey - NSPrevFunctionKey
276    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
277    // NSNextFunctionKey - NSModeSwitchFunctionKey
278    256, 256, 256, 256, 256, 256, 114, 1
279  };
280
281  CGKeyCode outCode = 0;
282
283  // Look in the function keys
284  if (charCode >= NSUpArrowFunctionKey && charCode <= NSModeSwitchFunctionKey) {
285    outCode = functionKeys[charCode - NSUpArrowFunctionKey];
286  } else {
287    // Look in our character map
288    for (size_t i = 0; i < (sizeof(characters) / sizeof (int)); i++) {
289      if (characters[i] == charCode) {
290        outCode = i;
291        break;
292      }
293    }
294  }
295  return outCode;
296}
297
298@implementation NSApplication (GTMUnitTestingRunAdditions)
299
300- (BOOL)gtm_runUntilDate:(NSDate *)date
301                 context:(id<GTMUnitTestingRunLoopContext>)context {
302  BOOL contextShouldStop = NO;
303  while (1) {
304    contextShouldStop = [context shouldStop];
305    if (contextShouldStop) break;
306    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
307    NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask
308                                         untilDate:date
309                                            inMode:NSDefaultRunLoopMode
310                                           dequeue:YES];
311    if (!event) {
312      [pool drain];
313      break;
314    }
315    [NSApp sendEvent:event];
316    [pool drain];
317  }
318  return contextShouldStop;
319}
320
321- (BOOL)gtm_runUpToSixtySecondsWithContext:(id<GTMUnitTestingRunLoopContext>)context {
322  return [self gtm_runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60]
323                        context:context];
324}
325
326@end