From c53ec5520e39096e0804ce8d89a21378c0904481 Mon Sep 17 00:00:00 2001 From: thomasvl Date: Fri, 13 Jun 2008 19:21:50 +0000 Subject: Landing a log of AppleScript/AppleEvent support code. Landing GTMHTTPServer as a simple server but mainly for use in unittesting. _GTMCompileAssert for doing compile time assertions to GTMDefines.h Lots of improvments for UnitTesting, Dave's gonna put up a wiki page shortly with the full details of what can be done. --- AppKit/GTMLinearRGBShading.m | 16 +- AppKit/GTMLinearRGBShadingTest.m | 32 +- AppKit/GTMLoginItems.m | 16 +- AppKit/GTMLoginItemsTest.m | 2 +- AppKit/GTMNSBezierPath+CGPathTest.m | 7 +- AppKit/GTMNSBezierPath+RoundRectTest.m | 6 +- AppKit/GTMNSBezierPath+Shading.m | 4 +- AppKit/GTMNSBezierPath+ShadingTest.m | 7 +- AppKit/GTMNSWorkspace+ScreenSaver.m | 8 +- AppKit/GTMNSWorkspace+ScreenSaverTest.m | 4 +- BuildScripts/BuildAllSDKs.sh | 54 ++ BuildingAndUsing.txt | 18 + DebugUtils/GTMDebugSelectorValidation.h | 22 +- DebugUtils/GTMDevLog.m | 34 ++ DebugUtils/GTMMethodCheck.m | 13 +- DebugUtils/GTMMethodCheckTest.m | 28 +- Foundation/GTMBase64Test.m | 2 +- Foundation/GTMCalculatedRange.m | 31 +- Foundation/GTMCalculatedRangeTest.m | 2 +- Foundation/GTMFourCharCode.h | 51 ++ Foundation/GTMFourCharCode.m | 98 ++++ Foundation/GTMFourCharCodeTest.m | 82 +++ Foundation/GTMGeometryUtils.h | 326 ++++++----- Foundation/GTMGeometryUtils.m | 34 +- Foundation/GTMGeometryUtilsTest.m | 128 +++-- Foundation/GTMHTTPFetcher.m | 9 +- Foundation/GTMHTTPFetcherTest.m | 124 +---- Foundation/GTMHTTPServer.h | 133 +++++ Foundation/GTMHTTPServer.m | 584 +++++++++++++++++++ Foundation/GTMHTTPServerTest.m | 573 +++++++++++++++++++ Foundation/GTMNSAppleEvent+HandlerTest.applescript | 39 ++ Foundation/GTMNSAppleEventDescriptor+Foundation.h | 91 +++ Foundation/GTMNSAppleEventDescriptor+Foundation.m | 488 ++++++++++++++++ .../GTMNSAppleEventDescriptor+FoundationTest.m | 619 +++++++++++++++++++++ Foundation/GTMNSAppleEventDescriptor+Handler.h | 40 ++ Foundation/GTMNSAppleEventDescriptor+Handler.m | 130 +++++ Foundation/GTMNSAppleEventDescriptor+HandlerTest.m | 70 +++ Foundation/GTMNSAppleScript+Handler.h | 59 ++ Foundation/GTMNSAppleScript+Handler.m | 283 ++++++++++ Foundation/GTMNSAppleScript+HandlerTest.m | 331 +++++++++++ Foundation/GTMNSData+zlibTest.m | 136 +++-- Foundation/GTMNSDictionary+URLArguments.m | 2 +- Foundation/GTMNSDictionary+URLArgumentsTest.m | 2 +- Foundation/GTMNSEnumerator+Filter.m | 34 +- Foundation/GTMNSEnumerator+FilterTest.m | 2 +- Foundation/GTMNSFileManager+PathTest.m | 10 +- Foundation/GTMNSString+HTML.m | 16 +- Foundation/GTMNSString+HTMLTest.m | 2 +- Foundation/GTMNSString+URLArgumentsTest.m | 2 +- Foundation/GTMNSString+XML.m | 12 +- Foundation/GTMNSString+XMLTest.m | 2 +- Foundation/GTMObjC2RuntimeTest.m | 2 +- Foundation/GTMRegex.m | 21 +- Foundation/GTMRegexTest.m | 9 +- Foundation/GTMScriptRunnerTest.m | 28 +- Foundation/GTMStackTrace.c | 2 +- Foundation/GTMStackTraceTest.m | 2 +- Foundation/GTMSystemVersionTest.m | 2 +- Foundation/TestData/GTMHTTPFetcherTestServer | 274 --------- GTM.xcodeproj/project.pbxproj | 320 ++++++++++- GTMDefines.h | 30 +- GTM_Prefix.pch | 17 + GTMiPhone-Info.plist | 4 +- GTMiPhone.xcodeproj/project.pbxproj | 28 +- GTMiPhone_Prefix.pch | 20 - ReleaseNotes.txt | 19 + UnitTesting/GTMAppKit+UnitTesting.h | 9 +- UnitTesting/GTMIPhoneUnitTestDelegate.h | 29 + UnitTesting/GTMIPhoneUnitTestDelegate.m | 160 ++++++ UnitTesting/GTMIPhoneUnitTestMain.m | 161 +----- UnitTesting/GTMNSObject+UnitTesting.h | 2 +- UnitTesting/GTMNSObject+UnitTesting.m | 46 +- UnitTesting/GTMSenTestCase.h | 20 +- UnitTesting/GTMSenTestCase.m | 75 ++- UnitTesting/GTMTestHTTPServer.h | 39 ++ UnitTesting/GTMTestHTTPServer.m | 166 ++++++ UnitTesting/GTMUIKit+UnitTesting.h | 4 +- UnitTesting/GTMUnitTestDevLog.h | 70 +++ UnitTesting/GTMUnitTestDevLog.m | 154 +++++ UnitTesting/GTMUnitTestingBindingTest.m | 4 +- UnitTesting/GTMUnitTestingTest.m | 14 +- UnitTesting/GTMUnitTestingUtilities.h | 1 + UnitTesting/GTMUnitTestingUtilities.m | 2 +- XcodeConfig/Project/DebugLeopardOrLater.xcconfig | 2 +- XcodeConfig/Project/DebugTigerOrLater.xcconfig | 2 +- XcodeConfig/Project/DebugiPhone.xcconfig | 2 +- XcodeConfig/Project/ReleaseLeopardOrLater.xcconfig | 2 +- XcodeConfig/Project/ReleaseTigerOrLater.xcconfig | 2 +- XcodeConfig/Project/ReleaseiPhone.xcconfig | 2 +- XcodeConfig/xcconfigs-readme.txt | 34 +- 90 files changed, 5537 insertions(+), 1061 deletions(-) create mode 100755 BuildScripts/BuildAllSDKs.sh create mode 100644 BuildingAndUsing.txt create mode 100644 DebugUtils/GTMDevLog.m create mode 100644 Foundation/GTMFourCharCode.h create mode 100644 Foundation/GTMFourCharCode.m create mode 100644 Foundation/GTMFourCharCodeTest.m create mode 100644 Foundation/GTMHTTPServer.h create mode 100644 Foundation/GTMHTTPServer.m create mode 100644 Foundation/GTMHTTPServerTest.m create mode 100644 Foundation/GTMNSAppleEvent+HandlerTest.applescript create mode 100644 Foundation/GTMNSAppleEventDescriptor+Foundation.h create mode 100644 Foundation/GTMNSAppleEventDescriptor+Foundation.m create mode 100644 Foundation/GTMNSAppleEventDescriptor+FoundationTest.m create mode 100644 Foundation/GTMNSAppleEventDescriptor+Handler.h create mode 100644 Foundation/GTMNSAppleEventDescriptor+Handler.m create mode 100644 Foundation/GTMNSAppleEventDescriptor+HandlerTest.m create mode 100644 Foundation/GTMNSAppleScript+Handler.h create mode 100644 Foundation/GTMNSAppleScript+Handler.m create mode 100644 Foundation/GTMNSAppleScript+HandlerTest.m delete mode 100755 Foundation/TestData/GTMHTTPFetcherTestServer delete mode 100644 GTMiPhone_Prefix.pch create mode 100644 UnitTesting/GTMIPhoneUnitTestDelegate.h create mode 100644 UnitTesting/GTMIPhoneUnitTestDelegate.m create mode 100644 UnitTesting/GTMTestHTTPServer.h create mode 100644 UnitTesting/GTMTestHTTPServer.m create mode 100644 UnitTesting/GTMUnitTestDevLog.h create mode 100644 UnitTesting/GTMUnitTestDevLog.m diff --git a/AppKit/GTMLinearRGBShading.m b/AppKit/GTMLinearRGBShading.m index 18af0e0..ef13986 100644 --- a/AppKit/GTMLinearRGBShading.m +++ b/AppKit/GTMLinearRGBShading.m @@ -88,28 +88,28 @@ static void cShadeFunction(void *info, const CGFloat *inPos, CGFloat *outVals); // Calculate a linear value based on our stops - (id)valueAtPosition:(CGFloat)position { - NSUInteger index = 0; + NSUInteger positionIndex = 0; NSUInteger colorCount = [self stopCount]; CGFloat stop1Position = 0.0; - NSColor *stop1Color = [self stopAtIndex:index position:&stop1Position]; - index += 1; + NSColor *stop1Color = [self stopAtIndex:positionIndex position:&stop1Position]; + positionIndex += 1; CGFloat stop2Position = 0.0; NSColor *stop2Color = nil; NSColor *theColor = nil; if (colorCount > 1) { - stop2Color = [self stopAtIndex:index position:&stop2Position]; - index += 1; + stop2Color = [self stopAtIndex:positionIndex position:&stop2Position]; + positionIndex += 1; } else { // if we only have one value, that's what we return stop2Position = stop1Position; stop2Color = stop1Color; } - while (index < colorCount && stop2Position < position) { + while (positionIndex < colorCount && stop2Position < position) { stop1Color = stop2Color; stop1Position = stop2Position; - stop2Color = [self stopAtIndex:index position:&stop2Position]; - index += 1; + stop2Color = [self stopAtIndex:positionIndex position:&stop2Position]; + positionIndex += 1; } if (position <= stop1Position) { diff --git a/AppKit/GTMLinearRGBShadingTest.m b/AppKit/GTMLinearRGBShadingTest.m index 119fa79..cb65572 100644 --- a/AppKit/GTMLinearRGBShadingTest.m +++ b/AppKit/GTMLinearRGBShadingTest.m @@ -20,7 +20,7 @@ #import "GTMSenTestCase.h" #import "GTMLinearRGBShading.h" -@interface GTMLinearRGBShadingTest : SenTestCase +@interface GTMLinearRGBShadingTest : GTMTestCase @end @implementation GTMLinearRGBShadingTest @@ -36,10 +36,10 @@ STAssertNotNil(theShading,nil); STAssertEquals([theShading stopCount], (NSUInteger)2, nil); CGFloat *theColor = (CGFloat*)[theShading valueAtPosition: 0.5]; - STAssertTrue(theColor[0] == [purple redComponent] && - theColor[1] == [purple greenComponent] && - theColor[2] == [purple blueComponent] && - theColor[3] == [purple alphaComponent], nil); + STAssertEqualsWithAccuracy(theColor[0], [purple redComponent], 0.001, nil); + STAssertEqualsWithAccuracy(theColor[1], [purple greenComponent], 0.001, nil); + STAssertEqualsWithAccuracy(theColor[2], [purple blueComponent], 0.001, nil); + STAssertEqualsWithAccuracy(theColor[3], [purple alphaComponent], 0.001, nil); } - (void)testShadingWith { @@ -49,11 +49,12 @@ CGFloat thePositions[kColorCount]; const CGFloat kColorIncrement = 1.0 / kColorCount; for (NSUInteger i = 0; i < kColorCount; i++) { - thePositions[i] = kColorIncrement * i; - theColors[i] = [NSColor colorWithCalibratedRed:kColorIncrement * i - green:kColorIncrement * i - blue:kColorIncrement * i - alpha:kColorIncrement * i]; + CGFloat newValue = kColorIncrement * i; + thePositions[i] = newValue; + theColors[i] = [NSColor colorWithCalibratedRed:newValue + green:newValue + blue:newValue + alpha:newValue]; } GTMLinearRGBShading *theShading = [GTMLinearRGBShading shadingWithColors:theColors @@ -61,11 +62,12 @@ atPositions:thePositions count:kColorCount]; for (NSUInteger i = 0; i < kColorCount; i++) { - CGFloat *theColor = (CGFloat*)[theShading valueAtPosition: kColorIncrement * i]; - STAssertTrue(theColor[0] == kColorIncrement * i && - theColor[1] == kColorIncrement * i && - theColor[2] == kColorIncrement * i && - theColor[3] == kColorIncrement * i, nil); + CGFloat newValue = kColorIncrement * i; + CGFloat *theColor = (CGFloat*)[theShading valueAtPosition:newValue]; + STAssertEqualsWithAccuracy(theColor[0], newValue, 0.001, nil); + STAssertEqualsWithAccuracy(theColor[1], newValue, 0.001, nil); + STAssertEqualsWithAccuracy(theColor[2], newValue, 0.001, nil); + STAssertEqualsWithAccuracy(theColor[3], newValue, 0.001, nil); } } diff --git a/AppKit/GTMLoginItems.m b/AppKit/GTMLoginItems.m index cc34c11..61a2120 100644 --- a/AppKit/GTMLoginItems.m +++ b/AppKit/GTMLoginItems.m @@ -136,18 +136,18 @@ NSString * const kGTMLoginItemsHiddenKey = @"Hide"; + (BOOL)pathInLoginItems:(NSString *)path { NSArray *loginItems = [self loginItems:nil]; - NSInteger index = [self indexOfLoginItemWithValue:path - forKey:kGTMLoginItemsPathKey - loginItems:loginItems]; - return (index != NSNotFound) ? YES : NO; + NSInteger itemIndex = [self indexOfLoginItemWithValue:path + forKey:kGTMLoginItemsPathKey + loginItems:loginItems]; + return (itemIndex != NSNotFound) ? YES : NO; } + (BOOL)itemWithNameInLoginItems:(NSString *)name { NSArray *loginItems = [self loginItems:nil]; - NSInteger index = [self indexOfLoginItemWithValue:name - forKey:kGTMLoginItemsNameKey - loginItems:loginItems]; - return (index != NSNotFound) ? YES : NO; + NSInteger itemIndex = [self indexOfLoginItemWithValue:name + forKey:kGTMLoginItemsNameKey + loginItems:loginItems]; + return (itemIndex != NSNotFound) ? YES : NO; } + (void)addPathToLoginItems:(NSString*)path hide:(BOOL)hide { diff --git a/AppKit/GTMLoginItemsTest.m b/AppKit/GTMLoginItemsTest.m index 05a93a1..ec08a85 100644 --- a/AppKit/GTMLoginItemsTest.m +++ b/AppKit/GTMLoginItemsTest.m @@ -26,7 +26,7 @@ #define TESTS_ENABLED 0 -@interface GTMLoginItemsTest : SenTestCase +@interface GTMLoginItemsTest : GTMTestCase @end #if TESTS_ENABLED diff --git a/AppKit/GTMNSBezierPath+CGPathTest.m b/AppKit/GTMNSBezierPath+CGPathTest.m index e22ee07..eb86353 100644 --- a/AppKit/GTMNSBezierPath+CGPathTest.m +++ b/AppKit/GTMNSBezierPath+CGPathTest.m @@ -23,13 +23,16 @@ #import "GTMAppKit+UnitTesting.h" #import "GTMSenTestCase.h" -@interface GTMNSBezierPath_CGPathTest : SenTestCase +@interface GTMNSBezierPath_CGPathTest : GTMTestCase @end @implementation GTMNSBezierPath_CGPathTest - (void)testCreateCGPath { - GTMAssertDrawingEqualToFile(self, NSMakeSize(100, 100), @"GTMNSBezierPath+CGPathTest", nil, nil); + GTMAssertDrawingEqualToImageNamed(self, + NSMakeSize(100, 100), + @"GTMNSBezierPath+CGPathTest", + nil, nil); } diff --git a/AppKit/GTMNSBezierPath+RoundRectTest.m b/AppKit/GTMNSBezierPath+RoundRectTest.m index c67c4b3..ad64fd4 100644 --- a/AppKit/GTMNSBezierPath+RoundRectTest.m +++ b/AppKit/GTMNSBezierPath+RoundRectTest.m @@ -22,14 +22,14 @@ #import "GTMNSBezierPath+RoundRect.h" #import "GTMAppKit+UnitTesting.h" -@interface GTMNSBezierPath_RoundRectTest : SenTestCase +@interface GTMNSBezierPath_RoundRectTest : GTMTestCase @end @implementation GTMNSBezierPath_RoundRectTest - (void)testRoundRects { - GTMAssertDrawingEqualToFile(self, NSMakeSize(330, 430), - @"GTMNSBezierPath+RoundRectTest", nil, nil); + GTMAssertDrawingEqualToImageNamed(self, NSMakeSize(330, 430), + @"GTMNSBezierPath+RoundRectTest", nil, nil); } // Draws all of our tests so that we can compare this to our stored TIFF file. diff --git a/AppKit/GTMNSBezierPath+Shading.m b/AppKit/GTMNSBezierPath+Shading.m index 4ab9ee3..eff8dfb 100644 --- a/AppKit/GTMNSBezierPath+Shading.m +++ b/AppKit/GTMNSBezierPath+Shading.m @@ -132,9 +132,9 @@ NSPoint newPoint = pointB; CGFloat x = (pointB.x - pointA.x); CGFloat y = (pointB.y - pointA.y); - if (x == 0.0) { + if (fpclassify(x) == FP_ZERO) { newPoint.y += length; - } else if (y == 0.0) { + } else if (fpclassify(y) == FP_ZERO) { newPoint.x += length; } else { #if CGFLOAT_IS_DOUBLE diff --git a/AppKit/GTMNSBezierPath+ShadingTest.m b/AppKit/GTMNSBezierPath+ShadingTest.m index 9fa7a8f..5634cf3 100644 --- a/AppKit/GTMNSBezierPath+ShadingTest.m +++ b/AppKit/GTMNSBezierPath+ShadingTest.m @@ -24,16 +24,17 @@ #import "GTMAppKit+UnitTesting.h" #import "GTMNSBezierPath+Shading.h" -@interface GTMNSBezierPath_ShadingTest : SenTestCase +@interface GTMNSBezierPath_ShadingTest : GTMTestCase @end @implementation GTMNSBezierPath_ShadingTest - (void)testShadings { - GTMAssertDrawingEqualToFile(self, NSMakeSize(200, 200), @"GTMNSBezierPath+ShadingTest", nil, nil); + GTMAssertDrawingEqualToImageNamed(self, + NSMakeSize(200, 200), + @"GTMNSBezierPath+ShadingTest", nil, nil); } - - (void)gtm_unitTestViewDrawRect:(NSRect)rect contextInfo:(void*)contextInfo { NSColor *theColorArray[] = { [NSColor blueColor], diff --git a/AppKit/GTMNSWorkspace+ScreenSaver.m b/AppKit/GTMNSWorkspace+ScreenSaver.m index 9e1eb6a..7ca5d70 100644 --- a/AppKit/GTMNSWorkspace+ScreenSaver.m +++ b/AppKit/GTMNSWorkspace+ScreenSaver.m @@ -20,6 +20,7 @@ #import #import "GTMNSWorkspace+ScreenSaver.h" #import "GTMDefines.h" +#import "GTMGarbageCollection.h" // Interesting class descriptions extracted from ScreenSaver.framework using // class-dump. Note that these are "not documented". @@ -114,15 +115,14 @@ kProcessDictionaryIncludeAllInformationMask); require(cfProcessInfo, CantGetFrontProcess); - - NSString *bundlePath = [(NSDictionary*)cfProcessInfo objectForKey:@"BundlePath"]; - + NSDictionary *processInfo = [GTMNSMakeCollectable(cfProcessInfo) autorelease]; + NSString *bundlePath = [processInfo objectForKey:@"BundlePath"]; + // ScreenSaverEngine is the frontmost app if the screen saver is actually // running Security Agent is the frontmost app if the "enter password" // dialog is showing answer = [bundlePath hasSuffix:@"ScreenSaverEngine.app"] || [bundlePath hasSuffix:@"SecurityAgent.app"]; - CFRelease(cfProcessInfo); // COV_NF_END } CantGetFrontProcess: diff --git a/AppKit/GTMNSWorkspace+ScreenSaverTest.m b/AppKit/GTMNSWorkspace+ScreenSaverTest.m index 2dfcbb9..923efe9 100644 --- a/AppKit/GTMNSWorkspace+ScreenSaverTest.m +++ b/AppKit/GTMNSWorkspace+ScreenSaverTest.m @@ -15,10 +15,10 @@ // License for the specific language governing permissions and limitations under // the License. // -#import +#import "GTMSenTestCase.h" #import "GTMNSWorkspace+ScreenSaver.h" -@interface GTMNSWorkspace_ScreenSaverTest : SenTestCase +@interface GTMNSWorkspace_ScreenSaverTest : GTMTestCase @end diff --git a/BuildScripts/BuildAllSDKs.sh b/BuildScripts/BuildAllSDKs.sh new file mode 100755 index 0000000..0c9bbe8 --- /dev/null +++ b/BuildScripts/BuildAllSDKs.sh @@ -0,0 +1,54 @@ +#!/bin/sh +# 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. +# +# 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. + +PROJECT_TARGET="$1" + +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" +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}" +} + +# 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?) diff --git a/BuildingAndUsing.txt b/BuildingAndUsing.txt new file mode 100644 index 0000000..67bbd79 --- /dev/null +++ b/BuildingAndUsing.txt @@ -0,0 +1,18 @@ +The GTM project provides a Framework target and Unit Testing targets. + +Unit Testing Targets - + +These targets are there to make sure all of GTM is working, and reflect the +hierarchy of the source tree. + +Building the "All UnitTests" target will build all of the unittests for the +current configuration included in the GTM package. We require that "All +UnitTests" builds cleanly before we will accept any code submissions. + +Framework Target - + +These targets are there to make it easy for anyone to quickly add GTM sources +to their own project to start using it or playing around with it. However, +the intent is for a developer to manually add the sources to their project in +the end to reduce the total size of their product (since they only have to +include the parts of GTM they need). diff --git a/DebugUtils/GTMDebugSelectorValidation.h b/DebugUtils/GTMDebugSelectorValidation.h index 82d00e8..19d8b8e 100644 --- a/DebugUtils/GTMDebugSelectorValidation.h +++ b/DebugUtils/GTMDebugSelectorValidation.h @@ -5,6 +5,8 @@ // function that takes an object and selector to invoke, you should call: // // GTMAssertSelectorNilOrImplementedWithArguments(obj, sel, @encode(arg1type), ..., NULL) +// or +// GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(obj, sel, @encode(returnType), @encode(arg1type), ..., NULL) // // This will then validate that the selector is defined and using the right // type(s), this can help catch errors much earlier then waiting for the @@ -31,12 +33,12 @@ #import #import "GTMDefines.h" -static void GTMAssertSelectorNilOrImplementedWithArguments(id obj, SEL sel, ...) { - +static void GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(id obj, SEL sel, const char *retType, ...) { + // verify that the object's selector is implemented with the proper // number and type of arguments va_list argList; - va_start(argList, sel); + va_start(argList, retType); if (obj && sel) { // check that the selector is implemented @@ -71,14 +73,28 @@ static void GTMAssertSelectorNilOrImplementedWithArguments(id obj, SEL sel, ...) NSStringFromClass([obj class]), NSStringFromSelector(sel), (argCount - 2)); + + // if asked, validate the return type + if (retType && (strcmp("gtm_skip_return_test", retType) != 0)) { + const char *foundRetType = [sig methodReturnType]; + _GTMDevAssert(0 == strncmp(foundRetType, retType, strlen(retType)), + @"\"%@\" selector \"%@\" return type should be type %s", + NSStringFromClass([obj class]), + NSStringFromSelector(sel), + retType); + } } va_end(argList); } +#define GTMAssertSelectorNilOrImplementedWithArguments(obj, sel, ...) \ + GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments((obj), (sel), "gtm_skip_return_test", __VA_ARGS__) + #else // DEBUG // make it go away if not debug +#define GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(obj, sel, retType, ...) do { } while (0) #define GTMAssertSelectorNilOrImplementedWithArguments(obj, sel, ...) do { } while (0) #endif // DEBUG diff --git a/DebugUtils/GTMDevLog.m b/DebugUtils/GTMDevLog.m new file mode 100644 index 0000000..af30713 --- /dev/null +++ b/DebugUtils/GTMDevLog.m @@ -0,0 +1,34 @@ +// +// GTMDevLog.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. +// + +#include "GTMUnitTestDevLog.h" + +// This is the logging function that is called by default when building +// GTMFramework. If it can find GTMUnitTestDevLog class it will use it, +// otherwise it falls onto NSLog. +void _GTMUnittestDevLog(NSString *format, ...) { + Class devLogClass = NSClassFromString(@"GTMUnitTestDevLog"); + va_list argList; + va_start(argList, format); + if (devLogClass) { + [devLogClass log:format args:argList]; + } else { + NSLogv(format, argList); + } + va_end(argList); +} diff --git a/DebugUtils/GTMMethodCheck.m b/DebugUtils/GTMMethodCheck.m index f91b1a9..300022b 100644 --- a/DebugUtils/GTMMethodCheck.m +++ b/DebugUtils/GTMMethodCheck.m @@ -41,9 +41,14 @@ static BOOL ConformsToNSObjectProtocol(Class cls) { // work for you. // Some classes (like _NSZombie) start with _NS. // On Leopard we have to look for CFObject as well. - if ((strncmp(className, "NS", 2) == 0) || - (strncmp(className, "_NS", 3) == 0) || - (strcmp(className, "CFObject") == 0)) { + // On iPhone we check Object as well + if ((strncmp(className, "NS", 2) == 0) + || (strncmp(className, "_NS", 3) == 0) + || (strcmp(className, "CFObject") == 0) +#if GTM_IPHONE_SDK + || (strcmp(className, "Object") == 0) +#endif + ) { return YES; } @@ -88,10 +93,12 @@ void GTMMethodCheckMethodChecker(void) { // @protocol(NSObject), or else we will tumble into a _objc_msgForward // recursive loop when we try and call a function by name. if (!ConformsToNSObjectProtocol(cls)) { + // COV_NF_START _GTMDevLog(@"GTMMethodCheckMethodChecker: Class %s does not conform to " "@protocol(NSObject), so won't be checked", class_getName(cls)); continue; + // COV_NF_END } // Since we are looking for a class method (+xxGMMethodCheckMethod...) // we need to query the isa pointer to see what methods it support, but diff --git a/DebugUtils/GTMMethodCheckTest.m b/DebugUtils/GTMMethodCheckTest.m index b5e7e6b..91b7300 100644 --- a/DebugUtils/GTMMethodCheckTest.m +++ b/DebugUtils/GTMMethodCheckTest.m @@ -20,33 +20,13 @@ static BOOL gTestCheckVar = NO; -// This is a contrived class that doesn't inherit from NSObject, but does -// implement some of it's functionality to force test a case in -// GTMMethodCheck. -@interface GTMClassThatDoesntInheritFromNSObject -+ (BOOL)instancesRespondToSelector:(SEL)selector; -+ (BOOL)respondsToSelector:(SEL)selector; -@end - -@implementation GTMClassThatDoesntInheritFromNSObject -GTM_METHOD_CHECK(GTMClassThatDoesntInheritFromNSObject, GTMMethodCheckTestMethod); -- (void)GTMMethodCheckTestMethod { -} -+ (BOOL)instancesRespondToSelector:(SEL)selector { - return YES; -} - -+ (BOOL)respondsToSelector:(SEL)selector { - return YES; -} -@end - -@interface GTMMethodCheckTest : SenTestCase +@interface GTMMethodCheckTest : GTMTestCase ++ (void)GTMMethodCheckTestClassMethod; @end @implementation GTMMethodCheckTest -GTM_METHOD_CHECK(GTMMethodCheckTest, GTMMethodCheckTestMethod); -GTM_METHOD_CHECK(GTMMethodCheckTest, GTMMethodCheckTestClassMethod); +GTM_METHOD_CHECK(GTMMethodCheckTest, GTMMethodCheckTestMethod); // COV_NF_LINE +GTM_METHOD_CHECK(GTMMethodCheckTest, GTMMethodCheckTestClassMethod); // COV_NF_LINE - (void)GTMMethodCheckTestMethod { } diff --git a/Foundation/GTMBase64Test.m b/Foundation/GTMBase64Test.m index 358c6ec..084ea1e 100644 --- a/Foundation/GTMBase64Test.m +++ b/Foundation/GTMBase64Test.m @@ -38,7 +38,7 @@ static BOOL NoEqualChar(NSData *data) { return YES; } -@interface GTMBase64Test : SenTestCase +@interface GTMBase64Test : GTMTestCase @end @implementation GTMBase64Test diff --git a/Foundation/GTMCalculatedRange.m b/Foundation/GTMCalculatedRange.m index 435ad65..218b811 100644 --- a/Foundation/GTMCalculatedRange.m +++ b/Foundation/GTMCalculatedRange.m @@ -30,6 +30,9 @@ - (CGFloat)position; @end +CG_INLINE BOOL FPEqual(CGFloat a, CGFloat b) { + return (fpclassify(a - b) == FP_ZERO); +} @implementation GTMCalculatedRangeStopPrivate + (id)stopWithObject:(id)item position:(CGFloat)inPosition { @@ -77,52 +80,52 @@ } - (void)insertStop:(id)item atPosition:(CGFloat)position { - NSUInteger index = 0; + NSUInteger positionIndex = 0; NSEnumerator *theEnumerator = [storage_ objectEnumerator]; GTMCalculatedRangeStopPrivate *theStop; while (nil != (theStop = [theEnumerator nextObject])) { if ([theStop position] < position) { - index += 1; + positionIndex += 1; } - else if ([theStop position] == position) { + else if (FPEqual([theStop position], position)) { // remove and stop the enum since we just modified the object - [storage_ removeObjectAtIndex:index]; + [storage_ removeObjectAtIndex:positionIndex]; break; } } [storage_ insertObject:[GTMCalculatedRangeStopPrivate stopWithObject:item position:position] - atIndex:index]; + atIndex:positionIndex]; } - (BOOL)removeStopAtPosition:(CGFloat)position { - NSUInteger index = 0; + NSUInteger positionIndex = 0; BOOL foundStop = NO; NSEnumerator *theEnumerator = [storage_ objectEnumerator]; GTMCalculatedRangeStopPrivate *theStop; while (nil != (theStop = [theEnumerator nextObject])) { - if ([theStop position] == position) { + if (FPEqual([theStop position], position)) { break; } else { - index += 1; + positionIndex += 1; } } if (nil != theStop) { - [self removeStopAtIndex:index]; + [self removeStopAtIndex:positionIndex]; foundStop = YES; } return foundStop; } -- (void)removeStopAtIndex:(NSUInteger)index { - [storage_ removeObjectAtIndex:index]; +- (void)removeStopAtIndex:(NSUInteger)positionIndex { + [storage_ removeObjectAtIndex:positionIndex]; } - (NSUInteger)stopCount { return [storage_ count]; } -- (id)stopAtIndex:(NSUInteger)index position:(CGFloat*)outPosition { - GTMCalculatedRangeStopPrivate *theStop = [storage_ objectAtIndex:index]; +- (id)stopAtIndex:(NSUInteger)positionIndex position:(CGFloat*)outPosition { + GTMCalculatedRangeStopPrivate *theStop = [storage_ objectAtIndex:positionIndex]; if (nil != outPosition) { *outPosition = [theStop position]; } @@ -134,7 +137,7 @@ GTMCalculatedRangeStopPrivate *theStop; NSEnumerator *theEnumerator = [storage_ objectEnumerator]; while (nil != (theStop = [theEnumerator nextObject])) { - if ([theStop position] == position) { + if (FPEqual([theStop position], position)) { theValue = [theStop item]; break; } diff --git a/Foundation/GTMCalculatedRangeTest.m b/Foundation/GTMCalculatedRangeTest.m index 1d716c8..1790072 100644 --- a/Foundation/GTMCalculatedRangeTest.m +++ b/Foundation/GTMCalculatedRangeTest.m @@ -19,7 +19,7 @@ #import "GTMCalculatedRange.h" #import "GTMSenTestCase.h" -@interface GTMCalculatedRangeTest : SenTestCase { +@interface GTMCalculatedRangeTest : GTMTestCase { GTMCalculatedRange *range_; } @end diff --git a/Foundation/GTMFourCharCode.h b/Foundation/GTMFourCharCode.h new file mode 100644 index 0000000..90853b5 --- /dev/null +++ b/Foundation/GTMFourCharCode.h @@ -0,0 +1,51 @@ +// +// GTMFourCharCode +// Wrapper for FourCharCodes +// +// 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 + +// FourCharCodes are OSTypes, ResTypes etc. This class wraps them if +// you need to store them in dictionaries etc. +@interface GTMFourCharCode : NSObject { + FourCharCode code_; +} + +// returns a string for a FourCharCode ++ (id)stringWithFourCharCode:(FourCharCode)code; + +// String must be 4 chars or less, or you will get nil back. ++ (id)fourCharCodeWithString:(NSString*)string; ++ (id)fourCharCodeWithFourCharCode:(FourCharCode)code; + +// String must be 4 chars or less, or you will get nil back. +- (id)initWithString:(NSString*)string; + +// Designated Initializer +- (id)initWithFourCharCode:(FourCharCode)code; + +// Returns 'APPL' for "APPL" +- (FourCharCode)fourCharCode; + +// For FourCharCode of 'APPL' returns "APPL". For 1 returns "\0\0\0\1" +- (NSString*)stringValue; + +// For FourCharCode of "APPL" returns an NSNumber with 1095782476 (0x4150504C). +// For 1 returns 1. +- (NSNumber*)numberValue; + +@end diff --git a/Foundation/GTMFourCharCode.m b/Foundation/GTMFourCharCode.m new file mode 100644 index 0000000..6b67cae --- /dev/null +++ b/Foundation/GTMFourCharCode.m @@ -0,0 +1,98 @@ +// +// GTMFourCharCode.m +// Wrapper for FourCharCodes +// +// 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 "GTMDefines.h" +#import "GTMFourCharCode.h" +#import "GTMGarbageCollection.h" +#import + +@implementation GTMFourCharCode + ++ (id)stringWithFourCharCode:(FourCharCode)code { + return [GTMNSMakeCollectable(UTCreateStringForOSType(code)) autorelease]; +} + ++ (id)fourCharCodeWithString:(NSString*)string { + return [[[self alloc] initWithString:string] autorelease]; +} + ++ (id)fourCharCodeWithFourCharCode:(FourCharCode)code { + return [[[self alloc] initWithFourCharCode:code] autorelease]; +} + +- (id)initWithString:(NSString*)string { + NSUInteger length = [string length]; + if (length == 0 || length > 4) { + [self release]; + return nil; + } else { + return [self initWithFourCharCode:UTGetOSTypeFromString((CFStringRef)string)]; + } +} + +- (id)initWithFourCharCode:(FourCharCode)code { + if ((self = [super init])) { + code_ = code; + } + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + if ((self = [super init])) { + code_ = [aDecoder decodeInt32ForKey:@"FourCharCode"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeInt32:code_ forKey:@"FourCharCode"]; +} + +- (id)copyWithZone:(NSZone *)zone { + return [[[self class] alloc] initWithFourCharCode:code_]; +} + +- (BOOL)isEqual:(id)object { + return [object isKindOfClass:[self class]] && [object fourCharCode] == code_; +} + +- (NSUInteger)hash { + return (NSUInteger)code_; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@ - %@ (0x%X)", + [self class], + [self stringValue], + code_]; +} + +- (FourCharCode)fourCharCode { + return code_; +} + +- (NSString*)stringValue { + return [GTMNSMakeCollectable(UTCreateStringForOSType(code_)) autorelease]; +} + +- (NSNumber*)numberValue { + return [NSNumber numberWithUnsignedInt:code_]; +} + +@end diff --git a/Foundation/GTMFourCharCodeTest.m b/Foundation/GTMFourCharCodeTest.m new file mode 100644 index 0000000..f91b8f2 --- /dev/null +++ b/Foundation/GTMFourCharCodeTest.m @@ -0,0 +1,82 @@ +// +// GTMFourCharCodeTest.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 "GTMSenTestCase.h" +#import "GTMFourCharCode.h" + +@interface GTMFourCharCodeTest : GTMTestCase +@end + +@implementation GTMFourCharCodeTest + +const FourCharCode kGTMHighMacOSRomanCode = 0xA5A8A9AA; // '•®©™' + +- (void)testFourCharCode { + GTMFourCharCode *fcc = [GTMFourCharCode fourCharCodeWithString:@"APPL"]; + STAssertNotNil(fcc, nil); + STAssertEqualObjects([fcc stringValue], @"APPL", nil); + STAssertEqualObjects([fcc numberValue], [NSNumber numberWithUnsignedInt:'APPL'], nil); + STAssertEquals([fcc fourCharCode], (FourCharCode)'APPL', nil); + + STAssertEqualObjects([fcc description], @"GTMFourCharCode - APPL (0x4150504C)", nil); + STAssertEquals([fcc hash], (NSUInteger)'APPL', nil); + + GTMFourCharCode *fcc2 = [GTMFourCharCode fourCharCodeWithFourCharCode:kGTMHighMacOSRomanCode]; + STAssertNotNil(fcc2, nil); + STAssertEqualObjects([fcc2 stringValue], @"•®©™", nil); + STAssertEqualObjects([fcc2 numberValue], [NSNumber numberWithUnsignedInt:kGTMHighMacOSRomanCode], nil); + STAssertEquals([fcc2 fourCharCode], (FourCharCode)kGTMHighMacOSRomanCode, nil); + + STAssertNotEqualObjects(fcc, fcc2, nil); + + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:fcc]; + STAssertNotNil(data, nil); + fcc2 = (GTMFourCharCode*)[NSKeyedUnarchiver unarchiveObjectWithData:data]; + STAssertNotNil(fcc2, nil); + STAssertEqualObjects(fcc, fcc2, nil); + + fcc = [[[GTMFourCharCode alloc] initWithFourCharCode:'\?\?\?\?'] autorelease]; + STAssertNotNil(fcc, nil); + STAssertEqualObjects([fcc stringValue], @"????", nil); + STAssertEqualObjects([fcc numberValue], [NSNumber numberWithUnsignedInt:'\?\?\?\?'], nil); + STAssertEquals([fcc fourCharCode], (FourCharCode)'\?\?\?\?', nil); + + fcc = [[[GTMFourCharCode alloc] initWithString:@"????"] autorelease]; + STAssertNotNil(fcc, nil); + STAssertEqualObjects([fcc stringValue], @"????", nil); + STAssertEqualObjects([fcc numberValue], [NSNumber numberWithUnsignedInt:'\?\?\?\?'], nil); + STAssertEquals([fcc fourCharCode], (FourCharCode)'\?\?\?\?', nil); + + fcc = [GTMFourCharCode fourCharCodeWithFourCharCode:1]; + STAssertNotNil(fcc, nil); + STAssertEqualObjects([fcc stringValue], @"\0\0\0\1", nil); + STAssertEqualObjects([fcc numberValue], [NSNumber numberWithUnsignedInt:1], nil); + STAssertEquals([fcc fourCharCode], (FourCharCode)1, nil); + + + fcc = [GTMFourCharCode fourCharCodeWithString:@"BADDSTRING"]; + STAssertNil(fcc, nil); +} + +- (void)testStringWithCode { + STAssertEqualObjects([GTMFourCharCode stringWithFourCharCode:'APPL'], @"APPL", nil); + STAssertEqualObjects([GTMFourCharCode stringWithFourCharCode:1], @"\0\0\0\1", nil); + STAssertEqualObjects([GTMFourCharCode stringWithFourCharCode:kGTMHighMacOSRomanCode], @"•®©™", nil); +} + +@end diff --git a/Foundation/GTMGeometryUtils.h b/Foundation/GTMGeometryUtils.h index a58ac13..7d54cf2 100644 --- a/Foundation/GTMGeometryUtils.h +++ b/Foundation/GTMGeometryUtils.h @@ -42,7 +42,125 @@ enum { }; typedef NSUInteger GTMRectAlignment; -#pragma mark Miscellaneous +#pragma mark - +#pragma mark CG - Point On Rect +/// Return middle of min X side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of min X side of rect +CG_INLINE CGPoint GTMCGMidMinX(CGRect rect) { + return CGPointMake(CGRectGetMinX(rect), CGRectGetMidY(rect)); +} + +/// Return middle of max X side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of max X side of rect +CG_INLINE CGPoint GTMCGMidMaxX(CGRect rect) { + return CGPointMake(CGRectGetMaxX(rect), CGRectGetMidY(rect)); +} + +/// Return middle of max Y side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of max Y side of rect +CG_INLINE CGPoint GTMCGMidMaxY(CGRect rect) { + return CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect)); +} + +/// Return middle of min Y side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of min Y side of rect +CG_INLINE CGPoint GTMCGMidMinY(CGRect rect) { + return CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect)); +} + +/// Return center of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the center of rect +CG_INLINE CGPoint GTMCGCenter(CGRect rect) { + return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); +} + +#pragma mark - +#pragma mark CG - Rect-Size Conversion + +/// Return size of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// size of rectangle +CG_INLINE CGSize GTMCGRectSize(CGRect rect) { + return CGSizeMake(CGRectGetWidth(rect), CGRectGetHeight(rect)); +} + +/// Return rectangle of size +// +// Args: +// size - size +// +// Returns: +// rectangle of size (origin 0,0) +CG_INLINE CGRect GTMCGRectOfSize(CGSize size) { + return CGRectMake(0.0f, 0.0f, size.width, size.height); +} + +#pragma mark - +#pragma mark CG - Rect Scaling and Alignment + +/// Scales an CGRect +// +// Args: +// inRect: Rect to scale +// xScale: fraction to scale (1.0 is 100%) +// yScale: fraction to scale (1.0 is 100%) +// +// Returns: +// Converted Rect +CG_INLINE CGRect GTMCGRectScale(CGRect inRect, CGFloat xScale, CGFloat yScale) { + return CGRectMake(inRect.origin.x, inRect.origin.y, + inRect.size.width * xScale, inRect.size.height * yScale); +} + + +/// Align rectangles +// +// Args: +// alignee - rect to be aligned +// aligner - rect to be aligned from +// alignment - way to align the rectangles +CGRect GTMCGAlignRectangles(CGRect alignee, CGRect aligner, + GTMRectAlignment alignment); +/// Scale rectangle +// +// Args: +// scalee - rect to be scaled +// size - size to scale to +// scaling - way to scale the rectangle +CGRect GTMCGScaleRectangleToSize(CGRect scalee, CGSize size, + GTMScaling scaling); + +#pragma mark - +#pragma mark CG - Miscellaneous /// Calculate the distance between two points. // @@ -52,7 +170,7 @@ typedef NSUInteger GTMRectAlignment; // // Returns: // Distance -CG_INLINE CGFloat GTMDistanceBetweenPoints(NSPoint pt1, NSPoint pt2) { +CG_INLINE CGFloat GTMCGDistanceBetweenPoints(CGPoint pt1, CGPoint pt2) { CGFloat dX = pt1.x - pt2.x; CGFloat dY = pt1.y - pt2.y; #if CGFLOAT_IS_DOUBLE @@ -62,8 +180,14 @@ CG_INLINE CGFloat GTMDistanceBetweenPoints(NSPoint pt1, NSPoint pt2) { #endif } +#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) +// iPhone does not have NSTypes defined, only CGTypes. So no NSRect, NSPoint etc. + #pragma mark - -#pragma mark Point Conversion +// All of the conversion routines below are basically copied from the +// NSGeometry header in the 10.5 sdk. + +#pragma mark NS <-> CG Point Conversion /// Quickly convert from a CGPoint to a NSPoint. // @@ -76,7 +200,9 @@ CG_INLINE CGFloat GTMDistanceBetweenPoints(NSPoint pt1, NSPoint pt2) { // Returns: // Converted NSPoint CG_INLINE NSPoint GTMCGPointToNSPoint(CGPoint inPoint) { - return NSMakePoint(inPoint.x, inPoint.y); + _GTMCompileAssert(sizeof(NSPoint) == sizeof(CGPoint), NSPoint_and_CGPoint_must_be_the_same_size); + union convertUnion {NSPoint ns; CGPoint cg;}; + return ((union convertUnion *)&inPoint)->ns; } /// Quickly convert from a NSPoint to a CGPoint. @@ -90,11 +216,13 @@ CG_INLINE NSPoint GTMCGPointToNSPoint(CGPoint inPoint) { // Returns: // Converted CGPoint CG_INLINE CGPoint GTMNSPointToCGPoint(NSPoint inPoint) { - return CGPointMake(inPoint.x, inPoint.y); + _GTMCompileAssert(sizeof(NSPoint) == sizeof(CGPoint), NSPoint_and_CGPoint_must_be_the_same_size); + union convertUnion {NSPoint ns; CGPoint cg;}; + return ((union convertUnion *)&inPoint)->cg; } #pragma mark - -#pragma mark Rect Conversion +#pragma mark NS <-> CG Rect Conversion /// Convert from a CGRect to a NSRect. // @@ -107,7 +235,9 @@ CG_INLINE CGPoint GTMNSPointToCGPoint(NSPoint inPoint) { // Returns: // Converted NSRect CG_INLINE NSRect GTMCGRectToNSRect(CGRect inRect) { - return NSMakeRect(inRect.origin.x,inRect.origin.y,inRect.size.width,inRect.size.height); + _GTMCompileAssert(sizeof(NSRect) == sizeof(CGRect), NSRect_and_CGRect_must_be_the_same_size); + union convertUnion {NSRect ns; CGRect cg;}; + return ((union convertUnion *)&inRect)->ns; } /// Convert from a NSRect to a CGRect. @@ -121,11 +251,14 @@ CG_INLINE NSRect GTMCGRectToNSRect(CGRect inRect) { // Returns: // Converted CGRect CG_INLINE CGRect GTMNSRectToCGRect(NSRect inRect) { - return CGRectMake(inRect.origin.x,inRect.origin.y,inRect.size.width,inRect.size.height); + _GTMCompileAssert(sizeof(NSRect) == sizeof(CGRect), NSRect_and_CGRect_must_be_the_same_size); + union convertUnion {NSRect ns; CGRect cg;}; + return ((union convertUnion *)&inRect)->cg; } + #pragma mark - -#pragma mark Size Conversion +#pragma mark NS <-> CG Size Conversion /// Convert from a CGSize to an NSSize. // @@ -135,7 +268,9 @@ CG_INLINE CGRect GTMNSRectToCGRect(NSRect inRect) { // Returns: // Converted NSSize CG_INLINE NSSize GTMCGSizeToNSSize(CGSize inSize) { - return NSMakeSize(inSize.width, inSize.height); + _GTMCompileAssert(sizeof(NSSize) == sizeof(CGSize), NSSize_and_CGSize_must_be_the_same_size); + union convertUnion {NSSize ns; CGSize cg;}; + return ((union convertUnion *)&inSize)->ns; } /// Convert from a NSSize to a CGSize. @@ -146,53 +281,55 @@ CG_INLINE NSSize GTMCGSizeToNSSize(CGSize inSize) { // Returns: // Converted CGSize CG_INLINE CGSize GTMNSSizeToCGSize(NSSize inSize) { - return CGSizeMake(inSize.width, inSize.height); + _GTMCompileAssert(sizeof(NSSize) == sizeof(CGSize), NSSize_and_CGSize_must_be_the_same_size); + union convertUnion {NSSize ns; CGSize cg;}; + return ((union convertUnion *)&inSize)->cg; } #pragma mark - -#pragma mark Point On Rect +#pragma mark NS - Point On Rect -/// Return middle of left side of rectangle +/// Return middle of min X side of rectangle // // Args: // rect - rectangle // // Returns: -// point located in the middle of left side of rect -CG_INLINE NSPoint GTMNSMidLeft(NSRect rect) { +// point located in the middle of min X side of rect +CG_INLINE NSPoint GTMNSMidMinX(NSRect rect) { return NSMakePoint(NSMinX(rect), NSMidY(rect)); } -/// Return middle of right side of rectangle +/// Return middle of max X side of rectangle // // Args: // rect - rectangle // // Returns: -// point located in the middle of right side of rect -CG_INLINE NSPoint GTMNSMidRight(NSRect rect) { +// point located in the middle of max X side of rect +CG_INLINE NSPoint GTMNSMidMaxX(NSRect rect) { return NSMakePoint(NSMaxX(rect), NSMidY(rect)); } -/// Return middle of top side of rectangle +/// Return middle of max Y side of rectangle // // Args: // rect - rectangle // // Returns: -// point located in the middle of top side of rect -CG_INLINE NSPoint GTMNSMidTop(NSRect rect) { +// point located in the middle of max Y side of rect +CG_INLINE NSPoint GTMNSMidMaxY(NSRect rect) { return NSMakePoint(NSMidX(rect), NSMaxY(rect)); } -/// Return middle of bottom side of rectangle +/// Return middle of min Y side of rectangle // // Args: // rect - rectangle // // Returns: -// point located in the middle of bottom side of rect -CG_INLINE NSPoint GTMNSMidBottom(NSRect rect) { +// point located in the middle of min Y side of rect +CG_INLINE NSPoint GTMNSMidMinY(NSRect rect) { return NSMakePoint(NSMidX(rect), NSMinY(rect)); } @@ -207,63 +344,8 @@ CG_INLINE NSPoint GTMNSCenter(NSRect rect) { return NSMakePoint(NSMidX(rect), NSMidY(rect)); } -/// Return middle of left side of rectangle -// -// Args: -// rect - rectangle -// -// Returns: -// point located in the middle of left side of rect -CG_INLINE CGPoint GTMCGMidLeft(CGRect rect) { - return CGPointMake(CGRectGetMinX(rect), CGRectGetMidY(rect)); -} - -/// Return middle of right side of rectangle -// -// Args: -// rect - rectangle -// -// Returns: -// point located in the middle of right side of rect -CG_INLINE CGPoint GTMCGMidRight(CGRect rect) { - return CGPointMake(CGRectGetMaxX(rect), CGRectGetMidY(rect)); -} - -/// Return middle of top side of rectangle -// -// Args: -// rect - rectangle -// -// Returns: -// point located in the middle of top side of rect -CG_INLINE CGPoint GTMCGMidTop(CGRect rect) { - return CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect)); -} - -/// Return middle of bottom side of rectangle -// -// Args: -// rect - rectangle -// -// Returns: -// point located in the middle of bottom side of rect -CG_INLINE CGPoint GTMCGMidBottom(CGRect rect) { - return CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect)); -} - -/// Return center of rectangle -// -// Args: -// rect - rectangle -// -// Returns: -// point located in the center of rect -CG_INLINE CGPoint GTMCGCenter(CGRect rect) { - return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); -} - #pragma mark - -#pragma mark Rect-Size Conversion +#pragma mark NS - Rect-Size Conversion /// Return size of rectangle // @@ -276,17 +358,6 @@ CG_INLINE NSSize GTMNSRectSize(NSRect rect) { return NSMakeSize(NSWidth(rect), NSHeight(rect)); } -/// Return size of rectangle -// -// Args: -// rect - rectangle -// -// Returns: -// size of rectangle -CG_INLINE CGSize GTMCGRectSize(CGRect rect) { - return CGSizeMake(CGRectGetWidth(rect), CGRectGetHeight(rect)); -} - /// Return rectangle of size // // Args: @@ -298,19 +369,8 @@ CG_INLINE NSRect GTMNSRectOfSize(NSSize size) { return NSMakeRect(0.0f, 0.0f, size.width, size.height); } -/// Return rectangle of size -// -// Args: -// size - size -// -// Returns: -// rectangle of size (origin 0,0) -CG_INLINE CGRect GTMCGRectOfSize(CGSize size) { - return CGRectMake(0.0f, 0.0f, size.width, size.height); -} - #pragma mark - -#pragma mark Rect Scaling and Alignment +#pragma mark NS - Rect Scaling and Alignment /// Scales an NSRect // @@ -326,40 +386,19 @@ CG_INLINE NSRect GTMNSRectScale(NSRect inRect, CGFloat xScale, CGFloat yScale) { inRect.size.width * xScale, inRect.size.height * yScale); } -/// Scales an CGRect -// -// Args: -// inRect: Rect to scale -// xScale: fraction to scale (1.0 is 100%) -// yScale: fraction to scale (1.0 is 100%) -// -// Returns: -// Converted Rect -CG_INLINE CGRect GTMCGRectScale(CGRect inRect, CGFloat xScale, CGFloat yScale) { - return CGRectMake(inRect.origin.x, inRect.origin.y, - inRect.size.width * xScale, inRect.size.height * yScale); -} -/// Align rectangles -// -// Args: -// alignee - rect to be aligned -// aligner - rect to be aligned from -NSRect GTMAlignRectangles(NSRect alignee, NSRect aligner, - GTMRectAlignment alignment); /// Align rectangles // // Args: // alignee - rect to be aligned // aligner - rect to be aligned from -// alignment - way to align the rectangles -CG_INLINE CGRect GTMCGAlignRectangles(CGRect alignee, CGRect aligner, +CG_INLINE NSRect GTMNSAlignRectangles(NSRect alignee, NSRect aligner, GTMRectAlignment alignment) { - return GTMNSRectToCGRect(GTMAlignRectangles(GTMCGRectToNSRect(alignee), - GTMCGRectToNSRect(aligner), + return GTMCGRectToNSRect(GTMCGAlignRectangles(GTMNSRectToCGRect(alignee), + GTMNSRectToCGRect(aligner), alignment)); -} +} /// Scale rectangle // @@ -367,18 +406,27 @@ CG_INLINE CGRect GTMCGAlignRectangles(CGRect alignee, CGRect aligner, // scalee - rect to be scaled // size - size to scale to // scaling - way to scale the rectangle -NSRect GTMScaleRectangleToSize(NSRect scalee, NSSize size, - GTMScaling scaling); +CG_INLINE NSRect GTMNSScaleRectangleToSize(NSRect scalee, NSSize size, + GTMScaling scaling) { + return GTMCGRectToNSRect(GTMCGScaleRectangleToSize(GTMNSRectToCGRect(scalee), + GTMNSSizeToCGSize(size), + scaling)); +} -/// Scale rectangle +#pragma mark - +#pragma mark NS - Miscellaneous + +/// Calculate the distance between two points. // // Args: -// scalee - rect to be scaled -// size - size to scale to -// scaling - way to scale the rectangle -CG_INLINE CGRect GTMCGScaleRectangleToSize(CGRect scalee, CGSize size, - GTMScaling scaling) { - return GTMNSRectToCGRect(GTMScaleRectangleToSize(GTMCGRectToNSRect(scalee), - GTMCGSizeToNSSize(size), - scaling)); +// pt1 first point +// pt2 second point +// +// Returns: +// Distance +CG_INLINE CGFloat GTMNSDistanceBetweenPoints(NSPoint pt1, NSPoint pt2) { + return GTMCGDistanceBetweenPoints(GTMNSPointToCGPoint(pt1), + GTMNSPointToCGPoint(pt2)); } + +#endif // (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) diff --git a/Foundation/GTMGeometryUtils.m b/Foundation/GTMGeometryUtils.m index 0e893ff..9ac2933 100644 --- a/Foundation/GTMGeometryUtils.m +++ b/Foundation/GTMGeometryUtils.m @@ -25,26 +25,26 @@ // aligner - rect to be aligned to // alignment - alignment to be applied to alignee based on aligner -NSRect GTMAlignRectangles(NSRect alignee, NSRect aligner, GTMRectAlignment alignment) { +CGRect GTMCGAlignRectangles(CGRect alignee, CGRect aligner, GTMRectAlignment alignment) { switch (alignment) { case GTMRectAlignTop: - alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f); - alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee); + alignee.origin.x = aligner.origin.x + (CGRectGetWidth(aligner) * .5f - CGRectGetWidth(alignee) * .5f); + alignee.origin.y = aligner.origin.y + CGRectGetHeight(aligner) - CGRectGetHeight(alignee); break; case GTMRectAlignTopLeft: alignee.origin.x = aligner.origin.x; - alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee); + alignee.origin.y = aligner.origin.y + CGRectGetHeight(aligner) - CGRectGetHeight(alignee); break; case GTMRectAlignTopRight: - alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee); - alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee); + alignee.origin.x = aligner.origin.x + CGRectGetWidth(aligner) - CGRectGetWidth(alignee); + alignee.origin.y = aligner.origin.y + CGRectGetHeight(aligner) - CGRectGetHeight(alignee); break; case GTMRectAlignLeft: alignee.origin.x = aligner.origin.x; - alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f); + alignee.origin.y = aligner.origin.y + (CGRectGetHeight(aligner) * .5f - CGRectGetHeight(alignee) * .5f); break; case GTMRectAlignBottomLeft: @@ -53,40 +53,40 @@ NSRect GTMAlignRectangles(NSRect alignee, NSRect aligner, GTMRectAlignment align break; case GTMRectAlignBottom: - alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f); + alignee.origin.x = aligner.origin.x + (CGRectGetWidth(aligner) * .5f - CGRectGetWidth(alignee) * .5f); alignee.origin.y = aligner.origin.y; break; case GTMRectAlignBottomRight: - alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee); + alignee.origin.x = aligner.origin.x + CGRectGetWidth(aligner) - CGRectGetWidth(alignee); alignee.origin.y = aligner.origin.y; break; case GTMRectAlignRight: - alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee); - alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f); + alignee.origin.x = aligner.origin.x + CGRectGetWidth(aligner) - CGRectGetWidth(alignee); + alignee.origin.y = aligner.origin.y + (CGRectGetHeight(aligner) * .5f - CGRectGetHeight(alignee) * .5f); break; default: case GTMRectAlignCenter: - alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f); - alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f); + alignee.origin.x = aligner.origin.x + (CGRectGetWidth(aligner) * .5f - CGRectGetWidth(alignee) * .5f); + alignee.origin.y = aligner.origin.y + (CGRectGetHeight(aligner) * .5f - CGRectGetHeight(alignee) * .5f); break; } return alignee; } -NSRect GTMScaleRectangleToSize(NSRect scalee, NSSize size, GTMScaling scaling) { +CGRect GTMCGScaleRectangleToSize(CGRect scalee, CGSize size, GTMScaling scaling) { switch (scaling) { case GTMScaleProportionally: { - CGFloat height = NSHeight(scalee); - CGFloat width = NSWidth(scalee); + CGFloat height = CGRectGetHeight(scalee); + CGFloat width = CGRectGetWidth(scalee); if (isnormal(height) && isnormal(width) && (height > size.height || width > size.width)) { CGFloat horiz = size.width / width; CGFloat vert = size.height / height; CGFloat newScale = horiz < vert ? horiz : vert; - scalee = GTMNSRectScale(scalee, newScale, newScale); + scalee = GTMCGRectScale(scalee, newScale, newScale); } break; } diff --git a/Foundation/GTMGeometryUtilsTest.m b/Foundation/GTMGeometryUtilsTest.m index 606ea6b..8a81f0d 100644 --- a/Foundation/GTMGeometryUtilsTest.m +++ b/Foundation/GTMGeometryUtilsTest.m @@ -19,11 +19,12 @@ #import "GTMSenTestCase.h" #import "GTMGeometryUtils.h" -@interface GTMGeometryUtilsTest : SenTestCase +@interface GTMGeometryUtilsTest : GTMTestCase @end @implementation GTMGeometryUtilsTest +#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) - (void)testGTMCGPointToNSPoint { CGPoint cgPoint = CGPointMake(15.1,6.2); NSPoint nsPoint = GTMCGPointToNSPoint(cgPoint); @@ -61,19 +62,48 @@ STAssertTrue(CGSizeEqualToSize(cgSize, *(CGSize*)&nsSize), nil); } +- (void)testGTMNSPointsOnRect { + NSRect rect = NSMakeRect(0, 0, 2, 2); + + NSPoint point = GTMNSMidMinX(rect); + STAssertEqualsWithAccuracy(point.y, (CGFloat)1.0, (CGFloat)0.01, nil); + STAssertEqualsWithAccuracy(point.x, (CGFloat)0.0, (CGFloat)0.01, nil); + + point = GTMNSMidMaxX(rect); + STAssertEqualsWithAccuracy(point.y, (CGFloat)1.0, (CGFloat)0.01, nil); + STAssertEqualsWithAccuracy(point.x, (CGFloat)2.0, (CGFloat)0.01, nil); + + point = GTMNSMidMaxY(rect); + STAssertEqualsWithAccuracy(point.y, (CGFloat)2.0, (CGFloat)0.01, nil); + STAssertEqualsWithAccuracy(point.x, (CGFloat)1.0, (CGFloat)0.01, nil); + + point = GTMNSMidMinY(rect); + STAssertEqualsWithAccuracy(point.y, (CGFloat)0.0, (CGFloat)0.01, nil); + STAssertEqualsWithAccuracy(point.x, (CGFloat)1.0, (CGFloat)0.01, nil); +} + +- (void)testGTMNSRectScaling { + NSRect rect = NSMakeRect(1.0f, 2.0f, 5.0f, 10.0f); + NSRect rect2 = NSMakeRect((CGFloat)1.0, (CGFloat)2.0, (CGFloat)1.0, (CGFloat)12.0); + STAssertEquals(GTMNSRectScale(rect, (CGFloat)0.2, (CGFloat)1.2), + rect2, nil); +} + +#endif // #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) + - (void)testGTMDistanceBetweenPoints { - NSPoint pt1 = NSMakePoint(0, 0); - NSPoint pt2 = NSMakePoint(3, 4); - STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), (CGFloat)5.0, nil); - STAssertEquals(GTMDistanceBetweenPoints(pt2, pt1), (CGFloat)5.0, nil); - pt1 = NSMakePoint(1, 1); - pt2 = NSMakePoint(1, 1); - STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), (CGFloat)0.0, nil); + CGPoint pt1 = CGPointMake(0, 0); + CGPoint pt2 = CGPointMake(3, 4); + STAssertEquals(GTMCGDistanceBetweenPoints(pt1, pt2), (CGFloat)5.0, nil); + STAssertEquals(GTMCGDistanceBetweenPoints(pt2, pt1), (CGFloat)5.0, nil); + pt1 = CGPointMake(1, 1); + pt2 = CGPointMake(1, 1); + STAssertEquals(GTMCGDistanceBetweenPoints(pt1, pt2), (CGFloat)0.0, nil); } - (void)testGTMAlignRectangles { typedef struct { - NSPoint expectedOrigin; + CGPoint expectedOrigin; GTMRectAlignment alignment; } TestData; @@ -89,65 +119,51 @@ { {1,1}, GTMRectAlignCenter }, }; - NSRect rect1 = NSMakeRect(0, 0, 4, 4); - NSRect rect2 = NSMakeRect(0, 0, 2, 2); + CGRect rect1 = CGRectMake(0, 0, 4, 4); + CGRect rect2 = CGRectMake(0, 0, 2, 2); + + CGRect expectedRect; + expectedRect.size = CGSizeMake(2, 2); - for (int i = 0; i < sizeof(data) / sizeof(TestData); i++) { - NSRect expectedRect; + for (size_t i = 0; i < sizeof(data) / sizeof(TestData); i++) { expectedRect.origin = data[i].expectedOrigin; - expectedRect.size = NSMakeSize(2, 2); - NSRect outRect = GTMAlignRectangles(rect2, rect1, data[i].alignment); + CGRect outRect = GTMCGAlignRectangles(rect2, rect1, data[i].alignment); STAssertEquals(outRect, expectedRect, nil); } } -- (void)testGTMPointsOnRect { - NSRect rect = NSMakeRect(0, 0, 2, 2); - CGRect cgRect = GTMNSRectToCGRect(rect); +- (void)testGTMCGPointsOnRect { + CGRect rect = CGRectMake(0, 0, 2, 2); - NSPoint point = GTMNSMidLeft(rect); - CGPoint cgPoint = GTMCGMidLeft(cgRect); - STAssertEquals(point.x, cgPoint.x, nil); - STAssertEquals(point.y, cgPoint.y, nil); + CGPoint point = GTMCGMidMinX(rect); STAssertEqualsWithAccuracy(point.y, (CGFloat)1.0, (CGFloat)0.01, nil); STAssertEqualsWithAccuracy(point.x, (CGFloat)0.0, (CGFloat)0.01, nil); - - point = GTMNSMidRight(rect); - cgPoint = GTMCGMidRight(cgRect); - STAssertEquals(point.x, cgPoint.x, nil); - STAssertEquals(point.y, cgPoint.y, nil); + + point = GTMCGMidMaxX(rect); STAssertEqualsWithAccuracy(point.y, (CGFloat)1.0, (CGFloat)0.01, nil); STAssertEqualsWithAccuracy(point.x, (CGFloat)2.0, (CGFloat)0.01, nil); - - point = GTMNSMidTop(rect); - cgPoint = GTMCGMidTop(cgRect); - STAssertEquals(point.x, cgPoint.x, nil); - STAssertEquals(point.y, cgPoint.y, nil); + + point = GTMCGMidMaxY(rect); STAssertEqualsWithAccuracy(point.y, (CGFloat)2.0, (CGFloat)0.01, nil); STAssertEqualsWithAccuracy(point.x, (CGFloat)1.0, (CGFloat)0.01, nil); - point = GTMNSMidBottom(rect); - cgPoint = GTMCGMidBottom(cgRect); - STAssertEquals(point.x, cgPoint.x, nil); - STAssertEquals(point.y, cgPoint.y, nil); + point = GTMCGMidMinY(rect); STAssertEqualsWithAccuracy(point.y, (CGFloat)0.0, (CGFloat)0.01, nil); STAssertEqualsWithAccuracy(point.x, (CGFloat)1.0, (CGFloat)0.01, nil); } -- (void)testGTMRectScaling { - NSRect rect = NSMakeRect(1.0f, 2.0f, 5.0f, 10.0f); - NSRect rect2 = NSMakeRect((CGFloat)1.0, (CGFloat)2.0, (CGFloat)1.0, (CGFloat)12.0); - STAssertEquals(GTMNSRectScale(rect, (CGFloat)0.2, (CGFloat)1.2), +- (void)testGTMCGRectScaling { + CGRect rect = CGRectMake(1.0f, 2.0f, 5.0f, 10.0f); + CGRect rect2 = CGRectMake((CGFloat)1.0, (CGFloat)2.0, (CGFloat)1.0, (CGFloat)12.0); + STAssertEquals(GTMCGRectScale(rect, (CGFloat)0.2, (CGFloat)1.2), rect2, nil); - STAssertEquals(GTMCGRectScale(GTMNSRectToCGRect(rect), (CGFloat)0.2, (CGFloat)1.2), - GTMNSRectToCGRect(rect2), nil); } - (void)testGTMScaleRectangleToSize { - NSRect rect = NSMakeRect(0.0f, 0.0f, 10.0f, 10.0f); + CGRect rect = CGRectMake(0.0f, 0.0f, 10.0f, 10.0f); typedef struct { - NSSize size_; - NSSize newSize_; + CGSize size_; + CGSize newSize_; } Test; Test tests[] = { { { 5.0, 10.0 }, { 5.0, 5.0 } }, @@ -161,21 +177,21 @@ }; for (size_t i = 0; i < sizeof(tests) / sizeof(Test); ++i) { - NSRect result = GTMScaleRectangleToSize(rect, tests[i].size_, - GTMScaleProportionally); - STAssertEquals(result, GTMNSRectOfSize(tests[i].newSize_), @"failed on test %z", i); + CGRect result = GTMCGScaleRectangleToSize(rect, tests[i].size_, + GTMScaleProportionally); + STAssertEquals(result, GTMCGRectOfSize(tests[i].newSize_), @"failed on test %z", i); } - NSRect result = GTMScaleRectangleToSize(NSZeroRect, tests[0].size_, - GTMScaleProportionally); - STAssertEquals(result, NSZeroRect, nil); + CGRect result = GTMCGScaleRectangleToSize(CGRectZero, tests[0].size_, + GTMScaleProportionally); + STAssertEquals(result, CGRectZero, nil); - result = GTMScaleRectangleToSize(rect, tests[0].size_, - GTMScaleToFit); - STAssertEquals(result, GTMNSRectOfSize(tests[0].size_), nil); + result = GTMCGScaleRectangleToSize(rect, tests[0].size_, + GTMScaleToFit); + STAssertEquals(result, GTMCGRectOfSize(tests[0].size_), nil); - result = GTMScaleRectangleToSize(rect, tests[0].size_, - GTMScaleNone); + result = GTMCGScaleRectangleToSize(rect, tests[0].size_, + GTMScaleNone); STAssertEquals(result, rect, nil); } @end diff --git a/Foundation/GTMHTTPFetcher.m b/Foundation/GTMHTTPFetcher.m index 9853d0d..cac49df 100644 --- a/Foundation/GTMHTTPFetcher.m +++ b/Foundation/GTMHTTPFetcher.m @@ -65,6 +65,7 @@ const NSTimeInterval kDefaultMaxRetryInterval = 60. * 10.; // 10 minutes - (NSArray *)cookiesForURL:(NSURL *)theURL inArray:(NSMutableArray *)cookieStorageArray; - (void)handleCookiesForResponse:(NSURLResponse *)response; - (BOOL)shouldRetryNowForStatus:(NSInteger)status error:(NSError *)error; +- (void)retryTimerFired:(NSTimer *)timer; - (void)destroyRetryTimer; - (void)beginRetryTimer; - (void)primeTimerWithNewTimeInterval:(NSTimeInterval)secs; @@ -135,7 +136,7 @@ const NSTimeInterval kDefaultMaxRetryInterval = 60. * 10.; // 10 minutes GTMAssertSelectorNilOrImplementedWithArguments(delegate, finishedSEL, @encode(GTMHTTPFetcher *), @encode(NSData *), NULL); GTMAssertSelectorNilOrImplementedWithArguments(delegate, failedSEL, @encode(GTMHTTPFetcher *), @encode(NSError *), NULL); GTMAssertSelectorNilOrImplementedWithArguments(delegate, receivedDataSEL_, @encode(GTMHTTPFetcher *), @encode(NSData *), NULL); - GTMAssertSelectorNilOrImplementedWithArguments(delegate, retrySEL_, @encode(GTMHTTPFetcher *), @encode(BOOL), @encode(NSError *), NULL); + GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(delegate, retrySEL_, @encode(BOOL), @encode(GTMHTTPFetcher *), @encode(BOOL), @encode(NSError *), NULL); if (connection_ != nil) { _GTMDevAssert(connection_ != nil, @@ -244,7 +245,7 @@ const NSTimeInterval kDefaultMaxRetryInterval = 60. * 10.; // 10 minutes delegate:self startImmediately:NO]; - for (int idx = 0; idx < [runLoopModes count]; idx++) { + for (NSUInteger idx = 0; idx < [runLoopModes count]; idx++) { NSString *mode = [runLoopModes objectAtIndex:idx]; [connection_ scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:mode]; } @@ -1412,7 +1413,7 @@ static NSString* gLoggingProcessName = nil; NSMutableData *mutableData = [NSMutableData dataWithData:data]; unsigned char *bytes = [mutableData mutableBytes]; - for (int idx = 0; idx < [mutableData length]; idx++) { + for (NSUInteger idx = 0; idx < [mutableData length]; idx++) { if (bytes[idx] > 0x7F || bytes[idx] == 0) { bytes[idx] = '_'; } @@ -1436,7 +1437,7 @@ static NSString* gLoggingProcessName = nil; // convert those into UTF-8 NSMutableArray *origParts = [NSMutableArray array]; NSUInteger offset = 0; - for (int partIdx = 0; partIdx < [mungedParts count]; partIdx++) { + for (NSUInteger partIdx = 0; partIdx < [mungedParts count]; partIdx++) { NSString *mungedPart = [mungedParts objectAtIndex:partIdx]; NSUInteger partSize = [mungedPart length]; diff --git a/Foundation/GTMHTTPFetcherTest.m b/Foundation/GTMHTTPFetcherTest.m index 16c8d0f..3722e9b 100644 --- a/Foundation/GTMHTTPFetcherTest.m +++ b/Foundation/GTMHTTPFetcherTest.m @@ -16,12 +16,11 @@ // the License. // -#import -#import -#import "GTMHTTPFetcher.h" #import "GTMSenTestCase.h" +#import "GTMHTTPFetcher.h" +#import "GTMTestHTTPServer.h" -@interface GTMHTTPFetcherTest : SenTestCase { +@interface GTMHTTPFetcherTest : GTMTestCase { // these ivars are checked after fetches, and are reset by resetFetchResponse NSData *fetchedData_; NSError *fetcherError_; @@ -31,10 +30,7 @@ // setup/teardown ivars NSMutableDictionary *fetchHistory_; - NSTask *server_; // python http server - BOOL didServerLaunch_; // Tracks the state of our server - BOOL didServerDie_; - NSMutableData *launchBuffer_; // Storage for output from our server + GTMTestHTTPServer *testServer_; } @end @@ -49,11 +45,18 @@ userData:(id)userData; - (NSString *)fileURLStringToTestFileName:(NSString *)name; +- (BOOL)countRetriesFetcher:(GTMHTTPFetcher *)fetcher + willRetry:(BOOL)suggestedWillRetry + forError:(NSError *)error; +- (BOOL)fixRequestFetcher:(GTMHTTPFetcher *)fetcher + willRetry:(BOOL)suggestedWillRetry + forError:(NSError *)error; +- (void)testFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data; +- (void)testFetcher:(GTMHTTPFetcher *)fetcher failedWithError:(NSError *)error; @end @implementation GTMHTTPFetcherTest -static const int kServerPortNumber = 54579; static const NSTimeInterval kRunLoopInterval = 0.01; // The bogus-fetch test can take >10s to pass. Pick something way higher // to avoid failing. @@ -61,96 +64,18 @@ static const NSTimeInterval kGiveUpInterval = 60.0; // bail on the test if 60 se static NSString *const kValidFileName = @"GTMHTTPFetcherTestPage.html"; -- (void)gotData:(NSNotification*)notification { - // our server sends out a string to confirm that it launched - NSFileHandle *handle = [notification object]; - NSData *launchMessageData = [handle availableData]; - [launchBuffer_ appendData:launchMessageData]; - NSString *launchStr = - [[[NSString alloc] initWithData:launchBuffer_ - encoding:NSUTF8StringEncoding] autorelease]; - didServerLaunch_ = - [launchStr rangeOfString:@"started GTMHTTPFetcherTestServer"].location != NSNotFound; - if (!didServerLaunch_) { - _GTMDevLog(@"gotData launching httpserver: %@", launchStr); - [handle readInBackgroundAndNotify]; - } -} - -- (void)didDie:(NSNotification*)notification { - _GTMDevLog(@"server died"); - didServerDie_ = YES; -} - - - (void)setUp { fetchHistory_ = [[NSMutableDictionary alloc] init]; - // run the python http server, located in the Tests directory NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; STAssertNotNil(testBundle, nil); + NSString *docRoot = [testBundle pathForResource:@"GTMHTTPFetcherTestPage" + ofType:@"html"]; + docRoot = [docRoot stringByDeletingLastPathComponent]; + STAssertNotNil(docRoot, nil); - NSString *serverPath = - [testBundle pathForResource:@"GTMHTTPFetcherTestServer" ofType:@""]; - STAssertNotNil(serverPath, nil); - - NSArray *argArray = [NSArray arrayWithObjects:serverPath, - @"-p", [NSString stringWithFormat:@"%d", kServerPortNumber], - @"-r", [serverPath stringByDeletingLastPathComponent], nil]; - - server_ = [[NSTask alloc] init]; - [server_ setArguments:argArray]; - [server_ setLaunchPath:@"/usr/bin/python"]; - [server_ setEnvironment:[NSDictionary dictionary]]; // don't inherit anything from us - - // pipes will be cleaned up when server_ is torn down. - NSPipe *outputPipe = [NSPipe pipe]; - NSPipe *errorPipe = [NSPipe pipe]; - - [server_ setStandardOutput:outputPipe]; - [server_ setStandardError:errorPipe]; - - NSFileHandle *outputHandle = [outputPipe fileHandleForReading]; - NSFileHandle *errorHandle = [errorPipe fileHandleForReading]; - - didServerLaunch_ = NO; - didServerDie_ = NO; - - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center addObserver:self - selector:@selector(gotData:) - name:NSFileHandleDataAvailableNotification - object:outputHandle]; - [center addObserver:self - selector:@selector(gotData:) - name:NSFileHandleDataAvailableNotification - object:errorHandle]; - [center addObserver:self - selector:@selector(didDie:) - name:NSTaskDidTerminateNotification - object:server_]; - - [launchBuffer_ autorelease]; - launchBuffer_ = [[NSMutableData data] retain]; - [outputHandle waitForDataInBackgroundAndNotify]; - [errorHandle waitForDataInBackgroundAndNotify]; - [server_ launch]; - - NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:kGiveUpInterval]; - while ((!didServerDie_ && !didServerLaunch_) && - [giveUpDate timeIntervalSinceNow] > 0) { - NSDate* loopIntervalDate = - [NSDate dateWithTimeIntervalSinceNow:kRunLoopInterval]; - [[NSRunLoop currentRunLoop] runUntilDate:loopIntervalDate]; - } - - [center removeObserver:self]; - - STAssertTrue(didServerLaunch_ && [server_ isRunning] && !didServerDie_, - @"Python http server not launched.\n" - "Args:%@\n" - "Environment:%@\n", - [argArray componentsJoinedByString:@" "], [server_ environment]); + testServer_ = [[GTMTestHTTPServer alloc] initWithDocRoot:docRoot]; + STAssertNotNil(testServer_, @"failed to create a testing server"); } - (void)resetFetchResponse { @@ -170,12 +95,9 @@ static NSString *const kValidFileName = @"GTMHTTPFetcherTestPage.html"; } - (void)tearDown { - [server_ terminate]; - [server_ waitUntilExit]; - [server_ release]; - server_ = nil; - [launchBuffer_ release]; - launchBuffer_ = nil; + [testServer_ release]; + testServer_ = nil; + [self resetFetchResponse]; [fetchHistory_ release]; @@ -451,8 +373,7 @@ static NSString *const kValidFileName = @"GTMHTTPFetcherTestPage.html"; // return a localhost:port URL for the test file NSString *urlString = [NSString stringWithFormat:@"http://localhost:%d/%@", - kServerPortNumber, name]; - + [testServer_ port], name]; // we exclude the "?status=" that would indicate that the URL // should cause a retryable error @@ -540,4 +461,3 @@ static NSString *const kValidFileName = @"GTMHTTPFetcherTestPage.html"; } @end - diff --git a/Foundation/GTMHTTPServer.h b/Foundation/GTMHTTPServer.h new file mode 100644 index 0000000..70e3f78 --- /dev/null +++ b/Foundation/GTMHTTPServer.h @@ -0,0 +1,133 @@ +// +// GTMHTTPServer.h +// +// This is a *very* *simple* webserver that can be built into something, it is +// not meant to stand up a site, it sends all requests to its delegate for +// processing on the main thread. It does not support pipelining, etc. It's +// great for places where you need a simple webserver to unittest some code +// that hits a server. +// +// NOTE: there are several TODOs left in here as markers for things that could +// be done if one wanted to add more to this class. +// +// 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. +// +// Based a little on HTTPServer, part of the CocoaHTTPServer sample code +// http://developer.apple.com/samplecode/CocoaHTTPServer/index.html +// + +#import +#import "GTMDefines.h" + +// Global contants needed for errors from start + +#undef _EXTERN +#undef _INITIALIZE_AS +#ifdef GTMHTTPSERVER_DEFINE_GLOBALS +#define _EXTERN +#define _INITIALIZE_AS(x) =x +#else +#define _EXTERN extern +#define _INITIALIZE_AS(x) +#endif + +_EXTERN NSString* kGTMHTTPServerErrorDomain _INITIALIZE_AS(@"com.google.mactoolbox.HTTPServerDomain"); +enum { + kGTMHTTPServerSocketCreateFailedError = -100, + kGTMHTTPServerBindFailedError = -101, + kGTMHTTPServerListenFailedError = -102, + kGTMHTTPServerHandleCreateFailedError = -103, +}; + +@class GTMHTTPRequestMessage, GTMHTTPResponseMessage; + +// ---------------------------------------------------------------------------- + +// See comment at top of file for the intened use of this class. +@interface GTMHTTPServer : NSObject { + @private + id delegate_; + uint16_t port_; + BOOL localhostOnly_; + NSFileHandle *listenHandle_; + NSMutableArray *connections_; +} + +// The delegate must support the httpServer:handleRequest: method in +// NSObject(GTMHTTPServerDeletateMethods) below. +- (id)initWithDelegate:(id)delegate; + +- (id)delegate; + +// Passing port zero will let one get assigned. +- (uint16_t)port; +- (void)setPort:(uint16_t)port; + +// Receive connections on the localHost loopback address only or on all +// interfaces for this machine. The default is to only listen on localhost. +- (BOOL)localhostOnly; +- (void)setLocalhostOnly:(BOOL)yesno; + +// Start/Stop the web server. If there is an error starting up the server, |NO| +// is returned, and the specific startup failure can be returned in |error| (see +// above for the error domain and error codes). If the server is started, |YES| +// is returned and the server's delegate is called for any requests that come +// in. +- (BOOL)start:(NSError **)error; +- (void)stop; + +// returns the number of requests currently active in the server (i.e.-being +// read in, sent replies). +- (NSUInteger)activeRequestCount; + +@end + +@interface NSObject (GTMHTTPServerDeletateMethods) +- (GTMHTTPResponseMessage *)httpServer:(GTMHTTPServer *)server + handleRequest:(GTMHTTPRequestMessage *)request; +@end + +// ---------------------------------------------------------------------------- + +// Encapsulates an http request, one of these is sent to the server's delegate +// for each request. +@interface GTMHTTPRequestMessage : NSObject { + @private + CFHTTPMessageRef message_; +} +- (NSString *)version; +- (NSURL *)URL; +- (NSString *)method; +- (NSData *)body; +- (NSDictionary *)allHeaderFieldValues; +@end + +// ---------------------------------------------------------------------------- + +// Encapsulates an http response, the server's delegate should return one for +// each request received. +@interface GTMHTTPResponseMessage : NSObject { + @private + CFHTTPMessageRef message_; +} ++ (id)responseWithHTMLString:(NSString *)htmlString; ++ (id)responseWithBody:(NSData *)body + contentType:(NSString *)contentType + statusCode:(int)statusCode; ++ (id)emptyResponseWithCode:(int)statusCode; +// TODO: class method for redirections? +// TODO: add helper for expire/no-cache +- (void)setValue:(NSString*)value forHeaderField:(NSString*)headerField; +@end diff --git a/Foundation/GTMHTTPServer.m b/Foundation/GTMHTTPServer.m new file mode 100644 index 0000000..daa6a4e --- /dev/null +++ b/Foundation/GTMHTTPServer.m @@ -0,0 +1,584 @@ +// +// GTMHTTPServer.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. +// +// Based a little on HTTPServer, part of the CocoaHTTPServer sample code +// http://developer.apple.com/samplecode/CocoaHTTPServer/index.html +// + +#import +#import +#import + +#define GTMHTTPSERVER_DEFINE_GLOBALS +#import "GTMHTTPServer.h" +#import "GTMDebugSelectorValidation.h" +#import "GTMGarbageCollection.h" +#import "GTMDefines.h" + +@interface GTMHTTPServer (PrivateMethods) +- (void)acceptedConnectionNotification:(NSNotification *)notification; +- (NSMutableDictionary *)newConnectionWithFileHandle:(NSFileHandle *)fileHandle; +- (void)dataAvailableNotification:(NSNotification *)notification; +- (NSMutableDictionary *)lookupConnection:(NSFileHandle *)fileHandle; +- (void)closeConnection:(NSMutableDictionary *)connDict; +- (void)sendResponseOnNewThread:(NSMutableDictionary *)connDict; +- (void)sentResponse:(NSMutableDictionary *)connDict; +@end + +// keys for our connection dictionaries +static NSString *kFileHandle = @"FileHandle"; +static NSString *kRequest = @"Request"; +static NSString *kResponse = @"Response"; + +@interface GTMHTTPRequestMessage (PrivateHelpers) +- (BOOL)isHeaderComplete; +- (BOOL)appendData:(NSData *)data; +- (NSString *)headerFieldValueForKey:(NSString *)key; +- (UInt32)contentLength; +- (void)setBody:(NSData *)body; +@end + +@interface GTMHTTPResponseMessage (PrivateMethods) +- (id)initWithBody:(NSData *)body + contentType:(NSString *)contentType + statusCode:(int)statusCode; +- (NSData*)serializedData; +@end + +@implementation GTMHTTPServer + +- (id)init { + return [self initWithDelegate:nil]; +} + +- (id)initWithDelegate:(id)delegate { + self = [super init]; + if (self) { + if (!delegate) { + _GTMDevLog(@"missing delegate"); + [self release]; + return nil; + } + delegate_ = delegate; + GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(delegate_, + @selector(httpServer:handleRequest:), + // return type + @encode(GTMHTTPResponseMessage *), + // args + @encode(GTMHTTPServer *), + @encode(GTMHTTPRequestMessage *), + NULL); + localhostOnly_ = YES; + connections_ = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)dealloc { + [self stop]; + [super dealloc]; +} + +- (void)finalize { + [self stop]; + [super finalize]; +} + +- (id)delegate { + return delegate_; +} + +- (uint16_t)port { + return port_; +} + +- (void)setPort:(uint16_t)port { + port_ = port; +} + +- (BOOL)localhostOnly { + return localhostOnly_; +} + +- (void)setLocalhostOnly:(BOOL)yesno { + localhostOnly_ = yesno; +} + +- (BOOL)start:(NSError **)error { + _GTMDevAssert(listenHandle_ == nil, + @"start called when we already have a listenHandle_"); + + if (error) *error = NULL; + + NSInteger startFailureCode = 0; + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd <= 0) { + // COV_NF_START - we'd need to use up *all* sockets to test this? + startFailureCode = kGTMHTTPServerSocketCreateFailedError; + goto startFailed; + // COV_NF_END + } + + // enable address reuse quicker after we are done w/ our socket + int yes = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + (void *)&yes, (socklen_t)sizeof(yes)) != 0) { + _GTMDevLog(@"failed to mark the socket as reusable"); // COV_NF_LINE + } + + // bind + struct sockaddr_in addr; + bzero(&addr, sizeof(addr)); + addr.sin_len = sizeof(addr); + addr.sin_family = AF_INET; + addr.sin_port = htons(port_); + if (localhostOnly_) { + addr.sin_addr.s_addr = htonl(0x7F000001); + } else { + // COV_NF_START - testing this could cause a leopard firewall prompt during tests. + addr.sin_addr.s_addr = htonl(INADDR_ANY); + // COV_NF_END + } + if (bind(fd, (struct sockaddr*)(&addr), (socklen_t)sizeof(addr)) != 0) { + startFailureCode = kGTMHTTPServerBindFailedError; + goto startFailed; + } + + // collect the port back out + if (port_ == 0) { + socklen_t len = (socklen_t)sizeof(addr); + if (getsockname(fd, (struct sockaddr*)(&addr), &len) == 0) { + port_ = ntohs(addr.sin_port); + } + } + + // tell it to listen for connections + if (listen(fd, 5) != 0) { + // COV_NF_START + startFailureCode = kGTMHTTPServerListenFailedError; + goto startFailed; + // COV_NF_END + } + + // now use a filehandle to accept connections + listenHandle_ = + [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES]; + if (listenHandle_ == nil) { + // COV_NF_START - we'd need to run out of memory to test this? + startFailureCode = kGTMHTTPServerHandleCreateFailedError; + goto startFailed; + // COV_NF_END + } + + // setup notifications for connects + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:self + selector:@selector(acceptedConnectionNotification:) + name:NSFileHandleConnectionAcceptedNotification + object:listenHandle_]; + [listenHandle_ acceptConnectionInBackgroundAndNotify]; + + // TODO: maybe hit the delegate incase it wants to register w/ NSNetService, + // or just know we're up and running? + + return YES; + +startFailed: + if (error) { + *error = [[[NSError alloc] initWithDomain:kGTMHTTPServerErrorDomain + code:startFailureCode + userInfo:nil] autorelease]; + } + if (fd > 0) { + close(fd); + } + return NO; +} + +- (void)stop { + if (listenHandle_) { + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center removeObserver:self + name:NSFileHandleConnectionAcceptedNotification + object:listenHandle_]; + [listenHandle_ release]; + listenHandle_ = nil; + // TODO: maybe hit the delegate in case it wants to unregister w/ + // NSNetService, or just know we've stopped running? + } + [connections_ removeAllObjects]; +} + +- (NSUInteger)activeRequestCount { + return [connections_ count]; +} + +- (NSString *)description { + NSString *result = + [NSString stringWithFormat:@"%@<%p>{ port=%d localHostOnly=%@ status=%@ }", + [self class], self, port_, (localhostOnly_ ? @"YES" : @"NO"), + (listenHandle_ != nil ? @"Started" : @"Stopped") ]; + return result; +} + + +@end + +@implementation GTMHTTPServer (PrivateMethods) + +- (void)acceptedConnectionNotification:(NSNotification *)notification { + NSDictionary *userInfo = [notification userInfo]; + NSFileHandle *newConnection = + [userInfo objectForKey:NSFileHandleNotificationFileHandleItem]; + _GTMDevAssert(newConnection != nil, + @"failed to get the connection in the notification: %@", + notification); + + // make sure we accept more... + [listenHandle_ acceptConnectionInBackgroundAndNotify]; + + // TODO: could let the delegate look at the address, before we start working + // on it. + + NSMutableDictionary *connDict = + [self newConnectionWithFileHandle:newConnection]; + [connections_ addObject:connDict]; +} + +- (NSMutableDictionary *)newConnectionWithFileHandle:(NSFileHandle *)fileHandle { + NSMutableDictionary *result = [NSMutableDictionary dictionary]; + + [result setObject:fileHandle forKey:kFileHandle]; + + GTMHTTPRequestMessage *request = + [[[GTMHTTPRequestMessage alloc] init] autorelease]; + [result setObject:request forKey:kRequest]; + + // setup for data notifications + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:self + selector:@selector(dataAvailableNotification:) + name:NSFileHandleReadCompletionNotification + object:fileHandle]; + [fileHandle readInBackgroundAndNotify]; + + return result; +} + +- (void)dataAvailableNotification:(NSNotification *)notification { + NSFileHandle *connectionHandle = [notification object]; + NSMutableDictionary *connDict = [self lookupConnection:connectionHandle]; + if (connDict == nil) return; // we are no longer tracking this one + + NSDictionary *userInfo = [notification userInfo]; + NSData *readData = [userInfo objectForKey:NSFileHandleNotificationDataItem]; + if ([readData length] == 0) { + // remote side closed + [self closeConnection:connDict]; + return; + } + + // Like Apple's sample, we just keep adding data until we get a full header + // and any referenced body. + + GTMHTTPRequestMessage *request = [connDict objectForKey:kRequest]; + [request appendData:readData]; + + // Is the header complete yet? + if (![request isHeaderComplete]) { + // more data... + [connectionHandle readInBackgroundAndNotify]; + return; + } + + // Do we have all the body? + UInt32 contentLength = [request contentLength]; + NSData *body = [request body]; + NSUInteger bodyLength = [body length]; + if (contentLength > bodyLength) { + // need more data... + [connectionHandle readInBackgroundAndNotify]; + return; + } + + if (contentLength < bodyLength) { + // We got extra (probably someone trying to pipeline on us), trim + // and let the extra data go... + NSData *newBody = [NSData dataWithBytes:[body bytes] + length:contentLength]; + [request setBody:newBody]; + _GTMDevLog(@"Got %lu extra bytes on http request, ignoring them", + (unsigned long)(bodyLength - contentLength)); + } + + GTMHTTPResponseMessage *response = nil; + @try { + // Off to the delegate + response = [delegate_ httpServer:self handleRequest:request]; + } @catch (NSException *e) { + _GTMDevLog(@"Exception trying to handle http request: %@", e); + } + + if (!response) { + [self closeConnection:connDict]; + return; + } + + // We don't support connection reuse, so we add (force) the header to close + // every connection. + [response setValue:@"close" forHeaderField:@"Connection"]; + + // spawn thread to send reply (since we do a blocking send) + [connDict setObject:response forKey:kResponse]; + [NSThread detachNewThreadSelector:@selector(sendResponseOnNewThread:) + toTarget:self + withObject:connDict]; +} + +- (NSMutableDictionary *)lookupConnection:(NSFileHandle *)fileHandle { + NSUInteger max = [connections_ count]; + for (NSUInteger x = 0; x < max; ++x) { + NSMutableDictionary *connDict = [connections_ objectAtIndex:x]; + if (fileHandle == [connDict objectForKey:kFileHandle]) { + return connDict; + } + } + return nil; +} + +- (void)closeConnection:(NSMutableDictionary *)connDict { + // remove the notification + NSFileHandle *connectionHandle = [connDict objectForKey:kFileHandle]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center removeObserver:self + name:NSFileHandleReadCompletionNotification + object:connectionHandle]; + + // remove it from the list + [connections_ removeObject:connDict]; +} + +- (void)sendResponseOnNewThread:(NSMutableDictionary *)connDict { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + @try { + GTMHTTPResponseMessage *response = [connDict objectForKey:kResponse]; + NSFileHandle *connectionHandle = [connDict objectForKey:kFileHandle]; + NSData *serialized = [response serializedData]; + [connectionHandle writeData:serialized]; + } @catch (NSException *e) { + // TODO: let the delegate know about the exception (but do it on the main + // thread) + _GTMDevLog(@"exception while sending reply: %@", e); + } + + // back to the main thread to close things down + [self performSelectorOnMainThread:@selector(sentResponse:) + withObject:connDict + waitUntilDone:NO]; + + [pool release]; +} + +- (void)sentResponse:(NSMutableDictionary *)connDict { + // make sure we're still tracking this connection (in case server was stopped) + NSFileHandle *connection = [connDict objectForKey:kFileHandle]; + NSMutableDictionary *connDict2 = [self lookupConnection:connection]; + if (connDict != connDict2) return; + + // TODO: message the delegate that it was sent + + // close it down + [self closeConnection:connDict]; +} + +@end + +#pragma mark - + +@implementation GTMHTTPRequestMessage + +- (id)init { + self = [super init]; + if (self) { + message_ = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, YES); + } + return self; +} + +- (void)dealloc { + CFRelease(message_); + [super dealloc]; +} + +- (NSString *)version { + return [GTMNSMakeCollectable(CFHTTPMessageCopyVersion(message_)) autorelease]; +} + +- (NSURL *)URL { + return [GTMNSMakeCollectable(CFHTTPMessageCopyRequestURL(message_)) autorelease]; +} + +- (NSString *)method { + return [GTMNSMakeCollectable(CFHTTPMessageCopyRequestMethod(message_)) autorelease]; +} + +- (NSData *)body { + return [GTMNSMakeCollectable(CFHTTPMessageCopyBody(message_)) autorelease]; +} + +- (NSDictionary *)allHeaderFieldValues { + return GTMNSMakeCollectable(CFHTTPMessageCopyAllHeaderFields(message_)); +} + +- (NSString *)description { + CFStringRef desc = CFCopyDescription(message_); + NSString *result = + [NSString stringWithFormat:@"%@<%p>{ message=%@ }", [self class], self, desc]; + CFRelease(desc); + return result; +} + +@end + +@implementation GTMHTTPRequestMessage (PrivateHelpers) + +- (BOOL)isHeaderComplete { + return CFHTTPMessageIsHeaderComplete(message_) ? YES : NO; +} + +- (BOOL)appendData:(NSData *)data { + return CFHTTPMessageAppendBytes(message_, + [data bytes], [data length]) ? YES : NO; +} + +- (NSString *)headerFieldValueForKey:(NSString *)key { + CFStringRef value = NULL; + if (key) { + value = CFHTTPMessageCopyHeaderFieldValue(message_, (CFStringRef)key); + } + return [GTMNSMakeCollectable(value) autorelease]; +} + +- (UInt32)contentLength { + return [[self headerFieldValueForKey:@"Content-Length"] intValue]; +} + +- (void)setBody:(NSData *)body { + if (!body) { + body = [NSData data]; // COV_NF_LINE - can only happen in we fail to make the new data object + } + CFHTTPMessageSetBody(message_, (CFDataRef)body); +} + +@end + +#pragma mark - + +@implementation GTMHTTPResponseMessage + +- (id)init { + return [self initWithBody:nil contentType:nil statusCode:0]; +} + +- (void)dealloc { + if (message_) { + CFRelease(message_); + } + [super dealloc]; +} + ++ (id)responseWithHTMLString:(NSString *)htmlString { + return [self responseWithBody:[htmlString dataUsingEncoding:NSUTF8StringEncoding] + contentType:@"text/html; charset=UTF-8" + statusCode:200]; +} + ++ (id)responseWithBody:(NSData *)body + contentType:(NSString *)contentType + statusCode:(int)statusCode { + return [[[[self class] alloc] initWithBody:body + contentType:contentType + statusCode:statusCode] autorelease]; +} + ++ (id)emptyResponseWithCode:(int)statusCode { + return [[[[self class] alloc] initWithBody:nil + contentType:nil + statusCode:statusCode] autorelease]; +} + +- (void)setValue:(NSString*)value forHeaderField:(NSString*)headerField { + if ([headerField length] == 0) return; + if (value == nil) { + value = @""; + } + CFHTTPMessageSetHeaderFieldValue(message_, + (CFStringRef)headerField, (CFStringRef)value); +} + +- (NSString *)description { + CFStringRef desc = CFCopyDescription(message_); + NSString *result = + [NSString stringWithFormat:@"%@<%p>{ message=%@ }", [self class], self, desc]; + CFRelease(desc); + return result; +} + +@end + +@implementation GTMHTTPResponseMessage (PrivateMethods) + +- (id)initWithBody:(NSData *)body + contentType:(NSString *)contentType + statusCode:(int)statusCode { + self = [super init]; + if (self) { + if ((statusCode < 100) || (statusCode > 599)) { + [self release]; + return nil; + } + message_ = CFHTTPMessageCreateResponse(kCFAllocatorDefault, + statusCode, NULL, + kCFHTTPVersion1_0); + if (!message_) { + // COV_NF_START + [self release]; + return nil; + // COV_NF_END + } + NSUInteger bodyLength = 0; + if (body) { + bodyLength = [body length]; + CFHTTPMessageSetBody(message_, (CFDataRef)body); + } + if ([contentType length] == 0) { + contentType = @"text/html"; + } + NSString *bodyLenStr = + [NSString stringWithFormat:@"%lu", (unsigned long)bodyLength]; + [self setValue:bodyLenStr forHeaderField:@"Content-Length"]; + [self setValue:contentType forHeaderField:@"Content-Type"]; + } + return self; +} + +- (NSData *)serializedData { + return [GTMNSMakeCollectable(CFHTTPMessageCopySerializedMessage(message_)) autorelease]; +} + +@end diff --git a/Foundation/GTMHTTPServerTest.m b/Foundation/GTMHTTPServerTest.m new file mode 100644 index 0000000..d96d54e --- /dev/null +++ b/Foundation/GTMHTTPServerTest.m @@ -0,0 +1,573 @@ +// +// GTMHTTPServerTest.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 +#import +#import +#import "GTMSenTestCase.h" +#import "GTMUnitTestDevLog.h" +#import "GTMHTTPServer.h" +#import "GTMRegex.h" + +@interface GTMHTTPServerTest : GTMTestCase { + NSData *fetchedData_; +} +@end + +@interface GTMHTTPServerTest (PrivateMethods) +- (NSData *)fetchFromPort:(unsigned short)port + payload:(NSString *)payload + chunkSize:(NSUInteger)chunkSize; +- (NSFileHandle *)fileHandleSendingToPort:(unsigned short)port + payload:(NSString *)payload + chunkSize:(NSUInteger)chunkSize; +- (void)readFinished:(NSNotification *)notification; +@end + +// helper class +@interface TestServerDelegate : NSObject { + NSMutableArray *requests_; + NSMutableArray *responses_; +} ++ (id)testServerDelegate; +- (NSUInteger)requestCount; +- (GTMHTTPRequestMessage *)popRequest; +- (void)pushResponse:(GTMHTTPResponseMessage *)message; +@end + +// helper that throws while handling its request +@interface TestThrowingServerDelegate : TestServerDelegate +// since this method ALWAYS throws, we can mark it as noreturn +- (GTMHTTPResponseMessage *)httpServer:(GTMHTTPServer *)server + handleRequest:(GTMHTTPRequestMessage *)request __attribute__ ((noreturn)); +@end + +// The timings used for waiting for replies +const NSTimeInterval kGiveUpInterval = 5.0; +const NSTimeInterval kRunLoopInterval = 0.01; + +// the size we break writes up into to test the reading code and how long to +// wait between writes. +const NSUInteger kSendChunkSize = 12; +const NSTimeInterval kSendChunkInterval = 0.05; + +// ---------------------------------------------------------------------------- + +@implementation GTMHTTPServerTest + +- (void)testInit { + // bad delegates + [GTMUnitTestDevLog expectString:@"missing delegate"]; + STAssertNil([[GTMHTTPServer alloc] init], nil); + [GTMUnitTestDevLog expectString:@"missing delegate"]; + STAssertNil([[GTMHTTPServer alloc] initWithDelegate:nil], nil); + + TestServerDelegate *delegate = [TestServerDelegate testServerDelegate]; + STAssertNotNil(delegate, nil); + GTMHTTPServer *server = + [[[GTMHTTPServer alloc] initWithDelegate:delegate] autorelease]; + STAssertNotNil(server, nil); + + // some attributes + + STAssertTrue([server delegate] == delegate, nil); + + [server setLocalhostOnly:NO]; + STAssertFalse([server localhostOnly], nil); + [server setLocalhostOnly:YES]; + STAssertTrue([server localhostOnly], nil); + + STAssertEquals([server port], (uint16_t)0, nil); + [server setPort:8080]; + STAssertEquals([server port], (uint16_t)8080, nil); + [server setPort:80]; + STAssertEquals([server port], (uint16_t)80, nil); + + // description (atleast 10 chars) + STAssertGreaterThan([[server description] length], (NSUInteger)10, nil); +} + +- (void)testStartStop { + TestServerDelegate *delegate1 = [TestServerDelegate testServerDelegate]; + STAssertNotNil(delegate1, nil); + GTMHTTPServer *server1 = + [[[GTMHTTPServer alloc] initWithDelegate:delegate1] autorelease]; + STAssertNotNil(server1, nil); + NSError *error = nil; + STAssertTrue([server1 start:&error], @"failed to start (error=%@)", error); + STAssertNil(error, @"error: %@", error); + STAssertGreaterThanOrEqual([server1 port], (uint16_t)1024, + @"how'd we get a reserved port?"); + + TestServerDelegate *delegate2 = [TestServerDelegate testServerDelegate]; + STAssertNotNil(delegate2, nil); + GTMHTTPServer *server2 = + [[[GTMHTTPServer alloc] initWithDelegate:delegate2] autorelease]; + STAssertNotNil(server2, nil); + + // try the reserved port + [server2 setPort:666]; + error = nil; + STAssertFalse([server2 start:&error], nil); + STAssertNotNil(error, nil); + STAssertEqualObjects([error domain], kGTMHTTPServerErrorDomain, nil); + STAssertEquals([error code], (NSInteger)kGTMHTTPServerBindFailedError, + @"port should have been reserved"); + + // try the same port + [server2 setPort:[server1 port]]; + error = nil; + STAssertFalse([server2 start:&error], nil); + STAssertNotNil(error, nil); + STAssertEqualObjects([error domain], kGTMHTTPServerErrorDomain, nil); + STAssertEquals([error code], (NSInteger)kGTMHTTPServerBindFailedError, + @"port should have been in use"); + + // try a random port again so we really start (prove two can run at once) + [server2 setPort:0]; + error = nil; + STAssertTrue([server2 start:&error], @"failed to start (error=%@)", error); + STAssertNil(error, @"error: %@", error); + + // shut them down + [server1 stop]; + [server2 stop]; +} + +- (void)testRequests { + TestServerDelegate *delegate = [TestServerDelegate testServerDelegate]; + STAssertNotNil(delegate, nil); + GTMHTTPServer *server = + [[[GTMHTTPServer alloc] initWithDelegate:delegate] autorelease]; + STAssertNotNil(server, nil); + NSError *error = nil; + STAssertTrue([server start:&error], @"failed to start (error=%@)", error); + STAssertNil(error, @"error: %@", error); + + // a request to test all the fields of a request object + + NSString *payload = + @"PUT /some/server/path HTTP/1.0\r\n" + @"Content-Length: 16\r\n" + @"Custom-Header: Custom_Value\r\n" + @"\r\n" + @"this is the body"; + NSData *reply = + [self fetchFromPort:[server port] payload:payload chunkSize:kSendChunkSize]; + STAssertNotNil(reply, nil); + + GTMHTTPRequestMessage *request = [delegate popRequest]; + STAssertEqualObjects([request version], @"HTTP/1.0", nil); + STAssertEqualObjects([[request URL] absoluteString], @"/some/server/path", nil); + STAssertEqualObjects([request method], @"PUT", nil); + STAssertEqualObjects([request body], + [@"this is the body" dataUsingEncoding:NSUTF8StringEncoding], + nil); + NSDictionary *allHeaders = [request allHeaderFieldValues]; + STAssertNotNil(allHeaders, nil); + STAssertEquals([allHeaders count], (NSUInteger)2, nil); + STAssertEqualObjects([allHeaders objectForKey:@"Content-Length"], + @"16", nil); + STAssertEqualObjects([allHeaders objectForKey:@"Custom-Header"], + @"Custom_Value", nil); + STAssertGreaterThan([[request description] length], (NSUInteger)10, nil); + + // test different request types (in simple form) + + typedef struct { + NSString *method; + NSString *url; + } TestData; + + TestData data[] = { + { @"GET", @"/foo/bar" }, + { @"HEAD", @"/foo/baz" }, + { @"POST", @"/foo" }, + { @"PUT", @"/foo/spam" }, + { @"DELETE", @"/fooby/doo" }, + { @"TRACE", @"/something.html" }, + { @"CONNECT", @"/spam" }, + { @"OPTIONS", @"/wee/doggies" }, + }; + + for (size_t i = 0; i < sizeof(data) / sizeof(TestData); i++) { + payload = [NSString stringWithFormat:@"%@ %@ HTTP/1.0\r\n\r\n", + data[i].method, data[i].url]; + STAssertNotNil(payload, nil); + reply = [self fetchFromPort:[server port] + payload:payload + chunkSize:kSendChunkSize]; + STAssertNotNil(reply, // just want a reply in this test + @"failed of method %@", data[i].method); + request = [delegate popRequest]; + STAssertEqualObjects([[request URL] absoluteString], data[i].url, + @"urls didn't match for index %d", i); + STAssertEqualObjects([request method], data[i].method, + @"methods didn't match for index %d", i); + } + + [server stop]; +} + +- (void)testResponses { + + // some quick init tests for invalid things + STAssertNil([[GTMHTTPResponseMessage alloc] init], nil); + STAssertNil([GTMHTTPResponseMessage responseWithBody:nil + contentType:nil + statusCode:99], + nil); + STAssertNil([GTMHTTPResponseMessage responseWithBody:nil + contentType:nil + statusCode:602], + nil); + + TestServerDelegate *delegate = [TestServerDelegate testServerDelegate]; + STAssertNotNil(delegate, nil); + GTMHTTPServer *server = + [[[GTMHTTPServer alloc] initWithDelegate:delegate] autorelease]; + STAssertNotNil(server, nil); + NSError *error = nil; + STAssertTrue([server start:&error], @"failed to start (error=%@)", error); + STAssertNil(error, @"error: %@", error); + + // test the html helper + + GTMHTTPResponseMessage *expectedResponse = + [GTMHTTPResponseMessage responseWithHTMLString:@"Success!"]; + STAssertNotNil(expectedResponse, nil); + STAssertGreaterThan([[expectedResponse description] length], + (NSUInteger)0, nil); + [delegate pushResponse:expectedResponse]; + NSData *responseData = [self fetchFromPort:[server port] + payload:@"GET /foo HTTP/1.0\r\n\r\n" + chunkSize:kSendChunkSize]; + STAssertNotNil(responseData, nil); + NSString *responseString = + [[[NSString alloc] initWithData:responseData + encoding:NSUTF8StringEncoding] autorelease]; + STAssertNotNil(responseString, nil); + STAssertTrue([responseString hasPrefix:@"HTTP/1.0 200 OK"], nil); + STAssertTrue([responseString hasSuffix:@"Success!"], @"should end w/ our data"); + STAssertNotEquals([responseString rangeOfString:@"Content-Length: 8"].location, + (NSUInteger)NSNotFound, nil); + STAssertNotEquals([responseString rangeOfString:@"Content-Type: text/html; charset=UTF-8"].location, + (NSUInteger)NSNotFound, nil); + + // test the plain code response + + expectedResponse = [GTMHTTPResponseMessage emptyResponseWithCode:299]; + STAssertNotNil(expectedResponse, nil); + STAssertGreaterThan([[expectedResponse description] length], + (NSUInteger)0, nil); + [delegate pushResponse:expectedResponse]; + responseData = [self fetchFromPort:[server port] + payload:@"GET /foo HTTP/1.0\r\n\r\n" + chunkSize:kSendChunkSize]; + STAssertNotNil(responseData, nil); + responseString = + [[[NSString alloc] initWithData:responseData + encoding:NSUTF8StringEncoding] autorelease]; + STAssertNotNil(responseString, nil); + STAssertTrue([responseString hasPrefix:@"HTTP/1.0 299 "], nil); + STAssertNotEquals([responseString rangeOfString:@"Content-Length: 0"].location, + (NSUInteger)NSNotFound, nil); + STAssertNotEquals([responseString rangeOfString:@"Content-Type: text/html"].location, + (NSUInteger)NSNotFound, nil); + + // test the general api w/ extra header add + + expectedResponse = + [GTMHTTPResponseMessage responseWithBody:[@"FOO" dataUsingEncoding:NSUTF8StringEncoding] + contentType:@"some/type" + statusCode:298]; + STAssertNotNil(expectedResponse, nil); + STAssertGreaterThan([[expectedResponse description] length], + (NSUInteger)0, nil); + [expectedResponse setValue:@"Custom_Value" + forHeaderField:@"Custom-Header"]; + [expectedResponse setValue:nil + forHeaderField:@"Custom-Header2"]; + [delegate pushResponse:expectedResponse]; + responseData = [self fetchFromPort:[server port] + payload:@"GET /foo HTTP/1.0\r\n\r\n" + chunkSize:kSendChunkSize]; + STAssertNotNil(responseData, nil); + responseString = + [[[NSString alloc] initWithData:responseData + encoding:NSUTF8StringEncoding] autorelease]; + STAssertNotNil(responseString, nil); + STAssertTrue([responseString hasPrefix:@"HTTP/1.0 298"], nil); + STAssertTrue([responseString hasSuffix:@"FOO"], @"should end w/ our data"); + STAssertNotEquals([responseString rangeOfString:@"Content-Length: 3"].location, + (NSUInteger)NSNotFound, nil); + STAssertNotEquals([responseString rangeOfString:@"Content-Type: some/type"].location, + (NSUInteger)NSNotFound, nil); + STAssertNotEquals([responseString rangeOfString:@"Custom-Header: Custom_Value"].location, + (NSUInteger)NSNotFound, nil); + STAssertNotEquals([responseString rangeOfString:@"Custom-Header2: "].location, + (NSUInteger)NSNotFound, nil); + + [server stop]; +} + +- (void)testRequstEdgeCases { + // test all the odd things about requests + + TestServerDelegate *delegate = [TestServerDelegate testServerDelegate]; + STAssertNotNil(delegate, nil); + GTMHTTPServer *server = + [[[GTMHTTPServer alloc] initWithDelegate:delegate] autorelease]; + STAssertNotNil(server, nil); + NSError *error = nil; + STAssertTrue([server start:&error], @"failed to start (error=%@)", error); + STAssertNil(error, @"error: %@", error); + + // extra data (ie-pipelining) + + NSString *payload = + @"GET /some/server/path HTTP/1.0\r\n" + @"\r\n" + @"GET /some/server/path/too HTTP/1.0\r\n" + @"\r\n"; + // don't chunk this, we want to make sure both requests get to our server + [GTMUnitTestDevLog expectString:@"Got 38 extra bytes on http request, " + "ignoring them"]; + NSData *reply = + [self fetchFromPort:[server port] payload:payload chunkSize:0]; + STAssertNotNil(reply, nil); + STAssertEquals([delegate requestCount], (NSUInteger)1, nil); + + // close w/o full request + { + // local pool so we can force our handle to close + NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init]; + NSFileHandle *handle = + [self fileHandleSendingToPort:[server port] + payload:@"GET /some/server/path HTTP/" + chunkSize:kSendChunkSize]; + STAssertNotNil(handle, nil); + // spin the run loop so reads the start of the request + NSDate* loopIntervalDate = + [NSDate dateWithTimeIntervalSinceNow:kRunLoopInterval]; + [[NSRunLoop currentRunLoop] runUntilDate:loopIntervalDate]; + // make sure we see the request at this point + STAssertEquals([server activeRequestCount], (NSUInteger)1, + @"should have started the request by now"); + // drop the pool to close the connection + [localPool release]; + // spin the run loop so it should see the close + loopIntervalDate = [NSDate dateWithTimeIntervalSinceNow:kRunLoopInterval]; + [[NSRunLoop currentRunLoop] runUntilDate:loopIntervalDate]; + // make sure we didn't get a request (1 is from test before) and make sure + // we don't have some in flight. + STAssertEquals([delegate requestCount], (NSUInteger)1, + @"shouldn't have gotten another request"); + STAssertEquals([server activeRequestCount], (NSUInteger)0, + @"should have cleaned up the pending connection"); + } + +} + +- (void)testExceptionDuringRequest { + + TestServerDelegate *delegate = [TestThrowingServerDelegate testServerDelegate]; + STAssertNotNil(delegate, nil); + GTMHTTPServer *server = + [[[GTMHTTPServer alloc] initWithDelegate:delegate] autorelease]; + STAssertNotNil(server, nil); + NSError *error = nil; + STAssertTrue([server start:&error], @"failed to start (error=%@)", error); + STAssertNil(error, @"error: %@", error); + [GTMUnitTestDevLog expectString:@"Exception trying to handle http request: " + "To test our handling"]; + NSData *responseData = [self fetchFromPort:[server port] + payload:@"GET /foo HTTP/1.0\r\n\r\n" + chunkSize:kSendChunkSize]; + STAssertNotNil(responseData, nil); + STAssertEquals([responseData length], (NSUInteger)0, nil); + STAssertEquals([delegate requestCount], (NSUInteger)1, nil); + STAssertEquals([server activeRequestCount], (NSUInteger)0, nil); +} + +@end + +// ---------------------------------------------------------------------------- + +@implementation GTMHTTPServerTest (PrivateMethods) + +- (NSData *)fetchFromPort:(unsigned short)port + payload:(NSString *)payload + chunkSize:(NSUInteger)chunkSize { + fetchedData_ = nil; + + NSFileHandle *handle = [self fileHandleSendingToPort:port + payload:payload + chunkSize:chunkSize]; + + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:self + selector:@selector(readFinished:) + name:NSFileHandleReadToEndOfFileCompletionNotification + object:handle]; + [handle readToEndOfFileInBackgroundAndNotify]; + + // wait for our reply + NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:kGiveUpInterval]; + while (!fetchedData_ && [giveUpDate timeIntervalSinceNow] > 0) { + NSDate* loopIntervalDate = + [NSDate dateWithTimeIntervalSinceNow:kRunLoopInterval]; + [[NSRunLoop currentRunLoop] runUntilDate:loopIntervalDate]; + } + + [center removeObserver:self + name:NSFileHandleReadToEndOfFileCompletionNotification + object:handle]; + + NSData *result = [fetchedData_ autorelease]; + fetchedData_ = nil; + return result; +} + +- (NSFileHandle *)fileHandleSendingToPort:(unsigned short)port + payload:(NSString *)payload + chunkSize:(NSUInteger)chunkSize { + int fd = socket(AF_INET, SOCK_STREAM, 0); + STAssertGreaterThan(fd, 0, @"failed to create socket"); + + struct sockaddr_in addr; + bzero(&addr, sizeof(addr)); + addr.sin_len = sizeof(addr); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(0x7F000001); + int connectResult = + connect(fd, (struct sockaddr*)(&addr), (socklen_t)sizeof(addr)); + STAssertEquals(connectResult, 0, nil); + + NSFileHandle *handle = + [[[NSFileHandle alloc] initWithFileDescriptor:fd + closeOnDealloc:YES] autorelease]; + STAssertNotNil(handle, nil); + + NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding]; + + // we can send in one block or in chunked mode + if (chunkSize > 0) { + // we don't write the data in one large block, instead of write it out + // in bits to help test the data collection code. + NSUInteger length = [payloadData length]; + for (NSUInteger x = 0 ; x < length ; x += chunkSize) { + NSUInteger dataChunkSize = length - x; + if (dataChunkSize > chunkSize) { + dataChunkSize = chunkSize; + } + NSData *dataChunk + = [payloadData subdataWithRange:NSMakeRange(x, dataChunkSize)]; + [handle writeData:dataChunk]; + // delay after all but the last chunk to give it time to be read. + if ((x + chunkSize) < length) { + NSDate* loopIntervalDate = + [NSDate dateWithTimeIntervalSinceNow:kSendChunkInterval]; + [[NSRunLoop currentRunLoop] runUntilDate:loopIntervalDate]; + } + } + } else { + [handle writeData:payloadData]; + } + + return handle; +} + +- (void)readFinished:(NSNotification *)notification { + NSDictionary *userInfo = [notification userInfo]; + fetchedData_ = + [[userInfo objectForKey:NSFileHandleNotificationDataItem] retain]; +} + +@end + +// ---------------------------------------------------------------------------- + +@implementation TestServerDelegate + +- (id)init { + self = [super init]; + if (self) { + requests_ = [[NSMutableArray alloc] init]; + responses_ = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)dealloc { + [requests_ release]; + [responses_ release]; + [super dealloc]; +} + ++ (id)testServerDelegate { + return [[[[self class] alloc] init] autorelease]; +} + +- (NSUInteger)requestCount { + return [requests_ count]; +} + +- (GTMHTTPRequestMessage *)popRequest { + GTMHTTPRequestMessage *result = [[[requests_ lastObject] retain] autorelease]; + [requests_ removeLastObject]; + return result; +} + +- (void)pushResponse:(GTMHTTPResponseMessage *)message { + [responses_ addObject:message]; +} + +- (GTMHTTPResponseMessage *)httpServer:(GTMHTTPServer *)server + handleRequest:(GTMHTTPRequestMessage *)request { + [requests_ addObject:request]; + + GTMHTTPResponseMessage *result = nil; + if ([responses_ count] > 0) { + result = [[[responses_ lastObject] retain] autorelease]; + [responses_ removeLastObject]; + } else { + result = [GTMHTTPResponseMessage responseWithHTMLString:@"success"]; + } + return result; +} + +@end + +// ---------------------------------------------------------------------------- + +@implementation TestThrowingServerDelegate + +- (GTMHTTPResponseMessage *)httpServer:(GTMHTTPServer *)server + handleRequest:(GTMHTTPRequestMessage *)request { + // let the base do its normal work for counts, etc. + [super httpServer:server handleRequest:request]; + NSException *exception = + [NSException exceptionWithName:@"InternalTestingException" + reason:@"To test our handling" + userInfo:nil]; + @throw exception; +} + +@end diff --git a/Foundation/GTMNSAppleEvent+HandlerTest.applescript b/Foundation/GTMNSAppleEvent+HandlerTest.applescript new file mode 100644 index 0000000..ec99433 --- /dev/null +++ b/Foundation/GTMNSAppleEvent+HandlerTest.applescript @@ -0,0 +1,39 @@ +-- +-- 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. +-- + +property foo : 1 + +on test() +end test + +on testReturnOne() + return 1 +end testReturnOne + +on testReturnParam(param) + return param +end testReturnParam + +on testAddParams(param1, param2) + return param1 + param2 +end testAddParams + +on testAdd of a onto b given otherValue:d + return a + b + d +end testAdd + +on open +end open diff --git a/Foundation/GTMNSAppleEventDescriptor+Foundation.h b/Foundation/GTMNSAppleEventDescriptor+Foundation.h new file mode 100644 index 0000000..51a724f --- /dev/null +++ b/Foundation/GTMNSAppleEventDescriptor+Foundation.h @@ -0,0 +1,91 @@ +// +// NSAppleEventDescriptor+Foundation.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 +#import "GTMDefines.h" + +// A category for dealing with NSAppleEventDescriptors and NSArrays. +@interface NSAppleEventDescriptor (GTMAppleEventDescriptorArrayAdditions) +// Used to register the types you know how to convert into +// NSAppleEventDescriptors. +// See examples in NSAppleEventDescriptor+String, NSAppleEventDescriptor+Number +// etc. +// Args: +// selector - selector to call for any of the types in |types| +// types - an std c array of types of length |count| +// count - number of types in |types| ++ (void)gtm_registerSelector:(SEL)selector + forTypes:(DescType*)types + count:(NSUInteger)count; + +// Returns an NSObject for any NSAppleEventDescriptor +// Uses types registerd by registerSelector:forTypes:count: to determine +// what type of object to create. If it doesn't know a type, it attempts +// to return [self stringValue]. +- (id)gtm_objectValue; + +// Return an NSArray for an AEList +// Returns nil on failure. +- (NSArray*)gtm_arrayValue; + +// Return an NSDictionary for an AERecord +// Returns nil on failure. +- (NSDictionary*)gtm_dictionaryValue; + +// Return an NSNull for a desc of typeNull +// Returns nil on failure. +- (NSNull*)gtm_nullValue; + +// Return a NSAppleEventDescriptor for a double value. ++ (NSAppleEventDescriptor*)gtm_descriptorWithDouble:(double)real; + +// Return a NSAppleEventDescriptor for a float value. ++ (NSAppleEventDescriptor*)gtm_descriptorWithFloat:(float)real; + +// Return a NSAppleEventDescriptor for a CGFloat value. ++ (NSAppleEventDescriptor*)gtm_descriptorWithCGFloat:(CGFloat)real; + +// Attempt to extract a double value. Returns NAN on error. +- (double)gtm_doubleValue; + +// Attempt to extract a float value. Returns NAN on error. +- (float)gtm_floatValue; + +// Attempt to extract a CGFloat value. Returns NAN on error. +- (CGFloat)gtm_cgFloatValue; + +// Attempt to extract a NSNumber. Returns nil on error. +- (NSNumber*)gtm_numberValue; + +@end + +@interface NSObject (GTMAppleEventDescriptorObjectAdditions) +// A informal protocol that objects can override to return appleEventDescriptors +// for their type. The default is to return [self description] rolled up +// in an NSAppleEventDescriptor. Built in support for: +// NSArray, NSDictionary, NSNull, NSString, NSNumber and NSProcessInfo +- (NSAppleEventDescriptor*)gtm_appleEventDescriptor; +@end + +@interface NSAppleEventDescriptor (GTMAppleEventDescriptorAdditions) +// Allows you to send events. +// Returns YES if send was successful. +- (BOOL)gtm_sendEventWithMode:(AESendMode)mode + timeOut:(NSTimeInterval)timeout + reply:(NSAppleEventDescriptor**)reply; +@end diff --git a/Foundation/GTMNSAppleEventDescriptor+Foundation.m b/Foundation/GTMNSAppleEventDescriptor+Foundation.m new file mode 100644 index 0000000..2b6acd4 --- /dev/null +++ b/Foundation/GTMNSAppleEventDescriptor+Foundation.m @@ -0,0 +1,488 @@ +// +// GTMNSAppleEventDescriptor+Foundation.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 "GTMNSAppleEventDescriptor+Foundation.h" +#import "GTMFourCharCode.h" +#import // Needed Solely For keyASUserRecordFields + +// Map of types to selectors. +static NSMutableDictionary *gTypeMap = nil; + +@implementation NSAppleEventDescriptor (GTMAppleEventDescriptorArrayAdditions) + ++ (void)gtm_registerSelector:(SEL)selector + forTypes:(DescType*)types + count:(NSUInteger)count { + if (selector && types && count > 0) { + @synchronized(self) { + if (!gTypeMap) { + gTypeMap = [[NSMutableDictionary alloc] init]; + } + NSString *selString = NSStringFromSelector(selector); + for (NSUInteger i = 0; i < count; ++i) { + NSNumber *key = [NSNumber numberWithUnsignedInt:types[i]]; + NSString *exists = [gTypeMap objectForKey:key]; + if (exists) { + _GTMDevLog(@"%@ being replaced with %@ exists for type: %@", + exists, selString, key); + } + [gTypeMap setObject:selString forKey:key]; + } + } + } +} + +- (id)gtm_objectValue { + id value = nil; + + // Check our registered types to see if we have anything + if (gTypeMap) { + @synchronized(gTypeMap) { + DescType type = [self descriptorType]; + NSNumber *key = [NSNumber numberWithUnsignedInt:type]; + NSString *selectorString = [gTypeMap objectForKey:key]; + if (selectorString) { + SEL selector = NSSelectorFromString(selectorString); + value = [self performSelector:selector]; + } else { + value = [self stringValue]; + } + } + } + return value; +} + +- (NSArray*)gtm_arrayValue { + NSUInteger count = [self numberOfItems]; + NSAppleEventDescriptor *workingDesc = self; + if (count == 0) { + // Create a list to work with. + workingDesc = [self coerceToDescriptorType:typeAEList]; + count = [workingDesc numberOfItems]; + } + NSMutableArray *items = [NSMutableArray arrayWithCapacity:count]; + for (NSUInteger i = 1; i <= count; ++i) { + NSAppleEventDescriptor *desc = [workingDesc descriptorAtIndex:i]; + id value = [desc gtm_objectValue]; + if (!value) { + _GTMDevLog(@"Unknown type of descriptor %@", [desc description]); + return nil; + } + [items addObject:value]; + } + return items; +} + +- (NSDictionary*)gtm_dictionaryValue { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + NSAppleEventDescriptor *userRecord = [self descriptorForKeyword:keyASUserRecordFields]; + if (userRecord) { + NSEnumerator *userItems = [[userRecord gtm_arrayValue] objectEnumerator]; + NSString *key; + while ((key = [userItems nextObject])) { + NSString *value = [userItems nextObject]; + if (!value) { + _GTMDevLog(@"Got a key %@ with no value in %@", key, userItems); + return nil; + } + [dictionary setObject:value forKey:key]; + } + } else { + NSUInteger count = [self numberOfItems]; + for (NSUInteger i = 1; i <= count; ++i) { + AEKeyword key = [self keywordForDescriptorAtIndex:i]; + NSAppleEventDescriptor *desc = [self descriptorForKeyword:key]; + id value = [desc gtm_objectValue]; + if (!value) { + _GTMDevLog(@"Unknown type of descriptor %@", [desc description]); + return nil; + } + [dictionary setObject:value + forKey:[GTMFourCharCode fourCharCodeWithFourCharCode:key]]; + } + } + return dictionary; +} + +- (NSNull*)gtm_nullValue { + return [NSNull null]; +} + ++ (NSAppleEventDescriptor*)gtm_descriptorWithDouble:(double)real { + return [NSAppleEventDescriptor descriptorWithDescriptorType:typeIEEE64BitFloatingPoint + bytes:&real + length:sizeof(real)]; +} + ++ (NSAppleEventDescriptor*)gtm_descriptorWithFloat:(float)real { + return [NSAppleEventDescriptor descriptorWithDescriptorType:typeIEEE32BitFloatingPoint + bytes:&real + length:sizeof(real)]; +} + + ++ (NSAppleEventDescriptor*)gtm_descriptorWithCGFloat:(CGFloat)real { +#if CGFLOAT_IS_DOUBLE + return [self gtm_descriptorWithDouble:real]; +#else + return [self gtm_descriptorWithFloat:real]; +#endif +} + +- (double)gtm_doubleValue { + double value = NAN; + NSNumber *number = [self gtm_numberValue]; + if (number) { + value = [number doubleValue]; + } + return value; +} + +- (float)gtm_floatValue { + float value = NAN; + NSNumber *number = [self gtm_numberValue]; + if (number) { + value = [number floatValue]; + } + return value; +} + +- (CGFloat)gtm_cgFloatValue { +#if CGFLOAT_IS_DOUBLE + return [self gtm_doubleValue]; +#else + return [self gtm_floatValue]; +#endif +} + +- (NSNumber*)gtm_numberValue { + typedef struct { + DescType type; + SEL selector; + } TypeSelectorMap; + TypeSelectorMap typeSelectorMap[] = { + { typeFalse, @selector(numberWithBool:) }, + { typeTrue, @selector(numberWithBool:) }, + { typeBoolean, @selector(numberWithBool:) }, + { typeSInt16, @selector(numberWithShort:) }, + { typeSInt32, @selector(numberWithInt:) }, + { typeUInt32, @selector(numberWithUnsignedInt:) }, + { typeSInt64, @selector(numberWithLongLong:) }, + { typeIEEE32BitFloatingPoint, @selector(numberWithFloat:) }, + { typeIEEE64BitFloatingPoint, @selector(numberWithDouble:) } + }; + DescType type = [self descriptorType]; + SEL selector = nil; + for (size_t i = 0; i < sizeof(typeSelectorMap) / sizeof(TypeSelectorMap); ++i) { + if (type == typeSelectorMap[i].type) { + selector = typeSelectorMap[i].selector; + break; + } + } + NSAppleEventDescriptor *desc = self; + if (!selector) { + // COV_NF_START - Don't know how to force this in a unittest + _GTMDevLog(@"Didn't get a valid selector?"); + desc = [self coerceToDescriptorType:typeIEEE64BitFloatingPoint]; + selector = @selector(numberWithDouble:); + // COV_NF_END + } + NSData *descData = [desc data]; + const void *bytes = [descData bytes]; + if (!bytes) { + // COV_NF_START - Don't know how to force this in a unittest + _GTMDevLog(@"Unable to get bytes from %@", desc); + return nil; + // COV_NF_END + } + Class numberClass = [NSNumber class]; + NSMethodSignature *signature = [numberClass methodSignatureForSelector:selector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + [invocation setSelector:selector]; + [invocation setArgument:(void*)bytes atIndex:2]; + [invocation setTarget:numberClass]; + [invocation invoke]; + NSNumber *value = nil; + [invocation getReturnValue:&value]; + return value; +} + +@end + +@implementation NSObject (GTMAppleEventDescriptorObjectAdditions) +- (NSAppleEventDescriptor*)gtm_appleEventDescriptor { + return [NSAppleEventDescriptor descriptorWithString:[self description]]; +} +@end + +@implementation NSArray (GTMAppleEventDescriptorObjectAdditions) + ++ (void)load { + DescType types[] = { + typeAEList, + }; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [NSAppleEventDescriptor gtm_registerSelector:@selector(gtm_arrayValue) + forTypes:types + count:sizeof(types)/sizeof(DescType)]; + [pool release]; +} + +- (NSAppleEventDescriptor*)gtm_appleEventDescriptor { + NSAppleEventDescriptor *desc = [NSAppleEventDescriptor listDescriptor]; + NSUInteger count = [self count]; + for (NSUInteger i = 1; i <= count; ++i) { + id item = [self objectAtIndex:i-1]; + NSAppleEventDescriptor *itemDesc = [item gtm_appleEventDescriptor]; + if (!itemDesc) { + _GTMDevLog(@"Unable to create Apple Event Descriptor for %@", [self description]); + return nil; + } + [desc insertDescriptor:itemDesc atIndex:i]; + } + return desc; +} +@end + +@implementation NSDictionary (GTMAppleEventDescriptorObjectAdditions) + ++ (void)load { + DescType types[] = { + typeAERecord, + }; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [NSAppleEventDescriptor gtm_registerSelector:@selector(gtm_dictionaryValue) + forTypes:types + count:sizeof(types)/sizeof(DescType)]; + [pool release]; +} + +- (NSAppleEventDescriptor*)gtm_appleEventDescriptor { + NSEnumerator* keys = [self keyEnumerator]; + Class keyClass = nil; + id key = nil; + while ((key = [keys nextObject])) { + if (!keyClass) { + if ([key isKindOfClass:[GTMFourCharCode class]]) { + keyClass = [GTMFourCharCode class]; + } else if ([key isKindOfClass:[NSString class]]) { + keyClass = [NSString class]; + } else { + _GTMDevLog(@"Keys must be of type NSString or GTMFourCharCode: %@", key); + return nil; + } + } + if (![key isKindOfClass:keyClass]) { + _GTMDevLog(@"Keys must be homogenous (first key was of type %@) " + "and of type NSString or GTMFourCharCode: %@", keyClass, key); + return nil; + } + } + NSAppleEventDescriptor *desc = [NSAppleEventDescriptor recordDescriptor]; + if ([keyClass isEqual:[NSString class]]) { + NSMutableArray *array = [NSMutableArray arrayWithCapacity:[self count] * 2]; + keys = [self keyEnumerator]; + while ((key = [keys nextObject])) { + [array addObject:key]; + [array addObject:[self objectForKey:key]]; + } + NSAppleEventDescriptor *userRecord = [array gtm_appleEventDescriptor]; + if (!userRecord) { + return nil; + } + [desc setDescriptor:userRecord forKeyword:keyASUserRecordFields]; + } else { + keys = [self keyEnumerator]; + while ((key = [keys nextObject])) { + id value = [self objectForKey:key]; + NSAppleEventDescriptor *valDesc = [value gtm_appleEventDescriptor]; + if (!valDesc) { + return nil; + } + [desc setDescriptor:valDesc forKeyword:[key fourCharCode]]; + } + } + return desc; +} + +@end + +@implementation NSNull (GTMAppleEventDescriptorObjectAdditions) ++ (void)load { + DescType types[] = { + typeNull + }; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [NSAppleEventDescriptor gtm_registerSelector:@selector(gtm_nullValue) + forTypes:types + count:sizeof(types)/sizeof(DescType)]; + [pool release]; +} + +- (NSAppleEventDescriptor*)gtm_appleEventDescriptor { + return [NSAppleEventDescriptor nullDescriptor]; +} +@end + +@implementation NSString (GTMAppleEventDescriptorObjectAdditions) + ++ (void)load { + DescType types[] = { + typeUTF16ExternalRepresentation, + typeUnicodeText, + typeUTF8Text, + typeCString, + typePString, + typeChar, + typeIntlText }; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [NSAppleEventDescriptor gtm_registerSelector:@selector(stringValue) + forTypes:types + count:sizeof(types)/sizeof(DescType)]; + [pool release]; +} + +- (NSAppleEventDescriptor*)gtm_appleEventDescriptor { + return [NSAppleEventDescriptor descriptorWithString:self]; +} +@end + +@implementation NSNumber (GTMAppleEventDescriptorObjectAdditions) + ++ (void)load { + DescType types[] = { + typeTrue, + typeFalse, + typeBoolean, + typeSInt16, + typeSInt32, + typeUInt32, + typeSInt64, + typeIEEE32BitFloatingPoint, + typeIEEE64BitFloatingPoint }; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [NSAppleEventDescriptor gtm_registerSelector:@selector(gtm_numberValue) + forTypes:types + count:sizeof(types)/sizeof(DescType)]; + [pool release]; +} + +- (NSAppleEventDescriptor*)gtm_appleEventDescriptor { + const char *type = [self objCType]; + if (!type || strlen(type) != 1) return nil; + + DescType desiredType = typeNull; + NSAppleEventDescriptor *desc = nil; + switch (type[0]) { + // COV_NF_START + // I can't seem to convince objcType to return something of this type + case 'B': + desc = [NSAppleEventDescriptor descriptorWithBoolean:[self boolValue]]; + break; + // COV_NF_END + + case 'c': + case 'C': + case 's': + case 'S': + desiredType = typeSInt16; + break; + + case 'i': + case 'l': + desiredType = typeSInt32; + break; + + // COV_NF_START + // I can't seem to convince objcType to return something of this type + case 'I': + case 'L': + desiredType = typeUInt32; + break; + // COV_NF_END + + case 'q': + case 'Q': + desiredType = typeSInt64; + break; + + case 'f': + desiredType = typeIEEE32BitFloatingPoint; + break; + + case 'd': + default: + desiredType = typeIEEE64BitFloatingPoint; + break; + } + + if (!desc) { + desc = [NSAppleEventDescriptor gtm_descriptorWithDouble:[self doubleValue]]; + if (desc && desiredType != typeIEEE64BitFloatingPoint) { + desc = [desc coerceToDescriptorType:desiredType]; + } + } + return desc; +} + +@end + +@implementation NSProcessInfo (GTMAppleEventDescriptorObjectAdditions) + +- (NSAppleEventDescriptor*)gtm_appleEventDescriptor { + ProcessSerialNumber psn = { 0, kCurrentProcess }; + return [NSAppleEventDescriptor descriptorWithDescriptorType:typeProcessSerialNumber + bytes:&psn + length:sizeof(ProcessSerialNumber)]; +} + +@end + +@implementation NSAppleEventDescriptor (GTMAppleEventDescriptorAdditions) + +- (BOOL)gtm_sendEventWithMode:(AESendMode)mode + timeOut:(NSTimeInterval)timeout + reply:(NSAppleEventDescriptor**)reply { + BOOL isGood = YES; + AppleEvent replyEvent = { typeNull, NULL }; + OSStatus err = AESendMessage([self aeDesc], &replyEvent, mode, timeout * 60); + if (err) { + isGood = NO; + _GTMDevLog(@"Unable to send message: %@ %d", self, err); + replyEvent.descriptorType = typeNull; + replyEvent.dataHandle = NULL; + } + NSAppleEventDescriptor *replyDesc = [[[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&replyEvent] autorelease]; + if (isGood) { + NSAppleEventDescriptor *errorDesc = [replyDesc descriptorForKeyword:keyErrorNumber]; + if (errorDesc && [errorDesc int32Value]) { + isGood = NO; + } + } + if (reply) { + *reply = replyDesc; + } + return isGood; +} + +@end diff --git a/Foundation/GTMNSAppleEventDescriptor+FoundationTest.m b/Foundation/GTMNSAppleEventDescriptor+FoundationTest.m new file mode 100644 index 0000000..c35c352 --- /dev/null +++ b/Foundation/GTMNSAppleEventDescriptor+FoundationTest.m @@ -0,0 +1,619 @@ +// +// GTMNSAppleEventDescriptor+FoundationTest.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 "GTMSenTestCase.h" +#import +#import "GTMNSAppleEventDescriptor+Foundation.h" +#import "GTMFourCharCode.h" +#import "GTMUnitTestDevLog.h" + +@interface GTMNSAppleEventDescriptor_TestObject : NSObject +@end + +@implementation GTMNSAppleEventDescriptor_TestObject + +- (NSAppleEventDescriptor*)gtm_appleEventDescriptor { + return nil; +} + +@end + +@interface GTMNSAppleEventDescriptor_FoundationTest : GTMTestCase { + BOOL gotEvent_; +} +- (void)handleEvent:(NSAppleEventDescriptor*)event + withReply:(NSAppleEventDescriptor*)reply; +- (void)handleEvent:(NSAppleEventDescriptor*)event + withError:(NSAppleEventDescriptor*)reply; + +@end + +@implementation GTMNSAppleEventDescriptor_FoundationTest +- (void)testRegisterSelectorForTypesCount { + // Weird edge casey stuff. + // + (void)registerSelector:(SEL)selector + // forTypes:(DescType*)types count:(int)count + // is tested heavily by the other NSAppleEventDescriptor+foo categories. + DescType type; + [NSAppleEventDescriptor gtm_registerSelector:nil + forTypes:&type count:1]; + [NSAppleEventDescriptor gtm_registerSelector:@selector(retain) + forTypes:nil count:1]; + [NSAppleEventDescriptor gtm_registerSelector:@selector(retain) + forTypes:&type count:0]; + // Test the duplicate case + [NSAppleEventDescriptor gtm_registerSelector:@selector(retain) + forTypes:&type count:1]; + [GTMUnitTestDevLog expectPattern:@"retain being replaced with retain exists " + "for type: [0-9]+"]; + [NSAppleEventDescriptor gtm_registerSelector:@selector(retain) + forTypes:&type count:1]; +} + +- (void)testObjectValue { + // - (void)testObjectValue is tested heavily by the other + // NSAppleEventDescriptor+foo categories. + long data = 1; + // v@#f is just a bogus descriptor type that we don't recognize. + NSAppleEventDescriptor *desc + = [NSAppleEventDescriptor descriptorWithDescriptorType:'v@#f' + bytes:&data + length:sizeof(data)]; + id value = [desc gtm_objectValue]; + STAssertNil(value, nil); +} + +- (void)testAppleEventDescriptor { + // - (NSAppleEventDescriptor*)appleEventDescriptor is tested heavily by the + // other NSAppleEventDescriptor+foo categories. + NSAppleEventDescriptor *desc = [self gtm_appleEventDescriptor]; + STAssertNotNil(desc, nil); + STAssertEquals([desc descriptorType], (DescType)typeUnicodeText, nil); +} + +- (void)testDescriptorWithArrayAndArrayValue { + // Test empty array + NSAppleEventDescriptor *desc = [[NSArray array] gtm_appleEventDescriptor]; + STAssertNotNil(desc, nil); + STAssertEquals([desc numberOfItems], (NSInteger)0, nil); + + // Complex array + NSArray *array = [NSArray arrayWithObjects: + [NSNumber numberWithInt:4], + @"foo", + [NSNumber numberWithInt:2], + @"bar", + [NSArray arrayWithObjects: + @"bam", + [NSArray arrayWithObject:[NSNumber numberWithFloat:4.2f]], + nil], + nil]; + STAssertNotNil(array, nil); + desc = [array gtm_appleEventDescriptor]; + STAssertNotNil(desc, nil); + NSArray *array2 = [desc gtm_objectValue]; + STAssertNotNil(array2, nil); + NSArray *array3 = [desc gtm_arrayValue]; + STAssertNotNil(array3, nil); + STAssertTrue([array isEqualToArray:array2], + @"array: %@\narray2: %@\ndesc: %@", + [array description], [array2 description], [desc description]); + STAssertTrue([array2 isEqualToArray:array3], + @"array: %@\narray2: %@\ndesc: %@", + [array description], [array2 description], [desc description]); + + // Test a single object + array = [NSArray arrayWithObject:@"foo"]; + desc = [NSAppleEventDescriptor descriptorWithString:@"foo"]; + STAssertNotNil(desc, nil); + array2 = [desc gtm_arrayValue]; + STAssertTrue([array isEqualToArray:array2], + @"array: %@\narray2: %@\ndesc: %@", + [array description], [array2 description], [desc description]); + + // Something that doesn't know how to register itself. + GTMNSAppleEventDescriptor_TestObject *obj + = [[[GTMNSAppleEventDescriptor_TestObject alloc] init] autorelease]; + [GTMUnitTestDevLog expectPattern:@"Unable to create Apple Event Descriptor for .*"]; + desc = [[NSArray arrayWithObject:obj] gtm_appleEventDescriptor]; + STAssertNil(desc, @"Should be nil"); + + // A list containing something we don't know how to deal with + desc = [NSAppleEventDescriptor listDescriptor]; + NSAppleEventDescriptor *desc2 + = [NSAppleEventDescriptor descriptorWithDescriptorType:'@!@#' + bytes:&desc + length:sizeof(desc)]; + [GTMUnitTestDevLog expectPattern:@"Unknown type of descriptor " + ""]; + [desc insertDescriptor:desc2 atIndex:0]; + array = [desc gtm_objectValue]; + STAssertEquals([array count], (NSUInteger)0, @"Should have 0 items"); +} + +- (void)testDescriptorWithDictionaryAndDictionaryValue { + // Test empty dictionary + NSAppleEventDescriptor *desc + = [[NSDictionary dictionary] gtm_appleEventDescriptor]; + STAssertNotNil(desc, nil); + STAssertEquals([desc numberOfItems], (NSInteger)0, nil); + + // Complex dictionary + NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys: + @"fooobject", + @"fookey", + @"barobject", + @"barkey", + [NSDictionary dictionaryWithObjectsAndKeys: + @"january", + [GTMFourCharCode fourCharCodeWithFourCharCode:cJanuary], + @"february", + [GTMFourCharCode fourCharCodeWithFourCharCode:cFebruary], + nil], + @"dictkey", + nil]; + STAssertNotNil(dictionary, nil); + desc = [dictionary gtm_appleEventDescriptor]; + STAssertNotNil(desc, nil); + NSDictionary *dictionary2 = [desc gtm_objectValue]; + STAssertNotNil(dictionary2, nil); + NSDictionary *dictionary3 = [desc gtm_dictionaryValue]; + STAssertNotNil(dictionary3, nil); + STAssertEqualObjects(dictionary, dictionary2, + @"desc: %@", [desc description]); + STAssertEqualObjects(dictionary2, dictionary3, + @"desc: %@", [desc description]); + + // Something that doesn't know how to register itself. + GTMNSAppleEventDescriptor_TestObject *obj + = [[[GTMNSAppleEventDescriptor_TestObject alloc] init] autorelease]; + [GTMUnitTestDevLog expectPattern:@"Unable to create Apple Event Descriptor for .*"]; + desc = [[NSDictionary dictionaryWithObject:obj + forKey:@"foo"] gtm_appleEventDescriptor]; + STAssertNil(desc, @"Should be nil"); + + GTMFourCharCode *fcc = [GTMFourCharCode fourCharCodeWithFourCharCode:cJanuary]; + desc = [[NSDictionary dictionaryWithObject:obj + forKey:fcc] gtm_appleEventDescriptor]; + STAssertNil(desc, @"Should be nil"); + + // A list containing something we don't know how to deal with + desc = [NSAppleEventDescriptor recordDescriptor]; + NSAppleEventDescriptor *desc2 + = [NSAppleEventDescriptor descriptorWithDescriptorType:'@!@#' + bytes:&desc + length:sizeof(desc)]; + [desc setDescriptor:desc2 forKeyword:cJanuary]; + [GTMUnitTestDevLog expectPattern:@"Unknown type of descriptor " + ""]; + dictionary = [desc gtm_objectValue]; + STAssertEquals([dictionary count], (NSUInteger)0, @"Should have 0 items"); + + // A bad dictionary + dictionary = [NSDictionary dictionaryWithObjectsAndKeys: + @"foo", + [GTMFourCharCode fourCharCodeWithFourCharCode:'APPL'], + @"bam", + @"bar", + nil]; + STAssertNotNil(dictionary, nil); + // I cannot use expectString here to the exact string because interestingly + // dictionaries in 64 bit enumerate in a different order from dictionaries + // on 32 bit. This is the closest pattern I can match. + [GTMUnitTestDevLog expectPattern:@"Keys must be homogenous .*"]; + desc = [dictionary gtm_appleEventDescriptor]; + STAssertNil(desc, nil); + + // Another bad dictionary + dictionary = [NSDictionary dictionaryWithObjectsAndKeys: + @"foo", + [NSNumber numberWithInt:4], + @"bam", + @"bar", + nil]; + STAssertNotNil(dictionary, nil); + // I cannot use expectString here to the exact string because interestingly + // dictionaries in 64 bit enumerate in a different order from dictionaries + // on 32 bit. This is the closest pattern I can match. + [GTMUnitTestDevLog expectPattern:@"Keys must be .*"]; + desc = [dictionary gtm_appleEventDescriptor]; + STAssertNil(desc, nil); + + // A bad descriptor + desc = [NSAppleEventDescriptor recordDescriptor]; + STAssertNotNil(desc, @""); + NSArray *array = [NSArray arrayWithObjects:@"foo", @"bar", @"bam", nil]; + STAssertNotNil(array, @""); + NSAppleEventDescriptor *userRecord = [array gtm_appleEventDescriptor]; + STAssertNotNil(userRecord, @""); + [desc setDescriptor:userRecord forKeyword:keyASUserRecordFields]; + [GTMUnitTestDevLog expectPattern:@"Got a key bam with no value in <.*"]; + dictionary = [desc gtm_objectValue]; + STAssertNil(dictionary, @"Should be nil"); +} + +- (void)testDescriptorWithNull { + // Test Null + NSNull *null = [NSNull null]; + NSAppleEventDescriptor *desc = [null gtm_appleEventDescriptor]; + STAssertNotNil(desc, nil); + NSNull *null2 = [desc gtm_objectValue]; + STAssertNotNil(null2, nil); + NSNull *null3 = [desc gtm_nullValue]; + STAssertNotNil(null2, nil); + STAssertEqualObjects(null, null2, + @"null: %@\null2: %@\ndesc: %@", + [null description], [null2 description], + [desc description]); + STAssertEqualObjects(null, null3, + @"null: %@\null3: %@\ndesc: %@", + [null description], [null3 description], + [desc description]); +} + +- (void)testDescriptorWithString { + // Test empty String + NSAppleEventDescriptor *desc = [[NSString string] gtm_appleEventDescriptor]; + STAssertNotNil(desc, nil); + + // Test String + NSString *string = @"Ratatouille!"; + desc = [string gtm_appleEventDescriptor]; + STAssertNotNil(desc, nil); + NSString *string2 = [desc gtm_objectValue]; + STAssertNotNil(string2, nil); + STAssertEqualObjects(string, string2, + @"string: %@\nstring: %@\ndesc: %@", + [string description], [string2 description], [desc description]); + +} + +- (void)testDescriptorWithNumberAndNumberValue { + // There's really no good way to make this into a loop sadly due + // to me having to pass a pointer of bytes to NSInvocation as an argument. + // I want the compiler to convert my int to the appropriate type. + + NSNumber *original = [NSNumber numberWithBool:YES]; + STAssertNotNil(original, @"Value: YES"); + NSAppleEventDescriptor *desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: YES"); + id returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: YES"); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: YES"); + STAssertEqualObjects(original, returned, @"Value: YES"); + desc = [desc coerceToDescriptorType:typeBoolean]; + NSNumber *number = [desc gtm_numberValue]; + STAssertEqualObjects(number, original, @"Value: YES"); + + original = [NSNumber numberWithBool:NO]; + STAssertNotNil(original, @"Value: NO"); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: NO"); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: NO"); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: NO"); + STAssertEqualObjects(original, returned, @"Value: NO"); + + sranddev(); + double value = rand(); + + original = [NSNumber numberWithChar:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = rand(); + original = [NSNumber numberWithUnsignedChar:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = rand(); + original = [NSNumber numberWithShort:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = rand(); + original = [NSNumber numberWithUnsignedShort:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = rand(); + original = [NSNumber numberWithInt:(int)value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = rand(); + original = [NSNumber numberWithUnsignedInt:(unsigned int)value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = rand(); + original = [NSNumber numberWithLong:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = rand(); + original = [NSNumber numberWithUnsignedLong:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = rand(); + original = [NSNumber numberWithLongLong:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = rand(); + original = [NSNumber numberWithUnsignedLongLong:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + float floatA = rand(); + float floatB = rand(); + value = floatA / floatB; + original = [NSNumber numberWithFloat:(float)value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + double doubleA = rand(); + double doubleB = rand(); + value = doubleA / doubleB; + original = [NSNumber numberWithDouble:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = rand(); + original = [NSNumber numberWithBool:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = NAN; + original = [NSNumber numberWithDouble:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = INFINITY; + original = [NSNumber numberWithDouble:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = -0.0; + original = [NSNumber numberWithDouble:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); + + value = -INFINITY; + original = [NSNumber numberWithDouble:value]; + STAssertNotNil(original, @"Value: %g", value); + desc = [original gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Value: %g", value); + returned = [desc gtm_objectValue]; + STAssertNotNil(returned, @"Value: %g", value); + STAssertTrue([returned isKindOfClass:[NSNumber class]], @"Value: %g", value); + STAssertEqualObjects(original, returned, @"Value: %g", value); +} + +- (void)testDescriptorWithDoubleAndDoubleValue { + sranddev(); + for (int i = 0; i < 1000; ++i) { + double value1 = rand(); + double value2 = rand(); + double value = value1 / value2; + NSAppleEventDescriptor *desc + = [NSAppleEventDescriptor gtm_descriptorWithDouble:value]; + STAssertNotNil(desc, @"Value: %g", value); + double returnedValue = [desc gtm_doubleValue]; + STAssertEquals(value, returnedValue, @"Value: %g", value); + } + + double specialCases[] = { 0.0f, __DBL_MIN__, __DBL_EPSILON__, INFINITY, NAN }; + for (size_t i = 0; i < sizeof(specialCases) / sizeof(double); ++i) { + double value = specialCases[i]; + NSAppleEventDescriptor *desc + = [NSAppleEventDescriptor gtm_descriptorWithDouble:value]; + STAssertNotNil(desc, @"Value: %g", value); + double returnedValue = [desc gtm_doubleValue]; + STAssertEquals(value, returnedValue, @"Value: %g", value); + } +} + +- (void)testDescriptorWithFloatAndFloatValue { + sranddev(); + for (int i = 0; i < 1000; ++i) { + float value1 = rand(); + float value2 = rand(); + float value = value1 / value2; + NSAppleEventDescriptor *desc + = [NSAppleEventDescriptor gtm_descriptorWithFloat:value]; + STAssertNotNil(desc, @"Value: %f", value); + float returnedValue = [desc gtm_floatValue]; + STAssertEquals(value, returnedValue, @"Value: %f", value); + } + + float specialCases[] = { 0.0f, FLT_MIN, FLT_MAX, FLT_EPSILON, INFINITY, NAN }; + for (size_t i = 0; i < sizeof(specialCases) / sizeof(float); ++i) { + float value = specialCases[i]; + NSAppleEventDescriptor *desc + = [NSAppleEventDescriptor gtm_descriptorWithFloat:value]; + STAssertNotNil(desc, @"Value: %f", value); + float returnedValue = [desc gtm_floatValue]; + STAssertEquals(value, returnedValue, @"Value: %f", value); + } +} + +- (void)testDescriptorWithCGFloatAndCGFloatValue { + sranddev(); + for (int i = 0; i < 1000; ++i) { + CGFloat value1 = rand(); + CGFloat value2 = rand(); + CGFloat value = value1 / value2; + NSAppleEventDescriptor *desc + = [NSAppleEventDescriptor gtm_descriptorWithCGFloat:value]; + STAssertNotNil(desc, @"Value: %g", (double)value); + CGFloat returnedValue = [desc gtm_cgFloatValue]; + STAssertEquals(value, returnedValue, @"Value: %g", (double)value); + } + + CGFloat specialCases[] = { 0.0f, CGFLOAT_MIN, CGFLOAT_MAX, NAN }; + for (size_t i = 0; i < sizeof(specialCases) / sizeof(CGFloat); ++i) { + CGFloat value = specialCases[i]; + NSAppleEventDescriptor *desc + = [NSAppleEventDescriptor gtm_descriptorWithCGFloat:value]; + STAssertNotNil(desc, @"Value: %g", (double)value); + CGFloat returnedValue = [desc gtm_cgFloatValue]; + STAssertEquals(value, returnedValue, @"Value: %g", (double)value); + } +} + +- (void)handleEvent:(NSAppleEventDescriptor*)event + withReply:(NSAppleEventDescriptor*)reply { + gotEvent_ = YES; + NSAppleEventDescriptor *answer = [NSAppleEventDescriptor descriptorWithInt32:1]; + [reply setDescriptor:answer forKeyword:keyDirectObject]; +} + +- (void)handleEvent:(NSAppleEventDescriptor*)event + withError:(NSAppleEventDescriptor*)error { + gotEvent_ = YES; + NSAppleEventDescriptor *answer = [NSAppleEventDescriptor descriptorWithInt32:1]; + [error setDescriptor:answer forKeyword:keyErrorNumber]; +} + +- (void)testSend { + const AEEventClass eventClass = 'Fooz'; + const AEEventID eventID = 'Ball'; + NSAppleEventManager *mgr = [NSAppleEventManager sharedAppleEventManager]; + [mgr setEventHandler:self + andSelector:@selector(handleEvent:withReply:) + forEventClass:eventClass + andEventID:'Ball']; + NSAppleEventDescriptor *currentProcess + = [[NSProcessInfo processInfo] gtm_appleEventDescriptor]; + NSAppleEventDescriptor *event + = [NSAppleEventDescriptor appleEventWithEventClass:eventClass + eventID:eventID + targetDescriptor:currentProcess + returnID:kAutoGenerateReturnID + transactionID:kAnyTransactionID]; + gotEvent_ = NO; + NSAppleEventDescriptor *reply; + BOOL goodEvent = [event gtm_sendEventWithMode:kAEWaitReply timeOut:60 reply:&reply]; + [mgr removeEventHandlerForEventClass:eventClass andEventID:eventID]; + STAssertTrue(goodEvent, @"bad event?"); + STAssertTrue(gotEvent_, @"Handler not called"); + NSAppleEventDescriptor *value = [reply descriptorForKeyword:keyDirectObject]; + STAssertEquals([value int32Value], (SInt32)1, @"didn't get reply"); + + + gotEvent_ = NO; + [GTMUnitTestDevLog expectString:@"Unable to send message: " + " -1708"]; + goodEvent = [event gtm_sendEventWithMode:kAEWaitReply timeOut:60 reply:&reply]; + STAssertFalse(goodEvent, @"good event?"); + STAssertFalse(gotEvent_, @"Handler called?"); + + [mgr setEventHandler:self + andSelector:@selector(handleEvent:withError:) + forEventClass:eventClass + andEventID:eventID]; + gotEvent_ = NO; + goodEvent = [event gtm_sendEventWithMode:kAEWaitReply timeOut:60 reply:&reply]; + STAssertFalse(goodEvent, @"good event?"); + STAssertTrue(gotEvent_, @"Handler not called?"); + [mgr removeEventHandlerForEventClass:eventClass andEventID:eventID]; +} + +@end diff --git a/Foundation/GTMNSAppleEventDescriptor+Handler.h b/Foundation/GTMNSAppleEventDescriptor+Handler.h new file mode 100644 index 0000000..f2ac880 --- /dev/null +++ b/Foundation/GTMNSAppleEventDescriptor+Handler.h @@ -0,0 +1,40 @@ +// +// GTMNSAppleEventDescriptor+Handler.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 +#import "GTMDefines.h" + +@interface NSAppleEventDescriptor (GTMAppleEventDescriptorHandlerAdditions) ++ (id)gtm_descriptorWithPositionalHandler:(NSString*)handler + parametersArray:(NSArray*)params; ++ (id)gtm_descriptorWithPositionalHandler:(NSString*)handler + parametersDescriptor:(NSAppleEventDescriptor*)params; ++ (id)gtm_descriptorWithLabeledHandler:(NSString*)handler + labels:(AEKeyword*)labels + parameters:(id*)params + count:(NSUInteger)count; + +- (id)gtm_initWithPositionalHandler:(NSString*)handler + parametersArray:(NSArray*)params; +- (id)gtm_initWithPositionalHandler:(NSString*)handler + parametersDescriptor:(NSAppleEventDescriptor*)params; +- (id)gtm_initWithLabeledHandler:(NSString*)handler + labels:(AEKeyword*)labels + parameters:(id*)params + count:(NSUInteger)count; +@end diff --git a/Foundation/GTMNSAppleEventDescriptor+Handler.m b/Foundation/GTMNSAppleEventDescriptor+Handler.m new file mode 100644 index 0000000..b5b2974 --- /dev/null +++ b/Foundation/GTMNSAppleEventDescriptor+Handler.m @@ -0,0 +1,130 @@ +// +// NSAppleEventDescriptor+Handler.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 "GTMNSAppleEventDescriptor+Handler.h" +#import "GTMNSAppleEventDescriptor+Foundation.h" +#import "GTMMethodCheck.h" +#import + +@implementation NSAppleEventDescriptor (GTMAppleEventDescriptorHandlerAdditions) +GTM_METHOD_CHECK(NSProcessInfo, gtm_appleEventDescriptor); // COV_NF_LINE + ++ (id)gtm_descriptorWithPositionalHandler:(NSString*)handler + parametersArray:(NSArray*)params { + return [[[self alloc] gtm_initWithPositionalHandler:handler + parametersArray:params] autorelease]; +} + ++ (id)gtm_descriptorWithPositionalHandler:(NSString*)handler + parametersDescriptor:(NSAppleEventDescriptor*)params { + return [[[self alloc] gtm_initWithPositionalHandler:handler + parametersDescriptor:params] autorelease]; +} + ++ (id)gtm_descriptorWithLabeledHandler:(NSString*)handler + labels:(AEKeyword*)labels + parameters:(id*)params + count:(NSUInteger)count { + return [[[self alloc] gtm_initWithLabeledHandler:handler + labels:labels + parameters:params + count:count] autorelease]; +} + +- (id)gtm_initWithPositionalHandler:(NSString*)handler + parametersArray:(NSArray*)params { + return [self gtm_initWithPositionalHandler:handler + parametersDescriptor:[params gtm_appleEventDescriptor]]; +} + +- (id)gtm_initWithPositionalHandler:(NSString*)handler + parametersDescriptor:(NSAppleEventDescriptor*)params { + if ((self = [self initWithEventClass:kASAppleScriptSuite + eventID:kASSubroutineEvent + targetDescriptor:[[NSProcessInfo processInfo] gtm_appleEventDescriptor] + returnID:kAutoGenerateReturnID + transactionID:kAnyTransactionID])) { + // Create an NSAppleEventDescriptor with the method handler. Note that the + // name must be lowercase (even if it is uppercase in AppleScript). + // http://developer.apple.com/qa/qa2001/qa1111.html + // has details. + handler = [handler lowercaseString]; + if (!handler) { + [self release]; + return nil; + } + NSAppleEventDescriptor *handlerDesc + = [NSAppleEventDescriptor descriptorWithString:handler]; + [self setParamDescriptor:handlerDesc forKeyword:keyASSubroutineName]; + if (params) { + [self setParamDescriptor:params forKeyword:keyDirectObject]; + } + } + return self; +} + + +- (id)gtm_initWithLabeledHandler:(NSString*)handler + labels:(AEKeyword*)labels + parameters:(id*)params + count:(NSUInteger)count { + if ((self = [self initWithEventClass:kASAppleScriptSuite + eventID:kASSubroutineEvent + targetDescriptor:[[NSProcessInfo processInfo] gtm_appleEventDescriptor] + returnID:kAutoGenerateReturnID + transactionID:kAnyTransactionID])) { + if (!handler) { + [self release]; + return nil; + } + // Create an NSAppleEventDescriptor with the method handler. Note that the + // name must be lowercase (even if it is uppercase in AppleScript). + NSAppleEventDescriptor *handlerDesc + = [NSAppleEventDescriptor descriptorWithString:[handler lowercaseString]]; + [self setParamDescriptor:handlerDesc forKeyword:keyASSubroutineName]; + for (NSUInteger i = 0; i < count; i++) { + NSAppleEventDescriptor *paramDesc = [params[i] gtm_appleEventDescriptor]; + if(labels[i] == keyASPrepositionGiven) { + if (![params[i] isKindOfClass:[NSDictionary class]]) { + _GTMDevLog(@"Must pass in dictionary for keyASPrepositionGiven " + "(got %@)", params[i]); + [self release]; + self = nil; + break; + } + NSAppleEventDescriptor *userDesc + = [paramDesc descriptorForKeyword:keyASUserRecordFields]; + if (!userDesc) { + _GTMDevLog(@"Dictionary for keyASPrepositionGiven must be a user " + "record field dictionary (got %@)", params[i]); + [self release]; + self = nil; + break; + } + [self setParamDescriptor:userDesc + forKeyword:keyASUserRecordFields]; + } else { + [self setParamDescriptor:paramDesc + forKeyword:labels[i]]; + } + } + } + return self; +} + +@end diff --git a/Foundation/GTMNSAppleEventDescriptor+HandlerTest.m b/Foundation/GTMNSAppleEventDescriptor+HandlerTest.m new file mode 100644 index 0000000..769e014 --- /dev/null +++ b/Foundation/GTMNSAppleEventDescriptor+HandlerTest.m @@ -0,0 +1,70 @@ +// +// NSAppleEventDescriptor+HandlerTest.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 +#import "GTMSenTestCase.h" +#import "GTMNSAppleEventDescriptor+Foundation.h" +#import "GTMNSAppleEventDescriptor+Handler.h" +#import "GTMUnitTestDevLog.h" + +@interface GTMNSAppleEventDescriptor_HandlerTest : GTMTestCase +@end + +@implementation GTMNSAppleEventDescriptor_HandlerTest +// Most of this gets tested by the NSAppleScript+Handler tests. +- (void)testPositionalHandlers { + NSAppleEventDescriptor *desc + = [NSAppleEventDescriptor gtm_descriptorWithPositionalHandler:nil + parametersArray:[NSArray array]]; + STAssertNil(desc, @"got a desc?"); + + desc = [NSAppleEventDescriptor gtm_descriptorWithPositionalHandler:@"happy" + parametersDescriptor:nil]; + STAssertNotNil(desc, @"didn't get a desc?"); + + desc = [NSAppleEventDescriptor gtm_descriptorWithLabeledHandler:nil + labels:nil + parameters:nil + count:0]; + STAssertNil(desc, @"got a desc?"); + + AEKeyword keys[] = { keyASPrepositionGiven }; + NSString *string = @"foo"; + [GTMUnitTestDevLog expectString:@"Must pass in dictionary for " + "keyASPrepositionGiven (got foo)"]; + desc = [NSAppleEventDescriptor gtm_descriptorWithLabeledHandler:@"happy" + labels:keys + parameters:&string + count:1]; + STAssertNil(desc, @"got a desc?"); + + NSDictionary *dict = [NSDictionary dictionaryWithObject:@"bart" + forKey:[NSNumber numberWithInt:4]]; + [GTMUnitTestDevLog expectString:@"Keys must be of type NSString or " + "GTMFourCharCode: 4"]; + [GTMUnitTestDevLog expectPattern:@"Dictionary for keyASPrepositionGiven must " + "be a user record field dictionary \\(got .*"]; + desc = [NSAppleEventDescriptor gtm_descriptorWithLabeledHandler:@"happy" + labels:keys + parameters:&dict + count:1]; + STAssertNil(desc, @"got a desc?"); + +} + +@end diff --git a/Foundation/GTMNSAppleScript+Handler.h b/Foundation/GTMNSAppleScript+Handler.h new file mode 100644 index 0000000..ca3d6d2 --- /dev/null +++ b/Foundation/GTMNSAppleScript+Handler.h @@ -0,0 +1,59 @@ +// +// GTMNSAppleScript+Handler.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 +#import "GTMDefines.h" + +// A category for calling handlers in NSAppleScript +@interface NSAppleScript(GTMAppleScriptHandlerAdditions) +// This method allows us to call a specific handler in an AppleScript. +// parameters are passed in left-right order 0-n. +// +// Args: +// handler - name of the handler to call in the Applescript +// params - the parameters to pass to the handler +// error - in non-nil returns any error that may have occurred. +// +// Returns: +// The result of the handler being called. nil on failure. +- (NSAppleEventDescriptor*)gtm_executePositionalHandler:(NSString*)handler + parameters:(NSArray*)params + error:(NSDictionary**)error; + + +- (NSAppleEventDescriptor*)gtm_executeLabeledHandler:(NSString*)handler + labels:(AEKeyword*)labels + parameters:(id*)params + count:(NSUInteger)count + error:(NSDictionary **)error; +- (NSSet*)gtm_handlers; +- (NSSet*)gtm_properties; +- (BOOL)gtm_setValue:(id)value forProperty:(NSString*)property; +- (id)gtm_valueForProperty:(NSString*)property; + +@end + +@interface NSAppleEventDescriptor(GTMAppleEventDescriptorScriptAdditions) + +// Return an NSAppleScript for a desc of typeScript +// Returns nil on failure. +- (NSAppleScript*)gtm_scriptValue; + +// Return a NSString with [eventClass][eventID] for typeEvent 'evnt' +- (NSString*)gtm_eventValue; +@end diff --git a/Foundation/GTMNSAppleScript+Handler.m b/Foundation/GTMNSAppleScript+Handler.m new file mode 100644 index 0000000..d3eeffd --- /dev/null +++ b/Foundation/GTMNSAppleScript+Handler.m @@ -0,0 +1,283 @@ +// +// NSAppleScript+Handler.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 +#import "GTMNSAppleScript+Handler.h" +#import "GTMNSAppleEventDescriptor+Foundation.h" +#import "GTMNSAppleEventDescriptor+Handler.h" +#import "GTMFourCharCode.h" +#import "GTMMethodCheck.h" + +// Some private methods that we need to call +@interface NSAppleScript (NSPrivate) ++ (ComponentInstance)_defaultScriptingComponent; +- (OSAID) _compiledScriptID; +- (id)_initWithData:(NSData*)data error:(NSDictionary**)error; +@end + +@interface NSMethodSignature (NSPrivate) ++ (id)signatureWithObjCTypes:(const char *)fp8; +@end + +@implementation NSAppleScript(GTMAppleScriptHandlerAdditions) +GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_descriptorWithPositionalHandler:parametersArray:); // COV_NF_LINE +GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_descriptorWithLabeledHandler:labels:parameters:count:); // COV_NF_LINE +GTM_METHOD_CHECK(NSAppleEventDescriptor, gtm_registerSelector:forTypes:count:); // COV_NF_LINE + ++ (void)load { + DescType types[] = { + typeScript + }; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [NSAppleEventDescriptor gtm_registerSelector:@selector(gtm_scriptValue) + forTypes:types + count:sizeof(types)/sizeof(DescType)]; + + DescType types2[] = { + 'evnt' // No type code for this one + }; + + [NSAppleEventDescriptor gtm_registerSelector:@selector(gtm_eventValue) + forTypes:types2 + count:sizeof(types2)/sizeof(DescType)]; + [pool release]; +} + +- (OSAID)gtm_realIDAndComponent:(ComponentInstance*)component { + if (![self isCompiled]) { + NSDictionary *error; + if (![self compileAndReturnError:&error]) { + _GTMDevLog(@"Unable to compile script: %@ %@", self, error); + return kOSANullScript; + } + } + OSAID genericID = [self _compiledScriptID]; + ComponentInstance genericComponent = [NSAppleScript _defaultScriptingComponent]; + OSAError err = OSAGenericToRealID(genericComponent, &genericID, component); + if (err) { + _GTMDevLog(@"Unable to get real id script: %@ %@", self, err); // COV_NF_LINE + genericID = kOSANullScript; // COV_NF_LINE + } + return genericID; +} + +- (NSAppleEventDescriptor*)gtm_executePositionalHandler:(NSString*)handler + parameters:(NSArray*)params + error:(NSDictionary**)error { + NSAppleEventDescriptor *event + = [NSAppleEventDescriptor gtm_descriptorWithPositionalHandler:handler + parametersArray:params]; + return [self executeAppleEvent:event error:error]; +} + +- (NSAppleEventDescriptor*)gtm_executeLabeledHandler:(NSString*)handler + labels:(AEKeyword*)labels + parameters:(id*)params + count:(NSUInteger)count + error:(NSDictionary **)error { + NSAppleEventDescriptor *event + = [NSAppleEventDescriptor gtm_descriptorWithLabeledHandler:handler + labels:labels + parameters:params + count:count]; + return [self executeAppleEvent:event error:error]; +} + +- (NSSet*)gtm_handlers { + AEDescList names = { typeNull, NULL }; + NSArray *array = nil; + ComponentInstance component; + OSAID osaID = [self gtm_realIDAndComponent:&component]; + OSAError err = OSAGetHandlerNames(component, kOSAModeNull, osaID, &names); + if (!err) { + NSAppleEventDescriptor *desc + = [[[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&names] autorelease]; + array = [desc gtm_objectValue]; + } + if (err) { + _GTMDevLog(@"Error getting handlers: %d", err); + } + return [NSSet setWithArray:array]; +} + +- (NSSet*)gtm_properties { + AEDescList names = { typeNull, NULL }; + NSArray *array = nil; + ComponentInstance component; + OSAID osaID = [self gtm_realIDAndComponent:&component]; + OSAError err = OSAGetPropertyNames(component, kOSAModeNull, osaID, &names); + if (!err) { + NSAppleEventDescriptor *desc + = [[[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&names] autorelease]; + array = [desc gtm_objectValue]; + } + if (err) { + _GTMDevLog(@"Error getting properties: %d", err); + } + return [NSSet setWithArray:array]; +} + +- (BOOL)gtm_setValue:(id)value forProperty:(NSString*)property { + BOOL wasGood = NO; + NSAppleEventDescriptor *desc = [value gtm_appleEventDescriptor]; + NSAppleEventDescriptor *propertyName = [property gtm_appleEventDescriptor]; + OSAError error = paramErr; + if (desc && propertyName) { + OSAID valueID = kOSANullScript; + ComponentInstance component; + OSAID scriptID = [self gtm_realIDAndComponent:&component]; + error = OSACoerceFromDesc(component, + [desc aeDesc], + kOSAModeNull, + &valueID); + if (!error) { + error = OSASetProperty(component, kOSAModeNull, + scriptID, [propertyName aeDesc], valueID); + if (!error) { + wasGood = YES; + } + } + } + if (!wasGood) { + _GTMDevLog(@"Unable to setValue:%@ forProperty:%@ from %@ (%d)", + value, property, self, error); + } + return wasGood; +} + +- (id)gtm_valueForProperty:(NSString*)property { + id value = nil; + NSAppleEventDescriptor *propertyName = [property gtm_appleEventDescriptor]; + OSAError error = paramErr; + if (propertyName) { + ComponentInstance component; + OSAID scriptID = [self gtm_realIDAndComponent:&component]; + OSAID valueID = kOSANullScript; + error = OSAGetProperty(component, + kOSAModeNull, + scriptID, + [propertyName aeDesc], + &valueID); + if (!error) { + AEDesc aeDesc; + error = OSACoerceToDesc(component, + valueID, + typeWildCard, + kOSAModeNull, + &aeDesc); + if (!error) { + NSAppleEventDescriptor *desc + = [[[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&aeDesc] autorelease]; + value = [desc gtm_objectValue]; + } + } + } + if (error) { + _GTMDevLog(@"Unable to get valueForProperty:%@ from %@ (%d)", + property, self, error); + } + return value; +} + +- (NSAppleEventDescriptor*)gtm_appleEventDescriptor { + ComponentInstance component; + OSAID osaID = [self gtm_realIDAndComponent:&component]; + AEDesc result = { typeNull, NULL }; + NSAppleEventDescriptor *desc = nil; + OSAError err = OSACoerceToDesc(component, + osaID, + typeScript, + kOSAModeNull, + &result); + if (!err) { + desc = [[[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&result] autorelease]; + } else { + _GTMDevLog(@"Unable to coerce script %d", err); // COV_NF_LINE + } + return desc; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { + NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; + if (!signature) { + NSMutableString *types = [NSMutableString stringWithString:@"@@:"]; + NSString *selName = NSStringFromSelector(aSelector); + NSArray *selArray = [selName componentsSeparatedByString:@":"]; + NSUInteger count = [selArray count]; + for (NSUInteger i = 1; i < count; i++) { + [types appendString:@"@"]; + } + signature = [NSMethodSignature signatureWithObjCTypes:[types UTF8String]]; + } + return signature; +} + +- (void)forwardInvocation:(NSInvocation *)invocation { + SEL sel = [invocation selector]; + NSMutableString *handlerName = [NSStringFromSelector(sel) mutableCopy]; + NSUInteger handlerOrigLength = [handlerName length]; + [handlerName replaceOccurrencesOfString:@":" + withString:@"" + options:0 + range:NSMakeRange(0,handlerOrigLength)]; + NSUInteger argCount = handlerOrigLength - [handlerName length]; + NSMutableArray *args = [NSMutableArray arrayWithCapacity:argCount]; + for (NSUInteger i = 0; i < argCount; ++i) { + id arg; + // +2 to ignore _sel and _cmd + [invocation getArgument:&arg atIndex:i + 2]; + [args addObject:arg]; + } + NSDictionary *error = nil; + NSAppleEventDescriptor *desc = [self gtm_executePositionalHandler:handlerName + parameters:args + error:&error]; + if ([[invocation methodSignature] methodReturnLength] > 0) { + id returnValue = [desc gtm_objectValue]; + [invocation setReturnValue:&returnValue]; + } +} +@end + +@implementation NSAppleEventDescriptor(GMAppleEventDescriptorScriptAdditions) + +- (NSAppleScript*)gtm_scriptValue { + NSDictionary *error; + NSAppleScript *script = [[[NSAppleScript alloc] _initWithData:[self data] + error:&error] autorelease]; + if (!script) { + _GTMDevLog(@"Unable to create script: %@", error); // COV_NF_LINE + } + return script; +} + +- (NSString*)gtm_eventValue { + struct AEEventRecordStruct { + AEEventClass eventClass; + AEEventID eventID; + }; + NSData *data = [self data]; + const struct AEEventRecordStruct *record + = (const struct AEEventRecordStruct*)[data bytes]; + NSString *eClass = [GTMFourCharCode stringWithFourCharCode:record->eventClass]; + NSString *eID = [GTMFourCharCode stringWithFourCharCode:record->eventID]; + return [eClass stringByAppendingString:eID]; +} +@end + diff --git a/Foundation/GTMNSAppleScript+HandlerTest.m b/Foundation/GTMNSAppleScript+HandlerTest.m new file mode 100644 index 0000000..5a9d52b --- /dev/null +++ b/Foundation/GTMNSAppleScript+HandlerTest.m @@ -0,0 +1,331 @@ +// +// NSAppleScript+HandlerTest.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 "GTMSenTestCase.h" +#import +#import "GTMNSAppleScript+Handler.h" +#import "GTMNSAppleEventDescriptor+Foundation.h" +#import "GTMUnitTestDevLog.h" + +@interface GTMNSAppleScript_HandlerTest : GTMTestCase { + NSAppleScript *script_; +} +@end + +@implementation GTMNSAppleScript_HandlerTest +- (void)setUp { + NSBundle *bundle = [NSBundle bundleForClass:[GTMNSAppleScript_HandlerTest class]]; + STAssertNotNil(bundle, nil); + NSString *path = [bundle pathForResource:@"GTMNSAppleEvent+HandlerTest" + ofType:@"scpt" + inDirectory:@"Scripts"]; + STAssertNotNil(path, [bundle description]); + NSDictionary *error = nil; + script_ = [[NSAppleScript alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] + error:&error]; + STAssertNotNil(script_, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); +} + +- (void)tearDown { + [script_ release]; + script_ = nil; +} + +- (void)testHandlerNoParamsNoReturn { + NSDictionary *error = nil; + NSAppleEventDescriptor *desc = [script_ gtm_executePositionalHandler:@"test" + parameters:nil + error:&error]; + STAssertNotNil(desc, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); + STAssertEquals([desc descriptorType], (DescType)typeNull, nil); + desc = [script_ gtm_executePositionalHandler:@"test" + parameters:[NSArray array] + error:&error]; + STAssertNotNil(desc, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); + STAssertEquals([desc descriptorType], (DescType)typeNull, nil); + + //Applescript doesn't appear to get upset about extra params + desc = [script_ gtm_executePositionalHandler:@"test" + parameters:[NSArray arrayWithObject:@"foo"] + error:&error]; + STAssertNotNil(desc, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); + STAssertEquals([desc descriptorType], (DescType)typeNull, nil); +} + +- (void)testHandlerNoParamsWithReturn { + NSDictionary *error = nil; + NSAppleEventDescriptor *desc = [script_ gtm_executePositionalHandler:@"testReturnOne" + parameters:nil + error:&error]; + STAssertNotNil(desc, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); + STAssertEquals([desc descriptorType], (DescType)typeSInt32, nil); + STAssertEquals([desc int32Value], (SInt32)1, nil); + desc = [script_ gtm_executePositionalHandler:@"testReturnOne" + parameters:[NSArray array] + error:&error]; + STAssertNotNil(desc, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); + STAssertEquals([desc descriptorType], (DescType)typeSInt32, nil); + STAssertEquals([desc int32Value], (SInt32)1, nil); + + //Applescript doesn't appear to get upset about extra params + desc = [script_ gtm_executePositionalHandler:@"testReturnOne" + parameters:[NSArray arrayWithObject:@"foo"] + error:&error]; + STAssertNotNil(desc, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); + STAssertEquals([desc descriptorType], (DescType)typeSInt32, nil); + STAssertEquals([desc int32Value], (SInt32)1, nil); +} + +- (void)testHandlerOneParamWithReturn { + NSDictionary *error = nil; + // Note case change in executeHandler call + NSAppleEventDescriptor *desc = [script_ gtm_executePositionalHandler:@"testreturnParam" + parameters:nil + error:&error]; + STAssertNil(desc, @"Desc should by nil %@", desc); + STAssertNotNil(error, nil); + error = nil; + + desc = [script_ gtm_executePositionalHandler:@"testReturnParam" + parameters:[NSArray array] + error:&error]; + STAssertNil(desc, @"Desc should by nil %@", desc); + STAssertNotNil(error, nil); + error = nil; + + desc = [script_ gtm_executePositionalHandler:@"testReturnParam" + parameters:[NSArray arrayWithObject:@"foo"] + error:&error]; + STAssertNotNil(desc, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); + STAssertEquals([desc descriptorType], (DescType)typeUnicodeText, nil); + STAssertEqualObjects([desc gtm_objectValue], @"foo", nil); +} + +- (void)testHandlerTwoParamsWithReturn { + NSDictionary *error = nil; + // Note case change in executeHandler call + // Test case and empty params + NSAppleEventDescriptor *desc = [script_ gtm_executePositionalHandler:@"testADDPArams" + parameters:nil + error:&error]; + STAssertNil(desc, @"Desc should by nil %@", desc); + STAssertNotNil(error, nil); + + // Test empty params + error = nil; + desc = [script_ gtm_executePositionalHandler:@"testAddParams" + parameters:[NSArray array] + error:&error]; + STAssertNil(desc, @"Desc should by nil %@", desc); + STAssertNotNil(error, nil); + + error = nil; + NSArray *args = [NSArray arrayWithObjects: + [NSNumber numberWithInt:1], + [NSNumber numberWithInt:2], + nil]; + desc = [script_ gtm_executePositionalHandler:@"testAddParams" + parameters:args + error:&error]; + STAssertNotNil(desc, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); + STAssertEquals([desc descriptorType], (DescType)typeSInt32, nil); + STAssertEquals([desc int32Value], (SInt32)3, nil); + + // Test bad params + error = nil; + args = [NSArray arrayWithObjects: + @"foo", + @"bar", + nil]; + desc = [script_ gtm_executePositionalHandler:@"testAddParams" + parameters:args + error:&error]; + STAssertNil(desc, @"Desc should by nil %@", desc); + STAssertNotNil(error, nil); + + // Test too many params. Currently Applescript allows this so it should pass + error = nil; + args = [NSArray arrayWithObjects: + [NSNumber numberWithInt:1], + [NSNumber numberWithInt:2], + [NSNumber numberWithInt:3], + nil]; + desc = [script_ gtm_executePositionalHandler:@"testAddParams" + parameters:args + error:&error]; + STAssertNotNil(desc, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); + STAssertEquals([desc descriptorType], (DescType)typeSInt32, nil); + STAssertEquals([desc int32Value], (SInt32)3, nil);} + +- (void)testLabeledHandler { + NSDictionary *error = nil; + AEKeyword labels[] = { keyDirectObject, + keyASPrepositionOnto, + keyASPrepositionGiven }; + id params[3]; + params[0] = [NSNumber numberWithInt:1]; + params[1] = [NSNumber numberWithInt:3]; + params[2] = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:4] + forKey:@"othervalue"]; + + NSAppleEventDescriptor *desc = [script_ gtm_executeLabeledHandler:@"testAdd" + labels:labels + parameters:params + count:sizeof(params) / sizeof(id) + error:&error]; + STAssertNotNil(desc, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); + STAssertEquals([desc descriptorType], (DescType)typeSInt32, nil); + STAssertEquals([desc int32Value], (SInt32)8, nil); + + // Test too many params. Currently Applescript allows this so it should pass + AEKeyword labels2[] = { keyDirectObject, + keyASPrepositionOnto, + keyASPrepositionBetween, + keyASPrepositionGiven }; + id params2[4]; + params2[0] = [NSNumber numberWithInt:1]; + params2[1] = [NSNumber numberWithInt:3]; + params2[2] = [NSNumber numberWithInt:5]; + params2[3] = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:4] + forKey:@"othervalue"]; + + error = nil; + desc = [script_ gtm_executeLabeledHandler:@"testAdd" + labels:labels2 + parameters:params2 + count:sizeof(params2) / sizeof(id) + error:&error]; + STAssertNotNil(desc, [error description]); + STAssertNil(error, @"Error should be nil. Error = %@", [error description]); + STAssertEquals([desc descriptorType], (DescType)typeSInt32, nil); + STAssertEquals([desc int32Value], (SInt32)8, nil);} + +- (void)testHandlers { + NSSet *handlers = [script_ gtm_handlers]; + NSSet *expected = [NSSet setWithObjects: + @"aevtodoc", + @"test", + @"testreturnone", + @"testreturnparam", + @"testaddparams", + @"testadd", + nil]; + STAssertEqualObjects(handlers, expected, @"Unexpected handlers?"); +} + +- (void)testProperties { + NSSet *properties = [script_ gtm_properties]; + NSSet *expected = [NSSet setWithObjects: + @"foo", + @"asdscriptuniqueidentifier", + nil]; + STAssertEqualObjects(properties, expected, @"Unexpected properties?"); + id value = [script_ gtm_valueForProperty:@"foo"]; + STAssertEqualObjects(value, [NSNumber numberWithInt:1], @"bad property?"); + BOOL goodSet = [script_ gtm_setValue:@"bar" forProperty:@"foo"]; + STAssertTrue(goodSet, @"Couldn't set property"); + value = [script_ gtm_valueForProperty:@"foo"]; + STAssertEqualObjects(value, @"bar", @"bad property?"); + + [GTMUnitTestDevLog expectPattern:@"Unable to setValue:bar forProperty:" + "\\(null\\) from \\(-50\\)"]; + goodSet = [script_ gtm_setValue:@"bar" forProperty:nil]; + STAssertFalse(goodSet, @"Set property?"); + [GTMUnitTestDevLog expectPattern:@"Unable to get valueForProperty:gargle " + "from \\(-1753\\)"]; + value = [script_ gtm_valueForProperty:@"gargle"]; + STAssertNil(value, @"Property named gargle?"); +} + +- (void)testFailures { + NSDictionary *error = nil; + NSAppleEventDescriptor *desc = [script_ gtm_executePositionalHandler:@"noSuchTest" + parameters:nil + error:&error]; + STAssertNil(desc, nil); + STAssertNotNil(error, nil); + + // Test with empty handler name + error = nil; + desc = [script_ gtm_executePositionalHandler:@"" + parameters:[NSArray array] + error:&error]; + STAssertNil(desc, nil); + STAssertNotNil(error, nil); + + // Test with nil handler + error = nil; + desc = [script_ gtm_executePositionalHandler:nil + parameters:[NSArray array] + error:&error]; + STAssertNil(desc, nil); + STAssertNotNil(error, nil); + + // Test with nil handler and nil error + desc = [script_ gtm_executePositionalHandler:nil + parameters:nil + error:nil]; + STAssertNil(desc, nil); + + // Test with a bad script + NSAppleScript *script = [[[NSAppleScript alloc] initWithSource:@"david hasselhoff"] autorelease]; + [GTMUnitTestDevLog expectPattern:@"Unable to compile script: .*"]; + [GTMUnitTestDevLog expectPattern:@"Error getting handlers: -[0-9]+"]; + NSSet *handlers = [script gtm_handlers]; + STAssertEquals([handlers count], (NSUInteger)0, @"Should have no handlers"); + [GTMUnitTestDevLog expectPattern:@"Unable to compile script: .*"]; + [GTMUnitTestDevLog expectPattern:@"Error getting properties: -[0-9]+"]; + NSSet *properties = [script gtm_properties]; + STAssertEquals([properties count], (NSUInteger)0, @"Should have no properties"); +} + +- (void)testScriptDescriptors { + NSAppleEventDescriptor *desc = [script_ gtm_appleEventDescriptor]; + STAssertNotNil(desc, @"Couldn't make a script desc"); + NSAppleScript *script = [desc gtm_objectValue]; + STAssertNotNil(script, @"Couldn't get a script back"); + NSSet *handlers = [script gtm_handlers]; + STAssertNotNil(handlers, @"Couldn't get handlers"); +} + +@protocol ScriptInterface +- (id)test; +- (id)testReturnParam:(id)param; +- (id)testAddParams:(id)param1 :(id)param2; +@end + +- (void)testForwarding { + id foo = (id)script_; + [foo test]; + NSNumber *val = [foo testReturnParam:[NSNumber numberWithInt:2]]; + STAssertEquals([val intValue], 2, @"should be 2"); + val = [foo testAddParams:[NSNumber numberWithInt:2] :[NSNumber numberWithInt:3]]; + STAssertEquals([val intValue], 5, @"should be 5"); +} +@end diff --git a/Foundation/GTMNSData+zlibTest.m b/Foundation/GTMNSData+zlibTest.m index 1d09e35..1b6264c 100644 --- a/Foundation/GTMNSData+zlibTest.m +++ b/Foundation/GTMNSData+zlibTest.m @@ -17,12 +17,12 @@ // #import "GTMSenTestCase.h" - +#import "GTMUnitTestDevLog.h" #import "GTMNSData+zlib.h" #import // for randiom/srandomdev #import -@interface GTMNSData_zlibTest : SenTestCase +@interface GTMNSData_zlibTest : GTMTestCase @end @@ -102,16 +102,26 @@ static BOOL HasGzipHeader(NSData *data) { STAssertEqualObjects(data, dataPrime, nil); // test non-compressed data data itself + [GTMUnitTestDevLog expectString:@"Error trying to inflate some of the " + "payload, error -3"]; STAssertNil([NSData gtm_dataByInflatingData:data], nil); // test deflated data runs that end before they are done + [GTMUnitTestDevLog expect:[deflated length] - 1 + casesOfString:@"Error trying to inflate some of the payload, " + "error -5"]; for (NSUInteger x = 1 ; x < [deflated length] ; ++x) { - STAssertNil([NSData gtm_dataByInflatingBytes:[deflated bytes] length:x], nil); + STAssertNil([NSData gtm_dataByInflatingBytes:[deflated bytes] + length:x], nil); } // test gzipped data runs that end before they are done + [GTMUnitTestDevLog expect:[gzipped length] - 1 + casesOfString:@"Error trying to inflate some of the payload, " + "error -5"]; for (NSUInteger x = 1 ; x < [gzipped length] ; ++x) { - STAssertNil([NSData gtm_dataByInflatingBytes:[gzipped bytes] length:x], nil); + STAssertNil([NSData gtm_dataByInflatingBytes:[gzipped bytes] + length:x], nil); } // test extra data before the deflated/gzipped data (just to make sure we @@ -121,7 +131,11 @@ static BOOL HasGzipHeader(NSData *data) { [prefixedDeflated setLength:20]; FillWithRandom([prefixedDeflated mutableBytes], [prefixedDeflated length]); [prefixedDeflated appendData:deflated]; + [GTMUnitTestDevLog expectString:@"Error trying to inflate some of the " + "payload, error -3"]; STAssertNil([NSData gtm_dataByInflatingData:prefixedDeflated], nil); + [GTMUnitTestDevLog expectString:@"Error trying to inflate some of the " + "payload, error -3"]; STAssertNil([NSData gtm_dataByInflatingBytes:[prefixedDeflated bytes] length:[prefixedDeflated length]], nil); @@ -130,7 +144,11 @@ static BOOL HasGzipHeader(NSData *data) { [prefixedGzipped setLength:20]; FillWithRandom([prefixedGzipped mutableBytes], [prefixedGzipped length]); [prefixedGzipped appendData:gzipped]; + [GTMUnitTestDevLog expectString:@"Error trying to inflate some of the " + "payload, error -3"]; STAssertNil([NSData gtm_dataByInflatingData:prefixedGzipped], nil); + [GTMUnitTestDevLog expectString:@"Error trying to inflate some of the " + "payload, error -3"]; STAssertNil([NSData gtm_dataByInflatingBytes:[prefixedGzipped bytes] length:[prefixedGzipped length]], nil); @@ -141,7 +159,11 @@ static BOOL HasGzipHeader(NSData *data) { STAssertNotNil(suffixedDeflated, @"failed to alloc data block"); [suffixedDeflated appendData:deflated]; [suffixedDeflated appendBytes:[data bytes] length:20]; + [GTMUnitTestDevLog expectString:@"thought we finished inflate w/o using " + "all input, 20 bytes left"]; STAssertNil([NSData gtm_dataByInflatingData:suffixedDeflated], nil); + [GTMUnitTestDevLog expectString:@"thought we finished inflate w/o using " + "all input, 20 bytes left"]; STAssertNil([NSData gtm_dataByInflatingBytes:[suffixedDeflated bytes] length:[suffixedDeflated length]], nil); @@ -149,7 +171,11 @@ static BOOL HasGzipHeader(NSData *data) { STAssertNotNil(suffixedGZipped, @"failed to alloc data block"); [suffixedGZipped appendData:gzipped]; [suffixedGZipped appendBytes:[data bytes] length:20]; + [GTMUnitTestDevLog expectString:@"thought we finished inflate w/o using " + "all input, 20 bytes left"]; STAssertNil([NSData gtm_dataByInflatingData:suffixedGZipped], nil); + [GTMUnitTestDevLog expectString:@"thought we finished inflate w/o using " + "all input, 20 bytes left"]; STAssertNil([NSData gtm_dataByInflatingBytes:[suffixedGZipped bytes] length:[suffixedGZipped length]], nil); @@ -173,24 +199,32 @@ static BOOL HasGzipHeader(NSData *data) { FillWithRandom([data mutableBytes], [data length]); // w/ *Bytes apis, default level - NSData *deflated = [NSData gtm_dataByDeflatingBytes:[data bytes] length:[data length]]; + NSData *deflated = [NSData gtm_dataByDeflatingBytes:[data bytes] + length:[data length]]; STAssertNotNil(deflated, @"failed to deflate data block"); - STAssertGreaterThan([deflated length], (NSUInteger)0, @"failed to deflate data block"); + STAssertGreaterThan([deflated length], + (NSUInteger)0, @"failed to deflate data block"); STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data"); - NSData *dataPrime = [NSData gtm_dataByInflatingBytes:[deflated bytes] length:[deflated length]]; + NSData *dataPrime = [NSData gtm_dataByInflatingBytes:[deflated bytes] + length:[deflated length]]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); - STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Bytes apis"); + STAssertGreaterThan([dataPrime length], + (NSUInteger)0, @"failed to inflate data block"); + STAssertEqualObjects(data, + dataPrime, @"failed to round trip via *Bytes apis"); // w/ *Data apis, default level deflated = [NSData gtm_dataByDeflatingData:data]; STAssertNotNil(deflated, @"failed to deflate data block"); - STAssertGreaterThan([deflated length], (NSUInteger)0, @"failed to deflate data block"); + STAssertGreaterThan([deflated length], + (NSUInteger)0, @"failed to deflate data block"); STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data"); dataPrime = [NSData gtm_dataByInflatingData:deflated]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); - STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Data apis"); + STAssertGreaterThan([dataPrime length], + (NSUInteger)0, @"failed to inflate data block"); + STAssertEqualObjects(data, + dataPrime, @"failed to round trip via *Data apis"); // loop over the compression levels for (int level = 1 ; level < 9 ; ++level) { @@ -199,22 +233,29 @@ static BOOL HasGzipHeader(NSData *data) { length:[data length] compressionLevel:level]; STAssertNotNil(deflated, @"failed to deflate data block"); - STAssertGreaterThan([deflated length], (NSUInteger)0, @"failed to deflate data block"); + STAssertGreaterThan([deflated length], + (NSUInteger)0, @"failed to deflate data block"); STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data"); - dataPrime = [NSData gtm_dataByInflatingBytes:[deflated bytes] length:[deflated length]]; + dataPrime = [NSData gtm_dataByInflatingBytes:[deflated bytes] + length:[deflated length]]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); - STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Bytes apis"); + STAssertGreaterThan([dataPrime length], + (NSUInteger)0, @"failed to inflate data block"); + STAssertEqualObjects(data, + dataPrime, @"failed to round trip via *Bytes apis"); // w/ *Data apis, using our level deflated = [NSData gtm_dataByDeflatingData:data compressionLevel:level]; STAssertNotNil(deflated, @"failed to deflate data block"); - STAssertGreaterThan([deflated length], (NSUInteger)0, @"failed to deflate data block"); + STAssertGreaterThan([deflated length], + (NSUInteger)0, @"failed to deflate data block"); STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data"); dataPrime = [NSData gtm_dataByInflatingData:deflated]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); - STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Data apis"); + STAssertGreaterThan([dataPrime length], + (NSUInteger)0, @"failed to inflate data block"); + STAssertEqualObjects(data, + dataPrime, @"failed to round trip via *Data apis"); } [localPool release]; @@ -238,24 +279,34 @@ static BOOL HasGzipHeader(NSData *data) { FillWithRandom([data mutableBytes], [data length]); // w/ *Bytes apis, default level - NSData *gzipped = [NSData gtm_dataByGzippingBytes:[data bytes] length:[data length]]; + NSData *gzipped = [NSData gtm_dataByGzippingBytes:[data bytes] + length:[data length]]; STAssertNotNil(gzipped, @"failed to gzip data block"); - STAssertGreaterThan([gzipped length], (NSUInteger)0, @"failed to gzip data block"); - STAssertTrue(HasGzipHeader(gzipped), @"doesn't have gzip header on gzipped data"); - NSData *dataPrime = [NSData gtm_dataByInflatingBytes:[gzipped bytes] length:[gzipped length]]; + STAssertGreaterThan([gzipped length], + (NSUInteger)0, @"failed to gzip data block"); + STAssertTrue(HasGzipHeader(gzipped), + @"doesn't have gzip header on gzipped data"); + NSData *dataPrime = [NSData gtm_dataByInflatingBytes:[gzipped bytes] + length:[gzipped length]]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); - STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Bytes apis"); + STAssertGreaterThan([dataPrime length], + (NSUInteger)0, @"failed to inflate data block"); + STAssertEqualObjects(data, + dataPrime, @"failed to round trip via *Bytes apis"); // w/ *Data apis, default level gzipped = [NSData gtm_dataByGzippingData:data]; STAssertNotNil(gzipped, @"failed to gzip data block"); - STAssertGreaterThan([gzipped length], (NSUInteger)0, @"failed to gzip data block"); - STAssertTrue(HasGzipHeader(gzipped), @"doesn't have gzip header on gzipped data"); + STAssertGreaterThan([gzipped length], + (NSUInteger)0, @"failed to gzip data block"); + STAssertTrue(HasGzipHeader(gzipped), + @"doesn't have gzip header on gzipped data"); dataPrime = [NSData gtm_dataByInflatingData:gzipped]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); - STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Data apis"); + STAssertGreaterThan([dataPrime length], + (NSUInteger)0, @"failed to inflate data block"); + STAssertEqualObjects(data, dataPrime, + @"failed to round trip via *Data apis"); // loop over the compression levels for (int level = 1 ; level < 9 ; ++level) { @@ -264,22 +315,31 @@ static BOOL HasGzipHeader(NSData *data) { length:[data length] compressionLevel:level]; STAssertNotNil(gzipped, @"failed to gzip data block"); - STAssertGreaterThan([gzipped length], (NSUInteger)0, @"failed to gzip data block"); - STAssertTrue(HasGzipHeader(gzipped), @"doesn't have gzip header on gzipped data"); - dataPrime = [NSData gtm_dataByInflatingBytes:[gzipped bytes] length:[gzipped length]]; + STAssertGreaterThan([gzipped length], + (NSUInteger)0, @"failed to gzip data block"); + STAssertTrue(HasGzipHeader(gzipped), + @"doesn't have gzip header on gzipped data"); + dataPrime = [NSData gtm_dataByInflatingBytes:[gzipped bytes] + length:[gzipped length]]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); - STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Bytes apis"); + STAssertGreaterThan([dataPrime length], + (NSUInteger)0, @"failed to inflate data block"); + STAssertEqualObjects(data, dataPrime, + @"failed to round trip via *Bytes apis"); // w/ *Data apis, using our level gzipped = [NSData gtm_dataByGzippingData:data compressionLevel:level]; STAssertNotNil(gzipped, @"failed to gzip data block"); - STAssertGreaterThan([gzipped length], (NSUInteger)0, @"failed to gzip data block"); - STAssertTrue(HasGzipHeader(gzipped), @"doesn't have gzip header on gzipped data"); + STAssertGreaterThan([gzipped length], + (NSUInteger)0, @"failed to gzip data block"); + STAssertTrue(HasGzipHeader(gzipped), + @"doesn't have gzip header on gzipped data"); dataPrime = [NSData gtm_dataByInflatingData:gzipped]; STAssertNotNil(dataPrime, @"failed to inflate data block"); - STAssertGreaterThan([dataPrime length], (NSUInteger)0, @"failed to inflate data block"); - STAssertEqualObjects(data, dataPrime, @"failed to round trip via *Data apis"); + STAssertGreaterThan([dataPrime length], + (NSUInteger)0, @"failed to inflate data block"); + STAssertEqualObjects(data, + dataPrime, @"failed to round trip via *Data apis"); } [localPool release]; diff --git a/Foundation/GTMNSDictionary+URLArguments.m b/Foundation/GTMNSDictionary+URLArguments.m index d67572c..9b3d67e 100644 --- a/Foundation/GTMNSDictionary+URLArguments.m +++ b/Foundation/GTMNSDictionary+URLArguments.m @@ -22,7 +22,7 @@ @implementation NSDictionary (GTMNSDictionaryURLArgumentsAdditions) -GTM_METHOD_CHECK(NSString, gtm_stringByEscapingForURLArgument); +GTM_METHOD_CHECK(NSString, gtm_stringByEscapingForURLArgument); // COV_NF_LINE - (NSString *)gtm_httpArgumentsString { NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:[self count]]; diff --git a/Foundation/GTMNSDictionary+URLArgumentsTest.m b/Foundation/GTMNSDictionary+URLArgumentsTest.m index 94d06b9..f01519e 100644 --- a/Foundation/GTMNSDictionary+URLArgumentsTest.m +++ b/Foundation/GTMNSDictionary+URLArgumentsTest.m @@ -20,7 +20,7 @@ #import "GTMNSDictionary+URLArguments.h" #import "GTMDefines.h" -@interface GTMNSDictionary_URLArgumentsTest : SenTestCase +@interface GTMNSDictionary_URLArgumentsTest : GTMTestCase @end @implementation GTMNSDictionary_URLArgumentsTest diff --git a/Foundation/GTMNSEnumerator+Filter.m b/Foundation/GTMNSEnumerator+Filter.m index 0fda2ca..51d56f8 100644 --- a/Foundation/GTMNSEnumerator+Filter.m +++ b/Foundation/GTMNSEnumerator+Filter.m @@ -19,6 +19,12 @@ #import "GTMNSEnumerator+Filter.h" #import "GTMDebugSelectorValidation.h" #import "GTMDefines.h" +#if GTM_IPHONE_SDK +#import +#import +#else +#import +#endif // a private subclass of NSEnumerator that does all the work. // public interface just returns one of these. @@ -101,10 +107,13 @@ @implementation GTMEnumeratorFilter // 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)filterObject:(id)obj returning:(id *)resultp { *resultp = obj; - // intptr_t is an integer the same size as a pointer. - return (BOOL) (intptr_t) [obj performSelector:operation_ withObject:other_]; + return ((BOOL (*)(id, SEL, id))objc_msgSend)(obj, operation_, other_); } @end @@ -115,10 +124,13 @@ @implementation GTMEnumeratorTargetFilter // 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)filterObject:(id)obj returning:(id *)resultp { *resultp = obj; - // intptr_t is an integer the same size as a pointer. - return (BOOL) (intptr_t) [other_ performSelector:operation_ withObject:obj]; + return ((BOOL (*)(id, SEL, id))objc_msgSend)(other_, operation_, obj); } @end @@ -142,9 +154,10 @@ - (NSEnumerator *)gtm_filteredEnumeratorByTarget:(id)target performOnEachSelector:(SEL)selector { // make sure the object impls this selector taking an object as an arg. - GTMAssertSelectorNilOrImplementedWithArguments(target, selector, - @encode(id), - NULL); + GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(target, selector, + @encode(BOOL), + @encode(id), + NULL); return [[[GTMEnumeratorTargetFilter alloc] initWithBase:self sel:selector object:target] autorelease]; @@ -153,9 +166,10 @@ - (NSEnumerator *)gtm_enumeratorByTarget:(id)target performOnEachSelector:(SEL)selector { // make sure the object impls this selector taking an object as an arg. - GTMAssertSelectorNilOrImplementedWithArguments(target, selector, - @encode(id), - NULL); + GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(target, selector, + @encode(id), + @encode(id), + NULL); return [[[GTMEnumeratorTargetTransformer alloc] initWithBase:self sel:selector object:target] autorelease]; diff --git a/Foundation/GTMNSEnumerator+FilterTest.m b/Foundation/GTMNSEnumerator+FilterTest.m index 224b8b0..ef0d955 100644 --- a/Foundation/GTMNSEnumerator+FilterTest.m +++ b/Foundation/GTMNSEnumerator+FilterTest.m @@ -19,7 +19,7 @@ #import "GTMSenTestCase.h" #import "GTMNSEnumerator+Filter.h" -@interface GTMNSEnumerator_FilterTest : SenTestCase +@interface GTMNSEnumerator_FilterTest : GTMTestCase @end @implementation GTMNSEnumerator_FilterTest diff --git a/Foundation/GTMNSFileManager+PathTest.m b/Foundation/GTMNSFileManager+PathTest.m index 0f2eb35..ac36cac 100644 --- a/Foundation/GTMNSFileManager+PathTest.m +++ b/Foundation/GTMNSFileManager+PathTest.m @@ -19,7 +19,7 @@ #import "GTMSenTestCase.h" #import "GTMNSFileManager+Path.h" -@interface GTMNSFileManager_PathTest : SenTestCase { +@interface GTMNSFileManager_PathTest : GTMTestCase { NSString *baseDir_; } @end @@ -131,7 +131,7 @@ @"a.txt", @"b.txt", @"c.rtf", @"d.m", }; - for (int i = 0; i < sizeof(testDirs) / sizeof(NSString*); i++) { + for (size_t i = 0; i < sizeof(testDirs) / sizeof(NSString*); i++) { NSString *testDir = nil; if ([testDirs[i] length]) { testDir = [baseDir_ stringByAppendingPathComponent:testDirs[i]]; @@ -139,7 +139,7 @@ } else { testDir = baseDir_; } - for (int j = 0; j < sizeof(testFiles) / sizeof(NSString*); j++) { + for (size_t j = 0; j < sizeof(testFiles) / sizeof(NSString*); j++) { NSString *testFile = [testDir stringByAppendingPathComponent:testFiles[j]]; STAssertTrue([@"test" writeToFile:testFile atomically:YES], nil); } @@ -147,13 +147,13 @@ // build set of the top level items NSMutableArray *allFiles = [NSMutableArray array]; - for (int i = 0; i < sizeof(testDirs) / sizeof(NSString*); i++) { + for (size_t i = 0; i < sizeof(testDirs) / sizeof(NSString*); i++) { if ([testDirs[i] length]) { NSString *testDir = [baseDir_ stringByAppendingPathComponent:testDirs[i]]; [allFiles addObject:testDir]; } } - for (int j = 0; j < sizeof(testFiles) / sizeof(NSString*); j++) { + for (size_t j = 0; j < sizeof(testFiles) / sizeof(NSString*); j++) { NSString *testFile = [baseDir_ stringByAppendingPathComponent:testFiles[j]]; [allFiles addObject:testFile]; } diff --git a/Foundation/GTMNSString+HTML.m b/Foundation/GTMNSString+HTML.m index 5178ba9..6853df0 100644 --- a/Foundation/GTMNSString+HTML.m +++ b/Foundation/GTMNSString+HTML.m @@ -357,8 +357,8 @@ static HTMLEscapeMap gUnicodeHTMLEscapeMap[] = { // Utility function for Bsearching table above static int EscapeMapCompare(const void *ucharVoid, const void *mapVoid) { - unichar *uchar = (unichar*)ucharVoid; - HTMLEscapeMap *map = (HTMLEscapeMap*)mapVoid; + const unichar *uchar = (const unichar*)ucharVoid; + const HTMLEscapeMap *map = (const HTMLEscapeMap*)mapVoid; int val; if (*uchar > map->uchar) { val = 1; @@ -387,18 +387,16 @@ static int EscapeMapCompare(const void *ucharVoid, const void *mapVoid) { // it's so short that it isn't really worth trying to share. const unichar *buffer = CFStringGetCharactersPtr((CFStringRef)self); if (!buffer) { - size_t memsize = length * sizeof(UniChar); - - // nope, alloc buffer and fetch the chars ourselves - buffer = malloc(memsize); - if (!buffer) { + // We want this buffer to be autoreleased. + NSMutableData *data = [NSMutableData dataWithLength:length * sizeof(UniChar)]; + if (!data) { // COV_NF_START - Memory fail case _GTMDevLog(@"couldn't alloc buffer"); return nil; // COV_NF_END } - [self getCharacters:(void*)buffer]; - [NSData dataWithBytesNoCopy:(void*)buffer length:memsize]; + [self getCharacters:[data mutableBytes]]; + buffer = [data bytes]; } if (!buffer || !data2) { diff --git a/Foundation/GTMNSString+HTMLTest.m b/Foundation/GTMNSString+HTMLTest.m index a56c5a5..1c7baf0 100644 --- a/Foundation/GTMNSString+HTMLTest.m +++ b/Foundation/GTMNSString+HTMLTest.m @@ -19,7 +19,7 @@ #import "GTMSenTestCase.h" #import "GTMNSString+HTML.h" -@interface GTMNSString_HTMLTest : SenTestCase +@interface GTMNSString_HTMLTest : GTMTestCase @end @implementation GTMNSString_HTMLTest diff --git a/Foundation/GTMNSString+URLArgumentsTest.m b/Foundation/GTMNSString+URLArgumentsTest.m index c87847e..90a5a81 100644 --- a/Foundation/GTMNSString+URLArgumentsTest.m +++ b/Foundation/GTMNSString+URLArgumentsTest.m @@ -19,7 +19,7 @@ #import "GTMSenTestCase.h" #import "GTMNSString+URLArguments.h" -@interface GTMNSString_URLArgumentsTest : SenTestCase +@interface GTMNSString_URLArgumentsTest : GTMTestCase @end @implementation GTMNSString_URLArgumentsTest diff --git a/Foundation/GTMNSString+XML.m b/Foundation/GTMNSString+XML.m index bc2f130..909a8f1 100644 --- a/Foundation/GTMNSString+XML.m +++ b/Foundation/GTMNSString+XML.m @@ -113,18 +113,16 @@ static NSString *AutoreleasedCloneForXML(NSString *src, BOOL escaping) { // it's so short that it isn't really worth trying to share. const UniChar *buffer = CFStringGetCharactersPtr((CFStringRef)src); if (!buffer) { - size_t memsize = length * sizeof(UniChar); - - // nope, alloc buffer and fetch the chars ourselves - buffer = malloc(memsize); - if (!buffer) { + // We want this buffer to be autoreleased. + NSMutableData *data = [NSMutableData dataWithLength:length * sizeof(UniChar)]; + if (!data) { // COV_NF_START - Memory fail case _GTMDevLog(@"couldn't alloc buffer"); return nil; // COV_NF_END } - [src getCharacters:(void*)buffer]; - [NSData dataWithBytesNoCopy:(void*)buffer length:memsize]; + [src getCharacters:[data mutableBytes]]; + buffer = [data bytes]; } const UniChar *goodRun = buffer; diff --git a/Foundation/GTMNSString+XMLTest.m b/Foundation/GTMNSString+XMLTest.m index f1e964d..dc157dc 100644 --- a/Foundation/GTMNSString+XMLTest.m +++ b/Foundation/GTMNSString+XMLTest.m @@ -21,7 +21,7 @@ #import "GTMNSString+XML.h" -@interface GTMNSString_XMLTest : SenTestCase +@interface GTMNSString_XMLTest : GTMTestCase @end @implementation GTMNSString_XMLTest diff --git a/Foundation/GTMObjC2RuntimeTest.m b/Foundation/GTMObjC2RuntimeTest.m index 2a7f354..6fb5bdf 100644 --- a/Foundation/GTMObjC2RuntimeTest.m +++ b/Foundation/GTMObjC2RuntimeTest.m @@ -34,7 +34,7 @@ AT_REQUIRED + (NSString*)class_required; @end -@interface GTMObjC2RuntimeTest : SenTestCase { +@interface GTMObjC2RuntimeTest : GTMTestCase { Class cls_; } @end diff --git a/Foundation/GTMRegex.m b/Foundation/GTMRegex.m index 92eb576..f183553 100644 --- a/Foundation/GTMRegex.m +++ b/Foundation/GTMRegex.m @@ -228,7 +228,7 @@ static NSString *const kReplacementPattern = // make sure the match is the full string return (regMatch.rm_so == 0) && - (regMatch.rm_eo == [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); + (regMatch.rm_eo == (regoff_t)[str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); } - (NSArray *)subPatternsOfString:(NSString *)str { @@ -252,7 +252,7 @@ static NSString *const kReplacementPattern = // make sure the match is the full string if ((regMatches[0].rm_so != 0) || - (regMatches[0].rm_eo != [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding])) { + (regMatches[0].rm_eo != (regoff_t)[str lengthOfBytesUsingEncoding:NSUTF8StringEncoding])) { // only matched a sub part of the string return nil; } @@ -551,7 +551,7 @@ static NSString *const kReplacementPattern = isMatch = YES; // if we have something saved, it was a pattern match } // have we reached the end? - else if (curParseIndex_ >= [utf8StrBuf_ length]) { + else if (curParseIndex_ >= (regoff_t)[utf8StrBuf_ length]) { // done, do nothing, we'll return nil } // do the search. @@ -697,17 +697,20 @@ static NSString *const kReplacementPattern = return [self subPatternString:0]; } -- (NSString *)subPatternString:(NSUInteger)index { - if ((index < 0) || (index > numRegMatches_)) +- (NSString *)subPatternString:(NSUInteger)patternIndex { + if (patternIndex > numRegMatches_) return nil; // pick off when it wasn't found - if ((regMatches_[index].rm_so == -1) && (regMatches_[index].rm_eo == -1)) + if ((regMatches_[patternIndex].rm_so == -1) && + (regMatches_[patternIndex].rm_eo == -1)) return nil; // fetch the string - const char *base = (const char*)[utf8StrBuf_ bytes] + regMatches_[index].rm_so; - regoff_t len = regMatches_[index].rm_eo - regMatches_[index].rm_so; + const char *base = (const char*)[utf8StrBuf_ bytes] + + regMatches_[patternIndex].rm_so; + regoff_t len = regMatches_[patternIndex].rm_eo + - regMatches_[patternIndex].rm_so; return [[[NSString alloc] initWithBytes:base length:(NSUInteger)len encoding:NSUTF8StringEncoding] autorelease]; @@ -748,7 +751,7 @@ static NSString *const kReplacementPattern = isMatch_ = isMatch; // check the args - if (!utf8StrBuf_ || !regMatches_ || (numRegMatches_ < 0)) { + if (!utf8StrBuf_ || !regMatches_) { // COV_NF_START // this could only happen something messed w/ our internal state. [self release]; diff --git a/Foundation/GTMRegexTest.m b/Foundation/GTMRegexTest.m index 033b560..6f41d60 100644 --- a/Foundation/GTMRegexTest.m +++ b/Foundation/GTMRegexTest.m @@ -18,6 +18,7 @@ #import "GTMSenTestCase.h" #import "GTMRegex.h" +#import "GTMUnitTestDevLog.h" // // NOTE: @@ -26,10 +27,10 @@ // libregex, we just want to test our wrapper. // -@interface GTMRegexTest : SenTestCase +@interface GTMRegexTest : GTMTestCase @end -@interface NSString_GTMRegexAdditions : SenTestCase +@interface NSString_GTMRegexAdditions : GTMTestCase @end @implementation GTMRegexTest @@ -57,7 +58,9 @@ STAssertNil([[[GTMRegex alloc] initWithPattern:nil] autorelease], nil); STAssertNil([[[GTMRegex alloc] initWithPattern:nil options:kGTMRegexOptionIgnoreCase] autorelease], nil); + [GTMUnitTestDevLog expectString:@"Invalid pattern \"(.\", error: \"parentheses not balanced\""]; STAssertNil([[[GTMRegex alloc] initWithPattern:@"(."] autorelease], nil); + [GTMUnitTestDevLog expectString:@"Invalid pattern \"(.\", error: \"parentheses not balanced\""]; STAssertNil([[[GTMRegex alloc] initWithPattern:@"(." options:kGTMRegexOptionIgnoreCase] autorelease], nil); // fail cases w/ error param @@ -93,7 +96,9 @@ STAssertNil([GTMRegex regexWithPattern:nil], nil); STAssertNil([GTMRegex regexWithPattern:nil options:0], nil); + [GTMUnitTestDevLog expectString:@"Invalid pattern \"(.\", error: \"parentheses not balanced\""]; STAssertNil([GTMRegex regexWithPattern:@"(."], nil); + [GTMUnitTestDevLog expectString:@"Invalid pattern \"(.\", error: \"parentheses not balanced\""]; STAssertNil([GTMRegex regexWithPattern:@"(." options:0], nil); // fail cases (helper) w/ error param diff --git a/Foundation/GTMScriptRunnerTest.m b/Foundation/GTMScriptRunnerTest.m index 5229800..45378bc 100644 --- a/Foundation/GTMScriptRunnerTest.m +++ b/Foundation/GTMScriptRunnerTest.m @@ -20,8 +20,9 @@ #import #import "GTMSenTestCase.h" #import "GTMScriptRunner.h" +#import "GTMUnitTestDevLog.h" -@interface GTMScriptRunnerTest : SenTestCase { +@interface GTMScriptRunnerTest : GTMTestCase { @private NSString *shScript_; NSString *perlScript_; @@ -67,14 +68,17 @@ - (void)tearDown { const char *path = [shScript_ fileSystemRepresentation]; - if (path) + if (path) { unlink(path); + } path = [perlScript_ fileSystemRepresentation]; - if (path) + if (path) { unlink(path); + } path = [shOutputScript_ fileSystemRepresentation]; - if (path) + if (path) { unlink(path); + } } - (void)testShCommands { @@ -184,30 +188,30 @@ output = [sr run:@"/usr/bin/env | wc -l"]; int numVars = [output intValue]; - STAssertTrue(numVars > 0, @"numVars should be positive"); + STAssertGreaterThan(numVars, 0, @"numVars should be positive"); // By default the environment is wiped clean, however shells often add a few // of their own env vars after things have been wiped. For example, sh will // add about 3 env vars (PWD, _, and SHLVL). - STAssertTrue(numVars < 5, @"Our env should be almost empty"); + STAssertLessThan(numVars, 5, @"Our env should be almost empty"); NSDictionary *newEnv = [NSDictionary dictionaryWithObject:@"bar" forKey:@"foo"]; [sr setEnvironment:newEnv]; output = [sr run:@"/usr/bin/env | wc -l"]; - STAssertTrue([output intValue] == numVars + 1, + STAssertEquals([output intValue], numVars + 1, @"should have one more env var now"); [sr setEnvironment:nil]; output = [sr run:@"/usr/bin/env | wc -l"]; - STAssertTrue([output intValue] == numVars, + STAssertEquals([output intValue], numVars, @"should be back down to %d vars", numVars); NSDictionary *currVars = [[NSProcessInfo processInfo] environment]; [sr setEnvironment:currVars]; output = [sr run:@"/usr/bin/env | wc -l"]; - STAssertTrue([output intValue] == [currVars count], + STAssertEquals([output intValue], (int)[currVars count], @"should be back down to %d vars", numVars); } @@ -339,6 +343,8 @@ STAssertNil([sr run:nil standardError:&err], nil); STAssertNil(err, nil); + [GTMUnitTestDevLog expectString:@"Failed to launch interpreter " + "'/path/that/does/not/exists/interpreter' due to: launch path not accessible"]; STAssertNil([sr run:@"ls /" standardError:&err], nil); STAssertNil(err, nil); } @@ -349,8 +355,12 @@ STAssertNotNil(sr, @"Script runner must not be nil"); NSString *err = nil; + [GTMUnitTestDevLog expectString:@"Failed to launch interpreter " + "'/path/that/does/not/exists/interpreter' due to: launch path not accessible"]; STAssertNil([sr runScript:shScript_ withArgs:nil standardError:&err], nil); STAssertNil(err, nil); + [GTMUnitTestDevLog expectString:@"Failed to launch interpreter " + "'/path/that/does/not/exists/interpreter' due to: launch path not accessible"]; STAssertNil([sr runScript:@"/path/that/does/not/exists/foo/bar/baz" withArgs:nil standardError:&err], nil); STAssertNil(err, nil); diff --git a/Foundation/GTMStackTrace.c b/Foundation/GTMStackTrace.c index febf623..68e5c7d 100644 --- a/Foundation/GTMStackTrace.c +++ b/Foundation/GTMStackTrace.c @@ -76,7 +76,7 @@ CFStringRef GTMStackTraceCreate(void) { CFMutableStringRef trace = CFStringCreateMutable(kCFAllocatorDefault, 0); for (int i = 0; i < depth; i++) { - Dl_info info = { 0 }; + Dl_info info = { NULL, NULL, NULL, NULL }; dladdr(pcs[i], &info); const char *symbol = info.dli_sname; const char *fname = info.dli_fname; diff --git a/Foundation/GTMStackTraceTest.m b/Foundation/GTMStackTraceTest.m index 7845a2d..16c6273 100644 --- a/Foundation/GTMStackTraceTest.m +++ b/Foundation/GTMStackTraceTest.m @@ -21,7 +21,7 @@ #import "GTMStackTrace.h" #import "GTMSenTestCase.h" -@interface GTMStackTraceTest : SenTestCase +@interface GTMStackTraceTest : GTMTestCase @end @implementation GTMStackTraceTest diff --git a/Foundation/GTMSystemVersionTest.m b/Foundation/GTMSystemVersionTest.m index 38c37eb..e95a33d 100644 --- a/Foundation/GTMSystemVersionTest.m +++ b/Foundation/GTMSystemVersionTest.m @@ -19,7 +19,7 @@ #import "GTMSenTestCase.h" #import "GTMSystemVersion.h" -@interface GTMSystemVersionTest : SenTestCase +@interface GTMSystemVersionTest : GTMTestCase @end @implementation GTMSystemVersionTest diff --git a/Foundation/TestData/GTMHTTPFetcherTestServer b/Foundation/TestData/GTMHTTPFetcherTestServer deleted file mode 100755 index 838c110..0000000 --- a/Foundation/TestData/GTMHTTPFetcherTestServer +++ /dev/null @@ -1,274 +0,0 @@ -#!/usr/bin/python -# -# 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. -# -# A simple server for testing the http calls - -"""A simple BaseHTTPRequestHandler for unit testing GTM Network code - -This http server is for use by GTMHTTPFetcherTest.m in testing -both authentication and object retrieval. - -Requests to the path /accounts/ClientLogin are assumed to be -for login; other requests are for object retrieval -""" - -__author__ = 'Google Inc.' - -import string -import cgi -import time -import os -import sys -import re -import mimetypes -import socket -from BaseHTTPServer import BaseHTTPRequestHandler -from BaseHTTPServer import HTTPServer -from optparse import OptionParser - -class ServerTimeoutException(Exception): - pass - - -class TimeoutServer(HTTPServer): - - """HTTP server for testing GTM network requests. - - This server will throw an exception if it receives no connections for - several minutes. We use this to ensure that the server will be cleaned - up if something goes wrong during the unit testing. - """ - - def get_request(self): - self.socket.settimeout(120.0) - result = None - while result is None: - try: - result = self.socket.accept() - except socket.timeout: - raise ServerTimeoutException - result[0].settimeout(None) - return result - - -class SimpleServer(BaseHTTPRequestHandler): - - """HTTP request handler for testing GTM network requests. - - This is an implementation of a request handler for BaseHTTPServer, - specifically designed for GTM network code usage. - - Normal requests for GET/POST/PUT simply retrieve the file from the - supplied path, starting in the current directory. A cookie called - TestCookie is set by the response header, with the value of the filename - requested. - - DELETE requests always succeed. - - Appending ?status=n results in a failure with status value n. - - Paths ending in .auth have the .auth extension stripped, and must have - an authorization header of "GoogleLogin auth=GoodAuthToken" to succeed. - - Successful results have a Last-Modified header set; if that header's value - ("thursday") is supplied in a request's "If-Modified-Since" header, the - result is 304 (Not Modified). - - Requests to /accounts/ClientLogin will fail if supplied with a body - containing Passwd=bad. If they contain logintoken and logincaptcha values, - those must be logintoken=CapToken&logincaptch=good to succeed. - """ - - def do_GET(self): - self.doAllRequests() - - def do_POST(self): - self.doAllRequests() - - def do_PUT(self): - self.doAllRequests() - - def do_DELETE(self): - self.doAllRequests() - - def doAllRequests(self): - # This method handles all expected incoming requests - # - # Requests to path /accounts/ClientLogin are assumed to be for signing in - # - # Other paths are for retrieving a local file. An .auth appended - # to a file path will require authentication (meaning the Authorization - # header must be present with the value "GoogleLogin auth=GoodAuthToken".) - # If the token is present, the file (without uthe .auth at the end) will - # be returned. - # - # HTTP Delete commands succeed but return no data. - # - # GData override headers (putting the verb in X-HTTP-Method-Override) - # are supported. - # - # Any auth password is valid except "bad", which will fail, and "captcha", - # which will fail unless the authentication request's post string includes - # "logintoken=CapToken&logincaptcha=good" - - # We will use a readable default result string since it should never show up - # in output - resultString = "default GTMHTTPFetcherTestServer result\n"; - resultStatus = 0 - headerType = "text/plain" - postString = "" - modifiedDate = "thursday" # clients should treat dates as opaque, generally - - # auth queries and some others may include post data - postLength = int(self.headers.getheader("Content-Length", "0")); - if postLength > 0: - postString = self.rfile.read(postLength) - - # auth queries and some GData queries include post data - ifModifiedSince = self.headers.getheader("If-Modified-Since", ""); - - # retrieve the auth header; require it if the file path ends - # with the string ".auth" - authorization = self.headers.getheader("Authorization", "") - if self.path.endswith(".auth"): - if authorization != "GoogleLogin auth=GoodAuthToken": - self.send_error(401,"Unauthorized: %s" % self.path) - return - self.path = self.path[:-5] # remove the .auth at the end - - overrideHeader = self.headers.getheader("X-HTTP-Method-Override", "") - httpCommand = self.command - if httpCommand == "POST" and len(overrideHeader) > 0: - httpCommand = overrideHeader - - try: - if self.path.endswith("/accounts/ClientLogin"): - # - # it's a sign-in attempt; it's good unless the password is "bad" or - # "captcha" - # - - # use regular expression to find the password - password = "" - searchResult = re.search("(Passwd=)([^&\n]*)", postString) - if searchResult: - password = searchResult.group(2) - - if password == "bad": - resultString = "Error=BadAuthentication\n" - resultStatus = 403 - - elif password == "captcha": - logintoken = "" - logincaptcha = "" - - # use regular expressions to find the captcha token and answer - searchResult = re.search("(logintoken=)([^&\n]*)", postString); - if searchResult: - logintoken = searchResult.group(2) - - searchResult = re.search("(logincaptcha=)([^&\n]*)", postString); - if searchResult: - logincaptcha = searchResult.group(2) - - # if the captcha token is "CapToken" and the answer is "good" - # then it's a valid sign in - if (logintoken == "CapToken") and (logincaptcha == "good"): - resultString = "SID=GoodSID\nLSID=GoodLSID\nAuth=GoodAuthToken\n" - resultStatus = 200 - else: - # incorrect captcha token or answer provided - resultString = ("Error=CaptchaRequired\nCaptchaToken=CapToken" - "\nCaptchaUrl=CapUrl\n") - resultStatus = 403 - - else: - # valid username/password - resultString = "SID=GoodSID\nLSID=GoodLSID\nAuth=GoodAuthToken\n" - resultStatus = 200 - - elif httpCommand == "DELETE": - # - # it's an object delete; read and return empty data - # - resultString = "" - resultStatus = 200 - headerType = "text/plain" - - else: - # queries that have something like "?status=456" should fail with the - # status code - searchResult = re.search("(status=)([0-9]+)", self.path) - if searchResult: - status = searchResult.group(2) - self.send_error(int(status), - "Test HTTP server status parameter: %s" % self.path) - return - - # if the client gave us back our not modified date, then say there's no - # change in the response - if ifModifiedSince == modifiedDate: - self.send_response(304) # Not Modified - return - - else: - # - # it's a file fetch; read and return the data file - # - f = open("." + self.path) - resultString = f.read() - f.close() - resultStatus = 200 - fileTypeInfo = mimetypes.guess_type("." + self.path) - headerType = fileTypeInfo[0] # first part of the tuple is mime type - - self.send_response(resultStatus) - self.send_header("Content-type", headerType) - self.send_header("Last-Modified", modifiedDate) - - cookieValue = os.path.basename("." + self.path) - self.send_header('Set-Cookie', 'TestCookie=%s' % cookieValue) - self.end_headers() - self.wfile.write(resultString) - - except IOError: - self.send_error(404,"File Not Found: %s" % self.path) - - -def main(): - try: - parser = OptionParser() - parser.add_option("-p", "--port", dest="port", help="Port to run server on", - type="int", default="80") - parser.add_option("-r", "--root", dest="root", help="Where to root server", - default=".") - (options, args) = parser.parse_args() - os.chdir(options.root) - server = TimeoutServer(("127.0.0.1", options.port), SimpleServer) - sys.stdout.write("started GTMHTTPFetcherTestServer," - " serving files from root directory %s..." % os.getcwd()); - sys.stdout.flush(); - server.serve_forever() - except KeyboardInterrupt: - print "^C received, shutting down server" - server.socket.close() - except ServerTimeoutException: - print "Too long since the last request, shutting down server" - server.socket.close() - - -if __name__ == "__main__": - main() diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj index 2407639..824ef32 100644 --- a/GTM.xcodeproj/project.pbxproj +++ b/GTM.xcodeproj/project.pbxproj @@ -22,6 +22,19 @@ }; /* End PBXAggregateTarget section */ +/* Begin PBXAppleScriptBuildPhase section */ + 8B3345CC0DBF8A95009FD32C /* AppleScript */ = { + isa = PBXAppleScriptBuildPhase; + buildActionMask = 2147483647; + contextName = ""; + files = ( + 8B3345890DBF8A55009FD32C /* GTMNSAppleEvent+HandlerTest.applescript in AppleScript */, + ); + isSharedContext = 0; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXAppleScriptBuildPhase section */ + /* Begin PBXBuildFile section */ 33C372A60DD8A88500E97817 /* GTMNSString+URLArguments.h in Headers */ = {isa = PBXBuildFile; fileRef = 33C372A40DD8A88500E97817 /* GTMNSString+URLArguments.h */; settings = {ATTRIBUTES = (Public, ); }; }; 33C372A70DD8A88500E97817 /* GTMNSString+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 33C372A50DD8A88500E97817 /* GTMNSString+URLArguments.m */; }; @@ -33,6 +46,14 @@ 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 */; }; 8B2A9BEC0D82714A00599386 /* ScreenSaver.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2A9BEB0D82714A00599386 /* ScreenSaver.framework */; }; + 8B2C21B50E00883F00B5ECB1 /* GTMObjC2Runtime.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F32040DA34A1B0052CA40 /* GTMObjC2Runtime.m */; }; + 8B2C21B60E00884000B5ECB1 /* GTMObjC2Runtime.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F32040DA34A1B0052CA40 /* GTMObjC2Runtime.m */; }; + 8B2C21B70E00885600B5ECB1 /* GTMObjC2Runtime.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F32040DA34A1B0052CA40 /* GTMObjC2Runtime.m */; }; + 8B3344210DBF7A36009FD32C /* GTMNSAppleScript+HandlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3344170DBF7A36009FD32C /* GTMNSAppleScript+HandlerTest.m */; }; + 8B3344230DBF7A36009FD32C /* GTMNSAppleEventDescriptor+HandlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B33441A0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+HandlerTest.m */; }; + 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, ); }; }; 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 */; }; @@ -60,13 +81,33 @@ 8B7AD49B0DABBB5800B84F4A /* GTMNSBezierPath+CGPathTest.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8B7AD4980DABBB5800B84F4A /* GTMNSBezierPath+CGPathTest.tiff */; }; 8B7AD49C0DABBB5800B84F4A /* GTMNSBezierPath+RoundRectTest.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8B7AD4990DABBB5800B84F4A /* GTMNSBezierPath+RoundRectTest.tiff */; }; 8B7AD4AE0DABBFEE00B84F4A /* GTMUnitTestingBindingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7AD4AD0DABBFEE00B84F4A /* GTMUnitTestingBindingTest.m */; }; + 8B7DCB9B0DFF0E850017E983 /* GTMFourCharCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BE2836B0DED0F130035B3F8 /* GTMFourCharCode.m */; }; + 8B7DCBA50DFF0EFF0017E983 /* GoogleToolboxForMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F42E086D0D199A5B00D5DDE0 /* GoogleToolboxForMac.framework */; }; + 8B7DCBBD0DFF0F5D0017E983 /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; + 8B7DCBC10DFF0F7F0017E983 /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; + 8B7DCBC20DFF0F7F0017E983 /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; + 8B7DCBC30DFF0F7F0017E983 /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; + 8B7DCBC40DFF0F800017E983 /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; + 8B7DCBD20DFF16070017E983 /* GTMNSAppleScript+Handler.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3344180DBF7A36009FD32C /* GTMNSAppleScript+Handler.m */; }; + 8B7DCBD30DFF16070017E983 /* GTMNSAppleEventDescriptor+Handler.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B33441B0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Handler.m */; }; + 8B7DCBD40DFF16070017E983 /* GTMNSAppleEventDescriptor+Foundation.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B33441E0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Foundation.m */; }; + 8B7DCBE20DFF18720017E983 /* GTMDevLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCBE10DFF18720017E983 /* GTMDevLog.m */; }; + 8B7DCBED0DFF1A4F0017E983 /* GTMUnitTestDevLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCBEC0DFF1A4F0017E983 /* GTMUnitTestDevLog.m */; }; + 8B7DCBEE0DFF1A4F0017E983 /* GTMUnitTestDevLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCBEC0DFF1A4F0017E983 /* GTMUnitTestDevLog.m */; }; + 8B7DCBEF0DFF1A4F0017E983 /* GTMUnitTestDevLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCBEC0DFF1A4F0017E983 /* GTMUnitTestDevLog.m */; }; + 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 */; }; 8BC045C20DAE899100C2D1CA /* GTMGeometryUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2800D198D0E009257D2 /* GTMGeometryUtilsTest.m */; }; 8BC046B90DAE8C4B00C2D1CA /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BC046B80DAE8C4B00C2D1CA /* ApplicationServices.framework */; }; - 8BC04CD70DB003CD00C2D1CA /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; 8BC04CD80DB003D800C2D1CA /* GTMMethodCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B6F31F40DA3489B0052CA40 /* GTMMethodCheck.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8BC04CDD0DB004A000C2D1CA /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; - 8BC04CDE0DB004A000C2D1CA /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; - 8BC04CDF0DB004A100C2D1CA /* GTMMethodCheck.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */; }; + 8BE281B00DEC7E930035B3F8 /* GTMNSAppleScript+Handler.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B3344190DBF7A36009FD32C /* GTMNSAppleScript+Handler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8BE281B10DEC7E930035B3F8 /* GTMNSAppleEventDescriptor+Handler.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B33441C0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Handler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8BE281B20DEC7E930035B3F8 /* GTMNSAppleEventDescriptor+Foundation.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B33441F0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Foundation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8BE2836F0DED0F130035B3F8 /* GTMFourCharCodeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BE2836C0DED0F130035B3F8 /* GTMFourCharCodeTest.m */; }; + 8BE283730DED13AB0035B3F8 /* GTMFourCharCode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BE2836D0DED0F130035B3F8 /* GTMFourCharCode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8BE869730DBE89C100749827 /* GTMNSBezierPath+RoundRectTest.x86_64.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8BE869720DBE89C100749827 /* GTMNSBezierPath+RoundRectTest.x86_64.tiff */; }; 8BEEA90D0DA7446300894774 /* GTMUnitTestingImage.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8BEEA90A0DA7446300894774 /* GTMUnitTestingImage.tiff */; }; 8BEEA90E0DA7446300894774 /* GTMUnitTestingWindow.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8BEEA90B0DA7446300894774 /* GTMUnitTestingWindow.tiff */; }; @@ -116,9 +157,7 @@ F435E08A0DC63F6D0069CDE8 /* GTMHTTPFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = F435E0880DC63F6D0069CDE8 /* GTMHTTPFetcher.m */; }; F435E27F0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = F435E27D0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; F435E2800DC7B0630069CDE8 /* GTMProgressMonitorInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F435E27E0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.m */; }; - F435E3940DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F435E3930DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m */; }; F435E3BB0DC8D1980069CDE8 /* GTMHTTPFetcherTestPage.html in Resources */ = {isa = PBXBuildFile; fileRef = F435E3B90DC8D1720069CDE8 /* GTMHTTPFetcherTestPage.html */; }; - F435E3BC0DC8D1980069CDE8 /* GTMHTTPFetcherTestServer in Resources */ = {isa = PBXBuildFile; fileRef = F435E3BA0DC8D1720069CDE8 /* GTMHTTPFetcherTestServer */; }; F437F55D0D50BC0A00F5C3A4 /* GTMRegex.h in Headers */ = {isa = PBXBuildFile; fileRef = F437F55A0D50BC0A00F5C3A4 /* GTMRegex.h */; settings = {ATTRIBUTES = (Public, ); }; }; F437F55E0D50BC0A00F5C3A4 /* GTMRegex.m in Sources */ = {isa = PBXBuildFile; fileRef = F437F55B0D50BC0A00F5C3A4 /* GTMRegex.m */; }; F437F5620D50BC1D00F5C3A4 /* GTMRegexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F437F55C0D50BC0A00F5C3A4 /* GTMRegexTest.m */; }; @@ -147,6 +186,10 @@ F47F1D300D4914AD00925B8F /* GTMCalculatedRange.h in Headers */ = {isa = PBXBuildFile; fileRef = F47F1D2D0D4914AD00925B8F /* GTMCalculatedRange.h */; settings = {ATTRIBUTES = (Public, ); }; }; F47F1D310D4914AD00925B8F /* GTMCalculatedRange.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1D2E0D4914AD00925B8F /* GTMCalculatedRange.m */; }; F47F1D350D4914B600925B8F /* GTMCalculatedRangeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F47F1D2F0D4914AD00925B8F /* GTMCalculatedRangeTest.m */; }; + F4BC1C880DDDD45D00108B7D /* GTMHTTPServer.h in Headers */ = {isa = PBXBuildFile; fileRef = F4BC1C860DDDD45D00108B7D /* GTMHTTPServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F4BC1C890DDDD45D00108B7D /* GTMHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = F4BC1C870DDDD45D00108B7D /* GTMHTTPServer.m */; }; + F4BC1E8D0DE1FC4A00108B7D /* GTMHTTPServerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F4BC1E8C0DE1FC4A00108B7D /* GTMHTTPServerTest.m */; }; + 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, ); }; }; /* End PBXBuildFile section */ @@ -166,14 +209,14 @@ remoteGlobalIDString = 8B45A2890DA49B99001148C5; remoteInfo = UIUnitTestingHarness; }; - F41D254E0DB9067C00774EEB /* PBXContainerItemProxy */ = { + 8B7DCBA30DFF0EFB0017E983 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; proxyType = 1; - remoteGlobalIDString = 8B45A0270DA4696C001148C5; - remoteInfo = "UnitTest - UnitTesting"; + remoteGlobalIDString = F42E086C0D199A5B00D5DDE0; + remoteInfo = GTM; }; - F41D25510DB9068700774EEB /* PBXContainerItemProxy */ = { + F41D254E0DB9067C00774EEB /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; proxyType = 1; @@ -228,6 +271,16 @@ 8B2A9B1E0D8270DA00599386 /* GTMNSWorkspace+ScreenSaverTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSWorkspace+ScreenSaverTest.m"; sourceTree = ""; }; 8B2A9B1F0D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSWorkspace+ScreenSaver.h"; sourceTree = ""; }; 8B2A9BEB0D82714A00599386 /* ScreenSaver.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScreenSaver.framework; path = /System/Library/Frameworks/ScreenSaver.framework; sourceTree = ""; }; + 8B3344170DBF7A36009FD32C /* GTMNSAppleScript+HandlerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSAppleScript+HandlerTest.m"; sourceTree = ""; }; + 8B3344180DBF7A36009FD32C /* GTMNSAppleScript+Handler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSAppleScript+Handler.m"; sourceTree = ""; }; + 8B3344190DBF7A36009FD32C /* GTMNSAppleScript+Handler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSAppleScript+Handler.h"; sourceTree = ""; }; + 8B33441A0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+HandlerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSAppleEventDescriptor+HandlerTest.m"; sourceTree = ""; }; + 8B33441B0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Handler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSAppleEventDescriptor+Handler.m"; sourceTree = ""; }; + 8B33441C0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Handler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSAppleEventDescriptor+Handler.h"; sourceTree = ""; }; + 8B33441D0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+FoundationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSAppleEventDescriptor+FoundationTest.m"; sourceTree = ""; }; + 8B33441E0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Foundation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSAppleEventDescriptor+Foundation.m"; sourceTree = ""; }; + 8B33441F0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Foundation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSAppleEventDescriptor+Foundation.h"; sourceTree = ""; }; + 8B3344200DBF7A36009FD32C /* GTMNSAppleEvent+HandlerTest.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = "GTMNSAppleEvent+HandlerTest.applescript"; sourceTree = ""; }; 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 = ""; }; 8B45A2670DA498A0001148C5 /* GTMUnitTestingUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMUnitTestingUtilities.h; sourceTree = ""; }; @@ -256,8 +309,15 @@ 8B7AD4980DABBB5800B84F4A /* GTMNSBezierPath+CGPathTest.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+CGPathTest.tiff"; sourceTree = ""; }; 8B7AD4990DABBB5800B84F4A /* GTMNSBezierPath+RoundRectTest.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+RoundRectTest.tiff"; sourceTree = ""; }; 8B7AD4AD0DABBFEE00B84F4A /* GTMUnitTestingBindingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMUnitTestingBindingTest.m; sourceTree = ""; }; + 8B7DCBE10DFF18720017E983 /* GTMDevLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDevLog.m; sourceTree = ""; }; + 8B7DCBEC0DFF1A4F0017E983 /* GTMUnitTestDevLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMUnitTestDevLog.m; sourceTree = ""; }; + 8B7DCBF00DFF1A610017E983 /* GTMUnitTestDevLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMUnitTestDevLog.h; sourceTree = ""; }; + 8B7DCE180DFF39850017E983 /* GTMSenTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSenTestCase.m; sourceTree = ""; }; 8BC046B80DAE8C4B00C2D1CA /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = /System/Library/Frameworks/ApplicationServices.framework; sourceTree = ""; }; 8BC04D140DB0061300C2D1CA /* RunMacOSUnitTests.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = RunMacOSUnitTests.sh; sourceTree = ""; }; + 8BE2836B0DED0F130035B3F8 /* GTMFourCharCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMFourCharCode.m; sourceTree = ""; }; + 8BE2836C0DED0F130035B3F8 /* GTMFourCharCodeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMFourCharCodeTest.m; sourceTree = ""; }; + 8BE2836D0DED0F130035B3F8 /* GTMFourCharCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMFourCharCode.h; sourceTree = ""; }; 8BE869720DBE89C100749827 /* GTMNSBezierPath+RoundRectTest.x86_64.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "GTMNSBezierPath+RoundRectTest.x86_64.tiff"; sourceTree = ""; }; 8BEEA90A0DA7446300894774 /* GTMUnitTestingImage.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = GTMUnitTestingImage.tiff; sourceTree = ""; }; 8BEEA90B0DA7446300894774 /* GTMUnitTestingWindow.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = GTMUnitTestingWindow.tiff; sourceTree = ""; }; @@ -273,7 +333,7 @@ F428FF010D48E55E00382ED1 /* GTMNSBezierPath+CGPathTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSBezierPath+CGPathTest.m"; sourceTree = ""; }; F42E08210D19987200D5DDE0 /* UnitTest - Foundation.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UnitTest - Foundation.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; F42E086D0D199A5B00D5DDE0 /* GoogleToolboxForMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GoogleToolboxForMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F42E086E0D199A5B00D5DDE0 /* GTM-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "GTM-Info.plist"; sourceTree = ""; }; + F42E086E0D199A5B00D5DDE0 /* GTM-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GTM-Info.plist"; sourceTree = ""; }; F42E089B0D199B1800D5DDE0 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; F42E09AD0D19A62F00D5DDE0 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; F43122190DD4E3B800F45252 /* GTMStackTrace.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = GTMStackTrace.c; sourceTree = ""; }; @@ -288,7 +348,6 @@ F435E27E0DC7B0630069CDE8 /* GTMProgressMonitorInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMProgressMonitorInputStream.m; sourceTree = ""; }; F435E3930DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPFetcherTest.m; sourceTree = ""; }; F435E3B90DC8D1720069CDE8 /* GTMHTTPFetcherTestPage.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = GTMHTTPFetcherTestPage.html; sourceTree = ""; }; - F435E3BA0DC8D1720069CDE8 /* GTMHTTPFetcherTestServer */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = GTMHTTPFetcherTestServer; sourceTree = ""; }; F437F55A0D50BC0A00F5C3A4 /* GTMRegex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMRegex.h; sourceTree = ""; }; F437F55B0D50BC0A00F5C3A4 /* GTMRegex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMRegex.m; sourceTree = ""; }; F437F55C0D50BC0A00F5C3A4 /* GTMRegexTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMRegexTest.m; sourceTree = ""; }; @@ -308,6 +367,7 @@ F43E4E5F0D4E5EC90041161F /* GTMNSData+zlib.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSData+zlib.m"; sourceTree = ""; }; F43E4E600D4E5EC90041161F /* GTMNSData+zlibTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSData+zlibTest.m"; sourceTree = ""; }; F43E4F6C0D4E60C50041161F /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = /usr/lib/libz.dylib; sourceTree = ""; }; + F440EDB70DFECC4B0003E81F /* BuildingAndUsing.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = BuildingAndUsing.txt; sourceTree = ""; }; F47A79850D746EE9002302AB /* GTMScriptRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMScriptRunner.h; sourceTree = ""; }; F47A79860D746EE9002302AB /* GTMScriptRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMScriptRunner.m; sourceTree = ""; }; F47A79870D746EE9002302AB /* GTMScriptRunnerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMScriptRunnerTest.m; sourceTree = ""; }; @@ -327,7 +387,7 @@ F48FE2460D197F9A009257D2 /* SharedLibrary.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SharedLibrary.xcconfig; sourceTree = ""; }; F48FE2470D197F9A009257D2 /* StaticLibrary.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = StaticLibrary.xcconfig; sourceTree = ""; }; F48FE2640D198C1E009257D2 /* UnitTest - AppKit.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UnitTest - AppKit.octest"; sourceTree = BUILT_PRODUCTS_DIR; }; - F48FE26E0D198CAD009257D2 /* UnitTest-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "UnitTest-Info.plist"; sourceTree = ""; }; + F48FE26E0D198CAD009257D2 /* UnitTest-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "UnitTest-Info.plist"; sourceTree = ""; }; F48FE27C0D198D0E009257D2 /* GTMDelegatingTableColumn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDelegatingTableColumn.h; sourceTree = ""; }; F48FE27D0D198D0E009257D2 /* GTMDelegatingTableColumn.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDelegatingTableColumn.m; sourceTree = ""; }; F48FE27E0D198D0E009257D2 /* GTMGeometryUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMGeometryUtils.h; sourceTree = ""; }; @@ -347,6 +407,11 @@ F48FE29C0D198D36009257D2 /* GTMNSObject+UnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSObject+UnitTesting.m"; sourceTree = ""; }; F48FE29F0D198D36009257D2 /* GTMSenTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMSenTestCase.h; sourceTree = ""; }; F48FE2E10D198E4C009257D2 /* GTMSystemVersionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSystemVersionTest.m; sourceTree = ""; }; + F4BC1C860DDDD45D00108B7D /* GTMHTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMHTTPServer.h; sourceTree = ""; }; + F4BC1C870DDDD45D00108B7D /* GTMHTTPServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPServer.m; sourceTree = ""; }; + F4BC1E8C0DE1FC4A00108B7D /* GTMHTTPServerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMHTTPServerTest.m; sourceTree = ""; }; + F4BC22CF0DE4C39000108B7D /* GTMTestHTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMTestHTTPServer.h; sourceTree = ""; }; + F4BC22D00DE4C39000108B7D /* GTMTestHTTPServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMTestHTTPServer.m; sourceTree = ""; }; F4C978090D5B79C7001C29A6 /* ReleaseNotes.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ReleaseNotes.txt; sourceTree = ""; }; F4CA854E0DAFAAB600B4AB10 /* xcconfigs-readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "xcconfigs-readme.txt"; sourceTree = ""; }; F4CA864A0DB3ACB200B4AB10 /* DebugLeopardOrLater.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DebugLeopardOrLater.xcconfig; sourceTree = ""; }; @@ -366,6 +431,7 @@ 8B45A0B80DA46A2F001148C5 /* SenTestingKit.framework in Frameworks */, 8B45A03A0DA46A2A001148C5 /* Foundation.framework in Frameworks */, 8B45A19A0DA46AAA001148C5 /* QuartzCore.framework in Frameworks */, + 8B7DCBA50DFF0EFF0017E983 /* GoogleToolboxForMac.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -385,6 +451,7 @@ F42E08800D199AB500D5DDE0 /* GoogleToolboxForMac.framework in Frameworks */, F42E089D0D199B1800D5DDE0 /* SenTestingKit.framework in Frameworks */, 8BC046B90DAE8C4B00C2D1CA /* ApplicationServices.framework in Frameworks */, + 8B33455E0DBF8844009FD32C /* Carbon.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -428,6 +495,7 @@ isa = PBXGroup; children = ( F4C978090D5B79C7001C29A6 /* ReleaseNotes.txt */, + F440EDB70DFECC4B0003E81F /* BuildingAndUsing.txt */, 8B1A16050D90344B00CA1E8E /* GTMDefines.h */, F48FE26F0D198CBA009257D2 /* AppKit */, F48FE2720D198CCE009257D2 /* Foundation */, @@ -502,7 +570,6 @@ isa = PBXGroup; children = ( F435E3B90DC8D1720069CDE8 /* GTMHTTPFetcherTestPage.html */, - F435E3BA0DC8D1720069CDE8 /* GTMHTTPFetcherTestServer */, ); path = TestData; sourceTree = ""; @@ -557,6 +624,16 @@ F48FE2720D198CCE009257D2 /* Foundation */ = { isa = PBXGroup; children = ( + 8B3344170DBF7A36009FD32C /* GTMNSAppleScript+HandlerTest.m */, + 8B3344180DBF7A36009FD32C /* GTMNSAppleScript+Handler.m */, + 8B3344190DBF7A36009FD32C /* GTMNSAppleScript+Handler.h */, + 8B3344200DBF7A36009FD32C /* GTMNSAppleEvent+HandlerTest.applescript */, + 8B33441A0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+HandlerTest.m */, + 8B33441B0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Handler.m */, + 8B33441C0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Handler.h */, + 8B33441D0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+FoundationTest.m */, + 8B33441E0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Foundation.m */, + 8B33441F0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+Foundation.h */, F41D25880DBD21A300774EEB /* GTMBase64.h */, F41D25890DBD21A300774EEB /* GTMBase64.m */, F41D258A0DBD21A300774EEB /* GTMBase64Test.m */, @@ -570,6 +647,9 @@ F435E0870DC63F6D0069CDE8 /* GTMHTTPFetcher.h */, F435E0880DC63F6D0069CDE8 /* GTMHTTPFetcher.m */, F435E3930DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m */, + F4BC1C860DDDD45D00108B7D /* GTMHTTPServer.h */, + F4BC1C870DDDD45D00108B7D /* GTMHTTPServer.m */, + F4BC1E8C0DE1FC4A00108B7D /* GTMHTTPServerTest.m */, 33C374360DD8D44800E97817 /* GTMNSDictionary+URLArguments.h */, 33C374370DD8D44800E97817 /* GTMNSDictionary+URLArguments.m */, 33C3745E0DD8D85B00E97817 /* GTMNSDictionary+URLArgumentsTest.m */, @@ -582,6 +662,9 @@ F48FE28E0D198D24009257D2 /* GTMNSString+HTML.h */, F48FE28F0D198D24009257D2 /* GTMNSString+HTML.m */, F48FE2900D198D24009257D2 /* GTMNSString+HTMLTest.m */, + 8BE2836B0DED0F130035B3F8 /* GTMFourCharCode.m */, + 8BE2836C0DED0F130035B3F8 /* GTMFourCharCodeTest.m */, + 8BE2836D0DED0F130035B3F8 /* GTMFourCharCode.h */, 33C372A40DD8A88500E97817 /* GTMNSString+URLArguments.h */, 33C372A50DD8A88500E97817 /* GTMNSString+URLArguments.m */, 33C372AE0DD8A8D700E97817 /* GTMNSString+URLArgumentsTest.m */, @@ -617,6 +700,10 @@ F48FE2770D198CEA009257D2 /* UnitTesting */ = { isa = PBXGroup; children = ( + 8B7DCBEC0DFF1A4F0017E983 /* GTMUnitTestDevLog.m */, + 8B7DCBF00DFF1A610017E983 /* GTMUnitTestDevLog.h */, + F4BC22CF0DE4C39000108B7D /* GTMTestHTTPServer.h */, + F4BC22D00DE4C39000108B7D /* GTMTestHTTPServer.m */, 8BC04D140DB0061300C2D1CA /* RunMacOSUnitTests.sh */, 8B55479A0DB3B7A50014CC1C /* GTMAppKit+UnitTesting.h */, 8B55479B0DB3B7A50014CC1C /* GTMAppKit+UnitTesting.m */, @@ -629,6 +716,7 @@ 8B45A2670DA498A0001148C5 /* GTMUnitTestingUtilities.h */, 8B45A2680DA498A0001148C5 /* GTMUnitTestingUtilities.m */, F48FE29F0D198D36009257D2 /* GTMSenTestCase.h */, + 8B7DCE180DFF39850017E983 /* GTMSenTestCase.m */, 8B45A2E00DA51ABC001148C5 /* GTMUnitTestingTest.h */, 8B45A2E10DA51ABC001148C5 /* GTMUnitTestingTest.m */, 8B7AD4AD0DABBFEE00B84F4A /* GTMUnitTestingBindingTest.m */, @@ -667,6 +755,7 @@ F4FF22760D9D47FB003880AC /* DebugUtils */ = { isa = PBXGroup; children = ( + 8B7DCBE10DFF18720017E983 /* GTMDevLog.m */, F4FF22770D9D4835003880AC /* GTMDebugSelectorValidation.h */, 8B6F31EF0DA347720052CA40 /* GTMMethodCheck.m */, 8B6F31F40DA3489B0052CA40 /* GTMMethodCheck.h */, @@ -712,11 +801,47 @@ F431221D0DD4E3B800F45252 /* GTMStackTrace.h in Headers */, 33C372A60DD8A88500E97817 /* GTMNSString+URLArguments.h in Headers */, 33C374380DD8D44800E97817 /* GTMNSDictionary+URLArguments.h in Headers */, + F4BC1C880DDDD45D00108B7D /* GTMHTTPServer.h in Headers */, + 8BE281B00DEC7E930035B3F8 /* GTMNSAppleScript+Handler.h in Headers */, + 8BE281B10DEC7E930035B3F8 /* GTMNSAppleEventDescriptor+Handler.h in Headers */, + 8BE281B20DEC7E930035B3F8 /* GTMNSAppleEventDescriptor+Foundation.h in Headers */, + 8BE283730DED13AB0035B3F8 /* GTMFourCharCode.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ +/* Begin PBXLegacyTarget section */ + F41A6EE00E02DB4F00788A6C /* Build GTM All SDKs */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "GTM \"$(BuildAllSDKs)\" \"$(DEVELOPER_BIN_DIR)\" \"$(ACTION)\""; + buildConfigurationList = F41A6EED0E02DB6800788A6C /* Build configuration list for PBXLegacyTarget "Build GTM All SDKs" */; + buildPhases = ( + ); + buildToolPath = BuildScripts/BuildAllSDKs.sh; + buildWorkingDirectory = "$(SRCROOT)"; + dependencies = ( + ); + name = "Build GTM All SDKs"; + passBuildSettingsInEnvironment = 1; + productName = "Build GTM All SDKs"; + }; + F41A6EF80E02DCFC00788A6C /* All UnitTests All SDKs */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "\"All UnitTests\" \"$(BuildAllSDKs)\" \"$(DEVELOPER_BIN_DIR)\" \"$(ACTION)\""; + buildConfigurationList = F41A6F070E02DD1500788A6C /* Build configuration list for PBXLegacyTarget "All UnitTests All SDKs" */; + buildPhases = ( + ); + buildToolPath = BuildScripts/BuildAllSDKs.sh; + buildWorkingDirectory = "$(SRCROOT)"; + dependencies = ( + ); + name = "All UnitTests All SDKs"; + passBuildSettingsInEnvironment = 1; + productName = "All UnitTests All SDKs"; + }; +/* End PBXLegacyTarget section */ + /* Begin PBXNativeTarget section */ 8B45A0270DA4696C001148C5 /* UnitTest - UnitTesting */ = { isa = PBXNativeTarget; @@ -731,6 +856,7 @@ ); dependencies = ( 8B45A2D40DA51A0E001148C5 /* PBXTargetDependency */, + 8B7DCBA40DFF0EFB0017E983 /* PBXTargetDependency */, ); name = "UnitTest - UnitTesting"; productName = "UnitTest - UnitTesting"; @@ -758,6 +884,7 @@ isa = PBXNativeTarget; buildConfigurationList = F42E081E0D19987200D5DDE0 /* Build configuration list for PBXNativeTarget "UnitTest - Foundation" */; buildPhases = ( + 8B3345CC0DBF8A95009FD32C /* AppleScript */, F42E08140D19987200D5DDE0 /* Resources */, F42E08160D19987200D5DDE0 /* Sources */, F42E081C0D19987200D5DDE0 /* Frameworks */, @@ -767,7 +894,6 @@ ); dependencies = ( F42E08790D199AA600D5DDE0 /* PBXTargetDependency */, - F41D25520DB9068700774EEB /* PBXTargetDependency */, ); name = "UnitTest - Foundation"; productName = "UnitTest - AppKit"; @@ -818,7 +944,7 @@ 0867D690FE84028FC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { - BuildIndependentTargetsInParallel = NO; + BuildIndependentTargetsInParallel = YES; }; buildConfigurationList = 1DEB918108733D990010E9CD /* Build configuration list for PBXProject "GTM" */; compatibilityVersion = "Xcode 3.0"; @@ -834,6 +960,8 @@ F42E08110D19987200D5DDE0 /* UnitTest - Foundation */, 8B45A0270DA4696C001148C5 /* UnitTest - UnitTesting */, 8B45A2890DA49B99001148C5 /* UIUnitTestingHarness */, + F41A6EE00E02DB4F00788A6C /* Build GTM All SDKs */, + F41A6EF80E02DCFC00788A6C /* All UnitTests All SDKs */, ); }; /* End PBXProject section */ @@ -866,7 +994,6 @@ buildActionMask = 2147483647; files = ( F435E3BB0DC8D1980069CDE8 /* GTMHTTPFetcherTestPage.html in Resources */, - F435E3BC0DC8D1980069CDE8 /* GTMHTTPFetcherTestServer in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -947,8 +1074,10 @@ 8B45A22C0DA46E51001148C5 /* GTMSystemVersion.m in Sources */, 8B45A2E20DA51ABC001148C5 /* GTMUnitTestingTest.m in Sources */, 8B7AD4AE0DABBFEE00B84F4A /* GTMUnitTestingBindingTest.m in Sources */, - 8BC04CDF0DB004A100C2D1CA /* GTMMethodCheck.m in Sources */, 8B55479C0DB3B7A50014CC1C /* GTMAppKit+UnitTesting.m in Sources */, + 8B7DCBC30DFF0F7F0017E983 /* GTMMethodCheck.m in Sources */, + 8B7DCBEF0DFF1A4F0017E983 /* GTMUnitTestDevLog.m in Sources */, + 8B7DCE1B0DFF39850017E983 /* GTMSenTestCase.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -958,6 +1087,9 @@ files = ( 8B45A2AC0DA49C47001148C5 /* main.m in Sources */, 8B45A2D00DA51A01001148C5 /* GTMUnitTestingUtilities.m in Sources */, + 8B7DCBC40DFF0F800017E983 /* GTMMethodCheck.m in Sources */, + 8B7DCEF10E002C210017E983 /* GTMDevLog.m in Sources */, + 8B2C21B70E00885600B5ECB1 /* GTMObjC2Runtime.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -977,12 +1109,21 @@ 8B6F32080DA34A1B0052CA40 /* GTMObjC2RuntimeTest.m in Sources */, 8B6F32160DA34C830052CA40 /* GTMMethodCheckTest.m in Sources */, 8BC045C20DAE899100C2D1CA /* GTMGeometryUtilsTest.m in Sources */, - 8BC04CDE0DB004A000C2D1CA /* GTMMethodCheck.m in Sources */, F41D258F0DBD21B900774EEB /* GTMBase64Test.m in Sources */, - F435E3940DC8CAAF0069CDE8 /* GTMHTTPFetcherTest.m in Sources */, F431221F0DD4E3C900F45252 /* GTMStackTraceTest.m in Sources */, 33C372B40DD8A93000E97817 /* GTMNSString+URLArgumentsTest.m in Sources */, 33C3745F0DD8D85B00E97817 /* GTMNSDictionary+URLArgumentsTest.m in Sources */, + F4BC1E8D0DE1FC4A00108B7D /* GTMHTTPServerTest.m in Sources */, + F4BC22D10DE4C39000108B7D /* GTMTestHTTPServer.m in Sources */, + 8B3344210DBF7A36009FD32C /* GTMNSAppleScript+HandlerTest.m in Sources */, + 8B3344230DBF7A36009FD32C /* GTMNSAppleEventDescriptor+HandlerTest.m in Sources */, + 8B3344250DBF7A36009FD32C /* GTMNSAppleEventDescriptor+FoundationTest.m in Sources */, + 8BE2836F0DED0F130035B3F8 /* GTMFourCharCodeTest.m in Sources */, + 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 */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1008,13 +1149,19 @@ F41390900D75F63C00F72B31 /* GTMNSFileManager+Path.m in Sources */, 8B2A9B200D8270DA00599386 /* GTMNSWorkspace+ScreenSaver.m in Sources */, 8B45A21E0DA46E34001148C5 /* GTMObjC2Runtime.m in Sources */, - 8BC04CD70DB003CD00C2D1CA /* GTMMethodCheck.m in Sources */, F41D258C0DBD21A300774EEB /* GTMBase64.m in Sources */, F435E08A0DC63F6D0069CDE8 /* GTMHTTPFetcher.m in Sources */, F435E2800DC7B0630069CDE8 /* GTMProgressMonitorInputStream.m in Sources */, F431221C0DD4E3B800F45252 /* GTMStackTrace.c in Sources */, 33C372A70DD8A88500E97817 /* GTMNSString+URLArguments.m in Sources */, 33C374390DD8D44800E97817 /* GTMNSDictionary+URLArguments.m in Sources */, + F4BC1C890DDDD45D00108B7D /* GTMHTTPServer.m in Sources */, + 8B7DCB9B0DFF0E850017E983 /* GTMFourCharCode.m in Sources */, + 8B7DCBBD0DFF0F5D0017E983 /* GTMMethodCheck.m in Sources */, + 8B7DCBD20DFF16070017E983 /* GTMNSAppleScript+Handler.m in Sources */, + 8B7DCBD30DFF16070017E983 /* GTMNSAppleEventDescriptor+Handler.m in Sources */, + 8B7DCBD40DFF16070017E983 /* GTMNSAppleEventDescriptor+Foundation.m in Sources */, + 8B7DCBE20DFF18720017E983 /* GTMDevLog.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1029,8 +1176,11 @@ F47F1C1B0D490BD200925B8F /* GTMNSBezierPath+ShadingTest.m in Sources */, F43E447F0D4918BC0041161F /* GTMLinearRGBShadingTest.m in Sources */, 8B2A9B240D8270DA00599386 /* GTMNSWorkspace+ScreenSaverTest.m in Sources */, - 8BC04CDD0DB004A000C2D1CA /* GTMMethodCheck.m in Sources */, 8B5547B90DB3BB220014CC1C /* GTMAppKit+UnitTesting.m in Sources */, + 8B7DCBC10DFF0F7F0017E983 /* GTMMethodCheck.m in Sources */, + 8B7DCBED0DFF1A4F0017E983 /* GTMUnitTestDevLog.m in Sources */, + 8B7DCE190DFF39850017E983 /* GTMSenTestCase.m in Sources */, + 8B2C21B60E00884000B5ECB1 /* GTMObjC2Runtime.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1047,15 +1197,15 @@ target = 8B45A2890DA49B99001148C5 /* UIUnitTestingHarness */; targetProxy = 8B45A2D30DA51A0E001148C5 /* PBXContainerItemProxy */; }; - F41D254F0DB9067C00774EEB /* PBXTargetDependency */ = { + 8B7DCBA40DFF0EFB0017E983 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 8B45A0270DA4696C001148C5 /* UnitTest - UnitTesting */; - targetProxy = F41D254E0DB9067C00774EEB /* PBXContainerItemProxy */; + target = F42E086C0D199A5B00D5DDE0 /* GTM */; + targetProxy = 8B7DCBA30DFF0EFB0017E983 /* PBXContainerItemProxy */; }; - F41D25520DB9068700774EEB /* PBXTargetDependency */ = { + F41D254F0DB9067C00774EEB /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 8B45A0270DA4696C001148C5 /* UnitTest - UnitTesting */; - targetProxy = F41D25510DB9068700774EEB /* PBXContainerItemProxy */; + targetProxy = F41D254E0DB9067C00774EEB /* PBXContainerItemProxy */; }; F42E08770D199A9B00D5DDE0 /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -1175,6 +1325,90 @@ }; name = "TigerOrLater-Release"; }; + F41A6EE10E02DB4F00788A6C /* TigerOrLater-Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "Build GTM All SDKs"; + }; + name = "TigerOrLater-Debug"; + }; + F41A6EE20E02DB4F00788A6C /* TigerOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "Build GTM All SDKs"; + }; + name = "TigerOrLater-Debug-gcov"; + }; + F41A6EE30E02DB4F00788A6C /* TigerOrLater-Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "Build GTM All SDKs"; + }; + name = "TigerOrLater-Release"; + }; + F41A6EE40E02DB4F00788A6C /* LeopardOrLater-Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "Build GTM All SDKs"; + }; + name = "LeopardOrLater-Debug"; + }; + F41A6EE50E02DB4F00788A6C /* LeopardOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "Build GTM All SDKs"; + }; + name = "LeopardOrLater-Debug-gcov"; + }; + F41A6EE60E02DB4F00788A6C /* LeopardOrLater-Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "Build GTM All SDKs"; + }; + name = "LeopardOrLater-Release"; + }; + F41A6EF90E02DCFC00788A6C /* TigerOrLater-Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "All UnitTests All SDKs"; + }; + name = "TigerOrLater-Debug"; + }; + F41A6EFA0E02DCFC00788A6C /* TigerOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "All UnitTests All SDKs"; + }; + name = "TigerOrLater-Debug-gcov"; + }; + F41A6EFB0E02DCFC00788A6C /* TigerOrLater-Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "All UnitTests All SDKs"; + }; + name = "TigerOrLater-Release"; + }; + F41A6EFC0E02DCFC00788A6C /* LeopardOrLater-Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "All UnitTests All SDKs"; + }; + name = "LeopardOrLater-Debug"; + }; + F41A6EFD0E02DCFC00788A6C /* LeopardOrLater-Debug-gcov */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "All UnitTests All SDKs"; + }; + name = "LeopardOrLater-Debug-gcov"; + }; + F41A6EFE0E02DCFC00788A6C /* LeopardOrLater-Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "All UnitTests All SDKs"; + }; + name = "LeopardOrLater-Release"; + }; F42E081F0D19987200D5DDE0 /* TigerOrLater-Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = F48FE2420D197F9A009257D2 /* DebugUnittest.xcconfig */; @@ -1183,6 +1417,7 @@ "$(value)", "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", ); + GTM_NO_DEBUG_FRAMEWORKS = YES; INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - Foundation"; }; @@ -1196,6 +1431,7 @@ "$(value)", "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", ); + GTM_NO_DEBUG_FRAMEWORKS = YES; INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - Foundation"; }; @@ -1298,6 +1534,7 @@ "$(value)", "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", ); + GTM_NO_DEBUG_FRAMEWORKS = YES; INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - Foundation"; }; @@ -1379,6 +1616,7 @@ "$(value)", "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", ); + GTM_NO_DEBUG_FRAMEWORKS = YES; INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - Foundation"; }; @@ -1459,6 +1697,7 @@ "$(value)", "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", ); + GTM_NO_DEBUG_FRAMEWORKS = YES; INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - Foundation"; }; @@ -1536,6 +1775,7 @@ "$(value)", "$(DEVELOPER_FRAMEWORKS_DIR_QUOTED)", ); + GTM_NO_DEBUG_FRAMEWORKS = YES; INFOPLIST_FILE = "UnitTest-Info.plist"; PRODUCT_NAME = "UnitTest - Foundation"; }; @@ -1605,6 +1845,32 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = "TigerOrLater-Release"; }; + F41A6EED0E02DB6800788A6C /* Build configuration list for PBXLegacyTarget "Build GTM All SDKs" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F41A6EE10E02DB4F00788A6C /* TigerOrLater-Debug */, + F41A6EE20E02DB4F00788A6C /* TigerOrLater-Debug-gcov */, + F41A6EE30E02DB4F00788A6C /* TigerOrLater-Release */, + F41A6EE40E02DB4F00788A6C /* LeopardOrLater-Debug */, + F41A6EE50E02DB4F00788A6C /* LeopardOrLater-Debug-gcov */, + F41A6EE60E02DB4F00788A6C /* LeopardOrLater-Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "TigerOrLater-Release"; + }; + F41A6F070E02DD1500788A6C /* Build configuration list for PBXLegacyTarget "All UnitTests All SDKs" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F41A6EF90E02DCFC00788A6C /* TigerOrLater-Debug */, + F41A6EFA0E02DCFC00788A6C /* TigerOrLater-Debug-gcov */, + F41A6EFB0E02DCFC00788A6C /* TigerOrLater-Release */, + F41A6EFC0E02DCFC00788A6C /* LeopardOrLater-Debug */, + F41A6EFD0E02DCFC00788A6C /* LeopardOrLater-Debug-gcov */, + F41A6EFE0E02DCFC00788A6C /* LeopardOrLater-Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = "TigerOrLater-Release"; + }; F42E081E0D19987200D5DDE0 /* Build configuration list for PBXNativeTarget "UnitTest - Foundation" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/GTMDefines.h b/GTMDefines.h index 8bc1385..32ae642 100644 --- a/GTMDefines.h +++ b/GTMDefines.h @@ -64,6 +64,14 @@ #define _GTMDevLog(...) do { } while (0) #endif +#endif // _GTMDevLog + +// Declared here so that it can easily be used for logging tracking if +// necessary. See GTMUnitTestDevLog.h for details. +@class NSString; +extern void _GTMUnittestDevLog(NSString *format, ...); + +#ifndef _GTMDevAssert // we directly invoke the NSAssert handler so we can pass on the varargs // (NSAssert doesn't have a macro we can use that takes varargs) #if !defined(NS_BLOCK_ASSERTIONS) @@ -81,7 +89,27 @@ #define _GTMDevAssert(condition, ...) do { } while (0) #endif // !defined(NS_BLOCK_ASSERTIONS) -#endif // _GTMDevLog +#endif // _GTMDevAssert + +// _GTMCompileAssert +// _GTMCompileAssert is an assert that is meant to fire at compile time if you +// want to check things at compile instead of runtime. For example if you +// want to check that a wchar is 4 bytes instead of 2 you would use +// _GTMCompileAssert(sizeof(wchar_t) == 4, wchar_t_is_4_bytes_on_OS_X) +// Note that the second "arg" is not in quotes, and must be a valid processor +// symbol in it's own right (no spaces, punctuation etc). + +// Wrapping this in an #ifndef allows external groups to define their own +// compile time assert scheme. +#ifndef _GTMCompileAssert +// We got this technique from here: +// http://unixjunkie.blogspot.com/2007/10/better-compile-time-asserts_29.html + +#define _GTMCompileAssertSymbolInner(line, msg) _GTMCOMPILEASSERT ## line ## __ ## msg +#define _GTMCompileAssertSymbol(line, msg) _GTMCompileAssertSymbolInner(line, msg) +#define _GTMCompileAssert(test, msg) \ + typedef char _GTMCompileAssertSymbol(__LINE__, msg) [ ((test) ? 1 : -1) ] +#endif // _GTMCompileAssert // ============================================================================ diff --git a/GTM_Prefix.pch b/GTM_Prefix.pch index c745642..6740ffb 100644 --- a/GTM_Prefix.pch +++ b/GTM_Prefix.pch @@ -14,6 +14,23 @@ // the License. // +// We don't require a prefix to use any GTM code, so this prefix is just to +// speed up the basic compiles of *all* of the GTM projects/targets. + +// We don't want to drag GTMDefines into the prefix so we make sure each file +// that directly needs it includes it, so we just use the iphone test directly +// instead of our GTM_IPHONE_SDK symbol. #ifdef __OBJC__ + #include + #if TARGET_OS_IPHONE // iPhone SDK + #import + #import + #else #import + #endif + // This turns on unit test logging so that we can track unittests if we are + // doing them. See GTMUnitTestDevLog.h for details. + #define _GTMDevLog _GTMUnittestDevLog #endif + + diff --git a/GTMiPhone-Info.plist b/GTMiPhone-Info.plist index cd3c096..6898224 100644 --- a/GTMiPhone-Info.plist +++ b/GTMiPhone-Info.plist @@ -8,10 +8,8 @@ ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} - CFBundleIconFile - CFBundleIdentifier - com.yourcompany.${PRODUCT_NAME:identifier} + com.google.GTMiPhone CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/GTMiPhone.xcodeproj/project.pbxproj b/GTMiPhone.xcodeproj/project.pbxproj index 0baf1b8..b0f39ed 100644 --- a/GTMiPhone.xcodeproj/project.pbxproj +++ b/GTMiPhone.xcodeproj/project.pbxproj @@ -24,9 +24,12 @@ 1D3623EC0D0F72F000981E51 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */; }; 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 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 */; }; 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 */; }; + 8B7DCEAD0DFF4CA60017E983 /* GTMUnitTestDevLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCEAC0DFF4CA60017E983 /* GTMUnitTestDevLog.m */; }; 8BC0480F0DAE928A00C2D1CA /* GTMCalculatedRange.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047780DAE928A00C2D1CA /* GTMCalculatedRange.m */; }; 8BC048100DAE928A00C2D1CA /* GTMCalculatedRangeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047790DAE928A00C2D1CA /* GTMCalculatedRangeTest.m */; }; 8BC048130DAE928A00C2D1CA /* GTMNSData+zlib.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC0477F0DAE928A00C2D1CA /* GTMNSData+zlib.m */; }; @@ -64,7 +67,7 @@ isa = PBXContainerItemProxy; containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; proxyType = 1; - remoteGlobalIDString = 1D6058900D05DD3D006BFB54 /* GTMiPhoneUnitTesting */; + remoteGlobalIDString = 1D6058900D05DD3D006BFB54; remoteInfo = GTMiPhoneUnitTesting; }; /* End PBXContainerItemProxy section */ @@ -74,12 +77,17 @@ 1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 1D6058910D05DD3D006BFB54 /* GTMiPhoneTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GTMiPhoneTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - 32CA4F630368D1EE00C91783 /* GTMiPhone_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMiPhone_Prefix.pch; sourceTree = ""; }; + 32CA4F630368D1EE00C91783 /* GTM_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTM_Prefix.pch; sourceTree = ""; }; + 67A7820A0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMIPhoneUnitTestDelegate.h; sourceTree = ""; }; + 67A7820B0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMIPhoneUnitTestDelegate.m; sourceTree = ""; }; 8B308AF40DAD070C00183556 /* RunIPhoneUnitTest.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = RunIPhoneUnitTest.sh; path = UnitTesting/RunIPhoneUnitTest.sh; sourceTree = ""; }; 8B308BCD0DAD0B8400183556 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 8B5547C70DB3BBF20014CC1C /* GTMUIKit+UnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMUIKit+UnitTesting.m"; sourceTree = ""; }; 8B5547C80DB3BBF20014CC1C /* GTMUIKit+UnitTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMUIKit+UnitTesting.h"; sourceTree = ""; }; 8B5547C90DB3BBF20014CC1C /* GTMUIKit+UnitTestingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMUIKit+UnitTestingTest.m"; sourceTree = ""; }; + 8B7DCEA90DFF4C760017E983 /* GTMDevLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDevLog.m; sourceTree = ""; }; + 8B7DCEAB0DFF4CA60017E983 /* GTMUnitTestDevLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMUnitTestDevLog.h; sourceTree = ""; }; + 8B7DCEAC0DFF4CA60017E983 /* GTMUnitTestDevLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMUnitTestDevLog.m; sourceTree = ""; }; 8BC047750DAE926E00C2D1CA /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDefines.h; sourceTree = ""; }; 8BC047770DAE928A00C2D1CA /* GTMCalculatedRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMCalculatedRange.h; sourceTree = ""; }; 8BC047780DAE928A00C2D1CA /* GTMCalculatedRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMCalculatedRange.m; sourceTree = ""; }; @@ -168,7 +176,7 @@ 8BC0479A0DAE928A00C2D1CA /* DebugUtils */, 8BC0479F0DAE928A00C2D1CA /* UnitTesting */, 8BC049840DAEC59100C2D1CA /* XcodeConfig */, - 32CA4F630368D1EE00C91783 /* GTMiPhone_Prefix.pch */, + 32CA4F630368D1EE00C91783 /* GTM_Prefix.pch */, 8B308AF40DAD070C00183556 /* RunIPhoneUnitTest.sh */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, @@ -232,6 +240,7 @@ isa = PBXGroup; children = ( 8BC0479B0DAE928A00C2D1CA /* GTMDebugSelectorValidation.h */, + 8B7DCEA90DFF4C760017E983 /* GTMDevLog.m */, 8BC0479C0DAE928A00C2D1CA /* GTMMethodCheck.h */, 8BC0479D0DAE928A00C2D1CA /* GTMMethodCheck.m */, 8BC0479E0DAE928A00C2D1CA /* GTMMethodCheckTest.m */, @@ -247,12 +256,16 @@ 8B5547C70DB3BBF20014CC1C /* GTMUIKit+UnitTesting.m */, 8B5547C80DB3BBF20014CC1C /* GTMUIKit+UnitTesting.h */, 8B5547C90DB3BBF20014CC1C /* GTMUIKit+UnitTestingTest.m */, + 67A7820A0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.h */, + 67A7820B0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m */, 8BC047DD0DAE928A00C2D1CA /* GTMIPhoneUnitTestMain.m */, 8BC047EC0DAE928A00C2D1CA /* GTMNSObject+UnitTesting.h */, 8BC047ED0DAE928A00C2D1CA /* GTMNSObject+UnitTesting.m */, 8BC047F60DAE928A00C2D1CA /* GTMSenTestCase.h */, 8BC047F70DAE928A00C2D1CA /* GTMSenTestCase.m */, 8BC048010DAE928A00C2D1CA /* GTMUIViewUnitTestingTest.png */, + 8B7DCEAB0DFF4CA60017E983 /* GTMUnitTestDevLog.h */, + 8B7DCEAC0DFF4CA60017E983 /* GTMUnitTestDevLog.m */, 8BC0480E0DAE928A00C2D1CA /* RunIPhoneUnitTest.sh */, F435E49F0DC8F5290069CDE8 /* TestData */, ); @@ -394,6 +407,9 @@ F439ADEC0DBD3C0000BE9B91 /* GTMBase64Test.m in Sources */, F439ADF00DBD3C4000BE9B91 /* GTMGeometryUtils.m in Sources */, F439ADF10DBD3C4000BE9B91 /* GTMGeometryUtilsTest.m in Sources */, + 8B7DCEAA0DFF4C760017E983 /* GTMDevLog.m in Sources */, + 8B7DCEAD0DFF4CA60017E983 /* GTMUnitTestDevLog.m in Sources */, + 67A7820C0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -429,7 +445,7 @@ baseConfigurationReference = 8BC049850DAEC59100C2D1CA /* DebugiPhone.xcconfig */; buildSettings = { "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - GCC_PREFIX_HEADER = GTMiPhone_Prefix.pch; + GCC_PREFIX_HEADER = GTM_Prefix.pch; }; name = "Debug-gcov"; }; @@ -446,7 +462,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 8BC049850DAEC59100C2D1CA /* DebugiPhone.xcconfig */; buildSettings = { - GCC_PREFIX_HEADER = GTMiPhone_Prefix.pch; + GCC_PREFIX_HEADER = GTM_Prefix.pch; }; name = Debug; }; @@ -454,7 +470,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 8BC049890DAEC59100C2D1CA /* ReleaseiPhone.xcconfig */; buildSettings = { - GCC_PREFIX_HEADER = GTMiPhone_Prefix.pch; + GCC_PREFIX_HEADER = GTM_Prefix.pch; }; name = Release; }; diff --git a/GTMiPhone_Prefix.pch b/GTMiPhone_Prefix.pch deleted file mode 100644 index 4c13635..0000000 --- a/GTMiPhone_Prefix.pch +++ /dev/null @@ -1,20 +0,0 @@ -// -// 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. -// - -#ifdef __OBJC__ - #import - #import -#endif diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index e6e565d..942f12d 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -101,11 +101,30 @@ Changes since 1.0.0 - GTMDelegatingTableColumn get an overhaul to match the 10.5 sdk so it's closer to a dropin for previous sdks. +- Added a lot of functionality to NSAppleEventDescriptor and NSAppleScript + allowing you to easily call labeled and positional handlers in an AppleScript, + get/set properties and get NSAppleEventDescriptors for most basic datatypes. + +- Added GTMFourCharCode for wrapping FourCharCodes in an ObjC object. Mainly for + use by the NSAppleEventDescriptor code, and also useful for storing them + in ObjC collection classes. + - Added GTMStackTrace. - Added NSString+URLArguments and NSDictionary+URLArguments +- Added GTMHTTPServer as a simple server but mainly for use in unittesting. + +- Added _GTMCompileAssert for doing compile time assertions to GTMDefines.h +- Added GTMUnitTestDevLog and GTMTestCase for logging and tracking logs while + running unittests to verify what is being logged is what you expect. All + unittests should now inherit from GTMTestCase instead of SenTestCase to take + advantage of the new log tracking. See GTMUnitTestDevLog.h for details. + +- Extracted GTMIPhoneUnitTestDelegate from GTMIPhoneUnitTestMain.m to its own + file. Tests can now be run from another application. + Release 1.0.0 14-January-2008 diff --git a/UnitTesting/GTMAppKit+UnitTesting.h b/UnitTesting/GTMAppKit+UnitTesting.h index 5db9ebb..016da7e 100644 --- a/UnitTesting/GTMAppKit+UnitTesting.h +++ b/UnitTesting/GTMAppKit+UnitTesting.h @@ -52,7 +52,7 @@ @protocol GTMUnitTestViewDrawer; // Fails when the |a1|'s drawing in an area |a2| does not equal the image file named |a3|. -// See the description of the GTMAssertViewRepEqualToFile macro +// See the description of the -gtm_pathForImageNamed method // to understand how |a3| is found and written out. // See the description of the GTMUnitTestView for a better idea // how the view works. @@ -70,15 +70,14 @@ // ...: A variable number of arguments to the format string. Can be absent. // - -#define GTMAssertDrawingEqualToFile(a1, a2, a3, a4, description, ...) \ +#define GTMAssertDrawingEqualToImageNamed(a1, a2, a3, a4, description, ...) \ do { \ - id a1Object = (a1); \ + id a1Drawer = (a1); \ NSSize a2Size = (a2); \ NSString* a3String = (a3); \ void *a4ContextInfo = (a4); \ NSRect frame = NSMakeRect(0, 0, a2Size.width, a2Size.height); \ - GTMUnitTestView *view = [[[GTMUnitTestView alloc] initWithFrame:frame drawer:a1Object contextInfo:a4ContextInfo] autorelease]; \ + GTMUnitTestView *view = [[[GTMUnitTestView alloc] initWithFrame:frame drawer:a1Drawer contextInfo:a4ContextInfo] autorelease]; \ GTMAssertObjectImageEqualToImageNamed(view, a3String, STComposeString(description, ##__VA_ARGS__)); \ } while(0) diff --git a/UnitTesting/GTMIPhoneUnitTestDelegate.h b/UnitTesting/GTMIPhoneUnitTestDelegate.h new file mode 100644 index 0000000..21ba84c --- /dev/null +++ b/UnitTesting/GTMIPhoneUnitTestDelegate.h @@ -0,0 +1,29 @@ +// +// GTMIPhoneUnitTestDelegate.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. +// + +// 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 +// gtm_runTests and clean up, restore data, etc. before the application +// terminates. +@interface GTMIPhoneUnitTestDelegate : NSObject +// Runs through all the registered classes and runs test methods on any +// that are subclasses of SenTestCase. Prints results and run time to +// the default output. +- (void)runTests; +@end diff --git a/UnitTesting/GTMIPhoneUnitTestDelegate.m b/UnitTesting/GTMIPhoneUnitTestDelegate.m new file mode 100644 index 0000000..28d2fe0 --- /dev/null +++ b/UnitTesting/GTMIPhoneUnitTestDelegate.m @@ -0,0 +1,160 @@ +// +// GTMIPhoneUnitTestDelegate.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 "GTMIPhoneUnitTestDelegate.h" + +#import "GTMDefines.h" +#if !GTM_IPHONE_SDK +#error GTMIPhoneUnitTestDelegate for iPhone only +#endif +#import +#import +#import +#import "GTMSenTestCase.h" + +// Used for sorting methods below +static int MethodSort(const void *a, const void *b) { + const char *nameA = sel_getName(method_getName(*(Method*)a)); + const char *nameB = sel_getName(method_getName(*(Method*)b)); + return strcmp(nameA, nameB); +} + +@interface UIApplication (iPhoneUnitTestAdditions) +// "Private" method that we need +- (void)terminate; +@end + +@implementation GTMIPhoneUnitTestDelegate + +// Return YES if class is subclass (1 or more generations) of SenTestCase +- (BOOL)isTestFixture:(Class)aClass { + BOOL iscase = NO; + Class testCaseClass = [SenTestCase class]; + Class superclass; + for (superclass = aClass; + !iscase && superclass; + superclass = class_getSuperclass(superclass)) { + iscase = superclass == testCaseClass ? YES : NO; + } + return iscase; +} + +// Run through all the registered classes and run test methods on any +// that are subclasses of SenTestCase. Terminate the application upon +// test completion. +- (void)applicationDidFinishLaunching:(UIApplication *)application { + [self runTests]; + // Using private call to end our tests + [[UIApplication sharedApplication] terminate]; +} + +// Run through all the registered classes and run test methods on any +// that are subclasses of SenTestCase. Print results and run time to +// the default output. +- (void)runTests { + int count = objc_getClassList(NULL, 0); + Class *classes = (Class*)malloc(sizeof(Class) * count); + _GTMDevAssert(classes, @"Couldn't allocate class list"); + objc_getClassList(classes, count); + int suiteSuccesses = 0; + int suiteFailures = 0; + int suiteTotal = 0; + NSString *suiteName = [[NSBundle mainBundle] bundlePath]; + NSDate *suiteStartDate = [NSDate date]; + NSString *suiteStartString = [NSString stringWithFormat:@"Test Suite '%@' started at %@\n", + suiteName, suiteStartDate]; + fputs([suiteStartString UTF8String], stderr); + fflush(stderr); + for (int i = 0; i < count; ++i) { + Class currClass = classes[i]; + if ([self isTestFixture:currClass]) { + NSDate *fixtureStartDate = [NSDate date]; + NSString *fixtureName = NSStringFromClass(currClass); + NSString *fixtureStartString = [NSString stringWithFormat:@"Test Suite '%@' started at %@\n", + fixtureName, fixtureStartDate]; + int fixtureSuccesses = 0; + int fixtureFailures = 0; + int fixtureTotal = 0; + fputs([fixtureStartString UTF8String], stderr); + fflush(stderr); + id testcase = [[currClass alloc] init]; + _GTMDevAssert(testcase, @"Unable to instantiate Test Suite: '%@'\n", + fixtureName); + unsigned int methodCount; + Method *methods = class_copyMethodList(currClass, &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); + const char *name = sel_getName(sel); + // If it starts with test, run it. + if (strstr(name, "test") == name) { + fixtureTotal += 1; + BOOL failed = NO; + NSDate *caseStartDate = [NSDate date]; + @try { + [testcase performTest:sel]; + } @catch (NSException *exception) { + failed = YES; + } + if (failed) { + fixtureFailures += 1; + } else { + fixtureSuccesses += 1; + } + NSTimeInterval caseEndTime = [[NSDate date] timeIntervalSinceDate:caseStartDate]; + NSString *caseEndString = [NSString stringWithFormat:@"Test Case '-[%@ %s]' %s (%0.3f seconds).\n", + fixtureName, name, + failed ? "failed" : "passed", caseEndTime]; + fputs([caseEndString UTF8String], stderr); + fflush(stderr); + } + } + if (methods) { + free(methods); + } + [testcase release]; + NSDate *fixtureEndDate = [NSDate date]; + NSTimeInterval fixtureEndTime = [fixtureEndDate timeIntervalSinceDate:fixtureStartDate]; + NSString *fixtureEndString = [NSString stringWithFormat:@"Test Suite '%@' finished at %@.\n" + "Executed %d tests, with %d failures (%d unexpected) in %0.3f (%0.3f) seconds\n", + fixtureName, fixtureEndDate, fixtureTotal, + fixtureFailures, fixtureFailures, + fixtureEndTime, fixtureEndTime]; + + fputs([fixtureEndString UTF8String], stderr); + fflush(stderr); + suiteTotal += fixtureTotal; + suiteSuccesses += fixtureSuccesses; + suiteFailures += fixtureFailures; + } + } + NSDate *suiteEndDate = [NSDate date]; + NSTimeInterval suiteEndTime = [suiteEndDate timeIntervalSinceDate:suiteStartDate]; + NSString *suiteEndString = [NSString stringWithFormat:@"Test Suite '%@' finished at %@.\n" + "Executed %d tests, with %d failures (%d unexpected) in %0.3f (%0.3f) seconds\n", + suiteName, suiteEndDate, suiteTotal, + suiteFailures, suiteFailures, + suiteEndTime, suiteEndTime]; + fputs([suiteEndString UTF8String], stderr); + fflush(stderr); +} + +@end diff --git a/UnitTesting/GTMIPhoneUnitTestMain.m b/UnitTesting/GTMIPhoneUnitTestMain.m index 981c48b..2a0a70d 100644 --- a/UnitTesting/GTMIPhoneUnitTestMain.m +++ b/UnitTesting/GTMIPhoneUnitTestMain.m @@ -20,166 +20,11 @@ #if !GTM_IPHONE_SDK #error GTMIPhoneUnitTestMain for iPhone only #endif -#import -#import #import -#import "GTMSenTestCase.h" - -// Used for sorting methods below -static int MethodSort(const void *a, const void *b) { - const char *nameA = sel_getName(method_getName(*(Method*)a)); - const char *nameB = sel_getName(method_getName(*(Method*)b)); - return strcmp(nameA, nameB); -} - -@interface UIApplication (iPhoneUnitTestAdditions) -// "Private" method that we need -- (void)terminate; -@end - -@interface GTMIPhoneUnitTestDelegate : NSObject -@end - -@implementation GTMIPhoneUnitTestDelegate - -// Return YES if class is subclass (1 or more generations) of SenTestCase -- (BOOL)isTestFixture:(Class)aClass { - BOOL iscase = NO; - Class testCaseClass = [SenTestCase class]; - Class superclass; - for (superclass = aClass; - !iscase && superclass; - superclass = class_getSuperclass(superclass)) { - iscase = superclass == testCaseClass ? YES : NO; - } - return iscase; -} - -// Log an error out to console in a way that Xcode will recognize it. -- (void)printError:(NSString *)error { - if ([error rangeOfString:@"error:"].location == NSNotFound) { - fprintf(stderr, "error: %s\n", [error UTF8String]); - } else { - fprintf(stderr, "%s\n", [error UTF8String]); - } - fflush(stderr); -} - -// Run through all the registered classes and run test methods on any -// that are subclasses of SenTestCase. -- (void)applicationDidFinishLaunching:(UIApplication *)application { - int count = objc_getClassList(NULL, 0); - Class *classes = (Class*)malloc(sizeof(Class) * count); - _GTMDevAssert(classes, @"Couldn't allocate class list"); - objc_getClassList(classes, count); - int suiteSuccesses = 0; - int suiteFailures = 0; - int suiteTotal = 0; - NSString *suiteName = [[NSBundle mainBundle] bundlePath]; - NSDate *suiteStartDate = [NSDate date]; - NSString *suiteStartString = [NSString stringWithFormat:@"Test Suite '%@' started at %@\n", - suiteName, suiteStartDate]; - fprintf(stderr, [suiteStartString UTF8String]); - fflush(stderr); - int i, j; - for (i = 0; i < count; ++i) { - Class currClass = classes[i]; - if ([self isTestFixture:currClass]) { - NSDate *fixtureStartDate = [NSDate date]; - NSString *fixtureName = NSStringFromClass(currClass); - NSString *fixtureStartString = [NSString stringWithFormat:@"Test Suite '%@' started at %@\n", - fixtureName, fixtureStartDate]; - int fixtureSuccesses = 0; - int fixtureFailures = 0; - int fixtureTotal = 0; - fprintf(stderr, [fixtureStartString UTF8String]); - fflush(stderr); - id testcase = [[currClass alloc] init]; - _GTMDevAssert(testcase, @"Unable to instantiate Test Suite: '%@'\n", - fixtureName); - unsigned int methodCount; - Method *methods = class_copyMethodList(currClass, &methodCount); - // Sort our methods so they are called in Alphabetical order just - // because we can. - qsort(methods, methodCount, sizeof(Method), MethodSort); - for (j = 0; j < methodCount; ++j) { - Method currMethod = methods[j]; - SEL sel = method_getName(currMethod); - const char *name = sel_getName(sel); - // If it starts with test, run it. - if (strstr(name, "test") == name) { - fixtureTotal += 1; - NSDate *caseStartDate = [NSDate date]; - BOOL failed = NO; - @try { - // Wrap things in autorelease pools because they may - // have an STMacro in their dealloc which may get called - // when the pool is cleaned up - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - @try { - [testcase setUp]; - @try { - [testcase performSelector:sel]; - } @catch (NSException *exception) { - failed = YES; - [self printError:[exception reason]]; - } - [testcase tearDown]; - } @catch (NSException *exception) { - failed = YES; - [self printError:[exception reason]]; - } - [pool release]; - } @catch (NSException *exception) { - failed = YES; - [self printError:[exception reason]]; - } - if (failed) { - fixtureFailures += 1; - } else { - fixtureSuccesses += 1; - } - NSTimeInterval caseEndTime = [[NSDate date] timeIntervalSinceDate:caseStartDate]; - NSString *caseEndString = [NSString stringWithFormat:@"Test Case '-[%@ %s]' %s (%0.3f seconds).\n", - fixtureName, name, - failed ? "failed" : "passed", caseEndTime]; - fprintf(stderr, [caseEndString UTF8String]); - fflush(stderr); - } - } - if (methods) { - free(methods); - } - [testcase release]; - NSDate *fixtureEndDate = [NSDate date]; - NSTimeInterval fixtureEndTime = [fixtureEndDate timeIntervalSinceDate:fixtureStartDate]; - NSString *fixtureEndString = [NSString stringWithFormat:@"Test Suite '%@' finished at %@.\n" - "Executed %d tests, with %d failures (%d unexpected) in %0.3f seconds\n", - fixtureName, fixtureEndDate, fixtureTotal, - fixtureFailures, fixtureFailures, fixtureEndTime]; - - fprintf(stderr, [fixtureEndString UTF8String]); - fflush(stderr); - suiteTotal += fixtureTotal; - suiteSuccesses += fixtureSuccesses; - suiteFailures += fixtureFailures; - } - } - NSDate *suiteEndDate = [NSDate date]; - NSTimeInterval suiteEndTime = [suiteEndDate timeIntervalSinceDate:suiteStartDate]; - NSString *suiteEndString = [NSString stringWithFormat:@"Test Suite '%@' finished at %@.\n" - "Executed %d tests, with %d failures (%d unexpected) in %0.3f seconds\n", - suiteName, suiteEndDate, suiteTotal, - suiteFailures, suiteFailures, suiteEndTime]; - fprintf(stderr, [suiteEndString UTF8String]); - fflush(stderr); - - // Using private call to end our tests - [[UIApplication sharedApplication] terminate]; -} - -@end +// Creates an application that runs all tests from classes extending +// SenTestCase, outputs results and test run time, and terminates right +// afterwards. int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, @"GTMIPhoneUnitTestDelegate"); diff --git a/UnitTesting/GTMNSObject+UnitTesting.h b/UnitTesting/GTMNSObject+UnitTesting.h index 7253fd4..68fab04 100644 --- a/UnitTesting/GTMNSObject+UnitTesting.h +++ b/UnitTesting/GTMNSObject+UnitTesting.h @@ -203,7 +203,7 @@ do { \ // Return the type of image to work with. Only valid types on the iPhone // are kUTTypeJPEG and kUTTypePNG. MacOS supports several more. -- (NSString*)gtm_imageUTI; +- (CFStringRef)gtm_imageUTI; // Return the extension to be used for saving unittest images // diff --git a/UnitTesting/GTMNSObject+UnitTesting.m b/UnitTesting/GTMNSObject+UnitTesting.m index 34aeb58..dd36cb5 100644 --- a/UnitTesting/GTMNSObject+UnitTesting.m +++ b/UnitTesting/GTMNSObject+UnitTesting.m @@ -353,7 +353,7 @@ static inline BOOL almostEqual(unsigned char a, unsigned char b) { length:(unsigned)lenv forKey:(NSString *)key { [self checkForKey:key]; - [dictionary_ setObject:[NSData dataWithBytes:(uint8_t*)bytesp + [dictionary_ setObject:[NSData dataWithBytes:bytesp length:lenv] forKey:key]; } @@ -502,7 +502,7 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; #endif // GTM_IPHONE_SDK extensions[1] = @""; - int i,j; + size_t i, j; // Note that we are searching for the most exact match first. for (i = 0; !thePath && i < sizeof(extensions) / sizeof(*extensions); @@ -525,9 +525,9 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; - (NSString *)gtm_saveToPathForFileNamed:(NSString*)name extension:(NSString*)extension { - char const *system; + char const *systemArchitecture; #if GTM_IPHONE_SDK - system = "iPhone"; + systemArchitecture = "iPhone"; #else // In reading arch(3) you'd thing this would work: // @@ -540,15 +540,15 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; // but on 64bit it returns the same things as on 32bit, so... #if __POWERPC__ #if __LP64__ - system = "ppc64"; + systemArchitecture = "ppc64"; #else // !__LP64__ - system = "ppc"; + systemArchitecture = "ppc"; #endif // __LP64__ #else // !__POWERPC__ #if __LP64__ - system = "x86_64"; + systemArchitecture = "x86_64"; #else // !__LP64__ - system = "i386"; + systemArchitecture = "i386"; #endif // __LP64__ #endif // !__POWERPC__ @@ -557,7 +557,7 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; [GTMSystemVersion getMajor:&major minor:&minor bugFix:&bugFix]; NSString *fullName = [NSString stringWithFormat:@"%@.%s.%d.%d.%d", - name, system, major, minor, bugFix]; + name, systemArchitecture, major, minor, bugFix]; NSString *basePath = [[self class] gtm_getUnitTestSaveToDirectory]; return [[basePath stringByAppendingPathComponent:fullName] @@ -622,13 +622,13 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; return YES; } -- (NSString *)gtm_imageUTI { +- (CFStringRef)gtm_imageUTI { #if GTM_IPHONE_SDK - return (NSString*)kUTTypePNG; + return kUTTypePNG; #else // Currently can't use PNG on Leopard. (10.5.2) // Radar:5844618 PNG importer/exporter in ImageIO is lossy - return (NSString*)kUTTypeTIFF; + return kUTTypeTIFF; #endif } @@ -637,18 +637,18 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; // Returns // An extension (e.g. "png") - (NSString*)gtm_imageExtension { - NSString *uti = [self gtm_imageUTI]; + CFStringRef uti = [self gtm_imageUTI]; #if GTM_IPHONE_SDK - if ([uti isEqualToString:(NSString*)kUTTypePNG]) { + if (CFEqual(uti, kUTTypePNG)) { return @"png"; - } else if ([uti isEqualToString:(NSString*)kUTTypeJPEG]) { + } else if (CFEqual(uti, kUTTypeJPEG)) { return @"jpg"; } else { _GTMDevAssert(NO, @"Illegal UTI for iPhone"); } return nil; #else - CFStringRef extension = UTTypeCopyPreferredTagWithClass((CFStringRef)uti, + CFStringRef extension = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension); _GTMDevAssert(extension, @"No extension for uti: %@", uti); @@ -666,10 +666,10 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; #if GTM_IPHONE_SDK // iPhone support UIImage *uiImage = [UIImage imageWithCGImage:image]; - NSString *uti = [self gtm_imageUTI]; - if ([uti isEqualToString:(NSString*)kUTTypePNG]) { + CFStringRef uti = [self gtm_imageUTI]; + if (CFEqual(uti, kUTTypePNG)) { data = UIImagePNGRepresentation(uiImage); - } else if ([uti isEqualToString:(NSString*)kUTTypeJPEG]) { + } else if (CFEqual(uti, kUTTypeJPEG)) { data = UIImageJPEGRepresentation(uiImage, 1.0f); } else { _GTMDevAssert(NO, @"Illegal UTI for iPhone"); @@ -677,17 +677,17 @@ static NSString *gGTMUnitTestSaveToDirectory = nil; #else data = [NSMutableData data]; CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)data, - (CFStringRef)[self gtm_imageUTI], + [self gtm_imageUTI], 1, NULL); // LZW Compression for TIFF NSDictionary *tiffDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:NSTIFFCompressionLZW] - forKey:(NSString*)kCGImagePropertyTIFFCompression]; + forKey:(const NSString*)kCGImagePropertyTIFFCompression]; NSDictionary *destProps = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithFloat:1.0f], - (NSString*)kCGImageDestinationLossyCompressionQuality, + (const NSString*)kCGImageDestinationLossyCompressionQuality, tiffDict, - (NSString*)kCGImagePropertyTIFFDictionary, + (const NSString*)kCGImagePropertyTIFFDictionary, nil]; CGImageDestinationAddImage(dest, image, (CFDictionaryRef)destProps); CGImageDestinationFinalize(dest); diff --git a/UnitTesting/GTMSenTestCase.h b/UnitTesting/GTMSenTestCase.h index 758f6d4..5f7856c 100644 --- a/UnitTesting/GTMSenTestCase.h +++ b/UnitTesting/GTMSenTestCase.h @@ -130,7 +130,7 @@ do { \ #define STAssertNotNULL(a1, description, ...) \ do { \ @try {\ - char* a1value = (char*)(a1); \ + const void* a1value = (a1); \ if (a1value == NULL) { \ NSString *_expression = [NSString stringWithFormat:@"(%s) != NULL", #a1]; \ if (description) { \ @@ -159,7 +159,7 @@ do { \ #define STAssertNULL(a1, description, ...) \ do { \ @try {\ - char* a1value = (char*)(a1); \ + const void* a1value = (a1); \ if (a1value != NULL) { \ NSString *_expression = [NSString stringWithFormat:@"(%s) == NULL", #a1]; \ if (description) { \ @@ -983,12 +983,22 @@ do { \ - (void)failWithException:(NSException*)exception; @end -@interface SenTestCase : NSObject -- (void) setUp; -- (void) tearDown; +@interface SenTestCase : NSObject { + SEL currentSelector_; +} + +- (void)setUp; +- (void)invokeTest; +- (void)tearDown; +- (void)performTest:(SEL)sel; @end CF_EXPORT NSString * const SenTestFailureException; #endif // GTM_IPHONE_SDK +// All unittest cases in GTM should inherit from GTMTestCase. It makes sure +// to set up our logging system correctly to verify logging calls. +// See GTMUnitTestDevLog.h for details +@interface GTMTestCase : SenTestCase +@end diff --git a/UnitTesting/GTMSenTestCase.m b/UnitTesting/GTMSenTestCase.m index 9b6cf17..f8d7ff3 100644 --- a/UnitTesting/GTMSenTestCase.m +++ b/UnitTesting/GTMSenTestCase.m @@ -129,12 +129,83 @@ NSString *STComposeString(NSString *formatString, ...) { NSString * const SenTestFailureException = @"SenTestFailureException"; +@interface SenTestCase (SenTestCasePrivate) +// our method of logging errors +- (void)printError:(NSString *)error; +@end + @implementation SenTestCase -- (void) setUp { +- (void)setUp { } -- (void) tearDown { +- (void)performTest:(SEL)sel { + currentSelector_ = sel; + @try { + [self invokeTest]; + } @catch (NSException *exception) { + [self printError:[exception reason]]; + [exception raise]; + } +} + +- (void)printError:(NSString *)error { + if ([error rangeOfString:@"error:"].location == NSNotFound) { + fprintf(stderr, "error: %s\n", [error UTF8String]); + } else { + fprintf(stderr, "%s\n", [error UTF8String]); + } + fflush(stderr); +} + +- (void)invokeTest { + NSException *e = nil; + @try { + // Wrap things in autorelease pools because they may + // have an STMacro in their dealloc which may get called + // when the pool is cleaned up + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + @try { + [self setUp]; + @try { + [self performSelector:currentSelector_]; + } @catch (NSException *exception) { + e = [exception retain]; + [self printError:[exception reason]]; + } + [self tearDown]; + } @catch (NSException *exception) { + e = [exception retain]; + [self printError:[exception reason]]; + } + [pool release]; + } @catch (NSException *exception) { + e = [exception retain]; + [self printError:[exception reason]]; + } + if (e) { + [e autorelease]; + [e raise]; + } +} + +- (void)tearDown { } @end #endif + +@implementation GTMTestCase : SenTestCase +- (void) invokeTest { + Class devLogClass = NSClassFromString(@"GTMUnitTestDevLog"); + if (devLogClass) { + [devLogClass performSelector:@selector(enableTracking)]; + [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)]; + + } + [super invokeTest]; + if (devLogClass) { + [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)]; + [devLogClass performSelector:@selector(disableTracking)]; + } +} +@end diff --git a/UnitTesting/GTMTestHTTPServer.h b/UnitTesting/GTMTestHTTPServer.h new file mode 100644 index 0000000..0128718 --- /dev/null +++ b/UnitTesting/GTMTestHTTPServer.h @@ -0,0 +1,39 @@ +// +// GTMTestHTTPServer.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 + +@class GTMHTTPServer; + +// This is a HTTP Server that can respond to certain requests that look like +// Google service logins. It takes extra url arguments to tell it what to +// return for testing the code using it. See GTMHTTPFetcherTest for an example +// of its usage. +@interface GTMTestHTTPServer : NSObject { + NSString *docRoot_; + GTMHTTPServer *server_; +} + +// Any url that isn't a specific server request (login, etc.), will be fetched +// off |docRoot| (to allow canned repsonses). +- (id)initWithDocRoot:(NSString *)docRoot; + +// fetch the port the server is running on +- (uint16_t)port; + +@end diff --git a/UnitTesting/GTMTestHTTPServer.m b/UnitTesting/GTMTestHTTPServer.m new file mode 100644 index 0000000..7744b5e --- /dev/null +++ b/UnitTesting/GTMTestHTTPServer.m @@ -0,0 +1,166 @@ +// +// GTMTestHTTPServer.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 "GTMTestHTTPServer.h" +#import "GTMHTTPServer.h" +#import "GTMRegex.h" + +static NSArray *GetSubPatternsOfFirstStringMatchedByPattern(NSString *stringToSearch, + NSString *pattern) { + GTMRegex *regex = [GTMRegex regexWithPattern:pattern]; + NSString *firstMatch = [regex firstSubStringMatchedInString:stringToSearch]; + NSArray *subPatterns = [regex subPatternsOfString:firstMatch]; + return subPatterns; +} + +@implementation GTMTestHTTPServer + +- (id)initWithDocRoot:(NSString *)docRoot { + self = [super init]; + if (self) { + docRoot_ = [docRoot copy]; + server_ = [[GTMHTTPServer alloc] initWithDelegate:self]; + NSError *error = nil; + if ((docRoot == nil) || (![server_ start:&error])) { + _GTMDevLog(@"Failed to start up the webserver (docRoot='%@', error=%@)", + docRoot_, error); + [self release]; + return nil; + } + } + return self; +} + +- (void)dealloc { + [docRoot_ release]; + [server_ release]; + [super dealloc]; +} + +- (uint16_t)port { + return [server_ port]; +} + +- (GTMHTTPResponseMessage *)httpServer:(GTMHTTPServer *)server + handleRequest:(GTMHTTPRequestMessage *)request { + _GTMDevAssert(server == server_, @"how'd we get a different server?!"); + UInt32 resultStatus = 0; + NSData *data = nil; + // clients should treat dates as opaque, generally + NSString *modifiedDate = @"thursday"; + + NSString *postString = @""; + NSData *postData = [request body]; + if ([postData length] > 0) { + postString = [[[NSString alloc] initWithData:postData + encoding:NSUTF8StringEncoding] autorelease]; + } + + NSDictionary *allHeaders = [request allHeaderFieldValues]; + NSString *ifModifiedSince = [allHeaders objectForKey:@"If-Modified-Since"]; + NSString *authorization = [allHeaders objectForKey:@"Authorization"]; + NSString *path = [[request URL] absoluteString]; + + if ([path hasSuffix:@".auth"]) { + if (![authorization isEqualToString:@"GoogleLogin auth=GoodAuthToken"]) { + GTMHTTPResponseMessage *response = + [GTMHTTPResponseMessage emptyResponseWithCode:401]; + return response; + } else { + path = [path substringToIndex:[path length] - 5]; + } + } + + NSString *overrideHeader = [allHeaders objectForKey:@"X-HTTP-Method-Override"]; + NSString *httpCommand = [request method]; + if ([httpCommand isEqualToString:@"POST"] && + [overrideHeader length] > 1) { + httpCommand = overrideHeader; + } + NSArray *searchResult = nil; + if ([path hasSuffix:@"/accounts/ClientLogin"]) { + // it's a sign-in attempt; it's good unless the password is "bad" or + // "captcha" + + // use regular expression to find the password + NSString *password = @""; + searchResult = GetSubPatternsOfFirstStringMatchedByPattern(path, @"Passwd=([^&\n]*)"); + if ([searchResult count] == 2) { + password = [searchResult objectAtIndex:1]; + } + + if ([password isEqualToString:@"bad"]) { + resultStatus = 403; + } else if ([password isEqualToString:@"captcha"]) { + NSString *loginToken = @""; + NSString *loginCaptcha = @""; + + searchResult = GetSubPatternsOfFirstStringMatchedByPattern(postString, @"logintoken=([^&\n]*)"); + if ([searchResult count] == 2) { + loginToken = [searchResult objectAtIndex:1]; + } + + searchResult = GetSubPatternsOfFirstStringMatchedByPattern(postString, @"logincaptcha=([^&\n]*)"); + if ([searchResult count] == 2) { + loginCaptcha = [searchResult objectAtIndex:1]; + } + + if ([loginToken isEqualToString:@"CapToken"] && + [loginCaptcha isEqualToString:@"good"]) { + resultStatus = 200; + } else { + // incorrect captcha token or answer provided + resultStatus = 403; + } + } else { + // valid username/password + resultStatus = 200; + } + } else if ([httpCommand isEqualToString:@"DELETE"]) { + // it's an object delete; read and return empty data + resultStatus = 200; + } else { + // queries that have something like "?status=456" should fail with the + // status code + searchResult = GetSubPatternsOfFirstStringMatchedByPattern(path, @"status=([0-9]+)"); + if ([searchResult count] == 2) { + resultStatus = [[searchResult objectAtIndex:1] intValue]; + } else if ([ifModifiedSince isEqualToString:modifiedDate]) { + resultStatus = 304; + } else { + NSString *docPath = [docRoot_ stringByAppendingPathComponent:path]; + data = [NSData dataWithContentsOfFile:docPath]; + if (data) { + resultStatus = 200; + } else { + resultStatus = 404; + } + } + } + + GTMHTTPResponseMessage *response = + [GTMHTTPResponseMessage responseWithBody:data + contentType:@"text/plain" + statusCode:resultStatus]; + [response setValue:modifiedDate forHeaderField:@"Last-Modified"]; + [response setValue:[NSString stringWithFormat:@"TestCookie=%@", [path lastPathComponent]] + forHeaderField:@"Set-Cookie"]; + return response; +} + +@end diff --git a/UnitTesting/GTMUIKit+UnitTesting.h b/UnitTesting/GTMUIKit+UnitTesting.h index 3c231cf..578ee4d 100644 --- a/UnitTesting/GTMUIKit+UnitTesting.h +++ b/UnitTesting/GTMUIKit+UnitTesting.h @@ -50,12 +50,12 @@ #define GTMAssertDrawingEqualToFile(a1, a2, a3, a4, description, ...) \ do { \ - id a1Object = (a1); \ + id a1Drawer = (a1); \ CGSize a2Size = (a2); \ NSString* a3String = (a3); \ void *a4ContextInfo = (a4); \ CGRect frame = CGRectMake(0, 0, a2Size.width, a2Size.height); \ - GTMUnitTestView *view = [[[GTMUnitTestView alloc] initWithFrame:frame drawer:a1Object contextInfo:a4ContextInfo] autorelease]; \ + GTMUnitTestView *view = [[[GTMUnitTestView alloc] initWithFrame:frame drawer:a1Drawer contextInfo:a4ContextInfo] autorelease]; \ GTMAssertObjectImageEqualToImageNamed(view, a3String, STComposeString(description, ##__VA_ARGS__)); \ } while(0) diff --git a/UnitTesting/GTMUnitTestDevLog.h b/UnitTesting/GTMUnitTestDevLog.h new file mode 100644 index 0000000..55ec44c --- /dev/null +++ b/UnitTesting/GTMUnitTestDevLog.h @@ -0,0 +1,70 @@ +// +// GTMUnitTestDevLog.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 "GTMDefines.h" +#import + +// GTMUnitTestDevLog tracks what messages are logged to verify that you only +// log what you expect to log during the running of unittests. This allows you +// to log with impunity from your actual core implementations and still be able +// to find unexpected logs in your output when running unittests. +// In your unittests you tell GTMUnitTestDevLog what messages you expect your +// test to spit out, and it will cause any that don't match to appear as errors +// in your unittest run output. You can match on exact strings or standard +// regexps. + +@interface GTMUnitTestDevLog : NSObject +// Log a message ++ (void)log:(NSString*)format, ...; ++ (void)log:(NSString*)format args:(va_list)args; + +// Turn tracking on/off ++ (void)enableTracking; ++ (void)disableTracking; ++ (BOOL)isTrackingEnabled; + +// Note that you are expecting a string that has an exact match. No need to +// escape any pattern characters. ++ (void)expectString:(NSString *)format, ...; + +// Note that you are expecting a pattern. Pattern characters that you want +// exact matches on must be escaped. See [GTMRegex escapedPatternForString]. +// Patterns match across newlines (kGTMRegexOptionSupressNewlineSupport) making +// it easier to match output from the descriptions of NS collection types such +// as NSArray and NSDictionary. ++ (void)expectPattern:(NSString *)format, ...; + +// Note that you are expecting exactly 'n' strings ++ (void)expect:(NSUInteger)n casesOfString:(NSString *)format, ...; + +// Note that you are expecting exactly 'n' patterns ++ (void)expect:(NSUInteger)n casesOfPattern:(NSString*)format, ...; ++ (void)expect:(NSUInteger)n casesOfPattern:(NSString*)format args:(va_list)args; + +// Call when you want to verify that you have matched all the logs you expect +// to match. If your unittests inherit from GTMTestcase (like they should) you +// will get this called for free. ++ (void)verifyNoMoreLogsExpected; + +// Resets the expected logs so that you don't have anything expected. +// In general should not be needed, unless you have a variable logging case +// of some sort. ++ (void)resetExpectedLogs; +@end + + diff --git a/UnitTesting/GTMUnitTestDevLog.m b/UnitTesting/GTMUnitTestDevLog.m new file mode 100644 index 0000000..30ab13b --- /dev/null +++ b/UnitTesting/GTMUnitTestDevLog.m @@ -0,0 +1,154 @@ +// +// GTMUnitTestDevLog.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 "GTMUnitTestDevLog.h" +#import "GTMRegex.h" +#import "GTMSenTestCase.h" + +@implementation GTMUnitTestDevLog +// If unittests are ever being run on separate threads, this may need to be +// made a thread local variable. +static BOOL gTrackingEnabled = NO; + ++ (NSMutableArray *)patterns { + static NSMutableArray *patterns = nil; + if (!patterns) { + patterns = [[NSMutableArray array] retain]; + } + return patterns; +} + ++ (BOOL)isTrackingEnabled { + return gTrackingEnabled; +} + ++ (void)enableTracking { + gTrackingEnabled = YES; +} + ++ (void)disableTracking { + gTrackingEnabled = NO; +} + ++ (void)log:(NSString*)format, ... { + va_list argList; + va_start(argList, format); + [self log:format args:argList]; + va_end(argList); +} + ++ (void)log:(NSString*)format args:(va_list)args { + if ([self isTrackingEnabled]) { + NSString *logString = [[[NSString alloc] initWithFormat:format + arguments:args] autorelease]; + @synchronized(self) { + NSMutableArray *patterns = [self patterns]; + BOOL logError = [patterns count] == 0 ? YES : NO; + GTMRegex *regex = nil; + if (!logError) { + regex = [[[patterns objectAtIndex:0] retain] autorelease]; + logError = [regex matchesString:logString] ? NO : YES; + [patterns removeObjectAtIndex:0]; + } + if (logError) { + if (regex) { + [NSException raise:SenTestFailureException + format:@"Unexpected log: %@\nExpected: %@", + logString, regex]; + } else { + [NSException raise:SenTestFailureException + format:@"Unexpected log: %@", logString]; + } + } else { + NSLog(@"Expected Log: %@", logString); + } + } + } else { + NSLogv(format, args); + } +} + ++ (void)expectString:(NSString *)format, ... { + va_list argList; + va_start(argList, format); + NSString *string = [[[NSString alloc] initWithFormat:format + arguments:argList] autorelease]; + va_end(argList); + NSString *pattern = [GTMRegex escapedPatternForString:string]; + [self expect:1 casesOfPattern:pattern]; + +} + ++ (void)expectPattern:(NSString *)format, ... { + va_list argList; + va_start(argList, format); + [self expect:1 casesOfPattern:format args:argList]; + va_end(argList); +} + ++ (void)expect:(NSUInteger)n casesOfString:(NSString *)format, ... { + va_list argList; + va_start(argList, format); + NSString *string = [[[NSString alloc] initWithFormat:format + arguments:argList] autorelease]; + va_end(argList); + NSString *pattern = [GTMRegex escapedPatternForString:string]; + [self expect:n casesOfPattern:pattern]; +} + ++ (void)expect:(NSUInteger)n casesOfPattern:(NSString*)format, ... { + va_list argList; + va_start(argList, format); + [self expect:n casesOfPattern:format args:argList]; + va_end(argList); +} + ++ (void)expect:(NSUInteger)n +casesOfPattern:(NSString*)format + args:(va_list)args { + NSString *pattern = [[[NSString alloc] initWithFormat:format + arguments:args] autorelease]; + GTMRegex *regex = [GTMRegex regexWithPattern:pattern + options:kGTMRegexOptionSupressNewlineSupport]; + @synchronized(self) { + NSMutableArray *patterns = [self patterns]; + for (NSUInteger i = 0; i < n; ++i) { + [patterns addObject:regex]; + } + } +} + ++ (void)verifyNoMoreLogsExpected { + @synchronized(self) { + NSMutableArray *patterns = [self patterns]; + if ([patterns count] > 0) { + NSMutableArray *patternsCopy = [[patterns copy] autorelease]; + [self resetExpectedLogs]; + [NSException raise:SenTestFailureException + format:@"Logs still expected %@", patternsCopy]; + } + } +} + ++ (void)resetExpectedLogs { + @synchronized(self) { + NSMutableArray *patterns = [self patterns]; + [patterns removeAllObjects]; + } +} +@end diff --git a/UnitTesting/GTMUnitTestingBindingTest.m b/UnitTesting/GTMUnitTestingBindingTest.m index d27e8e7..78e48b1 100644 --- a/UnitTesting/GTMUnitTestingBindingTest.m +++ b/UnitTesting/GTMUnitTestingBindingTest.m @@ -16,11 +16,11 @@ // the License. // -#import +#import "GTMSenTestCase.h" #import "GTMUnitTestingTest.h" #import "GTMNSObject+BindingUnitTesting.h" -@interface GTMUnitTestingBindingTest : SenTestCase { +@interface GTMUnitTestingBindingTest : GTMTestCase { int expectedFailureCount_; } @end diff --git a/UnitTesting/GTMUnitTestingTest.m b/UnitTesting/GTMUnitTestingTest.m index cc28a1a..5d2a9f3 100644 --- a/UnitTesting/GTMUnitTestingTest.m +++ b/UnitTesting/GTMUnitTestingTest.m @@ -23,7 +23,7 @@ NSString *const kGTMWindowNibName = @"GTMUnitTestingTest"; NSString *const kGTMWindowSaveFileName = @"GTMUnitTestingWindow"; -@interface GTMUnitTestingTest : SenTestCase { +@interface GTMUnitTestingTest : GTMTestCase { int expectedFailureCount_; } @end @@ -84,11 +84,11 @@ NSString *const kGTMWindowSaveFileName = @"GTMUnitTestingWindow"; - (void)testViewUnitTesting { GTMUnitTestingView *unitTestingView = [[GTMUnitTestingView alloc] init]; - GTMAssertDrawingEqualToFile(unitTestingView, - NSMakeSize(200,200), - @"GTMUnitTestingView", - NSApp, - @"Testing view drawing"); + GTMAssertDrawingEqualToImageNamed(unitTestingView, + NSMakeSize(200,200), + @"GTMUnitTestingView", + NSApp, + @"Testing view drawing"); STAssertTrue([unitTestingView hadGoodContext], @"bad context?"); [unitTestingView release]; } @@ -216,7 +216,7 @@ NSString *const kGTMWindowSaveFileName = @"GTMUnitTestingWindow"; [inCoder encodeInt64:1 forKey:@"Int64Test"]; [inCoder encodeFloat:1.0f forKey:@"FloatTest"]; [inCoder encodeDouble:1.0 forKey:@"DoubleTest"]; - [inCoder encodeBytes:(uint8_t*)"BytesTest" length:9 forKey:@"BytesTest"]; + [inCoder encodeBytes:(const uint8_t*)"BytesTest" length:9 forKey:@"BytesTest"]; didEncode_ = YES; } diff --git a/UnitTesting/GTMUnitTestingUtilities.h b/UnitTesting/GTMUnitTestingUtilities.h index 17ec766..eca0825 100644 --- a/UnitTesting/GTMUnitTestingUtilities.h +++ b/UnitTesting/GTMUnitTestingUtilities.h @@ -16,6 +16,7 @@ // the License. // +#import #import // Collection of utilities for unit testing diff --git a/UnitTesting/GTMUnitTestingUtilities.m b/UnitTesting/GTMUnitTestingUtilities.m index a37b7dd..a20023f 100644 --- a/UnitTesting/GTMUnitTestingUtilities.m +++ b/UnitTesting/GTMUnitTestingUtilities.m @@ -126,7 +126,7 @@ static void RestoreColorProfile(void) { } } -void SetColorProfileToGenericRGB() { +void SetColorProfileToGenericRGB(void) { NSColorSpace *genericSpace = [NSColorSpace genericRGBColorSpace]; CMProfileRef genericProfile = (CMProfileRef)[genericSpace colorSyncProfile]; CMProfileRef previousProfile; diff --git a/XcodeConfig/Project/DebugLeopardOrLater.xcconfig b/XcodeConfig/Project/DebugLeopardOrLater.xcconfig index 7bc3257..6aebf12 100644 --- a/XcodeConfig/Project/DebugLeopardOrLater.xcconfig +++ b/XcodeConfig/Project/DebugLeopardOrLater.xcconfig @@ -23,7 +23,7 @@ // the License. // -// Pull in our general Google settings +// Pull in the general settings #include "../subconfig/General.xcconfig" // Leopard or later diff --git a/XcodeConfig/Project/DebugTigerOrLater.xcconfig b/XcodeConfig/Project/DebugTigerOrLater.xcconfig index 76d2e27..725ea49 100644 --- a/XcodeConfig/Project/DebugTigerOrLater.xcconfig +++ b/XcodeConfig/Project/DebugTigerOrLater.xcconfig @@ -23,7 +23,7 @@ // the License. // -// Pull in our general Google settings +// Pull in the general settings #include "../subconfig/General.xcconfig" // Tiger or later diff --git a/XcodeConfig/Project/DebugiPhone.xcconfig b/XcodeConfig/Project/DebugiPhone.xcconfig index 45c5ba5..1ba99b4 100644 --- a/XcodeConfig/Project/DebugiPhone.xcconfig +++ b/XcodeConfig/Project/DebugiPhone.xcconfig @@ -23,7 +23,7 @@ // and individual Xcode target, there are other configuration files for that // purpose. -// Pull in our general Google settings +// Pull in the general settings #include "../subconfig/General.xcconfig" // iPhone settings diff --git a/XcodeConfig/Project/ReleaseLeopardOrLater.xcconfig b/XcodeConfig/Project/ReleaseLeopardOrLater.xcconfig index 52f0383..0188d10 100644 --- a/XcodeConfig/Project/ReleaseLeopardOrLater.xcconfig +++ b/XcodeConfig/Project/ReleaseLeopardOrLater.xcconfig @@ -23,7 +23,7 @@ // the License. // -// Pull in our general Google settings +// Pull in the general settings #include "../subconfig/General.xcconfig" // Leopard or later diff --git a/XcodeConfig/Project/ReleaseTigerOrLater.xcconfig b/XcodeConfig/Project/ReleaseTigerOrLater.xcconfig index d14e739..a024c4b 100644 --- a/XcodeConfig/Project/ReleaseTigerOrLater.xcconfig +++ b/XcodeConfig/Project/ReleaseTigerOrLater.xcconfig @@ -23,7 +23,7 @@ // the License. // -// Pull in our general Google settings +// Pull in the general settings #include "../subconfig/General.xcconfig" // Tiger or later diff --git a/XcodeConfig/Project/ReleaseiPhone.xcconfig b/XcodeConfig/Project/ReleaseiPhone.xcconfig index e61d1b6..d6c0127 100644 --- a/XcodeConfig/Project/ReleaseiPhone.xcconfig +++ b/XcodeConfig/Project/ReleaseiPhone.xcconfig @@ -23,7 +23,7 @@ // and individual Xcode target, there are other configuration files for that // purpose. -// Pull in our general Google settings +// Pull in the general settings #include "../subconfig/General.xcconfig" // iPhone Settings. diff --git a/XcodeConfig/xcconfigs-readme.txt b/XcodeConfig/xcconfigs-readme.txt index 23fde4b..5a44bb0 100644 --- a/XcodeConfig/xcconfigs-readme.txt +++ b/XcodeConfig/xcconfigs-readme.txt @@ -1,12 +1,32 @@ +Xcode Config is sorta a black art, any time you have a set of rules, you +quickly hit a few exceptions. -Configs w/in Project folder are configs expected to be set at the project level. +The main goal of using these is as follow: -Configs w/in the Target folder are configs expected to be set on the per target -level. +Edit your Project level build settings by removing as much as possible, and +then set the per Configuration settings to one of the project xcode config +files w/in the Project subfolder here. This will batch setup the project to +build Debug/Release w/ a specific SDK. -Configs w/in subconfig are not meant to be used directly; they are referenced by -the other configs. +If you are building a Shared Library, Loadable Bundle (Framework) or UnitTest +you will need to apply a further Xcode Config file at the target level. You do +this again by clearing most of the settings on the target, and just setting the +build config for that target to be the match from the Target subfolder here. +To see an example of this, look at CoverStory +(http://code.google.com/p/coverstory) or Vidnik +(http://code.google.com/p/vidnik). -Remember: when using the configs at any given layer, make sure you set them for -each build configuration. + +The common exception...If you need to have a few targets build w/ different +SDKs, then you hit the most common of the exceptions. For these, you'd need +the top level config not to set some things, the simplest way to do this seems +to be to remove as many of the settings from the project file, and make new +wrapper xcconfig files that inclue both the project level and target level +setting and set them on the targets (yes, this is like the MetroWerks days +where you can quickly explode in a what seems like N^2 (or worse) number of +config files. With a little luck, future versions of Xcode might have some +support to make mixing SDKs easier. + +Remember: When using the configs at any given layer, make sure you set them for +each build configuration you need (not just the active one). -- cgit v1.2.3