Authored by Hal Mueller

added the extra Google Toolbox for Mac testing routines that do graphics/UI test…

…ing. no tests written to these yet though.
//
// GTMCALayer+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 GTMUnitTestCALayerDrawer. 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 <QuartzCore/QuartzCore.h>
#import "GTMNSObject+UnitTesting.h"
// Category for making unit testing of graphics/UI easier.
// Allows you to take a state of a view. Supports both image and state.
// See GTMNSObject+UnitTesting.h for details.
@interface CALayer (GTMUnitTestingAdditions) <GTMUnitTestingImaging>
// Returns whether gtm_unitTestEncodeState should recurse into sublayers
//
// Returns:
// should gtm_unitTestEncodeState pick up sublayer state.
- (BOOL)gtm_shouldEncodeStateForSublayers;
@end
@interface NSObject (GTMCALayerUnitTestingDelegateMethods)
// Delegate method that allows a delegate for a layer to
// decide whether we should recurse
- (BOOL)gtm_shouldEncodeStateForSublayersOfLayer:(CALayer*)layer;
@end
... ...
//
// GTMCALayer+UnitTesting.m
//
// Category for making unit testing of graphics/UI easier.
// Allows you to save a view out to a image 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 "GTMCALayer+UnitTesting.h"
@implementation CALayer (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
- (CGImageRef)gtm_createUnitTestImage {
CGRect bounds = [self bounds];
CGSize size = CGSizeMake(CGRectGetWidth(bounds), CGRectGetHeight(bounds));
CGContextRef context = [self gtm_createUnitTestBitmapContextOfSize:size
data:NULL];
_GTMDevAssert(context, @"Couldn't create context");
// iPhone renders are flipped
CGAffineTransform transform = CGAffineTransformMakeTranslation(0, size.height);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
CGContextConcatCTM(context, transform);
[self renderInContext:context];
CGImageRef image = CGBitmapContextCreateImage(context);
CFRelease(context);
return image;
}
// 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)gtm_unitTestEncodeState:(NSCoder*)inCoder {
[super gtm_unitTestEncodeState:inCoder];
[inCoder encodeBool:[self isHidden] forKey:@"LayerIsHidden"];
[inCoder encodeBool:[self isDoubleSided] forKey:@"LayerIsDoublesided"];
[inCoder encodeBool:[self isOpaque] forKey:@"LayerIsOpaque"];
[inCoder encodeFloat:[self opacity] forKey:@"LayerOpacity"];
// TODO: There is a ton more we can add here. What are we interested in?
if ([self gtm_shouldEncodeStateForSublayers]) {
int i = 0;
for (CALayer *subLayer in [self sublayers]) {
[inCoder encodeObject:subLayer
forKey:[NSString stringWithFormat:@"CALayerSubLayer %d", i]];
i = i + 1;
}
}
}
// Returns whether gtm_unitTestEncodeState should recurse into sublayers
//
// Returns:
// should gtm_unitTestEncodeState pick up sublayer state.
- (BOOL)gtm_shouldEncodeStateForSublayers {
BOOL value = YES;
if([self.delegate respondsToSelector:@selector(gtm_shouldEncodeStateForSublayersOfLayer:)]) {
value = [self.delegate gtm_shouldEncodeStateForSublayersOfLayer:self];
}
return value;
}
@end
... ...
//
// GTMGarbageCollection.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.
//
#import <Foundation/Foundation.h>
// This allows us to easily move our code from GC to non GC.
// They are no-ops unless we are require Leopard or above.
// See
// http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/index.html
// for details.
// General use would be
// CFTypeRef type = ...
// return [GTMNSMakeCollectable(type) autorelease];
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
FOUNDATION_STATIC_INLINE id GTMNSMakeCollectable(CFTypeRef cf) {
return NSMakeCollectable(cf);
}
#else
FOUNDATION_STATIC_INLINE id GTMNSMakeCollectable(CFTypeRef cf) {
// NSMakeCollectable handles NULLs just fine and returns nil as expected.
return (id)cf;
}
#endif
... ...
//
// 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.
//
#import "GTMDefines.h"
#import <Foundation/Foundation.h>
#if GTM_MACOS_SDK
#import <ApplicationServices/ApplicationServices.h>
#elif GTM_IPHONE_SDK
#import <CoreGraphics/CoreGraphics.h>
#endif
#import "GTMSenTestCase.h"
// Utility functions for GTMAssert* Macros. Don't use them directly
// but use the macros below instead
BOOL GTMIsObjectImageEqualToImageNamed(id object,
NSString *filename,
NSString **error);
BOOL GTMIsObjectStateEqualToStateNamed(id object,
NSString *filename,
NSString **error);
// Fails when image of |a1| does not equal image in image file named |a2|
//
// Generates a failure when the unittest image of |a1| is not equal to the
// image stored in the image 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 image
// representation of |a1| in the save directory with name |a2|. This can then
// be included in the bundle as the master to test against.
// If |a2| != |a1|, we save a image representation of |a1| in the save
// directory named |a2|_Failed and a file named |a2|_Failed_Diff showing the
// diff in red so that we can see what has changed.
// See pathForImageNamed to see how name is searched for.
// The save directory is specified by +gtm_setUnitTestSaveToDirectory, and is
// the desktop by default.
// Implemented as a macro to match the rest of the SenTest macros.
//
// Args:
// a1: The object to be checked. Must implement the -createUnitTestImage method.
// a2: The name of the image 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 GTMAssertObjectImageEqualToImageNamed(a1, a2, description, ...) \
do { \
id a1Object = (a1); \
NSString* a2String = (a2); \
NSString *failString = nil; \
BOOL isGood = GTMIsObjectImageEqualToImageNamed(a1Object, a2String, &failString); \
if (!isGood) { \
if (description) { \
STFail(@"%@: %@", failString, STComposeString(description, ##__VA_ARGS__)); \
} 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| in the save directiry 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| in the save
// directory with name |a2|_Failed so that we can compare the two files to see
// what has changed.
// The save directory is specified by +gtm_setUnitTestSaveToDirectory, and is
// the desktop by default.
// Implemented as a macro to match the rest of the SenTest macros.
//
// Args:
// a1: The object to be checked. Must implement the -createUnitTestImage 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 { \
id a1Object = (a1); \
NSString* a2String = (a2); \
NSString *failString = nil; \
BOOL isGood = GTMIsObjectStateEqualToStateNamed(a1Object, a2String, &failString); \
if (!isGood) { \
if (description) { \
STFail(@"%@: %@", failString, STComposeString(description, ##__VA_ARGS__)); \
} else { \
STFail(@"%@", failString); \
} \
} \
} while(0);
// test both GTMAssertObjectImageEqualToImageNamed 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 { \
GTMAssertObjectImageEqualToImageNamed(a1, a2, description, ##__VA_ARGS__); \
GTMAssertObjectStateEqualToStateNamed(a1, a2, description, ##__VA_ARGS__); \
} while (0)
// GTMUnitTestingImaging protocol is for objects which need to save their
// image for using with the unit testing categories
@protocol GTMUnitTestingImaging
// Create a CGImageRef containing a representation suitable for use in
// comparing against a master image.
//
// Returns:
// an CGImageRef of the object. Caller must release
- (CGImageRef)gtm_createUnitTestImage;
@end
// 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)gtm_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" that we can compare at
// runtime to an image 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.
// 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, GTMAssertObjectImageEqualToImageNamed and
// GTMAssertObjectEqualToStateAndImageNamed.
@interface NSObject (GTMUnitTestingAdditions) <GTMUnitTestingEncoding>
// Allows you to control where the unit test utilities save any files
// (image or state) that they create on your behalf. By default they
// will save to the desktop.
+ (void)gtm_setUnitTestSaveToDirectory:(NSString*)path;
+ (NSString *)gtm_getUnitTestSaveToDirectory;
// Create a CGColorSpaceRef appropriate for using in creating a unit test image
// iPhone uses device colorspace.
// Returns:
// an CGColorSpaceRef of the object. Caller must release
- (CGColorSpaceRef)gtm_createUnitTestColorspace;
// Create a CGBitmapContextRef appropriate for using in creating a unit test
// image. If data is non-NULL, returns the buffer that the bitmap is
// using for it's underlying storage. You must free this buffer using
// free. If data is NULL, uses it's own internal storage.
// In either case, it will be filled with transparency.
//
// Returns:
// an CGContextRef of the object. Caller must release
- (CGContextRef)gtm_createUnitTestBitmapContextOfSize:(CGSize)size
data:(unsigned char **)data;
// Checks to see that system settings are valid for doing an image comparison.
// Most of these are set by our unit test app. See the unit test app main.m
// for details.
//
// Returns:
// YES if we can do image comparisons for this object type.
- (BOOL)gtm_areSystemSettingsValidForDoingImage;
// Return the type of image to work with. Only valid types on the iPhone
// are kUTTypeJPEG and kUTTypePNG. MacOS supports several more.
- (CFStringRef)gtm_imageUTI;
// Return the extension to be used for saving unittest images
//
// Returns
// An extension (e.g. "png")
- (NSString*)gtm_imageExtension;
// Return image data in the format expected for gtm_imageExtension
// So for a "png" extension I would expect "png" data
//
// Returns
// NSData for image
- (NSData*)gtm_imageDataForImage:(CGImageRef)image;
// Save the unitTestImage to a image file with name
// |name|.arch.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension
// in the save folder (desktop by default)
//
// Args:
// name: The name for the image file you would like saved.
//
// Returns:
// YES if the file was successfully saved.
//
- (BOOL)gtm_saveToImageNamed:(NSString*)name;
// Save unitTestImage of |self| to an image file at path |path|.
// All non-drawn areas will be transparent.
//
// Args:
// name: The name for the image file you would like saved.
//
// Returns:
// YES if the file was successfully saved.
//
- (BOOL)gtm_saveToImageAt:(NSString*)path;
// Compares unitTestImage of |self| to the image located at |path|
//
// Args:
// path: the path to the image file you want to compare against.
// If diff is non-nil, it will contain an auto-released diff of the images.
//
// Returns:
// YES if they are equal, NO is they are not
// If diff is non-nil, it will contain a diff of the images. Must
// be released by caller.
//
- (BOOL)gtm_compareWithImageAt:(NSString*)path diffImage:(CGImageRef*)diff;
// Find the path for a image by name in your bundle.
// Searches for the following:
// "name.arch.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension"
// "name.OSVersionMajor.OSVersionMinor.OSVersionBugfix.arch.extension"
// "name.arch.OSVersionMajor.OSVersionMinor.extension"
// "name.OSVersionMajor.OSVersionMinor.arch.extension"
// "name.arch.OSVersionMajor.extension"
// "name.OSVersionMajor.arch.extension"
// "name.arch.extension"
// "name.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension"
// "name.OSVersionMajor.OSVersionMinorextension"
// "name.OSVersionMajor.extension"
// "name.extension"
// Do not include the extension on your name.
//
// Args:
// name: The name for the image file you would like to find.
//
// Returns:
// the path if the image exists in your bundle
// or nil if no image to be found
//
- (NSString *)gtm_pathForImageNamed:(NSString*)name;
// Generates a CGImageRef from the image at |path|
// Args:
// path: The path to the image.
//
// Returns:
// A CGImageRef that you own, or nil if no image at path
- (CGImageRef)gtm_createImageUsingPath:(NSString*)path;
// Generates a path for a image in the save directory, which is desktop
// by default.
// Path will be:
// SaveDir/|name|.arch.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension
//
// Args:
// name: The name for the image file you would like to generate a path for.
//
// Returns:
// the path
//
- (NSString *)gtm_saveToPathForImageNamed:(NSString*)name;
// Gives us a representation of unitTestImage of |self|.
//
// Returns:
// a representation if successful
// nil if failed
//
- (NSData *)gtm_imageRepresentation;
// Return the extension to be used for saving unittest states
//
// Returns
// An extension (e.g. "gtmUTState")
- (NSString*)gtm_stateExtension;
// Save the encoded unit test state to a state file with name
// |name|.arch.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension
// in the save folder (desktop by default)
//
// Args:
// name: The name for the state file you would like saved.
//
// Returns:
// YES if the file was successfully saved.
//
- (BOOL)gtm_saveToStateNamed:(NSString*)name;
// Save encoded unit test state of |self| to a state 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)gtm_saveToStateAt:(NSString*)path;
// Compares encoded unit test state of |self| to the state file 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)gtm_compareWithStateAt:(NSString*)path;
// Find the path for a state by name in your bundle.
// Searches for:
// "name.arch.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension"
// "name.OSVersionMajor.OSVersionMinor.OSVersionBugfix.arch.extension"
// "name.arch.OSVersionMajor.OSVersionMinor.extension"
// "name.OSVersionMajor.OSVersionMinor.arch.extension"
// "name.arch.OSVersionMajor.extension"
// "name.OSVersionMajor.arch.extension"
// "name.arch.extension"
// "name.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension"
// "name.OSVersionMajor.OSVersionMinor.extension"
// "name.OSVersionMajor.extension"
// "name.extension"
// Do not include the 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 *)gtm_pathForStateNamed:(NSString*)name;
// Generates a path for a state in the save directory, which is desktop
// by default.
// Path will be:
// SaveDir/|name|.arch.OSVersionMajor.OSVersionMinor.OSVersionBugfix.extension
//
// Args:
// name: The name for the state file you would like to generate a path for.
//
// Returns:
// the path
//
- (NSString *)gtm_saveToPathForStateNamed:(NSString*)name;
// Gives us the encoded unit test state for |self|
//
// Returns:
// the encoded state if successful
// nil if failed
//
- (NSDictionary *)gtm_stateRepresentation;
// 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)gtm_unitTestEncodeState:(NSCoder*)inCoder;
@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)gtm_unitTestEncoderWillEncode:(id)sender inCoder:(NSCoder*)inCoder;
@end
// 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;
... ...
//
// 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.
//
#import <mach-o/arch.h>
#import "GTMNSObject+UnitTesting.h"
#import "GTMSystemVersion.h"
#import "GTMGarbageCollection.h"
#if GTM_IPHONE_SDK
#import <UIKit/UIKit.h>
#endif
NSString *const GTMUnitTestingEncodedObjectNotification = @"GTMUnitTestingEncodedObjectNotification";
NSString *const GTMUnitTestingEncoderKey = @"GTMUnitTestingEncoderKey";
#if GTM_IPHONE_SDK
// No UTIs on iPhone. Only two we need.
const CFStringRef kUTTypePNG = CFSTR("public.png");
const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg");
#endif
// 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
BOOL GTMIsObjectImageEqualToImageNamed(id object,
NSString* filename,
NSString **error) {
NSString *failString = nil;
if (error) {
*error = nil;
}
BOOL isGood = [object respondsToSelector:@selector(gtm_createUnitTestImage)];
if (isGood) {
if ([object gtm_areSystemSettingsValidForDoingImage]) {
NSString *aPath = [object gtm_pathForImageNamed:filename];
CGImageRef diff = nil;
isGood = aPath != nil;
if (isGood) {
isGood = [object gtm_compareWithImageAt:aPath diffImage:&diff];
}
if (!isGood) {
if (aPath) {
filename = [filename stringByAppendingString:@"_Failed"];
}
BOOL aSaved = [object gtm_saveToImageNamed:filename];
NSString *fileNameWithExtension = [NSString stringWithFormat:@"%@.%@",
filename, [object gtm_imageExtension]];
NSString *fullSavePath = [object gtm_saveToPathForImageNamed:filename];
if (NO == aSaved) {
if (!aPath) {
failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. "
"Tried to save as %@ and failed.",
fileNameWithExtension, fullSavePath];
} else {
failString = [NSString stringWithFormat:@"Object image different than file %@. "
"Tried to save as %@ and failed.",
aPath, fullSavePath];
}
} else {
if (!aPath) {
failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. "
"Saved to %@", fileNameWithExtension, fullSavePath];
} else {
NSString *diffPath = [filename stringByAppendingString:@"_Diff"];
diffPath = [object gtm_saveToPathForImageNamed:diffPath];
NSData *data = nil;
if (diff) {
data = [object gtm_imageDataForImage:diff];
CFRelease(diff);
}
if ([data writeToFile:diffPath atomically:YES]) {
failString = [NSString stringWithFormat:@"Object image different "
"than file %@. Saved image to %@. Saved diff to %@",
aPath, fullSavePath, diffPath];
} else {
failString = [NSString stringWithFormat:@"Object image different "
"than file %@. Saved image to %@. Unable to save "
"diff. Most likely the image and diff are "
"different sizes.",
aPath, fullSavePath];
}
}
}
}
} else {
failString = @"systemSettings not valid for taking image"; // COV_NF_LINE
}
} else {
failString = @"Object does not conform to GTMUnitTestingImaging protocol";
}
if (error) {
*error = failString;
}
return isGood;
}
BOOL GTMIsObjectStateEqualToStateNamed(id object,
NSString* filename,
NSString **error) {
NSString *failString = nil;
if (error) {
*error = nil;
}
BOOL isGood = [object conformsToProtocol:@protocol(GTMUnitTestingEncoding)];
if (isGood) {
NSString *aPath = [object gtm_pathForStateNamed:filename];
isGood = aPath != nil;
if (isGood) {
isGood = [object gtm_compareWithStateAt:aPath];
}
if (!isGood) {
if (aPath) {
filename = [filename stringByAppendingString:@"_Failed"];
}
BOOL aSaved = [object gtm_saveToStateNamed:filename];
NSString *fileNameWithExtension = [NSString stringWithFormat:@"%@.%@",
filename, [object gtm_stateExtension]];
NSString *fullSavePath = [object gtm_saveToPathForStateNamed:filename];
if (NO == aSaved) {
if (!aPath) {
failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. "
"Tried to save as %@ and failed.",
fileNameWithExtension, fullSavePath];
} else {
failString = [NSString stringWithFormat:@"Object state different than file %@. "
"Tried to save as %@ and failed.",
aPath, fullSavePath];
}
} else {
if (!aPath) {
failString = [NSString stringWithFormat:@"File %@ did not exist in bundle. "
"Saved to %@", fileNameWithExtension, fullSavePath];
} else {
failString = [NSString stringWithFormat:@"Object state different than file %@. "
"Saved to %@", aPath, fullSavePath];
}
}
}
} else {
failString = @"Object does not conform to GTMUnitTestingEncoding protocol";
}
if (error) {
*error = failString;
}
return isGood;
}
@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 *)gtm_pathForFileNamed:(NSString*)name extension:(NSString*)extension;
- (NSString *)gtm_saveToPathForFileNamed:(NSString*)name
extension:(NSString*)extension;
- (CGImageRef)gtm_createUnitTestImage;
// Returns nil if there is no override
- (NSString *)gtm_getOverrideDefaultUnitTestSaveToDirectory;
@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
// Small utility function for checking to see if a is b +/- 1.
static inline BOOL almostEqual(unsigned char a, unsigned char b) {
unsigned char diff = a > b ? a - b : b - a;
BOOL notEqual = diff < 2;
return notEqual;
}
@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:2];
[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 {
_GTMDevAssert(![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 gtm_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 gtm_unitTestEncodeState get it to record
// its data.
if ([objv respondsToSelector:@selector(gtm_unitTestEncodeState:)]) {
[objv gtm_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 {
_GTMDevAssert(NO, @"Unable to encode forKey: %@", key); // COV_NF_LINE
}
}
[dictionary_ release];
dictionary_ = curDictionary;
}
// Basic encoding methods for POD types.
//
// Arguments:
// *v - value to encode
// key - key to encode it in
- (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 dataWithBytes: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
static NSString *gGTMUnitTestSaveToDirectory = nil;
@implementation NSObject (GTMUnitTestingAdditions)
+ (void)gtm_setUnitTestSaveToDirectory:(NSString*)path {
@synchronized([self class]) {
[gGTMUnitTestSaveToDirectory autorelease];
gGTMUnitTestSaveToDirectory = [path copy];
}
}
+ (NSString *)gtm_getUnitTestSaveToDirectory {
NSString *result = nil;
@synchronized([self class]) {
if (!gGTMUnitTestSaveToDirectory) {
#if GTM_IPHONE_SDK
// Developer build, use their home directory Desktop.
gGTMUnitTestSaveToDirectory = [[[[[NSHomeDirectory() stringByDeletingLastPathComponent]
stringByDeletingLastPathComponent]
stringByDeletingLastPathComponent]
stringByDeletingLastPathComponent]
stringByAppendingPathComponent:@"Desktop"];
#else
NSArray *desktopDirs = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory,
NSUserDomainMask,
YES);
gGTMUnitTestSaveToDirectory = [desktopDirs objectAtIndex:0];
#endif
// Did we get overridden?
NSString *override = [self gtm_getOverrideDefaultUnitTestSaveToDirectory];
if (override) {
gGTMUnitTestSaveToDirectory = override;
}
[gGTMUnitTestSaveToDirectory retain];
}
result = gGTMUnitTestSaveToDirectory;
}
return result;
}
// Return nil if there is no override
- (NSString *)gtm_getOverrideDefaultUnitTestSaveToDirectory {
NSString *result = nil;
// If we have an environment variable that ends in "BUILD_NUMBER" odds are
// we're on an automated build system, so use the build products dir as an
// override instead of writing on the desktop.
NSDictionary *env = [[NSProcessInfo processInfo] environment];
NSEnumerator *enumerator = [env keyEnumerator];
NSString *key = nil;
while (((key = [enumerator nextObject]) != nil) &&
![key hasSuffix:@"BUILD_NUMBER"])
;
if (key) {
result = [env objectForKey:@"BUILT_PRODUCTS_DIR"];
}
if (result && [result length] == 0) {
result = nil;
}
return result;
}
/// 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 *)gtm_pathForFileNamed:(NSString*)name
extension:(NSString*)extension {
NSString *thePath = nil;
Class bundleClass = [GTMUnitTestingAdditionsBundleFinder class];
NSBundle *myBundle = [NSBundle bundleForClass:bundleClass];
_GTMDevAssert(myBundle, @"Couldn't find bundle for class: %@ searching for file:%@.%@",
NSStringFromClass(bundleClass), name, extension);
// 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] = @"";
NSString *extensions[2];
#if GTM_IPHONE_SDK
extensions[0] = @".iPhone";
#else // !GTM_IPHONE_SDK
// In reading arch(3) you'd thing this would work:
//
// const NXArchInfo *localInfo = NXGetLocalArchInfo();
// _GTMDevAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo");
// const NXArchInfo *genericInfo = NXGetArchInfoFromCpuType(localInfo->cputype, 0);
// _GTMDevAssert(genericInfo && genericInfo->name, @"Couldn't get generic NXArchInfo");
// extensions[0] = [NSString stringWithFormat:@".%s", genericInfo->name];
//
// but on 64bit it returns the same things as on 32bit, so...
#if __POWERPC__
#if __LP64__
extensions[0] = @".ppc64";
#else // !__LP64__
extensions[0] = @".ppc";
#endif // __LP64__
#else // !__POWERPC__
#if __LP64__
extensions[0] = @".x86_64";
#else // !__LP64__
extensions[0] = @".i386";
#endif // __LP64__
#endif // !__POWERPC__
#endif // GTM_IPHONE_SDK
extensions[1] = @"";
size_t i, j;
// Note that we are searching for the most exact match first.
for (i = 0;
!thePath && i < sizeof(extensions) / sizeof(*extensions);
++i) {
for (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 *)gtm_saveToPathForFileNamed:(NSString*)name
extension:(NSString*)extension {
char const *systemArchitecture;
#if GTM_IPHONE_SDK
systemArchitecture = "iPhone";
#else
// In reading arch(3) you'd thing this would work:
//
// const NXArchInfo *localInfo = NXGetLocalArchInfo();
// _GTMDevAssert(localInfo && localInfo->name, @"Couldn't get NXArchInfo");
// const NXArchInfo *genericInfo = NXGetArchInfoFromCpuType(localInfo->cputype, 0);
// _GTMDevAssert(genericInfo && genericInfo->name, @"Couldn't get generic NXArchInfo");
// system = genericInfo->name;
//
// but on 64bit it returns the same things as on 32bit, so...
#if __POWERPC__
#if __LP64__
systemArchitecture = "ppc64";
#else // !__LP64__
systemArchitecture = "ppc";
#endif // __LP64__
#else // !__POWERPC__
#if __LP64__
systemArchitecture = "x86_64";
#else // !__LP64__
systemArchitecture = "i386";
#endif // __LP64__
#endif // !__POWERPC__
#endif
long major, minor, bugFix;
[GTMSystemVersion getMajor:&major minor:&minor bugFix:&bugFix];
NSString *fullName = [NSString stringWithFormat:@"%@.%s.%d.%d.%d",
name, systemArchitecture, major, minor, bugFix];
NSString *basePath = [[self class] gtm_getUnitTestSaveToDirectory];
return [[basePath stringByAppendingPathComponent:fullName]
stringByAppendingPathExtension:extension];
}
#pragma mark UnitTestImage
// Create a CGColorSpaceRef appropriate for using in creating a unit test image
// iPhone uses device colorspace.
// Returns:
// an CGColorSpaceRef of the object. Caller must release
- (CGColorSpaceRef)gtm_createUnitTestColorspace {
#if GTM_IPHONE_SDK
return CGColorSpaceCreateDeviceRGB();
#else
return CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
#endif
}
// Create a CGBitmapContextRef appropriate for using in creating a unit test
// image. If data is non-NULL, returns the buffer that the bitmap is
// using for it's underlying storage. You must free this buffer using
// free. If data is NULL, uses it's own internal storage.
//
// Returns:
// an CGContextRef of the object. Caller must release
- (CGContextRef)gtm_createUnitTestBitmapContextOfSize:(CGSize)size
data:(unsigned char**)data {
CGContextRef context = NULL;
size_t height = size.height;
size_t width = size.width;
size_t bytesPerRow = width * 4;
size_t bitsPerComponent = 8;
CGColorSpaceRef cs = [self gtm_createUnitTestColorspace];
_GTMDevAssert(cs, @"Couldn't create colorspace");
CGBitmapInfo info = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault;
if (data) {
*data = (unsigned char*)calloc(bytesPerRow, height);
_GTMDevAssert(*data, @"Couldn't create bitmap");
}
context = CGBitmapContextCreate(data ? *data : NULL, width, height,
bitsPerComponent, bytesPerRow, cs, info);
_GTMDevAssert(context, @"Couldn't create an context");
if (!data) {
CGContextClearRect(context, CGRectMake(0, 0, size.width, size.height));
}
CGContextSetRenderingIntent(context, kCGRenderingIntentRelativeColorimetric);
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
CGContextSetShouldAntialias(context, NO);
CGContextSetAllowsAntialiasing(context, NO);
CGContextSetShouldSmoothFonts(context, NO);
CFRelease(cs);
return context;
}
// Checks to see that system settings are valid for doing an image comparison.
// To be overridden by subclasses.
// Returns:
// YES if we can do image comparisons for this object type.
- (BOOL)gtm_areSystemSettingsValidForDoingImage {
return YES;
}
- (CFStringRef)gtm_imageUTI {
#if GTM_IPHONE_SDK
return kUTTypePNG;
#else
// Currently can't use PNG on Leopard. (10.5.2)
// Radar:5844618 PNG importer/exporter in ImageIO is lossy
return kUTTypeTIFF;
#endif
}
// Return the extension to be used for saving unittest images
//
// Returns
// An extension (e.g. "png")
- (NSString*)gtm_imageExtension {
CFStringRef uti = [self gtm_imageUTI];
#if GTM_IPHONE_SDK
if (CFEqual(uti, kUTTypePNG)) {
return @"png";
} else if (CFEqual(uti, kUTTypeJPEG)) {
return @"jpg";
} else {
_GTMDevAssert(NO, @"Illegal UTI for iPhone");
}
return nil;
#else
CFStringRef extension = UTTypeCopyPreferredTagWithClass(uti,
kUTTagClassFilenameExtension);
_GTMDevAssert(extension, @"No extension for uti: %@", uti);
return [GTMNSMakeCollectable(extension) autorelease];
#endif
}
// Return image data in the format expected for gtm_imageExtension
// So for a "png" extension I would expect "png" data
//
// Returns
// NSData for image
- (NSData*)gtm_imageDataForImage:(CGImageRef)image {
NSData *data = nil;
#if GTM_IPHONE_SDK
// iPhone support
UIImage *uiImage = [UIImage imageWithCGImage:image];
CFStringRef uti = [self gtm_imageUTI];
if (CFEqual(uti, kUTTypePNG)) {
data = UIImagePNGRepresentation(uiImage);
} else if (CFEqual(uti, kUTTypeJPEG)) {
data = UIImageJPEGRepresentation(uiImage, 1.0f);
} else {
_GTMDevAssert(NO, @"Illegal UTI for iPhone");
}
#else
data = [NSMutableData data];
CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)data,
[self gtm_imageUTI],
1,
NULL);
// LZW Compression for TIFF
NSDictionary *tiffDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:NSTIFFCompressionLZW]
forKey:(const NSString*)kCGImagePropertyTIFFCompression];
NSDictionary *destProps = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:1.0f],
(const NSString*)kCGImageDestinationLossyCompressionQuality,
tiffDict,
(const NSString*)kCGImagePropertyTIFFDictionary,
nil];
CGImageDestinationAddImage(dest, image, (CFDictionaryRef)destProps);
CGImageDestinationFinalize(dest);
CFRelease(dest);
#endif
return data;
}
// Save the unitTestImage to an image file with name |name| at
// ~/Desktop/|name|.extension.
//
// Note: When running under Pulse automation output is redirected to the
// Pulse base directory.
//
// Args:
// name: The name for the image file you would like saved.
//
// Returns:
// YES if the file was successfully saved.
//
- (BOOL)gtm_saveToImageNamed:(NSString*)name {
NSString *newPath = [self gtm_saveToPathForImageNamed:name];
return [self gtm_saveToImageAt:newPath];
}
// Save unitTestImage of |self| to an image file at path |path|.
//
// Args:
// name: The name for the image file you would like saved.
//
// Returns:
// YES if the file was successfully saved.
//
- (BOOL)gtm_saveToImageAt:(NSString*)path {
if (!path) return NO;
NSData *data = [self gtm_imageRepresentation];
return [data writeToFile:path atomically:YES];
}
// Generates a CGImageRef from the image at |path|
// Args:
// path: The path to the image.
//
// Returns:
// A CGImageRef that you own, or nil if no image at path
- (CGImageRef)gtm_createImageUsingPath:(NSString*)path {
CGImageRef imageRef = nil;
#if GTM_IPHONE_SDK
UIImage *image = [UIImage imageWithContentsOfFile:path];
if (image) {
imageRef = CGImageRetain(image.CGImage);
}
#else
CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)path,
kCFURLPOSIXPathStyle, NO);
if (url) {
CGImageSourceRef imageSource = CGImageSourceCreateWithURL(url, NULL);
CFRelease(url);
if (imageSource) {
imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
CFRelease(imageSource);
}
}
#endif
return imageRef;
}
/// Compares unitTestImage of |self| to the image located at |path|
//
// Args:
// path: the path to the image file you want to compare against.
// If diff is non-nil, it will contain an auto-released diff of the images.
//
// Returns:
// YES if they are equal, NO is they are not
// If diff is non-nil, it will contain an auto-released diff of the images.
//
- (BOOL)gtm_compareWithImageAt:(NSString*)path diffImage:(CGImageRef*)diff {
BOOL answer = NO;
if (diff) {
*diff = nil;
}
CGImageRef fileRep = [self gtm_createImageUsingPath:path];
_GTMDevAssert(fileRep, @"Unable to create imagerep from %@", path);
CGImageRef imageRep = [self gtm_createUnitTestImage];
_GTMDevAssert(imageRep, @"Unable to create imagerep for %@", self);
size_t fileHeight = CGImageGetHeight(fileRep);
size_t fileWidth = CGImageGetWidth(fileRep);
size_t imageHeight = CGImageGetHeight(imageRep);
size_t imageWidth = CGImageGetWidth(imageRep);
if (fileHeight == imageHeight && fileWidth == imageWidth) {
// if all the sizes are equal, run through the bytes and compare
// them for equality.
// Do an initial fast check, if this fails and the caller wants a
// diff, we'll do the slow path and create the diff. The diff path
// could be optimized, but probably not necessary at this point.
answer = YES;
CGSize imageSize = CGSizeMake(fileWidth, fileHeight);
CGRect imageRect = CGRectMake(0, 0, fileWidth, fileHeight);
unsigned char *fileData;
unsigned char *imageData;
CGContextRef fileContext = [self gtm_createUnitTestBitmapContextOfSize:imageSize
data:&fileData];
_GTMDevAssert(fileContext, @"Unable to create filecontext");
CGContextDrawImage(fileContext, imageRect, fileRep);
CGContextRef imageContext =[self gtm_createUnitTestBitmapContextOfSize:imageSize
data:&imageData];
_GTMDevAssert(imageContext, @"Unable to create imageContext");
CGContextDrawImage(imageContext, imageRect, imageRep);
size_t fileBytesPerRow = CGBitmapContextGetBytesPerRow(fileContext);
size_t imageBytesPerRow = CGBitmapContextGetBytesPerRow(imageContext);
size_t row, col;
_GTMDevAssert(imageWidth * 4 <= imageBytesPerRow,
@"We expect image data to be 32bit RGBA");
for (row = 0; row < fileHeight && answer; row++) {
answer = memcmp(fileData + fileBytesPerRow * row,
imageData + imageBytesPerRow * row,
imageWidth * 4) == 0;
}
if (!answer && diff) {
answer = YES;
unsigned char *diffData;
CGContextRef diffContext = [self gtm_createUnitTestBitmapContextOfSize:imageSize
data:&diffData];
_GTMDevAssert(diffContext, @"Can't make diff context");
size_t diffRowBytes = CGBitmapContextGetBytesPerRow(diffContext);
for (row = 0; row < imageHeight; row++) {
uint32_t *imageRow = (uint32_t*)(imageData + imageBytesPerRow * row);
uint32_t *fileRow = (uint32_t*)(fileData + fileBytesPerRow * row);
uint32_t* diffRow = (uint32_t*)(diffData + diffRowBytes * row);
for (col = 0; col < imageWidth; col++) {
uint32_t imageColor = imageRow[col];
uint32_t fileColor = fileRow[col];
unsigned char imageAlpha = imageColor & 0xF;
unsigned char imageBlue = imageColor >> 8 & 0xF;
unsigned char imageGreen = imageColor >> 16 & 0xF;
unsigned char imageRed = imageColor >> 24 & 0xF;
unsigned char fileAlpha = fileColor & 0xF;
unsigned char fileBlue = fileColor >> 8 & 0xF;
unsigned char fileGreen = fileColor >> 16 & 0xF;
unsigned char fileRed = fileColor >> 24 & 0xF;
// Check to see if color is almost right.
// No matter how hard I've tried, I've still gotten occasionally
// screwed over by colorspaces not mapping correctly, and small
// sampling errors coming in. This appears to work for most cases.
// Almost equal is defined to check within 1% on all components.
BOOL equal = almostEqual(imageRed, fileRed) &&
almostEqual(imageGreen, fileGreen) &&
almostEqual(imageBlue, fileBlue) &&
almostEqual(imageAlpha, fileAlpha);
answer &= equal;
if (diff) {
uint32_t newColor;
if (equal) {
newColor = (((uint32_t)imageRed) << 24) +
(((uint32_t)imageGreen) << 16) +
(((uint32_t)imageBlue) << 8) +
(((uint32_t)imageAlpha) / 2);
} else {
newColor = 0xFF0000FF;
}
diffRow[col] = newColor;
}
}
}
*diff = CGBitmapContextCreateImage(diffContext);
free(diffData);
CFRelease(diffContext);
free(fileData);
CFRelease(fileContext);
free(imageData);
CFRelease(imageContext);
}
CFRelease(imageRep);
CFRelease(fileRep);
}
return answer;
}
// Find the path for an image by name in your bundle.
// Do not include the extension on your name.
//
// Args:
// name: The name for the image file you would like to find.
//
// Returns:
// the path if the image exists in your bundle
// or nil if no image to be found
//
- (NSString *)gtm_pathForImageNamed:(NSString*)name {
return [self gtm_pathForFileNamed:name
extension:[self gtm_imageExtension]];
}
- (NSString *)gtm_saveToPathForImageNamed:(NSString*)name {
return [self gtm_saveToPathForFileNamed:name
extension:[self gtm_imageExtension]];
}
// Gives us a representation of unitTestImage of |self|.
//
// Returns:
// a representation of image if successful
// nil if failed
//
- (NSData *)gtm_imageRepresentation {
CGImageRef imageRep = [self gtm_createUnitTestImage];
NSData *data = [self gtm_imageDataForImage:imageRep];
_GTMDevAssert(data, @"unable to create %@ from %@", [self gtm_imageExtension], self);
CFRelease(imageRep);
return data;
}
#pragma mark UnitTestState
// Return the extension to be used for saving unittest states
//
// Returns
// An extension (e.g. "gtmUTState")
- (NSString*)gtm_stateExtension {
return @"gtmUTState";
}
// Save the encoded unit test state to a state file with name |name| at
// ~/Desktop/|name|.extension.
//
// 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)gtm_saveToStateNamed:(NSString*)name {
NSString *newPath = [self gtm_saveToPathForStateNamed:name];
return [self gtm_saveToStateAt:newPath];
}
// Save encoded unit test state of |self| to a state 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)gtm_saveToStateAt:(NSString*)path {
if (!path) return NO;
NSDictionary *dictionary = [self gtm_stateRepresentation];
return [dictionary writeToFile:path atomically:YES];
}
// Compares encoded unit test state of |self| to the state file 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)gtm_compareWithStateAt:(NSString*)path {
NSDictionary *masterDict = [NSDictionary dictionaryWithContentsOfFile:path];
_GTMDevAssert(masterDict, @"Unable to create dictionary from %@", path);
NSDictionary *selfDict = [self gtm_stateRepresentation];
return [selfDict isEqual: masterDict];
}
// Find the path for a state by name in your bundle.
// Do not include the 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 *)gtm_pathForStateNamed:(NSString*)name {
return [self gtm_pathForFileNamed:name extension:[self gtm_stateExtension]];
}
- (NSString *)gtm_saveToPathForStateNamed:(NSString*)name {
return [self gtm_saveToPathForFileNamed:name
extension:[self gtm_stateExtension]];
}
// Gives us the encoded unit test state |self|
//
// Returns:
// the encoded state if successful
// nil if failed
//
- (NSDictionary *)gtm_stateRepresentation {
NSDictionary *dictionary = nil;
if ([self conformsToProtocol:@protocol(GTMUnitTestingEncoding)]) {
id<GTMUnitTestingEncoding> encoder = (id<GTMUnitTestingEncoding>)self;
GTMUnitTestingKeyedCoder *archiver;
archiver = [[[GTMUnitTestingKeyedCoder alloc] init] autorelease];
[encoder gtm_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)gtm_unitTestEncodeState:(NSCoder*)inCoder {
// All impls of gtm_unitTestEncodeState
// should be calling [super gtm_unitTestEncodeState] as their first action.
_GTMDevAssert([inCoder isKindOfClass:[GTMUnitTestingKeyedCoder class]],
@"Coder must be of kind GTMUnitTestingKeyedCoder");
// If the object has a delegate, give it a chance to respond
if ([self respondsToSelector:@selector(delegate)]) {
id delegate = [self performSelector:@selector(delegate)];
if (delegate &&
[delegate respondsToSelector:@selector(gtm_unitTestEncoderWillEncode:inCoder:)]) {
[delegate gtm_unitTestEncoderWillEncode:self inCoder:inCoder];
}
}
}
@end
... ...
//
// GTMSystemVersion.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.
//
#import <Foundation/Foundation.h>
#import "GTMDefines.h"
// A class for getting information about what system we are running on
@interface GTMSystemVersion : NSObject
// Returns the current system version major.minor.bugFix
+ (void)getMajor:(long*)major minor:(long*)minor bugFix:(long*)bugFix;
#if GTM_MACOS_SDK
// Returns YES if running on 10.3, NO otherwise.
+ (BOOL)isPanther;
// Returns YES if running on 10.4, NO otherwise.
+ (BOOL)isTiger;
// Returns YES if running on 10.5, NO otherwise.
+ (BOOL)isLeopard;
// Returns a YES/NO if the system is 10.3 or better
+ (BOOL)isPantherOrGreater;
// Returns a YES/NO if the system is 10.4 or better
+ (BOOL)isTigerOrGreater;
// Returns a YES/NO if the system is 10.5 or better
+ (BOOL)isLeopardOrGreater;
#endif // GTM_MACOS_SDK
@end
... ...
//
// GTMSystemVersion.m
//
// 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.
//
#import "GTMSystemVersion.h"
#if GTM_MACOS_SDK
#import <Carbon/Carbon.h>
#endif
static SInt32 sGTMSystemVersionMajor = 0;
static SInt32 sGTMSystemVersionMinor = 0;
static SInt32 sGTMSystemVersionBugFix = 0;
@implementation GTMSystemVersion
+ (void)initialize {
if (self == [GTMSystemVersion class]) {
// Gestalt is the recommended way of getting the OS version (despite a
// comment to the contrary in the 10.4 headers and docs; see
// <http://lists.apple.com/archives/carbon-dev/2007/Aug/msg00089.html>).
// The iPhone doesn't have Gestalt though, so use the plist there.
#if GTM_MACOS_SDK
require_noerr(Gestalt(gestaltSystemVersionMajor, &sGTMSystemVersionMajor), failedGestalt);
require_noerr(Gestalt(gestaltSystemVersionMinor, &sGTMSystemVersionMinor), failedGestalt);
require_noerr(Gestalt(gestaltSystemVersionBugFix, &sGTMSystemVersionBugFix), failedGestalt);
return;
failedGestalt:
;
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_3
// gestaltSystemVersionMajor et al are only on 10.4 and above, so they
// could fail when running on 10.3.
SInt32 binaryCodedDec;
OSStatus err = err = Gestalt(gestaltSystemVersion, &binaryCodedDec);
_GTMDevAssert(!err, @"Unable to get version from Gestalt");
// Note that this code will return x.9.9 for any system rev parts that are
// greater than 9 (i.e., 10.10.10 will be 10.9.9). This shouldn't ever be a
// problem as the code above takes care of 10.4+.
int msb = (binaryCodedDec & 0x0000F000L) >> 12;
msb *= 10;
int lsb = (binaryCodedDec & 0x00000F00L) >> 8;
sGTMSystemVersionMajor = msb + lsb;
sGTMSystemVersionMinor = (binaryCodedDec & 0x000000F0L) >> 4;
sGTMSystemVersionBugFix = (binaryCodedDec & 0x0000000FL);
#endif // MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_3
#else // GTM_MACOS_SDK
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDictionary *systemVersionPlist = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
NSString *version = [systemVersionPlist objectForKey:@"ProductVersion"];
_GTMDevAssert(version, @"Unable to get version");
NSArray *versionInfo = [version componentsSeparatedByString:@"."];
NSUInteger length = [versionInfo count];
_GTMDevAssert(length > 1 && length < 4, @"Unparseable version %@", version);
sGTMSystemVersionMajor = [[versionInfo objectAtIndex:0] intValue];
_GTMDevAssert(sGTMSystemVersionMajor != 0, @"Unknown version for %@", version);
sGTMSystemVersionMinor = [[versionInfo objectAtIndex:1] intValue];
if (length == 3) {
sGTMSystemVersionBugFix = [[versionInfo objectAtIndex:2] intValue];
}
[pool release];
#endif // GTM_MACOS_SDK
}
}
+ (void)getMajor:(long*)major minor:(long*)minor bugFix:(long*)bugFix {
if (major) {
*major = sGTMSystemVersionMajor;
}
if (minor) {
*minor = sGTMSystemVersionMinor;
}
if (major) {
*bugFix = sGTMSystemVersionBugFix;
}
}
#if GTM_MACOS_SDK
+ (BOOL)isPanther {
return sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor == 3;
}
+ (BOOL)isTiger {
return sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor == 4;
}
+ (BOOL)isLeopard {
return sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor == 5;
}
+ (BOOL)isPantherOrGreater {
return (sGTMSystemVersionMajor > 10) ||
(sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor >= 3);
}
+ (BOOL)isTigerOrGreater {
return (sGTMSystemVersionMajor > 10) ||
(sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor >= 4);
}
+ (BOOL)isLeopardOrGreater {
return (sGTMSystemVersionMajor > 10) ||
(sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor >= 5);
}
#endif // GTM_MACOS_SDK
@end
... ...
//
// GTMUIKit+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 <UIKit/UIKit.h>
#import "GTMNSObject+UnitTesting.h"
@protocol GTMUnitTestViewDrawer;
// Fails when the |a1|'s drawing in an area |a2| does not equal the image 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 image 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> a1Drawer = (a1); \
CGSize a2Size = (a2); \
NSString* a3String = (a3); \
void *a4ContextInfo = (a4); \
CGRect frame = CGRectMake(0, 0, a2Size.width, a2Size.height); \
GTMUnitTestView *view = [[[GTMUnitTestView alloc] initWithFrame:frame drawer:a1Drawer contextInfo:a4ContextInfo] autorelease]; \
GTMAssertObjectImageEqualToImageNamed(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 GTMNSObject+UnitTesting.h for details.
@interface UIView (GTMUnitTestingAdditions) <GTMUnitTestingImaging>
// Returns an image containing a representation suitable for use in comparing against a master image.
//
// Returns:
// an image of the object
- (CGImageRef)gtm_createUnitTestImage;
// 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)gtm_unitTestEncodeState:(NSCoder*)inCoder;
// Returns whether gtm_unitTestEncodeState should recurse into subviews
//
// Returns:
// should gtm_unitTestEncodeState pick up subview state.
- (BOOL)gtm_shouldEncodeStateForSubviews;
@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, CGSizeMake(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 : UIView {
@private
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:(CGRect)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 UIView.
//
// Args:
// rect: the area to draw.
- (void)gtm_unitTestViewDrawRect:(CGRect)rect contextInfo:(void*)contextInfo;
@end
... ...
//
// GTMUIKit+UnitTesting.m
//
// Category for making unit testing of graphics/UI easier.
// Allows you to save a view out to a image 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 "GTMUIKit+UnitTesting.h"
#import "GTMCALayer+UnitTesting.h"
#import "GTMDefines.h"
#if !GTM_IPHONE_SDK
#error This file is for iPhone use only
#endif // GTM_IPHONE_SDK
// 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, CGSizeMake(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:(CGRect)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:(CGRect)rect {
[drawer_ gtm_unitTestViewDrawRect:rect contextInfo:contextInfo_];
}
@end
@implementation UIView (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
- (CGImageRef)gtm_createUnitTestImage {
CALayer* layer = [self layer];
return [layer gtm_createUnitTestImage];
}
// 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)gtm_unitTestEncodeState:(NSCoder*)inCoder {
[super gtm_unitTestEncodeState:inCoder];
[inCoder encodeBool:[self isHidden] forKey:@"ViewIsHidden"];
CALayer* layer = [self layer];
if (layer) {
[layer gtm_unitTestEncodeState:inCoder];
}
if ([self gtm_shouldEncodeStateForSubviews]) {
NSEnumerator *subviewEnum = [[self subviews] objectEnumerator];
UIView *subview = nil;
int i = 0;
while ((subview = [subviewEnum nextObject])) {
[inCoder encodeObject:subview
forKey:[NSString stringWithFormat:@"ViewSubView %d", i]];
i = i + 1;
}
}
}
// Returns whether gtm_unitTestEncodeState should recurse into subviews
//
// Returns:
// should gtm_unitTestEncodeState pick up subview state.
- (BOOL)gtm_shouldEncodeStateForSubviews {
return YES;
}
- (BOOL)gtm_shouldEncodeStateForSublayersOfLayer:(CALayer*)layer {
return NO;
}
@end
... ...
... ... @@ -44,6 +44,10 @@
2B246AA20F8AD9D400A7D55D /* RouteMeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B246AA10F8AD9D400A7D55D /* RouteMeTests.m */; };
2B5682720F68E36000E8DF40 /* RMOpenAerialMapSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 2B5682700F68E36000E8DF40 /* RMOpenAerialMapSource.h */; };
2B5682730F68E36000E8DF40 /* RMOpenAerialMapSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B5682710F68E36000E8DF40 /* RMOpenAerialMapSource.m */; };
2B9B484E0F8B0DDB009BA472 /* GTMCALayer+UnitTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B9B48490F8B0DDB009BA472 /* GTMCALayer+UnitTesting.m */; };
2B9B484F0F8B0DDB009BA472 /* GTMNSObject+UnitTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B9B484B0F8B0DDB009BA472 /* GTMNSObject+UnitTesting.m */; };
2B9B48500F8B0DDB009BA472 /* GTMUIKit+UnitTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B9B484D0F8B0DDB009BA472 /* GTMUIKit+UnitTesting.m */; };
2B9B48550F8B0DF7009BA472 /* GTMSystemVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 2B9B48540F8B0DF7009BA472 /* GTMSystemVersion.m */; };
2BEC60170F8AC6B8008FB858 /* GTMIPhoneUnitTestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2BF306C30F8ABCA4007014EE /* GTMIPhoneUnitTestDelegate.m */; };
2BEC60180F8AC6B9008FB858 /* GTMIPhoneUnitTestMain.m in Sources */ = {isa = PBXBuildFile; fileRef = 2BF306C40F8ABCA4007014EE /* GTMIPhoneUnitTestMain.m */; };
2BEC60190F8AC6BA008FB858 /* GTMSenTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 2BF306C60F8ABCA4007014EE /* GTMSenTestCase.m */; };
... ... @@ -225,6 +229,15 @@
2B2BD41E0F79A95500B8B9A7 /* routeme.doxygen */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = routeme.doxygen; sourceTree = "<group>"; };
2B5682700F68E36000E8DF40 /* RMOpenAerialMapSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMOpenAerialMapSource.h; sourceTree = "<group>"; };
2B5682710F68E36000E8DF40 /* RMOpenAerialMapSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMOpenAerialMapSource.m; sourceTree = "<group>"; };
2B9B48480F8B0DDB009BA472 /* GTMCALayer+UnitTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMCALayer+UnitTesting.h"; sourceTree = "<group>"; };
2B9B48490F8B0DDB009BA472 /* GTMCALayer+UnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMCALayer+UnitTesting.m"; sourceTree = "<group>"; };
2B9B484A0F8B0DDB009BA472 /* GTMNSObject+UnitTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSObject+UnitTesting.h"; sourceTree = "<group>"; };
2B9B484B0F8B0DDB009BA472 /* GTMNSObject+UnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSObject+UnitTesting.m"; sourceTree = "<group>"; };
2B9B484C0F8B0DDB009BA472 /* GTMUIKit+UnitTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMUIKit+UnitTesting.h"; sourceTree = "<group>"; };
2B9B484D0F8B0DDB009BA472 /* GTMUIKit+UnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMUIKit+UnitTesting.m"; sourceTree = "<group>"; };
2B9B48530F8B0DF7009BA472 /* GTMSystemVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMSystemVersion.h; sourceTree = "<group>"; };
2B9B48540F8B0DF7009BA472 /* GTMSystemVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSystemVersion.m; sourceTree = "<group>"; };
2B9B48590F8B0E47009BA472 /* GTMGarbageCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMGarbageCollection.h; sourceTree = "<group>"; };
2BF306C20F8ABCA4007014EE /* GTMIPhoneUnitTestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMIPhoneUnitTestDelegate.h; sourceTree = "<group>"; };
2BF306C30F8ABCA4007014EE /* GTMIPhoneUnitTestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMIPhoneUnitTestDelegate.m; sourceTree = "<group>"; };
2BF306C40F8ABCA4007014EE /* GTMIPhoneUnitTestMain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMIPhoneUnitTestMain.m; sourceTree = "<group>"; };
... ... @@ -413,6 +426,7 @@
2B246B230F8AE20300A7D55D /* Testing */ = {
isa = PBXGroup;
children = (
2BF306BF0F8ABC35007014EE /* Google Toolbox for Mac (unit testing) */,
2B246AA00F8AD9D400A7D55D /* RouteMeTests.h */,
2B246AA10F8AD9D400A7D55D /* RouteMeTests.m */,
);
... ... @@ -447,9 +461,17 @@
2BF306C40F8ABCA4007014EE /* GTMIPhoneUnitTestMain.m */,
2BF306C50F8ABCA4007014EE /* GTMSenTestCase.h */,
2BF306C60F8ABCA4007014EE /* GTMSenTestCase.m */,
2B9B48530F8B0DF7009BA472 /* GTMSystemVersion.h */,
2B9B48540F8B0DF7009BA472 /* GTMSystemVersion.m */,
2B9B48480F8B0DDB009BA472 /* GTMCALayer+UnitTesting.h */,
2B9B48490F8B0DDB009BA472 /* GTMCALayer+UnitTesting.m */,
2B9B484A0F8B0DDB009BA472 /* GTMNSObject+UnitTesting.h */,
2B9B484B0F8B0DDB009BA472 /* GTMNSObject+UnitTesting.m */,
2B9B484C0F8B0DDB009BA472 /* GTMUIKit+UnitTesting.h */,
2B9B484D0F8B0DDB009BA472 /* GTMUIKit+UnitTesting.m */,
2B9B48590F8B0E47009BA472 /* GTMGarbageCollection.h */,
);
name = "Google Toolbox for Mac (unit testing)";
path = ..;
sourceTree = "<group>";
};
38D8184C0F6F67B90034598B /* Products */ = {
... ... @@ -643,7 +665,6 @@
B83E64B80E80E73F001663B6 /* Renderers */,
B86F26A80E8742ED007A3773 /* Markers and other layers */,
1266929E0EB75BEA00E002D5 /* Configuration */,
2BF306BF0F8ABC35007014EE /* Google Toolbox for Mac (unit testing) */,
B8474C610EB53A01006A0BC1 /* Resources */,
);
path = Map;
... ... @@ -734,9 +755,9 @@
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
2BF3078E0F8AC5C0007014EE /* RM Unit Tests */ = {
2BF3078E0F8AC5C0007014EE /* Unit Tests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2BF307940F8AC5C1007014EE /* Build configuration list for PBXNativeTarget "RM Unit Tests" */;
buildConfigurationList = 2BF307940F8AC5C1007014EE /* Build configuration list for PBXNativeTarget "Unit Tests" */;
buildPhases = (
2BF3078B0F8AC5C0007014EE /* Resources */,
2BF3078C0F8AC5C0007014EE /* Sources */,
... ... @@ -748,7 +769,7 @@
dependencies = (
2BEC606D0F8AC7B3008FB858 /* PBXTargetDependency */,
);
name = "RM Unit Tests";
name = "Unit Tests";
productName = "RM Unit Tests";
productReference = 2BF3078F0F8AC5C0007014EE /* RM Unit Tests.app */;
productType = "com.apple.product-type.application";
... ... @@ -792,7 +813,7 @@
B8C974130E8A19B2007D16AD /* MapView */,
386676D40F6B73AC00C56B17 /* MapView-framework */,
2B2BD43A0F79AA1B00B8B9A7 /* Doxygen-Documentation */,
2BF3078E0F8AC5C0007014EE /* RM Unit Tests */,
2BF3078E0F8AC5C0007014EE /* Unit Tests */,
);
};
/* End PBXProject section */
... ... @@ -908,6 +929,10 @@
2BEC60540F8AC744008FB858 /* RMWebTileImage.m in Sources */,
2BEC60550F8AC745008FB858 /* RMYahooMapSource.m in Sources */,
2B246AA20F8AD9D400A7D55D /* RouteMeTests.m in Sources */,
2B9B484E0F8B0DDB009BA472 /* GTMCALayer+UnitTesting.m in Sources */,
2B9B484F0F8B0DDB009BA472 /* GTMNSObject+UnitTesting.m in Sources */,
2B9B48500F8B0DDB009BA472 /* GTMUIKit+UnitTesting.m in Sources */,
2B9B48550F8B0DF7009BA472 /* GTMSystemVersion.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
... ... @@ -1165,7 +1190,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
2BF307940F8AC5C1007014EE /* Build configuration list for PBXNativeTarget "RM Unit Tests" */ = {
2BF307940F8AC5C1007014EE /* Build configuration list for PBXNativeTarget "Unit Tests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2BF307920F8AC5C0007014EE /* Debug */,
... ...
... ... @@ -7,9 +7,12 @@
//
#import "GTMSenTestCase.h"
@class RMMapView;
/// Unit tests go here. See http://developer.apple.com/tools/unittest.html for guidance.
/// Unit tests go here. See http://developer.apple.com/tools/unittest.html
///and http://code.google.com/p/google-toolbox-for-mac/wiki/iPhoneUnitTesting for guidance.
@interface RouteMeTests : SenTestCase {
RMMapView *mapView;
}
@end
... ...
... ... @@ -23,7 +23,6 @@
- (void)testObjectCreation
{
RMMapView *mapView;
STAssertNotNil((mapView = [[RMMapView alloc] init]), @"mapView alloc/init failed");
STAssertNoThrow([mapView release], @"mapView release failed");
... ...