aboutsummaryrefslogtreecommitdiff
path: root/UnitTesting/GTMNSObject+UnitTesting.m
blob: 5be6b9fa4a5756bb3b44812181e6b3532d1c94a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
//
//  GTMNSObject+UnitTesting.m
//  
//  An informal protocol for doing advanced unittesting with objects.
//
//  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.
//

#include <Carbon/Carbon.h>
#include <mach-o/arch.h>

#import "GTMNSObject+UnitTesting.h"
#import "GTMNSWorkspace+Theme.h"
#import "GTMSystemVersion.h"

NSString *const GTMUnitTestingEncodedObjectNotification = @"GTMUnitTestingEncodedObjectNotification";
NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey";

// This class exists so that we can locate our bundle using [NSBundle
// bundleForClass:]. We don't use [NSBundle mainBundle] because when we are
// being run as a unit test, we aren't the mainBundle
@interface GTMUnitTestingAdditionsBundleFinder : NSObject {
  // Nothing here
}
// or here
@end

@implementation GTMUnitTestingAdditionsBundleFinder 
// Nothing here. We're just interested in the name for finding our bundle.
@end

@interface NSObject (GTMUnitTestingAdditionsPrivate)
///  Find the path for a file named name.extension in your bundle.
//  Searches for the following:
//  "name.extension", 
//  "name.arch.extension", 
//  "name.arch.OSVersionMajor.extension"
//  "name.arch.OSVersionMajor.OSVersionMinor.extension"
//  "name.arch.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.extension"
//  "name.arch.OSVersionMajor.extension"
//  "name.OSVersionMajor.arch.extension"
//  "name.OSVersionMajor.OSVersionMinor.arch.extension"
//  "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.arch.extension"
//  "name.OSVersionMajor.extension"
//  "name.OSVersionMajor.OSVersionMinor.extension"
//  "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.extension"
//  Do not include the ".extension" extension on your name.
//
//  Args:
//    name: The name for the file you would like to find.
//    extension: the extension for the file you would like to find
//
//  Returns:
//    the path if the file exists in your bundle
//    or nil if no file is found
//
- (NSString *)pathForFileNamed:(NSString*)name extension:(NSString*)extension;
- (NSString *)saveToPathForFileNamed:(NSString*)name 
                           extension:(NSString*)extension;
@end

// This is a keyed coder for storing unit test state data. It is used only by
// the GTMUnitTestingAdditions category. Most of the work is done in
// encodeObject:forKey:.
@interface GTMUnitTestingKeyedCoder : NSCoder {
  NSMutableDictionary *dictionary_; // storage for data (STRONG)
}

//  get the data stored in coder.
//
//  Returns:
//    NSDictionary with currently stored data.
- (NSDictionary*)dictionary; 
@end

@implementation GTMUnitTestingKeyedCoder

//  Set up storage for coder. Stores type and version.
//  Version 1
// 
//  Returns:
//    self
- (id)init {
  self = [super init];
  if (self != nil) {
    dictionary_ = [[NSMutableDictionary alloc] initWithCapacity:0];
    [dictionary_ setObject:@"GTMUnitTestingArchive" forKey:@"$GTMArchive"];
    
    // Version number can be changed here.
    [dictionary_ setObject:[NSNumber numberWithInt:1] forKey:@"$GTMVersion"];
  }
  return self;
}

// Standard dealloc
- (void)dealloc {
  [dictionary_ release];
  [super dealloc];
}

// Utility function for checking for a key value. We don't want duplicate keys
// in any of our dictionaries as we may be writing over data stored by previous
// objects.
//
//  Arguments:
//    key - key to check for in dictionary
- (void)checkForKey:(NSString*)key {
  NSAssert1(![dictionary_ objectForKey:key], @"Key already exists for %@", key);
}

// Key routine for the encoder. We store objects in our dictionary based on
// their key. As we encode objects we send out notifications to let other
// classes doing tests add their specific data to the base types. If we can't
// encode the object (it doesn't support unitTestEncodeState) and we don't get
// any info back from the notifier, we attempt to store it's description.
//
//  Arguments:
//    objv - object to be encoded
//    key - key to encode it with
//
- (void)encodeObject:(id)objv forKey:(NSString *)key {
  // Sanity checks
  if (!objv) return;
  [self checkForKey:key];
  
  // Set up a new dictionary for the current object
  NSMutableDictionary *curDictionary = dictionary_;
  dictionary_ = [[NSMutableDictionary alloc] initWithCapacity:0];
  
  // If objv responds to unitTestEncodeState get it to record
  // its data.
  if ([objv respondsToSelector:@selector(unitTestEncodeState:)]) {
    [objv unitTestEncodeState:self];
  }
  
  // We then send out a notification to let other folks
  // add data for this object
  NSDictionary *notificationDict = [NSDictionary dictionaryWithObject:self
                                                               forKey:GTMUnitTestingEncoderKey];
  [[NSNotificationCenter defaultCenter] postNotificationName:GTMUnitTestingEncodedObjectNotification
                                                      object:objv
                                                    userInfo:notificationDict];
  
  // If we got anything from the object, or from the notification, store it in
  // our dictionary. Otherwise store the description.
  if ([dictionary_ count] > 0) {
    [curDictionary setObject:dictionary_ forKey:key];
  } else {
    NSString *description = [objv description];
    // If description has a pointer value in it, we don't want to store it
    // as the pointer value can change from run to run
    if (description && [description rangeOfString:@"0x"].length == 0) {
      [curDictionary setObject:description forKey:key];
    } else {
      NSAssert1(NO, @"Unable to encode forKey: %@", key);
    }
  }
  [dictionary_ release];
  dictionary_ = curDictionary;
}

//  Basic encoding methods for POD types.
//
//  Arguments:
//    *v - value to encode
//    key - key to encode it in
- (void)encodeConditionalObject:(id)objv forKey:(NSString *)key {
  [self checkForKey:key];
  [self encodeObject:(id)objv forKey:key];
}

- (void)encodeBool:(BOOL)boolv forKey:(NSString *)key {
  [self checkForKey:key];
  [dictionary_ setObject:[NSNumber numberWithBool:boolv] forKey:key];
}

- (void)encodeInt:(int)intv forKey:(NSString *)key {
  [self checkForKey:key];
  [dictionary_ setObject:[NSNumber numberWithInt:intv] forKey:key];
}

- (void)encodeInt32:(int32_t)intv forKey:(NSString *)key {
  [self checkForKey:key];
  [dictionary_ setObject:[NSNumber numberWithLong:intv] forKey:key];
}

- (void)encodeInt64:(int64_t)intv forKey:(NSString *)key {
  [self checkForKey:key];
  [dictionary_ setObject:[NSNumber numberWithLongLong:intv] forKey:key];
}

- (void)encodeFloat:(float)realv forKey:(NSString *)key {
  [self checkForKey:key];
  [dictionary_ setObject:[NSNumber numberWithFloat:realv] forKey:key];
}

- (void)encodeDouble:(double)realv forKey:(NSString *)key {
  [self checkForKey:key];
  [dictionary_ setObject:[NSNumber numberWithDouble:realv] forKey:key];
}

- (void)encodeBytes:(const uint8_t *)bytesp length:(unsigned)lenv forKey:(NSString *)key {
  [self checkForKey:key];
  [dictionary_ setObject:[NSData dataWithBytesNoCopy:(uint8_t*)bytesp length:lenv] forKey:key];
}

//  Get our storage back as an NSDictionary
//  
//  Returns:
//    NSDictionary containing our encoded info
-(NSDictionary*)dictionary {
  return [[dictionary_ retain] autorelease];
}

@end


@implementation NSObject (GTMUnitTestingAdditions)

// GTM_METHOD_CHECK(NSWorkspace, themeAppearance);
// GTM_METHOD_CHECK(NSWorkspace, themeScrollBarArrowStyle);

///  Find the path for a file named name.extension in your bundle.
//  Searches for the following:
//  "name.extension", 
//  "name.arch.extension", 
//  "name.arch.OSVersionMajor.extension"
//  "name.arch.OSVersionMajor.OSVersionMinor.extension"
//  "name.arch.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.extension"
//  "name.arch.OSVersionMajor.extension"
//  "name.OSVersionMajor.arch.extension"
//  "name.OSVersionMajor.OSVersionMinor.arch.extension"
//  "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.arch.extension"
//  "name.OSVersionMajor.extension"
//  "name.OSVersionMajor.OSVersionMinor.extension"
//  "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.extension"
//  Do not include the ".extension" extension on your name.
//
//  Args:
//    name: The name for the file you would like to find.
//    extension: the extension for the file you would like to find
//
//  Returns:
//    the path if the file exists in your bundle
//    or nil if no file is found
//
- (NSString *)pathForFileNamed:(NSString*)name extension:(NSString*)extension {
  NSString *thePath = nil;
  Class bundleClass = [GTMUnitTestingAdditionsBundleFinder class];
  NSBundle *myBundle = [NSBundle bundleForClass:bundleClass];
  NSAssert3(myBundle, @"Couldn't find bundle for class: %@ searching for file:%@.%@", 
            NSStringFromClass(bundleClass), name, extension);
  
  // Extensions
  NSString *extensions[2];
  const NXArchInfo *localInfo = NXGetLocalArchInfo();
  NSAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo");
  extensions[0] = [NSString stringWithUTF8String:localInfo->name];
  extensions[1] = @"";

  // System Version
  long major, minor, bugFix;
  [GTMSystemVersion getMajor:&major minor:&minor bugFix:&bugFix];
  NSString *systemVersions[4];
  systemVersions[0] = [NSString stringWithFormat:@".%d.%d.%d", major, minor, bugFix];
  systemVersions[1] = [NSString stringWithFormat:@".%d.%d", major, minor];
  systemVersions[2] = [NSString stringWithFormat:@".%d", major];
  systemVersions[3] = @"";
  
  // Note that we are searching for the most exact match first.
  for (int i = 0; !thePath && i < sizeof(extensions) / sizeof(*extensions); ++i) {
    for (int j = 0; !thePath && j < sizeof(systemVersions) / sizeof(*systemVersions); j++) {
      NSString *fullName = [NSString stringWithFormat:@"%@%@%@", name, extensions[i], systemVersions[j]];
      thePath = [myBundle pathForResource:fullName ofType:extension];
      if (thePath) break;
      fullName = [NSString stringWithFormat:@"%@%@%@", name, systemVersions[j], extensions[i]];
      thePath = [myBundle pathForResource:fullName ofType:extension];
    }
  }
  
  return thePath;
}  

- (NSString *)saveToPathForFileNamed:(NSString*)name 
                           extension:(NSString*)extension {
  NSString *newPath = nil;
  const NXArchInfo *localInfo = NXGetLocalArchInfo();
  NSAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo");
  long major, minor, bugFix;
  [GTMSystemVersion getMajor:&major minor:&minor bugFix:&bugFix];
  
  NSString *fullName = [NSString stringWithFormat:@"%@.%s.%d.%d.%d", 
                        name, localInfo->name, major, minor, bugFix];
  
  // Is this build under Pulse?
  if ([self isRunningUnderPulse]) {
    // Use the Pulse base directory
    newPath = [[[self pulseBaseDirectory] 
                stringByAppendingPathComponent:fullName]
               stringByAppendingPathExtension:extension];
  } else {
    // Developer build, use their home directory Desktop.
    newPath = [[[NSHomeDirectory() 
                 stringByAppendingPathComponent:@"Desktop"] 
                stringByAppendingPathComponent:fullName]
               stringByAppendingPathExtension:extension];
  }
  return newPath;
}
  
#pragma mark UnitTestImage

// Returns an image containing a representation of the object suitable for use
// in comparing against a master image. 
// NB this means that all colors should be device based, as colorsynced colors
// will be different on different devices.
//
//  Returns:
//    an image of the object
- (NSImage*)unitTestImage {
  // Must be overridden by subclasses
  [NSException raise:NSInternalInconsistencyException
              format:@"%@ must override -%@",
              NSStringFromClass([self class]),
              NSStringFromSelector(_cmd)];

  return nil;  // appease the compiler
}

// Checks to see that system settings are valid for doing an image comparison.
// The main issue is that we make sure that we are set to using Blue Aqua as
// our appearance and that the scroll arrows are set correctly.
//
// Returns:
//  YES if we can do image comparisons for this object type.
- (BOOL)areSystemSettingsValidForDoingImage {
  NSWorkspace *ws = [NSWorkspace sharedWorkspace];
  BOOL isGood = YES;
  
  if ([self needsAquaBlueAppearanceForDoingImage] &&
      ![[ws gtm_themeAppearance] isEqualToString:(NSString *)kThemeAppearanceAquaBlue]) {
    NSLog(@"Cannot do image test as appearance is not blue. "
          "Please set it in the Appearance System Preference.");
    isGood = NO;
  }
  
  if ([self needsScrollBarArrowsLowerRightForDoingImage] &&
      [ws gtm_themeScrollBarArrowStyle] != kThemeScrollBarArrowsLowerRight) {
    NSLog(@"Cannot do image test as scroll bar arrows are not together"
          "bottom right. Please set it in the Appearance System Preference.");
    isGood = NO;
  }
  
  return isGood;
}

// Defaults to the appearance not mattering, individual tests override.
- (BOOL)needsAquaBlueAppearanceForDoingImage {
  return NO;
}

// Defaults to the arrows not mattering, individual tests override.
- (BOOL)needsScrollBarArrowsLowerRightForDoingImage {
  return NO;
}

// Save the unitTestImage to a TIFF file with name |name| at
// ~/Desktop/|name|.tif. The TIFF will be compressed with LZW.
//
//  Note: When running under Pulse automation output is redirected to the
//  Pulse base directory.
//
//  Args:
//    name: The name for the TIFF file you would like saved.
//
//  Returns:
//    YES if the file was successfully saved.
//
- (BOOL)saveToTIFFNamed:(NSString*)name {
  NSString *newPath = [self saveToPathForFileNamed:name extension:@"tif"];
  return [self saveToTIFFAt:newPath];
}

//  Save unitTestImage of |self| to a TIFF file at path |path|.
//  The TIFF will be compressed with LZW. 
//
//  Args:
//    name: The name for the TIFF file you would like saved.
//
//  Returns:
//    YES if the file was successfully saved.
//
- (BOOL)saveToTIFFAt:(NSString*)path {
  if (!path) return NO;
  NSData *data = [self TIFFRepresentation];
  return [data writeToFile:path atomically:YES];
}

//  Compares unitTestImage of |self| to the TIFF located at |path|
//
//  Args:
//    path: the path to the TIFF file you want to compare against.
//
//  Returns:
//    YES if they are equal, NO is they are not
//
- (BOOL)compareWithTIFFNamed:(NSString*)name {
  NSString *path = [self pathForTIFFNamed:name];
  return [self compareWithTIFFAt:path];
}

//  Compares unitTestImage of |self| to the TIFF located at |path|
//
//  Args:
//    path: the path to the TIFF file you want to compare against.
//
//  Returns:
//    YES if they are equal, NO is they are not
//
- (BOOL)compareWithTIFFAt:(NSString*)path {
  BOOL answer = NO;
  NSData *fileData = [NSData dataWithContentsOfFile:path];
  if (fileData) {
    NSData *imageData = [self TIFFRepresentation];
    if (imageData) {
      NSBitmapImageRep *fileRep = [NSBitmapImageRep imageRepWithData:fileData];
      if (fileRep) {
        NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
        if (imageRep) {
          NSSize fileSize = [fileRep size];
          NSSize imageSize = [imageRep size];
          if (NSEqualSizes(fileSize,imageSize)) {
            // if all the sizes are equal, run through the bytes and compare
            // them for equality.
            answer = YES;
            for (int row = 0; row < fileSize.height; row++) {
              for (int col = 0; col < fileSize.width && answer == YES; col++) {
                NSColor *imageColor = [imageRep colorAtX:col y:row];
                NSColor *fileColor = [fileRep colorAtX:col y:row];
                
                answer = [imageColor isEqual:fileColor];
              }
            } 
          }
        }
      }
    }
  }
  return answer;
}
  
//  Find the path for a TIFF by name in your bundle.
//  Do not include the ".tif" extension on your name.
//
//  Args:
//    name: The name for the TIFF file you would like to find.
//
//  Returns:
//    the path if the TIFF exists in your bundle
//    or nil if no TIFF to be found
//
- (NSString *)pathForTIFFNamed:(NSString*)name {
  return [self pathForFileNamed:name extension:@"tif"];
}

//  Gives us a LZW compressed representation of unitTestImage of |self|.
//
//  Returns:
//    a LZW compressed TIFF if successful
//    nil if failed
//
- (NSData *)TIFFRepresentation {
  NSImage *image = [self unitTestImage];
  // factor is ignored unless compression style is NSJPEGCompression
  return [image TIFFRepresentationUsingCompression:NSTIFFCompressionLZW factor:0.0f];
}

#pragma mark UnitTestState

static NSString* const kGTMStateFileExtension = @"gtmUTState";

//  Save the encoded unit test state to a .gtmUTState file with name |name| at
//  ~/Desktop/|name|.gtmUTState.
//
//  Note: When running under Pulse automation output is redirected to the
//  Pulse base directory.
//
//  Args:
//    name: The name for the state file you would like saved.
//
//  Returns:
//    YES if the file was successfully saved.
//
- (BOOL)saveToStateNamed:(NSString*)name {
  NSString *newPath = [self saveToPathForFileNamed:name 
                                         extension:kGTMStateFileExtension];
  return [self saveToStateAt:newPath];
}

//  Save encoded unit test state of |self| to a .gtmUTState file at path |path|.
//
//  Args:
//    name: The name for the state file you would like saved.
//
//  Returns:
//    YES if the file was successfully saved.
//
- (BOOL)saveToStateAt:(NSString*)path {
  if (!path) return NO;
  NSDictionary *dictionary = [self stateRepresentation];
  return [dictionary writeToFile:path atomically:YES];
}

//  Compares encoded unit test state of |self| to the .gtmUTState named |name|
//
//  Args:
//    name: the name of the state file you want to compare against.
//
//  Returns:
//    YES if they are equal, NO is they are not
//
- (BOOL)compareWithStateNamed:(NSString*)name {
  NSString *path = [self pathForStateNamed:name];
  return [self compareWithStateAt:path];

}

//  Compares encoded unit test state of |self| to the .gtmUTState located at
//  |path|
//
//  Args:
//    path: the path to the state file you want to compare against.
//
//  Returns:
//    YES if they are equal, NO is they are not
//
- (BOOL)compareWithStateAt:(NSString*)path {
  NSDictionary *masterDict = [NSDictionary dictionaryWithContentsOfFile:path];
  NSAssert1(masterDict, @"Unable to create dictionary from %@", path);
  NSDictionary *selfDict = [self stateRepresentation];
  return [selfDict isEqualTo: masterDict];
}

//  Find the path for a state by name in your bundle.
//  Do not include the ".gtmUTState" extension.
//
//  Args:
//    name: The name for the state file you would like to find.
//
//  Returns:
//    the path if the state exists in your bundle
//    or nil if no state to be found
//
- (NSString *)pathForStateNamed:(NSString*)name {
  return [self pathForFileNamed:name extension:kGTMStateFileExtension];
}

//  Gives us the encoded unit test state |self|
//
//  Returns:
//    the encoded state if successful
//    nil if failed
//
- (NSDictionary *)stateRepresentation {
  NSDictionary *dictionary = nil;
  if ([self conformsToProtocol:@protocol(GTMUnitTestingEncoding)]) {
    id<GTMUnitTestingEncoding> encoder = (id<GTMUnitTestingEncoding>)self;
    GTMUnitTestingKeyedCoder *archiver = [[[GTMUnitTestingKeyedCoder alloc] init] autorelease];
    [encoder unitTestEncodeState:archiver];
    dictionary = [archiver dictionary];
  }
  return dictionary;
}

//  Encodes the state of an object in a manner suitable for comparing
//  against a master state file so we can determine whether the
//  object is in a suitable state. Encode data in the coder in the same
//  manner that you would encode data in any other Keyed NSCoder subclass.
//
//  Arguments:
//    inCoder - the coder to encode our state into
- (void)unitTestEncodeState:(NSCoder*)inCoder {
  // Currently does nothing, but all impls of unitTestEncodeState
  // should be calling [super unitTestEncodeState] as their first action.
}

- (NSMutableArray*)unitTestExposedBindingsToIgnore {
  NSMutableArray *array;
  if ([[self exposedBindings] containsObject:NSFontBinding]) {
    array = [NSMutableArray arrayWithObjects:
      NSFontBoldBinding, NSFontFamilyNameBinding, NSFontItalicBinding, 
      NSFontNameBinding, NSFontSizeBinding, nil];
  } else {
    array = [NSMutableArray array];
  }
  return array;
}

- (NSMutableDictionary*)unitTestExposedBindingsTestValues:(NSString*)binding {
  // Always test identity
  id value = [self valueForKey:binding];
  if (!value) {
    value = [NSNull null];
  }
  NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:value
                                                                 forKey:value];
  
  // Now some default test values for a variety of bindings to make
  // sure that we cover all the bases and save other people writing lots of
  // duplicate test code.
  
  // If anybody can think of more to add, please go nuts.
  if ([binding isEqualToString:NSAlignmentBinding]) {
    [dict setObjectAndKey:[NSNumber numberWithInt:NSLeftTextAlignment]];
    [dict setObjectAndKey:[NSNumber numberWithInt:NSRightTextAlignment]];
    [dict setObjectAndKey:[NSNumber numberWithInt:NSCenterTextAlignment]];
    [dict setObjectAndKey:[NSNumber numberWithInt:NSJustifiedTextAlignment]];
    NSNumber *natural = [NSNumber numberWithInt:NSNaturalTextAlignment];
    [dict setObjectAndKey:natural];
    [dict setObject:natural forKey:[NSNumber numberWithInt:500]];
    [dict setObject:natural forKey:[NSNumber numberWithInt:-1]];
  } else if ([binding isEqualToString:NSAlternateImageBinding] || 
             [binding isEqualToString:NSImageBinding] || 
             [binding isEqualToString:NSMixedStateImageBinding] || 
             [binding isEqualToString:NSOffStateImageBinding] ||
             [binding isEqualToString:NSOnStateImageBinding]) {
    // This handles all image bindings
    [dict setObjectAndKey:[NSImage imageNamed:@"NSApplicationIcon"]];
    [dict setObjectAndKey:[NSNull null]];
  } else if ([binding isEqualToString:NSAnimateBinding] || 
             [binding isEqualToString:NSDocumentEditedBinding] ||
             [binding isEqualToString:NSEditableBinding] ||
             [binding isEqualToString:NSEnabledBinding] ||
             [binding isEqualToString:NSHiddenBinding] ||
             [binding isEqualToString:NSVisibleBinding]) {
    // This handles all bool value bindings
    [dict setObjectAndKey:[NSNumber numberWithBool:YES]];
    [dict setObjectAndKey:[NSNumber numberWithBool:NO]];
  } else if ([binding isEqualToString:NSAlternateTitleBinding] ||
             [binding isEqualToString:NSHeaderTitleBinding] ||
             [binding isEqualToString:NSLabelBinding] ||
             [binding isEqualToString:NSRepresentedFilenameBinding] ||
             [binding isEqualToString:NSTitleBinding] ||
             [binding isEqualToString:NSToolTipBinding]) {
    // This handles all string value bindings
    [dict setObjectAndKey:@"happy"];
    [dict setObjectAndKey:[NSNull null]];
    // Test some non-ascii roman text
    char a_not_alpha[] = { 'A', 0xE2, 0x89, 0xA2, 0xCE, 0x91, '.', 0x00 };
    [dict setObjectAndKey:[NSString stringWithUTF8String:a_not_alpha]];
    // Test some korean
    char hangugo[] 
      = { 0xED, 0x95, 0x9C, 0xEA, 0xB5, 0xAD, 0xEC, 0x96, 0xB4, 0x00 };    
    [dict setObjectAndKey:[NSString stringWithUTF8String:hangugo]];
    // Test some japanese
    char nihongo[] 
      = { 0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC, 0xE8, 0xAA, 0x9E, 0x00 };
    [dict setObjectAndKey:[NSString stringWithUTF8String:nihongo]];
    // Test some arabic (right to left baby! ;-)
    char arabic[] = { 0xd9, 0x83, 0xd8, 0xa7, 0xd9, 0x83, 0xd8, 0xa7, 0x00 };
    [dict setObjectAndKey:[NSString stringWithUTF8String:arabic]];
  } else if ([binding isEqualToString:NSMaximumRecentsBinding] ||
              [binding isEqualToString:NSMaxValueBinding] ||
              [binding isEqualToString:NSMaxWidthBinding] ||
              [binding isEqualToString:NSMinValueBinding] ||
              [binding isEqualToString:NSMinWidthBinding] ||
              [binding isEqualToString:NSRecentSearchesBinding] || 
              [binding isEqualToString:NSRowHeightBinding] ||
              [binding isEqualToString:NSWidthBinding]) {
    // This handles all int value bindings
    [dict setObjectAndKey:[NSNumber numberWithInt:0]];
    [dict setObjectAndKey:[NSNumber numberWithInt:-1]];
    [dict setObjectAndKey:[NSNumber numberWithInt:INT16_MAX]];
    [dict setObjectAndKey:[NSNumber numberWithInt:INT16_MIN]];
  } else if ([binding isEqualToString:NSTextColorBinding]) {
    // This handles all color value bindings
    [dict setObjectAndKey:[NSColor colorWithDeviceWhite:1.0 alpha:1.0]];
    [dict setObjectAndKey:[NSColor colorWithDeviceWhite:1.0 alpha:0.0]];
    [dict setObjectAndKey:[NSColor colorWithDeviceWhite:1.0 alpha:0.5]];
    [dict setObjectAndKey:[NSColor colorWithCalibratedRed:0.5 green:0.5 blue:0.5 alpha:0.5]];
    [dict setObjectAndKey:[NSColor colorWithDeviceCyan:0.25 magenta:0.25 yellow:0.25 black:0.25 alpha:0.25]];
  } else if ([binding isEqualToString:NSFontBinding]) {
    // This handles all font value bindings
    [dict setObjectAndKey:[NSFont boldSystemFontOfSize:[NSFont systemFontSize]]];
    [dict setObjectAndKey:[NSFont toolTipsFontOfSize:[NSFont smallSystemFontSize]]];
    [dict setObjectAndKey:[NSFont labelFontOfSize:144.0]];
  }
  return dict;
}

@end

@implementation NSMutableDictionary (GTMUnitTestingAdditions)
// Sets an object and a key to the same value in a dictionary.
- (void)setObjectAndKey:(id)objectAndKey {
  [self setObject:objectAndKey forKey:objectAndKey];
}
@end


@implementation NSObject (GTMUnitTestingPulseAdditions)

- (BOOL)isRunningUnderPulse {
  
  if ([[[NSProcessInfo processInfo] environment] objectForKey:@"PULSE_BUILD_NUMBER"]) return YES;
  return NO;
  
}

- (NSString *)pulseBaseDirectory {
  
  return [[[NSProcessInfo processInfo] environment] objectForKey:@"PULSE_BASE_DIR"];
  
}

@end