diff options
Diffstat (limited to 'Foundation')
30 files changed, 2537 insertions, 303 deletions
diff --git a/Foundation/GTMCalculatedRange.h b/Foundation/GTMCalculatedRange.h index 5823ba6..5f51b3e 100644 --- a/Foundation/GTMCalculatedRange.h +++ b/Foundation/GTMCalculatedRange.h @@ -19,7 +19,7 @@ // the License. // -#import <Cocoa/Cocoa.h> +#import <Foundation/Foundation.h> /// Allows you to calculate a value based on defined stops in a range. // diff --git a/Foundation/GTMCalculatedRangeTest.m b/Foundation/GTMCalculatedRangeTest.m index cd336f0..0c374c1 100644 --- a/Foundation/GTMCalculatedRangeTest.m +++ b/Foundation/GTMCalculatedRangeTest.m @@ -16,7 +16,6 @@ // the License. // -#import <SenTestingKit/SenTestingKit.h> #import "GTMCalculatedRange.h" #import "GTMSenTestCase.h" diff --git a/Foundation/GTMGeometryUtils.h b/Foundation/GTMGeometryUtils.h new file mode 100644 index 0000000..32c8745 --- /dev/null +++ b/Foundation/GTMGeometryUtils.h @@ -0,0 +1,377 @@ +// +// GTMGeometryUtils.h +// +// Utilities for geometrical utilities such as conversions +// between different types. +// +// 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. +// + +#include <Foundation/Foundation.h> + +typedef enum { + GTMScaleProportionally = 0, // Fit proportionally + GTMScaleToFit, // Forced fit (distort if necessary) + GTMScaleNone // Don't scale (clip) +} GTMScaling; + +typedef enum { + GTMRectAlignCenter = 0, + GTMRectAlignTop, + GTMRectAlignTopLeft, + GTMRectAlignTopRight, + GTMRectAlignLeft, + GTMRectAlignBottom, + GTMRectAlignBottomLeft, + GTMRectAlignBottomRight, + GTMRectAlignRight +} GTMRectAlignment; + +#pragma mark Miscellaneous + +/// Calculate the distance between two points. +// +// Args: +// pt1 first point +// pt2 second point +// +// Returns: +// Distance +CG_INLINE float GTMDistanceBetweenPoints(NSPoint pt1, NSPoint pt2) { + float dX = pt1.x - pt2.x; + float dY = pt1.y - pt2.y; + return sqrtf(dX * dX + dY * dY); +} + +#pragma mark - +#pragma mark Point Conversion + +/// Quickly convert from a CGPoint to a NSPoint. +// +/// CGPoints are relative to 0,0 in lower left; +/// NSPoints are relative to 0,0 in lower left +// +// Args: +// inPoint: CGPoint to convert +// +// Returns: +// Converted NSPoint +CG_INLINE NSPoint GTMCGPointToNSPoint(CGPoint inPoint) { + return NSMakePoint(inPoint.x, inPoint.y); +} + +/// Quickly convert from a NSPoint to a CGPoint. +// +/// CGPoints are relative to 0,0 in lower left; +/// NSPoints are relative to 0,0 in lower left +// +// Args: +// inPoint: NSPoint to convert +// +// Returns: +// Converted CGPoint +CG_INLINE CGPoint GTMNSPointToCGPoint(NSPoint inPoint) { + return CGPointMake(inPoint.x, inPoint.y); +} + +#pragma mark - +#pragma mark Rect Conversion + +/// Convert from a CGRect to a NSRect. +// +/// NSRect are relative to 0,0 in lower left; +/// CGRect are relative to 0,0 in lower left +// +// Args: +// inRect: CGRect to convert +// +// Returns: +// Converted NSRect +CG_INLINE NSRect GTMCGRectToNSRect(CGRect inRect) { + return NSMakeRect(inRect.origin.x,inRect.origin.y,inRect.size.width,inRect.size.height); +} + +/// Convert from a NSRect to a CGRect. +// +/// NSRect are relative to 0,0 in lower left; +/// CGRect are relative to 0,0 in lower left +// +// Args: +// inRect: NSRect to convert +// +// Returns: +// Converted CGRect +CG_INLINE CGRect GTMNSRectToCGRect(NSRect inRect) { + return CGRectMake(inRect.origin.x,inRect.origin.y,inRect.size.width,inRect.size.height); +} + +#pragma mark - +#pragma mark Size Conversion + +/// Convert from a CGSize to an NSSize. +// +// Args: +// inSize: CGSize to convert +// +// Returns: +// Converted NSSize +CG_INLINE NSSize GTMCGSizeToNSSize(CGSize inSize) { + return NSMakeSize(inSize.width, inSize.height); +} + +/// Convert from a NSSize to a CGSize. +// +// Args: +// inSize: NSSize to convert +// +// Returns: +// Converted CGSize +CG_INLINE CGSize GTMNSSizeToCGSize(NSSize inSize) { + return CGSizeMake(inSize.width, inSize.height); +} + +#pragma mark - +#pragma mark Point On Rect + +/// Return middle of left side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of left side of rect +CG_INLINE NSPoint GTMNSMidLeft(NSRect rect) { + return NSMakePoint(NSMinX(rect), NSMidY(rect)); +} + +/// Return middle of right side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of right side of rect +CG_INLINE NSPoint GTMNSMidRight(NSRect rect) { + return NSMakePoint(NSMaxX(rect), NSMidY(rect)); +} + +/// Return middle of top side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of top side of rect +CG_INLINE NSPoint GTMNSMidTop(NSRect rect) { + return NSMakePoint(NSMidX(rect), NSMaxY(rect)); +} + +/// Return middle of bottom side of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the middle of bottom side of rect +CG_INLINE NSPoint GTMNSMidBottom(NSRect rect) { + return NSMakePoint(NSMidX(rect), NSMinY(rect)); +} + +/// Return center of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// point located in the center of rect +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 + +/// Return size of rectangle +// +// Args: +// rect - rectangle +// +// Returns: +// size of rectangle +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: +// size - size +// +// Returns: +// rectangle of size (origin 0,0) +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 + +/// Scales an NSRect +// +// 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 NSRect GTMNSRectScale(NSRect inRect, float xScale, float yScale) { + return NSMakeRect(inRect.origin.x, inRect.origin.y, + 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, float xScale, float 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, + GTMRectAlignment alignment) { + return GTMNSRectToCGRect(GTMAlignRectangles(GTMCGRectToNSRect(alignee), + GTMCGRectToNSRect(aligner), + alignment)); +} + +/// Scale rectangle +// +// Args: +// scalee - rect to be scaled +// size - size to scale to +// scaling - way to scale the rectangle +NSRect GTMScaleRectangleToSize(NSRect scalee, NSSize size, + GTMScaling scaling); + +/// Scale rectangle +// +// 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)); +} diff --git a/Foundation/GTMGeometryUtils.m b/Foundation/GTMGeometryUtils.m new file mode 100644 index 0000000..f5b38dc --- /dev/null +++ b/Foundation/GTMGeometryUtils.m @@ -0,0 +1,104 @@ +// +// GTMGeometryUtils.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMGeometryUtils.h" + +/// Align rectangles +// +// Args: +// alignee - rect to be aligned +// aligner - rect to be aligned to +// alignment - alignment to be applied to alignee based on aligner + +NSRect GTMAlignRectangles(NSRect alignee, NSRect 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); + break; + + case GTMRectAlignTopLeft: + alignee.origin.x = aligner.origin.x; + alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee); + break; + + case GTMRectAlignTopRight: + alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(alignee); + alignee.origin.y = aligner.origin.y + NSHeight(aligner) - NSHeight(alignee); + break; + + case GTMRectAlignLeft: + alignee.origin.x = aligner.origin.x; + alignee.origin.y = aligner.origin.y + (NSHeight(aligner) * .5f - NSHeight(alignee) * .5f); + break; + + case GTMRectAlignBottomLeft: + alignee.origin.x = aligner.origin.x; + alignee.origin.y = aligner.origin.y; + break; + + case GTMRectAlignBottom: + alignee.origin.x = aligner.origin.x + (NSWidth(aligner) * .5f - NSWidth(alignee) * .5f); + alignee.origin.y = aligner.origin.y; + break; + + case GTMRectAlignBottomRight: + alignee.origin.x = aligner.origin.x + NSWidth(aligner) - NSWidth(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); + 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); + break; + } + return alignee; +} + +NSRect GTMScaleRectangleToSize(NSRect scalee, NSSize size, GTMScaling scaling) { + switch (scaling) { + case GTMScaleProportionally: { + float height = NSHeight(scalee); + float width = NSWidth(scalee); + if (isnormal(height) && isnormal(width) && + (height > size.height || width > size.width)) { + float horiz = size.width / width; + float vert = size.height / height; + float newScale = horiz < vert ? horiz : vert; + scalee = GTMNSRectScale(scalee, newScale, newScale); + } + break; + } + + case GTMScaleToFit: + scalee.size = size; + break; + + case GTMScaleNone: + default: + // Do nothing + break; + } + return scalee; +} diff --git a/Foundation/GTMGeometryUtilsTest.m b/Foundation/GTMGeometryUtilsTest.m new file mode 100644 index 0000000..2fb0c68 --- /dev/null +++ b/Foundation/GTMGeometryUtilsTest.m @@ -0,0 +1,181 @@ +// +// GTMGeometryUtilsTest.m +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMSenTestCase.h" +#import "GTMGeometryUtils.h" + +@interface GTMGeometryUtilsTest : SenTestCase +@end + +@implementation GTMGeometryUtilsTest + +- (void)testGTMCGPointToNSPoint { + CGPoint cgPoint = CGPointMake(15.1,6.2); + NSPoint nsPoint = GTMCGPointToNSPoint(cgPoint); + STAssertTrue(CGPointEqualToPoint(*(CGPoint*)&nsPoint, cgPoint), nil); +} + +- (void)testGTMNSPointToCGPoint { + NSPoint nsPoint = NSMakePoint(10.2,1.5); + CGPoint cgPoint = GTMNSPointToCGPoint(nsPoint); + STAssertTrue(CGPointEqualToPoint(cgPoint, *(CGPoint*)&nsPoint), nil); +} + +- (void)testGTMCGRectToNSRect { + CGRect cgRect = CGRectMake(1.5,2.4,10.6,11.7); + NSRect nsRect = GTMCGRectToNSRect(cgRect); + STAssertTrue(CGRectEqualToRect(cgRect, *(CGRect*)&nsRect), nil); +} + + +- (void)testGTMNSRectToCGRect { + NSRect nsRect = NSMakeRect(4.6,3.2,22.1,45.0); + CGRect cgRect = GTMNSRectToCGRect(nsRect); + STAssertTrue(CGRectEqualToRect(cgRect, *(CGRect*)&nsRect), nil); +} + +- (void)testGTMCGSizeToNSSize { + CGSize cgSize = {5,6}; + NSSize nsSize = GTMCGSizeToNSSize(cgSize); + STAssertTrue(CGSizeEqualToSize(cgSize, *(CGSize*)&nsSize), nil); +} + +- (void)testGTMNSSizeToCGSize { + NSSize nsSize = {22,15}; + CGSize cgSize = GTMNSSizeToCGSize(nsSize); + STAssertTrue(CGSizeEqualToSize(cgSize, *(CGSize*)&nsSize), nil); +} + +- (void)testGTMDistanceBetweenPoints { + NSPoint pt1 = NSMakePoint(0, 0); + NSPoint pt2 = NSMakePoint(3, 4); + STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), 5.0f, nil); + STAssertEquals(GTMDistanceBetweenPoints(pt2, pt1), 5.0f, nil); + pt1 = NSMakePoint(1, 1); + pt2 = NSMakePoint(1, 1); + STAssertEquals(GTMDistanceBetweenPoints(pt1, pt2), 0.0f, nil); +} + +- (void)testGTMAlignRectangles { + typedef struct { + NSPoint expectedOrigin; + GTMRectAlignment alignment; + } TestData; + + TestData data[] = { + { {1,2}, GTMRectAlignTop }, + { {0,2}, GTMRectAlignTopLeft }, + { {2,2}, GTMRectAlignTopRight }, + { {0,1}, GTMRectAlignLeft }, + { {1,0}, GTMRectAlignBottom }, + { {0,0}, GTMRectAlignBottomLeft }, + { {2,0}, GTMRectAlignBottomRight }, + { {2,1}, GTMRectAlignRight }, + { {1,1}, GTMRectAlignCenter }, + }; + + NSRect rect1 = NSMakeRect(0, 0, 4, 4); + NSRect rect2 = NSMakeRect(0, 0, 2, 2); + + for (int i = 0; i < sizeof(data) / sizeof(TestData); i++) { + NSRect expectedRect; + expectedRect.origin = data[i].expectedOrigin; + expectedRect.size = NSMakeSize(2, 2); + NSRect outRect = GTMAlignRectangles(rect2, rect1, data[i].alignment); + STAssertEquals(outRect, expectedRect, nil); + } +} + +- (void)testGTMPointsOnRect { + NSRect rect = NSMakeRect(0, 0, 2, 2); + CGRect cgRect = GTMNSRectToCGRect(rect); + + NSPoint point = GTMNSMidLeft(rect); + CGPoint cgPoint = GTMCGMidLeft(cgRect); + STAssertEquals(point.x, cgPoint.x, nil); + STAssertEquals(point.y, cgPoint.y, nil); + STAssertEqualsWithAccuracy(point.y, 1.0f, 0.01f, nil); + STAssertEqualsWithAccuracy(point.x, 0.0f, 0.01f, nil); + + point = GTMNSMidRight(rect); + cgPoint = GTMCGMidRight(cgRect); + STAssertEquals(point.x, cgPoint.x, nil); + STAssertEquals(point.y, cgPoint.y, nil); + STAssertEqualsWithAccuracy(point.y, 1.0f, 0.01f, nil); + STAssertEqualsWithAccuracy(point.x, 2.0f, 0.01f, nil); + + point = GTMNSMidTop(rect); + cgPoint = GTMCGMidTop(cgRect); + STAssertEquals(point.x, cgPoint.x, nil); + STAssertEquals(point.y, cgPoint.y, nil); + STAssertEqualsWithAccuracy(point.y, 2.0f, 0.01f, nil); + STAssertEqualsWithAccuracy(point.x, 1.0f, 0.01f, nil); + + point = GTMNSMidBottom(rect); + cgPoint = GTMCGMidBottom(cgRect); + STAssertEquals(point.x, cgPoint.x, nil); + STAssertEquals(point.y, cgPoint.y, nil); + STAssertEqualsWithAccuracy(point.y, 0.0f, 0.01f, nil); + STAssertEqualsWithAccuracy(point.x, 1.0f, 0.01f, nil); +} + +- (void)testGTMRectScaling { + NSRect rect = NSMakeRect(1.0f, 2.0f, 5.0f, 10.0f); + NSRect rect2 = NSMakeRect(1.0f, 2.0f, 1.0f, 12.0f); + STAssertEquals(GTMNSRectScale(rect, 0.2f, 1.2f), + rect2, nil); + STAssertEquals(GTMCGRectScale(GTMNSRectToCGRect(rect), 0.2f, 1.2f), + GTMNSRectToCGRect(rect2), nil); +} + +- (void)testGTMScaleRectangleToSize { + NSRect rect = NSMakeRect(0.0f, 0.0f, 10.0f, 10.0f); + typedef struct { + NSSize size_; + NSSize newSize_; + } Test; + Test tests[] = { + { { 5.0, 10.0 }, { 5.0, 5.0 } }, + { { 10.0, 5.0 }, { 5.0, 5.0 } }, + { { 10.0, 10.0 }, { 10.0, 10.0 } }, + { { 11.0, 11.0, }, { 10.0, 10.0 } }, + { { 5.0, 2.0 }, { 2.0, 2.0 } }, + { { 2.0, 5.0 }, { 2.0, 2.0 } }, + { { 2.0, 2.0 }, { 2.0, 2.0 } }, + { { 0.0, 10.0 }, { 0.0, 0.0 } } + }; + + 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); + } + + NSRect result = GTMScaleRectangleToSize(NSZeroRect, tests[0].size_, + GTMScaleProportionally); + STAssertEquals(result, NSZeroRect, nil); + + result = GTMScaleRectangleToSize(rect, tests[0].size_, + GTMScaleToFit); + STAssertEquals(result, GTMNSRectOfSize(tests[0].size_), nil); + + result = GTMScaleRectangleToSize(rect, tests[0].size_, + GTMScaleNone); + STAssertEquals(result, rect, nil); +} +@end diff --git a/Foundation/GTMNSData+zlib.m b/Foundation/GTMNSData+zlib.m index be84114..514477f 100644 --- a/Foundation/GTMNSData+zlib.m +++ b/Foundation/GTMNSData+zlib.m @@ -18,6 +18,7 @@ #import "GTMNSData+zlib.h" #import <zlib.h> +#import "GTMDefines.h" #define kChunkSize 1024 @@ -33,31 +34,35 @@ length:(unsigned)length compressionLevel:(int)level useGzip:(BOOL)useGzip { - if (!bytes || !length) return nil; + if (!bytes || !length) { + return nil; + } if (level == Z_DEFAULT_COMPRESSION) { // the default value is actually outside the range, so we have to let it // through specifically. - } else if (level < Z_BEST_SPEED) + } else if (level < Z_BEST_SPEED) { level = Z_BEST_SPEED; - else if (level > Z_BEST_COMPRESSION) + } else if (level > Z_BEST_COMPRESSION) { level = Z_BEST_COMPRESSION; + } z_stream strm; bzero(&strm, sizeof(z_stream)); int windowBits = 15; // the default int memLevel = 8; // the default - if (useGzip) + if (useGzip) { windowBits += 16; // enable gzip header instead of zlib header + } int retCode; if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel, Z_DEFAULT_STRATEGY)) != Z_OK) { -#ifdef DEBUG - NSLog(@"Failed to init for deflate w/ level %d, error %d", - level, retCode); -#endif + // COV_NF_START - no real way to force this in a unittest (we guard all args) + _GTMDevLog(@"Failed to init for deflate w/ level %d, error %d", + level, retCode); return nil; + // COV_NF_END } // hint the size at 1/4 the input size @@ -75,12 +80,15 @@ strm.next_out = output; retCode = deflate(&strm, Z_FINISH); if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { -#ifdef DEBUG - NSLog(@"Error trying to deflate some of the payload, error %d", - retCode); -#endif + // COV_NF_START - no real way to force this in a unittest + // (in inflate, we can feed bogus/truncated data to test, but an error + // here would be some internal issue w/in zlib, and there isn't any real + // way to test it) + _GTMDevLog(@"Error trying to deflate some of the payload, error %d", + retCode); deflateEnd(&strm); return nil; + // COV_NF_END } // collect what we got unsigned gotBack = kChunkSize - strm.avail_out; @@ -90,16 +98,13 @@ } while (retCode == Z_OK); -#ifdef DEBUG - if (strm.avail_in != 0) { - NSLog(@"thought we finished deflate w/o using all input, %u bytes left", - strm.avail_in); - } - if (retCode != Z_STREAM_END) { - NSLog(@"thought we finished deflate w/o getting a result of stream end, code %d", - retCode); - } -#endif + // if the loop exits, we used all input and the stream ended + _GTMDevAssert(strm.avail_in == 0, + @"thought we finished deflate w/o using all input, %u bytes left", + strm.avail_in); + _GTMDevAssert(retCode == Z_STREAM_END, + @"thought we finished deflate w/o getting a result of stream end, code %d", + retCode); // clean up deflateEnd(&strm); @@ -179,7 +184,9 @@ + (NSData *)gtm_dataByInflatingBytes:(const void *)bytes length:(unsigned)length { - if (!bytes || !length) return nil; + if (!bytes || !length) { + return nil; + } z_stream strm; bzero(&strm, sizeof(z_stream)); @@ -192,10 +199,10 @@ windowBits += 32; // and +32 to enable zlib or gzip header detection. int retCode; if ((retCode = inflateInit2(&strm, windowBits)) != Z_OK) { -#ifdef DEBUG - NSLog(@"Failed to init for inflate, error %d", retCode); -#endif + // COV_NF_START - no real way to force this in a unittest (we guard all args) + _GTMDevLog(@"Failed to init for inflate, error %d", retCode); return nil; + // COV_NF_END } // hint the size at 4x the input size @@ -209,10 +216,8 @@ strm.next_out = output; retCode = inflate(&strm, Z_NO_FLUSH); if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { -#ifdef DEBUG - NSLog(@"Error trying to inflate some of the payload, error %d", - retCode); -#endif + _GTMDevLog(@"Error trying to inflate some of the payload, error %d", + retCode); inflateEnd(&strm); return nil; } @@ -224,16 +229,17 @@ } while (retCode == Z_OK); -#ifdef DEBUG + // make sure there wasn't more data tacked onto the end of a valid compressed + // stream. if (strm.avail_in != 0) { - NSLog(@"thought we finished inflate w/o using all input, %u bytes left", - strm.avail_in); - } - if (retCode != Z_STREAM_END) { - NSLog(@"thought we finished inflate w/o getting a result of stream end, code %d", - retCode); + _GTMDevLog(@"thought we finished inflate w/o using all input, %u bytes left", + strm.avail_in); + result = nil; } -#endif + // the only way out of the loop was by hitting the end of the stream + _GTMDevAssert(retCode == Z_STREAM_END, + @"thought we finished inflate w/o getting a result of stream end, code %d", + retCode); // clean up inflateEnd(&strm); diff --git a/Foundation/GTMNSData+zlibTest.m b/Foundation/GTMNSData+zlibTest.m index 0d863d5..38dbce3 100644 --- a/Foundation/GTMNSData+zlibTest.m +++ b/Foundation/GTMNSData+zlibTest.m @@ -16,7 +16,6 @@ // the License. // -#import <SenTestingKit/SenTestingKit.h> #import "GTMSenTestCase.h" #import "GTMNSData+zlib.h" @@ -36,11 +35,9 @@ static void FillWithRandom(char *data, unsigned long len) { static BOOL HasGzipHeader(NSData *data) { // very simple check - if ([data length] > 2) { - const unsigned char *bytes = [data bytes]; - return (bytes[0] == 0x1f) && (bytes[1] == 0x8b); - } - return NO; + const unsigned char *bytes = [data bytes]; + return ([data length] > 2) && + ((bytes[0] == 0x1f) && (bytes[1] == 0x8b)); } @@ -51,6 +48,115 @@ static BOOL HasGzipHeader(NSData *data) { srandomdev(); } +- (void)testBoundryValues { + NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init]; + STAssertNotNil(localPool, @"failed to alloc local pool"); + + // build some test data + NSMutableData *data = [NSMutableData data]; + STAssertNotNil(data, @"failed to alloc data block"); + [data setLength:512]; + FillWithRandom([data mutableBytes], [data length]); + + // bogus args to start + STAssertNil([NSData gtm_dataByDeflatingData:nil], nil); + STAssertNil([NSData gtm_dataByDeflatingBytes:nil length:666], nil); + STAssertNil([NSData gtm_dataByDeflatingBytes:[data bytes] length:0], nil); + STAssertNil([NSData gtm_dataByGzippingData:nil], nil); + STAssertNil([NSData gtm_dataByGzippingBytes:nil length:666], nil); + STAssertNil([NSData gtm_dataByGzippingBytes:[data bytes] length:0], nil); + STAssertNil([NSData gtm_dataByInflatingData:nil], nil); + STAssertNil([NSData gtm_dataByInflatingBytes:nil length:666], nil); + STAssertNil([NSData gtm_dataByInflatingBytes:[data bytes] length:0], nil); + + // test deflate w/ compression levels out of range + NSData *deflated = [NSData gtm_dataByDeflatingData:data + compressionLevel:-4]; + STAssertNotNil(deflated, nil); + STAssertFalse(HasGzipHeader(deflated), nil); + NSData *dataPrime = [NSData gtm_dataByInflatingData:deflated]; + STAssertNotNil(dataPrime, nil); + STAssertEqualObjects(data, dataPrime, nil); + deflated = [NSData gtm_dataByDeflatingData:data + compressionLevel:20]; + STAssertNotNil(deflated, nil); + STAssertFalse(HasGzipHeader(deflated), nil); + dataPrime = [NSData gtm_dataByInflatingData:deflated]; + STAssertNotNil(dataPrime, nil); + STAssertEqualObjects(data, dataPrime, nil); + + // test gzip w/ compression levels out of range + NSData *gzipped = [NSData gtm_dataByGzippingData:data + compressionLevel:-4]; + STAssertNotNil(gzipped, nil); + STAssertTrue(HasGzipHeader(gzipped), nil); + dataPrime = [NSData gtm_dataByInflatingData:gzipped]; + STAssertNotNil(dataPrime, nil); + STAssertEqualObjects(data, dataPrime, nil); + gzipped = [NSData gtm_dataByGzippingData:data + compressionLevel:20]; + STAssertNotNil(gzipped, nil); + STAssertTrue(HasGzipHeader(gzipped), nil); + dataPrime = [NSData gtm_dataByInflatingData:gzipped]; + STAssertNotNil(dataPrime, nil); + STAssertEqualObjects(data, dataPrime, nil); + + // test non-compressed data data itself + STAssertNil([NSData gtm_dataByInflatingData:data], nil); + + // test deflated data runs that end before they are done + for (int x = 1 ; x < [deflated length] ; ++x) { + STAssertNil([NSData gtm_dataByInflatingBytes:[deflated bytes] length:x], nil); + } + + // test gzipped data runs that end before they are done + for (int x = 1 ; x < [gzipped length] ; ++x) { + STAssertNil([NSData gtm_dataByInflatingBytes:[gzipped bytes] length:x], nil); + } + + // test extra data before the deflated/gzipped data (just to make sure we + // don't seek to the "real" data) + NSMutableData *prefixedDeflated = [NSMutableData data]; + STAssertNotNil(prefixedDeflated, @"failed to alloc data block"); + [prefixedDeflated setLength:20]; + FillWithRandom([prefixedDeflated mutableBytes], [prefixedDeflated length]); + [prefixedDeflated appendData:deflated]; + STAssertNil([NSData gtm_dataByInflatingData:prefixedDeflated], nil); + STAssertNil([NSData gtm_dataByInflatingBytes:[prefixedDeflated bytes] + length:[prefixedDeflated length]], + nil); + NSMutableData *prefixedGzipped = [NSMutableData data]; + STAssertNotNil(prefixedDeflated, @"failed to alloc data block"); + [prefixedGzipped setLength:20]; + FillWithRandom([prefixedGzipped mutableBytes], [prefixedGzipped length]); + [prefixedGzipped appendData:gzipped]; + STAssertNil([NSData gtm_dataByInflatingData:prefixedGzipped], nil); + STAssertNil([NSData gtm_dataByInflatingBytes:[prefixedGzipped bytes] + length:[prefixedGzipped length]], + nil); + + // test extra data after the deflated/gzipped data (just to make sure we + // don't ignore some of the data) + NSMutableData *suffixedDeflated = [NSMutableData data]; + STAssertNotNil(suffixedDeflated, @"failed to alloc data block"); + [suffixedDeflated appendData:deflated]; + [suffixedDeflated appendBytes:[data bytes] length:20]; + STAssertNil([NSData gtm_dataByInflatingData:suffixedDeflated], nil); + STAssertNil([NSData gtm_dataByInflatingBytes:[suffixedDeflated bytes] + length:[suffixedDeflated length]], + nil); + NSMutableData *suffixedGZipped = [NSMutableData data]; + STAssertNotNil(suffixedGZipped, @"failed to alloc data block"); + [suffixedGZipped appendData:gzipped]; + [suffixedGZipped appendBytes:[data bytes] length:20]; + STAssertNil([NSData gtm_dataByInflatingData:suffixedGZipped], nil); + STAssertNil([NSData gtm_dataByInflatingBytes:[suffixedGZipped bytes] + length:[suffixedGZipped length]], + nil); + + [localPool release]; +} + - (void)testInflateDeflate { // generate a range of sizes w/ random content for (int n = 0 ; n < 2 ; ++n) { diff --git a/Foundation/GTMNSEnumerator+Filter.m b/Foundation/GTMNSEnumerator+Filter.m index 60c85f3..5ac3a7c 100644 --- a/Foundation/GTMNSEnumerator+Filter.m +++ b/Foundation/GTMNSEnumerator+Filter.m @@ -17,33 +17,35 @@ // #import "GTMNSEnumerator+Filter.h" +#import "GTMDebugSelectorValidation.h" +#import "GTMDefines.h" // a private subclass of NSEnumerator that does all the work. // public interface just returns one of these. // This top level class contains all the additional boilerplate. Specific // behavior is in the subclasses. -@interface GTMEnumerator : NSEnumerator { +@interface GTMEnumeratorPrivateBase : NSEnumerator { @protected NSEnumerator *base_; // STRONG SEL operation_; // either a predicate or a transform depending on context. id other_; // STRONG, may be nil } -- (id)nextObject; +@end + +@interface GTMEnumeratorPrivateBase (SubclassesMustProvide) - (BOOL)filterObject:(id)obj returning:(id *)resultp; @end -@implementation GTMEnumerator + +@implementation GTMEnumeratorPrivateBase - (id)initWithBase:(NSEnumerator *)base sel:(SEL)filter object:(id)optionalOther { self = [super init]; if (self) { - // specializing a nil enumerator returns nil. - if (nil == base) { - [self release]; - return nil; - } - + // someone would have to subclass or directly create an object of this + // class, and this class is private to this impl. + _GTMDevAssert(base, @"can't bas a nil base enumerator"); base_ = [base retain]; operation_ = filter; other_ = [optionalOther retain]; @@ -51,11 +53,9 @@ return self; } -// it is an error to call this initializer. -- (id)init { - [self doesNotRecognizeSelector:_cmd]; - return nil; -} +// we don't provide an init because this base class is private to this +// impl, and no one would be able to create it (if they do, they get whatever +// they happens...). - (void)dealloc { [base_ release]; @@ -72,17 +72,10 @@ } return nil; } - -// subclasses must override -- (BOOL)filterObject:(id)obj returning:(id *)resultp { - [self doesNotRecognizeSelector:_cmd]; - return NO; -} @end // a transformer, for each item in the enumerator, returns a f(item). -@interface GTMEnumeratorTransformer : GTMEnumerator -- (BOOL)filterObject:(id)obj returning:(id *)resultp; +@interface GTMEnumeratorTransformer : GTMEnumeratorPrivateBase @end @implementation GTMEnumeratorTransformer - (BOOL)filterObject:(id)obj returning:(id *)resultp { @@ -93,8 +86,7 @@ // a transformer, for each item in the enumerator, returns a f(item). // a target transformer swaps the target and the argument. -@interface GTMEnumeratorTargetTransformer : GTMEnumerator -- (BOOL)filterObject:(id)obj returning:(id *)resultp; +@interface GTMEnumeratorTargetTransformer : GTMEnumeratorPrivateBase @end @implementation GTMEnumeratorTargetTransformer - (BOOL)filterObject:(id)obj returning:(id *)resultp { @@ -104,8 +96,7 @@ @end // a filter, for each item in the enumerator, if(f(item)) { returns item. } -@interface GTMEnumeratorFilter : GTMEnumerator -- (BOOL)filterObject:(id)obj returning:(id *)resultp; +@interface GTMEnumeratorFilter : GTMEnumeratorPrivateBase @end @implementation GTMEnumeratorFilter // We must take care here, since Intel leaves junk in high bytes of return register @@ -119,8 +110,7 @@ // a target filter, for each item in the enumerator, if(f(item)) { returns item. } // a target transformer swaps the target and the argument. -@interface GTMEnumeratorTargetFilter : GTMEnumerator -- (BOOL)filterObject:(id)obj returning:(id *)resultp; +@interface GTMEnumeratorTargetFilter : GTMEnumeratorPrivateBase @end @implementation GTMEnumeratorTargetFilter // We must take care here, since Intel leaves junk in high bytes of return register @@ -151,6 +141,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); return [[[GTMEnumeratorTargetFilter alloc] initWithBase:self sel:selector object:target] autorelease]; @@ -158,6 +152,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); return [[[GTMEnumeratorTargetTransformer alloc] initWithBase:self sel:selector object:target] autorelease]; diff --git a/Foundation/GTMNSEnumerator+FilterTest.m b/Foundation/GTMNSEnumerator+FilterTest.m index 99bde01..224b8b0 100644 --- a/Foundation/GTMNSEnumerator+FilterTest.m +++ b/Foundation/GTMNSEnumerator+FilterTest.m @@ -16,7 +16,7 @@ // the License. // -#import <SenTestingKit/SenTestingKit.h> +#import "GTMSenTestCase.h" #import "GTMNSEnumerator+Filter.h" @interface GTMNSEnumerator_FilterTest : SenTestCase @@ -24,8 +24,8 @@ @implementation GTMNSEnumerator_FilterTest -// test using an NSSet enumerator. - (void)testEnumeratorByMakingEachObjectPerformSelector { + // test w/ a set of strings NSSet *numbers = [NSSet setWithObjects: @"1", @"2", @"3", nil]; NSEnumerator *e = [[numbers objectEnumerator] gtm_enumeratorByMakingEachObjectPerformSelector:@selector(stringByAppendingString:) @@ -37,32 +37,97 @@ } NSSet *trailingSpacesGood = [NSSet setWithObjects: @"1 ", @"2 ", @"3 ", nil]; STAssertEqualObjects(trailingSpaces, trailingSpacesGood, @""); + + // test an empty set NSSet *empty = [NSSet set]; - NSEnumerator *ee = [[empty objectEnumerator] + e = [[empty objectEnumerator] gtm_enumeratorByMakingEachObjectPerformSelector:@selector(stringByAppendingString:) withObject:@" "]; - - NSMutableSet *emptySpaces = [NSMutableSet set]; - while (nil != (obj = [ee nextObject])) { - [emptySpaces addObject:obj]; - } - STAssertEqualObjects(empty, emptySpaces, @""); + STAssertNil([e nextObject], + @"shouldn't have gotten anything from first advance of enumerator"); } -// test using an NSDictionary enumerator. - (void)testFilteredEnumeratorByMakingEachObjectPerformSelector { - NSDictionary *numbers = [NSDictionary dictionaryWithObjectsAndKeys: @"1", @"1", @"", @"", @"3", @"3", nil]; + // test with a dict of strings + NSDictionary *testDict = [NSDictionary dictionaryWithObjectsAndKeys: + @"foo", @"1", + @"bar", @"2", + @"foobar", @"3", + nil]; + // test those that have prefixes + NSEnumerator *e = [[testDict objectEnumerator] + gtm_filteredEnumeratorByMakingEachObjectPerformSelector:@selector(hasPrefix:) + withObject:@"foo"]; + // since the dictionary iterates in any order, compare as sets + NSSet *filteredValues = [NSSet setWithArray:[e allObjects]]; + NSSet *expectedValues = [NSSet setWithObjects:@"foo", @"foobar", nil]; + STAssertEqualObjects(filteredValues, expectedValues, @""); - // |length| filters out length 0 objects - NSEnumerator *e = [[numbers objectEnumerator] - gtm_filteredEnumeratorByMakingEachObjectPerformSelector:@selector(length) - withObject:nil]; + // test an empty set + NSSet *empty = [NSSet set]; + e = [[empty objectEnumerator] + gtm_filteredEnumeratorByMakingEachObjectPerformSelector:@selector(hasPrefix:) + withObject:@"foo"]; + STAssertNil([e nextObject], + @"shouldn't have gotten anything from first advance of enumerator"); + + // test an set that will filter out + NSSet *filterAway = [NSSet setWithObjects:@"bar", @"baz", nil]; + e = [[filterAway objectEnumerator] + gtm_filteredEnumeratorByMakingEachObjectPerformSelector:@selector(hasPrefix:) + withObject:@"foo"]; + STAssertNil([e nextObject], + @"shouldn't have gotten anything from first advance of enumerator"); +} - NSArray *lengths = [e allObjects]; - NSArray *lengthsGood = [NSArray arrayWithObjects: @"1", @"3", nil]; - STAssertEqualObjects(lengths, lengthsGood, @""); +- (void)testEnumeratorByTargetPerformOnEachSelector { + // test w/ a set of strings + NSSet *numbers = [NSSet setWithObjects: @"1", @"2", @"3", nil]; + NSString *target = @"foo"; + NSEnumerator *e = [[numbers objectEnumerator] + gtm_enumeratorByTarget:target + performOnEachSelector:@selector(stringByAppendingString:)]; + // since the set iterates in any order, compare as sets + NSSet *collectedValues = [NSSet setWithArray:[e allObjects]]; + NSSet *expectedValues = [NSSet setWithObjects:@"foo1", @"foo2", @"foo3", nil]; + STAssertEqualObjects(collectedValues, expectedValues, @""); + + // test an empty set + NSSet *empty = [NSSet set]; + e = [[empty objectEnumerator] + gtm_enumeratorByTarget:target + performOnEachSelector:@selector(stringByAppendingString:)]; + STAssertNil([e nextObject], + @"shouldn't have gotten anything from first advance of enumerator"); } +- (void)testFilteredEnumeratorByTargetPerformOnEachSelector { + // test w/ a set of strings + NSSet *numbers = [NSSet setWithObjects:@"1", @"2", @"3", @"4", nil]; + NSSet *target = [NSSet setWithObjects:@"2", @"4", @"6", nil]; + NSEnumerator *e = [[numbers objectEnumerator] + gtm_filteredEnumeratorByTarget:target + performOnEachSelector:@selector(containsObject:)]; + // since the set iterates in any order, compare as sets + NSSet *filteredValues = [NSSet setWithArray:[e allObjects]]; + NSSet *expectedValues = [NSSet setWithObjects:@"2", @"4", nil]; + STAssertEqualObjects(filteredValues, expectedValues, @""); + + // test an empty set + NSSet *empty = [NSSet set]; + e = [[empty objectEnumerator] + gtm_filteredEnumeratorByTarget:target + performOnEachSelector:@selector(containsObject:)]; + STAssertNil([e nextObject], + @"shouldn't have gotten anything from first advance of enumerator"); + // test an set that will filter out + NSSet *filterAway = [NSSet setWithObjects:@"bar", @"baz", nil]; + e = [[filterAway objectEnumerator] + gtm_filteredEnumeratorByTarget:target + performOnEachSelector:@selector(containsObject:)]; + STAssertNil([e nextObject], + @"shouldn't have gotten anything from first advance of enumerator"); +} @end diff --git a/Foundation/GTMNSFileManager+Path.h b/Foundation/GTMNSFileManager+Path.h index 330c45e..95ba41e 100644 --- a/Foundation/GTMNSFileManager+Path.h +++ b/Foundation/GTMNSFileManager+Path.h @@ -52,7 +52,8 @@ /// Args: /// extension - the file extension (excluding the leading ".") to match. /// If nil, all files are matched. -/// directoryPath - the directory to look in. Subdirectories are not traversed. +/// directoryPath - the directory to look in. NOTE: Subdirectories are NOT +/// traversed. /// /// Returns: /// An NSArray of absolute file paths that have |extension|. nil is returned diff --git a/Foundation/GTMNSFileManager+PathTest.m b/Foundation/GTMNSFileManager+PathTest.m index 10b4cbd..0f2eb35 100644 --- a/Foundation/GTMNSFileManager+PathTest.m +++ b/Foundation/GTMNSFileManager+PathTest.m @@ -16,30 +16,72 @@ // the License. // -#import <SenTestingKit/SenTestingKit.h> +#import "GTMSenTestCase.h" #import "GTMNSFileManager+Path.h" -@interface GTMNSFileManager_PathTest : SenTestCase +@interface GTMNSFileManager_PathTest : SenTestCase { + NSString *baseDir_; +} @end @implementation GTMNSFileManager_PathTest +- (void)setUp { + // make a directory to scribble in + baseDir_ = + [[NSTemporaryDirectory() + stringByAppendingPathComponent:@"GTMNSFileManager_PathTest"] retain]; + if (baseDir_) { + NSFileManager *fm = [NSFileManager defaultManager]; + if (![fm fileExistsAtPath:baseDir_] && + ![fm createDirectoryAtPath:baseDir_ attributes:nil]) { + // COV_NF_START + // if the dir exists or we failed to create it, drop the baseDir_ + [baseDir_ release]; + baseDir_ = nil; + // COV_NF_END + } + } +} + +- (void)tearDown { + if (baseDir_) { + // clean up our directory + NSFileManager *fm = [NSFileManager defaultManager]; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 + NSError *error = nil; + [fm removeItemAtPath:baseDir_ error:&error]; + STAssertNil(error, + @"Unable to delete %@: %@", baseDir_, [error description]); +#else + [fm removeFileAtPath:baseDir_ handler:nil]; +#endif + + [baseDir_ release]; + baseDir_ = nil; + } +} + #if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 - (void)testCreateFullPathToDirectoryAttributes { - NSString *baseDir = - [NSTemporaryDirectory() stringByAppendingPathComponent:@"testCreateFullPathToDirectoryAttributes"]; - NSString *testPath = [baseDir stringByAppendingPathComponent:@"/foo/bar/baz"]; + STAssertNotNil(baseDir_, @"setUp failed"); + + NSString *testPath = + [baseDir_ stringByAppendingPathComponent:@"/foo/bar/baz"]; + STAssertNotNil(testPath, nil); NSFileManager *fm = [NSFileManager defaultManager]; STAssertFalse([fm fileExistsAtPath:testPath], - @"You must delete '%@' before running this test", baseDir); + @"You must delete '%@' before running this test", testPath); + STAssertFalse([fm gtm_createFullPathToDirectory:nil attributes:nil], + @"didn't fail on nil input"); + STAssertTrue([fm gtm_createFullPathToDirectory:testPath attributes:nil], @"Failed to create nested testPath"); - - STAssertTrue([fm removeFileAtPath:baseDir handler:nil], - @"Failed to delete \'%@\'", baseDir); + STAssertTrue([fm gtm_createFullPathToDirectory:testPath attributes:nil], + @"Failed to succeed on second create of testPath"); NSString *pathToFail = [@"/etc" stringByAppendingPathComponent:testPath]; STAssertFalse([fm gtm_createFullPathToDirectory:pathToFail attributes:nil], @@ -52,7 +94,156 @@ #endif // MAC_OS_X_VERSION_MIN_REQUIRED < 1050 - (void)testfilePathsWithExtensionsInDirectory { - // TODO: need a test for filePathsWithExtensions:inDirectory: + STAssertNotNil(baseDir_, @"setUp failed"); + + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *bogusPath = @"/some/place/that/does/not/exist"; + + // -------------------------------------------------------------------------- + // test fail cases first + + // single + STAssertNil([fm gtm_filePathsWithExtension:nil inDirectory:nil], + @"shouldn't have gotten anything for nil dir"); + STAssertNil([fm gtm_filePathsWithExtension:@"txt" inDirectory:nil], + @"shouldn't have gotten anything for nil dir"); + STAssertNil([fm gtm_filePathsWithExtension:@"txt" inDirectory:bogusPath], + @"shouldn't have gotten anything for a bogus dir"); + // array + STAssertNil([fm gtm_filePathsWithExtensions:nil inDirectory:nil], + @"shouldn't have gotten anything for nil dir"); + STAssertNil([fm gtm_filePathsWithExtensions:[NSArray array] inDirectory:nil], + @"shouldn't have gotten anything for nil dir"); + STAssertNil([fm gtm_filePathsWithExtensions:[NSArray arrayWithObject:@"txt"] + inDirectory:nil], + @"shouldn't have gotten anything for nil dir"); + STAssertNil([fm gtm_filePathsWithExtensions:[NSArray arrayWithObject:@"txt"] + inDirectory:bogusPath], + @"shouldn't have gotten anything for a bogus dir"); + + // -------------------------------------------------------------------------- + // create some test data + + NSString *testDirs[] = { + @"", @"/foo", // mave a subdir to make sure we don't match w/in it + }; + NSString *testFiles[] = { + @"a.txt", @"b.txt", @"c.rtf", @"d.m", + }; + + for (int i = 0; i < sizeof(testDirs) / sizeof(NSString*); i++) { + NSString *testDir = nil; + if ([testDirs[i] length]) { + testDir = [baseDir_ stringByAppendingPathComponent:testDirs[i]]; + STAssertTrue([fm createDirectoryAtPath:testDir attributes:nil], nil); + } else { + testDir = baseDir_; + } + for (int j = 0; j < sizeof(testFiles) / sizeof(NSString*); j++) { + NSString *testFile = [testDir stringByAppendingPathComponent:testFiles[j]]; + STAssertTrue([@"test" writeToFile:testFile atomically:YES], nil); + } + } + + // build set of the top level items + NSMutableArray *allFiles = [NSMutableArray array]; + for (int 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++) { + NSString *testFile = [baseDir_ stringByAppendingPathComponent:testFiles[j]]; + [allFiles addObject:testFile]; + } + + NSArray *matches = nil; + NSArray *expectedMatches = nil; + NSArray *extensions = nil; + + // NOTE: we do all compares w/ sets so order doesn't matter + + // -------------------------------------------------------------------------- + // test match all + + // single + matches = [fm gtm_filePathsWithExtension:nil inDirectory:baseDir_]; + STAssertEqualObjects([NSSet setWithArray:matches], + [NSSet setWithArray:allFiles], + @"didn't get all files for nil extension"); + matches = [fm gtm_filePathsWithExtension:@"" inDirectory:baseDir_]; + STAssertEqualObjects([NSSet setWithArray:matches], + [NSSet setWithArray:allFiles], + @"didn't get all files for nil extension"); + // array + matches = [fm gtm_filePathsWithExtensions:nil inDirectory:baseDir_]; + STAssertEqualObjects([NSSet setWithArray:matches], + [NSSet setWithArray:allFiles], + @"didn't get all files for nil extension"); + matches = [fm gtm_filePathsWithExtensions:[NSArray array] + inDirectory:baseDir_]; + STAssertEqualObjects([NSSet setWithArray:matches], + [NSSet setWithArray:allFiles], + @"didn't get all files for nil extension"); + + // -------------------------------------------------------------------------- + // test match something + + // single + extensions = [NSArray arrayWithObject:@"txt"]; + matches = [fm gtm_filePathsWithExtension:@"txt" inDirectory:baseDir_]; + expectedMatches = [allFiles pathsMatchingExtensions:extensions]; + STAssertEqualObjects([NSSet setWithArray:matches], + [NSSet setWithArray:expectedMatches], + @"didn't get expected files"); + // array + matches = [fm gtm_filePathsWithExtensions:extensions inDirectory:baseDir_]; + expectedMatches = [allFiles pathsMatchingExtensions:extensions]; + STAssertEqualObjects([NSSet setWithArray:matches], + [NSSet setWithArray:expectedMatches], + @"didn't get expected files"); + extensions = [NSArray arrayWithObjects:@"txt", @"rtf", @"xyz", nil]; + matches = [fm gtm_filePathsWithExtensions:extensions inDirectory:baseDir_]; + expectedMatches = [allFiles pathsMatchingExtensions:extensions]; + STAssertEqualObjects([NSSet setWithArray:matches], + [NSSet setWithArray:expectedMatches], + @"didn't get expected files"); + + // -------------------------------------------------------------------------- + // test match nothing + + // single + extensions = [NSArray arrayWithObject:@"xyz"]; + matches = [fm gtm_filePathsWithExtension:@"xyz" inDirectory:baseDir_]; + expectedMatches = [allFiles pathsMatchingExtensions:extensions]; + STAssertEqualObjects([NSSet setWithArray:matches], + [NSSet setWithArray:expectedMatches], + @"didn't get expected files"); + // array + matches = [fm gtm_filePathsWithExtensions:extensions inDirectory:baseDir_]; + expectedMatches = [allFiles pathsMatchingExtensions:extensions]; + STAssertEqualObjects([NSSet setWithArray:matches], + [NSSet setWithArray:expectedMatches], + @"didn't get expected files"); + + // -------------------------------------------------------------------------- + // test match an empty dir + + // create the empty dir + NSString *emptyDir = [baseDir_ stringByAppendingPathComponent:@"emptyDir"]; + STAssertTrue([fm createDirectoryAtPath:emptyDir attributes:nil], nil); + + // single + matches = [fm gtm_filePathsWithExtension:@"txt" inDirectory:emptyDir]; + STAssertEqualObjects([NSSet setWithArray:matches], [NSSet set], + @"expected empty dir"); + // array + matches = [fm gtm_filePathsWithExtensions:[NSArray arrayWithObject:@"txt"] + inDirectory:emptyDir]; + STAssertEqualObjects([NSSet setWithArray:matches], [NSSet set], + @"expected empty dir"); + } @end diff --git a/Foundation/GTMNSString+HTML.m b/Foundation/GTMNSString+HTML.m index f9e99dc..a6abb0e 100644 --- a/Foundation/GTMNSString+HTML.m +++ b/Foundation/GTMNSString+HTML.m @@ -17,7 +17,10 @@ // the License. // +#import "GTMDefines.h" #import "GTMNSString+HTML.h" +#import "GTMNSString+Utilities.h" +#import "GTMMethodCheck.h" typedef struct { NSString *escapeSequence; @@ -355,7 +358,7 @@ static HTMLEscapeMap gUnicodeHTMLEscapeMap[] = { // Utility function for Bsearching table above -static int escapeMapCompare(const void *ucharVoid, const void *mapVoid) { +static int EscapeMapCompare(const void *ucharVoid, const void *mapVoid) { unichar *uchar = (unichar*)ucharVoid; HTMLEscapeMap *map = (HTMLEscapeMap*)mapVoid; int val; @@ -370,45 +373,49 @@ static int escapeMapCompare(const void *ucharVoid, const void *mapVoid) { } @implementation NSString (GTMNSStringHTMLAdditions) +GTM_METHOD_CHECK(NSString, gtm_UTF16StringWithLength:); - (NSString *)gtm_stringByEscapingHTMLUsingTable:(HTMLEscapeMap*)table ofSize:(int)size - escapingUnicode:(BOOL)escapeUnicode { - NSMutableString *finalString = [NSMutableString string]; + escapingUnicode:(BOOL)escapeUnicode { int length = [self length]; - require_quiet(length != 0, cantConvertAnything); + if (!length) { + return nil; + } + + NSMutableString *finalString = [NSMutableString string]; - unichar *buffer = malloc(sizeof(unichar) * length); - require_action(buffer, cantAllocBuffer, finalString = nil); - unichar *buffer2 = malloc(sizeof(unichar) * length); - require_action(buffer2, cantAllocBuffer2, finalString = nil); + NSMutableData *data2 = [NSMutableData dataWithCapacity:sizeof(unichar) * length]; + const unichar *buffer = (const unichar *)[self gtm_UTF16StringWithLength:nil]; + + if (!buffer || !data2) { + // COV_NF_BEGIN + _GTMDevLog(@"Unable to allocate buffer or data2"); + return nil; + // COV_NF_END + } + + unichar *buffer2 = (unichar *)[data2 mutableBytes]; - [self getCharacters:buffer]; int buffer2Length = 0; for (int i = 0; i < length; ++i) { HTMLEscapeMap *val = bsearch(&buffer[i], table, size / sizeof(HTMLEscapeMap), - sizeof(HTMLEscapeMap), escapeMapCompare); + sizeof(HTMLEscapeMap), EscapeMapCompare); if (val || (escapeUnicode && buffer[i] > 127)) { if (buffer2Length) { - CFStringRef buffer2String = - CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, - buffer2, buffer2Length, - kCFAllocatorNull); - require_action(buffer2String, cantCreateString, finalString = nil); - [finalString appendString:(NSString*)buffer2String]; - CFRelease(buffer2String); + CFStringAppendCharacters((CFMutableStringRef)finalString, + buffer2, + buffer2Length); buffer2Length = 0; } if (val) { [finalString appendString:val->escapeSequence]; } - else if (escapeUnicode && buffer[i] > 127) { + else { + _GTMDevAssert(escapeUnicode && buffer[i] > 127, @"Illegal Character"); [finalString appendFormat:@"&#%d;", buffer[i]]; - } else { - // Should never get here. Assert in debug. - require_action(NO, cantCreateString, finalString = nil); } } else { buffer2[buffer2Length] = buffer[i]; @@ -416,20 +423,10 @@ static int escapeMapCompare(const void *ucharVoid, const void *mapVoid) { } } if (buffer2Length) { - CFStringRef buffer2String = - CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, - buffer2, buffer2Length, - kCFAllocatorNull); - require_action(buffer2String, cantCreateString, finalString = nil); - [finalString appendString:(NSString*)buffer2String]; - CFRelease(buffer2String); + CFStringAppendCharacters((CFMutableStringRef)finalString, + buffer2, + buffer2Length); } -cantCreateString: - free(buffer2); -cantAllocBuffer2: - free(buffer); -cantAllocBuffer: -cantConvertAnything: return finalString; } diff --git a/Foundation/GTMNSString+HTMLTest.m b/Foundation/GTMNSString+HTMLTest.m index b60ed9d..c7b931a 100644 --- a/Foundation/GTMNSString+HTMLTest.m +++ b/Foundation/GTMNSString+HTMLTest.m @@ -16,7 +16,6 @@ // the License. // -#import <SenTestingKit/SenTestingKit.h> #import "GTMSenTestCase.h" #import "GTMNSString+HTML.h" diff --git a/Foundation/GTMNSString+Utilities.h b/Foundation/GTMNSString+Utilities.h new file mode 100644 index 0000000..3b4ee00 --- /dev/null +++ b/Foundation/GTMNSString+Utilities.h @@ -0,0 +1,41 @@ +// +// GTMNSString+Utilities.h +// Misc NSString Utilities +// +// 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> + +@interface NSString (GTMNSStringUtilitiesAdditions) + +// Returns a a UTF16 buffer. Avoids copying the data if at all +// possible for fastest possible/least memory access to the underlying +// unicode characters (UTF16). This returned buffer is NOT null terminated. +// *DANGER* +// Since we avoid copying data you can only be guaranteed access to +// the bytes of the data for the lifetime of the string that you have extracted +// the data from. This exists to allow speedy access to the underlying buffer +// and guaranteed memory cleanup if memory needs to be allocated. +// Do not free the returned pointer. +// +// Args: +// length - returns the number of unichars in the buffer. Send in nil if +// you don't care. +// +// Returns: +// pointer to the buffer. Nil on failure. +- (const unichar*)gtm_UTF16StringWithLength:(size_t*)length; +@end diff --git a/Foundation/GTMNSString+Utilities.m b/Foundation/GTMNSString+Utilities.m new file mode 100644 index 0000000..3419d43 --- /dev/null +++ b/Foundation/GTMNSString+Utilities.m @@ -0,0 +1,48 @@ +// +// GTMNSString+Utilities.m +// Misc NSString Utilities +// +// 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 "GTMNSString+Utilities.h" + +@implementation NSString (GTMNSStringUtilitiesAdditions) + +- (const unichar*)gtm_UTF16StringWithLength:(size_t*)length { + size_t size = [self length]; + const UniChar *buffer = CFStringGetCharactersPtr((CFStringRef)self); + if (!buffer) { + size_t memsize = size * sizeof(UniChar); + + // nope, alloc buffer and fetch the chars ourselves + buffer = malloc(memsize); + if (!buffer) { + // COV_NF_BEGIN - Memory fail case + _GTMDevLog(@"couldn't alloc buffer"); + return nil; + // COV_NF_END + } + [self getCharacters:(void*)buffer]; + [NSData dataWithBytesNoCopy:(void*)buffer length:size]; + } + if (length) { + *length = size; + } + return buffer; +} + +@end diff --git a/Foundation/GTMNSString+UtilitiesTest.m b/Foundation/GTMNSString+UtilitiesTest.m new file mode 100644 index 0000000..8394aaf --- /dev/null +++ b/Foundation/GTMNSString+UtilitiesTest.m @@ -0,0 +1,62 @@ +// +// GTMNSString+UtilitiesTest.m +// Misc NSString Utilities +// +// Copyright 2006-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + + +#import "GTMSenTestCase.h" +#import "GTMNSString+Utilities.h" + +@interface GTMNSString_UtilitiesTest : SenTestCase +@end + +@implementation GTMNSString_UtilitiesTest + +- (void)testStringWithLength { + NSString *string = @""; + size_t length; + const unichar *buffer = [string gtm_UTF16StringWithLength:&length]; + STAssertNotNULL(buffer, @"Buffer shouldn't be NULL"); + STAssertEquals(length, 0LU, @"Length should be 0"); + + UniChar unicharBytes[] = { 0x50, 0x51, 0x52 }; + string = [[[NSString alloc] initWithCharactersNoCopy:unicharBytes + length:3 + freeWhenDone:NO] autorelease]; + buffer = [string gtm_UTF16StringWithLength:&length]; + STAssertEquals(buffer, + (const unichar*)unicharBytes, + @"Pointers should be equal"); + STAssertEquals(length, + 3UL, + nil); + + char utf8Bytes[] = { 0x50, 0x51, 0x52, 0x0 }; + string = [NSString stringWithUTF8String:utf8Bytes]; + buffer = [string gtm_UTF16StringWithLength:&length]; + STAssertNotEquals(buffer, + (const unichar*)utf8Bytes, + @"Pointers should not be equal"); + STAssertEquals(length, + 3UL, + nil); + buffer = [string gtm_UTF16StringWithLength:nil]; + STAssertNotEquals(buffer, + (const unichar*)utf8Bytes, + @"Pointers should not be equal"); +} +@end diff --git a/Foundation/GTMNSString+XML.m b/Foundation/GTMNSString+XML.m index c08f7b7..7ea97b4 100644 --- a/Foundation/GTMNSString+XML.m +++ b/Foundation/GTMNSString+XML.m @@ -16,8 +16,11 @@ // the License. // +#import "GTMDefines.h" #import "GTMNSString+XML.h" #import "GTMGarbageCollection.h" +#import "GTMNSString+Utilities.h" +#import "GTMMethodCheck.h" typedef enum { kGMXMLCharModeEncodeQUOT = 0, @@ -88,6 +91,7 @@ FOUNDATION_STATIC_INLINE GMXMLCharMode XMLModeForUnichar(UniChar c) { return kGMXMLCharModeInvalid; } // XMLModeForUnichar + static NSString *AutoreleasedCloneForXML(NSString *src, BOOL escaping) { // // NOTE: @@ -99,23 +103,16 @@ static NSString *AutoreleasedCloneForXML(NSString *src, BOOL escaping) { // we can't use the CF call here because it leaves the invalid chars // in the string. - - NSMutableString *finalString = [NSMutableString string]; int length = [src length]; - require_quiet(length != 0, cantConvertAnything); - - // see if we can just use the interal version - BOOL freeBuffer = NO; - UniChar *buffer = (UniChar*)CFStringGetCharactersPtr((CFStringRef)src); - if (!buffer) { - // nope, alloc buffer and fetch the chars ourselves - buffer = malloc(sizeof(UniChar) * length); - require_action(buffer, cantCreateString, finalString = nil); - freeBuffer = YES; - [src getCharacters:buffer]; + if (!length) { + return nil; } - UniChar *goodRun = buffer; + NSMutableString *finalString = [NSMutableString string]; + const UniChar *buffer = [src gtm_UTF16StringWithLength:nil]; + _GTMDevAssert(buffer, @"couldn't alloc buffer"); + + const UniChar *goodRun = buffer; int goodRunLength = 0; for (int i = 0; i < length; ++i) { @@ -133,13 +130,9 @@ static NSString *AutoreleasedCloneForXML(NSString *src, BOOL escaping) { // start by adding what we already collected (if anything) if (goodRunLength) { - CFStringRef goodRunString = - CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, - goodRun, goodRunLength, - kCFAllocatorNull); - require_action(goodRunString != NULL, cantCreateString, finalString = nil); - [finalString appendString:(NSString*)goodRunString]; - CFRelease(goodRunString); + CFStringAppendCharacters((CFMutableStringRef)finalString, + goodRun, + goodRunLength); goodRunLength = 0; } @@ -156,23 +149,15 @@ static NSString *AutoreleasedCloneForXML(NSString *src, BOOL escaping) { // anything left to add? if (goodRunLength) { - CFStringRef goodRunString = - CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, - goodRun, goodRunLength, - kCFAllocatorNull); - require_action(goodRunString != NULL, cantCreateString2, finalString = nil); - [finalString appendString:(NSString*)goodRunString]; - CFRelease(goodRunString); + CFStringAppendCharacters((CFMutableStringRef)finalString, + goodRun, + goodRunLength); } -cantCreateString: -cantCreateString2: - if (freeBuffer) - free(buffer); -cantConvertAnything: return finalString; } // AutoreleasedCloneForXML @implementation NSString (GTMNSStringXMLAdditions) +GTM_METHOD_CHECK(NSString, gtm_UTF16StringWithLength:); - (NSString *)gtm_stringBySanitizingAndEscapingForXML { return AutoreleasedCloneForXML(self, YES); diff --git a/Foundation/GTMNSString+XMLTest.m b/Foundation/GTMNSString+XMLTest.m index 52834f5..926708f 100644 --- a/Foundation/GTMNSString+XMLTest.m +++ b/Foundation/GTMNSString+XMLTest.m @@ -17,7 +17,7 @@ // -#import <SenTestingKit/SenTestingKit.h> +#import "GTMSenTestCase.h" #import "GTMNSString+XML.h" @@ -27,33 +27,61 @@ @implementation GTMNSString_XMLTest - (void)testStringBySanitizingAndEscapingForXML { + // test the substitutions cases UniChar chars[] = { 'z', 0, 'z', 1, 'z', 4, 'z', 5, 'z', 34, 'z', 38, 'z', 39, 'z', - 60, 'z', 62, 'z', ' ', 'z', 0xd800, 'z', 0xDFFF, 'z', 0xFFFE, - 'z', 0xFFFF, 'z' }; + 60, 'z', 62, 'z', ' ', 'z', 0xd800, 'z', 0xDFFF, 'z', 0xE000, + 'z', 0xFFFE, 'z', 0xFFFF, 'z', '\n', 'z', '\r', 'z', '\t', 'z' }; NSString *string1 = [NSString stringWithCharacters:chars length:sizeof(chars) / sizeof(UniChar)]; - NSString *string2 = @"zzzzz"z&z'z<z>z zzzzz"; + NSString *string2 = + [NSString stringWithFormat:@"zzzzz"z&z'z<z>z zzz%Czzz\nz\rz\tz", + 0xE000]; STAssertEqualObjects([string1 gtm_stringBySanitizingAndEscapingForXML], string2, @"Sanitize and Escape for XML failed"); + + // force the backing store of the NSString to test extraction paths + char ascBuffer[] = "a\01bcde\nf"; + NSString *ascString = + [[[NSString alloc] initWithBytesNoCopy:ascBuffer + length:sizeof(ascBuffer) / sizeof(char) + encoding:NSASCIIStringEncoding + freeWhenDone:NO] autorelease]; + STAssertEqualObjects([ascString gtm_stringBySanitizingAndEscapingForXML], + @"abcde\nf", + @"Sanitize and Escape for XML from asc buffer failed"); } - (void)testStringBySanitizingToXMLSpec { + // test the substitutions cases UniChar chars[] = { 'z', 0, 'z', 1, 'z', 4, 'z', 5, 'z', 34, 'z', 38, 'z', 39, 'z', - 60, 'z', 62, 'z', ' ', 'z', 0xd800, 'z', 0xDFFF, 'z', 0xFFFE, - 'z', 0xFFFF, 'z' }; + 60, 'z', 62, 'z', ' ', 'z', 0xd800, 'z', 0xDFFF, 'z', 0xE000, + 'z', 0xFFFE, 'z', 0xFFFF, 'z', '\n', 'z', '\r', 'z', '\t', 'z' }; NSString *string1 = [NSString stringWithCharacters:chars length:sizeof(chars) / sizeof(UniChar)]; - NSString *string2 = @"zzzzz\"z&z'z<z>z zzzzz"; + NSString *string2 = + [NSString stringWithFormat:@"zzzzz\"z&z'z<z>z zzz%Czzz\nz\rz\tz", + 0xE000]; STAssertEqualObjects([string1 gtm_stringBySanitizingToXMLSpec], string2, @"Sanitize for XML failed"); + + // force the backing store of the NSString to test extraction paths + char ascBuffer[] = "a\01bcde\nf"; + NSString *ascString = + [[[NSString alloc] initWithBytesNoCopy:ascBuffer + length:sizeof(ascBuffer) / sizeof(char) + encoding:NSASCIIStringEncoding + freeWhenDone:NO] autorelease]; + STAssertEqualObjects([ascString gtm_stringBySanitizingToXMLSpec], + @"abcde\nf", + @"Sanitize and Escape for XML from asc buffer failed"); } @end diff --git a/Foundation/GTMObjC2Runtime.h b/Foundation/GTMObjC2Runtime.h new file mode 100644 index 0000000..325a752 --- /dev/null +++ b/Foundation/GTMObjC2Runtime.h @@ -0,0 +1,54 @@ +// +// GTMObjC2Runtime.h +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import <objc/objc-runtime.h> +#import <objc/Object.h> + +// These functions exist for code that we want to compile on both the < 10.5 +// sdks and on the >= 10.5 sdks without warnings. It basically reimplements +// certain parts of the objc2 runtime in terms of the objc1 runtime. It is not +// a complete implementation as I've only implemented the routines I know we +// use. Feel free to add more as necessary. +// These functions are not documented because they conform to the documentation +// for the ObjC2 Runtime. + +#if OBJC_API_VERSION >= 2 // Only have optional and req'd keywords in ObjC2. +#define AT_OPTIONAL @optional +#define AT_REQUIRED @required +#else +#define AT_OPTIONAL +#define AT_REQUIRED +#endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#import "objc/Protocol.h" + +Class object_getClass(id obj); +const char *class_getName(Class cls); +BOOL class_conformsToProtocol(Class cls, Protocol *protocol); +Class class_getSuperclass(Class cls); +Method *class_copyMethodList(Class cls, unsigned int *outCount); +SEL method_getName(Method m); +void method_exchangeImplementations(Method m1, Method m2); +IMP method_getImplementation(Method method); +IMP method_setImplementation(Method method, IMP imp); +struct objc_method_description protocol_getMethodDescription(Protocol *p, + SEL aSel, + BOOL isRequiredMethod, + BOOL isInstanceMethod); +#endif // OBJC2_UNAVAILABLE diff --git a/Foundation/GTMObjC2Runtime.m b/Foundation/GTMObjC2Runtime.m new file mode 100644 index 0000000..00a3c6e --- /dev/null +++ b/Foundation/GTMObjC2Runtime.m @@ -0,0 +1,148 @@ +// +// GTMObjC2Runtime.m +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMObjC2Runtime.h" + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 +#import <stdlib.h> +#import <string.h> + +Class object_getClass(id obj) { + if (!obj) return NULL; + return obj->isa; +} + +const char *class_getName(Class cls) { + if (!cls) return "nil"; + return cls->name; +} + +BOOL class_conformsToProtocol(Class cls, Protocol *protocol) { + // We intentionally don't check cls as it crashes on Leopard so we want + // to crash on Tiger as well. + // I logged + // Radar 5572978 class_conformsToProtocol crashes when arg1 is passed as nil + // because it seems odd that this API won't accept nil for cls considering + // all the other apis will accept nil args. + // If this does get fixed, remember to enable the unit tests. + if (!protocol) return NO; + + struct objc_protocol_list *protos; + for (protos = cls->protocols; protos != NULL; protos = protos->next) { + for (int i = 0; i < protos->count; i++) { + if ([protos->list[i] conformsTo:protocol]) { + return YES; + } + } + } + return NO; +} + +Class class_getSuperclass(Class cls) { + if (!cls) return NULL; + return cls->super_class; +} + +Method *class_copyMethodList(Class cls, unsigned int *outCount) { + if (!cls) return NULL; + + unsigned int count = 0; + void *iterator = NULL; + struct objc_method_list *mlist; + Method *methods = NULL; + if (outCount) *outCount = 0; + + while ( (mlist = class_nextMethodList(cls, &iterator)) ) { + if (mlist->method_count == 0) continue; + methods = (Method *)realloc(methods, + sizeof(Method) * (count + mlist->method_count + 1)); + if (!methods) { + //Memory alloc failed, so what can we do? + return NULL; // COV_NF_LINE + } + for (int i = 0; i < mlist->method_count; i++) { + methods[i + count] = &mlist->method_list[i]; + } + count += mlist->method_count; + } + + // List must be NULL terminated + if (methods) { + methods[count] = NULL; + } + if (outCount) *outCount = count; + return methods; +} + +SEL method_getName(Method method) { + if (!method) return NULL; + return method->method_name; +} + +IMP method_getImplementation(Method method) { + if (!method) return NULL; + return method->method_imp; +} + +IMP method_setImplementation(Method method, IMP imp) { + // We intentionally don't test method for nil. + // Leopard fails here, so should we. + // I logged this as Radar: + // 5572981 method_setImplementation crashes if you pass nil for the + // method arg (arg 1) + // because it seems odd that this API won't accept nil for method considering + // all the other apis will accept nil args. + // If this does get fixed, remember to enable the unit tests. + IMP oldImp = method->method_imp; + method->method_imp = imp; + return oldImp; +} + +void method_exchangeImplementations(Method m1, Method m2) { + if (m1 == m2) return; + if (!m1 || !m2) return; + IMP imp2 = method_getImplementation(m2); + IMP imp1 = method_setImplementation(m1, imp2); + method_setImplementation(m2, imp1); +} + +struct objc_method_description protocol_getMethodDescription(Protocol *p, + SEL aSel, + BOOL isRequiredMethod, + BOOL isInstanceMethod) { + struct objc_method_description *descPtr = NULL; + // No such thing as required in ObjC1. + if (isInstanceMethod) { + descPtr = [p descriptionForInstanceMethod:aSel]; + } else { + descPtr = [p descriptionForClassMethod:aSel]; + } + + struct objc_method_description desc; + if (descPtr) { + desc = *descPtr; + } else { + bzero(&desc, sizeof(desc)); + } + return desc; +} + + +#endif + + diff --git a/Foundation/GTMObjC2RuntimeTest.m b/Foundation/GTMObjC2RuntimeTest.m new file mode 100644 index 0000000..2a7f354 --- /dev/null +++ b/Foundation/GTMObjC2RuntimeTest.m @@ -0,0 +1,385 @@ +// +// GTMObjC2RuntimeTest.m +// +// Copyright 2007-2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMObjC2Runtime.h" +#import "GTMSenTestCase.h" +#import <string.h> + +@protocol GTMObjC2Runtime_TestProtocol +@end + +@protocol GTMObjC2Runtime_Test2Protocol +AT_OPTIONAL +- (NSString*)optional; +AT_REQUIRED +- (NSString*)required; +AT_OPTIONAL ++ (NSString*)class_optional; +AT_REQUIRED ++ (NSString*)class_required; +@end + +@interface GTMObjC2RuntimeTest : SenTestCase { + Class cls_; +} +@end + +@interface GTMObjC2Runtime_TestClass : NSObject <GTMObjC2Runtime_TestProtocol> +- (NSString*)kwyjibo; + +@end + +@interface GTMObjC2Runtime_TestClass (GMObjC2Runtime_TestClassCategory) +- (NSString*)eatMyShorts; +@end + +@implementation GTMObjC2Runtime_TestClass + ++ (NSString*)dontHaveACow { + return @"dontHaveACow"; +} + +- (NSString*)kwyjibo { + return @"kwyjibo"; +} +@end + +@implementation GTMObjC2Runtime_TestClass (GMObjC2Runtime_TestClassCategory) +- (NSString*)eatMyShorts { + return @"eatMyShorts"; +} + ++ (NSString*)brokeHisBrain { + return @"brokeHisBrain"; +} + +@end + +@interface GTMObjC2NotificationWatcher : NSObject +@end + +@implementation GTMObjC2NotificationWatcher +- (void)startedTest:(NSNotification *)notification { + // Logs if we are testing on Tiger or Leopard runtime. + NSString *testName = [(SenTest*)[[notification object] test] name]; + NSString *className = NSStringFromClass([GTMObjC2RuntimeTest class]); + if ([testName isEqualToString:className]) { + NSString *runtimeString; +#ifndef OBJC2_UNAVAILABLE + runtimeString = @"ObjC1"; +#else + runtimeString = @"ObjC2"; +#endif + NSLog(@"Running GTMObjC2RuntimeTests using %@ runtime.", runtimeString); + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc removeObserver:self]; + [self autorelease]; + } +} +@end + +@implementation GTMObjC2RuntimeTest + ++ (void)initialize { + // This allows us to track which runtime we are actually testing. + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + + // Watcher is released when it is notified. + GTMObjC2NotificationWatcher *watcher = [[GTMObjC2NotificationWatcher alloc] init]; + [nc addObserver:watcher + selector:@selector(startedTest:) + name:SenTestSuiteDidStartNotification + object:nil]; +} + +- (void)setUp { + cls_ = [[GTMObjC2Runtime_TestClass class] retain]; +} + +- (void)tearDown { + [cls_ release]; +} + +- (void)test_object_getClass { + // Nil Checks + STAssertNil(object_getClass(nil), nil); + + // Standard use check + GTMObjC2Runtime_TestClass *test = [[[cls_ alloc] init] autorelease]; + Class cls = object_getClass(test); + STAssertEqualObjects(cls, cls_, nil); +} + +- (void)test_class_getName { + // Nil Checks + const char *name = class_getName(nil); + STAssertEqualCStrings(name, "nil", nil); + + // Standard use check + STAssertEqualCStrings(class_getName(cls_), "GTMObjC2Runtime_TestClass", nil); +} + +- (void)test_class_conformsToProtocol { + // Nil Checks + STAssertFalse(class_conformsToProtocol(cls_, @protocol(NSObject)), nil); + STAssertFalse(class_conformsToProtocol(cls_, nil), nil); + // The following two tests intentionally commented out as they fail on + // Leopard with a crash, so we fail on Tiger intentionally as well. + // STAssertFalse(class_conformsToProtocol(nil, @protocol(NSObject)), nil); + // STAssertFalse(class_conformsToProtocol(nil, nil), nil); + + // Standard use check + STAssertTrue(class_conformsToProtocol(cls_, + @protocol(GTMObjC2Runtime_TestProtocol)), + nil); +} + +- (void)test_class_getSuperclass { + // Nil Checks + STAssertNil(class_getSuperclass(nil), nil); + + // Standard use check + STAssertEqualObjects(class_getSuperclass(cls_), [NSObject class], nil); +} + +- (void)test_class_copyMethodList { + // Nil Checks + Method *list = class_copyMethodList(nil, nil); + STAssertNULL(list, nil); + + // Standard use check + list = class_copyMethodList(cls_, nil); + STAssertNotNULL(list, nil); + free(list); + unsigned int count = 0; + list = class_copyMethodList(cls_, &count); + STAssertNotNULL(list, nil); + STAssertEquals(count, 2U, nil); + STAssertNULL(list[count], nil); + free(list); + + // Now test meta class + count = 0; + list = class_copyMethodList((Class)objc_getMetaClass(class_getName(cls_)), + &count); + STAssertNotNULL(list, nil); + STAssertEquals(count, 2U, nil); + STAssertNULL(list[count], nil); + free(list); +} + +- (void)test_method_getName { + // Nil Checks + STAssertNULL(method_getName(nil), nil); + + // Standard use check + Method *list = class_copyMethodList(cls_, nil); + STAssertNotNULL(list, nil); + const char* selName1 = sel_getName(method_getName(list[0])); + const char* selName2 = sel_getName(@selector(kwyjibo)); + const char* selName3 = sel_getName(@selector(eatMyShorts)); + BOOL isGood = ((strcmp(selName1, selName2)) == 0 || (strcmp(selName1, selName3) == 0)); + STAssertTrue(isGood, nil); + free(list); +} + +- (void)test_method_exchangeImplementations { + // nil checks + method_exchangeImplementations(nil, nil); + + // Standard use check + GTMObjC2Runtime_TestClass *test = [[GTMObjC2Runtime_TestClass alloc] init]; + STAssertNotNil(test, nil); + + // Get initial values + NSString *val1 = [test kwyjibo]; + STAssertNotNil(val1, nil); + NSString *val2 = [test eatMyShorts]; + STAssertNotNil(val2, nil); + NSString *val3 = [GTMObjC2Runtime_TestClass dontHaveACow]; + STAssertNotNil(val3, nil); + NSString *val4 = [GTMObjC2Runtime_TestClass brokeHisBrain]; + STAssertNotNil(val4, nil); + + // exchange the imps + Method *list = class_copyMethodList(cls_, nil); + STAssertNotNULL(list, nil); + method_exchangeImplementations(list[0], list[1]); + + // test against initial values + NSString *val5 = [test kwyjibo]; + STAssertNotNil(val5, nil); + NSString *val6 = [test eatMyShorts]; + STAssertNotNil(val6, nil); + STAssertEqualStrings(val1, val6, nil); + STAssertEqualStrings(val2, val5, nil); + + // Check that other methods not affected + STAssertEqualStrings([GTMObjC2Runtime_TestClass dontHaveACow], val3, nil); + STAssertEqualStrings([GTMObjC2Runtime_TestClass brokeHisBrain], val4, nil); + + // exchange the imps back + method_exchangeImplementations(list[0], list[1]); + + // and test against initial values again + NSString *val7 = [test kwyjibo]; + STAssertNotNil(val7, nil); + NSString *val8 = [test eatMyShorts]; + STAssertNotNil(val8, nil); + STAssertEqualStrings(val1, val7, nil); + STAssertEqualStrings(val2, val8, nil); + + method_exchangeImplementations(list[0], nil); + method_exchangeImplementations(nil, list[0]); + + val7 = [test kwyjibo]; + STAssertNotNil(val7, nil); + val8 = [test eatMyShorts]; + STAssertNotNil(val8, nil); + STAssertEqualStrings(val1, val7, nil); + STAssertEqualStrings(val2, val8, nil); + + free(list); + [test release]; +} + +- (void)test_method_getImplementation { + // Nil Checks + STAssertNULL(method_getImplementation(nil), nil); + + // Standard use check + Method *list = class_copyMethodList(cls_, nil); + STAssertNotNULL(list, nil); + STAssertNotNULL(method_getImplementation(list[0]), nil); + free(list); +} + +- (void)test_method_setImplementation { + // Nil Checks + // This case intentionally not tested. Passing nil to method_setImplementation + // on Leopard crashes. It does on Tiger as well. + // STAssertNULL(method_setImplementation(nil, nil), nil); + + // Standard use check + GTMObjC2Runtime_TestClass *test = [[GTMObjC2Runtime_TestClass alloc] init]; + Method *list = class_copyMethodList(cls_, nil); + + // Get initial value + NSString *str1 = objc_msgSend(test, method_getName(list[0])); + STAssertNotNil(str1, nil); + + // set the imp to something else + IMP oldImp = method_setImplementation(list[0], method_getImplementation(list[1])); + STAssertNotNULL(oldImp, nil); + + // make sure they are different + NSString *str2 = objc_msgSend(test,method_getName(list[0])); + STAssertNotNil(str2, nil); + STAssertNotEqualStrings(str1, str2, nil); + + // reset the imp + IMP newImp = method_setImplementation(list[0], oldImp); + STAssertNotEquals(oldImp, newImp, nil); + + // test nils + oldImp = method_setImplementation(list[0], nil); + STAssertNotNULL(oldImp, nil); + + newImp = method_setImplementation(list[0], oldImp); + STAssertNULL(newImp, nil); + + [test release]; + free(list); +} + +- (void)test_protocol_getMethodDescription { + // Check nil cases + struct objc_method_description desc = protocol_getMethodDescription(nil, nil, + YES, YES); + STAssertNULL(desc.name, nil); + desc = protocol_getMethodDescription(nil, @selector(optional), YES, YES); + STAssertNULL(desc.name, nil); + desc = protocol_getMethodDescription(@protocol(GTMObjC2Runtime_Test2Protocol), + nil, YES, YES); + STAssertNULL(desc.name, nil); + + // Instance Methods + // Check Required case. Only OBJC2 supports required. + desc = protocol_getMethodDescription(@protocol(GTMObjC2Runtime_Test2Protocol), + @selector(optional), YES, YES); +#if OBJC_API_VERSION >= 2 + STAssertNULL(desc.name, nil); +#else + STAssertNotNULL(desc.name, nil); +#endif + + // Check Required case. Only OBJC2 supports required. + desc = protocol_getMethodDescription(@protocol(GTMObjC2Runtime_Test2Protocol), + @selector(required), YES, YES); + + STAssertNotNULL(desc.name, nil); + + // Check Optional case. Only OBJC2 supports optional. + desc = protocol_getMethodDescription(@protocol(GTMObjC2Runtime_Test2Protocol), + @selector(optional), NO, YES); + + STAssertNotNULL(desc.name, nil); + + // Check Optional case. Only OBJC2 supports optional. + desc = protocol_getMethodDescription(@protocol(GTMObjC2Runtime_Test2Protocol), + @selector(required), NO, YES); +#if OBJC_API_VERSION >= 2 + STAssertNULL(desc.name, nil); +#else + STAssertNotNULL(desc.name, nil); +#endif + + // Class Methods + // Check Required case. Only OBJC2 supports required. + desc = protocol_getMethodDescription(@protocol(GTMObjC2Runtime_Test2Protocol), + @selector(class_optional), YES, NO); +#if OBJC_API_VERSION >= 2 + STAssertNULL(desc.name, nil); +#else + STAssertNotNULL(desc.name, nil); +#endif + + // Check Required case. Only OBJC2 supports required. + desc = protocol_getMethodDescription(@protocol(GTMObjC2Runtime_Test2Protocol), + @selector(class_required), YES, NO); + + STAssertNotNULL(desc.name, nil); + + // Check Optional case. Only OBJC2 supports optional. + desc = protocol_getMethodDescription(@protocol(GTMObjC2Runtime_Test2Protocol), + @selector(class_optional), NO, NO); + + STAssertNotNULL(desc.name, nil); + + // Check Optional case. Only OBJC2 supports optional. + desc = protocol_getMethodDescription(@protocol(GTMObjC2Runtime_Test2Protocol), + @selector(class_required), NO, NO); +#if OBJC_API_VERSION >= 2 + STAssertNULL(desc.name, nil); +#else + STAssertNotNULL(desc.name, nil); +#endif + +} + +@end diff --git a/Foundation/GTMObjectSingleton.h b/Foundation/GTMObjectSingleton.h index 9bdeb0a..116d232 100644 --- a/Foundation/GTMObjectSingleton.h +++ b/Foundation/GTMObjectSingleton.h @@ -17,6 +17,8 @@ // the License. // +#import "GTMDefines.h" + /// This macro implements the various methods needed to make a safe singleton. // /// This Singleton pattern was taken from: @@ -35,7 +37,7 @@ static _object_name_ *z##_shared_obj_name_ = nil; \ /* Note that 'self' may not be the same as _object_name_ */ \ /* first assignment done in allocWithZone but we must reassign in case init fails */ \ z##_shared_obj_name_ = [[self alloc] init]; \ - NSAssert((z##_shared_obj_name_ != nil), @"didn't catch singleton allocation"); \ + _GTMDevAssert((z##_shared_obj_name_ != nil), @"didn't catch singleton allocation"); \ } \ } \ return z##_shared_obj_name_; \ @@ -49,7 +51,7 @@ static _object_name_ *z##_shared_obj_name_ = nil; \ } \ \ /* We can't return the shared instance, because it's been init'd */ \ - NSAssert(NO, @"use the singleton API, not alloc+init"); \ + _GTMDevAssert(NO, @"use the singleton API, not alloc+init"); \ return nil; \ } \ - (id)retain { \ diff --git a/Foundation/GTMRegex.h b/Foundation/GTMRegex.h index 8e0f492..ee56b98 100644 --- a/Foundation/GTMRegex.h +++ b/Foundation/GTMRegex.h @@ -50,6 +50,28 @@ typedef enum { } GTMRegexOptions; +/// Global contants needed for errors from consuming patterns + +#undef _EXTERN +#undef _INITIALIZE_AS +#ifdef GTMREGEX_DEFINE_GLOBALS +#define _EXTERN +#define _INITIALIZE_AS(x) =x +#else +#define _EXTERN extern +#define _INITIALIZE_AS(x) +#endif + +_EXTERN NSString* kGTMRegexErrorDomain _INITIALIZE_AS(@"com.google_toolbox_for_mac.GTMRegexDomain"); + +enum { + kGTMRegexPatternParseFailedError = -100 +}; + +// Keys for the userInfo from a kGTMRegexErrorDomain/kGTMRegexPatternParseFailedError error +_EXTERN NSString* kGTMRegexPatternErrorPattern _INITIALIZE_AS(@"pattern"); +_EXTERN NSString* kGTMRegexPatternErrorErrorString _INITIALIZE_AS(@"patternError"); + /// Class for doing Extended Regex operations w/ libregex (see re_format(7)). // // NOTE: the docs for recomp/regexec make *no* claims about i18n. All work @@ -102,6 +124,11 @@ typedef enum { /// Create a new, autoreleased object w/ the given regex pattern and specify the matching options + (id)regexWithPattern:(NSString *)pattern options:(GTMRegexOptions)options; +/// Create a new, autoreleased object w/ the given regex pattern, specify the matching options and receive any error consuming the pattern. ++ (id)regexWithPattern:(NSString *)pattern + options:(GTMRegexOptions)options + withError:(NSError **)outErrorOrNULL; + /// Returns a new, autoreleased copy of |str| w/ any pattern chars in it escaped so they have no meaning when used w/in a pattern. + (NSString *)escapedPatternForString:(NSString *)str; @@ -111,6 +138,11 @@ typedef enum { /// Initialize a new object w/ the given regex pattern and specify the matching options - (id)initWithPattern:(NSString *)pattern options:(GTMRegexOptions)options; +/// Initialize a new object w/ the given regex pattern, specify the matching options, and receive any error consuming the pattern. +- (id)initWithPattern:(NSString *)pattern + options:(GTMRegexOptions)options + withError:(NSError **)outErrorOrNULL; + /// Returns the number of sub patterns in the pattern // // Sub Patterns are basically the number of parenthesis blocks w/in the pattern. @@ -140,6 +172,12 @@ typedef enum { // - (NSArray *)subPatternsOfString:(NSString *)str; +/// Returns the first match for this pattern in |str|. +- (NSString *)firstSubStringMatchedInString:(NSString *)str; + +/// Returns YES if this pattern some substring of |str|. +- (BOOL)matchesSubStringInString:(NSString *)str; + /// Returns a new, autoreleased enumerator that will walk segments (GTMRegexStringSegment) of |str| based on the pattern. // // This will split the string into "segments" using the given pattern. You get @@ -307,6 +345,9 @@ typedef enum { /// Returns a new, autoreleased string w/ the first substring that matched the regex |pattern| using the default match options - (NSString *)gtm_firstSubStringMatchedByPattern:(NSString *)pattern; +/// Returns YES if a substring string matches regex |pattern| using the default match options +- (BOOL)gtm_subStringMatchesPattern:(NSString *)pattern; + /// Returns a new, autoreleased array of substrings in the string that match the regex |pattern| using the default match options // // Note: if the string has no matches, you get an empty array. diff --git a/Foundation/GTMRegex.m b/Foundation/GTMRegex.m index d7900fa..c50ff2b 100644 --- a/Foundation/GTMRegex.m +++ b/Foundation/GTMRegex.m @@ -16,7 +16,9 @@ // the License. // +#define GTMREGEX_DEFINE_GLOBALS 1 #import "GTMRegex.h" +#import "GTMDefines.h" // This is the pattern to use for walking replacement text when doing // substitutions. @@ -71,6 +73,14 @@ static NSString *const kReplacementPattern = options:options] autorelease]; } ++ (id)regexWithPattern:(NSString *)pattern + options:(GTMRegexOptions)options + withError:(NSError **)outErrorOrNULL { + return [[[self alloc] initWithPattern:pattern + options:options + withError:outErrorOrNULL] autorelease]; +} + + (NSString *)escapedPatternForString:(NSString *)str { if (str == nil) return nil; @@ -117,9 +127,18 @@ static NSString *const kReplacementPattern = } - (id)initWithPattern:(NSString *)pattern options:(GTMRegexOptions)options { + return [self initWithPattern:pattern options:options withError:nil]; +} + +- (id)initWithPattern:(NSString *)pattern + options:(GTMRegexOptions)options + withError:(NSError **)outErrorOrNULL { + self = [super init]; if (!self) return nil; + if (outErrorOrNULL) *outErrorOrNULL = nil; + if ([pattern length] == 0) { [self release]; return nil; @@ -138,17 +157,31 @@ static NSString *const kReplacementPattern = // error info). we use pattern_ as this flag. pattern_ = [pattern copy]; if (!pattern_) { + // COV_NF_START - no real way to force this in a unittest [self release]; return nil; + // COV_NF_END } // compile it int compResult = regcomp(®exData_, [pattern_ UTF8String], flags); if (compResult != 0) { - // we don't want to throw if we failed, so we'll return nil, but still - // log the error just so it's out there. NSString *errorStr = [self errorMessage:compResult]; - NSLog(@"Invalid pattern \"%@\", error: \"%@\"", pattern_, errorStr); + if (outErrorOrNULL) { + // include the pattern and patternError message in the userInfo. + NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + pattern_, kGTMRegexPatternErrorPattern, + errorStr, kGTMRegexPatternErrorErrorString, + nil]; + *outErrorOrNULL = [NSError errorWithDomain:kGTMRegexErrorDomain + code:kGTMRegexPatternParseFailedError + userInfo:userInfo]; + } else { + // if caller didn't get us an NSError to fill in, we log the error to help + // debugging. + _GTMDevLog(@"Invalid pattern \"%@\", error: \"%@\"", + pattern_, errorStr); + } [self release]; return nil; @@ -193,7 +226,7 @@ static NSString *const kReplacementPattern = int count = regexData_.re_nsub + 1; regmatch_t *regMatches = malloc(sizeof(regmatch_t) * count); if (!regMatches) - return nil; + return nil; // COV_NF_LINE - no real way to force this in a unittest // wrap it all in a try so we don't leak the malloc @try { @@ -232,7 +265,7 @@ static NSString *const kReplacementPattern = } result = buildResult; - } + } // COV_NF_LINE - radar 5851992 not all brackets reachable w/ obj-c exceptions and coverage @finally { free(regMatches); } @@ -240,6 +273,38 @@ static NSString *const kReplacementPattern = return result; } +- (NSString *)firstSubStringMatchedInString:(NSString *)str { + NSString *result = nil; + + regmatch_t regMatch; + const char *utf8Str = [str UTF8String]; + if ([self runRegexOnUTF8:utf8Str + nmatch:1 + pmatch:®Match + flags:0]) { + // fetch the string + const char *base = utf8Str + regMatch.rm_so; + unsigned len = regMatch.rm_eo - regMatch.rm_so; + result = + [[[NSString alloc] initWithBytes:base + length:len + encoding:NSUTF8StringEncoding] autorelease]; + } + return result; +} + +- (BOOL)matchesSubStringInString:(NSString *)str { + regmatch_t regMatch; + if ([self runRegexOnUTF8:[str UTF8String] + nmatch:1 + pmatch:®Match + flags:0]) { + // don't really care what matched, just report the match + return YES; + } + return NO; +} + - (NSEnumerator *)segmentEnumeratorForString:(NSString *)str { return [[[GTMRegexEnumerator alloc] initWithRegex:self processString:str @@ -266,8 +331,9 @@ static NSString *const kReplacementPattern = [GTMRegex regexWithPattern:kReplacementPattern options:kGTMRegexOptionSupressNewlineSupport]; #ifdef DEBUG - if (!replacementRegex) - NSLog(@"failed to parse out replacement regex!!!"); + if (!replacementRegex) { + _GTMDevLog(@"failed to parse out replacement regex!!!"); // COV_NF_LINE + } #endif GTMRegexEnumerator *relacementEnumerator = [[[GTMRegexEnumerator alloc] initWithRegex:replacementRegex @@ -292,8 +358,10 @@ static NSString *const kReplacementPattern = // pull them all into an array so we can walk this as many times as needed. replacements = [relacementEnumerator allObjects]; if (!replacements) { - NSLog(@"failed to create the replacements for subtituations"); + // COV_NF_START - no real way to force this in a unittest + _GTMDevLog(@"failed to create the replacements for substitutions"); return nil; + // COV_NF_END } } @@ -388,9 +456,11 @@ static NSString *const kReplacementPattern = if (execResult != 0) { #ifdef DEBUG if (execResult != REG_NOMATCH) { + // COV_NF_START - no real way to force this in a unittest NSString *errorStr = [self errorMessage:execResult]; - NSLog(@"%@: matching string \"%.20s...\", had error: \"%@\"", - self, utf8Str, errorStr); + _GTMDevLog(@"%@: matching string \"%.20s...\", had error: \"%@\"", + self, utf8Str, errorStr); + // COV_NF_END } #endif return NO; @@ -402,9 +472,8 @@ static NSString *const kReplacementPattern = @implementation GTMRegexEnumerator -- (id)init { - return [self initWithRegex:nil processString:nil allSegments:NO]; -} +// we don't block init because the class isn't exported, so no one can +// create one, or if they do, they get whatever happens... - (id)initWithRegex:(GTMRegex *)regex processString:(NSString *)str @@ -480,7 +549,7 @@ static NSString *const kReplacementPattern = size_t matchBufSize = ([regex_ subPatternCount] + 1) * sizeof(regmatch_t); nextMatches = malloc(matchBufSize); if (!nextMatches) - return nil; + return nil; // COV_NF_LINE - no real way to force this in a unittest // setup our range to work on nextMatches[0].rm_so = curParseIndex_; @@ -511,7 +580,7 @@ static NSString *const kReplacementPattern = savedRegMatches_ = nextMatches; nextMatches = malloc(matchBufSize); if (!nextMatches) - return nil; + return nil; // COV_NF_LINE - no real way to force this in a unittest isMatch = NO; // mark everything but the zero slot w/ not used @@ -568,14 +637,13 @@ static NSString *const kReplacementPattern = isMatch:isMatch] autorelease]; nextMatches = nil; } - } + } // COV_NF_START - no real way to force this in a test @catch (id e) { - NSLog(@"Exceptions while trying to advance enumeration (%@)", e); - } - - // if we still have something in our temp, free it - if (nextMatches) - free(nextMatches); + _GTMDevLog(@"Exceptions while trying to advance enumeration (%@)", e); + // if we still have something in our temp, free it + if (nextMatches) + free(nextMatches); + } // COV_NF_END return result; } @@ -593,10 +661,10 @@ static NSString *const kReplacementPattern = @implementation GTMRegexStringSegment - (id)init { - return [self initWithUTF8StrBuf:nil - regMatches:nil - numRegMatches:0 - isMatch:NO]; + // make sure init is never called, the class in in the header so someone + // could try to create it by mistake. + [self doesNotRecognizeSelector:_cmd]; + return nil; // COV_NF_LINE - return is just here to keep gcc happy } - (void)dealloc { @@ -669,8 +737,11 @@ static NSString *const kReplacementPattern = // check the args if (!utf8StrBuf_ || !regMatches_ || (numRegMatches_ < 0)) { + // COV_NF_START + // this could only happen something messed w/ our internal state. [self release]; return nil; + // COV_NF_END } return self; @@ -692,9 +763,12 @@ static NSString *const kReplacementPattern = - (NSString *)gtm_firstSubStringMatchedByPattern:(NSString *)pattern { GTMRegex *regex = [GTMRegex regexWithPattern:pattern]; - NSEnumerator *enumerator = [regex matchSegmentEnumeratorForString:self]; - GTMRegexStringSegment *firstMatch = [enumerator nextObject]; - return [firstMatch string]; + return [regex firstSubStringMatchedInString:self]; +} + +- (BOOL)gtm_subStringMatchesPattern:(NSString *)pattern { + GTMRegex *regex = [GTMRegex regexWithPattern:pattern]; + return [regex matchesSubStringInString:self]; } - (NSArray *)gtm_allSubstringsMatchedByPattern:(NSString *)pattern { diff --git a/Foundation/GTMRegexTest.m b/Foundation/GTMRegexTest.m index 71c8405..22c571e 100644 --- a/Foundation/GTMRegexTest.m +++ b/Foundation/GTMRegexTest.m @@ -16,8 +16,6 @@ // the License. // -#import <SenTestingKit/SenTestingKit.h> - #import "GTMSenTestCase.h" #import "GTMRegex.h" @@ -62,6 +60,22 @@ STAssertNil([[[GTMRegex alloc] initWithPattern:@"(."] autorelease], nil); STAssertNil([[[GTMRegex alloc] initWithPattern:@"(." options:kGTMRegexOptionIgnoreCase] autorelease], nil); + // fail cases w/ error param + NSError *error = nil; + STAssertNil([[[GTMRegex alloc] initWithPattern:nil + options:kGTMRegexOptionIgnoreCase + withError:&error] autorelease], nil); + STAssertNil(error, @"no pattern, shouldn't get error object"); + STAssertNil([[[GTMRegex alloc] initWithPattern:@"(." + options:kGTMRegexOptionIgnoreCase + withError:&error] autorelease], nil); + STAssertNotNil(error, nil); + STAssertEqualObjects([error domain], kGTMRegexErrorDomain, nil); + STAssertEquals([error code], kGTMRegexPatternParseFailedError, nil); + NSDictionary *userInfo = [error userInfo]; + STAssertNotNil(userInfo, @"failed to get userInfo from error"); + STAssertEqualObjects([userInfo objectForKey:kGTMRegexPatternErrorPattern], @"(.", nil); + STAssertNotNil([userInfo objectForKey:kGTMRegexPatternErrorErrorString], nil); // basic pattern w/ options STAssertNotNil([[[GTMRegex alloc] initWithPattern:@"(.*)"] autorelease], nil); @@ -69,6 +83,11 @@ options:0] autorelease], nil); STAssertNotNil([[[GTMRegex alloc] initWithPattern:@"(.*)" options:kGTMRegexOptionIgnoreCase] autorelease], nil); + error = nil; + STAssertNotNil([[[GTMRegex alloc] initWithPattern:@"(.*)" + options:kGTMRegexOptionIgnoreCase + withError:&error] autorelease], nil); + STAssertNil(error, @"shouldn't have been any error"); // fail cases (helper) STAssertNil([GTMRegex regexWithPattern:nil], nil); @@ -77,13 +96,39 @@ STAssertNil([GTMRegex regexWithPattern:@"(."], nil); STAssertNil([GTMRegex regexWithPattern:@"(." options:0], nil); - + // fail cases (helper) w/ error param + STAssertNil([GTMRegex regexWithPattern:nil + options:kGTMRegexOptionIgnoreCase + withError:&error], nil); + STAssertNil(error, @"no pattern, shouldn't get error object"); + STAssertNil([GTMRegex regexWithPattern:@"(." + options:kGTMRegexOptionIgnoreCase + withError:&error], nil); + STAssertNotNil(error, nil); + STAssertEqualObjects([error domain], kGTMRegexErrorDomain, nil); + STAssertEquals([error code], kGTMRegexPatternParseFailedError, nil); + userInfo = [error userInfo]; + STAssertNotNil(userInfo, @"failed to get userInfo from error"); + STAssertEqualObjects([userInfo objectForKey:kGTMRegexPatternErrorPattern], @"(.", nil); + STAssertNotNil([userInfo objectForKey:kGTMRegexPatternErrorErrorString], nil); + // basic pattern w/ options (helper) STAssertNotNil([GTMRegex regexWithPattern:@"(.*)"], nil); STAssertNotNil([GTMRegex regexWithPattern:@"(.*)" options:0], nil); STAssertNotNil([GTMRegex regexWithPattern:@"(.*)" options:kGTMRegexOptionIgnoreCase], nil); + error = nil; + STAssertNotNil([GTMRegex regexWithPattern:@"(.*)" + options:kGTMRegexOptionIgnoreCase + withError:&error], nil); + STAssertNil(error, @"shouldn't have been any error"); + + // not really a test on GTMRegex, but make sure we block attempts to directly + // alloc/init a GTMRegexStringSegment. + STAssertThrowsSpecificNamed([[[GTMRegexStringSegment alloc] init] autorelease], + NSException, NSInvalidArgumentException, + @"shouldn't have been able to alloc/init a GTMRegexStringSegment"); } - (void)testOptions { @@ -397,10 +442,68 @@ STAssertNil(subPatterns, nil); } +- (void)testFirstSubStringMatchedInString { + // simple pattern + GTMRegex *regex = [GTMRegex regexWithPattern:@"foo.*bar"]; + STAssertNotNil(regex, nil); + STAssertEqualStrings([regex firstSubStringMatchedInString:@"foobar"], + @"foobar", nil); + STAssertEqualStrings([regex firstSubStringMatchedInString:@"foobydoo spambar"], + @"foobydoo spambar", nil); + STAssertEqualStrings([regex firstSubStringMatchedInString:@"zzfoobarzz"], + @"foobar", nil); + STAssertEqualStrings([regex firstSubStringMatchedInString:@"zzfoobydoo spambarzz"], + @"foobydoo spambar", nil); + STAssertNil([regex firstSubStringMatchedInString:@"abcdef"], nil); + STAssertNil([regex firstSubStringMatchedInString:@""], nil); + // pattern w/ sub patterns + regex = [GTMRegex regexWithPattern:@"(foo)(.*)(bar)"]; + STAssertNotNil(regex, nil); + STAssertEqualStrings([regex firstSubStringMatchedInString:@"foobar"], + @"foobar", nil); + STAssertEqualStrings([regex firstSubStringMatchedInString:@"foobydoo spambar"], + @"foobydoo spambar", nil); + STAssertEqualStrings([regex firstSubStringMatchedInString:@"zzfoobarzz"], + @"foobar", nil); + STAssertEqualStrings([regex firstSubStringMatchedInString:@"zzfoobydoo spambarzz"], + @"foobydoo spambar", nil); + STAssertNil([regex firstSubStringMatchedInString:@"abcdef"], nil); + STAssertNil([regex firstSubStringMatchedInString:@""], nil); +} + +- (void)testMatchesSubStringInString { + // simple pattern + GTMRegex *regex = [GTMRegex regexWithPattern:@"foo.*bar"]; + STAssertNotNil(regex, nil); + STAssertTrue([regex matchesSubStringInString:@"foobar"], nil); + STAssertTrue([regex matchesSubStringInString:@"foobydoo spambar"], nil); + STAssertTrue([regex matchesSubStringInString:@"zzfoobarzz"], nil); + STAssertTrue([regex matchesSubStringInString:@"zzfoobydoo spambarzz"], nil); + STAssertFalse([regex matchesSubStringInString:@"abcdef"], nil); + STAssertFalse([regex matchesSubStringInString:@""], nil); + // pattern w/ sub patterns + regex = [GTMRegex regexWithPattern:@"(foo)(.*)(bar)"]; + STAssertNotNil(regex, nil); + STAssertTrue([regex matchesSubStringInString:@"foobar"], nil); + STAssertTrue([regex matchesSubStringInString:@"foobydoo spambar"], nil); + STAssertTrue([regex matchesSubStringInString:@"zzfoobarzz"], nil); + STAssertTrue([regex matchesSubStringInString:@"zzfoobydoo spambarzz"], nil); + STAssertFalse([regex matchesSubStringInString:@"abcdef"], nil); + STAssertFalse([regex matchesSubStringInString:@""], nil); +} + - (void)testSegmentEnumeratorForString { GTMRegex *regex = [GTMRegex regexWithPattern:@"foo+ba+r"]; STAssertNotNil(regex, nil); - NSEnumerator *enumerator = [regex segmentEnumeratorForString:@"afoobarbfooobaarfoobarzz"]; + + // test odd input + NSEnumerator *enumerator = [regex segmentEnumeratorForString:@""]; + STAssertNotNil(enumerator, nil); + enumerator = [regex segmentEnumeratorForString:nil]; + STAssertNil(enumerator, nil); + + // on w/ the normal tests + enumerator = [regex segmentEnumeratorForString:@"afoobarbfooobaarfoobarzz"]; STAssertNotNil(enumerator, nil); // "a" GTMRegexStringSegment *seg = [enumerator nextObject]; @@ -566,12 +669,32 @@ // (end) seg = [enumerator nextObject]; STAssertNil(seg, nil); + + // make sure the enum cleans up if not walked to the end + regex = [GTMRegex regexWithPattern:@"b+"]; + STAssertNotNil(regex, nil); + enumerator = [regex segmentEnumeratorForString:@"aabbcc"]; + STAssertNotNil(enumerator, nil); + // "aa" + seg = [enumerator nextObject]; + STAssertNotNil(seg, nil); + STAssertFalse([seg isMatch], nil); + STAssertEqualStrings([seg string], @"aa", nil); + // and done w/o walking the rest } - (void)testMatchSegmentEnumeratorForString { GTMRegex *regex = [GTMRegex regexWithPattern:@"foo+ba+r"]; STAssertNotNil(regex, nil); - NSEnumerator *enumerator = [regex matchSegmentEnumeratorForString:@"afoobarbfooobaarfoobarzz"]; + + // test odd input + NSEnumerator *enumerator = [regex matchSegmentEnumeratorForString:@""]; + STAssertNotNil(enumerator, nil); + enumerator = [regex matchSegmentEnumeratorForString:nil]; + STAssertNil(enumerator, nil); + + // on w/ the normal tests + enumerator = [regex matchSegmentEnumeratorForString:@"afoobarbfooobaarfoobarzz"]; STAssertNotNil(enumerator, nil); // "a" - skipped // "foobar" @@ -718,6 +841,13 @@ [regex stringByReplacingMatchesInString:@"weefoobydoo spambardoggies" withReplacement:@""], nil); + STAssertEqualStrings(@"", + [regex stringByReplacingMatchesInString:@"" + withReplacement:@"abc"], + nil); + STAssertNil([regex stringByReplacingMatchesInString:nil + withReplacement:@"abc"], + nil); // use optional and invale subexpression parts to confirm that works regex = [GTMRegex regexWithPattern:@"(fo(o+))((bar)|(baz))"]; STAssertNotNil(regex, nil); @@ -746,6 +876,30 @@ nil); } +- (void)testDescriptions { + // default options + GTMRegex *regex = [GTMRegex regexWithPattern:@"a+"]; + STAssertNotNil(regex, nil); + STAssertGreaterThan([[regex description] length], 10U, + @"failed to get a reasonable description for regex"); + // enumerator + NSEnumerator *enumerator = [regex segmentEnumeratorForString:@"aaabbbccc"]; + STAssertNotNil(enumerator, nil); + STAssertGreaterThan([[enumerator description] length], 10U, + @"failed to get a reasonable description for regex enumerator"); + // string segment + GTMRegexStringSegment *seg = [enumerator nextObject]; + STAssertNotNil(seg, nil); + STAssertGreaterThan([[seg description] length], 10U, + @"failed to get a reasonable description for regex string segment"); + // regex w/ other options + regex = [GTMRegex regexWithPattern:@"a+" + options:(kGTMRegexOptionIgnoreCase | kGTMRegexOptionSupressNewlineSupport)]; + STAssertNotNil(regex, nil); + STAssertGreaterThan([[regex description] length], 10U, + @"failed to get a reasonable description for regex w/ options"); +} + @end @implementation NSString_GTMRegexAdditions @@ -818,6 +972,23 @@ STAssertNil([@"" gtm_firstSubStringMatchedByPattern:@"(foo)(.*)(bar)"], nil); } +- (void)testSubStringMatchesPattern { + // simple pattern + STAssertTrue([@"foobar" gtm_subStringMatchesPattern:@"foo.*bar"], nil); + STAssertTrue([@"foobydoo spambar" gtm_subStringMatchesPattern:@"foo.*bar"], nil); + STAssertTrue([@"zzfoobarzz" gtm_subStringMatchesPattern:@"foo.*bar"], nil); + STAssertTrue([@"zzfoobydoo spambarzz" gtm_subStringMatchesPattern:@"foo.*bar"], nil); + STAssertFalse([@"abcdef" gtm_subStringMatchesPattern:@"foo.*bar"], nil); + STAssertFalse([@"" gtm_subStringMatchesPattern:@"foo.*bar"], nil); + // pattern w/ sub patterns + STAssertTrue([@"foobar" gtm_subStringMatchesPattern:@"(foo)(.*)(bar)"], nil); + STAssertTrue([@"foobydoo spambar" gtm_subStringMatchesPattern:@"(foo)(.*)(bar)"], nil); + STAssertTrue([@"zzfoobarzz" gtm_subStringMatchesPattern:@"(foo)(.*)(bar)"], nil); + STAssertTrue([@"zzfoobydoo spambarzz" gtm_subStringMatchesPattern:@"(foo)(.*)(bar)"], nil); + STAssertFalse([@"abcdef" gtm_subStringMatchesPattern:@"(foo)(.*)(bar)"], nil); + STAssertFalse([@"" gtm_subStringMatchesPattern:@"(foo)(.*)(bar)"], nil); +} + - (void)testSegmentEnumeratorForPattern { NSEnumerator *enumerator = [@"afoobarbfooobaarfoobarzz" gtm_segmentEnumeratorForPattern:@"foo+ba+r"]; @@ -1038,6 +1209,10 @@ [@"weefoobydoo spambardoggies" gtm_stringByReplacingMatchesOfPattern:@"(foo)(.*)(bar)" withReplacement:@""], nil); + STAssertEqualStrings(@"", + [@"" gtm_stringByReplacingMatchesOfPattern:@"(foo)(.*)(bar)" + withReplacement:@"abc"], + nil); // use optional and invale subexpression parts to confirm that works STAssertEqualStrings(@"aaa baz bar bar foo baz aaa", [@"aaa foooooobaz fooobar bar foo baz aaa" gtm_stringByReplacingMatchesOfPattern:@"(fo(o+))((bar)|(baz))" diff --git a/Foundation/GTMScriptRunner.m b/Foundation/GTMScriptRunner.m index e2d0995..8a76c4a 100644 --- a/Foundation/GTMScriptRunner.m +++ b/Foundation/GTMScriptRunner.m @@ -17,6 +17,7 @@ // #import "GTMScriptRunner.h" +#import "GTMDefines.h" static BOOL LaunchNSTaskCatchingExceptions(NSTask *task); @@ -77,8 +78,8 @@ static BOOL LaunchNSTaskCatchingExceptions(NSTask *task); } - (NSString *)description { - return [NSString stringWithFormat:@"%@<%p>{ interpreter = '%@', args = %@ }", - [self class], self, interpreter_, interpreterArgs_]; + return [NSString stringWithFormat:@"%@<%p>{ interpreter = '%@', args = %@, environment = %@ }", + [self class], self, interpreter_, interpreterArgs_, environment_]; } - (NSString *)run:(NSString *)cmds { @@ -112,6 +113,11 @@ static BOOL LaunchNSTaskCatchingExceptions(NSTask *task); if (trimsWhitespace_) { *err = [*err stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } + + // let folks test for nil instead of @"" + if ([*err length] < 1) { + *err = nil; + } } [task terminate]; @@ -120,6 +126,7 @@ static BOOL LaunchNSTaskCatchingExceptions(NSTask *task); output = [output stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } + // let folks test for nil instead of @"" if ([output length] < 1) { output = nil; } @@ -159,6 +166,11 @@ static BOOL LaunchNSTaskCatchingExceptions(NSTask *task); if (trimsWhitespace_) { *err = [*err stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } + + // let folks test for nil instead of @"" + if ([*err length] < 1) { + *err = nil; + } } [task terminate]; @@ -167,6 +179,7 @@ static BOOL LaunchNSTaskCatchingExceptions(NSTask *task); output = [output stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } + // let folks test for nil instead of @"" if ([output length] < 1) { output = nil; } @@ -233,7 +246,8 @@ static BOOL LaunchNSTaskCatchingExceptions(NSTask *task) { [task launch]; } @catch (id ex) { isOK = NO; - NSLog(@"Failed to launch interpreter '%@' due to: %@", [task launchPath], ex); + _GTMDevLog(@"Failed to launch interpreter '%@' due to: %@", + [task launchPath], ex); } return isOK; } diff --git a/Foundation/GTMScriptRunnerTest.m b/Foundation/GTMScriptRunnerTest.m index dc92ac7..a4497b6 100644 --- a/Foundation/GTMScriptRunnerTest.m +++ b/Foundation/GTMScriptRunnerTest.m @@ -16,15 +16,16 @@ // the License. // -#import <SenTestingKit/SenTestingKit.h> #import <sys/types.h> #import <unistd.h> +#import "GTMSenTestCase.h" #import "GTMScriptRunner.h" @interface GTMScriptRunnerTest : SenTestCase { @private NSString *shScript_; NSString *perlScript_; + NSString *shOutputScript_; } @end @@ -53,6 +54,15 @@ @"}\n" @"print \"$i\n\"\n" writeToFile:perlScript_ atomically:YES encoding:NSUTF8StringEncoding error:nil]; + + shOutputScript_ = [NSString stringWithFormat:@"/tmp/script_runner_unittest_err_%d_%d_sh", geteuid(), getpid()]; + [@"#!/bin/sh\n" + @"if [ \"err\" = \"$1\" ]; then\n" + @" echo \" on err \" > /dev/stderr\n" + @"else\n" + @" echo \" on out \"\n" + @"fi\n" + writeToFile:shOutputScript_ atomically:YES encoding:NSUTF8StringEncoding error:nil]; } - (void)tearDown { @@ -62,6 +72,9 @@ path = [perlScript_ fileSystemRepresentation]; if (path) unlink(path); + path = [shOutputScript_ fileSystemRepresentation]; + if (path) + unlink(path); } - (void)testShCommands { @@ -167,6 +180,8 @@ STAssertNotNil(sr, @"Script runner must not be nil"); NSString *output = nil; + STAssertNil([sr environment], @"should start w/ empty env"); + output = [sr run:@"/usr/bin/env | wc -l"]; int numVars = [output intValue]; STAssertTrue(numVars > 0, @"numVars should be positive"); @@ -196,6 +211,152 @@ @"should be back down to %d vars", numVars); } +- (void)testDescription { + // make sure description doesn't choke + GTMScriptRunner *sr = [GTMScriptRunner runner]; + STAssertNotNil(sr, @"Script runner must not be nil"); + STAssertGreaterThan([[sr description] length], 10U, + @"expected a description of at least 10 chars"); +} + +- (void)testRunCommandOutputHandling { + // Test whitespace trimming & stdout vs. stderr w/ run command api + + GTMScriptRunner *sr = [GTMScriptRunner runnerWithBash]; + STAssertNotNil(sr, @"Script runner must not be nil"); + NSString *output = nil; + NSString *err = nil; + + // w/o whitespace trimming + { + [sr setTrimsWhitespace:NO]; + STAssertFalse([sr trimsWhitespace], @"setTrimsWhitespace to NO failed"); + + // test stdout + output = [sr run:@"echo \" on out \"" standardError:&err]; + STAssertEqualObjects(output, @" on out \n", @"failed to get stdout output"); + STAssertNil(err, @"stderr should have been empty"); + + // test stderr + output = [sr run:@"echo \" on err \" > /dev/stderr" standardError:&err]; + STAssertNil(output, @"stdout should have been empty"); + STAssertEqualObjects(err, @" on err \n", nil); + } + + // w/ whitespace trimming + { + [sr setTrimsWhitespace:YES]; + STAssertTrue([sr trimsWhitespace], @"setTrimsWhitespace to YES failed"); + + // test stdout + output = [sr run:@"echo \" on out \"" standardError:&err]; + STAssertEqualObjects(output, @"on out", @"failed to get stdout output"); + STAssertNil(err, @"stderr should have been empty"); + + // test stderr + output = [sr run:@"echo \" on err \" > /dev/stderr" standardError:&err]; + STAssertNil(output, @"stdout should have been empty"); + STAssertEqualObjects(err, @"on err", nil); + } +} + +- (void)testScriptOutputHandling { + // Test whitespace trimming & stdout vs. stderr w/ script api + + GTMScriptRunner *sr = [GTMScriptRunner runner]; + STAssertNotNil(sr, @"Script runner must not be nil"); + NSString *output = nil; + NSString *err = nil; + + // w/o whitespace trimming + { + [sr setTrimsWhitespace:NO]; + STAssertFalse([sr trimsWhitespace], @"setTrimsWhitespace to NO failed"); + + // test stdout + output = [sr runScript:shOutputScript_ + withArgs:[NSArray arrayWithObject:@"out"] + standardError:&err]; + STAssertEqualObjects(output, @" on out \n", nil); + STAssertNil(err, @"stderr should have been empty"); + + // test stderr + output = [sr runScript:shOutputScript_ + withArgs:[NSArray arrayWithObject:@"err"] + standardError:&err]; + STAssertNil(output, @"stdout should have been empty"); + STAssertEqualObjects(err, @" on err \n", nil); + } + + // w/ whitespace trimming + { + [sr setTrimsWhitespace:YES]; + STAssertTrue([sr trimsWhitespace], @"setTrimsWhitespace to YES failed"); + + // test stdout + output = [sr runScript:shOutputScript_ + withArgs:[NSArray arrayWithObject:@"out"] + standardError:&err]; + STAssertEqualObjects(output, @"on out", nil); + STAssertNil(err, @"stderr should have been empty"); + + // test stderr + output = [sr runScript:shOutputScript_ + withArgs:[NSArray arrayWithObject:@"err"] + standardError:&err]; + STAssertNil(output, @"stdout should have been empty"); + STAssertEqualObjects(err, @"on err", nil); + } +} + +- (void)testBadRunCommandInput { + GTMScriptRunner *sr = [GTMScriptRunner runner]; + STAssertNotNil(sr, @"Script runner must not be nil"); + NSString *err = nil; + + STAssertNil([sr run:nil standardError:&err], nil); + STAssertNil(err, nil); +} + +- (void)testBadScriptInput { + GTMScriptRunner *sr = [GTMScriptRunner runner]; + STAssertNotNil(sr, @"Script runner must not be nil"); + NSString *err = nil; + + STAssertNil([sr runScript:nil withArgs:nil standardError:&err], nil); + STAssertNil(err, nil); + STAssertNil([sr runScript:@"/path/that/does/not/exists/foo/bar/baz" + withArgs:nil standardError:&err], nil); + STAssertNotNil(err, + @"should have gotten something about the path not existing"); +} + +- (void)testBadCmdInterpreter { + GTMScriptRunner *sr = + [GTMScriptRunner runnerWithInterpreter:@"/path/that/does/not/exists/interpreter"]; + STAssertNotNil(sr, @"Script runner must not be nil"); + NSString *err = nil; + + STAssertNil([sr run:nil standardError:&err], nil); + STAssertNil(err, nil); + STAssertNil([sr run:@"ls /" standardError:&err], nil); + STAssertNil(err, nil); +} + +- (void)testBadScriptInterpreter { + GTMScriptRunner *sr = + [GTMScriptRunner runnerWithInterpreter:@"/path/that/does/not/exists/interpreter"]; + STAssertNotNil(sr, @"Script runner must not be nil"); + NSString *err = nil; + + STAssertNil([sr runScript:shScript_ withArgs:nil standardError:&err], nil); + STAssertNil(err, nil); + STAssertNil([sr runScript:@"/path/that/does/not/exists/foo/bar/baz" + withArgs:nil standardError:&err], nil); + STAssertNil(err, nil); +} + + @end @implementation GTMScriptRunnerTest (PrivateMethods) diff --git a/Foundation/GTMSystemVersion.h b/Foundation/GTMSystemVersion.h index c9f9d75..13c3c19 100644 --- a/Foundation/GTMSystemVersion.h +++ b/Foundation/GTMSystemVersion.h @@ -17,29 +17,32 @@ // #import <Foundation/Foundation.h> +#import "GTMDefines.h" -/// A class for getting information about what system we are running on +// A class for getting information about what system we are running on @interface GTMSystemVersion : NSObject -/// Returns YES if running on 10.3, NO otherwise. +// Returns the current system version major.minor.bugFix ++ (void)getMajor:(long*)major minor:(long*)minor bugFix:(long*)bugFix; + +#if GTM_MACOS_SDK +// Returns YES if running on 10.3, NO otherwise. + (BOOL)isPanther; -/// Returns YES if running on 10.4, NO otherwise. +// Returns YES if running on 10.4, NO otherwise. + (BOOL)isTiger; -/// Returns YES if running on 10.5, NO otherwise. +// Returns YES if running on 10.5, NO otherwise. + (BOOL)isLeopard; -/// Returns a YES/NO if the system is 10.3 or better +// Returns a YES/NO if the system is 10.3 or better + (BOOL)isPantherOrGreater; -/// Returns a YES/NO if the system is 10.4 or better +// Returns a YES/NO if the system is 10.4 or better + (BOOL)isTigerOrGreater; -/// Returns a YES/NO if the system is 10.5 or better +// Returns a YES/NO if the system is 10.5 or better + (BOOL)isLeopardOrGreater; - -/// Returns the current system version major.minor.bugFix -+ (void)getMajor:(long*)major minor:(long*)minor bugFix:(long*)bugFix; +#endif // GTM_IPHONE_SDK @end diff --git a/Foundation/GTMSystemVersion.m b/Foundation/GTMSystemVersion.m index d9b1923..a2e4d7b 100644 --- a/Foundation/GTMSystemVersion.m +++ b/Foundation/GTMSystemVersion.m @@ -17,88 +17,71 @@ // #import "GTMSystemVersion.h" -#import <Carbon/Carbon.h> -#import <stdlib.h> + +static int sGTMSystemVersionMajor = 0; +static int sGTMSystemVersionMinor = 0; +static int sGTMSystemVersionBugFix = 0; @implementation GTMSystemVersion ++ (void)initialize { + if (self == [GTMSystemVersion class]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSDictionary *systemVersionPlist = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]; + NSString *version = [systemVersionPlist objectForKey:@"ProductVersion"]; + _GTMDevAssert(version, @"Unable to get version"); + NSArray *versionInfo = [version componentsSeparatedByString:@"."]; + int length = [versionInfo count]; + _GTMDevAssert(length > 1 && length < 4, @"Unparseable version %@", version); + sGTMSystemVersionMajor = [[versionInfo objectAtIndex:0] intValue]; + _GTMDevAssert(sGTMSystemVersionMajor != 0, @"Unknown version for %@", version); + sGTMSystemVersionMinor = [[versionInfo objectAtIndex:1] intValue]; + if (length == 3) { + sGTMSystemVersionBugFix = [[versionInfo objectAtIndex:2] intValue]; + } + [pool release]; + } +} ++ (void)getMajor:(long*)major minor:(long*)minor bugFix:(long*)bugFix { + if (major) { + *major = sGTMSystemVersionMajor; + } + if (minor) { + *minor = sGTMSystemVersionMinor; + } + if (major) { + *bugFix = sGTMSystemVersionBugFix; + } +} + +#if GTM_MACOS_SDK + (BOOL)isPanther { - long major, minor; - [self getMajor:&major minor:&minor bugFix:nil]; - return major == 10 && minor == 3; + return sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor == 3; } + (BOOL)isTiger { - long major, minor; - [self getMajor:&major minor:&minor bugFix:nil]; - return major == 10 && minor == 4; + return sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor == 4; } + (BOOL)isLeopard { - long major, minor; - [self getMajor:&major minor:&minor bugFix:nil]; - return major == 10 && minor == 5; + return sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor == 5; } + (BOOL)isPantherOrGreater { - long major, minor; - [self getMajor:&major minor:&minor bugFix:nil]; - return (major > 10) || (major == 10 && minor >= 3); + return (sGTMSystemVersionMajor > 10) || + (sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor >= 3); } + (BOOL)isTigerOrGreater { - long major, minor; - [self getMajor:&major minor:&minor bugFix:nil]; - return (major > 10) || (major == 10 && minor >= 4); + return (sGTMSystemVersionMajor > 10) || + (sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor >= 4); } + (BOOL)isLeopardOrGreater { - long major, minor; - [self getMajor:&major minor:&minor bugFix:nil]; - return (major > 10) || (major == 10 && minor >= 5); + return (sGTMSystemVersionMajor > 10) || + (sGTMSystemVersionMajor == 10 && sGTMSystemVersionMinor >= 5); } -+ (void)getMajor:(long*)major minor:(long*)minor bugFix:(long*)bugFix { - long binaryCodedDec; - - if (major) { - require_noerr(Gestalt(gestaltSystemVersionMajor, major), failedGestalt); - } - if (minor) { - require_noerr(Gestalt(gestaltSystemVersionMinor, minor), failedGestalt); - } - if (bugFix) { - require_noerr(Gestalt(gestaltSystemVersionBugFix, bugFix), failedGestalt); - } - return; - -failedGestalt: - // gestaltSystemVersionMajor et al are only on 10.4 and above, so they - // could fail if we have this code on 10.3. - if (Gestalt(gestaltSystemVersion, &binaryCodedDec)) { - // not much else we can do... - if (major) *major = 0; - if (minor) *minor = 0; - if (bugFix) *bugFix = 0; - return; - } - - // Note that this code will return x.9.9 for any system rev parts that are - // greater than 9 (ie 10.10.10 will be 10.9.9. This shouldn't ever be a - // problem as the code above takes care of this for any system above 10.4. - if (major) { - int msb = (binaryCodedDec & 0x0000F000L) >> 12; - msb *= 10; - int lsb = (binaryCodedDec & 0x00000F00L) >> 8; - *major = msb + lsb; - } - if (minor) { - *minor = (binaryCodedDec & 0x000000F0L) >> 4; - } - if (bugFix) { - *bugFix = (binaryCodedDec & 0x0000000FL); - } - -} +#endif // GTM_IPHONE_SDK @end diff --git a/Foundation/GTMSystemVersionTest.m b/Foundation/GTMSystemVersionTest.m index 57b22aa..38c37eb 100644 --- a/Foundation/GTMSystemVersionTest.m +++ b/Foundation/GTMSystemVersionTest.m @@ -16,7 +16,7 @@ // the License. // -#import <SenTestingKit/SenTestingKit.h> +#import "GTMSenTestCase.h" #import "GTMSystemVersion.h" @interface GTMSystemVersionTest : SenTestCase @@ -28,20 +28,26 @@ long minor; long bugFix; + [GTMSystemVersion getMajor:nil minor:nil bugFix:nil]; [GTMSystemVersion getMajor:&major minor:&minor bugFix:&bugFix]; +#if GTM_IPHONE_SDK + STAssertTrue(major >= 2 && minor >= 0 && bugFix >= 0, nil); +#else STAssertTrue(major >= 10 && minor >= 3 && bugFix >= 0, nil); - STAssertTrue([GTMSystemVersion isPantherOrGreater], nil); - if (minor > 3) { - STAssertTrue([GTMSystemVersion isTigerOrGreater], nil); - } else { - STAssertFalse([GTMSystemVersion isTigerOrGreater], nil); - } - if (minor > 4) { - STAssertTrue([GTMSystemVersion isLeopardOrGreater], nil); - } else { - STAssertFalse([GTMSystemVersion isLeopardOrGreater], nil); - } - [GTMSystemVersion getMajor:nil minor:nil bugFix:nil]; + BOOL isPanther = (major == 10) && (minor == 3); + BOOL isTiger = (major == 10) && (minor == 4); + BOOL isLeopard = (major == 10) && (minor == 5); + BOOL isLater = (major > 10) || ((major == 10) && (minor > 5)); + STAssertEquals([GTMSystemVersion isPanther], isPanther, nil); + STAssertEquals([GTMSystemVersion isPantherOrGreater], + (BOOL)(isPanther || isTiger || isLeopard || isLater), nil); + STAssertEquals([GTMSystemVersion isTiger], isTiger, nil); + STAssertEquals([GTMSystemVersion isTigerOrGreater], + (BOOL)(isTiger || isLeopard || isLater), nil); + STAssertEquals([GTMSystemVersion isLeopard], isLeopard, nil); + STAssertEquals([GTMSystemVersion isLeopardOrGreater], + (BOOL)(isLeopard || isLater), nil); +#endif } @end |