diff options
author | thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2008-01-28 20:19:42 +0000 |
---|---|---|
committer | thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2008-01-28 20:19:42 +0000 |
commit | 2a5219567634ab7ab74314ff3615132becadff4a (patch) | |
tree | 8e6f447544e5eaf460da741bf57771f929b4a70c /UnitTesting |
initial drop of a few sources to start things out
Diffstat (limited to 'UnitTesting')
-rw-r--r-- | UnitTesting/GTMNSObject+UnitTesting.h | 534 | ||||
-rw-r--r-- | UnitTesting/GTMNSObject+UnitTesting.m | 730 | ||||
-rw-r--r-- | UnitTesting/GTMNSView+UnitTesting.h | 138 | ||||
-rw-r--r-- | UnitTesting/GTMNSView+UnitTesting.m | 135 | ||||
-rw-r--r-- | UnitTesting/GTMSenTestCase.h | 429 |
5 files changed, 1966 insertions, 0 deletions
diff --git a/UnitTesting/GTMNSObject+UnitTesting.h b/UnitTesting/GTMNSObject+UnitTesting.h new file mode 100644 index 0000000..73a2c9a --- /dev/null +++ b/UnitTesting/GTMNSObject+UnitTesting.h @@ -0,0 +1,534 @@ +// +// GTMNSObject+UnitTesting.h +// +// Utilities 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 <Cocoa/Cocoa.h> + +/// Fails when image of |a1| does not equal image in TIFF file named |a2| +// +// Generates a failure when the unittest image of |a1| is not equal to the +// image stored in the TIFF file named |a2|, or |a2| does not exist in the +// executable code's bundle. +// If |a2| does not exist in the executable code's bundle, we save a TIFF +// representation of |a1| on the desktop with name |a2|. This can then be +// included in the bundle as the master to test against. +// If |a2| != |a1|, we save a TIFF representation of |a1| on the desktop +// with name |a2|_Failed so that we can compare the two files to see what +// has changed. +// See pathForTIFFNamed to see how name is searched for. +// Implemented as a macro to match the rest of the SenTest macros. +// +// Args: +// a1: The object to be checked. Must implement the -unitTestImage method. +// a2: The name of the TIFF file to check against. +// Do not include the extension +// description: A format string as in the printf() function. +// Can be nil or an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +// +#define GTMAssertObjectImageEqualToTIFFNamed(a1, a2, description, ...) \ +do { \ + NSObject* a1Object = (a1); \ + NSString* a2String = (a2); \ + NSString *failString = nil; \ + BOOL isGood = [a1Object respondsToSelector:@selector(unitTestImage)]; \ + if (isGood) { \ + if (![a1Object areSystemSettingsValidForDoingImage]) { \ + break; \ + } \ + NSString *aPath = [a1Object pathForTIFFNamed:a2String]; \ + isGood = aPath != nil; \ + if (isGood) { \ + isGood = [a1Object compareWithTIFFAt:aPath]; \ + } \ + if (!isGood) { \ + if (aPath != nil) { \ + a2String = [a2String stringByAppendingString:@"_Failed"]; \ + } \ + BOOL aSaved = [a1Object saveToTIFFNamed:a2String]; \ + if (NO == aSaved) {\ + if (aPath == nil) { \ + failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. Tried to save %@ to desktop and failed.", a2String, a2String]; \ + } else { \ + failString = [NSString stringWithFormat:@"Object image different than file %@. Tried to save to desktop as %@ and failed.", aPath, a2String]; \ + } \ + } else { \ + if (aPath == nil) { \ + failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. Saved to ~/Desktop/%@", a2String, a2String]; \ + } else { \ + failString = [NSString stringWithFormat:@"Object image different than file %@. Saved image to desktop as %@.", aPath, a2String]; \ + } \ + } \ + } \ + } else { \ + failString = @"Object does not respond to -unitTestImage"; \ + } \ + if (!isGood) { \ + if (nil != description) { \ + STFail(@"%@: %@", STComposeString(description, ##__VA_ARGS__), failString); \ + } else { \ + STFail(@"%@", failString); \ + } \ + } \ +} while(0) + +/// Fails when state of |a1| does not equal state in file |a2| +// +// Generates a failure when the unittest state of |a1| is not equal to the +// state stored in the state file named |a2|, or |a2| does not exist in the +// executable code's bundle. +// If |a2| does not exist in the executable code's bundle, we save a state +// representation of |a1| on the desktop with name |a2|. This can then be +// included in the bundle as the master to test against. +// If |a2| != |a1|, we save a state representation of |a1| on the desktop +// with name |a2|_Failed so that we can compare the two files to see what +// has changed. +// Implemented as a macro to match the rest of the SenTest macros. +// +// Args: +// a1: The object to be checked. Must implement the -unitTestImage method. +// a2: The name of the state file to check against. +// Do not include the extension +// description: A format string as in the printf() function. +// Can be nil or an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +// +#define GTMAssertObjectStateEqualToStateNamed(a1, a2, description, ...) \ +do { \ + NSObject* a1Object = (a1); \ + NSString* a2String = (a2); \ + NSString *failString = nil; \ + BOOL isGood = [a1Object respondsToSelector:@selector(unitTestEncodeState:)]; \ + if (isGood) { \ + NSString *aPath = [a1Object pathForStateNamed:a2String]; \ + isGood = aPath != nil; \ + if (isGood) { \ + isGood = [a1Object compareWithStateAt:aPath]; \ + } \ + if (!isGood) { \ + if (aPath != nil) { \ + a2String = [a2String stringByAppendingString:@"_Failed"]; \ + } \ + BOOL aSaved = [a1Object saveToStateNamed:a2String]; \ + if (NO == aSaved) {\ + if (aPath == nil) { \ + failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. Tried to save %@ to desktop and failed.", a2String, a2String]; \ + } else { \ + failString = [NSString stringWithFormat:@"Object state different than file %@. Tried to save to desktop as %@ and failed.", aPath, a2String]; \ + } \ + } else { \ + if (aPath == nil) { \ + failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. Saved to ~/Desktop/%@", a2String, a2String]; \ + } else { \ + failString = [NSString stringWithFormat:@"Object state different than file %@. Saved image to desktop as %@.", aPath, a2String]; \ + } \ + } \ + } \ + } else { \ + failString = @"Object does not respond to -unitTestEncodeState:"; \ + } \ + if (!isGood) { \ + if (nil != description) { \ + STFail(@"%@: %@", STComposeString(description, ##__VA_ARGS__), failString); \ + } else { \ + STFail(@"%@", failString); \ + } \ + } \ +} while(0) + +/// test both GTMAssertObjectImageEqualToTIFFNamed and GTMAssertObjectStateEqualToStateNamed +// +// Combines the above two macros into a single ubermacro for comparing +// both state and image. When only the best will do... +#define GTMAssertObjectEqualToStateAndImageNamed(a1, a2, description, ...) \ +do { \ + GTMAssertObjectImageEqualToTIFFNamed(a1, a2, description, ##__VA_ARGS__); \ + GTMAssertObjectStateEqualToStateNamed(a1, a2, description, ##__VA_ARGS__); \ +} while (0) + +/// Tests the setters and getters for exposed bindings +// For objects that expose bindings, this tests them for you, saving you from +// having to write a whole pile of set/get test code if you add binding support. +// You will need to implement valueClassForBinding: for your bindings, +// and you may possibly want to implement unitTestExposedBindingsToIgnore +// and unitTestExposedBindingsTestValues. See descriptions of those +// methods below for details. +// Implemented as a macro to match the rest of the SenTest macros. +// +// Args: +// a1: The object to be checked. +// description: A format string as in the printf() function. +// Can be nil or an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +// +#define GTMTestExposedBindings(a1, description, ...) \ +do { \ + NSArray *bindings = [a1 exposedBindings]; \ + if (bindings) { \ + NSArray *bindingsToIgnore = [a1 unitTestExposedBindingsToIgnore]; \ + NSEnumerator *bindingsEnum = [bindings objectEnumerator]; \ + NSString *bindingKey; \ + while ((bindingKey = [bindingsEnum nextObject])) { \ + if (![bindingsToIgnore containsObject:bindingKey]) { \ + Class theClass = [a1 valueClassForBinding:bindingKey]; \ + STAssertNotNil(theClass, @"Should have valueClassForBinding %@", bindingKey); \ + NSDictionary *testValues = [a1 unitTestExposedBindingsTestValues:bindingKey]; \ + NSEnumerator *testEnum = [testValues keyEnumerator]; \ + id testValue; \ + while ((testValue = [testEnum nextObject])) { \ + [a1 setValue:testValue forKey:bindingKey]; \ + id value = [a1 valueForKey:bindingKey]; \ + STAssertEqualObjects([testValues objectForKey:testValue], value, description, ##__VA_ARGS__); \ + } \ + } \ + } \ + } \ +} while(0) + +/// \cond Protocols + +// GTMUnitTestingEncoding protocol is for objects which need to save their +// "state" for using with the unit testing categories +@protocol GTMUnitTestingEncoding +// 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; +@end + +/// Category for saving and comparing object state and image for unit tests +// +// The GTMUnitTestAdditions category gives object the ability to store their +// state for use in unittesting in two different manners. +// 1) Objects can elect to save their "image" as a TIFF that we can compare at +// runtime to a TIFF on file to make sure that the representation hasn't +// changed. All views and Windows can save their image. In the case of Windows, +// they are "bluescreened" so that any transparent areas can be compared between +// machines. For this to work, the appearance must be set to "Aqua blue" In the +// case of NSWindows and NSScreens, we do a screen capture operation to capture +// their image. In these cases, font smoothing settings must be set consistently +// across machines. The current standard is +// Font Smoothing Style: Standard - Best For CRT +// Turn Off Text Smoothing For Font Sizes: 8 And Smaller +// If you do not have these settings, any unit tests depending on them will not +// be executed, and a warning will be logged. +// Also, we need to be careful about avoiding ColorSync. In most cases the +// unittesting system handles this for you. If you are running into troubles +// make sure that you are using device colors, and not calibrated colors +// wherever you are doing drawing. +// 2) Objects can elect to save their "state". State is the attributes that we +// want to verify when running unit tests. Applications, Windows, Views, +// Controls and Cells currently return a variety of state information. If you +// want to customize the state information that a particular object returns, you +// can do it via the GTMUnitTestingEncodedObjectNotification. Items that have +// delegates (Applications/Windows) can also have their delegates return state +// information if appropriate via the unitTestEncoderDidEncode:inCoder: delegate +// method. +// To compare state/image in your unit tests, you can use the three macros above +// GTMAssertObjectStateEqualToStateNamed, GTMAssertObjectImageEqualToTIFFNamed and +// GTMAssertObjectEqualToStateAndImageNamed. +@interface NSObject (GTMUnitTestingAdditions) <GTMUnitTestingEncoding> +/// Returns an image containing a representation 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; + +/// 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. +// Instead of directly overriding this, a unit test can just use: +// needsAquaBlueAppearanceForDoingImage +// needsScrollBarArrowsLowerRightForDoingImage +// to enable those tests w/in this base implementation. +// The other issues are for NSScreen and NSWindow images as they are affected by +// the font smoothing settings in the system preferences. For things to work +// these settings must be set to: +// Font Smoothing Style: Standard - Best For CRT +// Turn Off Text Smoothing For Font Sizes: 8 And Smaller +// +// Returns: +// YES if we can do image comparisons for this object type. +- (BOOL)areSystemSettingsValidForDoingImage; + +/// Checks if this test needs the AquaBlue Appearance for doing the image comparison. +// If the test uses the appearance colors, this should be overriden to return +// YES (ie-default is no). This provides a hook so the unittest can be skipped +// if the running user's settings aren't the "standard" for the UI unitttests. +// +// Returns: +// YES if this test needs the AquaBlue Appearance. +- (BOOL)needsAquaBlueAppearanceForDoingImage; + +/// Checks if this test needs the ScrollBarArrows LowerRight for doing the image comparison. +// If the test uses the scrollbar drawing, this should be overriden to return +// YES (ie-default is no). This provides a hook so the unittest can be skipped +// if the running user's settings aren't the "standard" for the UI unitttests. +// +// Returns: +// YES if this test needs the ScrollBarArrows LowerRight. +- (BOOL)needsScrollBarArrowsLowerRightForDoingImage; + +/// Save the unitTestImage to a TIFF file with name |name| at ~/Desktop/|name|.tif. +// 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)saveToTIFFNamed:(NSString*)name; + +/// Save unitTestImage of |self| to a TIFF file at path |path|. +// The TIFF will be compressed with LZW. All non-drawn areas will be transparent. +// +// Args: +// name: The name for the TIFF file you would like saved. +// +// Returns: +// YES if the file was successfully saved. +// +- (BOOL)saveToTIFFAt:(NSString*)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)compareWithTIFFNamed:(NSString*)name; + +/// 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; + +/// Find the path for a TIFF by name in your bundle. +// Searches for the following: +// "name.tif", +// "name.arch.tif", +// "name.arch.OSVersionMajor.tif" +// "name.arch.OSVersionMajor.OSVersionMinor.tif" +// "name.arch.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.tif" +// "name.arch.OSVersionMajor.tif" +// "name.OSVersionMajor.arch.tif" +// "name.OSVersionMajor.OSVersionMinor.arch.tif" +// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.arch.tif" +// "name.OSVersionMajor.tif" +// "name.OSVersionMajor.OSVersionMinor.tif" +// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.tif" +// 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; + + +/// Gives us a LZW compressed representation of unitTestImage of |self|. +// +// Returns: +// a LZW compressed TIFF if successful +// nil if failed +// +- (NSData *)TIFFRepresentation; + + +/// Save the encoded unit test state to a .gtmUTState file with name |name| at ~/Desktop/|name|.gtmUTState. +// +// Args: +// name: The name for the state file you would like saved. +// +// Returns: +// YES if the file was successfully saved. +// +- (BOOL)saveToStateNamed:(NSString*)name; + +/// 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; + +/// 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; + +/// 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; + +/// Find the path for a state by name in your bundle. +// Searches for: +// "name.gtmUTState", +// "name.arch.gtmUTState", +// "name.arch.OSVersionMajor.gtmUTState" +// "name.arch.OSVersionMajor.OSVersionMinor.gtmUTState" +// "name.arch.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.gtmUTState" +// "name.arch.OSVersionMajor.gtmUTState" +// "name.OSVersionMajor.arch.gtmUTState" +// "name.OSVersionMajor.OSVersionMinor.arch.gtmUTState" +// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.arch.gtmUTState" +// "name.OSVersionMajor.gtmUTState" +// "name.OSVersionMajor.OSVersionMinor.gtmUTState" +// "name.OSVersionMajor.OSVersionMinor.OSVersion.bugfix.gtmUTState" +// Do not include the ".gtmUTState" extension on your name. +// +// 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; + + +/// Gives us the encoded unit test state for |self| +// +// Returns: +// the encoded state if successful +// nil if failed +// +- (NSDictionary *)stateRepresentation; + + +/// Encodes the state of an object +// 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; + +/// Allows you to ignore certain bindings when running GTMTestExposedBindings +// If you have bindings you want to ignore, add them to the array returned +// by this method. The standard way to implement this would be: +// - (NSMutableArray*)unitTestExposedBindingsToIgnore { +// NSMutableArray *array = [super unitTestExposedBindingsToIgnore]; +// [array addObject:@"bindingToIgnore1"]; +// ... +// return array; +// } +// The NSObject implementation by default will ignore NSFontBoldBinding, +// NSFontFamilyNameBinding, NSFontItalicBinding, NSFontNameBinding and +// NSFontSizeBinding if your exposed bindings contains NSFontBinding because +// the NSFont*Bindings are NOT KVC/KVO compliant, and they just happen to work +// through what can only be described as magic :) +- (NSMutableArray*)unitTestExposedBindingsToIgnore; + +/// Allows you to set up test values for your different bindings. +// if you have certain values you want to test against your bindings, add +// them to the dictionary returned by this method. The dictionary is a "value" key +// and an "expected return" object. +// The standard way to implement this would be: +// - (NSMutableDictionary*)unitTestExposedBindingsTestValues:(NSString*)binding { +// NSMutableDictionary *dict = [super unitTestExposedBindingsTestValues:binding]; +// if ([binding isEqualToString:@"myBinding"]) { +// [dict setObject:[[[MySpecialBindingValueSet alloc] init] autorelease] +// forKey:[[[MySpecialBindingValueGet alloc] init] autorelease]]; +// [dict setObjectAndKey:[[[MySpecialBindingValue alloc] init] autorelease]]; +// ... +// else if ([binding isEqualToString:@"myBinding2"]) { +// ... +// } +// return array; +// } +// The NSObject implementation handles many of the default bindings, and +// gives you a reasonable set of test values to start. +// See the implementation for the current list of bindings, and values that we +// set for those bindings. +- (NSMutableDictionary*)unitTestExposedBindingsTestValues:(NSString*)binding; +@end + +// Utility for simplifying unitTestExposedBindingsTestValues implementations +@interface NSMutableDictionary (GTMUnitTestingAdditions) +// Sets an object and a key to the same value in a dictionary. +- (void)setObjectAndKey:(id)objectAndKey; +@end + +// Informal protocol for delegates that wanst to be able to add state info +// when state info is collected for their "owned" objects +@interface NSObject (GTMUnitTestingEncodingAdditions) +// Delegate function for unit test objects that have delegates. Delegates have +// the option of encoding more data into the coder to store their state for +// unittest usage. +- (void)unitTestEncoderDidEncode:(id)sender inCoder:(NSCoder*)inCoder; +@end + +/// \endcond + +// Whenever an object is encoded by the unit test encoder, it send out a +// notification so that objects who want to add data to the encoded objects unit +// test state can do so. The Coder will be in the userInfo dictionary for the +// notification under the GTMUnitTestingEncoderKey key. +extern NSString *const GTMUnitTestingEncodedObjectNotification; + +// Key for finding the encoder in the userInfo dictionary for +// GTMUnitTestingEncodedObjectNotification notifications. +extern NSString *const GTMUnitTestingEncoderKey; + + +/// Support for Pulse automated builds +@interface NSObject (GTMUnitTestingPulseAdditions) + +// Determine if the current unittest is running under Pulse +- (BOOL)isRunningUnderPulse; + +// Get the current base directory for Pulse +- (NSString *)pulseBaseDirectory; + +@end diff --git a/UnitTesting/GTMNSObject+UnitTesting.m b/UnitTesting/GTMNSObject+UnitTesting.m new file mode 100644 index 0000000..5be6b9f --- /dev/null +++ b/UnitTesting/GTMNSObject+UnitTesting.m @@ -0,0 +1,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 + + diff --git a/UnitTesting/GTMNSView+UnitTesting.h b/UnitTesting/GTMNSView+UnitTesting.h new file mode 100644 index 0000000..fcda16b --- /dev/null +++ b/UnitTesting/GTMNSView+UnitTesting.h @@ -0,0 +1,138 @@ +// +// GTMNSView+UnitTesting.h +// +// Code for making unit testing of graphics/UI easier. Generally you +// will only want to look at the macros: +// GTMAssertDrawingEqualToFile +// GTMAssertViewRepEqualToFile +// and the protocol GTMUnitTestViewDrawer. When using these routines +// make sure you are using device colors and not calibrated/generic colors +// or else your test graphics WILL NOT match across devices/graphics cards. +// +// 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 <Cocoa/Cocoa.h> +#import "GTMNSObject+UnitTesting.h" + +@protocol GTMUnitTestViewDrawer; + +/// Fails when the |a1|'s drawing in an area |a2| does not equal the TIFF file named |a3|. +// See the description of the GTMAssertViewRepEqualToFile macro +// to understand how |a3| is found and written out. +// See the description of the GTMUnitTestView for a better idea +// how the view works. +// Implemented as a macro to match the rest of the SenTest macros. +// +// Args: +// a1: The object that implements the GTMUnitTestViewDrawer protocol +// that is doing the drawing. +// a2: The size of the drawing +// a3: The name of the TIFF file to check against. +// Do not include the extension +// a4: contextInfo to pass to drawer +// description: A format string as in the printf() function. +// Can be nil or an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +// + + +#define GTMAssertDrawingEqualToFile(a1, a2, a3, a4, description, ...) \ + do { \ + id<GTMUnitTestViewDrawer> a1Object = (a1); \ + NSSize a2Size = (a2); \ + NSString* a3String = (a3); \ + void *a4ContextInfo = (a4); \ + NSRect frame = NSMakeRect(0, 0, a2Size.width, a2Size.height); \ + GTMUnitTestView *view = [[[GTMUnitTestView alloc] initWithFrame:frame drawer:a1Object contextInfo:a4ContextInfo] autorelease]; \ + GTMAssertObjectImageEqualToTIFFNamed(view, a3String, STComposeString(description, ##__VA_ARGS__)); \ + } while(0) + +// Category for making unit testing of graphics/UI easier. + +/// Allows you to take a state of a view. Supports both image and state. +// See NSObject+UnitTesting.h for details. +@interface NSView (GTMUnitTestingAdditions) <GTMUnitTestingEncoding> + +/// Returns an image containing a representation suitable for use in comparing against a master image. +// +// NB this means that all colors should be device based. +// +// Returns: +// an image of the object +- (NSImage*)unitTestImage; + +/// Encodes the state of an object in a manner suitable for comparing against a master state file +// This enables us to determine whether the object is in a suitable state. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)unitTestEncodeState:(NSCoder*)inCoder; + +/// Returns whether unitTestEncodeState should recurse into subviews +// +// Dan Waylonis discovered that if you have "Full keyboard access" in the +// Keyboard & Mouse > Keyboard Shortcuts preferences pane set to "Text boxes +// and Lists only" that Apple adds a set of subviews to NSTextFields. So in the +// case of NSTextFields we don't want to recurse into their subviews. There may +// be other cases like this, so instead of specializing unitTestEncodeState: to +// look for NSTextFields, NSTextFields will just not allow us to recurse into +// their subviews. +// +// Returns: +// should unitTestEncodeState pick up subview state. +- (BOOL)shouldEncodeStateRecurseIntoSubviews; + +@end + +/// A view that allows you to delegate out drawing using the formal GTMUnitTestViewDelegate protocol +// This is useful when writing up unit tests for visual elements. +// Your test will often end up looking like this: +// - (void)testFoo { +// GTMAssertDrawingEqualToFile(self, NSMakeSize(200, 200), @"Foo", nil, nil); +// } +// and your testSuite will also implement the unitTestViewDrawRect method to do +// it's actual drawing. The above creates a view of size 200x200 that draws +// it's content using |self|'s unitTestViewDrawRect method and compares it to +// the contents of the file Foo.tif to make sure it's valid +@interface GTMUnitTestView : NSView { + id<GTMUnitTestViewDrawer> drawer_; // delegate for doing drawing (STRONG) + void* contextInfo_; // info passed in by user for them to use when drawing +} + +// Create a GTMUnitTestView. +// +// Args: +// rect: the area to draw. +// drawer: the object that will do the drawing via the GTMUnitTestViewDrawer +// protocol +// contextInfo: +- (id)initWithFrame:(NSRect)frame drawer:(id<GTMUnitTestViewDrawer>)drawer contextInfo:(void*)contextInfo; +@end + +/// \cond Protocols + +// Formal protocol for doing unit testing of views. See description of +// GTMUnitTestView for details. +@protocol GTMUnitTestViewDrawer <NSObject> + +// Draw the view. Equivalent to drawRect on a standard NSView. +// +// Args: +// rect: the area to draw. +- (void)unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo; +@end + +/// \endcond diff --git a/UnitTesting/GTMNSView+UnitTesting.m b/UnitTesting/GTMNSView+UnitTesting.m new file mode 100644 index 0000000..dd3e423 --- /dev/null +++ b/UnitTesting/GTMNSView+UnitTesting.m @@ -0,0 +1,135 @@ +// +// GTMNSView+UnitTesting.m +// +// Category for making unit testing of graphics/UI easier. +// Allows you to save a view out to a TIFF file, and compare a view +// with a previously stored representation to make sure it hasn't changed. +// +// 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 "GTMNSView+UnitTesting.h" +#import <SenTestingKit/SenTestingKit.h> +#import "GTMGeometryUtils.h" + +// A view that allows you to delegate out drawing using the formal +// GTMUnitTestViewDelegate protocol above. This is useful when writing up unit +// tests for visual elements. +// Your test will often end up looking like this: +// - (void)testFoo { +// GTMAssertDrawingEqualToFile(self, NSMakeSize(200, 200), @"Foo", nil, nil); +// } +// and your testSuite will also implement the unitTestViewDrawRect method to do +// it's actual drawing. The above creates a view of size 200x200 that draws +// it's content using |self|'s unitTestViewDrawRect method and compares it to +// the contents of the file Foo.tif to make sure it's valid +@implementation GTMUnitTestView + +- (id)initWithFrame:(NSRect)frame drawer:(id<GTMUnitTestViewDrawer>)drawer contextInfo:(void*)contextInfo{ + self = [super initWithFrame:frame]; + if (self != nil) { + drawer_ = [drawer retain]; + contextInfo_ = contextInfo; + } + return self; +} + +- (void) dealloc { + [drawer_ release]; + [super dealloc]; +} + + +- (void)drawRect:(NSRect)rect { + [drawer_ unitTestViewDrawRect:rect contextInfo:contextInfo_]; +} + + +@end + +@implementation NSView (GTMUnitTestingAdditions) + +// 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 from "NSDevice" color space +// Does all of it's drawing with smoothfonts and antialiasing off +// to avoid issues with font smoothing settings and antialias differences +// between ppc and x86. +// +// Returns: +// an image of the object +- (NSImage*)unitTestImage { + // Create up a context + NSBitmapImageRep *imageRep = [self bitmapImageRepForCachingDisplayInRect:[self bounds]]; + NSGraphicsContext *bitmapContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:imageRep]; + + // Store Current Context and switch to bitmap context + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext: bitmapContext]; + CGContextRef contextRef = (CGContextRef)[bitmapContext graphicsPort]; + + // Save our state and turn off font smoothing and antialias. + CGContextSaveGState(contextRef); + CGContextSetShouldSmoothFonts(contextRef, false); + CGContextSetShouldAntialias(contextRef, false); + CGContextClearRect(contextRef, GTMNSRectToCGRect([self bounds])); + [self displayRectIgnoringOpacity:[self bounds] inContext:bitmapContext]; + + // Clean up and create image + CGContextRestoreGState(contextRef); + [NSGraphicsContext restoreGraphicsState]; + NSImage *image = [[[NSImage alloc] init] autorelease]; + [image addRepresentation:imageRep]; + return image; +} + +// Returns whether unitTestEncodeState should recurse into subviews +// of a particular view. +// Dan Waylonis discovered that if you have "Full keyboard access" in the +// Keyboard & Mouse > Keyboard Shortcuts preferences pane set to "Text boxes +// and Lists only" that Apple adds a set of subviews to NSTextFields. So in the +// case of NSTextFields we don't want to recurse into their subviews. There may +// be other cases like this, so instead of specializing unitTestEncodeState: to +// look for NSTextFields, NSTextFields will just not allow us to recurse into +// their subviews. +// +// Returns: +// should unitTestEncodeState pick up subview state. +- (BOOL)shouldEncodeStateRecurseIntoSubviews { + return YES; +} + +// 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. +// +// Arguments: +// inCoder - the coder to encode our state into +- (void)unitTestEncodeState:(NSCoder*)inCoder { + [super unitTestEncodeState:inCoder]; + [inCoder encodeBool:[self isHidden] forKey:@"ViewIsHidden"]; + if ([self shouldEncodeStateRecurseIntoSubviews]) { + NSEnumerator *subviewEnum = [[self subviews] objectEnumerator]; + NSView *subview = nil; + int i = 0; + while ((subview = [subviewEnum nextObject])) { + [inCoder encodeObject:subview forKey:[NSString stringWithFormat:@"ViewSubView %d", i]]; + i = i + 1; + } + } +} + +@end + diff --git a/UnitTesting/GTMSenTestCase.h b/UnitTesting/GTMSenTestCase.h new file mode 100644 index 0000000..75adc13 --- /dev/null +++ b/UnitTesting/GTMSenTestCase.h @@ -0,0 +1,429 @@ +// +// GTMSenTestCase.h +// +// Copyright 2007-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. +// + +// Some extra test case macros that would have been convenient for SenTestingKit +// to provide. I didn't stick GTM in front of the Macro names, so that they would +// be easy to remember. + +#import <SenTestingKit/SenTestingKit.h> + +// Generates a failure when a1 != noErr +// Args: +// a1: should be either an OSErr or an OSStatus +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNoErr(a1, description, ...) \ +do { \ + @try {\ + OSStatus a1value = (a1); \ + if (a1value != noErr) { \ + NSString *_expression = [NSString stringWithFormat:@"Expected noErr, got %ld for (%s)", a1value, #a1]; \ + if (description) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + } \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == noErr fails", #a1] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +// Generates a failure when a1 != a2 +// Args: +// a1: received value. Should be either an OSErr or an OSStatus +// a2: expected value. Should be either an OSErr or an OSStatus +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertErr(a1, a2, description, ...) \ +do { \ + @try {\ + OSStatus a1value = (a1); \ + OSStatus a2value = (a2); \ + if (a1value != a2value) { \ + NSString *_expression = [NSString stringWithFormat:@"Expected %s(%ld) but got %ld for (%s)", #a2, a2value, a1value, #a1]; \ + if (description) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + } \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == noErr fails", #a1] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + + +// Generates a failure when a1 is NULL +// Args: +// a1: should be a pointer (use STAssertNotNil for an object) +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNotNULL(a1, description, ...) \ +do { \ + @try {\ + char* a1value = (char*)(a1); \ + if (a1value == NULL) { \ + NSString *_expression = [NSString stringWithFormat:@"(%s) != NULL", #a1]; \ + if (description) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + } \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) != NULL fails", #a1] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +// Generates a failure when a1 is not NULL +// Args: +// a1: should be a pointer (use STAssertNil for an object) +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNULL(a1, description, ...) \ +do { \ + @try {\ + char* a1value = (char*)(a1); \ + if (a1value != NULL) { \ + NSString *_expression = [NSString stringWithFormat:@"(%s) == NULL", #a1]; \ + if (description) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + } \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == NULL fails", #a1] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +// Generates a failure when a1 is unequal to a2. This test is for C scalars, +// structs and unions. +// Args: +// a1: argument 1 +// a2: argument 2 +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNotEquals(a1, a2, description, ...) \ +do { \ + @try {\ + if (@encode(typeof(a1)) != @encode(typeof(a2))) { \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:[@"Type mismatch -- " stringByAppendingString:STComposeString(description, ##__VA_ARGS__)]]]; \ + } \ + else { \ + typeof(a1) a1value = (a1); \ + typeof(a2) a2value = (a2); \ + NSValue *a1encoded = [NSValue value:&a1value withObjCType:@encode(typeof(a1))]; \ + NSValue *a2encoded = [NSValue value:&a2value withObjCType:@encode(typeof(a2))]; \ + if ([a1encoded isEqualToValue:a2encoded]) { \ + NSString *_expression = [NSString stringWithFormat:@"(%s) != (%s)", #a1, #a2]; \ + if (description) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + } \ + } \ + } \ + @catch (id anException) {\ + [self failWithException:[NSException \ + failureInRaise:[NSString stringWithFormat:@"(%s) != (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +// Generates a failure when a1 is equal to a2. This test is for objects. +// Args: +// a1: argument 1. object. +// a2: argument 2. object. +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNotEqualObjects(a1, a2, desc, ...) \ +do { \ + @try {\ + id a1value = (a1); \ + id a2value = (a2); \ + if ( (@encode(typeof(a1value)) == @encode(id)) && \ + (@encode(typeof(a2value)) == @encode(id)) && \ + ![(id)a1value isEqual:(id)a2value] ) continue; \ + NSString *_expression = [NSString stringWithFormat:@"%s('%@') != %s('%@')", #a1, [a1 description], #a2, [a2 description]]; \ + if (desc) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(desc, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(desc, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +// Generates a failure when a1 is not 'op' to a2. This test is for C scalars. +// Args: +// a1: argument 1 +// a2: argument 2 +// op: operation +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertOperation(a1, a2, op, description, ...) \ +do { \ + @try {\ + if (@encode(typeof(a1)) != @encode(typeof(a2))) { \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:[@"Type mismatch -- " stringByAppendingString:STComposeString(description, ##__VA_ARGS__)]]]; \ + } \ + else { \ + typeof(a1) a1value = (a1); \ + typeof(a2) a2value = (a2); \ + if (!(a1value op a2value)) { \ + double a1DoubleValue = a1value; \ + double a2DoubleValue = a2value; \ + NSString *_expression = [NSString stringWithFormat:@"%s (%lg) %s %s (%lg)", #a1, a1DoubleValue, #op, #a2, a2DoubleValue]; \ + if (description) { \ + _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ + } \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:_expression]]; \ + } \ + } \ + } \ + @catch (id anException) {\ + [self failWithException:[NSException \ + failureInRaise:[NSString stringWithFormat:@"(%s) %s (%s)", #a1, #op, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ +} while(0) + +// Generates a failure when a1 is not > a2. This test is for C scalars. +// Args: +// a1: argument 1 +// a2: argument 2 +// op: operation +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertGreaterThan(a1, a2, description, ...) \ + STAssertOperation(a1, a2, >, description, ##__VA_ARGS__) + +// Generates a failure when a1 is not >= a2. This test is for C scalars. +// Args: +// a1: argument 1 +// a2: argument 2 +// op: operation +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertGreaterThanOrEqual(a1, a2, description, ...) \ + STAssertOperation(a1, a2, >=, description, ##__VA_ARGS__) + +// Generates a failure when a1 is not < a2. This test is for C scalars. +// Args: +// a1: argument 1 +// a2: argument 2 +// op: operation +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertLessThan(a1, a2, description, ...) \ + STAssertOperation(a1, a2, <, description, ##__VA_ARGS__) + +// Generates a failure when a1 is not <= a2. This test is for C scalars. +// Args: +// a1: argument 1 +// a2: argument 2 +// op: operation +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertLessThanOrEqual(a1, a2, description, ...) \ + STAssertOperation(a1, a2, <=, description, ##__VA_ARGS__) + +// Generates a failure when string a1 is not equal to string a2. This call +// differs from STAssertEqualObjects in that strings that are different in +// composition (precomposed vs decomposed) will compare equal if their final +// representation is equal. +// ex O + umlaut decomposed is the same as O + umlaut composed. +// Args: +// a1: string 1 +// a2: string 2 +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertEqualStrings(a1, a2, description, ...) \ +do { \ + @try {\ + id a1value = (a1); \ + id a2value = (a2); \ + if (a1value == a2value) continue; \ + if ([a1value isKindOfClass:[NSString class]] && \ + [a2value isKindOfClass:[NSString class]] && \ + [a1value compare:a2value options:0] == NSOrderedSame) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: a1value \ + andObject: a2value \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + } while(0) + +// Generates a failure when string a1 is equal to string a2. This call +// differs from STAssertEqualObjects in that strings that are different in +// composition (precomposed vs decomposed) will compare equal if their final +// representation is equal. +// ex O + umlaut decomposed is the same as O + umlaut composed. +// Args: +// a1: string 1 +// a2: string 2 +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNotEqualStrings(a1, a2, description, ...) \ +do { \ + @try {\ + id a1value = (a1); \ + id a2value = (a2); \ + if ([a1value isKindOfClass:[NSString class]] && \ + [a2value isKindOfClass:[NSString class]] && \ + [a1value compare:a2value options:0] != NSOrderedSame) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: a1value \ + andObject: a2value \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + } while(0) + +// Generates a failure when c-string a1 is not equal to c-string a2. +// Args: +// a1: string 1 +// a2: string 2 +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertEqualCStrings(a1, a2, description, ...) \ +do { \ + @try {\ + const char* a1value = (a1); \ + const char* a2value = (a2); \ + if (a1value == a2value) continue; \ + if (strcmp(a1value, a2value) == 0) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: [NSString stringWithUTF8String:a1value] \ + andObject: [NSString stringWithUTF8String:a2value] \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + } while(0) + +// Generates a failure when c-string a1 is equal to c-string a2. +// Args: +// a1: string 1 +// a2: string 2 +// description: A format string as in the printf() function. Can be nil or +// an empty string but must be present. +// ...: A variable number of arguments to the format string. Can be absent. +#define STAssertNotEqualCStrings(a1, a2, description, ...) \ +do { \ + @try {\ + const char* a1value = (a1); \ + const char* a2value = (a2); \ + if (strcmp(a1value, a2value) != 0) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject: [NSString stringWithUTF8String:a1value] \ + andObject: [NSString stringWithUTF8String:a2value] \ + inFile: [NSString stringWithUTF8String:__FILE__] \ + atLine: __LINE__ \ + withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + @catch (id anException) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ + }\ + } while(0) + |