diff options
author | 2008-06-13 19:21:50 +0000 | |
---|---|---|
committer | 2008-06-13 19:21:50 +0000 | |
commit | c53ec5520e39096e0804ce8d89a21378c0904481 (patch) | |
tree | d36a0055b59b1376d86c4ba4a01f9c479c2101a7 /Foundation | |
parent | 80d493da05c8d461d74bfaa919ffc487be03ffe6 (diff) |
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.
Diffstat (limited to 'Foundation')
43 files changed, 4180 insertions, 707 deletions
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 <Foundation/Foundation.h> + +// FourCharCodes are OSTypes, ResTypes etc. This class wraps them if +// you need to store them in dictionaries etc. +@interface GTMFourCharCode : NSObject <NSCopying, NSCoding> { + 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 <CoreServices/CoreServices.h> + +@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 <SenTestingKit/SenTestingKit.h> -#import <unistd.h> -#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 <Foundation/Foundation.h> +#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 <netinet/in.h> +#import <sys/socket.h> +#import <unistd.h> + +#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 <netinet/in.h> +#import <sys/socket.h> +#import <unistd.h> +#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 <Foundation/Foundation.h> +#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 <Carbon/Carbon.h> // 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 <Carbon/Carbon.h> +#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 " + "<NSAppleEventDescriptor: '@!@#'\\(\\$[0-9A-F]*\\$\\)>"]; + [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 " + "<NSAppleEventDescriptor: '@!@#'\\(\\$[0-9A-F]+\\$\\)>"]; + 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: " + "<NSAppleEventDescriptor: 'Fooz'\\'Ball'{ }> -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 <Foundation/Foundation.h> +#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 <Carbon/Carbon.h> + +@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 <Carbon/Carbon.h> +#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 <Foundation/Foundation.h> +#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 <Carbon/Carbon.h> +#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 <Carbon/Carbon.h> +#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 <NSAppleScript: 0x[0-9a-f]+> \\(-50\\)"]; + goodSet = [script_ gtm_setValue:@"bar" forProperty:nil]; + STAssertFalse(goodSet, @"Set property?"); + [GTMUnitTestDevLog expectPattern:@"Unable to get valueForProperty:gargle " + "from <NSAppleScript: 0x[0-9a-f]+> \\(-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<ScriptInterface> foo = (id<ScriptInterface>)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 <stdlib.h> // for randiom/srandomdev #import <zlib.h> -@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 <objc/message.h> +#import <objc/runtime.h> +#else +#import <objc/objc-runtime.h> +#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. <stdint.h> - 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. <stdint.h> - 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 <unistd.h> #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() |