diff options
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 |