aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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