aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-07-09 21:19:26 +0000
committerGravatar thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-07-09 21:19:26 +0000
commit6ddca07d6c48b0226550b6ff3e01a177b6afd6a5 (patch)
treeadbd23d96eacb322eb47a1c8c525bc4d2536dfc3
parent098f7782e376e818b131a8f9a8222056c63d51ee (diff)
- Added GTMSignalHandler for simple signal handling (via kqueue/runloop).
- Fixed up GTMIPhoneUnitTestDelegate to be pickier about which tests it runs - Added GTMNSString+URLArguments to GTMiPhone - Added GTMHTTPFetcher and GTMHTTPServer to GTMiPhone - Made sure that build would work with iPhone device attached, and that all tests run directly on the phone. - Added GTMValidatingContainers which are a set of mutable container classes that allow you to have a selector on a target that is called to verify that the objects being put into the container are valid. This can be controlled at compile time so that you don't take the performance hit in a release build. - Added GTMPath, which represents an existing absolute path on the file system. It also makes it very easy to contruct new paths in the file system as well as whole directory hierarchies. - Added GTMNSString+Replace for a common replacement need. - Added NSString+FindFolder for two commen helpers for building paths to common locations. - Added GTMLargeTypeWindow for doing display windows similar to Address Book Large Type display for phone numbers.
-rw-r--r--AppKit/GTMLargeTypeWindow.h51
-rw-r--r--AppKit/GTMLargeTypeWindow.m276
-rw-r--r--AppKit/GTMLargeTypeWindowTest.m146
-rw-r--r--AppKit/TestData/GTMLargeTypeWindowImageTest.gtmUTState49
-rw-r--r--AppKit/TestData/GTMLargeTypeWindowLongTextTest.gtmUTState26
-rw-r--r--AppKit/TestData/GTMLargeTypeWindowMediumTextTest.gtmUTState26
-rw-r--r--AppKit/TestData/GTMLargeTypeWindowShortTextTest.gtmUTState26
-rwxr-xr-xBuildScripts/BuildAllSDKs.sh66
-rw-r--r--DebugUtils/GTMMethodCheck.h2
-rw-r--r--Foundation/GTMGeometryUtils.h4
-rw-r--r--Foundation/GTMGeometryUtilsTest.m2
-rw-r--r--Foundation/GTMHTTPServer.h4
-rw-r--r--Foundation/GTMNSFileManager+Path.h3
-rw-r--r--Foundation/GTMNSString+FindFolder.h55
-rw-r--r--Foundation/GTMNSString+FindFolder.m79
-rw-r--r--Foundation/GTMNSString+FindFolderTest.m77
-rw-r--r--Foundation/GTMNSString+Replace.h40
-rw-r--r--Foundation/GTMNSString+Replace.m48
-rw-r--r--Foundation/GTMNSString+ReplaceTest.m55
-rw-r--r--Foundation/GTMPath.h132
-rw-r--r--Foundation/GTMPath.m156
-rw-r--r--Foundation/GTMPathTest.m231
-rw-r--r--Foundation/GTMRegex.m2
-rw-r--r--Foundation/GTMSignalHandler.h75
-rw-r--r--Foundation/GTMSignalHandler.m209
-rw-r--r--Foundation/GTMSignalHandlerTest.m143
-rw-r--r--Foundation/GTMStackTraceTest.m1
-rw-r--r--Foundation/GTMValidatingContainers.h195
-rw-r--r--Foundation/GTMValidatingContainers.m476
-rw-r--r--Foundation/GTMValidatingContainersTest.m367
-rw-r--r--GTM.xcodeproj/project.pbxproj102
-rw-r--r--GTMDefines.h7
-rw-r--r--GTMiPhone.xcodeproj/project.pbxproj62
-rw-r--r--ReleaseNotes.txt33
-rw-r--r--UnitTesting/GTMIPhoneUnitTestDelegate.h2
-rw-r--r--UnitTesting/GTMIPhoneUnitTestDelegate.m37
-rw-r--r--UnitTesting/GTMNSObject+UnitTesting.m9
-rwxr-xr-xUnitTesting/RunIPhoneUnitTest.sh17
38 files changed, 3240 insertions, 51 deletions
diff --git a/AppKit/GTMLargeTypeWindow.h b/AppKit/GTMLargeTypeWindow.h
new file mode 100644
index 0000000..9bb6b85
--- /dev/null
+++ b/AppKit/GTMLargeTypeWindow.h
@@ -0,0 +1,51 @@
+//
+// GTMLargeTypeWindow.h
+//
+// Copyright 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>
+
+// GTMLargeTypeWindow displays a block of text in a large panel window much
+// like Address Book displays phone numbers. It will also display an image
+// so you can pop up "alerts" similar to the way BBEdit does when you attempt
+// to do a find and find nothing. It will fade in and out appropriately when
+// ordered forward or backward.
+// A typical fire-and-forget type usage would be:
+// GTMLargeTypeWindow *window
+// = [[[GTMLargeTypeWindow alloc] initWithString:@"Foo"] autorelease];
+// [window makeKeyAndOrderFront:nil];
+
+// Amount of time to fade the window in or out
+const NSTimeInterval kGTMLargeTypeWindowFadeTime;
+
+@interface GTMLargeTypeWindow : NSPanel
+// Creates a display window with |string| displayed.
+// Formats |string| as best as possible to fill the screen.
+- (id)initWithString:(NSString *)string;
+// Creates a display window with |attrString| displayed.
+// Expects you to format it as you want it to appear.
+- (id)initWithAttributedString:(NSAttributedString *)attrString;
+// Creates a display window with |image| displayed.
+// Make sure you set the image size to what you want
+- (id)initWithImage:(NSImage*)image;
+// Creates a display window with |view| displayed.
+- (id)initWithContentView:(NSView *)view;
+
+// Copy the text out of the window if appropriate. This is normally called
+// as part of the responder chain so that the user can copy the displayed text
+// using cmd-c.
+- (void)copy:(id)sender;
+@end
diff --git a/AppKit/GTMLargeTypeWindow.m b/AppKit/GTMLargeTypeWindow.m
new file mode 100644
index 0000000..d84ce42
--- /dev/null
+++ b/AppKit/GTMLargeTypeWindow.m
@@ -0,0 +1,276 @@
+//
+// GTMLargeTypeWindow.m
+//
+// Copyright 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 "GTMLargeTypeWindow.h"
+#import "GTMGeometryUtils.h"
+#import "GTMNSBezierPath+RoundRect.h"
+
+
+// Amount of time to fade the window in or out
+const NSTimeInterval kGTMLargeTypeWindowFadeTime = 0.333;
+
+// How far to inset the text from the edge of the window
+static const CGFloat kEdgeInset = 16.0;
+
+// Give us an alpha value for our backing window
+static const CGFloat kTwoThirdsAlpha = 0.66;
+
+@interface GTMLargeTypeBackgroundView : NSView
+@end
+
+@interface GTMLargeTypeWindow (GTMLargeTypeWindowPrivate)
++ (CGFloat)displayWidth;
+- (void)startFadeInAnimation;
+@end
+
+@implementation GTMLargeTypeWindow
+- (id)initWithString:(NSString *)string {
+ if ([string length] == 0) {
+ _GTMDevLog(@"GTMLargeTypeWindow got an empty string");
+ [self release];
+ return nil;
+ }
+ CGFloat displayWidth = [[self class] displayWidth];
+ NSMutableAttributedString *attrString
+ = [[[NSMutableAttributedString alloc] initWithString:string] autorelease];
+
+ // Try and find a size that fits without iterating too many times.
+ // We start going 50 pixels at a time, then 10, then 1
+ int size = -26; // start at 24 (-26 + 50)
+ int offsets[] = { 50, 10, 1 };
+ for (size_t i = 0; i < sizeof(offsets) / sizeof(int); ++i) {
+ for(size = size + offsets[i]; size >= 24 && size < 300; size += offsets[i]) {
+ NSFont *textFont = [NSFont boldSystemFontOfSize:size];
+ NSDictionary *fontAttr
+ = [NSDictionary dictionaryWithObject:textFont
+ forKey:NSFontAttributeName];
+ NSSize textSize = [string sizeWithAttributes:fontAttr];
+ if (textSize.width > displayWidth) {
+ size = size - offsets[i];
+ break;
+ }
+ }
+ }
+
+ // Bounds check our values
+ if (size > 300) {
+ size = 300;
+ } else if (size < 24) {
+ size = 24;
+ }
+
+ NSRange fullRange = NSMakeRange(0, [string length]);
+ [attrString addAttribute:NSFontAttributeName
+ value:[NSFont boldSystemFontOfSize:size]
+ range:fullRange];
+ [attrString addAttribute:NSForegroundColorAttributeName
+ value:[NSColor whiteColor]
+ range:fullRange];
+
+ NSMutableParagraphStyle *style
+ = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
+ [style setAlignment:NSCenterTextAlignment];
+ [attrString addAttribute:NSParagraphStyleAttributeName
+ value:style
+ range:fullRange];
+
+ NSShadow *textShadow = [[[NSShadow alloc] init] autorelease];
+ [textShadow setShadowOffset:NSMakeSize( 5, -5 )];
+ [textShadow setShadowBlurRadius:10];
+ [textShadow setShadowColor:[NSColor colorWithCalibratedWhite:0
+ alpha:kTwoThirdsAlpha]];
+ [attrString addAttribute:NSShadowAttributeName
+ value:textShadow
+ range:fullRange];
+ return [self initWithAttributedString:attrString];
+}
+
+- (id)initWithAttributedString:(NSAttributedString *)attrString {
+ if ([attrString length] == 0) {
+ _GTMDevLog(@"GTMLargeTypeWindow got an empty string");
+ [self release];
+ return nil;
+ }
+ CGFloat displayWidth =[[self class] displayWidth];
+ NSRect frame = NSMakeRect(0, 0, displayWidth, 0);
+ NSTextView *textView = [[[NSTextView alloc] initWithFrame:frame] autorelease];
+ [textView setEditable:NO];
+ [textView setSelectable:NO];
+ [textView setDrawsBackground:NO];
+ [[textView textStorage] setAttributedString:attrString];
+ [textView sizeToFit];
+
+ return [self initWithContentView:textView];
+}
+
+- (id)initWithImage:(NSImage*)image {
+ if (!image) {
+ _GTMDevLog(@"GTMLargeTypeWindow got an empty image");
+ [self release];
+ return nil;
+ }
+ NSRect rect = GTMNSRectOfSize([image size]);
+ NSImageView *imageView
+ = [[[NSImageView alloc] initWithFrame:rect] autorelease];
+ [imageView setImage:image];
+ return [self initWithContentView:imageView];
+}
+
+- (id)initWithContentView:(NSView *)view {
+ NSRect bounds = NSZeroRect;
+ if (view) {
+ bounds = [view bounds];
+ }
+ if (bounds.size.height <= 0 || bounds.size.width <= 0) {
+ _GTMDevLog(@"GTMLargeTypeWindow got an empty view");
+ [self release];
+ return nil;
+ }
+ NSRect screenRect = [[NSScreen mainScreen] frame];
+ NSRect windowRect = GTMNSAlignRectangles([view frame],
+ screenRect,
+ GTMRectAlignCenter);
+ windowRect = NSInsetRect(windowRect, -kEdgeInset, -kEdgeInset);
+ windowRect = NSIntegralRect(windowRect);
+ NSUInteger mask = NSBorderlessWindowMask | NSNonactivatingPanelMask;
+ self = [super initWithContentRect:windowRect
+ styleMask:mask
+ backing:NSBackingStoreBuffered
+ defer:NO];
+ if (self) {
+ [self setFrame:GTMNSAlignRectangles(windowRect,
+ screenRect,
+ GTMRectAlignCenter)
+ display:YES];
+ [self setBackgroundColor:[NSColor clearColor]];
+ [self setOpaque:NO];
+ [self setLevel:NSFloatingWindowLevel];
+ [self setHidesOnDeactivate:NO];
+
+ GTMLargeTypeBackgroundView *content
+ = [[[GTMLargeTypeBackgroundView alloc] initWithFrame:NSZeroRect]
+ autorelease];
+ [self setHasShadow:YES];
+ [self setContentView:content];
+ [self setAlphaValue:0];
+ [self setIgnoresMouseEvents:YES];
+ [view setFrame:GTMNSAlignRectangles([view frame],
+ [content frame],
+ GTMRectAlignCenter)];
+ [content addSubview:view];
+ [self setInitialFirstResponder:view];
+ }
+ return self;
+}
+
+- (void)copy:(id)sender {
+ id firstResponder = [self initialFirstResponder];
+ if ([firstResponder respondsToSelector:@selector(textStorage)]) {
+ NSPasteboard *pb = [NSPasteboard generalPasteboard];
+ [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self];
+ [pb setString:[[firstResponder textStorage] string]
+ forType:NSStringPboardType];
+ }
+}
+
+- (BOOL)canBecomeKeyWindow {
+ return YES;
+}
+
+- (void)keyDown:(NSEvent *)theEvent {
+ [self close];
+}
+
+- (void)resignKeyWindow {
+ [super resignKeyWindow];
+ if([self isVisible]) {
+ [self close];
+ }
+}
+
+- (void)makeKeyAndOrderFront:(id)sender {
+ [self startFadeInAnimation];
+ [super makeKeyAndOrderFront:sender];
+}
+
+- (void)orderFront:(id)sender {
+ [self startFadeInAnimation];
+ [super orderFront:sender];
+}
+
+- (void)orderOut:(id)sender {
+ NSDictionary *fadeOut = [NSDictionary dictionaryWithObjectsAndKeys:
+ self, NSViewAnimationTargetKey,
+ NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey,
+ nil];
+ NSArray *animation = [NSArray arrayWithObject:fadeOut];
+ NSViewAnimation *viewAnim
+ = [[[NSViewAnimation alloc] initWithViewAnimations:animation] autorelease];
+ [viewAnim setDuration:kGTMLargeTypeWindowFadeTime];
+ [viewAnim startAnimation];
+ NSDate *fadeOutDate
+ = [NSDate dateWithTimeIntervalSinceNow:kGTMLargeTypeWindowFadeTime];
+ // We have a local run loop because if this is called as part of a close
+ // our window will be hidden immediately before it has a chance to fade.
+ [[NSRunLoop currentRunLoop] runUntilDate:fadeOutDate];
+}
+
++ (CGFloat)displayWidth {
+ NSRect screenRect = [[NSScreen mainScreen] frame];
+ // This is just a rough calculation to make us fill a good proportion
+ // of the main screen.
+ return NSWidth( screenRect ) * 11.0 / 12.0 - 2.0 * kEdgeInset;
+}
+
+- (void)startFadeInAnimation {
+ // If we aren't already fully visible, start fading us in.
+ if ([self alphaValue] < 1.0) {
+ NSDictionary *fadeIn = [NSDictionary dictionaryWithObjectsAndKeys:
+ self, NSViewAnimationTargetKey,
+ NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
+ nil];
+ NSArray *animation = [NSArray arrayWithObject:fadeIn];
+ NSViewAnimation *viewAnim
+ = [[[NSViewAnimation alloc] initWithViewAnimations:animation] autorelease];
+ [viewAnim setDuration:kGTMLargeTypeWindowFadeTime];
+ [viewAnim startAnimation];
+ }
+}
+@end
+
+@implementation GTMLargeTypeBackgroundView
+
+- (BOOL)isOpaque {
+ return NO;
+}
+
+- (void)drawRect:(NSRect)rect {
+ [[NSColor colorWithDeviceWhite:0 alpha:kTwoThirdsAlpha] set];
+ rect = [self bounds];
+
+ NSBezierPath *roundRect = [NSBezierPath bezierPath];
+ CGFloat minRadius = MIN(NSWidth(rect), NSHeight(rect)) * 0.5f;
+
+ [roundRect gtm_appendBezierPathWithRoundRect:rect
+ cornerRadius:MIN(minRadius, 32)];
+ [roundRect addClip];
+ NSRectFill(rect);
+ [super drawRect:rect];
+}
+
+@end
diff --git a/AppKit/GTMLargeTypeWindowTest.m b/AppKit/GTMLargeTypeWindowTest.m
new file mode 100644
index 0000000..b4a9fe4
--- /dev/null
+++ b/AppKit/GTMLargeTypeWindowTest.m
@@ -0,0 +1,146 @@
+//
+// GTMLargeTypeWindowTest.m
+//
+// 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 "GTMSenTestCase.h"
+#import "GTMLargeTypeWindow.h"
+#import "GTMNSObject+UnitTesting.h"
+#import "GTMUnitTestDevLog.h"
+
+NSString *const kLongTextBlock =
+ @"`Twas brillig, and the slithy toves "
+ "Did gyre and gimble in the wabe: "
+ "all mimsy were the borogoves, "
+ "and the mome raths outgrabe. "
+ "Beware the Jabberwock, my son! "
+ "The jaws that bite, the claws that catch! "
+ "Beware the Jubjub bird, and shun "
+ "the frumious Bandersnatch! "
+ "He took his vorpal sword in hand: "
+ "long time the manxome foe he sought -- "
+ "so rested he by the Tumtum tree, "
+ "and stood awhile in thought. "
+ "And, as in uffish thought he stood, "
+ "the Jabberwock, with eyes of flame, "
+ "came whiffling through the tulgey wood, "
+ "and burbled as it came! "
+ "One, two! One, two! And through and through "
+ "the vorpal blade went snicker-snack! "
+ "He left it dead, and with its head "
+ "he went galumphing back. "
+ "And, has thou slain the Jabberwock? "
+ "Come to my arms, my beamish boy! "
+ "O frabjous day! Callooh! Callay! "
+ "He chortled in his joy.";
+
+NSString *const kMediumTextBlock = @"For the Snark was a Boojum, you see.";
+
+@interface GTMLargeTypeWindowTest : GTMTestCase
+@end
+
+@implementation GTMLargeTypeWindowTest
+- (void)testLargeTypeWindow {
+ [GTMUnitTestDevLog expectString:@"GTMLargeTypeWindow got an empty string"];
+ GTMLargeTypeWindow *window = [[[GTMLargeTypeWindow alloc]
+ initWithString:@""] autorelease];
+ STAssertNil(window, nil);
+
+ [GTMUnitTestDevLog expectString:@"GTMLargeTypeWindow got an empty string"];
+ window = [[[GTMLargeTypeWindow alloc] initWithString:nil] autorelease];
+ STAssertNil(window, nil);
+
+ [GTMUnitTestDevLog expectString:@"GTMLargeTypeWindow got an empty string"];
+ NSAttributedString *attrString = [[[NSAttributedString alloc]
+ initWithString:@""] autorelease];
+ window = [[[GTMLargeTypeWindow alloc]
+ initWithAttributedString:attrString] autorelease];
+ STAssertNil(window, nil);
+
+ [GTMUnitTestDevLog expectString:@"GTMLargeTypeWindow got an empty string"];
+ window = [[[GTMLargeTypeWindow alloc]
+ initWithAttributedString:nil] autorelease];
+ STAssertNil(window, nil);
+
+ [GTMUnitTestDevLog expectString:@"GTMLargeTypeWindow got an empty view"];
+ window = [[[GTMLargeTypeWindow alloc] initWithContentView:nil] autorelease];
+ STAssertNil(window, nil);
+
+ [GTMUnitTestDevLog expectString:@"GTMLargeTypeWindow got an empty image"];
+ window = [[[GTMLargeTypeWindow alloc] initWithImage:nil] autorelease];
+ STAssertNil(window, nil);
+
+ window = [[[GTMLargeTypeWindow alloc]
+ initWithString:kMediumTextBlock] autorelease];
+ STAssertNotNil(window, nil);
+ STAssertTrue([window canBecomeKeyWindow], nil);
+ [window makeKeyAndOrderFront:nil];
+ NSDate *endDate
+ = [NSDate dateWithTimeIntervalSinceNow:kGTMLargeTypeWindowFadeTime];
+ [[NSRunLoop currentRunLoop] runUntilDate:endDate];
+ GTMAssertObjectStateEqualToStateNamed(window,
+ @"GTMLargeTypeWindowMediumTextTest",
+ nil);
+ [window copy:nil];
+ NSPasteboard *pb = [NSPasteboard generalPasteboard];
+ NSString *pbString = [pb stringForType:NSStringPboardType];
+ STAssertEqualObjects(pbString, kMediumTextBlock, nil);
+ [window keyDown:nil];
+
+ window = [[[GTMLargeTypeWindow alloc] initWithString:@"Short"] autorelease];
+ STAssertNotNil(window, nil);
+ STAssertTrue([window canBecomeKeyWindow], nil);
+ [window makeKeyAndOrderFront:nil];
+ endDate = [NSDate dateWithTimeIntervalSinceNow:kGTMLargeTypeWindowFadeTime];
+ [[NSRunLoop currentRunLoop] runUntilDate:endDate];
+ GTMAssertObjectStateEqualToStateNamed(window,
+ @"GTMLargeTypeWindowShortTextTest",
+ nil);
+ [window copy:nil];
+ pbString = [pb stringForType:NSStringPboardType];
+ STAssertEqualObjects(pbString, @"Short", nil);
+ [window resignKeyWindow];
+
+ window = [[[GTMLargeTypeWindow alloc]
+ initWithString:kLongTextBlock] autorelease];
+ STAssertNotNil(window, nil);
+ [window orderFront:nil];
+ endDate = [NSDate dateWithTimeIntervalSinceNow:kGTMLargeTypeWindowFadeTime];
+ [[NSRunLoop currentRunLoop] runUntilDate:endDate];
+ // Can't do state for long text as it will wrap differently on different
+ // sized screens.
+ GTMAssertObjectStateEqualToStateNamed(window,
+ @"GTMLargeTypeWindowLongTextTest",
+ nil);
+ [window keyDown:nil];
+
+ NSImage *image = [NSApp applicationIconImage];
+ window = [[[GTMLargeTypeWindow alloc] initWithImage:image] autorelease];
+ STAssertNotNil(window, nil);
+ [window makeKeyAndOrderFront:nil];
+ endDate = [NSDate dateWithTimeIntervalSinceNow:kGTMLargeTypeWindowFadeTime];
+ [[NSRunLoop currentRunLoop] runUntilDate:endDate];
+ GTMAssertObjectStateEqualToStateNamed(window,
+ @"GTMLargeTypeWindowImageTest",
+ nil);
+ [window copy:nil];
+ // Pasteboard should not change for an image
+ pbString = [pb stringForType:NSStringPboardType];
+ STAssertEqualObjects(pbString, @"Short", nil);
+ [window resignKeyWindow];
+}
+
+@end
diff --git a/AppKit/TestData/GTMLargeTypeWindowImageTest.gtmUTState b/AppKit/TestData/GTMLargeTypeWindowImageTest.gtmUTState
new file mode 100644
index 0000000..7a7512c
--- /dev/null
+++ b/AppKit/TestData/GTMLargeTypeWindowImageTest.gtmUTState
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>$GTMArchive</key>
+ <string>GTMUnitTestingArchive</string>
+ <key>$GTMVersion</key>
+ <integer>1</integer>
+ <key>WindowContent</key>
+ <dict>
+ <key>ViewIsHidden</key>
+ <false/>
+ <key>ViewSubView 0</key>
+ <dict>
+ <key>ControlIsEnabled</key>
+ <true/>
+ <key>ControlSelectedCell</key>
+ <dict>
+ <key>CellState</key>
+ <integer>0</integer>
+ <key>CellTag</key>
+ <integer>-1</integer>
+ <key>CellValue</key>
+ <dict>
+ <key>ImageSize</key>
+ <string>{128, 128}</string>
+ </dict>
+ </dict>
+ <key>ControlTag</key>
+ <integer>0</integer>
+ <key>ControlType</key>
+ <string>NSImageView</string>
+ <key>ControlValue</key>
+ <dict>
+ <key>ImageSize</key>
+ <string>{128, 128}</string>
+ </dict>
+ <key>ViewIsHidden</key>
+ <false/>
+ </dict>
+ </dict>
+ <key>WindowIsMain</key>
+ <false/>
+ <key>WindowIsVisible</key>
+ <true/>
+ <key>WindowTitle</key>
+ <string></string>
+</dict>
+</plist>
diff --git a/AppKit/TestData/GTMLargeTypeWindowLongTextTest.gtmUTState b/AppKit/TestData/GTMLargeTypeWindowLongTextTest.gtmUTState
new file mode 100644
index 0000000..e4687f5
--- /dev/null
+++ b/AppKit/TestData/GTMLargeTypeWindowLongTextTest.gtmUTState
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>$GTMArchive</key>
+ <string>GTMUnitTestingArchive</string>
+ <key>$GTMVersion</key>
+ <integer>1</integer>
+ <key>WindowContent</key>
+ <dict>
+ <key>ViewIsHidden</key>
+ <false/>
+ <key>ViewSubView 0</key>
+ <dict>
+ <key>ViewIsHidden</key>
+ <false/>
+ </dict>
+ </dict>
+ <key>WindowIsMain</key>
+ <false/>
+ <key>WindowIsVisible</key>
+ <true/>
+ <key>WindowTitle</key>
+ <string></string>
+</dict>
+</plist>
diff --git a/AppKit/TestData/GTMLargeTypeWindowMediumTextTest.gtmUTState b/AppKit/TestData/GTMLargeTypeWindowMediumTextTest.gtmUTState
new file mode 100644
index 0000000..e4687f5
--- /dev/null
+++ b/AppKit/TestData/GTMLargeTypeWindowMediumTextTest.gtmUTState
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>$GTMArchive</key>
+ <string>GTMUnitTestingArchive</string>
+ <key>$GTMVersion</key>
+ <integer>1</integer>
+ <key>WindowContent</key>
+ <dict>
+ <key>ViewIsHidden</key>
+ <false/>
+ <key>ViewSubView 0</key>
+ <dict>
+ <key>ViewIsHidden</key>
+ <false/>
+ </dict>
+ </dict>
+ <key>WindowIsMain</key>
+ <false/>
+ <key>WindowIsVisible</key>
+ <true/>
+ <key>WindowTitle</key>
+ <string></string>
+</dict>
+</plist>
diff --git a/AppKit/TestData/GTMLargeTypeWindowShortTextTest.gtmUTState b/AppKit/TestData/GTMLargeTypeWindowShortTextTest.gtmUTState
new file mode 100644
index 0000000..e4687f5
--- /dev/null
+++ b/AppKit/TestData/GTMLargeTypeWindowShortTextTest.gtmUTState
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>$GTMArchive</key>
+ <string>GTMUnitTestingArchive</string>
+ <key>$GTMVersion</key>
+ <integer>1</integer>
+ <key>WindowContent</key>
+ <dict>
+ <key>ViewIsHidden</key>
+ <false/>
+ <key>ViewSubView 0</key>
+ <dict>
+ <key>ViewIsHidden</key>
+ <false/>
+ </dict>
+ </dict>
+ <key>WindowIsMain</key>
+ <false/>
+ <key>WindowIsVisible</key>
+ <true/>
+ <key>WindowTitle</key>
+ <string></string>
+</dict>
+</plist>
diff --git a/BuildScripts/BuildAllSDKs.sh b/BuildScripts/BuildAllSDKs.sh
index 0c9bbe8..0928dcd 100755
--- a/BuildScripts/BuildAllSDKs.sh
+++ b/BuildScripts/BuildAllSDKs.sh
@@ -2,8 +2,7 @@
# BuildAllSDKs.sh
#
# This script builds both the Tiger and Leopard versions of the requested
-# target in the current basic config (debug, release, debug-gcov). This script
-# should be run from the same directory as the GTM Xcode project file.
+# target in the current basic config (debug, release, debug-gcov).
#
# Copyright 2006-2008 Google Inc.
#
@@ -20,8 +19,9 @@
# the License.
PROJECT_TARGET="$1"
+STARTING_TARGET="${TARGET_NAME}"
+SCRIPT_APP="${TMPDIR}DoBuild.app"
-XCODEBUILD="${DEVELOPER_BIN_DIR}/xcodebuild"
REQUESTED_BUILD_STYLE=$(echo "${BUILD_STYLE}" | sed "s/.*OrLater-\(.*\)/\1/")
# See if we were told to clean instead of build.
PROJECT_ACTION="build"
@@ -29,26 +29,42 @@ if [ "${ACTION}" == "clean" ]; then
PROJECT_ACTION="clean"
fi
-# helper for doing a build
-function doIt {
- local myProject=$1
- local myTarget=$2
- local myConfig=$3
- echo "note: Starting ${PROJECT_ACTION} of ${myTarget} from ${myProject} in ${myConfig}"
- ${XCODEBUILD} -project "${myProject}" \
- -target "${myTarget}" \
- -configuration "${myConfig}" \
- "${PROJECT_ACTION}"
- buildResult=$?
- if [ $buildResult -ne 0 ]; then
- echo "Error: ** ${PROJECT_ACTION} Failed **"
- exit $buildResult
- fi
- echo "note: Done ${PROJECT_ACTION}"
-}
+# build up our AppleScript
+OUR_BUILD_SCRIPT="on run
+ tell application \"Xcode\"
+ activate
+ tell project \"GTM\"
+ -- wait for build to finish
+ set x to 0
+ repeat while currently building
+ delay 0.5
+ set x to x + 1
+ if x > 6 then
+ display alert \"GTM is still building, can't start.\"
+ return
+ end if
+ end repeat
+ -- do the build
+ with timeout of 9999 seconds
+ set active target to target \"${PROJECT_TARGET}\"
+ set buildResult to ${PROJECT_ACTION} using build configuration \"TigerOrLater-${REQUESTED_BUILD_STYLE}\"
+ if buildResult is not equal to \"Build succeeded\" then
+ set active target to target \"${STARTING_TARGET}\"
+ return
+ end if
+ -- do not need the result since we are not doing another build
+ ${PROJECT_ACTION} using build configuration \"LeopardOrLater-${REQUESTED_BUILD_STYLE}\"
+ set active target to target \"${STARTING_TARGET}\"
+ end timeout
+ end tell
+ end tell
+end run"
-# now build tiger and then leopard
-doIt GTM.xcodeproj "${PROJECT_TARGET}" "TigerOrLater-${REQUESTED_BUILD_STYLE}"
-doIt GTM.xcodeproj "${PROJECT_TARGET}" "LeopardOrLater-${REQUESTED_BUILD_STYLE}"
-
-# TODO(iphone if right tool chain?)
+# Xcode won't actually let us spawn this and run it w/ osascript because it
+# watches and waits for everything we have spawned to exit before the build is
+# considered done, so instead we compile this to a script app, and then use
+# open to invoke it, there by escaping our little sandbox.
+# xcode defeats this: ( echo "${OUR_BUILD_SCRIPT}" | osascript - & )
+rm -rf "${SCRIPT_APP}"
+echo "${OUR_BUILD_SCRIPT}" | osacompile -o "${SCRIPT_APP}" -x
+open "${SCRIPT_APP}"
diff --git a/DebugUtils/GTMMethodCheck.h b/DebugUtils/GTMMethodCheck.h
index 0915c0b..7a6fe9f 100644
--- a/DebugUtils/GTMMethodCheck.h
+++ b/DebugUtils/GTMMethodCheck.h
@@ -82,7 +82,7 @@ __attribute__ ((constructor, visibility("hidden"))) void GTMMethodCheckMethodChe
#else // !DEBUG
-// Do nothing in debug
+// Do nothing in release.
#define GTM_METHOD_CHECK(class, method)
#endif // DEBUG
diff --git a/Foundation/GTMGeometryUtils.h b/Foundation/GTMGeometryUtils.h
index 7d54cf2..e8a078c 100644
--- a/Foundation/GTMGeometryUtils.h
+++ b/Foundation/GTMGeometryUtils.h
@@ -180,7 +180,7 @@ CG_INLINE CGFloat GTMCGDistanceBetweenPoints(CGPoint pt1, CGPoint pt2) {
#endif
}
-#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
+#if !GTM_IPHONE_SDK
// iPhone does not have NSTypes defined, only CGTypes. So no NSRect, NSPoint etc.
#pragma mark -
@@ -429,4 +429,4 @@ CG_INLINE CGFloat GTMNSDistanceBetweenPoints(NSPoint pt1, NSPoint pt2) {
GTMNSPointToCGPoint(pt2));
}
-#endif // (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
+#endif // !GTM_IPHONE_SDK
diff --git a/Foundation/GTMGeometryUtilsTest.m b/Foundation/GTMGeometryUtilsTest.m
index 8a81f0d..ca817f8 100644
--- a/Foundation/GTMGeometryUtilsTest.m
+++ b/Foundation/GTMGeometryUtilsTest.m
@@ -179,7 +179,7 @@
for (size_t i = 0; i < sizeof(tests) / sizeof(Test); ++i) {
CGRect result = GTMCGScaleRectangleToSize(rect, tests[i].size_,
GTMScaleProportionally);
- STAssertEquals(result, GTMCGRectOfSize(tests[i].newSize_), @"failed on test %z", i);
+ STAssertEquals(result, GTMCGRectOfSize(tests[i].newSize_), @"failed on test %zd", i);
}
CGRect result = GTMCGScaleRectangleToSize(CGRectZero, tests[0].size_,
diff --git a/Foundation/GTMHTTPServer.h b/Foundation/GTMHTTPServer.h
index 70e3f78..c70178c 100644
--- a/Foundation/GTMHTTPServer.h
+++ b/Foundation/GTMHTTPServer.h
@@ -31,6 +31,10 @@
#import <Foundation/Foundation.h>
#import "GTMDefines.h"
+#if GTM_IPHONE_SDK
+#import <CFNetwork/CFNetwork.h>
+#endif // GTM_IPHONE_SDK
+
// Global contants needed for errors from start
#undef _EXTERN
diff --git a/Foundation/GTMNSFileManager+Path.h b/Foundation/GTMNSFileManager+Path.h
index 95ba41e..2ed6888 100644
--- a/Foundation/GTMNSFileManager+Path.h
+++ b/Foundation/GTMNSFileManager+Path.h
@@ -31,6 +31,9 @@
///
/// If you are building for 10.5 or later, you should just use the new api:
/// createDirectoryAtPath:withIntermediateDirectories:attributes:error:
+///
+/// Also if you need more control over the creation of paths and their
+/// attributes, look into using GTMPath.
///
/// Args:
/// path - the path of the directory to create.
diff --git a/Foundation/GTMNSString+FindFolder.h b/Foundation/GTMNSString+FindFolder.h
new file mode 100644
index 0000000..1b4d362
--- /dev/null
+++ b/Foundation/GTMNSString+FindFolder.h
@@ -0,0 +1,55 @@
+//
+// GTMNSString+FindFolder.h
+//
+// 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 <Foundation/Foundation.h>
+
+@interface NSString (GTMStringFindFolderAdditions)
+
+// Create a path to a folder located with FindFolder
+//
+// Args:
+// theFolderType: one of the folder types in Folders.h
+// (kPreferencesFolderType, etc)
+// theDomain: one of the domains in Folders.h (kLocalDomain, kUserDomain, etc)
+// doCreate: create the folder if it does not already exist
+//
+// Returns:
+// full path to folder, or nil if the folder doesn't exist or can't be created
+//
++ (NSString *)gtm_stringWithPathForFolder:(OSType)theFolderType
+ inDomain:(short)theDomain
+ doCreate:(BOOL)doCreate;
+
+// Create a path to a folder inside a folder located with FindFolder
+//
+// Args:
+// theFolderType: one of the folder types in Folders.h
+// (kPreferencesFolderType, etc)
+// subfolderName: name of directory inside the Apple folder to be located or created
+// theDomain: one of the domains in Folders.h (kLocalDomain, kUserDomain, etc)
+// doCreate: create the folder if it does not already exist
+//
+// Returns:
+// full path to subdirectory, or nil if the folder doesn't exist or can't be created
+//
++ (NSString *)gtm_stringWithPathForFolder:(OSType)theFolderType
+ subfolderName:(NSString *)subfolderName
+ inDomain:(short)theDomain
+ doCreate:(BOOL)doCreate;
+
+@end
diff --git a/Foundation/GTMNSString+FindFolder.m b/Foundation/GTMNSString+FindFolder.m
new file mode 100644
index 0000000..7d2f3b5
--- /dev/null
+++ b/Foundation/GTMNSString+FindFolder.m
@@ -0,0 +1,79 @@
+//
+// GTMNSString+FindFolder.m
+//
+// 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 "GTMNSString+FindFolder.h"
+#import "GTMGarbageCollection.h"
+
+@implementation NSString (GTMStringFindFolderAdditions)
+
++ (NSString *)gtm_stringWithPathForFolder:(OSType)theFolderType
+ inDomain:(short)theDomain
+ doCreate:(BOOL)doCreate {
+
+ NSString* folderPath = nil;
+ FSRef folderRef;
+
+ OSErr err = FSFindFolder(theDomain, theFolderType, doCreate, &folderRef);
+ if (err == noErr) {
+
+ CFURLRef folderURL = CFURLCreateFromFSRef(kCFAllocatorSystemDefault, &folderRef);
+ if (folderURL) {
+
+ folderPath = GTMNSMakeCollectable(CFURLCopyFileSystemPath(folderURL, kCFURLPOSIXPathStyle));
+ [folderPath autorelease];
+
+ CFRelease(folderURL);
+ }
+ }
+ return folderPath;
+}
+
++ (NSString *)gtm_stringWithPathForFolder:(OSType)theFolderType
+ subfolderName:(NSString *)subfolderName
+ inDomain:(short)theDomain
+ doCreate:(BOOL)doCreate {
+ NSString *resultPath = nil;
+ NSString *subdirPath = nil;
+ NSString *parentFolderPath = [self gtm_stringWithPathForFolder:theFolderType
+ inDomain:theDomain
+ doCreate:doCreate];
+ if (parentFolderPath) {
+
+ // find the path to the subdirectory
+ subdirPath = [parentFolderPath stringByAppendingPathComponent:subfolderName];
+
+ NSFileManager* fileMgr = [NSFileManager defaultManager];
+ BOOL isDir = NO;
+ if ([fileMgr fileExistsAtPath:subdirPath isDirectory:&isDir] && isDir) {
+ // it already exists
+ resultPath = subdirPath;
+ } else if (doCreate) {
+
+ // create the subdirectory with the parent folder's attributes
+ NSDictionary* attrs = [fileMgr fileAttributesAtPath:parentFolderPath
+ traverseLink:YES];
+ if ([fileMgr createDirectoryAtPath:subdirPath
+ attributes:attrs]) {
+ resultPath = subdirPath;
+ }
+ }
+ }
+ return resultPath;
+}
+
+@end
diff --git a/Foundation/GTMNSString+FindFolderTest.m b/Foundation/GTMNSString+FindFolderTest.m
new file mode 100644
index 0000000..66fd329
--- /dev/null
+++ b/Foundation/GTMNSString+FindFolderTest.m
@@ -0,0 +1,77 @@
+//
+// GTMNSString+FindFolderTest.m
+//
+// 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 "GTMSenTestCase.h"
+#import "GTMNSString+FindFolder.h"
+
+@interface GTMNSString_FindFolderTest : GTMTestCase
+@end
+
+@implementation GTMNSString_FindFolderTest
+
+- (void)testStringWithPathForFolder {
+ // for gtm_stringWithPathForFolder:inDomain:doCreate:
+ // the parameters all get passed through to FSFindFolder so there's no point testing
+ // other combinations; our semantics will match FSFindFolder's
+ NSString *prefsPath = [NSString gtm_stringWithPathForFolder:kPreferencesFolderType
+ inDomain:kUserDomain
+ doCreate:NO];
+ NSString *realPrefsPath = [@"~/Library/Preferences" stringByExpandingTildeInPath];
+ STAssertEqualObjects(realPrefsPath, prefsPath, @"Found incorrect prefs path");
+
+
+ // test the subfolder method; it should return nil if we pass NO and the
+ // subfolder doesn't already exist
+
+ NSString *googCacheNoCreatePath = [NSString gtm_stringWithPathForFolder:kCachedDataFolderType
+ subfolderName:@"GTMUnitTestDuzntExist"
+ inDomain:kUserDomain
+ doCreate:NO];
+ STAssertNil(googCacheNoCreatePath, @"Should not exist: %@", googCacheNoCreatePath);
+
+ // test creating ~/Library/Cache/GTMUnitTestCreated
+
+ NSString *folderName = @"GTMUnitTestCreated";
+ NSString *gtmCachePath = [NSString gtm_stringWithPathForFolder:kCachedDataFolderType
+ subfolderName:folderName
+ inDomain:kUserDomain
+ doCreate:YES];
+ NSString *testPath = [NSString gtm_stringWithPathForFolder:kCachedDataFolderType
+ inDomain:kUserDomain
+ doCreate:NO];
+ NSString *testPathAppended = [testPath stringByAppendingPathComponent:folderName];
+ STAssertEqualObjects(gtmCachePath, testPathAppended, @"Unexpected path name");
+
+ NSFileManager* fileMgr = [NSFileManager defaultManager];
+ BOOL isDir = NO;
+ BOOL pathExists = [fileMgr fileExistsAtPath:gtmCachePath isDirectory:&isDir] && isDir;
+ STAssertTrue(pathExists, @"Path %@ is not existing like it should", gtmCachePath);
+
+ // test finding it again w/o having to create it
+ NSString *gtmCachePath2 = [NSString gtm_stringWithPathForFolder:kCachedDataFolderType
+ subfolderName:folderName
+ inDomain:kUserDomain
+ doCreate:NO];
+ STAssertEqualObjects(gtmCachePath2, gtmCachePath, nil);
+
+ BOOL didRemove = [fileMgr removeFileAtPath:gtmCachePath
+ handler:nil];
+ STAssertTrue(didRemove, @"Error removing %@", gtmCachePath);
+}
+
+@end
diff --git a/Foundation/GTMNSString+Replace.h b/Foundation/GTMNSString+Replace.h
new file mode 100644
index 0000000..3a88c80
--- /dev/null
+++ b/Foundation/GTMNSString+Replace.h
@@ -0,0 +1,40 @@
+//
+// GTMNSString+Replace.h
+//
+// 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 <Foundation/Foundation.h>
+
+/// Give easy search-n-replace functionality to NSString.
+@interface NSString (GTMStringReplaceAdditions)
+
+/// Returns a new autoreleased string by replacing all occurrences of
+// |oldString| with |newString| (case sensitive). If |oldString| is nil or
+// @"" nothing is done and |self| is returned. If |newString| is nil, it's
+// treated as if |newString| were the empty string, thus effectively
+// deleting all occurrences of |oldString| from |self|.
+//
+// Args:
+// target - the NSString to search for
+// replacement - the NSString to replace |oldString| with
+//
+// Returns:
+// A new autoreleased NSString
+//
+- (NSString *)gtm_stringByReplacingString:(NSString *)target
+ withString:(NSString *)replacement;
+
+@end
diff --git a/Foundation/GTMNSString+Replace.m b/Foundation/GTMNSString+Replace.m
new file mode 100644
index 0000000..4e8195c
--- /dev/null
+++ b/Foundation/GTMNSString+Replace.m
@@ -0,0 +1,48 @@
+//
+// GTMNSString+Replace.m
+//
+// 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 "GTMNSString+Replace.h"
+
+
+@implementation NSString (GTMStringReplaceAdditions)
+
+- (NSString *)gtm_stringByReplacingString:(NSString *)target
+ withString:(NSString *)replacement {
+ // If |target| was nil, then do nothing and return |self|
+ //
+ // We do the retain+autorelease dance here because of this use case:
+ // NSString *s1 = [[NSString alloc] init...];
+ // NSString *s2 = [s1 stringByReplacingString:@"foo" withString:@"bar"];
+ // [s1 release]; // |s2| still needs to be valid after this line
+ if (!target)
+ return [[self retain] autorelease];
+
+ // If |replacement| is nil we want it to be treated as if @"" was specified
+ // ... effectively removing |target| from self
+ if (!replacement)
+ replacement = @"";
+
+ NSMutableString *result = [[self mutableCopy] autorelease];
+ [result replaceOccurrencesOfString:target
+ withString:replacement
+ options:0
+ range:NSMakeRange(0, [result length])];
+ return result;
+}
+
+@end
diff --git a/Foundation/GTMNSString+ReplaceTest.m b/Foundation/GTMNSString+ReplaceTest.m
new file mode 100644
index 0000000..e814040
--- /dev/null
+++ b/Foundation/GTMNSString+ReplaceTest.m
@@ -0,0 +1,55 @@
+//
+// GTMNSString+ReplaceTest.m
+//
+// 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 "GTMSenTestCase.h"
+#import "GTMNSString+Replace.h"
+
+@interface GTMNSString_ReplaceTest : GTMTestCase
+@end
+
+@implementation GTMNSString_ReplaceTest
+
+- (void)testStringByReplacingStringWithString {
+ NSString *testString = @"a bc debc gh";
+ NSString *result;
+
+ result = [testString gtm_stringByReplacingString:@"bc" withString:@"BC"];
+ STAssertEqualObjects(@"a BC deBC gh", result,
+ @"'bc' wasn't replaced with 'BC'");
+
+ result = [testString gtm_stringByReplacingString:@"bc" withString:@""];
+ STAssertEqualObjects(@"a de gh", result, @"'bc' wasn't replaced with ''");
+
+ result = [testString gtm_stringByReplacingString:@"bc" withString:nil];
+ STAssertEqualObjects(@"a de gh", result, @"'bc' wasn't replaced with (nil)");
+
+ result = [testString gtm_stringByReplacingString:@" " withString:@"S"];
+ STAssertEqualObjects(@"aSbcSdebcSgh", result, @"' ' wasn't replaced with 'S'");
+
+ result = [testString gtm_stringByReplacingString:nil withString:@"blah"];
+ STAssertEqualObjects(testString, result, @"(nil) wasn't replaced with 'blah'");
+
+ result = [testString gtm_stringByReplacingString:nil withString:nil];
+ STAssertEqualObjects(testString, result, @"(nil) wasn't replaced with (nil)");
+
+ result = [testString gtm_stringByReplacingString:@"" withString:@"X"];
+ STAssertEqualObjects(testString, result,
+ @"replacing '' with anything should yield the original string");
+}
+
+@end
diff --git a/Foundation/GTMPath.h b/Foundation/GTMPath.h
new file mode 100644
index 0000000..6ac2347
--- /dev/null
+++ b/Foundation/GTMPath.h
@@ -0,0 +1,132 @@
+//
+// GTMPath.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>
+
+
+// GTMPath
+//
+// This class represents a single, absolute file system path. The represented
+// path must exist at the time of creation. This class also allows you to easily
+// create new paths (or full hierarchies) based on existing GTMPath instances.
+//
+// Given a GTMPath instance, new files and directories can be created inside
+// that path providing the instance refers to a directory. It is an error to try
+// to create a file/directory from a GTMPath that represents a file (this should
+// be common sense: clearly mkdir /etc/passwd/foo won't work).
+//
+// === Examples ===
+//
+// 1. This sample creates a GTMPath that references /tmp, then gets the
+// attributes for that directory.
+//
+// GTMPath *tmp = [GTMPath pathWithFullPath:@"/tmp"];
+// NSDictionary *attr = [tmp attributes];
+//
+//
+// 2. This sample creates a new directory inside /tmp named "foo".
+//
+// GTMPath *tmp = [GTMPath pathWithFullPath:@"/tmp"];
+// GTMPath *foo = [tmp createDirectoryName:@"foo" mode:0755];
+//
+//
+// 3. This sample creates a GTMPath instance that represents a user's ~/Library
+// folder.
+//
+// GTMPath *library = [GTMPath pathWithFullPath:@"/Users/bob/Library"];
+// ...
+//
+//
+// 4. This sample creates a directory hierarchy, where each level has its own
+// mode. Notice that the return value from these -create* methods is the
+// GTMPath that was just created. This allows these creation calls to be
+// chained together enabling easy creation of directory hierarchies.
+// This is one of the big benefits of this class.
+//
+// GTMPath *tmp = [GTMPath pathWithFullPath:@"/tmp"];
+// GTMPath *baz = [[[tmp createDirectoryName:@"foo" mode:0755]
+// createDirectoryName:@"bar" mode:0756]
+// createDirectoryName:@"baz" mode:0757];
+//
+@interface GTMPath : NSObject {
+ @private
+ NSString *fullPath_;
+}
+
+// Returns a GTMPath instance that represents the full path specified by
+// |fullPath|. Note that |fullPath| MUST be an absolute path.
++ (id)pathWithFullPath:(NSString *)fullPath;
+
+// Returns a GTMPath instance that represents the full path specified by
+// |fullPath|. Note that |fullPath| MUST be an absolute path. This method is the
+// designated initializer.
+- (id)initWithFullPath:(NSString *)fullPath;
+
+// Returns the name of this GTMPath instance. This is not the full path. It is
+// just the component name of this GTMPath instance. This is equivalent to
+// the Unix basename(3) function.
+- (NSString *)name;
+
+// Returns this path's parent GTMPath. This method will ONLY (and always) return
+// nil when |name| is "/". In otherwords, parent will be nil IFF this GTMPath
+// instance represents the root path, because "/" doesn't really have a parent.
+- (GTMPath *)parent;
+
+// Returns YES if this GTMPath represents a directory.
+- (BOOL)isDirectory;
+
+// Returns YES if this GTMPath instance represents the root path "/".
+- (BOOL)isRoot;
+
+// Returns the file system attributes of the path represented by this GTMPath
+// instance. See -[NSFileManager fileAttributesAtPath:...] for details.
+- (NSDictionary *)attributes;
+
+// Returns a string representation of the absolute path represented by this
+// GTMPath instance.
+- (NSString *)fullPath;
+
+@end
+
+
+// Methods for creating files and directories inside a GTMPath instance. These
+// methods are only allowed to be called on GTMPath instances that represent
+// directories. See the NSFileManager documentation for details about the
+// |attributes| parameters.
+@interface GTMPath (GTMPathGeneration)
+
+// Creates a new directory with the specified mode or attributes inside the
+// current GTMPath instance. If the creation is successful, a GTMPath for the
+// newly created directory is returned. Otherwise, nil is returned.
+- (GTMPath *)createDirectoryName:(NSString *)name mode:(mode_t)mode;
+- (GTMPath *)createDirectoryName:(NSString *)name
+ attributes:(NSDictionary *)attributes;
+
+// Creates a new file with the specified mode or attributes inside the
+// current GTMPath instance. If the creation is successful, a GTMPath for the
+// newly created file is returned. Otherwise, nil is returned. |data| is the
+// data to put in the file when created.
+- (GTMPath *)createFileName:(NSString *)name mode:(mode_t)mode;
+- (GTMPath *)createFileName:(NSString *)name
+ attributes:(NSDictionary *)attributes;
+- (GTMPath *)createFileName:(NSString *)name
+ attributes:(NSDictionary *)attributes
+ data:(NSData *)data;
+
+@end
+
diff --git a/Foundation/GTMPath.m b/Foundation/GTMPath.m
new file mode 100644
index 0000000..28ffad0
--- /dev/null
+++ b/Foundation/GTMPath.m
@@ -0,0 +1,156 @@
+//
+// GTMPath.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 "GTMPath.h"
+
+
+@implementation GTMPath
+
++ (id)pathWithFullPath:(NSString *)fullPath {
+ return [[[self alloc] initWithFullPath:fullPath] autorelease];
+}
+
+- (id)init {
+ return [self initWithFullPath:nil];
+}
+
+- (id)initWithFullPath:(NSString *)fullPath {
+ if ((self = [super init])) {
+ fullPath_ = [fullPath copy];
+ if (![fullPath_ isAbsolutePath] || [self attributes] == nil) {
+ [self release];
+ return nil;
+ }
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [fullPath_ release];
+ [super dealloc];
+}
+
+- (NSString *)description {
+ return [self fullPath];
+}
+
+- (NSString *)name {
+ return [fullPath_ lastPathComponent];
+}
+
+- (GTMPath *)parent {
+ if ([self isRoot]) return nil;
+ NSString *parentPath = [fullPath_ stringByDeletingLastPathComponent];
+ return [[self class] pathWithFullPath:parentPath];
+}
+
+- (BOOL)isDirectory {
+ BOOL isDir = NO;
+ BOOL exists = [[NSFileManager defaultManager]
+ fileExistsAtPath:fullPath_ isDirectory:&isDir];
+ return exists && isDir;
+}
+
+- (BOOL)isRoot {
+ return [fullPath_ isEqualToString:@"/"];
+}
+
+- (NSDictionary *)attributes {
+ return [[NSFileManager defaultManager]
+ fileAttributesAtPath:fullPath_
+ traverseLink:YES];
+}
+
+- (NSString *)fullPath {
+ return [[fullPath_ copy] autorelease];
+}
+
+@end
+
+
+@implementation GTMPath (GTMPathGeneration)
+
+- (GTMPath *)createDirectoryName:(NSString *)name mode:(mode_t)mode {
+ NSDictionary *attributes =
+ [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:mode]
+ forKey:NSFilePosixPermissions];
+ return [self createDirectoryName:name attributes:attributes];
+}
+
+- (GTMPath *)createDirectoryName:(NSString *)name
+ attributes:(NSDictionary *)attributes {
+ if ([name length] == 0) return nil;
+
+ // We first check to see if the requested directory alread exists by trying
+ // to create a GTMPath from the desired new path string. Only if the path
+ // doesn't already exist do we attempt to create it. If the path already
+ // exists, we will end up returning a GTMPath for the pre-existing path.
+ NSString *newPath = [fullPath_ stringByAppendingPathComponent:name];
+ GTMPath *nascentPath = [GTMPath pathWithFullPath:newPath];
+ if (nascentPath != nil && ![nascentPath isDirectory]) {
+ return nil; // Return nil because the path exists, but it's not a dir
+ }
+
+ if (nascentPath == nil) {
+ BOOL created = [[NSFileManager defaultManager]
+ createDirectoryAtPath:newPath
+ attributes:attributes];
+ nascentPath = created ? [GTMPath pathWithFullPath:newPath] : nil;
+ }
+
+ return nascentPath;
+}
+
+- (GTMPath *)createFileName:(NSString *)name mode:(mode_t)mode {
+ NSDictionary *attributes =
+ [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:mode]
+ forKey:NSFilePosixPermissions];
+ return [self createFileName:name attributes:attributes];
+}
+
+- (GTMPath *)createFileName:(NSString *)name
+ attributes:(NSDictionary *)attributes {
+ return [self createFileName:name attributes:attributes data:[NSData data]];
+}
+
+- (GTMPath *)createFileName:(NSString *)name
+ attributes:(NSDictionary *)attributes
+ data:(NSData *)data {
+ if ([name length] == 0) return nil;
+
+ // See createDirectoryName:attribute: for some high-level notes about what and
+ // why this method does what it does.
+ NSString *newPath = [fullPath_ stringByAppendingPathComponent:name];
+ GTMPath *nascentPath = [GTMPath pathWithFullPath:newPath];
+ if (nascentPath != nil && [nascentPath isDirectory]) {
+ return nil; // Return nil because the path exists, but it's a dir
+ }
+
+ if (nascentPath == nil) {
+ BOOL created = [[NSFileManager defaultManager]
+ createFileAtPath:newPath
+ contents:data
+ attributes:attributes];
+ nascentPath = created ? [GTMPath pathWithFullPath:newPath] : nil;
+ }
+
+ return nascentPath;
+}
+
+@end
diff --git a/Foundation/GTMPathTest.m b/Foundation/GTMPathTest.m
new file mode 100644
index 0000000..3130c4f
--- /dev/null
+++ b/Foundation/GTMPathTest.m
@@ -0,0 +1,231 @@
+//
+// GTMPathTest.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 "GTMSenTestCase.h"
+#import "GTMPath.h"
+#import "GTMUnitTestDevLog.h"
+
+
+@interface GTMPathTest : GTMTestCase {
+ @private
+ NSString *testDirectory_;
+}
+@end
+
+@implementation GTMPathTest
+
+- (void)setUp {
+ NSString *tmp = NSTemporaryDirectory();
+ STAssertNotNil(tmp, nil);
+
+ testDirectory_ = [[tmp stringByAppendingPathComponent:@"GTMPathTest"] retain];
+ STAssertNotNil(testDirectory_, nil);
+
+ BOOL created = [[NSFileManager defaultManager]
+ createDirectoryAtPath:testDirectory_
+ attributes:nil];
+ STAssertTrue(created, nil);
+}
+
+- (void)tearDown {
+ // Make sure it's safe to remove this directory before nuking it.
+ STAssertNotNil(testDirectory_, nil);
+ STAssertNotEqualObjects(testDirectory_, @"/", nil);
+ [[NSFileManager defaultManager] removeFileAtPath:testDirectory_ handler:nil];
+}
+
+- (void)testBasicCreation {
+ GTMPath *path = nil;
+
+ path = [[[GTMPath alloc] init] autorelease];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@"/"];
+ STAssertNotNil(path, nil);
+ STAssertNil([path parent], nil);
+ STAssertTrue([path isRoot], nil);
+ STAssertTrue([path isDirectory], nil);
+ STAssertEqualObjects([path name], @"/", nil);
+ STAssertEqualObjects([path fullPath], @"/", nil);
+}
+
+- (void)testRecursiveInitialization {
+ GTMPath *path = nil;
+
+ path = [GTMPath pathWithFullPath:nil];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@""];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@"etc"];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@"/"];
+ STAssertNotNil(path, nil);
+ STAssertNil([path parent], nil);
+ STAssertTrue([path isRoot], nil);
+ STAssertTrue([path isDirectory], nil);
+ STAssertEqualObjects([path name], @"/", nil);
+ STAssertEqualObjects([path fullPath], @"/", nil);
+
+ path = [GTMPath pathWithFullPath:@"/etc"];
+ STAssertNotNil(path, nil);
+ STAssertEqualObjects([path name], @"etc", nil);
+ STAssertEqualObjects([path fullPath], @"/etc", nil);
+ STAssertTrue([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+ STAssertNotNil([path parent], nil);
+ STAssertTrue([[path parent] isRoot], nil);
+
+ path = [GTMPath pathWithFullPath:@"/etc/passwd"];
+ STAssertNotNil(path, nil);
+ STAssertEqualObjects([path name], @"passwd", nil);
+ STAssertEqualObjects([path fullPath], @"/etc/passwd", nil);
+ STAssertFalse([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+ STAssertNotNil([path parent], nil);
+ STAssertFalse([[path parent] isRoot], nil);
+ STAssertTrue([[path parent] isDirectory], nil);
+ STAssertTrue([[[path parent] parent] isRoot], nil);
+
+ STAssertTrue([[path description] length] > 1, nil);
+}
+
+- (void)testCreationWithNonExistentPath {
+ GTMPath *path = nil;
+
+ path = [GTMPath pathWithFullPath:@" "];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@"/abcxyz"];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@"/etc/foo"];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@"/foo/bar/baz"];
+ STAssertNil(path, nil);
+}
+
+- (void)testDirectoryCreation {
+ GTMPath *tmp = [GTMPath pathWithFullPath:testDirectory_];
+ GTMPath *path = nil;
+
+ NSString *fooPath = [[tmp fullPath] stringByAppendingPathComponent:@"foo"];
+ path = [GTMPath pathWithFullPath:fooPath];
+ STAssertNil(path, nil);
+
+ path = [tmp createDirectoryName:@"foo" mode:0555];
+ STAssertNotNil(path, nil);
+ STAssertEqualObjects([path name], @"foo", nil);
+ // filePosixPermissions has odd return types in different SDKs, so we use
+ // STAssertTrue to avoid the macros type checks from choking us.
+ STAssertTrue([[path attributes] filePosixPermissions] == 0555, nil);
+ STAssertTrue([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+
+ // Trying to create a file where a dir already exists should fail
+ path = [tmp createFileName:@"foo" mode:0555];
+ STAssertNil(path, nil);
+
+ // Calling create again should succeed
+ path = [tmp createDirectoryName:@"foo" mode:0555];
+ STAssertNotNil(path, nil);
+ STAssertEqualObjects([path name], @"foo", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0555, nil);
+ STAssertTrue([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+
+ GTMPath *foo = [GTMPath pathWithFullPath:fooPath];
+ STAssertNotNil(foo, nil);
+ STAssertEqualObjects([path name], @"foo", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0555, nil);
+ STAssertTrue([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+}
+
+- (void)testFileCreation {
+ GTMPath *tmp = [GTMPath pathWithFullPath:testDirectory_];
+ GTMPath *path = nil;
+
+ NSString *fooPath = [[tmp fullPath] stringByAppendingPathComponent:@"foo"];
+ path = [GTMPath pathWithFullPath:fooPath];
+ STAssertNil(path, nil);
+
+ path = [tmp createFileName:@"foo" mode:0555];
+ STAssertNotNil(path, nil);
+ STAssertEqualObjects([path name], @"foo", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0555, nil);
+ STAssertFalse([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+
+ // Trying to create a dir where a file already exists should fail.
+ path = [tmp createDirectoryName:@"foo" mode:0555];
+ STAssertNil(path, nil);
+
+ // Calling create again should succeed
+ path = [tmp createFileName:@"foo" mode:0555];
+ STAssertNotNil(path, nil);
+ STAssertEqualObjects([path name], @"foo", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0555, nil);
+ STAssertFalse([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+
+ GTMPath *foo = [GTMPath pathWithFullPath:fooPath];
+ STAssertNotNil(foo, nil);
+ STAssertEqualObjects([path name], @"foo", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0555, nil);
+ STAssertFalse([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+
+ // Make sure we can't create a file/directory rooted off of |foo|, since it's
+ // not a directory.
+ path = [foo createFileName:@"bar" mode:0555];
+ STAssertNil(path, nil);
+ path = [foo createDirectoryName:@"bar" mode:0555];
+ STAssertNil(path, nil);
+}
+
+- (void)testHierarchyCreation {
+ GTMPath *tmp = [GTMPath pathWithFullPath:testDirectory_];
+ NSString *fooPath = [[tmp fullPath] stringByAppendingPathComponent:@"foo"];
+ GTMPath *path = [GTMPath pathWithFullPath:fooPath];
+ STAssertNil(path, nil);
+
+ path = [[[tmp createDirectoryName:@"foo" mode:0755]
+ createDirectoryName:@"bar" mode:0756]
+ createDirectoryName:@"baz" mode:0757];
+ STAssertNotNil(path, nil);
+
+ // Check "baz"
+ STAssertEqualObjects([path name], @"baz", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0757, nil);
+
+ // Check "bar"
+ path = [path parent];
+ STAssertEqualObjects([path name], @"bar", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0756, nil);
+
+ // Check "foo"
+ path = [path parent];
+ STAssertEqualObjects([path name], @"foo", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0755, nil);
+}
+
+@end
diff --git a/Foundation/GTMRegex.m b/Foundation/GTMRegex.m
index f183553..fb6e3a0 100644
--- a/Foundation/GTMRegex.m
+++ b/Foundation/GTMRegex.m
@@ -422,7 +422,7 @@ static NSString *const kReplacementPattern =
- (NSString *)description {
NSMutableString *result =
- [NSMutableString stringWithFormat:@"%@<%p> { pattern=\"%@\", rawNumSubPatterns=%z, options=(",
+ [NSMutableString stringWithFormat:@"%@<%p> { pattern=\"%@\", rawNumSubPatterns=%zd, options=(",
[self class], self, pattern_, regexData_.re_nsub];
if (options_) {
if (options_ & kGTMRegexOptionIgnoreCase)
diff --git a/Foundation/GTMSignalHandler.h b/Foundation/GTMSignalHandler.h
new file mode 100644
index 0000000..da3a3ea
--- /dev/null
+++ b/Foundation/GTMSignalHandler.h
@@ -0,0 +1,75 @@
+//
+// GTMSignalHandler.h
+//
+// Copyright 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>
+
+// GTMSignalHandler.
+//
+// This is a very simple, easy-to-use class for registering handlers that get
+// called when a specific signal is delivered. Also handy for ignoring
+// inconvenient signals. Ignoring SIGKILL is not support for what should be
+// obvious reasons.
+//
+// Example of how to catch SIGABRT and SIGTERM while ignring SIGWINCH:
+// GTMSignalHandler *abrt, *term, *winch;
+// abrt = [[GTMSignalHandler alloc]
+// initWithSignal:SIGABRT
+// target:self
+// handler:@selector(handleAbort:)];
+//
+// term = [[GTMSignalHandler alloc]
+// initWithSignal:SIGTERM
+// target:self
+// handler:@selector(handleTerm:)];
+//
+// winch = [[GTMSignalHandler alloc] initWithSignal:SIGWINCH
+// initWithSignal:SIGWINCH
+// target:nil
+// handler:NULL
+//
+// And then the signal handler has the triggered signal number boxed in an
+// NSNumber:
+// -(void)handleTerm:(NSNumber *)signo {
+// .. do stuff ..
+// }
+//
+// Release the handler when you're no longer interested in handling that signal.
+// Note that signal(SIG_IGN, signo) is performed on each signal handled by
+// objects of this class, and those do not get un-done.
+//
+// Multiple handlers for the same signal is NOT supported.
+//
+// kqueue() is used to handle the signals, and the default runloop for the first
+// signal handler is used for signal delivery, so keep that in mind when you're
+// using this class. This class explicitly does not handle arbitrary runloops
+// and threading.
+//
+@interface GTMSignalHandler : NSObject {
+ @private
+ int signo_;
+ __weak id target_;
+ SEL handler_;
+}
+
+// Returns a retained signal handler object that will invoke |handler| on the
+// |target| whenever a signal of number |signo| is delivered to the process.
+-(id)initWithSignal:(int)signo
+ target:(id)target
+ handler:(SEL)handler;
+
+@end // GTMSignalHandler
diff --git a/Foundation/GTMSignalHandler.m b/Foundation/GTMSignalHandler.m
new file mode 100644
index 0000000..f88adf6
--- /dev/null
+++ b/Foundation/GTMSignalHandler.m
@@ -0,0 +1,209 @@
+//
+// GTMSignalHandler.m
+//
+// Copyright 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 "GTMSignalHandler.h"
+#import "GTMDefines.h"
+
+#import <sys/event.h> // for kqueue() and kevent
+#import "GTMDebugSelectorValidation.h"
+
+// Simplifying assumption: No more than one handler for a particular signal is
+// alive at a time. When the second signal is registered, kqueue just updates
+// the info about the first signal, which makes -dealloc time complicated (what
+// happens when handler1(SIGUSR1) is released before handler2(SIGUSR1)?). This
+// could be solved by having one kqueue per signal, or keeping a list of
+// handlers interested in a particular signal, but not really worth it for apps
+// that register the handlers at startup and don't change them.
+
+
+// File descriptor for the kqueue that will hold all of our signal events.
+static int gSignalKQueueFileDescriptor;
+
+// A wrapper around the kqueue file descriptor so we can put it into a
+// runloop.
+static CFSocketRef gRunLoopSocket;
+
+
+@interface GTMSignalHandler (PrivateMethods)
+
+// Invoke |handler_| on the |target_|, passing a boxed |signo_|.
+- (void)notify;
+
+// Wrap the file descriptor in a CFSocket and add it to the runloop so that a
+// callback function will be called when there's activity on the descriptor. In
+// this case, we're interested in new stuff from the kqueue.
+- (void)addFileDescriptorMonitor:(int)fd;
+
+// Add ourselves to our global kqueue.
+- (void)registerWithKQueue;
+
+// Remove ourseves from our global kqueue.
+- (void)unregisterWithKQueue;
+
+@end // PrivateMethods
+
+
+@implementation GTMSignalHandler
+
+-(id)init {
+ // Folks shouldn't call init directly, so they get what they deserve.
+ return [self initWithSignal:0 target:nil handler:NULL];
+} // init
+
+
+- (id)initWithSignal:(int)signo
+ target:(id)target
+ handler:(SEL)handler {
+
+ if ((self = [super init])) {
+
+ if (signo == 0) {
+ [self release];
+ return nil;
+ }
+
+ signo_ = signo;
+ target_ = target; // Don't retain since target will most likely retain us.
+ handler_ = handler;
+ GTMAssertSelectorNilOrImplementedWithArguments(target_,
+ handler_,
+ @encode(NSNumber *),
+ NULL);
+
+ // We're handling this signal via kqueue, so turn off the usual signal
+ // handling.
+ signal(signo_, SIG_IGN);
+
+ if (handler != NULL) {
+ [self registerWithKQueue];
+ }
+ }
+ return self;
+} // initWithSignal
+
+- (void)finalize {
+ [self unregisterWithKQueue];
+
+ [super finalize];
+
+} // finalize
+
+- (void)dealloc {
+ [self unregisterWithKQueue];
+
+ [super dealloc];
+
+} // dealloc
+
+
+// Cribbed from Advanced Mac OS X Programming.
+static void SocketCallBack(CFSocketRef socketref, CFSocketCallBackType type,
+ CFDataRef address, const void *data, void *info) {
+ struct kevent event;
+
+ if (kevent(gSignalKQueueFileDescriptor, NULL, 0, &event, 1, NULL) == -1) {
+ _GTMDevLog(@"could not pick up kqueue event. Errno %d", errno); // COV_NF_LINE
+ } else {
+ GTMSignalHandler *handler = (GTMSignalHandler *)event.udata;
+ [handler notify];
+ }
+
+} // SocketCallBack
+
+
+// Cribbed from Advanced Mac OS X Programming
+- (void)addFileDescriptorMonitor:(int)fd {
+ CFSocketContext context = { 0, NULL, NULL, NULL, NULL };
+
+ gRunLoopSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
+ fd,
+ kCFSocketReadCallBack,
+ SocketCallBack,
+ &context);
+ if (gRunLoopSocket == NULL) {
+ _GTMDevLog(@"could not CFSocketCreateWithNative"); // COV_NF_LINE
+ goto bailout; // COV_NF_LINE
+ }
+
+ CFRunLoopSourceRef rls;
+ rls = CFSocketCreateRunLoopSource(NULL, gRunLoopSocket, 0);
+ if (rls == NULL) {
+ _GTMDevLog(@"could not create a run loop source"); // COV_NF_LINE
+ goto bailout; // COV_NF_LINE
+ }
+
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), rls,
+ kCFRunLoopDefaultMode);
+ CFRelease(rls);
+
+ bailout:
+ return;
+
+} // addFileDescriptorMonitor
+
+
+- (void)registerWithKQueue {
+
+ // Make sure we have our kqueue.
+ if (gSignalKQueueFileDescriptor == 0) {
+ gSignalKQueueFileDescriptor = kqueue();
+
+ if (gSignalKQueueFileDescriptor == -1) {
+ _GTMDevLog(@"could not make signal kqueue. Errno %d", errno); // COV_NF_LINE
+ return; // COV_NF_LINE
+ }
+
+ // Add the kqueue file descriptor to the runloop.
+ [self addFileDescriptorMonitor:gSignalKQueueFileDescriptor];
+ }
+
+ // Add a new event for the signal.
+ struct kevent filter;
+ EV_SET(&filter, signo_, EVFILT_SIGNAL, EV_ADD | EV_ENABLE | EV_CLEAR,
+ 0, 0, self);
+
+ const struct timespec noWait = { 0, 0 };
+ if (kevent(gSignalKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) != 0) {
+ _GTMDevLog(@"could not add event for signal %d. Errno %d", signo_, errno); // COV_NF_LINE
+ }
+
+} // registerWithKQueue
+
+
+- (void)unregisterWithKQueue {
+ // Short-circuit cases where we didn't actually register a kqueue event.
+ if (signo_ == 0) return;
+ if (handler_ == 0) return;
+
+ struct kevent filter;
+ EV_SET(&filter, signo_, EVFILT_SIGNAL, EV_DELETE, 0, 0, self);
+
+ const struct timespec noWait = { 0, 0 };
+ if (kevent(gSignalKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) != 0) {
+ _GTMDevLog(@"could not remove event for signal %d. Errno %d", signo_, errno); // COV_NF_LINE
+ }
+
+} // unregisterWithKQueue
+
+
+- (void)notify {
+ [target_ performSelector:handler_
+ withObject:[NSNumber numberWithInt:signo_]];
+} // notify
+
+@end // GTMSignalHandler
diff --git a/Foundation/GTMSignalHandlerTest.m b/Foundation/GTMSignalHandlerTest.m
new file mode 100644
index 0000000..9dcabf4
--- /dev/null
+++ b/Foundation/GTMSignalHandlerTest.m
@@ -0,0 +1,143 @@
+//
+// GTMSignalHandlerTest.m
+//
+// Copyright 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 "GTMSignalHandler.h"
+#import "GTMSenTestCase.h"
+
+@interface GTMSignalHandlerTest : GTMTestCase
+@end // GTMSignalHandlerTest
+
+@interface SignalCounter : NSObject {
+ @public
+ int signalCount_;
+ int lastSeenSignal_;
+}
+- (int)count;
+- (int)lastSeen;
+- (void)countSignal:(NSNumber *)signo;
++ (id)signalCounter;
+@end // SignalCounter
+
+@implementation SignalCounter
++ (id)signalCounter {
+ return [[[[self class] alloc] init] autorelease];
+}
+- (int)count {
+ return signalCount_;
+}
+- (int)lastSeen {
+ return lastSeenSignal_;
+}
+// Count the number of times this signal handler has fired.
+- (void)countSignal:(NSNumber *)signo {
+ signalCount_++;
+ lastSeenSignal_ = [signo intValue];
+} // countSignal
+@end // SignalCounter
+
+@implementation GTMSignalHandlerTest
+
+// Spin the run loop so that the kqueue event notifications will get delivered.
+- (void)giveSomeLove {
+ NSDate *endTime = [NSDate dateWithTimeIntervalSinceNow:0.5];
+ [[NSRunLoop currentRunLoop] runUntilDate:endTime];
+} // giveSomeLove
+
+
+- (void)testNillage {
+ GTMSignalHandler *handler;
+
+ // Just an init should return nil.
+ handler = [[[GTMSignalHandler alloc] init] autorelease];
+ STAssertNil(handler, nil);
+
+ // Zero signal should return nil as well.
+ handler = [[[GTMSignalHandler alloc]
+ initWithSignal:0
+ target:self
+ handler:@selector(nomnomnom:)] autorelease];
+ STAssertNil(handler, nil);
+
+} // testNillage
+
+
+- (void)testSingleHandler {
+ SignalCounter *counter = [SignalCounter signalCounter];
+ STAssertNotNil(counter, nil);
+
+ GTMSignalHandler *handler = [[GTMSignalHandler alloc]
+ initWithSignal:SIGWINCH
+ target:counter
+ handler:@selector(countSignal:)];
+ STAssertNotNil(handler, nil);
+ raise(SIGWINCH);
+ [self giveSomeLove];
+
+ STAssertEquals([counter count], 1, nil);
+ STAssertEquals([counter lastSeen], SIGWINCH, nil);
+
+ raise(SIGWINCH);
+ [self giveSomeLove];
+
+ STAssertEquals([counter count], 2, nil);
+ STAssertEquals([counter lastSeen], SIGWINCH, nil);
+
+ // create a second one to make sure we're seding data where we want
+ SignalCounter *counter2 = [SignalCounter signalCounter];
+ STAssertNotNil(counter2, nil);
+ [[[GTMSignalHandler alloc] initWithSignal:SIGUSR1
+ target:counter2
+ handler:@selector(countSignal:)] autorelease];
+
+ raise(SIGUSR1);
+ [self giveSomeLove];
+
+ STAssertEquals([counter count], 2, nil);
+ STAssertEquals([counter lastSeen], SIGWINCH, nil);
+ STAssertEquals([counter2 count], 1, nil);
+ STAssertEquals([counter2 lastSeen], SIGUSR1, nil);
+
+ [handler release];
+
+ // The signal is still ignored (so we shouldn't die), but the
+ // the handler method should not get called.
+ raise(SIGWINCH);
+
+ STAssertEquals([counter count], 2, nil);
+ STAssertEquals([counter lastSeen], SIGWINCH, nil);
+ STAssertEquals([counter2 count], 1, nil);
+ STAssertEquals([counter2 lastSeen], SIGUSR1, nil);
+
+} // testSingleHandler
+
+
+- (void)testIgnore {
+ SignalCounter *counter = [SignalCounter signalCounter];
+ STAssertNotNil(counter, nil);
+
+ [[[GTMSignalHandler alloc] initWithSignal:SIGUSR1
+ target:counter
+ handler:NULL] autorelease];
+
+ raise(SIGUSR1);
+ [self giveSomeLove];
+ STAssertEquals([counter count], 0, nil);
+
+} // testIgnore
+
+@end // GTMSignalHandlerTest
diff --git a/Foundation/GTMStackTraceTest.m b/Foundation/GTMStackTraceTest.m
index 16c6273..edcb851 100644
--- a/Foundation/GTMStackTraceTest.m
+++ b/Foundation/GTMStackTraceTest.m
@@ -16,7 +16,6 @@
// the License.
//
-#import <SenTestingKit/SenTestingKit.h>
#import <Foundation/Foundation.h>
#import "GTMStackTrace.h"
#import "GTMSenTestCase.h"
diff --git a/Foundation/GTMValidatingContainers.h b/Foundation/GTMValidatingContainers.h
new file mode 100644
index 0000000..1b11f1b
--- /dev/null
+++ b/Foundation/GTMValidatingContainers.h
@@ -0,0 +1,195 @@
+//
+// GTMValidatingContainers.h
+//
+// Mutable containers that do verification of objects being added to them
+// at runtime. Support for arrays, dictionaries and sets.
+//
+// Copyright 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.
+//
+
+// GTMValidatingContainers are a set of mutable container classes that allow
+// you to have a selector on a target that is called to verify that the objects
+// being put into the container are valid. This can be controlled at compile
+// time so that you don't take the performance hit in a release build using the
+// GTM_CONTAINERS_VALIDATE macro.
+// We have supplied validators for simple cases such as kindOfClass or
+// conformsToProtocol. See GTMKindOfClassValidator et al. for details.
+//
+// Example of usage:
+// id target = [GTMKindOfClassValidator validateAgainstClass:[NSString class]];
+// SEL selector = @selector(validateObject:forContainer:);
+// GTMValidatingArray *array = [GTMValidatingArray validatingArrayWithTarget:target
+// selector:selector];
+// [array addObject:@"foo"]; // Will be good
+// [array addObject:[NSNumber numberWithInt:2]; // Will fail
+//
+// By setting the GTM_CONTAINERS_VALIDATION_FAILED_LOG and
+// GTM_CONTAINERS_VALIDATION_FAILED_ASSERT macros you can control what happens
+// when a validation fails. If you implement your own validators, you may want
+// to control their internals using the same macros for consistency.
+//
+// Note that the validating collection types retain their targets.
+
+#import <Foundation/Foundation.h>
+#import "GTMDefines.h"
+
+// By default we only validate containers in debug. If you want to validate
+// in release as well, #define GTM_CONTAINERS_VALIDATE in a prefix or build
+// settings.
+#ifndef GTM_CONTAINERS_VALIDATE
+#if DEBUG
+#define GTM_CONTAINERS_VALIDATE 1
+#else // DEBUG
+#define GTM_CONTAINERS_VALIDATE 0
+#endif // DEBUG
+#endif // GTM_CONTAINERS_VALIDATE
+
+// If GTM_CONTAINERS_VALIDATE is on, and log and assert are both turned off
+// (see below), the object that failed validation will just not be added
+// to the container.
+
+// If you don't want log to occur on validation failure define
+// GTM_CONTAINERS_VALIDATION_FAILED_LOG to 0 in a prefix or build settings.
+#ifndef GTM_CONTAINERS_VALIDATION_FAILED_LOG
+#define GTM_CONTAINERS_VALIDATION_FAILED_LOG GTM_CONTAINERS_VALIDATE
+#endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG
+
+// If you don't want an assert to occur on validation failure define
+// GTM_CONTAINERS_VALIDATION_FAILED_ASSERT to 0 in a prefix or build settings.
+#ifndef GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+#define GTM_CONTAINERS_VALIDATION_FAILED_ASSERT GTM_CONTAINERS_VALIDATE
+#endif // GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+
+// Sometimes you get a container back from somebody else and want to validate
+// that it contains what you think it contains. _GTMValidateContainer
+// allows you to do exactly that. _GTMValidateContainer... give you specialty
+// functions for doing common types of validations. These all inline to nothing
+// if GTM_CONTAINERS_VALIDATE is not defined.
+#if GTM_CONTAINERS_VALIDATE
+void _GTMValidateContainer(id container, id target, SEL selector);
+void _GTMValidateContainerContainsKindOfClass(id container, Class cls);
+void _GTMValidateContainerContainsMemberOfClass(id container, Class cls);
+void _GTMValidateContainerConformsToProtocol(id container, Protocol *prot);
+void _GTMValidateContainerItemsRespondToSelector(id container, SEL sel);
+#else
+inline void _GTMValidateContainer(id container, id target, SEL selector) {
+}
+inline void _GTMValidateContainerContainsKindOfClass(id container, Class cls) {
+}
+inline void _GTMValidateContainerContainsMemberOfClass(id container,
+ Class cls) {
+}
+inline void _GTMValidateContainerConformsToProtocol(id container,
+ Protocol *prot) {
+}
+inline void _GTMValidateContainerItemsRespondToSelector(id container,
+ SEL sel) {
+}
+#endif
+
+
+// See comments near top of file for class description.
+@interface GTMValidatingArray : NSMutableArray {
+#if GTM_CONTAINERS_VALIDATE
+ NSMutableArray *embeddedContainer_;
+ id target_;
+ SEL selector_;
+#endif // #if GTM_CONTAINERS_VALIDATE
+}
++ (id)validatingArrayWithTarget:(id)target selector:(SEL)sel;
++ (id)validatingArrayWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel;
+- (id)initValidatingWithTarget:(id)target selector:(SEL)sel;
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel;
+@end
+
+// See comments near top of file for class description.
+@interface GTMValidatingDictionary : NSMutableDictionary {
+#if GTM_CONTAINERS_VALIDATE
+ NSMutableDictionary *embeddedContainer_;
+ id target_;
+ SEL selector_;
+#endif // #if GTM_CONTAINERS_VALIDATE
+}
++ (id)validatingDictionaryWithTarget:(id)target selector:(SEL)sel;
++ (id)validatingDictionaryWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel;
+- (id)initValidatingWithTarget:(id)target selector:(SEL)sel;
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel;
+@end
+
+// See comments near top of file for class description.
+@interface GTMValidatingSet : NSMutableSet {
+#if GTM_CONTAINERS_VALIDATE
+ NSMutableSet *embeddedContainer_;
+ id target_;
+ SEL selector_;
+#endif // #if GTM_CONTAINERS_VALIDATE
+}
++ (id)validatingSetWithTarget:(id)target selector:(SEL)sel;
++ (id)validatingSetWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel;
+- (id)initValidatingWithTarget:(id)target selector:(SEL)sel;
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel;
+@end
+
+#pragma mark -
+#pragma mark Simple Common Validators
+// See comments near top of file for examples of how these are used.
+@protocol GTMContainerValidatorProtocol
+- (BOOL)validateObject:(id)object forContainer:(id)container;
+@end
+
+// Validates that a given object is a kind of class (instance of class or an
+// instance of any class that inherits from that class)
+@interface GTMKindOfClassValidator : NSObject <GTMContainerValidatorProtocol> {
+ Class cls_;
+}
++ (id)validateAgainstClass:(Class)cls;
+- (id)initWithClass:(Class)cls;
+@end
+
+// Validates that a given object is a member of class (exact instance of class)
+@interface GTMMemberOfClassValidator : NSObject <GTMContainerValidatorProtocol> {
+ Class cls_;
+}
++ (id)validateAgainstClass:(Class)cls;
+- (id)initWithClass:(Class)cls;
+@end
+
+// Validates that a given object conforms to a protocol
+@interface GTMConformsToProtocolValidator : NSObject <GTMContainerValidatorProtocol> {
+ Protocol* prot_;
+}
++ (id)validateAgainstProtocol:(Protocol*)prot;
+- (id)initWithProtocol:(Protocol*)prot;
+@end
+
+// Validates that a given object responds to a given selector
+@interface GTMRespondsToSelectorValidator : NSObject <GTMContainerValidatorProtocol> {
+ SEL sel_;
+}
++ (id)validateAgainstSelector:(SEL)sel;
+- (id)initWithSelector:(SEL)sel;
+@end
diff --git a/Foundation/GTMValidatingContainers.m b/Foundation/GTMValidatingContainers.m
new file mode 100644
index 0000000..f46ba3f
--- /dev/null
+++ b/Foundation/GTMValidatingContainers.m
@@ -0,0 +1,476 @@
+//
+// GTMValidatingContainers.m
+//
+// Mutable containers that do verification of objects being added to them
+// at runtime. Support for arrays, dictionaries and sets.
+//
+// Documentation on subclassing class clusters (which we are doing) is here:
+// http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_9.html#//apple_ref/doc/uid/TP40002974-CH4-DontLinkElementID_105
+//
+// Copyright 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 "GTMValidatingContainers.h"
+
+#if GTM_CONTAINERS_VALIDATE
+
+#import "GTMDebugSelectorValidation.h"
+#if GTM_IPHONE_SDK
+#import <objc/message.h>
+#import <objc/runtime.h>
+#else // GTM_IPHONE_SDK
+#import <objc/objc-runtime.h>
+#endif // GTM_IPHONE_SDK
+
+static inline BOOL VerifyObjectWithTargetAndSelectorForContainer(id anObject,
+ id target,
+ SEL selector,
+ id container) {
+ // We must take care here, since Intel leaves junk in high bytes of return
+ // register for predicates that return BOOL.
+ // For details see:
+ // http://developer.apple.com/documentation/MacOSX/Conceptual/universal_binary/universal_binary_tips/chapter_5_section_23.html
+ // and
+ // http://www.red-sweater.com/blog/320/abusing-objective-c-with-class#comment-83187
+ BOOL isGood = ((BOOL (*)(id, SEL, id, id))objc_msgSend)(target, selector,
+ anObject, container);
+ if (!isGood) {
+#if GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ _GTMDevAssert(isGood, @"%@ failed container verification for %@",
+ anObject, [container description]);
+#endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG
+#if GTM_CONTAINERS_VALIDATION_FAILED_LOG
+ _GTMDevLog(@"%@ failed container verification for %@", anObject,
+ [container description]);
+#endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG
+ }
+ return isGood;
+}
+
+static inline void VerifySelectorOnTarget(SEL sel, id target) {
+ GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(target,
+ sel,
+ @encode(BOOL),
+ @encode(id),
+ @encode(id),
+ nil);
+}
+
+void _GTMValidateContainerContainsKindOfClass(id container, Class cls) {
+ GTMKindOfClassValidator *validator;
+ validator = [GTMKindOfClassValidator validateAgainstClass:cls];
+ _GTMValidateContainer(container,
+ validator,
+ @selector(validateObject:forContainer:));
+}
+
+void _GTMValidateContainerContainsMemberOfClass(id container, Class cls) {
+ GTMMemberOfClassValidator *validator;
+ validator = [GTMMemberOfClassValidator validateAgainstClass:cls];
+ _GTMValidateContainer(container,
+ validator,
+ @selector(validateObject:forContainer:));
+}
+
+void _GTMValidateContainerConformsToProtocol(id container, Protocol* prot) {
+ GTMConformsToProtocolValidator *validator;
+ validator = [GTMConformsToProtocolValidator validateAgainstProtocol:prot];
+ _GTMValidateContainer(container,
+ validator,
+ @selector(validateObject:forContainer:));
+}
+
+void _GTMValidateContainerItemsRespondToSelector(id container, SEL sel) {
+ GTMRespondsToSelectorValidator *validator;
+ validator = [GTMRespondsToSelectorValidator validateAgainstSelector:sel];
+ _GTMValidateContainer(container,
+ validator,
+ @selector(validateObject:forContainer:));
+}
+
+void _GTMValidateContainer(id container, id target, SEL selector) {
+ if ([container respondsToSelector:@selector(objectEnumerator)]) {
+ NSEnumerator *enumerator = [container objectEnumerator];
+ id val;
+ while ((val = [enumerator nextObject])) {
+ VerifyObjectWithTargetAndSelectorForContainer(val,
+ target,
+ selector,
+ container);
+ }
+ } else {
+#if GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ _GTMDevAssert(0, @"container %@ does not respond to -objectEnumerator",
+ [container description]);
+#endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG
+#if GTM_CONTAINERS_VALIDATION_FAILED_LOG
+ _GTMDevLog(@"container does not respont to -objectEnumerator: %@",
+ [container description]);
+#endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG
+ }
+}
+#endif // GTM_CONTAINERS_VALIDATE
+
+@implementation GTMValidatingArray
+
++ (id)validatingArrayWithTarget:(id)target selector:(SEL)sel {
+ return [self validatingArrayWithCapacity:0 target:target selector:sel];
+}
+
++ (id)validatingArrayWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ return [[[[self class] alloc] initValidatingWithCapacity:0
+ target:target
+ selector:sel] autorelease];
+}
+
+- (id)initValidatingWithTarget:(id)target selector:(SEL)sel {
+ return [self initValidatingWithCapacity:0 target:target selector:sel];
+}
+
+#if GTM_CONTAINERS_VALIDATE
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ if ((self = [super init])) {
+ embeddedContainer_ = [[NSMutableArray alloc] initWithCapacity:capacity];
+ target_ = [target retain];
+ selector_ = sel;
+ VerifySelectorOnTarget(selector_, target_);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [embeddedContainer_ release];
+ [target_ release];
+ [super dealloc];
+}
+
+- (NSUInteger)count {
+ return [embeddedContainer_ count];
+}
+
+- (id)objectAtIndex:(NSUInteger)idx {
+ return [embeddedContainer_ objectAtIndex:idx];
+}
+
+- (void)addObject:(id)anObject {
+ if (VerifyObjectWithTargetAndSelectorForContainer(anObject, target_,
+ selector_, self)) {
+ [embeddedContainer_ addObject:anObject];
+ }
+}
+
+- (void)insertObject:(id)anObject atIndex:(NSUInteger)idx {
+ if (VerifyObjectWithTargetAndSelectorForContainer(anObject, target_,
+ selector_, self)) {
+ [embeddedContainer_ insertObject:anObject atIndex:idx];
+ }
+}
+
+- (void)removeLastObject {
+ [embeddedContainer_ removeLastObject];
+}
+
+- (void)removeObjectAtIndex:(NSUInteger)idx {
+ [embeddedContainer_ removeObjectAtIndex:idx];
+}
+
+- (void)replaceObjectAtIndex:(NSUInteger)idx withObject:(id)anObject {
+ if (VerifyObjectWithTargetAndSelectorForContainer(anObject, target_,
+ selector_, self)) {
+ [embeddedContainer_ replaceObjectAtIndex:idx withObject:anObject];
+ }
+}
+
+- (NSString*)description {
+ return [NSString stringWithFormat:@"%@ - %@",
+ NSStringFromClass([self class]),
+ [embeddedContainer_ description]];
+}
+
+#else // GTM_CONTAINERS_VALIDATE
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ [self release];
+ return [[NSMutableArray alloc] initWithCapacity:capacity];
+}
+#endif // GTM_CONTAINERS_VALIDATE
+@end
+
+@implementation GTMValidatingDictionary
++ (id)validatingDictionaryWithTarget:(id)target selector:(SEL)sel {
+ return [self validatingDictionaryWithCapacity:0 target:target selector:sel];
+}
+
++ (id)validatingDictionaryWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ return [[[[self class] alloc] initValidatingWithCapacity:0
+ target:target
+ selector:sel] autorelease];
+}
+
+- (id)initValidatingWithTarget:(id)target selector:(SEL)sel {
+ return [self initValidatingWithCapacity:0 target:target selector:sel];
+}
+
+#if GTM_CONTAINERS_VALIDATE
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ if ((self = [super init])) {
+ embeddedContainer_ = [[NSMutableDictionary alloc] initWithCapacity:capacity];
+ target_ = [target retain];
+ selector_ = sel;
+ VerifySelectorOnTarget(selector_, target_);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [target_ release];
+ [embeddedContainer_ release];
+ [super dealloc];
+}
+
+- (NSUInteger)count {
+ return [embeddedContainer_ count];
+}
+
+- (NSEnumerator *)keyEnumerator {
+ return [embeddedContainer_ keyEnumerator];
+}
+
+- (id)objectForKey:(id)aKey {
+ return [embeddedContainer_ objectForKey:aKey];
+}
+
+- (void)removeObjectForKey:(id)aKey {
+ [embeddedContainer_ removeObjectForKey:aKey];
+}
+
+- (void)setObject:(id)anObject forKey:(id)aKey {
+ if (VerifyObjectWithTargetAndSelectorForContainer(anObject, target_,
+ selector_, self)) {
+ [embeddedContainer_ setObject:anObject forKey:aKey];
+ }
+}
+
+- (NSString*)description {
+ return [NSString stringWithFormat:@"%@ - %@",
+ NSStringFromClass([self class]),
+ [embeddedContainer_ description]];
+}
+
+#else // GTM_CONTAINERS_VALIDATE
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ [self release];
+ return [[NSMutableDictionary alloc] initWithCapacity:capacity];
+
+}
+#endif // GTM_CONTAINERS_VALIDATE
+@end
+
+@implementation GTMValidatingSet
++ (id)validatingSetWithTarget:(id)target selector:(SEL)sel {
+ return [self validatingSetWithCapacity:0 target:target selector:sel];
+}
+
++ (id)validatingSetWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ return [[[[self class] alloc] initValidatingWithCapacity:0
+ target:target
+ selector:sel] autorelease];
+}
+- (id)initValidatingWithTarget:(id)target selector:(SEL)sel {
+ return [self initValidatingWithCapacity:0 target:target selector:sel];
+}
+
+#if GTM_CONTAINERS_VALIDATE
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ if ((self = [super init])) {
+ embeddedContainer_ = [[NSMutableSet alloc] initWithCapacity:capacity];
+ target_ = [target retain];
+ selector_ = sel;
+ VerifySelectorOnTarget(selector_, target_);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [target_ release];
+ [embeddedContainer_ release];
+ [super dealloc];
+}
+
+- (NSUInteger)count {
+ return [embeddedContainer_ count];
+}
+
+- (id)member:(id)object {
+ return [embeddedContainer_ member:object];
+}
+
+- (NSEnumerator *)objectEnumerator {
+ return [embeddedContainer_ objectEnumerator];
+}
+
+- (void)addObject:(id)object {
+ if (object && VerifyObjectWithTargetAndSelectorForContainer(object,
+ target_,
+ selector_,
+ self)) {
+ [embeddedContainer_ addObject:object];
+ }
+}
+
+- (void)removeObject:(id)object {
+ [embeddedContainer_ removeObject:object];
+}
+
+- (NSString*)description {
+ return [NSString stringWithFormat:@"%@ - %@",
+ NSStringFromClass([self class]),
+ [embeddedContainer_ description]];
+}
+
+#else // GTM_CONTAINERS_VALIDATE
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ [self release];
+ return [[NSMutableSet alloc] initWithCapacity:capacity];
+}
+#endif // GTM_CONTAINERS_VALIDATE
+@end
+
+#pragma mark -
+#pragma mark Simple Common Validators
+@implementation GTMKindOfClassValidator
++ (id)validateAgainstClass:(Class)cls {
+ return [[[[self class] alloc] initWithClass:cls] autorelease];
+}
+
+- (id)initWithClass:(Class)cls {
+#if GTM_CONTAINERS_VALIDATE
+ if ((self = [super init])) {
+ if (!cls) {
+ _GTMDevLog(@"nil class");
+ [self release];
+ return nil;
+ }
+ cls_ = cls;
+ }
+ return self;
+#else // GTM_CONTAINERS_VALIDATE
+ [self release];
+ return nil;
+#endif // GTM_CONTAINERS_VALIDATE
+}
+
+- (BOOL)validateObject:(id)object forContainer:(id)container {
+ return [object isKindOfClass:cls_];
+}
+@end
+
+@implementation GTMMemberOfClassValidator
++ (id)validateAgainstClass:(Class)cls {
+ return [[[[self class] alloc] initWithClass:cls] autorelease];
+}
+
+- (id)initWithClass:(Class)cls {
+#if GTM_CONTAINERS_VALIDATE
+ if ((self = [super init])) {
+ if (!cls) {
+ _GTMDevLog(@"nil class");
+ [self release];
+ return nil;
+ }
+ cls_ = cls;
+ }
+ return self;
+#else // GTM_CONTAINERS_VALIDATE
+ [self release];
+ return nil;
+#endif // GTM_CONTAINERS_VALIDATE
+}
+
+- (BOOL)validateObject:(id)object forContainer:(id)container {
+ return [object isMemberOfClass:cls_];
+}
+@end
+
+@implementation GTMConformsToProtocolValidator
++ (id)validateAgainstProtocol:(Protocol*)prot {
+ return [[[[self class] alloc] initWithProtocol:prot] autorelease];
+}
+
+- (id)initWithProtocol:(Protocol*)prot {
+#if GTM_CONTAINERS_VALIDATE
+ if ((self = [super init])) {
+ if (!prot) {
+ _GTMDevLog(@"nil protocol");
+ [self release];
+ return nil;
+ }
+ prot_ = prot;
+ }
+ return self;
+#else // GTM_CONTAINERS_VALIDATE
+ [self release];
+ return nil;
+#endif // GTM_CONTAINERS_VALIDATE
+}
+
+- (BOOL)validateObject:(id)object forContainer:(id)container {
+ return [object conformsToProtocol:prot_];
+}
+@end
+
+@implementation GTMRespondsToSelectorValidator
++ (id)validateAgainstSelector:(SEL)sel {
+ return [[[[self class] alloc] initWithSelector:sel] autorelease];
+}
+
+- (id)initWithSelector:(SEL)sel {
+#if GTM_CONTAINERS_VALIDATE
+ if ((self = [super init])) {
+ if (!sel) {
+ _GTMDevLog(@"nil selector");
+ [self release];
+ return nil;
+ }
+ sel_ = sel;
+ }
+ return self;
+#else // GTM_CONTAINERS_VALIDATE
+ [self release];
+ return nil;
+#endif // GTM_CONTAINERS_VALIDATE
+}
+
+- (BOOL)validateObject:(id)object forContainer:(id)container {
+ return [object respondsToSelector:sel_];
+}
+@end
diff --git a/Foundation/GTMValidatingContainersTest.m b/Foundation/GTMValidatingContainersTest.m
new file mode 100644
index 0000000..a819787
--- /dev/null
+++ b/Foundation/GTMValidatingContainersTest.m
@@ -0,0 +1,367 @@
+//
+// GTMValidatingContainersTest.m
+//
+// Copyright 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 "GTMValidatingContainers.h"
+#import "GTMSenTestCase.h"
+#import "GTMUnitTestDevLog.h"
+
+#pragma mark Test Support Declarations
+@protocol GTMVCTestProtocol
+@end
+
+@interface GTMVCTestClass : NSObject
++ (id)instance;
+@end
+
+@interface GTMVCTestSubClass : GTMVCTestClass <GTMVCTestProtocol>
+- (void)foo;
+@end
+
+@interface GTMVCValidatorTests : GTMTestCase {
+ GTMVCTestClass *testClass_;
+ GTMVCTestSubClass *testSubClass_;
+}
+@end
+
+@interface GTMVCContainerTests : GTMVCValidatorTests {
+ GTMConformsToProtocolValidator *validator_;
+ SEL selector_;
+}
+@end
+
+@interface GTMVCArrayTests : GTMVCContainerTests
+@end
+
+@interface GTMVCDictionaryTests : GTMVCContainerTests
+@end
+
+@interface GTMVCSetTests : GTMVCContainerTests
+@end
+
+@interface GTMValidateContainerTests : GTMTestCase
+@end
+
+#pragma mark -
+#pragma mark Test Support Definitions
+
+@implementation GTMVCTestClass
++ (id)instance {
+ return [[[[self class] alloc] init] autorelease];
+}
+
+- (NSString*)description {
+ return NSStringFromClass([self class]);
+}
+@end
+
+@implementation GTMVCTestSubClass
+- (void)foo {
+}
+@end
+
+@implementation GTMVCContainerTests
+- (void)setUp {
+ [super setUp];
+ Protocol *prot = @protocol(GTMVCTestProtocol);
+ validator_ = [[GTMConformsToProtocolValidator alloc] initWithProtocol:prot];
+ selector_ = @selector(validateObject:forContainer:);
+}
+
+- (void)tearDown {
+ [validator_ release];
+ [super tearDown];
+}
+@end
+
+@implementation GTMVCValidatorTests
+- (void)setUp {
+ [super setUp];
+ testClass_ = [[GTMVCTestClass alloc] init];
+ testSubClass_ = [[GTMVCTestSubClass alloc] init];
+}
+
+- (void)tearDown {
+ [testClass_ release];
+ [testSubClass_ release];
+ [super tearDown];
+}
+
+- (void)testKindOfClassValidator {
+#if GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ [GTMUnitTestDevLog expectString:@"nil class"];
+ GTMKindOfClassValidator *validator;
+ validator = [GTMKindOfClassValidator validateAgainstClass:nil];
+ STAssertNil(validator, @"should be nil");
+
+ Class cls = [GTMVCTestClass class];
+ validator = [GTMKindOfClassValidator validateAgainstClass:cls];
+ STAssertNotNil(validator, @"should be valid");
+
+ BOOL isGood = [validator validateObject:testClass_ forContainer:nil];
+ STAssertTrue(isGood, @"should be validated");
+
+ isGood = [validator validateObject:testSubClass_ forContainer:nil];
+ STAssertTrue(isGood, @"should be validated");
+
+ isGood = [validator validateObject:[NSNumber numberWithInt:0]
+ forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+#else // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ GTMKindOfClassValidator *validator;
+ validator = [GTMKindOfClassValidator validateAgainstClass:nil];
+ STAssertNil(validator, @"should be nil");
+
+ Class cls = [GTMVCTestClass class];
+ validator = [GTMKindOfClassValidator validateAgainstClass:cls];
+ STAssertNil(validator, @"should be nil");
+#endif // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+}
+
+- (void)testMemberOfClassValidator {
+#if GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ [GTMUnitTestDevLog expectString:@"nil class"];
+ GTMMemberOfClassValidator *validator;
+ validator = [GTMMemberOfClassValidator validateAgainstClass:nil];
+ STAssertNil(validator, @"should be nil");
+
+ Class cls = [GTMVCTestClass class];
+ validator = [GTMMemberOfClassValidator validateAgainstClass:cls];
+ STAssertNotNil(validator, @"should be valid");
+
+ BOOL isGood = [validator validateObject:testClass_ forContainer:nil];
+ STAssertTrue(isGood, @"should be validated");
+
+ isGood = [validator validateObject:testSubClass_ forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+
+ isGood = [validator validateObject:nil forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+
+ isGood = [validator validateObject:[NSNumber numberWithInt:0]
+ forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+#else // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ GTMMemberOfClassValidator *validator;
+ validator = [GTMMemberOfClassValidator validateAgainstClass:nil];
+ STAssertNil(validator, @"should be nil");
+
+ Class cls = [GTMVCTestClass class];
+ validator = [GTMMemberOfClassValidator validateAgainstClass:cls];
+ STAssertNil(validator, @"should be nil");
+#endif // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+}
+
+- (void)testConformsToProtocolValidator {
+#if GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ [GTMUnitTestDevLog expectString:@"nil protocol"];
+ GTMConformsToProtocolValidator *validator;
+ validator = [GTMConformsToProtocolValidator validateAgainstProtocol:nil];
+ STAssertNil(validator, @"should be nil");
+
+ Protocol *prot = @protocol(GTMVCTestProtocol);
+ validator = [GTMConformsToProtocolValidator validateAgainstProtocol:prot];
+ STAssertNotNil(validator, @"should be valid");
+
+ BOOL isGood = [validator validateObject:testClass_ forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+
+ isGood = [validator validateObject:testSubClass_ forContainer:nil];
+ STAssertTrue(isGood, @"should succeed");
+
+ isGood = [validator validateObject:nil forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+#else // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ GTMConformsToProtocolValidator *validator;
+ validator = [GTMConformsToProtocolValidator validateAgainstProtocol:nil];
+ STAssertNil(validator, @"should be nil");
+
+ Protocol *prot = @protocol(GTMVCTestProtocol);
+ validator = [GTMConformsToProtocolValidator validateAgainstProtocol:prot];
+ STAssertNil(validator, @"should be nil");
+#endif // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+}
+
+- (void)testRespondsToSelectorValidator {
+#if GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ [GTMUnitTestDevLog expectString:@"nil selector"];
+ GTMRespondsToSelectorValidator *validator;
+ validator = [GTMRespondsToSelectorValidator validateAgainstSelector:nil];
+ STAssertNil(validator, @"should be nil");
+
+ SEL sel = @selector(foo);
+ validator = [GTMRespondsToSelectorValidator validateAgainstSelector:sel];
+ STAssertNotNil(validator, @"should be valid");
+
+ BOOL isGood = [validator validateObject:testClass_ forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+
+ isGood = [validator validateObject:testSubClass_ forContainer:nil];
+ STAssertTrue(isGood, @"should succeed");
+
+ isGood = [validator validateObject:nil forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+#else // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ GTMRespondsToSelectorValidator *validator;
+ validator = [GTMRespondsToSelectorValidator validateAgainstSelector:nil];
+ STAssertNil(validator, @"should be nil");
+
+ SEL sel = @selector(foo);
+ validator = [GTMRespondsToSelectorValidator validateAgainstSelector:sel];
+ STAssertNil(validator, @"should be nil");
+#endif // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+}
+
+
+@end
+
+@implementation GTMVCArrayTests
+- (void)testContainer {
+ GTMValidatingArray *array;
+ array = [GTMValidatingArray validatingArrayWithTarget:validator_
+ selector:selector_];
+ STAssertNotNil(array, @"should be valid");
+
+ array = [[[GTMValidatingArray alloc] initValidatingWithTarget:validator_
+ selector:selector_] autorelease];
+ STAssertNotNil(array, @"should be valid");
+
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for GTMValidatingArray .*"];
+ [array addObject:testSubClass_];
+ [array addObject:testClass_];
+ STAssertEquals([array objectAtIndex:0], testSubClass_, @"");
+
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for GTMValidatingArray .*"];
+ [array insertObject:testClass_ atIndex:0];
+ [array insertObject:testSubClass_ atIndex:0];
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for GTMValidatingArray .*"];
+ [array replaceObjectAtIndex:0 withObject:testClass_];
+ [array replaceObjectAtIndex:0 withObject:testSubClass_];
+ [array removeLastObject];
+ [array removeObjectAtIndex:0];
+ NSUInteger expectedCount = 0U;
+#if !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+ // If we're not validating, we don't expect any logs
+ [GTMUnitTestDevLog resetExpectedLogs];
+ expectedCount = 2U;
+#endif // !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+ STAssertEquals([array count], expectedCount, @"should have no objects left");
+
+}
+@end
+
+@implementation GTMVCDictionaryTests
+- (void)testContainer {
+ GTMValidatingDictionary *dictionary;
+ dictionary = [GTMValidatingDictionary validatingDictionaryWithTarget:validator_
+ selector:selector_];
+ STAssertNotNil(dictionary, @"should be valid");
+
+ dictionary = [[[GTMValidatingDictionary alloc] initValidatingWithTarget:validator_
+ selector:selector_] autorelease];
+ STAssertNotNil(dictionary, @"should be valid");
+
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for GTMValidatingDictionary .*"];
+ [dictionary setObject:testClass_ forKey:@"Key1"];
+ [dictionary setObject:testSubClass_ forKey:@"Key2"];
+ STAssertEquals([dictionary objectForKey:@"Key2"], testSubClass_, @"");
+ STAssertNotNil([dictionary keyEnumerator], @"");
+
+ [dictionary removeObjectForKey:@"Key2"];
+ [dictionary removeObjectForKey:@"Key1"];
+ STAssertEquals([dictionary count], (NSUInteger)0, @"should have no objects left");
+#if !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+ // If we're not validating, we don't expect any logs
+ [GTMUnitTestDevLog resetExpectedLogs];
+#endif // !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+}
+@end
+
+@implementation GTMVCSetTests
+- (void)testContainer {
+ GTMValidatingSet *set;
+ set = [GTMValidatingSet validatingSetWithTarget:validator_
+ selector:selector_];
+ STAssertNotNil(set, @"should be valid");
+
+ set = [[[GTMValidatingSet alloc] initValidatingWithTarget:validator_
+ selector:selector_] autorelease];
+ STAssertNotNil(set, @"should be valid");
+
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for GTMValidatingSet .*"];
+ [set addObject:testClass_];
+ [set addObject:testSubClass_];
+ STAssertEqualObjects([set member:testSubClass_], testSubClass_, @"");
+ STAssertNotNil([set objectEnumerator], @"");
+
+ [set removeObject:testClass_];
+ [set removeObject:testSubClass_];
+#if !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+ // If we're not validating, we don't expect any logs
+ [GTMUnitTestDevLog resetExpectedLogs];
+#endif // !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+ STAssertEquals([set count], (NSUInteger)0, @"should have no objects left");
+}
+@end
+
+@implementation GTMValidateContainerTests
+- (void)testValidatingContainers {
+ NSDictionary *homogenousDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [GTMVCTestSubClass instance], @"key1",
+ [GTMVCTestSubClass instance], @"key2",
+ nil];
+ NSDictionary *heterogenousDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [GTMVCTestClass instance], @"key1",
+ [GTMVCTestSubClass instance], @"key2",
+ nil];
+
+ // Test bad container
+ [GTMUnitTestDevLog expectPattern:@"container does not respont to -objectEnumerator: .*"];
+ _GTMValidateContainerContainsKindOfClass([NSString string],
+ [GTMVCTestSubClass class]);
+
+ _GTMValidateContainerContainsKindOfClass(homogenousDict,
+ [GTMVCTestSubClass class]);
+ _GTMValidateContainerContainsKindOfClass(heterogenousDict,
+ [GTMVCTestClass class]);
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for .*"];
+ _GTMValidateContainerContainsKindOfClass(heterogenousDict,
+ [GTMVCTestSubClass class]);
+
+ _GTMValidateContainerContainsMemberOfClass(homogenousDict,
+ [GTMVCTestSubClass class]);
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestSubClass failed container verification for .*"];
+ _GTMValidateContainerContainsMemberOfClass(heterogenousDict,
+ [GTMVCTestClass class]);
+
+ _GTMValidateContainerConformsToProtocol(homogenousDict,
+ @protocol(GTMVCTestProtocol));
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for .*"];
+ _GTMValidateContainerConformsToProtocol(heterogenousDict,
+ @protocol(GTMVCTestProtocol));
+
+ _GTMValidateContainerItemsRespondToSelector(homogenousDict,
+ @selector(foo));
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for .*"];
+ _GTMValidateContainerItemsRespondToSelector(heterogenousDict,
+ @selector(foo));
+#if !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+ // If we're not validating, we don't expect any logs
+ [GTMUnitTestDevLog resetExpectedLogs];
+#endif // !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+}
+@end
diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj
index bc8b288..087b886 100644
--- a/GTM.xcodeproj/project.pbxproj
+++ b/GTM.xcodeproj/project.pbxproj
@@ -42,6 +42,13 @@
33C374380DD8D44800E97817 /* GTMNSDictionary+URLArguments.h in Headers */ = {isa = PBXBuildFile; fileRef = 33C374360DD8D44800E97817 /* GTMNSDictionary+URLArguments.h */; settings = {ATTRIBUTES = (Public, ); }; };
33C374390DD8D44800E97817 /* GTMNSDictionary+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 33C374370DD8D44800E97817 /* GTMNSDictionary+URLArguments.m */; };
33C3745F0DD8D85B00E97817 /* GTMNSDictionary+URLArgumentsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 33C3745E0DD8D85B00E97817 /* GTMNSDictionary+URLArgumentsTest.m */; };
+ 8B1801A20E2533D500280961 /* GTMLargeTypeWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B1801A00E2533D500280961 /* GTMLargeTypeWindow.m */; };
+ 8B1801A30E2533D500280961 /* GTMLargeTypeWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B1801A10E2533D500280961 /* GTMLargeTypeWindow.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 8B1801A50E2533DB00280961 /* GTMLargeTypeWindowTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B1801A40E2533DB00280961 /* GTMLargeTypeWindowTest.m */; };
+ 8B1801AE0E25341B00280961 /* GTMLargeTypeWindowImageTest.gtmUTState in Resources */ = {isa = PBXBuildFile; fileRef = 8B1801A80E25341B00280961 /* GTMLargeTypeWindowImageTest.gtmUTState */; };
+ 8B1801B20E25341B00280961 /* GTMLargeTypeWindowLongTextTest.gtmUTState in Resources */ = {isa = PBXBuildFile; fileRef = 8B1801AC0E25341B00280961 /* GTMLargeTypeWindowLongTextTest.gtmUTState */; };
+ 8B1801B30E25341B00280961 /* GTMLargeTypeWindowShortTextTest.gtmUTState in Resources */ = {isa = PBXBuildFile; fileRef = 8B1801AD0E25341B00280961 /* GTMLargeTypeWindowShortTextTest.gtmUTState */; };
+ 8B1802420E25592200280961 /* GTMLargeTypeWindowMediumTextTest.gtmUTState in Resources */ = {isa = PBXBuildFile; fileRef = 8B1802410E25592200280961 /* GTMLargeTypeWindowMediumTextTest.gtmUTState */; };
8B2A9B200D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B2A9B1D0D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.m */; };
8B2A9B220D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B2A9B1F0D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.h */; settings = {ATTRIBUTES = (Public, ); }; };
8B2A9B240D8270DA00599386 /* GTMNSWorkspace+ScreenSaverTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B2A9B1E0D8270DA00599386 /* GTMNSWorkspace+ScreenSaverTest.m */; };
@@ -54,6 +61,9 @@
8B3344250DBF7A36009FD32C /* GTMNSAppleEventDescriptor+FoundationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B33441D0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+FoundationTest.m */; };
8B33455E0DBF8844009FD32C /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */; };
8B3345890DBF8A55009FD32C /* GTMNSAppleEvent+HandlerTest.applescript in AppleScript */ = {isa = PBXBuildFile; fileRef = 8B3344200DBF7A36009FD32C /* GTMNSAppleEvent+HandlerTest.applescript */; settings = {ATTRIBUTES = (Debug, ); }; };
+ 8B3AA9F10E033E23007E31B5 /* GTMValidatingContainers.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B3AA9EF0E033E23007E31B5 /* GTMValidatingContainers.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 8B3AA9F20E033E23007E31B5 /* GTMValidatingContainers.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA9F00E033E23007E31B5 /* GTMValidatingContainers.m */; };
+ 8B3AA9F80E033E5F007E31B5 /* GTMValidatingContainersTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA9F70E033E5F007E31B5 /* GTMValidatingContainersTest.m */; };
8B45A03A0DA46A2A001148C5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; };
8B45A0B80DA46A2F001148C5 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */; };
8B45A0D50DA46A57001148C5 /* GTMNSObject+UnitTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE29C0D198D36009257D2 /* GTMNSObject+UnitTesting.m */; };
@@ -98,8 +108,8 @@
8B7DCE190DFF39850017E983 /* GTMSenTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCE180DFF39850017E983 /* GTMSenTestCase.m */; };
8B7DCE1A0DFF39850017E983 /* GTMSenTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCE180DFF39850017E983 /* GTMSenTestCase.m */; };
8B7DCE1B0DFF39850017E983 /* GTMSenTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCE180DFF39850017E983 /* GTMSenTestCase.m */; };
- 8B7DCE6D0DFF459C0017E983 /* GTMHTTPFetcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F435E3930DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m */; };
8B7DCEF10E002C210017E983 /* GTMDevLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCBE10DFF18720017E983 /* GTMDevLog.m */; };
+ 8B7E35750E048E2D00EF70C8 /* GTMHTTPFetcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F435E3930DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m */; };
8BC045C20DAE899100C2D1CA /* GTMGeometryUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2800D198D0E009257D2 /* GTMGeometryUtilsTest.m */; };
8BC046B90DAE8C4B00C2D1CA /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BC046B80DAE8C4B00C2D1CA /* ApplicationServices.framework */; };
8BC04CD80DB003D800C2D1CA /* GTMMethodCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B6F31F40DA3489B0052CA40 /* GTMMethodCheck.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -115,11 +125,20 @@
F413908F0D75F63C00F72B31 /* GTMNSFileManager+Path.h in Headers */ = {isa = PBXBuildFile; fileRef = F413908C0D75F63C00F72B31 /* GTMNSFileManager+Path.h */; settings = {ATTRIBUTES = (Public, ); }; };
F41390900D75F63C00F72B31 /* GTMNSFileManager+Path.m in Sources */ = {isa = PBXBuildFile; fileRef = F413908D0D75F63C00F72B31 /* GTMNSFileManager+Path.m */; };
F41390920D75F64D00F72B31 /* GTMNSFileManager+PathTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F413908E0D75F63C00F72B31 /* GTMNSFileManager+PathTest.m */; };
+ F41A6F820E02EC3600788A6C /* GTMSignalHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = F41A6F7F0E02EC3600788A6C /* GTMSignalHandler.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ F41A6F830E02EC3600788A6C /* GTMSignalHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = F41A6F800E02EC3600788A6C /* GTMSignalHandler.m */; };
+ F41A6F850E02EC4D00788A6C /* GTMSignalHandlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F41A6F810E02EC3600788A6C /* GTMSignalHandlerTest.m */; };
F41D258B0DBD21A300774EEB /* GTMBase64.h in Headers */ = {isa = PBXBuildFile; fileRef = F41D25880DBD21A300774EEB /* GTMBase64.h */; settings = {ATTRIBUTES = (Public, ); }; };
F41D258C0DBD21A300774EEB /* GTMBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = F41D25890DBD21A300774EEB /* GTMBase64.m */; };
F41D258F0DBD21B900774EEB /* GTMBase64Test.m in Sources */ = {isa = PBXBuildFile; fileRef = F41D258A0DBD21A300774EEB /* GTMBase64Test.m */; };
F424F7010D9AA02B000B87EF /* GTMNSData+zlibTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F43E4E600D4E5EC90041161F /* GTMNSData+zlibTest.m */; };
F424F75F0D9AF019000B87EF /* GTMDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B1A16050D90344B00CA1E8E /* GTMDefines.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ F42597480E23AA57003BEA3E /* GTMNSString+Replace.h in Headers */ = {isa = PBXBuildFile; fileRef = F42597450E23AA57003BEA3E /* GTMNSString+Replace.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ F42597490E23AA57003BEA3E /* GTMNSString+Replace.m in Sources */ = {isa = PBXBuildFile; fileRef = F42597460E23AA57003BEA3E /* GTMNSString+Replace.m */; };
+ F425974B0E23AA94003BEA3E /* GTMNSString+ReplaceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F42597470E23AA57003BEA3E /* GTMNSString+ReplaceTest.m */; };
+ F42597790E23FE3A003BEA3E /* GTMNSString+FindFolder.h in Headers */ = {isa = PBXBuildFile; fileRef = F42597760E23FE3A003BEA3E /* GTMNSString+FindFolder.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ F425977A0E23FE3A003BEA3E /* GTMNSString+FindFolder.m in Sources */ = {isa = PBXBuildFile; fileRef = F42597770E23FE3A003BEA3E /* GTMNSString+FindFolder.m */; };
+ F425977F0E23FE43003BEA3E /* GTMNSString+FindFolderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F42597780E23FE3A003BEA3E /* GTMNSString+FindFolderTest.m */; };
F428FF030D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h in Headers */ = {isa = PBXBuildFile; fileRef = F428FEFF0D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h */; settings = {ATTRIBUTES = (Public, ); }; };
F428FF040D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F428FF000D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m */; };
F428FF090D48E57300382ED1 /* GTMNSBezierPath+CGPathTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F428FF010D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.m */; };
@@ -192,6 +211,9 @@
F4BC22D10DE4C39000108B7D /* GTMTestHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = F4BC22D00DE4C39000108B7D /* GTMTestHTTPServer.m */; };
F4CA854F0DAFAAF600B4AB10 /* GTMObjC2Runtime.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B6F32060DA34A1B0052CA40 /* GTMObjC2Runtime.h */; settings = {ATTRIBUTES = (Public, ); }; };
F4FF22780D9D4835003880AC /* GTMDebugSelectorValidation.h in Headers */ = {isa = PBXBuildFile; fileRef = F4FF22770D9D4835003880AC /* GTMDebugSelectorValidation.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ F9FD94630E1D31280005867E /* GTMPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F9FD945C0E1D30F80005867E /* GTMPath.m */; };
+ F9FD94640E1D312E0005867E /* GTMPathTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F9FD945D0E1D30F80005867E /* GTMPathTest.m */; };
+ F9FD94CD0E1D50450005867E /* GTMPath.h in Headers */ = {isa = PBXBuildFile; fileRef = F9FD945E0E1D30F80005867E /* GTMPath.h */; settings = {ATTRIBUTES = (Public, ); }; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -264,6 +286,13 @@
33C374360DD8D44800E97817 /* GTMNSDictionary+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSDictionary+URLArguments.h"; sourceTree = "<group>"; };
33C374370DD8D44800E97817 /* GTMNSDictionary+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArguments.m"; sourceTree = "<group>"; };
33C3745E0DD8D85B00E97817 /* GTMNSDictionary+URLArgumentsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArgumentsTest.m"; sourceTree = "<group>"; };
+ 8B1801A00E2533D500280961 /* GTMLargeTypeWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLargeTypeWindow.m; sourceTree = "<group>"; };
+ 8B1801A10E2533D500280961 /* GTMLargeTypeWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLargeTypeWindow.h; sourceTree = "<group>"; };
+ 8B1801A40E2533DB00280961 /* GTMLargeTypeWindowTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLargeTypeWindowTest.m; sourceTree = "<group>"; };
+ 8B1801A80E25341B00280961 /* GTMLargeTypeWindowImageTest.gtmUTState */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = GTMLargeTypeWindowImageTest.gtmUTState; sourceTree = "<group>"; };
+ 8B1801AC0E25341B00280961 /* GTMLargeTypeWindowLongTextTest.gtmUTState */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = GTMLargeTypeWindowLongTextTest.gtmUTState; sourceTree = "<group>"; };
+ 8B1801AD0E25341B00280961 /* GTMLargeTypeWindowShortTextTest.gtmUTState */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = GTMLargeTypeWindowShortTextTest.gtmUTState; sourceTree = "<group>"; };
+ 8B1802410E25592200280961 /* GTMLargeTypeWindowMediumTextTest.gtmUTState */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = GTMLargeTypeWindowMediumTextTest.gtmUTState; sourceTree = "<group>"; };
8B1A14E90D900BC800CA1E8E /* GTMNSObject+BindingUnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSObject+BindingUnitTesting.m"; sourceTree = "<group>"; };
8B1A14EA0D900BC800CA1E8E /* GTMNSObject+BindingUnitTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSObject+BindingUnitTesting.h"; sourceTree = "<group>"; };
8B1A16050D90344B00CA1E8E /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDefines.h; sourceTree = "<group>"; };
@@ -281,6 +310,9 @@
8B33441E0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Foundation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSAppleEventDescriptor+Foundation.m"; sourceTree = "<group>"; };
8B33441F0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Foundation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSAppleEventDescriptor+Foundation.h"; sourceTree = "<group>"; };
8B3344200DBF7A36009FD32C /* GTMNSAppleEvent+HandlerTest.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = "GTMNSAppleEvent+HandlerTest.applescript"; sourceTree = "<group>"; };
+ 8B3AA9EF0E033E23007E31B5 /* GTMValidatingContainers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMValidatingContainers.h; sourceTree = "<group>"; };
+ 8B3AA9F00E033E23007E31B5 /* GTMValidatingContainers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMValidatingContainers.m; sourceTree = "<group>"; };
+ 8B3AA9F70E033E5F007E31B5 /* GTMValidatingContainersTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMValidatingContainersTest.m; sourceTree = "<group>"; };
8B45A0280DA4696C001148C5 /* UnitTest - UnitTesting.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UnitTest - UnitTesting.octest"; sourceTree = BUILT_PRODUCTS_DIR; };
8B45A1990DA46AAA001148C5 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = /System/Library/Frameworks/QuartzCore.framework; sourceTree = "<absolute>"; };
8B45A2670DA498A0001148C5 /* GTMUnitTestingUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMUnitTestingUtilities.h; sourceTree = "<group>"; };
@@ -325,9 +357,18 @@
F413908C0D75F63C00F72B31 /* GTMNSFileManager+Path.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSFileManager+Path.h"; sourceTree = "<group>"; };
F413908D0D75F63C00F72B31 /* GTMNSFileManager+Path.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSFileManager+Path.m"; sourceTree = "<group>"; };
F413908E0D75F63C00F72B31 /* GTMNSFileManager+PathTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSFileManager+PathTest.m"; sourceTree = "<group>"; };
+ F41A6F7F0E02EC3600788A6C /* GTMSignalHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMSignalHandler.h; sourceTree = "<group>"; };
+ F41A6F800E02EC3600788A6C /* GTMSignalHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSignalHandler.m; sourceTree = "<group>"; };
+ F41A6F810E02EC3600788A6C /* GTMSignalHandlerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSignalHandlerTest.m; sourceTree = "<group>"; };
F41D25880DBD21A300774EEB /* GTMBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMBase64.h; sourceTree = "<group>"; };
F41D25890DBD21A300774EEB /* GTMBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMBase64.m; sourceTree = "<group>"; };
F41D258A0DBD21A300774EEB /* GTMBase64Test.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMBase64Test.m; sourceTree = "<group>"; };
+ F42597450E23AA57003BEA3E /* GTMNSString+Replace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+Replace.h"; sourceTree = "<group>"; };
+ F42597460E23AA57003BEA3E /* GTMNSString+Replace.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+Replace.m"; sourceTree = "<group>"; };
+ F42597470E23AA57003BEA3E /* GTMNSString+ReplaceTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+ReplaceTest.m"; sourceTree = "<group>"; };
+ F42597760E23FE3A003BEA3E /* GTMNSString+FindFolder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+FindFolder.h"; sourceTree = "<group>"; };
+ F42597770E23FE3A003BEA3E /* GTMNSString+FindFolder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+FindFolder.m"; sourceTree = "<group>"; };
+ F42597780E23FE3A003BEA3E /* GTMNSString+FindFolderTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+FindFolderTest.m"; sourceTree = "<group>"; };
F428FEFF0D48E55E00382ED1 /* GTMNSBezierPath+CGPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSBezierPath+CGPath.h"; sourceTree = "<group>"; };
F428FF000D48E55E00382ED1 /* GTMNSBezierPath+CGPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+CGPath.m"; sourceTree = "<group>"; };
F428FF010D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+CGPathTest.m"; sourceTree = "<group>"; };
@@ -420,6 +461,9 @@
F4CA864D0DB3ACD200B4AB10 /* SharedLibraryGCSupported.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SharedLibraryGCSupported.xcconfig; sourceTree = "<group>"; };
F4CA864E0DB3ACD200B4AB10 /* StaticLibraryGCSupported.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = StaticLibraryGCSupported.xcconfig; sourceTree = "<group>"; };
F4FF22770D9D4835003880AC /* GTMDebugSelectorValidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDebugSelectorValidation.h; sourceTree = "<group>"; };
+ F9FD945C0E1D30F80005867E /* GTMPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMPath.m; sourceTree = "<group>"; };
+ F9FD945D0E1D30F80005867E /* GTMPathTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMPathTest.m; sourceTree = "<group>"; };
+ F9FD945E0E1D30F80005867E /* GTMPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMPath.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -555,6 +599,10 @@
F435E4840DC8F3DC0069CDE8 /* TestData */ = {
isa = PBXGroup;
children = (
+ 8B1802410E25592200280961 /* GTMLargeTypeWindowMediumTextTest.gtmUTState */,
+ 8B1801A80E25341B00280961 /* GTMLargeTypeWindowImageTest.gtmUTState */,
+ 8B1801AC0E25341B00280961 /* GTMLargeTypeWindowLongTextTest.gtmUTState */,
+ 8B1801AD0E25341B00280961 /* GTMLargeTypeWindowShortTextTest.gtmUTState */,
8B7AD4980DABBB5800B84F4A /* GTMNSBezierPath+CGPathTest.tiff */,
F435DE7A0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.ppc64.tiff */,
F435DE7B0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.x86_64.tiff */,
@@ -597,6 +645,9 @@
children = (
F48FE27C0D198D0E009257D2 /* GTMDelegatingTableColumn.h */,
F48FE27D0D198D0E009257D2 /* GTMDelegatingTableColumn.m */,
+ 8B1801A10E2533D500280961 /* GTMLargeTypeWindow.h */,
+ 8B1801A00E2533D500280961 /* GTMLargeTypeWindow.m */,
+ 8B1801A40E2533DB00280961 /* GTMLargeTypeWindowTest.m */,
F43E44770D4918B20041161F /* GTMLinearRGBShading.h */,
F43E44780D4918B20041161F /* GTMLinearRGBShading.m */,
F43E44790D4918B20041161F /* GTMLinearRGBShadingTest.m */,
@@ -637,6 +688,9 @@
F41D25880DBD21A300774EEB /* GTMBase64.h */,
F41D25890DBD21A300774EEB /* GTMBase64.m */,
F41D258A0DBD21A300774EEB /* GTMBase64Test.m */,
+ 8BE2836B0DED0F130035B3F8 /* GTMFourCharCode.m */,
+ 8BE2836C0DED0F130035B3F8 /* GTMFourCharCodeTest.m */,
+ 8BE2836D0DED0F130035B3F8 /* GTMFourCharCode.h */,
F48FE27E0D198D0E009257D2 /* GTMGeometryUtils.h */,
F48FE27F0D198D0E009257D2 /* GTMGeometryUtils.m */,
F48FE2800D198D0E009257D2 /* GTMGeometryUtilsTest.m */,
@@ -659,12 +713,15 @@
F413908C0D75F63C00F72B31 /* GTMNSFileManager+Path.h */,
F413908D0D75F63C00F72B31 /* GTMNSFileManager+Path.m */,
F413908E0D75F63C00F72B31 /* GTMNSFileManager+PathTest.m */,
+ F42597760E23FE3A003BEA3E /* GTMNSString+FindFolder.h */,
+ F42597770E23FE3A003BEA3E /* GTMNSString+FindFolder.m */,
+ F42597780E23FE3A003BEA3E /* GTMNSString+FindFolderTest.m */,
F48FE28E0D198D24009257D2 /* GTMNSString+HTML.h */,
F48FE28F0D198D24009257D2 /* GTMNSString+HTML.m */,
F48FE2900D198D24009257D2 /* GTMNSString+HTMLTest.m */,
- 8BE2836B0DED0F130035B3F8 /* GTMFourCharCode.m */,
- 8BE2836C0DED0F130035B3F8 /* GTMFourCharCodeTest.m */,
- 8BE2836D0DED0F130035B3F8 /* GTMFourCharCode.h */,
+ F42597450E23AA57003BEA3E /* GTMNSString+Replace.h */,
+ F42597460E23AA57003BEA3E /* GTMNSString+Replace.m */,
+ F42597470E23AA57003BEA3E /* GTMNSString+ReplaceTest.m */,
33C372A40DD8A88500E97817 /* GTMNSString+URLArguments.h */,
33C372A50DD8A88500E97817 /* GTMNSString+URLArguments.m */,
33C372AE0DD8A8D700E97817 /* GTMNSString+URLArgumentsTest.m */,
@@ -678,6 +735,9 @@
8B6F32050DA34A1B0052CA40 /* GTMObjC2RuntimeTest.m */,
8B6F32060DA34A1B0052CA40 /* GTMObjC2Runtime.h */,
F48FE2910D198D24009257D2 /* GTMObjectSingleton.h */,
+ F9FD945E0E1D30F80005867E /* GTMPath.h */,
+ F9FD945C0E1D30F80005867E /* GTMPath.m */,
+ F9FD945D0E1D30F80005867E /* GTMPathTest.m */,
F435E27D0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.h */,
F435E27E0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.m */,
F437F55A0D50BC0A00F5C3A4 /* GTMRegex.h */,
@@ -686,12 +746,18 @@
F47A79850D746EE9002302AB /* GTMScriptRunner.h */,
F47A79860D746EE9002302AB /* GTMScriptRunner.m */,
F47A79870D746EE9002302AB /* GTMScriptRunnerTest.m */,
+ F41A6F7F0E02EC3600788A6C /* GTMSignalHandler.h */,
+ F41A6F800E02EC3600788A6C /* GTMSignalHandler.m */,
+ F41A6F810E02EC3600788A6C /* GTMSignalHandlerTest.m */,
F43122190DD4E3B800F45252 /* GTMStackTrace.c */,
F431221A0DD4E3B800F45252 /* GTMStackTrace.h */,
F431221B0DD4E3B800F45252 /* GTMStackTraceTest.m */,
F48FE2920D198D24009257D2 /* GTMSystemVersion.h */,
F48FE2930D198D24009257D2 /* GTMSystemVersion.m */,
F48FE2E10D198E4C009257D2 /* GTMSystemVersionTest.m */,
+ 8B3AA9EF0E033E23007E31B5 /* GTMValidatingContainers.h */,
+ 8B3AA9F00E033E23007E31B5 /* GTMValidatingContainers.m */,
+ 8B3AA9F70E033E5F007E31B5 /* GTMValidatingContainersTest.m */,
F435E4B50DC903E20069CDE8 /* TestData */,
);
path = Foundation;
@@ -806,6 +872,12 @@
8BE281B10DEC7E930035B3F8 /* GTMNSAppleEventDescriptor+Handler.h in Headers */,
8BE281B20DEC7E930035B3F8 /* GTMNSAppleEventDescriptor+Foundation.h in Headers */,
8BE283730DED13AB0035B3F8 /* GTMFourCharCode.h in Headers */,
+ F41A6F820E02EC3600788A6C /* GTMSignalHandler.h in Headers */,
+ 8B3AA9F10E033E23007E31B5 /* GTMValidatingContainers.h in Headers */,
+ F9FD94CD0E1D50450005867E /* GTMPath.h in Headers */,
+ F42597480E23AA57003BEA3E /* GTMNSString+Replace.h in Headers */,
+ F42597790E23FE3A003BEA3E /* GTMNSString+FindFolder.h in Headers */,
+ 8B1801A30E2533D500280961 /* GTMLargeTypeWindow.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -814,7 +886,7 @@
/* Begin PBXLegacyTarget section */
F41A6EE00E02DB4F00788A6C /* Build GTM All SDKs */ = {
isa = PBXLegacyTarget;
- buildArgumentsString = "GTM \"$(BuildAllSDKs)\" \"$(DEVELOPER_BIN_DIR)\" \"$(ACTION)\"";
+ buildArgumentsString = GTM;
buildConfigurationList = F41A6EED0E02DB6800788A6C /* Build configuration list for PBXLegacyTarget "Build GTM All SDKs" */;
buildPhases = (
);
@@ -828,7 +900,7 @@
};
F41A6EF80E02DCFC00788A6C /* All UnitTests All SDKs */ = {
isa = PBXLegacyTarget;
- buildArgumentsString = "\"All UnitTests\" \"$(BuildAllSDKs)\" \"$(DEVELOPER_BIN_DIR)\" \"$(ACTION)\"";
+ buildArgumentsString = "\"All UnitTests\"";
buildConfigurationList = F41A6F070E02DD1500788A6C /* Build configuration list for PBXLegacyTarget "All UnitTests All SDKs" */;
buildPhases = (
);
@@ -1015,6 +1087,10 @@
F435DE7C0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.ppc64.tiff in Resources */,
F435DE7D0DC0B6580069CDE8 /* GTMNSBezierPath+CGPathTest.x86_64.tiff in Resources */,
F435DE8B0DC0B7620069CDE8 /* GTMNSBezierPath+RoundRectTest.ppc64.tiff in Resources */,
+ 8B1801AE0E25341B00280961 /* GTMLargeTypeWindowImageTest.gtmUTState in Resources */,
+ 8B1801B20E25341B00280961 /* GTMLargeTypeWindowLongTextTest.gtmUTState in Resources */,
+ 8B1801B30E25341B00280961 /* GTMLargeTypeWindowShortTextTest.gtmUTState in Resources */,
+ 8B1802420E25592200280961 /* GTMLargeTypeWindowMediumTextTest.gtmUTState in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1122,8 +1198,13 @@
8B7DCBC20DFF0F7F0017E983 /* GTMMethodCheck.m in Sources */,
8B7DCBEE0DFF1A4F0017E983 /* GTMUnitTestDevLog.m in Sources */,
8B7DCE1A0DFF39850017E983 /* GTMSenTestCase.m in Sources */,
- 8B7DCE6D0DFF459C0017E983 /* GTMHTTPFetcherTest.m in Sources */,
8B2C21B50E00883F00B5ECB1 /* GTMObjC2Runtime.m in Sources */,
+ F41A6F850E02EC4D00788A6C /* GTMSignalHandlerTest.m in Sources */,
+ 8B3AA9F80E033E5F007E31B5 /* GTMValidatingContainersTest.m in Sources */,
+ 8B7E35750E048E2D00EF70C8 /* GTMHTTPFetcherTest.m in Sources */,
+ F9FD94640E1D312E0005867E /* GTMPathTest.m in Sources */,
+ F425974B0E23AA94003BEA3E /* GTMNSString+ReplaceTest.m in Sources */,
+ F425977F0E23FE43003BEA3E /* GTMNSString+FindFolderTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1162,6 +1243,12 @@
8B7DCBD30DFF16070017E983 /* GTMNSAppleEventDescriptor+Handler.m in Sources */,
8B7DCBD40DFF16070017E983 /* GTMNSAppleEventDescriptor+Foundation.m in Sources */,
8B7DCBE20DFF18720017E983 /* GTMDevLog.m in Sources */,
+ F41A6F830E02EC3600788A6C /* GTMSignalHandler.m in Sources */,
+ 8B3AA9F20E033E23007E31B5 /* GTMValidatingContainers.m in Sources */,
+ F9FD94630E1D31280005867E /* GTMPath.m in Sources */,
+ F42597490E23AA57003BEA3E /* GTMNSString+Replace.m in Sources */,
+ F425977A0E23FE3A003BEA3E /* GTMNSString+FindFolder.m in Sources */,
+ 8B1801A20E2533D500280961 /* GTMLargeTypeWindow.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1181,6 +1268,7 @@
8B7DCBED0DFF1A4F0017E983 /* GTMUnitTestDevLog.m in Sources */,
8B7DCE190DFF39850017E983 /* GTMSenTestCase.m in Sources */,
8B2C21B60E00884000B5ECB1 /* GTMObjC2Runtime.m in Sources */,
+ 8B1801A50E2533DB00280961 /* GTMLargeTypeWindowTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/GTMDefines.h b/GTMDefines.h
index 32ae642..25b554d 100644
--- a/GTMDefines.h
+++ b/GTMDefines.h
@@ -35,6 +35,13 @@
# define GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING 0
#endif // GTM_HTTPFETCHER_ENABLE_INPUTSTREAM_LOGGING
+// By setting the GTM_CONTAINERS_VALIDATION_FAILED_LOG and
+// GTM_CONTAINERS_VALIDATION_FAILED_ASSERT macros you can control what happens
+// when a validation fails. If you implement your own validators, you may want
+// to control their internals using the same macros for consistency.
+#ifndef GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+#define GTM_CONTAINERS_VALIDATION_FAILED_ASSERT 0
+#endif
// _GTMDevLog & _GTMDevAssert
//
diff --git a/GTMiPhone.xcodeproj/project.pbxproj b/GTMiPhone.xcodeproj/project.pbxproj
index b0f39ed..060b9ac 100644
--- a/GTMiPhone.xcodeproj/project.pbxproj
+++ b/GTMiPhone.xcodeproj/project.pbxproj
@@ -26,6 +26,17 @@
1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; };
67A7820C0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 67A7820B0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m */; };
8B308BCE0DAD0B8400183556 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B308BCD0DAD0B8400183556 /* QuartzCore.framework */; };
+ 8B3AA8F30E032FC7007E31B5 /* GTMNSString+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA8F10E032FC7007E31B5 /* GTMNSString+URLArguments.m */; };
+ 8B3AA8F40E032FC7007E31B5 /* GTMNSString+URLArgumentsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA8F20E032FC7007E31B5 /* GTMNSString+URLArgumentsTest.m */; };
+ 8B3AA9220E033624007E31B5 /* GTMHTTPFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA91D0E033624007E31B5 /* GTMHTTPFetcher.m */; };
+ 8B3AA9230E033624007E31B5 /* GTMHTTPFetcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA91E0E033624007E31B5 /* GTMHTTPFetcherTest.m */; };
+ 8B3AA9240E033624007E31B5 /* GTMHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA9200E033624007E31B5 /* GTMHTTPServer.m */; };
+ 8B3AA9250E033624007E31B5 /* GTMHTTPServerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA9210E033624007E31B5 /* GTMHTTPServerTest.m */; };
+ 8B3AA9290E033647007E31B5 /* GTMTestHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA9280E033647007E31B5 /* GTMTestHTTPServer.m */; };
+ 8B3AA9340E0336AC007E31B5 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B3AA9330E0336AC007E31B5 /* CFNetwork.framework */; };
+ 8B3AA96A0E0337E4007E31B5 /* GTMHTTPFetcherTestPage.html in Resources */ = {isa = PBXBuildFile; fileRef = 8B3AA9690E0337E4007E31B5 /* GTMHTTPFetcherTestPage.html */; };
+ 8B41EC0F0E0711D40040CF9F /* GTMValidatingContainersTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B41EC0C0E0711D40040CF9F /* GTMValidatingContainersTest.m */; };
+ 8B41EC100E0711D40040CF9F /* GTMValidatingContainers.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B41EC0D0E0711D40040CF9F /* GTMValidatingContainers.m */; };
8B5547CA0DB3BBF20014CC1C /* GTMUIKit+UnitTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5547C70DB3BBF20014CC1C /* GTMUIKit+UnitTesting.m */; };
8B5547CB0DB3BBF20014CC1C /* GTMUIKit+UnitTestingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5547C90DB3BBF20014CC1C /* GTMUIKit+UnitTestingTest.m */; };
8B7DCEAA0DFF4C760017E983 /* GTMDevLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCEA90DFF4C760017E983 /* GTMDevLog.m */; };
@@ -82,6 +93,22 @@
67A7820B0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMIPhoneUnitTestDelegate.m; sourceTree = "<group>"; };
8B308AF40DAD070C00183556 /* RunIPhoneUnitTest.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = RunIPhoneUnitTest.sh; path = UnitTesting/RunIPhoneUnitTest.sh; sourceTree = "<group>"; };
8B308BCD0DAD0B8400183556 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
+ 8B3AA8F00E032FC7007E31B5 /* GTMNSString+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+URLArguments.h"; sourceTree = "<group>"; };
+ 8B3AA8F10E032FC7007E31B5 /* GTMNSString+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+URLArguments.m"; sourceTree = "<group>"; };
+ 8B3AA8F20E032FC7007E31B5 /* GTMNSString+URLArgumentsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+URLArgumentsTest.m"; sourceTree = "<group>"; };
+ 8B3AA91C0E033624007E31B5 /* GTMHTTPFetcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPFetcher.h; sourceTree = "<group>"; };
+ 8B3AA91D0E033624007E31B5 /* GTMHTTPFetcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcher.m; sourceTree = "<group>"; };
+ 8B3AA91E0E033624007E31B5 /* GTMHTTPFetcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcherTest.m; sourceTree = "<group>"; };
+ 8B3AA91F0E033624007E31B5 /* GTMHTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPServer.h; sourceTree = "<group>"; };
+ 8B3AA9200E033624007E31B5 /* GTMHTTPServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPServer.m; sourceTree = "<group>"; };
+ 8B3AA9210E033624007E31B5 /* GTMHTTPServerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPServerTest.m; sourceTree = "<group>"; };
+ 8B3AA9270E033647007E31B5 /* GTMTestHTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMTestHTTPServer.h; sourceTree = "<group>"; };
+ 8B3AA9280E033647007E31B5 /* GTMTestHTTPServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMTestHTTPServer.m; sourceTree = "<group>"; };
+ 8B3AA9330E0336AC007E31B5 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
+ 8B3AA9690E0337E4007E31B5 /* GTMHTTPFetcherTestPage.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = GTMHTTPFetcherTestPage.html; sourceTree = "<group>"; };
+ 8B41EC0C0E0711D40040CF9F /* GTMValidatingContainersTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMValidatingContainersTest.m; sourceTree = "<group>"; };
+ 8B41EC0D0E0711D40040CF9F /* GTMValidatingContainers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMValidatingContainers.m; sourceTree = "<group>"; };
+ 8B41EC0E0E0711D40040CF9F /* GTMValidatingContainers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMValidatingContainers.h; sourceTree = "<group>"; };
8B5547C70DB3BBF20014CC1C /* GTMUIKit+UnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMUIKit+UnitTesting.m"; sourceTree = "<group>"; };
8B5547C80DB3BBF20014CC1C /* GTMUIKit+UnitTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMUIKit+UnitTesting.h"; sourceTree = "<group>"; };
8B5547C90DB3BBF20014CC1C /* GTMUIKit+UnitTestingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMUIKit+UnitTestingTest.m"; sourceTree = "<group>"; };
@@ -153,6 +180,7 @@
1D3623EC0D0F72F000981E51 /* CoreGraphics.framework in Frameworks */,
8B308BCE0DAD0B8400183556 /* QuartzCore.framework in Frameworks */,
8BC04D480DB0088500C2D1CA /* libz.dylib in Frameworks */,
+ 8B3AA9340E0336AC007E31B5 /* CFNetwork.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -187,6 +215,7 @@
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 8B3AA9330E0336AC007E31B5 /* CFNetwork.framework */,
8BC04D470DB0088500C2D1CA /* libz.dylib */,
8B308BCD0DAD0B8400183556 /* QuartzCore.framework */,
1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */,
@@ -196,6 +225,14 @@
name = Frameworks;
sourceTree = "<group>";
};
+ 8B3AA9680E0337E4007E31B5 /* TestData */ = {
+ isa = PBXGroup;
+ children = (
+ 8B3AA9690E0337E4007E31B5 /* GTMHTTPFetcherTestPage.html */,
+ );
+ path = TestData;
+ sourceTree = "<group>";
+ };
8BC047760DAE928A00C2D1CA /* Foundation */ = {
isa = PBXGroup;
children = (
@@ -209,6 +246,12 @@
F439ADED0DBD3C4000BE9B91 /* GTMGeometryUtils.h */,
F439ADEE0DBD3C4000BE9B91 /* GTMGeometryUtils.m */,
F439ADEF0DBD3C4000BE9B91 /* GTMGeometryUtilsTest.m */,
+ 8B3AA91C0E033624007E31B5 /* GTMHTTPFetcher.h */,
+ 8B3AA91D0E033624007E31B5 /* GTMHTTPFetcher.m */,
+ 8B3AA91E0E033624007E31B5 /* GTMHTTPFetcherTest.m */,
+ 8B3AA91F0E033624007E31B5 /* GTMHTTPServer.h */,
+ 8B3AA9200E033624007E31B5 /* GTMHTTPServer.m */,
+ 8B3AA9210E033624007E31B5 /* GTMHTTPServerTest.m */,
8BC0477E0DAE928A00C2D1CA /* GTMNSData+zlib.h */,
8BC0477F0DAE928A00C2D1CA /* GTMNSData+zlib.m */,
8BC047800DAE928A00C2D1CA /* GTMNSData+zlibTest.m */,
@@ -221,6 +264,9 @@
8BC047870DAE928A00C2D1CA /* GTMNSString+HTML.h */,
8BC047880DAE928A00C2D1CA /* GTMNSString+HTML.m */,
8BC047890DAE928A00C2D1CA /* GTMNSString+HTMLTest.m */,
+ 8B3AA8F00E032FC7007E31B5 /* GTMNSString+URLArguments.h */,
+ 8B3AA8F10E032FC7007E31B5 /* GTMNSString+URLArguments.m */,
+ 8B3AA8F20E032FC7007E31B5 /* GTMNSString+URLArgumentsTest.m */,
8BC0478A0DAE928A00C2D1CA /* GTMNSString+XML.h */,
8BC0478B0DAE928A00C2D1CA /* GTMNSString+XML.m */,
8BC0478C0DAE928A00C2D1CA /* GTMNSString+XMLTest.m */,
@@ -232,6 +278,10 @@
8BC04A740DAF145200C2D1CA /* GTMSystemVersion.m */,
8BC04A6F0DAF144200C2D1CA /* GTMSystemVersion.h */,
8BC04A710DAF144700C2D1CA /* GTMSystemVersionTest.m */,
+ 8B41EC0D0E0711D40040CF9F /* GTMValidatingContainers.m */,
+ 8B41EC0E0E0711D40040CF9F /* GTMValidatingContainers.h */,
+ 8B41EC0C0E0711D40040CF9F /* GTMValidatingContainersTest.m */,
+ 8B3AA9680E0337E4007E31B5 /* TestData */,
);
path = Foundation;
sourceTree = "<group>";
@@ -251,6 +301,8 @@
8BC0479F0DAE928A00C2D1CA /* UnitTesting */ = {
isa = PBXGroup;
children = (
+ 8B3AA9270E033647007E31B5 /* GTMTestHTTPServer.h */,
+ 8B3AA9280E033647007E31B5 /* GTMTestHTTPServer.m */,
8BC047A00DAE928A00C2D1CA /* GTMCALayer+UnitTesting.h */,
8BC047A10DAE928A00C2D1CA /* GTMCALayer+UnitTesting.m */,
8B5547C70DB3BBF20014CC1C /* GTMUIKit+UnitTesting.m */,
@@ -353,6 +405,7 @@
8BC0486B0DAE928A00C2D1CA /* GTMUIViewUnitTestingTest.gtmUTState in Resources */,
8BC0486C0DAE928A00C2D1CA /* GTMUIViewUnitTestingTest.png in Resources */,
8BC04DE80DB023D400C2D1CA /* ReleaseNotes.txt in Resources */,
+ 8B3AA96A0E0337E4007E31B5 /* GTMHTTPFetcherTestPage.html in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -410,6 +463,15 @@
8B7DCEAA0DFF4C760017E983 /* GTMDevLog.m in Sources */,
8B7DCEAD0DFF4CA60017E983 /* GTMUnitTestDevLog.m in Sources */,
67A7820C0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m in Sources */,
+ 8B3AA8F30E032FC7007E31B5 /* GTMNSString+URLArguments.m in Sources */,
+ 8B3AA8F40E032FC7007E31B5 /* GTMNSString+URLArgumentsTest.m in Sources */,
+ 8B3AA9220E033624007E31B5 /* GTMHTTPFetcher.m in Sources */,
+ 8B3AA9230E033624007E31B5 /* GTMHTTPFetcherTest.m in Sources */,
+ 8B3AA9240E033624007E31B5 /* GTMHTTPServer.m in Sources */,
+ 8B3AA9250E033624007E31B5 /* GTMHTTPServerTest.m in Sources */,
+ 8B3AA9290E033647007E31B5 /* GTMTestHTTPServer.m in Sources */,
+ 8B41EC0F0E0711D40040CF9F /* GTMValidatingContainersTest.m in Sources */,
+ 8B41EC100E0711D40040CF9F /* GTMValidatingContainers.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt
index d730a07..5bd20be 100644
--- a/ReleaseNotes.txt
+++ b/ReleaseNotes.txt
@@ -3,6 +3,39 @@ Google Toolbox for Mac Release Notes
Project site: http://code.google.com/p/google-toolbox-for-mac/
Discussion group: http://groups.google.com/group/google-toolbox-for-mac
+
+Release ?.?.?
+Changes since 1.5.1
+
+- Added GTMSignalHandler for simple signal handling (via kqueue/runloop).
+
+- Fixed up GTMIPhoneUnitTestDelegate to be pickier about which tests it runs
+
+- Added GTMNSString+URLArguments to GTMiPhone
+
+- Added GTMHTTPFetcher and GTMHTTPServer to GTMiPhone
+
+- Made sure that build would work with iPhone device attached, and that all
+ tests run directly on the phone.
+
+- Added GTMValidatingContainers which are a set of mutable container classes
+ that allow you to have a selector on a target that is called to verify that
+ the objects being put into the container are valid. This can be controlled
+ at compile time so that you don't take the performance hit in a release build.
+
+- Added GTMPath, which represents an existing absolute path on the file system.
+ It also makes it very easy to contruct new paths in the file system as well
+ as whole directory hierarchies.
+
+- Added GTMNSString+Replace for a common replacement need.
+
+- Added NSString+FindFolder for two commen helpers for building paths to common
+ locations.
+
+- Added GTMLargeTypeWindow for doing display windows similar to Address Book
+ Large Type display for phone numbers.
+
+
Release 1.5.1
Changes since 1.5.0
16-June-2008
diff --git a/UnitTesting/GTMIPhoneUnitTestDelegate.h b/UnitTesting/GTMIPhoneUnitTestDelegate.h
index 21ba84c..f12a9b6 100644
--- a/UnitTesting/GTMIPhoneUnitTestDelegate.h
+++ b/UnitTesting/GTMIPhoneUnitTestDelegate.h
@@ -16,6 +16,8 @@
// the License.
//
+#import <Foundation/Foundation.h>
+
// Application delegate that runs all test methods in registered classes
// extending SenTestCase. The application is terminated afterwards.
// You can also run the tests directly from your application by invoking
diff --git a/UnitTesting/GTMIPhoneUnitTestDelegate.m b/UnitTesting/GTMIPhoneUnitTestDelegate.m
index 28d2fe0..f3df548 100644
--- a/UnitTesting/GTMIPhoneUnitTestDelegate.m
+++ b/UnitTesting/GTMIPhoneUnitTestDelegate.m
@@ -68,7 +68,8 @@ static int MethodSort(const void *a, const void *b) {
// the default output.
- (void)runTests {
int count = objc_getClassList(NULL, 0);
- Class *classes = (Class*)malloc(sizeof(Class) * count);
+ NSMutableData *classData = [NSMutableData dataWithLength:sizeof(Class) * count];
+ Class *classes = (Class*)[classData mutableBytes];
_GTMDevAssert(classes, @"Couldn't allocate class list");
objc_getClassList(classes, count);
int suiteSuccesses = 0;
@@ -97,15 +98,44 @@ static int MethodSort(const void *a, const void *b) {
fixtureName);
unsigned int methodCount;
Method *methods = class_copyMethodList(currClass, &methodCount);
+ if (!methods) {
+ // If the class contains no methods, head on to the next class
+ NSString *output = [NSString stringWithFormat:@"Test Suite '%@' "
+ "finished at %@.\nExecuted 0 tests, with 0 "
+ "failures (0 unexpected) in 0 (0) seconds\n",
+ fixtureName, fixtureStartDate];
+
+ fputs([output UTF8String], stderr);
+ continue;
+ }
+ // This handles disposing of methods for us even if an
+ // exception should fly.
+ [NSData dataWithBytesNoCopy:methods
+ length:sizeof(Method) * methodCount];
// Sort our methods so they are called in Alphabetical order just
// because we can.
qsort(methods, methodCount, sizeof(Method), MethodSort);
for (size_t j = 0; j < methodCount; ++j) {
Method currMethod = methods[j];
SEL sel = method_getName(currMethod);
+ char *returnType = NULL;
const char *name = sel_getName(sel);
- // If it starts with test, run it.
+ // If it starts with test, takes 2 args (target and sel) and returns
+ // void run it.
if (strstr(name, "test") == name) {
+ returnType = method_copyReturnType(currMethod);
+ if (returnType) {
+ // This handles disposing of returnType for us even if an
+ // exception should fly. Length +1 for the terminator, not that
+ // the length really matters here, as we never reference inside
+ // the data block.
+ [NSData dataWithBytesNoCopy:returnType
+ length:strlen(returnType) + 1];
+ }
+ }
+ if (returnType // True if name starts with "test"
+ && strcmp(returnType, @encode(void)) == 0
+ && method_getNumberOfArguments(currMethod) == 2) {
fixtureTotal += 1;
BOOL failed = NO;
NSDate *caseStartDate = [NSDate date];
@@ -127,9 +157,6 @@ static int MethodSort(const void *a, const void *b) {
fflush(stderr);
}
}
- if (methods) {
- free(methods);
- }
[testcase release];
NSDate *fixtureEndDate = [NSDate date];
NSTimeInterval fixtureEndTime = [fixtureEndDate timeIntervalSinceDate:fixtureStartDate];
diff --git a/UnitTesting/GTMNSObject+UnitTesting.m b/UnitTesting/GTMNSObject+UnitTesting.m
index dd36cb5..80450b3 100644
--- a/UnitTesting/GTMNSObject+UnitTesting.m
+++ b/UnitTesting/GTMNSObject+UnitTesting.m
@@ -66,7 +66,9 @@ BOOL GTMIsObjectImageEqualToImageNamed(id object,
if (isGood) {
isGood = [object gtm_compareWithImageAt:aPath diffImage:&diff];
}
- if (!isGood) {
+ if (isGood) {
+ CGImageRelease(diff);
+ } else {
if (aPath) {
filename = [filename stringByAppendingString:@"_Failed"];
}
@@ -869,7 +871,10 @@ static NSString *gGTMUnitTestSaveToDirectory = nil;
CFRelease(fileContext);
free(imageData);
CFRelease(imageContext);
- }
+ } else {
+ CFRelease(fileContext);
+ CFRelease(imageContext);
+ }
CFRelease(imageRep);
CFRelease(fileRep);
}
diff --git a/UnitTesting/RunIPhoneUnitTest.sh b/UnitTesting/RunIPhoneUnitTest.sh
index 50709d3..9c09622 100755
--- a/UnitTesting/RunIPhoneUnitTest.sh
+++ b/UnitTesting/RunIPhoneUnitTest.sh
@@ -14,11 +14,16 @@
# License for the specific language governing permissions and limitations under
# the License.
#
-# Runs all unittests through the iPhone simulator
+# Runs all unittests through the iPhone simulator. We don't handle running them
+# on the device. To run on the device just choose "run".
-export DYLD_ROOT_PATH="$SDKROOT"
-export DYLD_FRAMEWORK_PATH="$CONFIGURATION_BUILD_DIR"
-export IPHONE_SIMULATOR_ROOT="$SDKROOT"
-export CFFIXED_USER_HOME="$USER_LIBRARY_DIR/Application Support/iPhone Simulator/User"
-"$TARGET_BUILD_DIR/$EXECUTABLE_PATH" -RegisterForSystemEvents
+if [ "$IPHONEOS_DEPLOYMENT_TARGET" == "" ]; then
+ export DYLD_ROOT_PATH="$SDKROOT"
+ export DYLD_FRAMEWORK_PATH="$CONFIGURATION_BUILD_DIR"
+ export IPHONE_SIMULATOR_ROOT="$SDKROOT"
+ export CFFIXED_USER_HOME="$USER_LIBRARY_DIR/Application Support/iPhone Simulator/User"
+ "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" -RegisterForSystemEvents
+else
+ echo "note: Skipping running of unittests for device build."
+fi
exit 0