diff options
author | thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2008-12-12 15:24:34 +0000 |
---|---|---|
committer | thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2008-12-12 15:24:34 +0000 |
commit | 2e8516354aacef064d01425808da06d2cdcb4791 (patch) | |
tree | 9da4758828930280d32f18d54ece7a249df742c7 /Foundation | |
parent | 9f64d056dd70f2f938ac6f5adb8e75b650dc2e1a (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.h | 78 | ||||
-rw-r--r-- | Foundation/GTMFileSystemKQueue.m | 250 | ||||
-rw-r--r-- | Foundation/GTMFileSystemKQueueTest.m | 394 | ||||
-rw-r--r-- | Foundation/GTMHTTPFetcher.h | 2 | ||||
-rw-r--r-- | Foundation/GTMHTTPServer.h | 2 | ||||
-rw-r--r-- | Foundation/GTMLightweightProxy.m | 4 | ||||
-rw-r--r-- | Foundation/GTMLightweightProxyTest.m | 24 | ||||
-rw-r--r-- | Foundation/GTMNSAppleScript+HandlerTest.m | 9 | ||||
-rw-r--r-- | Foundation/GTMNSArray+Merge.h | 47 | ||||
-rw-r--r-- | Foundation/GTMNSArray+Merge.m | 112 | ||||
-rw-r--r-- | Foundation/GTMNSArray+MergeTest.m | 219 | ||||
-rw-r--r-- | Foundation/GTMNSFileManager+Carbon.h | 60 | ||||
-rw-r--r-- | Foundation/GTMNSFileManager+Carbon.m | 99 | ||||
-rw-r--r-- | Foundation/GTMNSFileManager+CarbonTest.m | 69 | ||||
-rw-r--r-- | Foundation/GTMObjC2Runtime.h | 41 | ||||
-rw-r--r-- | Foundation/GTMRegex.h | 2 | ||||
-rw-r--r-- | Foundation/GTMStackTrace.h | 10 | ||||
-rw-r--r-- | Foundation/GTMStackTrace.m | 108 | ||||
-rw-r--r-- | Foundation/GTMStackTraceTest.m | 8 | ||||
-rw-r--r-- | Foundation/GTMSystemVersion.h | 15 | ||||
-rw-r--r-- | Foundation/GTMSystemVersion.m | 1 |
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 { |