aboutsummaryrefslogtreecommitdiff
path: root/Foundation
diff options
context:
space:
mode:
authorGravatar thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-12-12 15:24:34 +0000
committerGravatar thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-12-12 15:24:34 +0000
commit2e8516354aacef064d01425808da06d2cdcb4791 (patch)
tree9da4758828930280d32f18d54ece7a249df742c7 /Foundation
parent9f64d056dd70f2f938ac6f5adb8e75b650dc2e1a (diff)
- GTMStackTrace works on 10.5+ (and iPhone) using NSThread to build the call stack.
- Added GTM_EXTERN that makes it easier to mix and match objc and objc++ code. - Added GTMHotKeysTextField for display and editing of hot key settings. - Added GTMCarbonEvent for dealing with Carbon Events and HotKeys in a ObjC like way. - Backported the Atomic Barrier Swap functions for Objective C back to Tiger. - Added a variety of new functions to GTMUnitTestingUtilities for checking if the screensaver is in the way, waiting on user events, and generating keystrokes. - If you are using any Carbon routines that log (DebugStr, AssertMacros.h) and use GTMUnitTestDevLog, the log routines now go through _GTMDevLog so that they can be caught in GTMUnitTestDevLog and verified like any _GTMDevLog calls you may make. For an example of this in action see GTMCarbonEventTest.m. - Added GTMFileSystemKQueue. It provides a simple wrapper for kqueuing something in the file system and tracking changes to it. - RunIPhoneUnitTest.sh now cleans up the user home directory and creates a documents directory within it, used when requesting a NSDocumentDirectory. - Added GTMNSFileManager+Carbon which contains routines for path <-> Alias conversion and path <-> FSRef conversion. - Added GTMNSArray+Merge for merging one array into another with or without a custom merging function, returning a new array with the merged contents.
Diffstat (limited to 'Foundation')
-rw-r--r--Foundation/GTMFileSystemKQueue.h78
-rw-r--r--Foundation/GTMFileSystemKQueue.m250
-rw-r--r--Foundation/GTMFileSystemKQueueTest.m394
-rw-r--r--Foundation/GTMHTTPFetcher.h2
-rw-r--r--Foundation/GTMHTTPServer.h2
-rw-r--r--Foundation/GTMLightweightProxy.m4
-rw-r--r--Foundation/GTMLightweightProxyTest.m24
-rw-r--r--Foundation/GTMNSAppleScript+HandlerTest.m9
-rw-r--r--Foundation/GTMNSArray+Merge.h47
-rw-r--r--Foundation/GTMNSArray+Merge.m112
-rw-r--r--Foundation/GTMNSArray+MergeTest.m219
-rw-r--r--Foundation/GTMNSFileManager+Carbon.h60
-rw-r--r--Foundation/GTMNSFileManager+Carbon.m99
-rw-r--r--Foundation/GTMNSFileManager+CarbonTest.m69
-rw-r--r--Foundation/GTMObjC2Runtime.h41
-rw-r--r--Foundation/GTMRegex.h2
-rw-r--r--Foundation/GTMStackTrace.h10
-rw-r--r--Foundation/GTMStackTrace.m108
-rw-r--r--Foundation/GTMStackTraceTest.m8
-rw-r--r--Foundation/GTMSystemVersion.h15
-rw-r--r--Foundation/GTMSystemVersion.m1
21 files changed, 1497 insertions, 57 deletions
diff --git a/Foundation/GTMFileSystemKQueue.h b/Foundation/GTMFileSystemKQueue.h
new file mode 100644
index 0000000..a81d433
--- /dev/null
+++ b/Foundation/GTMFileSystemKQueue.h
@@ -0,0 +1,78 @@
+//
+// GTMFileSystemKQueue.h
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Foundation/Foundation.h>
+
+#import <sys/event.h> // for kqueue() and kevent and the NOTE_* constants
+
+// Event constants
+enum {
+ kGTMFileSystemKQueueDeleteEvent = NOTE_DELETE,
+ kGTMFileSystemKQueueWriteEvent = NOTE_WRITE,
+ kGTMFileSystemKQueueExtendEvent = NOTE_EXTEND,
+ kGTMFileSystemKQueueAttributeChangeEvent = NOTE_ATTRIB,
+ kGTMFileSystemKQueueLinkChangeEvent = NOTE_LINK,
+ kGTMFileSystemKQueueRenameEvent = NOTE_RENAME,
+ kGTMFileSystemKQueueRevokeEvent = NOTE_REVOKE,
+ kGTMFileSystemKQueueAllEvents = NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND |
+ NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME |
+ NOTE_REVOKE,
+};
+typedef unsigned int GTMFileSystemKQueueEvents;
+
+// GTMFileSystemKQueue.
+//
+// This is a very simple, easy-to-use class for registering handlers that get
+// called when a events happen to a given file system path.
+//
+// The default runloop for the first path kqueued is used for notification
+// delivery, so keep that in mind when you're using this class. This class
+// explicitly does not handle arbitrary runloops and threading.
+//
+@interface GTMFileSystemKQueue : NSObject {
+ @private
+ NSString *path_;
+ int fd_;
+ GTMFileSystemKQueueEvents events_;
+ BOOL acrossReplace_;
+ __weak id target_;
+ SEL action_;
+}
+
+// |path| is the full path to monitor. |events| is a combination of events
+// listed above that you want notification of. |acrossReplace| will cause this
+// object to reattach when a the file is deleted & recreated or moved out of the
+// way and a new one put in place. |selector| should be of the signature:
+// - (void)fileSystemKQueue:(GTMFileSystemKQueue *)fskq
+// events:(GTMFileSystemKQueueEvents)events;
+// where the events can be one or more of the events listed above ORed together.
+//
+// NOTE: |acrossReplace| is not fool proof. If the file is renamed/deleted,
+// then the object will make one attempt at the time it gets the rename/delete
+// to reopen the file. If the new file has not been created, no more action is
+// taken. To handle the file coming into existance later, you need to monitor
+// the directory in some other way.
+- (id)initWithPath:(NSString *)path
+ forEvents:(GTMFileSystemKQueueEvents)events
+ acrossReplace:(BOOL)acrossReplace
+ target:(id)target
+ action:(SEL)action;
+
+- (NSString *)path;
+
+@end
diff --git a/Foundation/GTMFileSystemKQueue.m b/Foundation/GTMFileSystemKQueue.m
new file mode 100644
index 0000000..e2c399e
--- /dev/null
+++ b/Foundation/GTMFileSystemKQueue.m
@@ -0,0 +1,250 @@
+//
+// GTMFileSystemKQueue.m
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMFileSystemKQueue.h"
+#import <unistd.h>
+
+#import "GTMDebugSelectorValidation.h"
+
+
+// File descriptor for the kqueue that will hold all of our file system events.
+static int gFileSystemKQueueFileDescriptor = 0;
+
+// A wrapper around the kqueue file descriptor so we can put it into a
+// runloop.
+static CFSocketRef gRunLoopSocket = NULL;
+
+
+@interface GTMFileSystemKQueue (PrivateMethods)
+- (void)notify:(GTMFileSystemKQueueEvents)eventFFlags;
+- (void)addFileDescriptorMonitor:(int)fd;
+- (int)registerWithKQueue;
+- (void)unregisterWithKQueue;
+@end
+
+
+@implementation GTMFileSystemKQueue
+
+-(id)init {
+ // Folks shouldn't call init directly, so they get what they deserve.
+ _GTMDevLog(@"Don't call init, use "
+ @"initWithPath:forEvents:acrossReplace:target:action:");
+ return [self initWithPath:nil forEvents:0 acrossReplace:NO
+ target:nil action:nil];
+}
+
+
+- (id)initWithPath:(NSString *)path
+ forEvents:(GTMFileSystemKQueueEvents)events
+ acrossReplace:(BOOL)acrossReplace
+ target:(id)target
+ action:(SEL)action {
+
+ if ((self = [super init])) {
+
+ fd_ = -1;
+ path_ = [path copy];
+ events_ = events;
+ acrossReplace_ = acrossReplace;
+
+ target_ = target; // Don't retain since target will most likely retain us.
+ action_ = action;
+ if ([path_ length] == 0 || !events_ || !target_ || !action_) {
+ [self release];
+ return nil;
+ }
+
+ // Make sure it imples what we expect
+ GTMAssertSelectorNilOrImplementedWithArguments(target_,
+ action_,
+ @encode(GTMFileSystemKQueue*),
+ @encode(GTMFileSystemKQueueEvents),
+ NULL);
+
+ fd_ = [self registerWithKQueue];
+ if (fd_ < 0) {
+ [self release];
+ return nil;
+ }
+ }
+ return self;
+}
+
+- (void)finalize {
+ [self unregisterWithKQueue];
+
+ [super finalize];
+}
+
+- (void)dealloc {
+ [self unregisterWithKQueue];
+ [path_ release];
+
+ [super dealloc];
+}
+
+- (NSString *)path {
+ return path_;
+}
+
+// Cribbed from Advanced Mac OS X Programming.
+static void SocketCallBack(CFSocketRef socketref, CFSocketCallBackType type,
+ CFDataRef address, const void *data, void *info) {
+ struct kevent event;
+
+ if (kevent(gFileSystemKQueueFileDescriptor, NULL, 0, &event, 1, NULL) == -1) {
+ _GTMDevLog(@"could not pick up kqueue event. Errno %d", errno); // COV_NF_LINE
+ } else {
+ GTMFileSystemKQueue *fskq = (GTMFileSystemKQueue *)event.udata;
+ [fskq notify:event.fflags];
+ }
+
+}
+
+// Cribbed from Advanced Mac OS X Programming
+- (void)addFileDescriptorMonitor:(int)fd {
+ _GTMDevAssert(gRunLoopSocket == NULL, @"socket should be NULL at this point");
+
+ CFSocketContext context = { 0, NULL, NULL, NULL, NULL };
+ gRunLoopSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
+ fd,
+ kCFSocketReadCallBack,
+ SocketCallBack,
+ &context);
+ if (gRunLoopSocket == NULL) {
+ _GTMDevLog(@"could not CFSocketCreateWithNative"); // COV_NF_LINE
+ goto bailout; // COV_NF_LINE
+ }
+
+ CFRunLoopSourceRef rls;
+ rls = CFSocketCreateRunLoopSource(NULL, gRunLoopSocket, 0);
+ if (rls == NULL) {
+ _GTMDevLog(@"could not create a run loop source"); // COV_NF_LINE
+ goto bailout; // COV_NF_LINE
+ }
+
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), rls,
+ kCFRunLoopDefaultMode);
+ CFRelease(rls);
+
+ bailout:
+ return;
+
+}
+
+// Returns the FD we got in registering
+- (int)registerWithKQueue {
+
+ // Make sure we have our kqueue.
+ if (gFileSystemKQueueFileDescriptor == 0) {
+ gFileSystemKQueueFileDescriptor = kqueue();
+
+ if (gFileSystemKQueueFileDescriptor == -1) {
+ // COV_NF_START
+ _GTMDevLog(@"could not make filesystem kqueue. Errno %d", errno);
+ return -1;
+ // COV_NF_END
+ }
+
+ // Add the kqueue file descriptor to the runloop.
+ [self addFileDescriptorMonitor:gFileSystemKQueueFileDescriptor];
+ }
+
+ int newFD = open([path_ fileSystemRepresentation], O_EVTONLY, 0);
+ if (newFD >= 0) {
+
+ // Add a new event for the file.
+ struct kevent filter;
+ EV_SET(&filter, newFD, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
+ events_, 0, self);
+
+ const struct timespec noWait = { 0, 0 };
+ if (kevent(gFileSystemKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) == -1) {
+ // COV_NF_START
+ _GTMDevLog(@"could not add event for path %@. Errno %d", path_, errno);
+ close(newFD);
+ newFD = -1;
+ // COV_NF_END
+ }
+ }
+
+ return newFD;
+}
+
+- (void)unregisterWithKQueue {
+ // Short-circuit cases where we didn't actually register a kqueue event.
+ if (fd_ < 0) return;
+
+ struct kevent filter;
+ EV_SET(&filter, fd_, EVFILT_VNODE, EV_DELETE, 0, 0, self);
+
+ const struct timespec noWait = { 0, 0 };
+ if (kevent(gFileSystemKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) != 0) {
+ _GTMDevLog(@"could not remove event for path %@. Errno %d", path_, errno); // COV_NF_LINE
+ }
+
+ // Now close the file down
+ close(fd_);
+ fd_ = -1;
+}
+
+
+- (void)notify:(GTMFileSystemKQueueEvents)eventFFlags {
+
+ // Some notifications get a little bit of overhead first
+
+ if (eventFFlags & NOTE_REVOKE) {
+ // COV_NF_START - no good way to do this in a unittest
+ // Assume revoke means unmount and give up
+ [self unregisterWithKQueue];
+ // COV_NF_END
+ }
+
+ if (eventFFlags & NOTE_DELETE) {
+ [self unregisterWithKQueue];
+ if (acrossReplace_) {
+ fd_ = [self registerWithKQueue];
+ }
+ }
+
+ if (eventFFlags & NOTE_RENAME) {
+ // If we're doing it across replace, we move to the new fd for the new file
+ // that might have come onto the path. if we aren't doing accross replace,
+ // nothing to do, just stay on the file.
+ if (acrossReplace_) {
+ int newFD = [self registerWithKQueue];
+ if (newFD >= 0) {
+ [self unregisterWithKQueue];
+ fd_ = newFD;
+ }
+ }
+ }
+
+ // Now, fire the selector
+ NSMethodSignature *methodSig = [target_ methodSignatureForSelector:action_];
+ _GTMDevAssert(methodSig != nil, @"failed to get the signature?");
+ NSInvocation *invocation
+ = [NSInvocation invocationWithMethodSignature:methodSig];
+ [invocation setTarget:target_];
+ [invocation setSelector:action_];
+ [invocation setArgument:self atIndex:2];
+ [invocation setArgument:&eventFFlags atIndex:3];
+ [invocation invoke];
+}
+
+@end
diff --git a/Foundation/GTMFileSystemKQueueTest.m b/Foundation/GTMFileSystemKQueueTest.m
new file mode 100644
index 0000000..c2ded10
--- /dev/null
+++ b/Foundation/GTMFileSystemKQueueTest.m
@@ -0,0 +1,394 @@
+//
+// GTMFileSystemKQueueTest.m
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMSenTestCase.h"
+#import "GTMFileSystemKQueue.h"
+#import "GTMUnitTestDevLog.h"
+
+@interface GTMFileSystemKQueueTest : GTMTestCase {
+ @private
+ NSString *testPath_;
+ NSString *testPath2_;
+}
+@end
+
+
+// Helper class to serve as callback target of the kqueue test
+@interface GTMFSKQTestHelper : NSObject {
+ @private
+ int writes_, renames_, deletes_;
+}
+@end
+
+@implementation GTMFSKQTestHelper
+
+- (void)callbackForQueue:(GTMFileSystemKQueue *)queue
+ events:(GTMFileSystemKQueueEvents)event {
+ if (event & kGTMFileSystemKQueueWriteEvent) {
+ ++writes_;
+ }
+ if (event & kGTMFileSystemKQueueDeleteEvent) {
+ ++deletes_;
+ }
+ if (event & kGTMFileSystemKQueueRenameEvent) {
+ ++renames_;
+ }
+}
+- (int)totals {
+ return writes_ + renames_ + deletes_;
+}
+- (int)writes {
+ return writes_;
+}
+- (int)renames {
+ return renames_;
+}
+- (int)deletes {
+ return deletes_;
+}
+@end
+
+
+@implementation GTMFileSystemKQueueTest
+
+- (void)setUp {
+ NSString *temp = NSTemporaryDirectory();
+ testPath_
+ = [[temp stringByAppendingPathComponent:
+ @"GTMFileSystemKQueueTest.testfile"] retain];
+ testPath2_ = [[testPath_ stringByAppendingPathExtension:@"2"] retain];
+
+ // make sure the files aren't in the way of the test
+ NSFileManager *fm = [NSFileManager defaultManager];
+ [fm removeFileAtPath:testPath_ handler:nil];
+ [fm removeFileAtPath:testPath2_ handler:nil];
+}
+
+- (void)tearDown {
+ // make sure we clean up the files from a failed test
+ NSFileManager *fm = [NSFileManager defaultManager];
+ [fm removeFileAtPath:testPath_ handler:nil];
+ [fm removeFileAtPath:testPath2_ handler:nil];
+
+ [testPath_ release];
+ testPath_ = nil;
+ [testPath2_ release];
+ testPath2_ = nil;
+}
+
+- (void)testInit {
+ GTMFileSystemKQueue *testKQ;
+ GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
+ STAssertNotNil(helper, nil);
+
+ // init should fail
+ [GTMUnitTestDevLog expectString:@"Don't call init, use "
+ @"initWithPath:forEvents:acrossReplace:target:action:"];
+ testKQ = [[[GTMFileSystemKQueue alloc] init] autorelease];
+ STAssertNil(testKQ, nil);
+
+ // no path
+ testKQ
+ = [[[GTMFileSystemKQueue alloc] initWithPath:nil
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:YES
+ target:helper
+ action:@selector(callbackForQueue:events:)] autorelease];
+ STAssertNil(testKQ, nil);
+
+ // not events
+ testKQ
+ = [[[GTMFileSystemKQueue alloc] initWithPath:@"/var/log/system.log"
+ forEvents:0
+ acrossReplace:YES
+ target:helper
+ action:@selector(callbackForQueue:events:)] autorelease];
+ STAssertNil(testKQ, nil);
+
+ // no target
+ testKQ
+ = [[[GTMFileSystemKQueue alloc] initWithPath:@"/var/log/system.log"
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:YES
+ target:nil
+ action:@selector(callbackForQueue:events:)] autorelease];
+ STAssertNil(testKQ, nil);
+
+ // no handler
+ testKQ
+ = [[[GTMFileSystemKQueue alloc] initWithPath:@"/var/log/system.log"
+ forEvents:0
+ acrossReplace:YES
+ target:helper
+ action:nil] autorelease];
+ STAssertNil(testKQ, nil);
+
+
+ // path that doesn't exist
+ testKQ
+ = [[[GTMFileSystemKQueue alloc] initWithPath:@"/path/that/does/not/exist"
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:YES
+ target:helper
+ action:@selector(callbackForQueue:events:)] autorelease];
+ STAssertNil(testKQ, nil);
+}
+
+- (void)spinForEvents:(GTMFSKQTestHelper *)helper {
+ // Spin the runloop for a second so that the helper callbacks fire
+ unsigned int attempts = 0;
+ int initialTotals = [helper totals];
+ while (([helper totals] == initialTotals) && (attempts < 10)) { // Try for up to 2s
+ [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]];
+ attempts++;
+ }
+}
+
+- (void)testWriteAndDelete {
+
+ NSFileManager *fm = [NSFileManager defaultManager];
+ GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
+ STAssertNotNil(helper, nil);
+
+ STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
+ NSFileHandle *testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
+ STAssertNotNil(testFH, nil);
+
+ // Start monitoring the file
+ GTMFileSystemKQueue *testKQ
+ = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:YES
+ target:helper
+ action:@selector(callbackForQueue:events:)];
+ STAssertNotNil(testKQ, nil);
+ STAssertEqualObjects([testKQ path], testPath_, nil);
+
+ // Write to the file
+ [testFH writeData:[@"doh!" dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 1, nil);
+
+ // Close and delete
+ [testFH closeFile];
+ STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
+
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 2, nil);
+
+ // Clean up the kqueue
+ [testKQ release];
+ testKQ = nil;
+
+ STAssertEquals([helper writes], 1, nil);
+ STAssertEquals([helper deletes], 1, nil);
+ STAssertEquals([helper renames], 0, nil);
+}
+
+- (void)testWriteAndDeleteAndWrite {
+
+ // One will pass YES to |acrossReplace|, the other will pass NO.
+
+ NSFileManager *fm = [NSFileManager defaultManager];
+ GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
+ STAssertNotNil(helper, nil);
+ GTMFSKQTestHelper *helper2 = [[[GTMFSKQTestHelper alloc] init] autorelease];
+ STAssertNotNil(helper, nil);
+
+ // Create a temp file path
+ STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
+ NSFileHandle *testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
+ STAssertNotNil(testFH, nil);
+
+ // Start monitoring the file
+ GTMFileSystemKQueue *testKQ
+ = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:YES
+ target:helper
+ action:@selector(callbackForQueue:events:)];
+ STAssertNotNil(testKQ, nil);
+ STAssertEqualObjects([testKQ path], testPath_, nil);
+ GTMFileSystemKQueue *testKQ2
+ = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:NO
+ target:helper2
+ action:@selector(callbackForQueue:events:)];
+ STAssertNotNil(testKQ2, nil);
+ STAssertEqualObjects([testKQ2 path], testPath_, nil);
+
+ // Write to the file
+ [testFH writeData:[@"doh!" dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 1, nil);
+ STAssertEquals([helper2 totals], 1, nil);
+
+ // Close and delete
+ [testFH closeFile];
+ STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
+
+ // Recreate
+ STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
+ testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
+ STAssertNotNil(testFH, nil);
+ [testFH writeData:[@"ha!" dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 2, nil);
+ STAssertEquals([helper2 totals], 2, nil);
+
+ // Write to it again
+ [testFH writeData:[@"continued..." dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 3, nil);
+ STAssertEquals([helper2 totals], 2, nil);
+
+ // Close and delete
+ [testFH closeFile];
+ STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 4, nil);
+ STAssertEquals([helper2 totals], 2, nil);
+
+ // Clean up the kqueue
+ [testKQ release];
+ testKQ = nil;
+ [testKQ2 release];
+ testKQ2 = nil;
+
+ STAssertEquals([helper writes], 2, nil);
+ STAssertEquals([helper deletes], 2, nil);
+ STAssertEquals([helper renames], 0, nil);
+ STAssertEquals([helper2 writes], 1, nil);
+ STAssertEquals([helper2 deletes], 1, nil);
+ STAssertEquals([helper2 renames], 0, nil);
+}
+
+- (void)testWriteAndRenameAndWrite {
+
+ // One will pass YES to |acrossReplace|, the other will pass NO.
+
+ NSFileManager *fm = [NSFileManager defaultManager];
+ GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
+ STAssertNotNil(helper, nil);
+ GTMFSKQTestHelper *helper2 = [[[GTMFSKQTestHelper alloc] init] autorelease];
+ STAssertNotNil(helper2, nil);
+
+ // Create a temp file path
+ STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
+ NSFileHandle *testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
+ STAssertNotNil(testFH, nil);
+
+ // Start monitoring the file
+ GTMFileSystemKQueue *testKQ
+ = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:YES
+ target:helper
+ action:@selector(callbackForQueue:events:)];
+ STAssertNotNil(testKQ, nil);
+ STAssertEqualObjects([testKQ path], testPath_, nil);
+ GTMFileSystemKQueue *testKQ2
+ = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
+ forEvents:kGTMFileSystemKQueueAllEvents
+ acrossReplace:NO
+ target:helper2
+ action:@selector(callbackForQueue:events:)];
+ STAssertNotNil(testKQ2, nil);
+ STAssertEqualObjects([testKQ2 path], testPath_, nil);
+
+ // Write to the file
+ [testFH writeData:[@"doh!" dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 1, nil);
+ STAssertEquals([helper2 totals], 1, nil);
+
+ // Move it and create the file again
+ STAssertTrue([fm movePath:testPath_ toPath:testPath2_ handler:nil], nil);
+ STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
+ NSFileHandle *testFHPrime
+ = [NSFileHandle fileHandleForWritingAtPath:testPath_];
+ STAssertNotNil(testFHPrime, nil);
+ [testFHPrime writeData:[@"eh?" dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 2, nil);
+ STAssertEquals([helper2 totals], 2, nil);
+
+ // Write to the new file
+ [testFHPrime writeData:[@"continue..." dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 3, nil);
+ STAssertEquals([helper2 totals], 2, nil);
+
+ // Write to the old file
+ [testFH writeData:[@"continue old..." dataUsingEncoding:NSUnicodeStringEncoding]];
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 3, nil);
+ STAssertEquals([helper2 totals], 3, nil);
+
+ // and now close old
+ [testFH closeFile];
+ STAssertTrue([fm removeFileAtPath:testPath2_ handler:nil], nil);
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 3, nil);
+ STAssertEquals([helper2 totals], 4, nil);
+
+ // and now close new
+ [testFHPrime closeFile];
+ STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
+
+ // Spin the runloop for a second so that the helper callbacks fire
+ [self spinForEvents:helper];
+ STAssertEquals([helper totals], 4, nil);
+ STAssertEquals([helper2 totals], 4, nil);
+
+ // Clean up the kqueue
+ [testKQ release];
+ testKQ = nil;
+ [testKQ2 release];
+ testKQ2 = nil;
+
+ STAssertEquals([helper writes], 2, nil);
+ STAssertEquals([helper deletes], 1, nil);
+ STAssertEquals([helper renames], 1, nil);
+ STAssertEquals([helper2 writes], 2, nil);
+ STAssertEquals([helper2 deletes], 1, nil);
+ STAssertEquals([helper2 renames], 1, nil);
+}
+
+@end
diff --git a/Foundation/GTMHTTPFetcher.h b/Foundation/GTMHTTPFetcher.h
index fadd9b0..5abbe1a 100644
--- a/Foundation/GTMHTTPFetcher.h
+++ b/Foundation/GTMHTTPFetcher.h
@@ -202,7 +202,7 @@
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
-#define _EXTERN extern
+#define _EXTERN GTM_EXTERN
#define _INITIALIZE_AS(x)
#endif
diff --git a/Foundation/GTMHTTPServer.h b/Foundation/GTMHTTPServer.h
index 71522df..0caa149 100644
--- a/Foundation/GTMHTTPServer.h
+++ b/Foundation/GTMHTTPServer.h
@@ -43,7 +43,7 @@
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
-#define _EXTERN extern
+#define _EXTERN GTM_EXTERN
#define _INITIALIZE_AS(x)
#endif
diff --git a/Foundation/GTMLightweightProxy.m b/Foundation/GTMLightweightProxy.m
index ad7e0a1..39f5f5c 100644
--- a/Foundation/GTMLightweightProxy.m
+++ b/Foundation/GTMLightweightProxy.m
@@ -48,9 +48,9 @@
// Even though we don't retain this object, we hang it on the lifetime
// of the calling threads pool so it's lifetime is safe for at least that
// long.
- repObject = [[representedObject_ retain] autorelease];
+ repObject = [representedObject_ retain];
}
- return repObject;
+ return [repObject autorelease];
}
- (void)setRepresentedObject:(id)object {
diff --git a/Foundation/GTMLightweightProxyTest.m b/Foundation/GTMLightweightProxyTest.m
index ad0961e..e50a9dc 100644
--- a/Foundation/GTMLightweightProxyTest.m
+++ b/Foundation/GTMLightweightProxyTest.m
@@ -31,13 +31,16 @@
@implementation GTMLightweightProxyTest
- (void)testProxy {
- id proxy = [[GTMLightweightProxy alloc] initWithRepresentedObject:self];
- STAssertEqualObjects(self, [proxy representedObject], @"Represented object setup failed");
+ id proxy
+ = [[[GTMLightweightProxy alloc] initWithRepresentedObject:self] autorelease];
+ STAssertEqualObjects(self, [proxy representedObject],
+ @"Represented object setup failed");
// Check that it identifies itself as a proxy.
STAssertTrue([proxy isProxy], @"Should identify as a proxy");
// Check that it passes class requests on
- STAssertTrue([proxy isMemberOfClass:[self class]], @"Should pass class requests through");
+ STAssertTrue([proxy isMemberOfClass:[self class]],
+ @"Should pass class requests through");
// Check that it claims to respond to its selectors.
STAssertTrue([proxy respondsToSelector:@selector(initWithRepresentedObject:)],
@@ -50,7 +53,8 @@
STAssertTrue([proxy respondsToSelector:@selector(returnYes)],
@"Claims not to respond to returnYes");
// ... but not to made up selectors.
- STAssertThrows([proxy someMadeUpMethod], @"Calling a bogus method should throw");
+ STAssertThrows([proxy someMadeUpMethod],
+ @"Calling a bogus method should throw");
// Check that callthrough works.
STAssertTrue([proxy returnYes],
@@ -59,14 +63,18 @@
// Check that nilling out the represented object works.
[proxy setRepresentedObject:nil];
STAssertTrue([proxy respondsToSelector:@selector(setRepresentedObject:)],
- @"Claims not to respond to setRepresentedObject: after nilling out represented object");
+ @"Claims not to respond to setRepresentedObject: after nilling"
+ @" out represented object");
STAssertFalse([proxy respondsToSelector:@selector(returnYes)],
- @"Claims to respond to returnYes after nilling out represented object");
+ @"Claims to respond to returnYes after nilling out represented"
+ @" object");
// Calling through once the represented object is nil should fail silently
STAssertNoThrow([proxy returnYes],
- @"Calling through without a represented object should fail silently");
+ @"Calling through without a represented object should fail"
+ @" silently");
// ... even when they are made up.
- STAssertNoThrow([proxy someMadeUpMethod], @"Calling a bogus method on a nilled proxy should not throw");
+ STAssertNoThrow([proxy someMadeUpMethod],
+ @"Calling a bogus method on a nilled proxy should not throw");
}
// Simple method to test calling through the proxy.
diff --git a/Foundation/GTMNSAppleScript+HandlerTest.m b/Foundation/GTMNSAppleScript+HandlerTest.m
index 02f62e0..16bbd81 100644
--- a/Foundation/GTMNSAppleScript+HandlerTest.m
+++ b/Foundation/GTMNSAppleScript+HandlerTest.m
@@ -455,15 +455,20 @@
NSAppleScript *script
= [[[NSAppleScript alloc] initWithSource:@"david hasselhoff"] autorelease];
[GTMUnitTestDevLog expectPattern:@"Unable to compile script: .*"];
- [GTMUnitTestDevLog expectPattern:@"Unable to coerce script -2147450879"];
+ [GTMUnitTestDevLog expectString:@"Unable to coerce script -2147450879"];
NSSet *handlers = [script gtm_handlers];
STAssertEquals([handlers count], (NSUInteger)0, @"Should have no handlers");
[GTMUnitTestDevLog expectPattern:@"Unable to compile script: .*"];
- [GTMUnitTestDevLog expectPattern:@"Unable to coerce script -2147450879"];
+ [GTMUnitTestDevLog expectString:@"Unable to coerce script -2147450879"];
NSSet *properties = [script gtm_properties];
STAssertEquals([properties count],
(NSUInteger)0,
@"Should have no properties");
+ [GTMUnitTestDevLog expectPattern:@"Unable to compile script: .*"];
+ [GTMUnitTestDevLog expectString:@"Unable to get script info about "
+ @"open handler -2147450879"];
+ STAssertFalse([script gtm_hasOpenDocumentsHandler],
+ @"Has an opendoc handler?");
}
- (void)testScriptDescriptors {
diff --git a/Foundation/GTMNSArray+Merge.h b/Foundation/GTMNSArray+Merge.h
new file mode 100644
index 0000000..8140f80
--- /dev/null
+++ b/Foundation/GTMNSArray+Merge.h
@@ -0,0 +1,47 @@
+//
+// GTMNSArray+Merge.h
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Foundation/Foundation.h>
+
+// Extension to NSArray to allow merging of arrays.
+//
+@interface NSArray (GTMNSArrayMergingAdditions)
+
+// Merge our array with |newArray| by sorting each array then merging the
+// two arrays. If |merger| is provided then call that method on any old
+// items that compare as equal to a new item, passing the new item as
+// the only argument. If |merger| is not provided, then insert new items
+// in front of matching old items. If neither array has any items then
+// nil is returned.
+//
+// The signature of the |merger| is:
+// - (id)merge:(id)newItem;
+//
+// Returns a new, sorted array.
+- (NSArray *)gtm_mergeArray:(NSArray *)newArray
+ mergeSelector:(SEL)merger;
+
+// Same as above, only |comparer| is used to sort/compare the objects, just like
+// -[NSArray sortedArrayUsingSelector]. If |comparer| is nil, nil is returned.
+- (NSArray *)gtm_mergeArray:(NSArray *)newArray
+ compareSelector:(SEL)comparer
+ mergeSelector:(SEL)merger;
+
+@end
+
+
diff --git a/Foundation/GTMNSArray+Merge.m b/Foundation/GTMNSArray+Merge.m
new file mode 100644
index 0000000..725aa8a
--- /dev/null
+++ b/Foundation/GTMNSArray+Merge.m
@@ -0,0 +1,112 @@
+//
+// GTMNSArray+Merge.m
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+//
+
+#import "GTMNSArray+Merge.h"
+
+#import "GTMDefines.h"
+
+#if GTM_IPHONE_SDK
+#import <objc/message.h>
+#else // GTM_IPHONE_SDK
+#import <objc/objc-runtime.h>
+#endif // GTM_IPHONE_SDK
+
+@implementation NSArray (GTMNSArrayMergingAdditions)
+
+- (NSArray *)gtm_mergeArray:(NSArray *)newArray
+ mergeSelector:(SEL)merger {
+ return [self gtm_mergeArray:newArray
+ compareSelector:@selector(compare:)
+ mergeSelector:merger];
+}
+
+- (NSArray *)gtm_mergeArray:(NSArray *)newArray
+ compareSelector:(SEL)comparer
+ mergeSelector:(SEL)merger {
+ // must have a compare selector
+ if (!comparer) return nil;
+
+ // Sort and merge the contents of |self| with |newArray|.
+ NSArray *sortedMergedArray = nil;
+ if ([self count] && [newArray count]) {
+ NSMutableArray *mergingArray = [self mutableCopy];
+ [mergingArray sortUsingSelector:comparer];
+ NSArray *sortedNewArray
+ = [newArray sortedArrayUsingSelector:comparer];
+
+ NSUInteger oldIndex = 0;
+ NSUInteger oldCount = [mergingArray count];
+ id oldItem = (oldIndex < oldCount)
+ ? [mergingArray objectAtIndex:0]
+ : nil;
+
+ id newItem = nil;
+ NSEnumerator *itemEnum = [sortedNewArray objectEnumerator];
+ while ((newItem = [itemEnum nextObject])) {
+ BOOL stillLooking = YES;
+ while (oldIndex < oldCount && stillLooking) {
+ // We must take care here, since Intel leaves junk in high bytes of
+ // return register for predicates that return BOOL.
+ // For details see:
+ // http://developer.apple.com/documentation/MacOSX/Conceptual/universal_binary/universal_binary_tips/chapter_5_section_23.html
+ // and
+ // http://www.red-sweater.com/blog/320/abusing-objective-c-with-class#comment-83187
+ NSComparisonResult result
+ = ((NSComparisonResult (*)(id, SEL, id))objc_msgSend)(newItem, comparer, oldItem);
+ if (result == NSOrderedSame && merger) {
+ // It's a match!
+ id repItem = [oldItem performSelector:merger
+ withObject:newItem];
+ [mergingArray replaceObjectAtIndex:oldIndex
+ withObject:repItem];
+ ++oldIndex;
+ oldItem = (oldIndex < oldCount)
+ ? [mergingArray objectAtIndex:oldIndex]
+ : nil;
+ stillLooking = NO;
+ } else if (result == NSOrderedAscending
+ || (result == NSOrderedSame && !merger)) {
+ // This is either a new item and belongs right here, or it's
+ // a match to an existing item but we're not merging.
+ [mergingArray insertObject:newItem
+ atIndex:oldIndex];
+ ++oldIndex;
+ ++oldCount;
+ stillLooking = NO;
+ } else {
+ ++oldIndex;
+ oldItem = (oldIndex < oldCount)
+ ? [mergingArray objectAtIndex:oldIndex]
+ : nil;
+ }
+ }
+ if (stillLooking) {
+ // Once we get here, the rest of the new items get appended.
+ [mergingArray addObject:newItem];
+ }
+ }
+ sortedMergedArray = mergingArray;
+ } else if ([self count]) {
+ sortedMergedArray = [self sortedArrayUsingSelector:comparer];
+ } else if ([newArray count]) {
+ sortedMergedArray = [newArray sortedArrayUsingSelector:comparer];
+ }
+ return sortedMergedArray;
+}
+
+@end
diff --git a/Foundation/GTMNSArray+MergeTest.m b/Foundation/GTMNSArray+MergeTest.m
new file mode 100644
index 0000000..0d8eb81
--- /dev/null
+++ b/Foundation/GTMNSArray+MergeTest.m
@@ -0,0 +1,219 @@
+//
+// GTMNSArray+MergeTest.m
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+//
+
+#import "GTMSenTestCase.h"
+#import "GTMNSArray+Merge.h"
+
+@interface GTMNSArray_MergeTest : GTMTestCase
+@end
+
+
+@interface NSString (GTMStringMergingTestAdditions)
+
+- (NSString *)mergeString:(NSString *)stringB;
+
+@end
+
+
+@implementation GTMNSArray_MergeTest
+
+- (void)testMergingTwoEmptyArrays {
+ NSArray *emptyArrayA = [NSArray array];
+ NSArray *emptyArrayB = [NSArray array];
+ NSArray *mergedArray = [emptyArrayA gtm_mergeArray:emptyArrayB
+ mergeSelector:nil];
+ STAssertNil(mergedArray,
+ @"merge of two empty arrays with no merger should render nil");
+}
+
+- (void)testMergingTwoEmptyArraysWithMerger {
+ NSArray *emptyArrayA = [NSArray array];
+ NSArray *emptyArrayB = [NSArray array];
+ NSArray *mergedArray
+ = [emptyArrayA gtm_mergeArray:emptyArrayB
+ mergeSelector:@selector(mergeString:)];
+ STAssertNil(mergedArray,
+ @"merge of two empty arrays with merger should render nil");
+}
+
+- (void)testMergingEmptyWithNilArray {
+ NSArray *emptyArrayA = [NSArray array];
+ NSArray *nilArrayB = nil;
+ NSArray *mergedArray = [emptyArrayA gtm_mergeArray:nilArrayB
+ mergeSelector:nil];
+ STAssertNil(mergedArray,
+ @"merge of empty with nil array with no merger should render nil");
+}
+
+- (void)testMergingEmptyWithNilArrayWithMerger {
+ NSArray *emptyArrayA = [NSArray array];
+ NSArray *nilArrayB = nil;
+ NSArray *mergedArray
+ = [emptyArrayA gtm_mergeArray:nilArrayB
+ mergeSelector:@selector(mergeObject:)];
+ STAssertNil(mergedArray,
+ @"merge of empty with nil array with merger should render nil");
+}
+
+- (void)testMergingTwoOneItemArraysThatDontMatch {
+ NSArray *arrayA = [NSArray arrayWithObject:@"abc.def"];
+ NSArray *arrayB = [NSArray arrayWithObject:@"abc.ghi"];
+ NSArray *mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:nil];
+ STAssertNotNil(mergedArray,
+ @"merge of two non empty arrays with no merger should render "
+ @"an array");
+ STAssertEquals([mergedArray count], (NSUInteger)2,
+ @"merged array should have two items");
+ STAssertEqualObjects([mergedArray objectAtIndex:0], @"abc.def", nil);
+ STAssertEqualObjects([mergedArray objectAtIndex:1], @"abc.ghi", nil);
+}
+
+- (void)testMergingTwoOneItemArraysThatDontMatchWithMerger {
+ NSArray *arrayA = [NSArray arrayWithObject:@"abc.def"];
+ NSArray *arrayB = [NSArray arrayWithObject:@"abc.ghi"];
+ NSArray *mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:@selector(mergeString:)];
+ STAssertNotNil(mergedArray,
+ @"merge of two non empty arrays with merger should render "
+ @"an array");
+ STAssertEquals([mergedArray count], (NSUInteger)2,
+ @"merged array should have two items");
+ STAssertEqualObjects([mergedArray objectAtIndex:0], @"abc.def", nil);
+ STAssertEqualObjects([mergedArray objectAtIndex:1], @"abc.ghi", nil);
+}
+
+- (void)testMergingTwoOneItemArraysThatMatch {
+ NSArray *arrayA = [NSArray arrayWithObject:@"abc.def"];
+ NSArray *arrayB = [NSArray arrayWithObject:@"abc.def"];
+ NSArray *mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:nil];
+ STAssertNotNil(mergedArray,
+ @"merge of two matching arrays with no merger should render "
+ @"an array");
+ STAssertEquals([mergedArray count], (NSUInteger)2,
+ @"merged array with no merger should have two items");
+ STAssertEqualObjects([mergedArray objectAtIndex:0], @"abc.def", nil);
+ STAssertEqualObjects([mergedArray objectAtIndex:1], @"abc.def", nil);
+}
+
+- (void)testMergingTwoOneItemArraysThatMatchWithMerger {
+ NSArray *arrayA = [NSArray arrayWithObject:@"abc.def"];
+ NSArray *arrayB = [NSArray arrayWithObject:@"abc.def"];
+ NSArray *mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:@selector(mergeString:)];
+ STAssertNotNil(mergedArray,
+ @"merge of two matching arrays with merger should render "
+ @"an array");
+ STAssertEquals([mergedArray count], (NSUInteger)1,
+ @"merged array with merger should have one items");
+ STAssertEqualObjects([mergedArray objectAtIndex:0], @"abc.def", nil);
+}
+
+- (void)testMergingMultipleItemArray {
+ NSArray *arrayA = [NSArray arrayWithObjects:
+ @"Kansas",
+ @"Arkansas",
+ @"Wisconson",
+ @"South Carolina",
+ nil];
+ NSArray *arrayB = [NSArray arrayWithObjects:
+ @"South Carolina",
+ @"Quebec",
+ @"British Columbia",
+ @"Arkansas",
+ @"South Hamptom",
+ nil];
+ NSArray *mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:nil];
+ STAssertNotNil(mergedArray,
+ @"merge of two non empty arrays with no merger should render "
+ @"an array");
+ STAssertEquals([mergedArray count], (NSUInteger)9,
+ @"merged array should have 9 items");
+}
+
+- (void)testMergingMultipleItemArrayWithMerger {
+ NSArray *arrayA = [NSArray arrayWithObjects:
+ @"Kansas",
+ @"Arkansas",
+ @"Wisconson",
+ @"South Carolina",
+ nil];
+ NSArray *arrayB = [NSArray arrayWithObjects:
+ @"South Carolina",
+ @"Quebec",
+ @"British Columbia",
+ @"Arkansas",
+ @"South Hamptom",
+ nil];
+ NSArray *mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:@selector(mergeString:)];
+ STAssertNotNil(mergedArray,
+ @"merge of two non empty arrays with merger should render "
+ @"an array");
+ STAssertEquals([mergedArray count], (NSUInteger)7,
+ @"merged array should have 7 items");
+}
+
+- (void)testMergeWithEmptyArrays {
+ NSArray *arrayA = [NSArray arrayWithObjects:@"xyz", @"abc", @"mno", nil];
+ NSArray *arrayB = [NSArray array];
+ NSArray *expected = [NSArray arrayWithObjects:@"abc", @"mno", @"xyz", nil];
+ STAssertNotNil(arrayA, nil);
+ STAssertNotNil(arrayB, nil);
+ STAssertNotNil(expected, nil);
+ NSArray *mergedArray;
+
+ // no merger
+ mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:nil];
+ STAssertNotNil(mergedArray, nil);
+ STAssertEqualObjects(mergedArray, expected, nil);
+
+ // w/ merger
+ mergedArray = [arrayA gtm_mergeArray:arrayB
+ mergeSelector:@selector(mergeString:)];
+ STAssertNotNil(mergedArray, nil);
+ STAssertEqualObjects(mergedArray, expected, nil);
+
+ // no merger and array args reversed
+ mergedArray = [arrayB gtm_mergeArray:arrayA
+ mergeSelector:nil];
+ STAssertNotNil(mergedArray, nil);
+ STAssertEqualObjects(mergedArray, expected, nil);
+
+ // w/ merger and array args reversed
+ mergedArray = [arrayB gtm_mergeArray:arrayA
+ mergeSelector:@selector(mergeString:)];
+ STAssertNotNil(mergedArray, nil);
+ STAssertEqualObjects(mergedArray, expected, nil);
+
+}
+
+@end
+
+
+@implementation NSString (GTMStringMergingTestAdditions)
+
+- (NSString *)mergeString:(NSString *)stringB {
+ return stringB;
+}
+
+@end
+
diff --git a/Foundation/GTMNSFileManager+Carbon.h b/Foundation/GTMNSFileManager+Carbon.h
new file mode 100644
index 0000000..86215ea
--- /dev/null
+++ b/Foundation/GTMNSFileManager+Carbon.h
@@ -0,0 +1,60 @@
+//
+// GTMNSFileManager+Carbon.h
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Foundation/Foundation.h>
+
+
+// A few useful methods for dealing with paths and carbon structures
+@interface NSFileManager (GTMFileManagerCarbonAdditions)
+
+// Converts a path to an alias
+// Args:
+// path - the path to convert
+//
+// Returns:
+// An alias wrapped up in an autoreleased NSData. Nil on failure.
+//
+- (NSData *)gtm_aliasDataForPath:(NSString *)path;
+
+// Converts an alias to a path
+// Args:
+// alias - an alias wrapped up in an NSData
+//
+// Returns:
+// The path. Nil on failure.
+//
+- (NSString *)gtm_pathFromAliasData:(NSData *)alias;
+
+// Converts a path to an FSRef *
+// Args:
+// path - the path to convert
+//
+// Returns:
+// An autoreleased FSRef *. Nil on failure.
+//
+- (FSRef *)gtm_FSRefForPath:(NSString *)path;
+
+// Converts an FSRef to a path
+// Args:
+// fsRef - the FSRef to convert
+//
+// Returns:
+// The path. Nil on failure.
+//
+- (NSString *)gtm_pathFromFSRef:(FSRef *)fsRef;
+@end
diff --git a/Foundation/GTMNSFileManager+Carbon.m b/Foundation/GTMNSFileManager+Carbon.m
new file mode 100644
index 0000000..4f8ba8b
--- /dev/null
+++ b/Foundation/GTMNSFileManager+Carbon.m
@@ -0,0 +1,99 @@
+//
+// GTMNSFileManager+Carbon.m
+//
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMNSFileManager+Carbon.h"
+#import <CoreServices/CoreServices.h>
+#import <sys/param.h>
+#import "GTMDefines.h"
+
+@implementation NSFileManager (GTMFileManagerCarbonAdditions)
+
+- (NSData *)gtm_aliasDataForPath:(NSString *)path {
+ NSData *data = nil;
+ FSRef ref;
+ AliasHandle alias = NULL;
+
+ require_quiet([path length], CantUseParams);
+ require_noerr(FSPathMakeRef((UInt8 *)[path fileSystemRepresentation],
+ &ref, NULL), CantMakeRef);
+ require_noerr(FSNewAlias(NULL, &ref, &alias), CantMakeAlias);
+
+ Size length = GetAliasSize(alias);
+ data = [NSData dataWithBytes:*alias length:length];
+
+ DisposeHandle((Handle)alias);
+
+CantMakeAlias:
+CantMakeRef:
+CantUseParams:
+ return data;
+}
+
+- (NSString *)gtm_pathFromAliasData:(NSData *)data {
+ NSString *path = nil;
+ require_quiet(data, CantUseParams);
+
+ AliasHandle alias;
+ const void *bytes = [data bytes];
+ NSUInteger length = [data length];
+ require_noerr(PtrToHand(bytes, (Handle *)&alias, length), CantMakeHandle);
+
+ FSRef ref;
+ Boolean wasChanged;
+ // we don't use a require here because it is quite legitimate for an alias
+ // resolve to fail.
+ if (FSResolveAlias(NULL, alias, &ref, &wasChanged) == noErr) {
+ path = [self gtm_pathFromFSRef:&ref];
+ }
+ DisposeHandle((Handle)alias);
+CantMakeHandle:
+CantUseParams:
+ return path;
+}
+
+- (FSRef *)gtm_FSRefForPath:(NSString *)path {
+ FSRef* fsRef = NULL;
+ require_quiet([path length], CantUseParams);
+ NSMutableData *fsRefData = [NSMutableData dataWithLength:sizeof(FSRef)];
+ require(fsRefData, CantAllocateFSRef);
+ fsRef = (FSRef*)[fsRefData mutableBytes];
+ Boolean isDir = FALSE;
+ const UInt8 *filePath = (const UInt8 *)[path fileSystemRepresentation];
+ require_noerr_action(FSPathMakeRef(filePath, fsRef, &isDir),
+ CantMakeRef, fsRef = NULL);
+CantMakeRef:
+CantAllocateFSRef:
+CantUseParams:
+ return fsRef;
+}
+
+- (NSString *)gtm_pathFromFSRef:(FSRef *)fsRef {
+ NSString *nsPath = nil;
+ require_quiet(fsRef, CantUseParams);
+
+ char path[MAXPATHLEN];
+ require_noerr(FSRefMakePath(fsRef, (UInt8 *)path, MAXPATHLEN), CantMakePath);
+ nsPath = [self stringWithFileSystemRepresentation:path length:strlen(path)];
+ nsPath = [nsPath stringByStandardizingPath];
+
+CantMakePath:
+CantUseParams:
+ return nsPath;
+}
+
+@end
diff --git a/Foundation/GTMNSFileManager+CarbonTest.m b/Foundation/GTMNSFileManager+CarbonTest.m
new file mode 100644
index 0000000..ae58840
--- /dev/null
+++ b/Foundation/GTMNSFileManager+CarbonTest.m
@@ -0,0 +1,69 @@
+//
+// GTMNSFileManager+CarbonTest.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 "GTMNSFileManager+Carbon.h"
+#import "GTMUnitTestDevLog.h"
+#import <CoreServices/CoreServices.h>
+
+@interface GTMNSFileManager_CarbonTest : GTMTestCase
+@end
+
+@implementation GTMNSFileManager_CarbonTest
+
+- (void)testAliasPathFSRefConversion {
+ NSString *path = NSHomeDirectory();
+ STAssertNotNil(path, nil);
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ FSRef *fsRef = [fileManager gtm_FSRefForPath:path];
+ STAssertNotNULL(fsRef, nil);
+ AliasHandle alias;
+ STAssertNoErr(FSNewAlias(nil, fsRef, &alias), nil);
+ STAssertNotNULL(alias, nil);
+ NSData *aliasData = [NSData dataWithBytes:*alias
+ length:GetAliasSize(alias)];
+ STAssertNotNil(aliasData, nil);
+ NSString *path2 = [fileManager gtm_pathFromAliasData:aliasData];
+ STAssertEqualObjects(path, path2, nil);
+ NSData *aliasData2 = [fileManager gtm_aliasDataForPath:path2];
+ STAssertNotNil(aliasData2, nil);
+ NSString *path3 = [fileManager gtm_pathFromAliasData:aliasData2];
+ STAssertEqualObjects(path2, path3, nil);
+ NSString *path4 = [fileManager gtm_pathFromFSRef:fsRef];
+ STAssertEqualObjects(path, path4, nil);
+
+ // Failure cases
+ [GTMUnitTestDevLog expectPattern:@"DebugAssert: "
+ @"GoogleToolboxForMac: FSPathMakeRef.*"];
+ STAssertNULL([fileManager gtm_FSRefForPath:@"/ptah/taht/dosent/esixt/"],
+ nil);
+
+ STAssertNULL([fileManager gtm_FSRefForPath:@""], nil);
+ STAssertNULL([fileManager gtm_FSRefForPath:nil], nil);
+ STAssertNil([fileManager gtm_pathFromFSRef:nil], nil);
+ STAssertNil([fileManager gtm_pathFromAliasData:nil], nil);
+ STAssertNil([fileManager gtm_pathFromAliasData:[NSData data]], nil);
+
+ [GTMUnitTestDevLog expectPattern:@"DebugAssert: "
+ @"GoogleToolboxForMac: FSPathMakeRef.*"];
+ STAssertNil([fileManager gtm_aliasDataForPath:@"/ptah/taht/dosent/esixt/"], nil);
+ STAssertNil([fileManager gtm_aliasDataForPath:@""], nil);
+ STAssertNil([fileManager gtm_aliasDataForPath:nil], nil);
+}
+
+@end
diff --git a/Foundation/GTMObjC2Runtime.h b/Foundation/GTMObjC2Runtime.h
index f901d1e..8d62abf 100644
--- a/Foundation/GTMObjC2Runtime.h
+++ b/Foundation/GTMObjC2Runtime.h
@@ -49,11 +49,8 @@
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
#import "objc/Protocol.h"
+#import <libkern/OSAtomic.h>
-#ifdef __cplusplus
-extern "C" {
-#endif
-
OBJC_EXPORT Class object_getClass(id obj);
OBJC_EXPORT const char *class_getName(Class cls);
OBJC_EXPORT BOOL class_conformsToProtocol(Class cls, Protocol *protocol);
@@ -67,7 +64,41 @@ OBJC_EXPORT struct objc_method_description protocol_getMethodDescription(Protoco
SEL aSel,
BOOL isRequiredMethod,
BOOL isInstanceMethod);
-#ifdef __cplusplus
+
+// If building for 10.4 but using the 10.5 SDK, don't include these.
+#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
+// atomics
+// On Leopard these are GC aware
+// Intentionally did not include the non-barrier versions, because I couldn't
+// come up with a case personally where you wouldn't want to use the
+// barrier versions.
+GTM_INLINE bool OSAtomicCompareAndSwapPtrBarrier(void *predicate,
+ void *replacement,
+ volatile void *theValue) {
+#if defined(__LP64__) && __LP64__
+ return OSAtomicCompareAndSwap64Barrier((int64_t)predicate,
+ (int64_t)replacement,
+ (int64_t *)theValue);
+#else // defined(__LP64__) && __LP64__
+ return OSAtomicCompareAndSwap32Barrier((int32_t)predicate,
+ (int32_t)replacement,
+ (int32_t *)theValue);
+#endif // defined(__LP64__) && __LP64__
+}
+
+GTM_INLINE BOOL objc_atomicCompareAndSwapGlobalBarrier(id predicate,
+ id replacement,
+ volatile id *objectLocation) {
+ return OSAtomicCompareAndSwapPtrBarrier(predicate,
+ replacement,
+ objectLocation);
+}
+GTM_INLINE BOOL objc_atomicCompareAndSwapInstanceVariableBarrier(id predicate,
+ id replacement,
+ volatile id *objectLocation) {
+ return OSAtomicCompareAndSwapPtrBarrier(predicate,
+ replacement,
+ objectLocation);
}
#endif
diff --git a/Foundation/GTMRegex.h b/Foundation/GTMRegex.h
index 3313e0e..c32eee2 100644
--- a/Foundation/GTMRegex.h
+++ b/Foundation/GTMRegex.h
@@ -60,7 +60,7 @@ typedef NSUInteger GTMRegexOptions;
#define _EXTERN
#define _INITIALIZE_AS(x) =x
#else
-#define _EXTERN extern
+#define _EXTERN GTM_EXTERN
#define _INITIALIZE_AS(x)
#endif
diff --git a/Foundation/GTMStackTrace.h b/Foundation/GTMStackTrace.h
index 9726da5..d8cc642 100644
--- a/Foundation/GTMStackTrace.h
+++ b/Foundation/GTMStackTrace.h
@@ -51,9 +51,7 @@ struct GTMAddressDescriptor {
// #6 0x000025b9 tart () [/Users/me/./StackLog]
//
-#ifdef GTM_MACOS_SDK // currently not supported on iPhone
NSString *GTMStackTrace(void);
-#endif
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
// Returns a string containing a nicely formatted stack trace from the
@@ -63,12 +61,15 @@ NSString *GTMStackTrace(void);
NSString *GTMStackTraceFromException(NSException *e);
#endif
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
// Returns an array of program counters from the current thread's stack.
// *** You should probably use GTMStackTrace() instead of this function ***
// However, if you actually want all the PCs in "void *" form, then this
// funtion is more convenient. This will include PCs of GTMStaceTrace and
// its inner utility functions that you may want to strip out.
//
+// You can use +[NSThread callStackReturnAddresses] in 10.5 or later.
+//
// Args:
// outPcs - an array of "void *" pointers to the program counters found on the
// current thread's stack.
@@ -77,9 +78,8 @@ NSString *GTMStackTraceFromException(NSException *e);
// Returns:
// The number of program counters actually added to outPcs.
//
-#ifdef GTM_MACOS_SDK // currently not supported on iPhone
NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger count);
-#endif
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
// Returns an array of GTMAddressDescriptors from the current thread's stack.
// *** You should probably use GTMStackTrace() instead of this function ***
@@ -97,10 +97,8 @@ NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger count);
// Returns:
// The number of program counters actually added to outPcs.
//
-#ifdef GTM_MACOS_SDK // currently not supported on iPhone
NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[],
NSUInteger count);
-#endif
#ifdef __cplusplus
}
diff --git a/Foundation/GTMStackTrace.m b/Foundation/GTMStackTrace.m
index c22c153..0b28743 100644
--- a/Foundation/GTMStackTrace.m
+++ b/Foundation/GTMStackTrace.m
@@ -22,16 +22,6 @@
#include "GTMStackTrace.h"
#include "GTMObjC2Runtime.h"
-// Structure representing a small portion of a stack, starting from the saved
-// frame pointer, and continuing through the saved program counter.
-struct GTMStackFrame {
- void *saved_fp;
-#if defined (__ppc__) || defined(__ppc64__)
- void *padding;
-#endif
- void *saved_pc;
-};
-
struct GTMClassDescription {
const char *class_name;
Method *class_methods;
@@ -187,13 +177,26 @@ static NSString *GTMStackTraceFromAddressDescriptors(struct GTMAddressDescriptor
#pragma mark Public functions
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
+// Before 10.5, we have to do this ourselves. 10.5 adds
+// +[NSThread callStackReturnAddresses].
+
+// Structure representing a small portion of a stack, starting from the saved
+// frame pointer, and continuing through the saved program counter.
+struct GTMStackFrame {
+ void *saved_fp;
+#if defined (__ppc__) || defined(__ppc64__)
+ void *padding;
+#endif
+ void *saved_pc;
+};
+
// __builtin_frame_address(0) is a gcc builtin that returns a pointer to the
// current frame pointer. We then use the frame pointer to walk the stack
// picking off program counters and other saved frame pointers. This works
// great on i386, but PPC requires a little more work because the PC (or link
// register) isn't always stored on the stack.
//
-#ifdef GTM_MACOS_SDK // currently not supported on iPhone
NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger count) {
if (!outPcs || (count < 1)) return 0;
@@ -220,41 +223,106 @@ NSUInteger GTMGetStackProgramCounters(void *outPcs[], NSUInteger count) {
return level;
}
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
NSUInteger GTMGetStackAddressDescriptors(struct GTMAddressDescriptor outDescs[],
NSUInteger count) {
if (count < 1 || !outDescs) return 0;
+ NSUInteger result = 0;
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
+ // Before 10.5, we collect the stack ourselves.
+
void **pcs = calloc(count, sizeof(void*));
if (!pcs) return 0;
NSUInteger newSize = GTMGetStackProgramCounters(pcs, count);
- NSUInteger result
- = GTMGetStackAddressDescriptorsForAddresses(pcs, outDescs, newSize);
+ result = GTMGetStackAddressDescriptorsForAddresses(pcs, outDescs, newSize);
free(pcs);
+
+#else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+ // Use +[NSThread callStackReturnAddresses]
+
+ NSArray *addresses = [NSThread callStackReturnAddresses];
+ NSUInteger addrCount = [addresses count];
+ if (addrCount) {
+ void **pcs = calloc(addrCount, sizeof(void*));
+ if (pcs) {
+ void **pcsScanner = pcs;
+ for (NSNumber *address in addresses) {
+ NSUInteger addr = [address unsignedIntegerValue];
+ *pcsScanner = (void *)addr;
+ ++pcsScanner;
+ }
+ if (count < addrCount) {
+ addrCount = count;
+ }
+ // Fill in the desc structures
+ result = GTMGetStackAddressDescriptorsForAddresses(pcs, outDescs, addrCount);
+ }
+ if (pcs) free(pcs);
+ }
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+
return result;
}
NSString *GTMStackTrace(void) {
+ // If we don't have enough frames, return an empty string
+ NSString *result = @"";
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
+ // Before 10.5, we collect the stack ourselves.
+
// The maximum number of stack frames that we will walk. We limit this so
// that super-duper recursive functions (or bugs) don't send us for an
// infinite loop.
struct GTMAddressDescriptor descs[100];
size_t depth = sizeof(descs) / sizeof(struct GTMAddressDescriptor);
depth = GTMGetStackAddressDescriptors(descs, depth);
-
+
// Start at the second item so that GTMStackTrace and it's utility calls (of
// which there is currently 1) is not included in the output.
const size_t kTracesToStrip = 2;
if (depth > kTracesToStrip) {
- return GTMStackTraceFromAddressDescriptors(&descs[kTracesToStrip],
- (depth - kTracesToStrip));
+ result = GTMStackTraceFromAddressDescriptors(&descs[kTracesToStrip],
+ (depth - kTracesToStrip));
}
- // If we didn't have enough frames, return an empty string
- return @"";
+#else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+ // Use +[NSThread callStackReturnAddresses]
+
+ NSArray *addresses = [NSThread callStackReturnAddresses];
+ NSUInteger count = [addresses count];
+ if (count) {
+ void **pcs = calloc(count, sizeof(void*));
+ struct GTMAddressDescriptor *descs
+ = calloc(count, sizeof(struct GTMAddressDescriptor));
+ if (pcs && descs) {
+ void **pcsScanner = pcs;
+ for (NSNumber *address in addresses) {
+ NSUInteger addr = [address unsignedIntegerValue];
+ *pcsScanner = (void *)addr;
+ ++pcsScanner;
+ }
+ // Fill in the desc structures
+ count = GTMGetStackAddressDescriptorsForAddresses(pcs, descs, count);
+ // Build the trace
+ // We skip 1 frame because the +[NSThread callStackReturnAddresses] will
+ // start w/ this frame.
+ const size_t kTracesToStrip = 1;
+ if (count > kTracesToStrip) {
+ result = GTMStackTraceFromAddressDescriptors(&descs[kTracesToStrip],
+ (count - kTracesToStrip));
+ }
+ }
+ if (pcs) free(pcs);
+ if (descs) free(descs);
+ }
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+
+ return result;
}
-#endif // GTM_MACOS_SDK
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
@@ -287,4 +355,4 @@ NSString *GTMStackTraceFromException(NSException *e) {
return trace;
}
-#endif // MAC_OS_X_VERSION_MIN_REQUIRED
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
diff --git a/Foundation/GTMStackTraceTest.m b/Foundation/GTMStackTraceTest.m
index 01b02a3..457f3e9 100644
--- a/Foundation/GTMStackTraceTest.m
+++ b/Foundation/GTMStackTraceTest.m
@@ -25,7 +25,6 @@
@implementation GTMStackTraceTest
-#ifdef GTM_MACOS_SDK // currently not supported on iPhone
- (void)testStackTraceBasic {
NSString *stacktrace = GTMStackTrace();
NSArray *stacklines = [stacktrace componentsSeparatedByString:@"\n"];
@@ -45,7 +44,6 @@
@"First frame should contain #0, stack trace: %@",
stacktrace);
}
-#endif // GTM_MACOS_SDK
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
@@ -84,7 +82,8 @@
#endif
-#ifdef GTM_MACOS_SDK // currently not supported on iPhone
+#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
+
- (void)testProgramCountersBasic {
void *pcs[10];
NSUInteger depth = 10;
@@ -120,6 +119,7 @@
void *current_pc = __builtin_return_address(0);
STAssertEquals(pcs2[1], current_pc, @"pcs[1] should equal the current PC");
}
-#endif // GTM_MACOS_SDK
+
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5
@end
diff --git a/Foundation/GTMSystemVersion.h b/Foundation/GTMSystemVersion.h
index 959d2af..07580a7 100644
--- a/Foundation/GTMSystemVersion.h
+++ b/Foundation/GTMSystemVersion.h
@@ -72,14 +72,15 @@
// Architecture Strings
// TODO: Should probably break iPhone up into iPhone_ARM and iPhone_Simulator
// but haven't found a need yet.
-extern NSString *const kGTMArch_iPhone;
-extern NSString *const kGTMArch_ppc;
-extern NSString *const kGTMArch_ppc64;
-extern NSString *const kGTMArch_x86_64;
-extern NSString *const kGTMArch_i386;
+GTM_EXTERN NSString *const kGTMArch_iPhone;
+GTM_EXTERN NSString *const kGTMArch_ppc;
+GTM_EXTERN NSString *const kGTMArch_ppc64;
+GTM_EXTERN NSString *const kGTMArch_x86_64;
+GTM_EXTERN NSString *const kGTMArch_i386;
// System Build Number constants
-extern NSString *const kGTMSystemBuild10_5_5;
-extern NSString *const kGTMSystemBuild10_6_0_WWDC;
+GTM_EXTERN NSString *const kGTMSystemBuild10_5_5;
+GTM_EXTERN NSString *const kGTMSystemBuild10_6_0_WWDC;
+GTM_EXTERN NSString *const kGTMSystemBuild10_6_0_10A190;
diff --git a/Foundation/GTMSystemVersion.m b/Foundation/GTMSystemVersion.m
index 868f6b8..f9c7861 100644
--- a/Foundation/GTMSystemVersion.m
+++ b/Foundation/GTMSystemVersion.m
@@ -37,6 +37,7 @@ static NSString *const kSystemVersionPlistPath = @"/System/Library/CoreServices/
NSString *const kGTMSystemBuild10_5_5 = @"9F33";
NSString *const kGTMSystemBuild10_6_0_WWDC = @"10A96";
+NSString *const kGTMSystemBuild10_6_0_10A190 = @"10A190";
@implementation GTMSystemVersion
+ (void)initialize {