aboutsummaryrefslogtreecommitdiff
path: root/UnitTesting
diff options
context:
space:
mode:
authorGravatar thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-01-28 20:19:42 +0000
committerGravatar thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-01-28 20:19:42 +0000
commit2a5219567634ab7ab74314ff3615132becadff4a (patch)
tree8e6f447544e5eaf460da741bf57771f929b4a70c /UnitTesting
initial drop of a few sources to start things out
Diffstat (limited to 'UnitTesting')
-rw-r--r--UnitTesting/GTMNSObject+UnitTesting.h534
-rw-r--r--UnitTesting/GTMNSObject+UnitTesting.m730
-rw-r--r--UnitTesting/GTMNSView+UnitTesting.h138
-rw-r--r--UnitTesting/GTMNSView+UnitTesting.m135
-rw-r--r--UnitTesting/GTMSenTestCase.h429
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)
+