aboutsummaryrefslogtreecommitdiff
path: root/Foundation
diff options
context:
space:
mode:
authorGravatar thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-06-13 19:21:50 +0000
committerGravatar thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-06-13 19:21:50 +0000
commitc53ec5520e39096e0804ce8d89a21378c0904481 (patch)
treed36a0055b59b1376d86c4ba4a01f9c479c2101a7 /Foundation
parent80d493da05c8d461d74bfaa919ffc487be03ffe6 (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')
-rw-r--r--Foundation/GTMBase64Test.m2
-rw-r--r--Foundation/GTMCalculatedRange.m31
-rw-r--r--Foundation/GTMCalculatedRangeTest.m2
-rw-r--r--Foundation/GTMFourCharCode.h51
-rw-r--r--Foundation/GTMFourCharCode.m98
-rw-r--r--Foundation/GTMFourCharCodeTest.m82
-rw-r--r--Foundation/GTMGeometryUtils.h326
-rw-r--r--Foundation/GTMGeometryUtils.m34
-rw-r--r--Foundation/GTMGeometryUtilsTest.m128
-rw-r--r--Foundation/GTMHTTPFetcher.m9
-rw-r--r--Foundation/GTMHTTPFetcherTest.m124
-rw-r--r--Foundation/GTMHTTPServer.h133
-rw-r--r--Foundation/GTMHTTPServer.m584
-rw-r--r--Foundation/GTMHTTPServerTest.m573
-rw-r--r--Foundation/GTMNSAppleEvent+HandlerTest.applescript39
-rw-r--r--Foundation/GTMNSAppleEventDescriptor+Foundation.h91
-rw-r--r--Foundation/GTMNSAppleEventDescriptor+Foundation.m488
-rw-r--r--Foundation/GTMNSAppleEventDescriptor+FoundationTest.m619
-rw-r--r--Foundation/GTMNSAppleEventDescriptor+Handler.h40
-rw-r--r--Foundation/GTMNSAppleEventDescriptor+Handler.m130
-rw-r--r--Foundation/GTMNSAppleEventDescriptor+HandlerTest.m70
-rw-r--r--Foundation/GTMNSAppleScript+Handler.h59
-rw-r--r--Foundation/GTMNSAppleScript+Handler.m283
-rw-r--r--Foundation/GTMNSAppleScript+HandlerTest.m331
-rw-r--r--Foundation/GTMNSData+zlibTest.m136
-rw-r--r--Foundation/GTMNSDictionary+URLArguments.m2
-rw-r--r--Foundation/GTMNSDictionary+URLArgumentsTest.m2
-rw-r--r--Foundation/GTMNSEnumerator+Filter.m34
-rw-r--r--Foundation/GTMNSEnumerator+FilterTest.m2
-rw-r--r--Foundation/GTMNSFileManager+PathTest.m10
-rw-r--r--Foundation/GTMNSString+HTML.m16
-rw-r--r--Foundation/GTMNSString+HTMLTest.m2
-rw-r--r--Foundation/GTMNSString+URLArgumentsTest.m2
-rw-r--r--Foundation/GTMNSString+XML.m12
-rw-r--r--Foundation/GTMNSString+XMLTest.m2
-rw-r--r--Foundation/GTMObjC2RuntimeTest.m2
-rw-r--r--Foundation/GTMRegex.m21
-rw-r--r--Foundation/GTMRegexTest.m9
-rw-r--r--Foundation/GTMScriptRunnerTest.m28
-rw-r--r--Foundation/GTMStackTrace.c2
-rw-r--r--Foundation/GTMStackTraceTest.m2
-rw-r--r--Foundation/GTMSystemVersionTest.m2
-rwxr-xr-xFoundation/TestData/GTMHTTPFetcherTestServer274
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()