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