aboutsummaryrefslogtreecommitdiff
path: root/Foundation
diff options
context:
space:
mode:
authorGravatar thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-07-09 21:19:26 +0000
committerGravatar thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-07-09 21:19:26 +0000
commit6ddca07d6c48b0226550b6ff3e01a177b6afd6a5 (patch)
treeadbd23d96eacb322eb47a1c8c525bc4d2536dfc3 /Foundation
parent098f7782e376e818b131a8f9a8222056c63d51ee (diff)
- Added GTMSignalHandler for simple signal handling (via kqueue/runloop).
- Fixed up GTMIPhoneUnitTestDelegate to be pickier about which tests it runs - Added GTMNSString+URLArguments to GTMiPhone - Added GTMHTTPFetcher and GTMHTTPServer to GTMiPhone - Made sure that build would work with iPhone device attached, and that all tests run directly on the phone. - Added GTMValidatingContainers which are a set of mutable container classes that allow you to have a selector on a target that is called to verify that the objects being put into the container are valid. This can be controlled at compile time so that you don't take the performance hit in a release build. - Added GTMPath, which represents an existing absolute path on the file system. It also makes it very easy to contruct new paths in the file system as well as whole directory hierarchies. - Added GTMNSString+Replace for a common replacement need. - Added NSString+FindFolder for two commen helpers for building paths to common locations. - Added GTMLargeTypeWindow for doing display windows similar to Address Book Large Type display for phone numbers.
Diffstat (limited to 'Foundation')
-rw-r--r--Foundation/GTMGeometryUtils.h4
-rw-r--r--Foundation/GTMGeometryUtilsTest.m2
-rw-r--r--Foundation/GTMHTTPServer.h4
-rw-r--r--Foundation/GTMNSFileManager+Path.h3
-rw-r--r--Foundation/GTMNSString+FindFolder.h55
-rw-r--r--Foundation/GTMNSString+FindFolder.m79
-rw-r--r--Foundation/GTMNSString+FindFolderTest.m77
-rw-r--r--Foundation/GTMNSString+Replace.h40
-rw-r--r--Foundation/GTMNSString+Replace.m48
-rw-r--r--Foundation/GTMNSString+ReplaceTest.m55
-rw-r--r--Foundation/GTMPath.h132
-rw-r--r--Foundation/GTMPath.m156
-rw-r--r--Foundation/GTMPathTest.m231
-rw-r--r--Foundation/GTMRegex.m2
-rw-r--r--Foundation/GTMSignalHandler.h75
-rw-r--r--Foundation/GTMSignalHandler.m209
-rw-r--r--Foundation/GTMSignalHandlerTest.m143
-rw-r--r--Foundation/GTMStackTraceTest.m1
-rw-r--r--Foundation/GTMValidatingContainers.h195
-rw-r--r--Foundation/GTMValidatingContainers.m476
-rw-r--r--Foundation/GTMValidatingContainersTest.m367
21 files changed, 2349 insertions, 5 deletions
diff --git a/Foundation/GTMGeometryUtils.h b/Foundation/GTMGeometryUtils.h
index 7d54cf2..e8a078c 100644
--- a/Foundation/GTMGeometryUtils.h
+++ b/Foundation/GTMGeometryUtils.h
@@ -180,7 +180,7 @@ CG_INLINE CGFloat GTMCGDistanceBetweenPoints(CGPoint pt1, CGPoint pt2) {
#endif
}
-#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
+#if !GTM_IPHONE_SDK
// iPhone does not have NSTypes defined, only CGTypes. So no NSRect, NSPoint etc.
#pragma mark -
@@ -429,4 +429,4 @@ CG_INLINE CGFloat GTMNSDistanceBetweenPoints(NSPoint pt1, NSPoint pt2) {
GTMNSPointToCGPoint(pt2));
}
-#endif // (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
+#endif // !GTM_IPHONE_SDK
diff --git a/Foundation/GTMGeometryUtilsTest.m b/Foundation/GTMGeometryUtilsTest.m
index 8a81f0d..ca817f8 100644
--- a/Foundation/GTMGeometryUtilsTest.m
+++ b/Foundation/GTMGeometryUtilsTest.m
@@ -179,7 +179,7 @@
for (size_t i = 0; i < sizeof(tests) / sizeof(Test); ++i) {
CGRect result = GTMCGScaleRectangleToSize(rect, tests[i].size_,
GTMScaleProportionally);
- STAssertEquals(result, GTMCGRectOfSize(tests[i].newSize_), @"failed on test %z", i);
+ STAssertEquals(result, GTMCGRectOfSize(tests[i].newSize_), @"failed on test %zd", i);
}
CGRect result = GTMCGScaleRectangleToSize(CGRectZero, tests[0].size_,
diff --git a/Foundation/GTMHTTPServer.h b/Foundation/GTMHTTPServer.h
index 70e3f78..c70178c 100644
--- a/Foundation/GTMHTTPServer.h
+++ b/Foundation/GTMHTTPServer.h
@@ -31,6 +31,10 @@
#import <Foundation/Foundation.h>
#import "GTMDefines.h"
+#if GTM_IPHONE_SDK
+#import <CFNetwork/CFNetwork.h>
+#endif // GTM_IPHONE_SDK
+
// Global contants needed for errors from start
#undef _EXTERN
diff --git a/Foundation/GTMNSFileManager+Path.h b/Foundation/GTMNSFileManager+Path.h
index 95ba41e..2ed6888 100644
--- a/Foundation/GTMNSFileManager+Path.h
+++ b/Foundation/GTMNSFileManager+Path.h
@@ -31,6 +31,9 @@
///
/// If you are building for 10.5 or later, you should just use the new api:
/// createDirectoryAtPath:withIntermediateDirectories:attributes:error:
+///
+/// Also if you need more control over the creation of paths and their
+/// attributes, look into using GTMPath.
///
/// Args:
/// path - the path of the directory to create.
diff --git a/Foundation/GTMNSString+FindFolder.h b/Foundation/GTMNSString+FindFolder.h
new file mode 100644
index 0000000..1b4d362
--- /dev/null
+++ b/Foundation/GTMNSString+FindFolder.h
@@ -0,0 +1,55 @@
+//
+// GTMNSString+FindFolder.h
+//
+// 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 <Foundation/Foundation.h>
+
+@interface NSString (GTMStringFindFolderAdditions)
+
+// Create a path to a folder located with FindFolder
+//
+// Args:
+// theFolderType: one of the folder types in Folders.h
+// (kPreferencesFolderType, etc)
+// theDomain: one of the domains in Folders.h (kLocalDomain, kUserDomain, etc)
+// doCreate: create the folder if it does not already exist
+//
+// Returns:
+// full path to folder, or nil if the folder doesn't exist or can't be created
+//
++ (NSString *)gtm_stringWithPathForFolder:(OSType)theFolderType
+ inDomain:(short)theDomain
+ doCreate:(BOOL)doCreate;
+
+// Create a path to a folder inside a folder located with FindFolder
+//
+// Args:
+// theFolderType: one of the folder types in Folders.h
+// (kPreferencesFolderType, etc)
+// subfolderName: name of directory inside the Apple folder to be located or created
+// theDomain: one of the domains in Folders.h (kLocalDomain, kUserDomain, etc)
+// doCreate: create the folder if it does not already exist
+//
+// Returns:
+// full path to subdirectory, or nil if the folder doesn't exist or can't be created
+//
++ (NSString *)gtm_stringWithPathForFolder:(OSType)theFolderType
+ subfolderName:(NSString *)subfolderName
+ inDomain:(short)theDomain
+ doCreate:(BOOL)doCreate;
+
+@end
diff --git a/Foundation/GTMNSString+FindFolder.m b/Foundation/GTMNSString+FindFolder.m
new file mode 100644
index 0000000..7d2f3b5
--- /dev/null
+++ b/Foundation/GTMNSString+FindFolder.m
@@ -0,0 +1,79 @@
+//
+// GTMNSString+FindFolder.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 "GTMNSString+FindFolder.h"
+#import "GTMGarbageCollection.h"
+
+@implementation NSString (GTMStringFindFolderAdditions)
+
++ (NSString *)gtm_stringWithPathForFolder:(OSType)theFolderType
+ inDomain:(short)theDomain
+ doCreate:(BOOL)doCreate {
+
+ NSString* folderPath = nil;
+ FSRef folderRef;
+
+ OSErr err = FSFindFolder(theDomain, theFolderType, doCreate, &folderRef);
+ if (err == noErr) {
+
+ CFURLRef folderURL = CFURLCreateFromFSRef(kCFAllocatorSystemDefault, &folderRef);
+ if (folderURL) {
+
+ folderPath = GTMNSMakeCollectable(CFURLCopyFileSystemPath(folderURL, kCFURLPOSIXPathStyle));
+ [folderPath autorelease];
+
+ CFRelease(folderURL);
+ }
+ }
+ return folderPath;
+}
+
++ (NSString *)gtm_stringWithPathForFolder:(OSType)theFolderType
+ subfolderName:(NSString *)subfolderName
+ inDomain:(short)theDomain
+ doCreate:(BOOL)doCreate {
+ NSString *resultPath = nil;
+ NSString *subdirPath = nil;
+ NSString *parentFolderPath = [self gtm_stringWithPathForFolder:theFolderType
+ inDomain:theDomain
+ doCreate:doCreate];
+ if (parentFolderPath) {
+
+ // find the path to the subdirectory
+ subdirPath = [parentFolderPath stringByAppendingPathComponent:subfolderName];
+
+ NSFileManager* fileMgr = [NSFileManager defaultManager];
+ BOOL isDir = NO;
+ if ([fileMgr fileExistsAtPath:subdirPath isDirectory:&isDir] && isDir) {
+ // it already exists
+ resultPath = subdirPath;
+ } else if (doCreate) {
+
+ // create the subdirectory with the parent folder's attributes
+ NSDictionary* attrs = [fileMgr fileAttributesAtPath:parentFolderPath
+ traverseLink:YES];
+ if ([fileMgr createDirectoryAtPath:subdirPath
+ attributes:attrs]) {
+ resultPath = subdirPath;
+ }
+ }
+ }
+ return resultPath;
+}
+
+@end
diff --git a/Foundation/GTMNSString+FindFolderTest.m b/Foundation/GTMNSString+FindFolderTest.m
new file mode 100644
index 0000000..66fd329
--- /dev/null
+++ b/Foundation/GTMNSString+FindFolderTest.m
@@ -0,0 +1,77 @@
+//
+// GTMNSString+FindFolderTest.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 "GTMNSString+FindFolder.h"
+
+@interface GTMNSString_FindFolderTest : GTMTestCase
+@end
+
+@implementation GTMNSString_FindFolderTest
+
+- (void)testStringWithPathForFolder {
+ // for gtm_stringWithPathForFolder:inDomain:doCreate:
+ // the parameters all get passed through to FSFindFolder so there's no point testing
+ // other combinations; our semantics will match FSFindFolder's
+ NSString *prefsPath = [NSString gtm_stringWithPathForFolder:kPreferencesFolderType
+ inDomain:kUserDomain
+ doCreate:NO];
+ NSString *realPrefsPath = [@"~/Library/Preferences" stringByExpandingTildeInPath];
+ STAssertEqualObjects(realPrefsPath, prefsPath, @"Found incorrect prefs path");
+
+
+ // test the subfolder method; it should return nil if we pass NO and the
+ // subfolder doesn't already exist
+
+ NSString *googCacheNoCreatePath = [NSString gtm_stringWithPathForFolder:kCachedDataFolderType
+ subfolderName:@"GTMUnitTestDuzntExist"
+ inDomain:kUserDomain
+ doCreate:NO];
+ STAssertNil(googCacheNoCreatePath, @"Should not exist: %@", googCacheNoCreatePath);
+
+ // test creating ~/Library/Cache/GTMUnitTestCreated
+
+ NSString *folderName = @"GTMUnitTestCreated";
+ NSString *gtmCachePath = [NSString gtm_stringWithPathForFolder:kCachedDataFolderType
+ subfolderName:folderName
+ inDomain:kUserDomain
+ doCreate:YES];
+ NSString *testPath = [NSString gtm_stringWithPathForFolder:kCachedDataFolderType
+ inDomain:kUserDomain
+ doCreate:NO];
+ NSString *testPathAppended = [testPath stringByAppendingPathComponent:folderName];
+ STAssertEqualObjects(gtmCachePath, testPathAppended, @"Unexpected path name");
+
+ NSFileManager* fileMgr = [NSFileManager defaultManager];
+ BOOL isDir = NO;
+ BOOL pathExists = [fileMgr fileExistsAtPath:gtmCachePath isDirectory:&isDir] && isDir;
+ STAssertTrue(pathExists, @"Path %@ is not existing like it should", gtmCachePath);
+
+ // test finding it again w/o having to create it
+ NSString *gtmCachePath2 = [NSString gtm_stringWithPathForFolder:kCachedDataFolderType
+ subfolderName:folderName
+ inDomain:kUserDomain
+ doCreate:NO];
+ STAssertEqualObjects(gtmCachePath2, gtmCachePath, nil);
+
+ BOOL didRemove = [fileMgr removeFileAtPath:gtmCachePath
+ handler:nil];
+ STAssertTrue(didRemove, @"Error removing %@", gtmCachePath);
+}
+
+@end
diff --git a/Foundation/GTMNSString+Replace.h b/Foundation/GTMNSString+Replace.h
new file mode 100644
index 0000000..3a88c80
--- /dev/null
+++ b/Foundation/GTMNSString+Replace.h
@@ -0,0 +1,40 @@
+//
+// GTMNSString+Replace.h
+//
+// 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 <Foundation/Foundation.h>
+
+/// Give easy search-n-replace functionality to NSString.
+@interface NSString (GTMStringReplaceAdditions)
+
+/// Returns a new autoreleased string by replacing all occurrences of
+// |oldString| with |newString| (case sensitive). If |oldString| is nil or
+// @"" nothing is done and |self| is returned. If |newString| is nil, it's
+// treated as if |newString| were the empty string, thus effectively
+// deleting all occurrences of |oldString| from |self|.
+//
+// Args:
+// target - the NSString to search for
+// replacement - the NSString to replace |oldString| with
+//
+// Returns:
+// A new autoreleased NSString
+//
+- (NSString *)gtm_stringByReplacingString:(NSString *)target
+ withString:(NSString *)replacement;
+
+@end
diff --git a/Foundation/GTMNSString+Replace.m b/Foundation/GTMNSString+Replace.m
new file mode 100644
index 0000000..4e8195c
--- /dev/null
+++ b/Foundation/GTMNSString+Replace.m
@@ -0,0 +1,48 @@
+//
+// GTMNSString+Replace.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 "GTMNSString+Replace.h"
+
+
+@implementation NSString (GTMStringReplaceAdditions)
+
+- (NSString *)gtm_stringByReplacingString:(NSString *)target
+ withString:(NSString *)replacement {
+ // If |target| was nil, then do nothing and return |self|
+ //
+ // We do the retain+autorelease dance here because of this use case:
+ // NSString *s1 = [[NSString alloc] init...];
+ // NSString *s2 = [s1 stringByReplacingString:@"foo" withString:@"bar"];
+ // [s1 release]; // |s2| still needs to be valid after this line
+ if (!target)
+ return [[self retain] autorelease];
+
+ // If |replacement| is nil we want it to be treated as if @"" was specified
+ // ... effectively removing |target| from self
+ if (!replacement)
+ replacement = @"";
+
+ NSMutableString *result = [[self mutableCopy] autorelease];
+ [result replaceOccurrencesOfString:target
+ withString:replacement
+ options:0
+ range:NSMakeRange(0, [result length])];
+ return result;
+}
+
+@end
diff --git a/Foundation/GTMNSString+ReplaceTest.m b/Foundation/GTMNSString+ReplaceTest.m
new file mode 100644
index 0000000..e814040
--- /dev/null
+++ b/Foundation/GTMNSString+ReplaceTest.m
@@ -0,0 +1,55 @@
+//
+// GTMNSString+ReplaceTest.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 "GTMNSString+Replace.h"
+
+@interface GTMNSString_ReplaceTest : GTMTestCase
+@end
+
+@implementation GTMNSString_ReplaceTest
+
+- (void)testStringByReplacingStringWithString {
+ NSString *testString = @"a bc debc gh";
+ NSString *result;
+
+ result = [testString gtm_stringByReplacingString:@"bc" withString:@"BC"];
+ STAssertEqualObjects(@"a BC deBC gh", result,
+ @"'bc' wasn't replaced with 'BC'");
+
+ result = [testString gtm_stringByReplacingString:@"bc" withString:@""];
+ STAssertEqualObjects(@"a de gh", result, @"'bc' wasn't replaced with ''");
+
+ result = [testString gtm_stringByReplacingString:@"bc" withString:nil];
+ STAssertEqualObjects(@"a de gh", result, @"'bc' wasn't replaced with (nil)");
+
+ result = [testString gtm_stringByReplacingString:@" " withString:@"S"];
+ STAssertEqualObjects(@"aSbcSdebcSgh", result, @"' ' wasn't replaced with 'S'");
+
+ result = [testString gtm_stringByReplacingString:nil withString:@"blah"];
+ STAssertEqualObjects(testString, result, @"(nil) wasn't replaced with 'blah'");
+
+ result = [testString gtm_stringByReplacingString:nil withString:nil];
+ STAssertEqualObjects(testString, result, @"(nil) wasn't replaced with (nil)");
+
+ result = [testString gtm_stringByReplacingString:@"" withString:@"X"];
+ STAssertEqualObjects(testString, result,
+ @"replacing '' with anything should yield the original string");
+}
+
+@end
diff --git a/Foundation/GTMPath.h b/Foundation/GTMPath.h
new file mode 100644
index 0000000..6ac2347
--- /dev/null
+++ b/Foundation/GTMPath.h
@@ -0,0 +1,132 @@
+//
+// GTMPath.h
+//
+// Copyright 2007-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Foundation/Foundation.h>
+
+
+// GTMPath
+//
+// This class represents a single, absolute file system path. The represented
+// path must exist at the time of creation. This class also allows you to easily
+// create new paths (or full hierarchies) based on existing GTMPath instances.
+//
+// Given a GTMPath instance, new files and directories can be created inside
+// that path providing the instance refers to a directory. It is an error to try
+// to create a file/directory from a GTMPath that represents a file (this should
+// be common sense: clearly mkdir /etc/passwd/foo won't work).
+//
+// === Examples ===
+//
+// 1. This sample creates a GTMPath that references /tmp, then gets the
+// attributes for that directory.
+//
+// GTMPath *tmp = [GTMPath pathWithFullPath:@"/tmp"];
+// NSDictionary *attr = [tmp attributes];
+//
+//
+// 2. This sample creates a new directory inside /tmp named "foo".
+//
+// GTMPath *tmp = [GTMPath pathWithFullPath:@"/tmp"];
+// GTMPath *foo = [tmp createDirectoryName:@"foo" mode:0755];
+//
+//
+// 3. This sample creates a GTMPath instance that represents a user's ~/Library
+// folder.
+//
+// GTMPath *library = [GTMPath pathWithFullPath:@"/Users/bob/Library"];
+// ...
+//
+//
+// 4. This sample creates a directory hierarchy, where each level has its own
+// mode. Notice that the return value from these -create* methods is the
+// GTMPath that was just created. This allows these creation calls to be
+// chained together enabling easy creation of directory hierarchies.
+// This is one of the big benefits of this class.
+//
+// GTMPath *tmp = [GTMPath pathWithFullPath:@"/tmp"];
+// GTMPath *baz = [[[tmp createDirectoryName:@"foo" mode:0755]
+// createDirectoryName:@"bar" mode:0756]
+// createDirectoryName:@"baz" mode:0757];
+//
+@interface GTMPath : NSObject {
+ @private
+ NSString *fullPath_;
+}
+
+// Returns a GTMPath instance that represents the full path specified by
+// |fullPath|. Note that |fullPath| MUST be an absolute path.
++ (id)pathWithFullPath:(NSString *)fullPath;
+
+// Returns a GTMPath instance that represents the full path specified by
+// |fullPath|. Note that |fullPath| MUST be an absolute path. This method is the
+// designated initializer.
+- (id)initWithFullPath:(NSString *)fullPath;
+
+// Returns the name of this GTMPath instance. This is not the full path. It is
+// just the component name of this GTMPath instance. This is equivalent to
+// the Unix basename(3) function.
+- (NSString *)name;
+
+// Returns this path's parent GTMPath. This method will ONLY (and always) return
+// nil when |name| is "/". In otherwords, parent will be nil IFF this GTMPath
+// instance represents the root path, because "/" doesn't really have a parent.
+- (GTMPath *)parent;
+
+// Returns YES if this GTMPath represents a directory.
+- (BOOL)isDirectory;
+
+// Returns YES if this GTMPath instance represents the root path "/".
+- (BOOL)isRoot;
+
+// Returns the file system attributes of the path represented by this GTMPath
+// instance. See -[NSFileManager fileAttributesAtPath:...] for details.
+- (NSDictionary *)attributes;
+
+// Returns a string representation of the absolute path represented by this
+// GTMPath instance.
+- (NSString *)fullPath;
+
+@end
+
+
+// Methods for creating files and directories inside a GTMPath instance. These
+// methods are only allowed to be called on GTMPath instances that represent
+// directories. See the NSFileManager documentation for details about the
+// |attributes| parameters.
+@interface GTMPath (GTMPathGeneration)
+
+// Creates a new directory with the specified mode or attributes inside the
+// current GTMPath instance. If the creation is successful, a GTMPath for the
+// newly created directory is returned. Otherwise, nil is returned.
+- (GTMPath *)createDirectoryName:(NSString *)name mode:(mode_t)mode;
+- (GTMPath *)createDirectoryName:(NSString *)name
+ attributes:(NSDictionary *)attributes;
+
+// Creates a new file with the specified mode or attributes inside the
+// current GTMPath instance. If the creation is successful, a GTMPath for the
+// newly created file is returned. Otherwise, nil is returned. |data| is the
+// data to put in the file when created.
+- (GTMPath *)createFileName:(NSString *)name mode:(mode_t)mode;
+- (GTMPath *)createFileName:(NSString *)name
+ attributes:(NSDictionary *)attributes;
+- (GTMPath *)createFileName:(NSString *)name
+ attributes:(NSDictionary *)attributes
+ data:(NSData *)data;
+
+@end
+
diff --git a/Foundation/GTMPath.m b/Foundation/GTMPath.m
new file mode 100644
index 0000000..28ffad0
--- /dev/null
+++ b/Foundation/GTMPath.m
@@ -0,0 +1,156 @@
+//
+// GTMPath.m
+//
+// Copyright 2007-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMPath.h"
+
+
+@implementation GTMPath
+
++ (id)pathWithFullPath:(NSString *)fullPath {
+ return [[[self alloc] initWithFullPath:fullPath] autorelease];
+}
+
+- (id)init {
+ return [self initWithFullPath:nil];
+}
+
+- (id)initWithFullPath:(NSString *)fullPath {
+ if ((self = [super init])) {
+ fullPath_ = [fullPath copy];
+ if (![fullPath_ isAbsolutePath] || [self attributes] == nil) {
+ [self release];
+ return nil;
+ }
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [fullPath_ release];
+ [super dealloc];
+}
+
+- (NSString *)description {
+ return [self fullPath];
+}
+
+- (NSString *)name {
+ return [fullPath_ lastPathComponent];
+}
+
+- (GTMPath *)parent {
+ if ([self isRoot]) return nil;
+ NSString *parentPath = [fullPath_ stringByDeletingLastPathComponent];
+ return [[self class] pathWithFullPath:parentPath];
+}
+
+- (BOOL)isDirectory {
+ BOOL isDir = NO;
+ BOOL exists = [[NSFileManager defaultManager]
+ fileExistsAtPath:fullPath_ isDirectory:&isDir];
+ return exists && isDir;
+}
+
+- (BOOL)isRoot {
+ return [fullPath_ isEqualToString:@"/"];
+}
+
+- (NSDictionary *)attributes {
+ return [[NSFileManager defaultManager]
+ fileAttributesAtPath:fullPath_
+ traverseLink:YES];
+}
+
+- (NSString *)fullPath {
+ return [[fullPath_ copy] autorelease];
+}
+
+@end
+
+
+@implementation GTMPath (GTMPathGeneration)
+
+- (GTMPath *)createDirectoryName:(NSString *)name mode:(mode_t)mode {
+ NSDictionary *attributes =
+ [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:mode]
+ forKey:NSFilePosixPermissions];
+ return [self createDirectoryName:name attributes:attributes];
+}
+
+- (GTMPath *)createDirectoryName:(NSString *)name
+ attributes:(NSDictionary *)attributes {
+ if ([name length] == 0) return nil;
+
+ // We first check to see if the requested directory alread exists by trying
+ // to create a GTMPath from the desired new path string. Only if the path
+ // doesn't already exist do we attempt to create it. If the path already
+ // exists, we will end up returning a GTMPath for the pre-existing path.
+ NSString *newPath = [fullPath_ stringByAppendingPathComponent:name];
+ GTMPath *nascentPath = [GTMPath pathWithFullPath:newPath];
+ if (nascentPath != nil && ![nascentPath isDirectory]) {
+ return nil; // Return nil because the path exists, but it's not a dir
+ }
+
+ if (nascentPath == nil) {
+ BOOL created = [[NSFileManager defaultManager]
+ createDirectoryAtPath:newPath
+ attributes:attributes];
+ nascentPath = created ? [GTMPath pathWithFullPath:newPath] : nil;
+ }
+
+ return nascentPath;
+}
+
+- (GTMPath *)createFileName:(NSString *)name mode:(mode_t)mode {
+ NSDictionary *attributes =
+ [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:mode]
+ forKey:NSFilePosixPermissions];
+ return [self createFileName:name attributes:attributes];
+}
+
+- (GTMPath *)createFileName:(NSString *)name
+ attributes:(NSDictionary *)attributes {
+ return [self createFileName:name attributes:attributes data:[NSData data]];
+}
+
+- (GTMPath *)createFileName:(NSString *)name
+ attributes:(NSDictionary *)attributes
+ data:(NSData *)data {
+ if ([name length] == 0) return nil;
+
+ // See createDirectoryName:attribute: for some high-level notes about what and
+ // why this method does what it does.
+ NSString *newPath = [fullPath_ stringByAppendingPathComponent:name];
+ GTMPath *nascentPath = [GTMPath pathWithFullPath:newPath];
+ if (nascentPath != nil && [nascentPath isDirectory]) {
+ return nil; // Return nil because the path exists, but it's a dir
+ }
+
+ if (nascentPath == nil) {
+ BOOL created = [[NSFileManager defaultManager]
+ createFileAtPath:newPath
+ contents:data
+ attributes:attributes];
+ nascentPath = created ? [GTMPath pathWithFullPath:newPath] : nil;
+ }
+
+ return nascentPath;
+}
+
+@end
diff --git a/Foundation/GTMPathTest.m b/Foundation/GTMPathTest.m
new file mode 100644
index 0000000..3130c4f
--- /dev/null
+++ b/Foundation/GTMPathTest.m
@@ -0,0 +1,231 @@
+//
+// GTMPathTest.m
+//
+// Copyright 2007-2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMSenTestCase.h"
+#import "GTMPath.h"
+#import "GTMUnitTestDevLog.h"
+
+
+@interface GTMPathTest : GTMTestCase {
+ @private
+ NSString *testDirectory_;
+}
+@end
+
+@implementation GTMPathTest
+
+- (void)setUp {
+ NSString *tmp = NSTemporaryDirectory();
+ STAssertNotNil(tmp, nil);
+
+ testDirectory_ = [[tmp stringByAppendingPathComponent:@"GTMPathTest"] retain];
+ STAssertNotNil(testDirectory_, nil);
+
+ BOOL created = [[NSFileManager defaultManager]
+ createDirectoryAtPath:testDirectory_
+ attributes:nil];
+ STAssertTrue(created, nil);
+}
+
+- (void)tearDown {
+ // Make sure it's safe to remove this directory before nuking it.
+ STAssertNotNil(testDirectory_, nil);
+ STAssertNotEqualObjects(testDirectory_, @"/", nil);
+ [[NSFileManager defaultManager] removeFileAtPath:testDirectory_ handler:nil];
+}
+
+- (void)testBasicCreation {
+ GTMPath *path = nil;
+
+ path = [[[GTMPath alloc] init] autorelease];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@"/"];
+ STAssertNotNil(path, nil);
+ STAssertNil([path parent], nil);
+ STAssertTrue([path isRoot], nil);
+ STAssertTrue([path isDirectory], nil);
+ STAssertEqualObjects([path name], @"/", nil);
+ STAssertEqualObjects([path fullPath], @"/", nil);
+}
+
+- (void)testRecursiveInitialization {
+ GTMPath *path = nil;
+
+ path = [GTMPath pathWithFullPath:nil];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@""];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@"etc"];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@"/"];
+ STAssertNotNil(path, nil);
+ STAssertNil([path parent], nil);
+ STAssertTrue([path isRoot], nil);
+ STAssertTrue([path isDirectory], nil);
+ STAssertEqualObjects([path name], @"/", nil);
+ STAssertEqualObjects([path fullPath], @"/", nil);
+
+ path = [GTMPath pathWithFullPath:@"/etc"];
+ STAssertNotNil(path, nil);
+ STAssertEqualObjects([path name], @"etc", nil);
+ STAssertEqualObjects([path fullPath], @"/etc", nil);
+ STAssertTrue([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+ STAssertNotNil([path parent], nil);
+ STAssertTrue([[path parent] isRoot], nil);
+
+ path = [GTMPath pathWithFullPath:@"/etc/passwd"];
+ STAssertNotNil(path, nil);
+ STAssertEqualObjects([path name], @"passwd", nil);
+ STAssertEqualObjects([path fullPath], @"/etc/passwd", nil);
+ STAssertFalse([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+ STAssertNotNil([path parent], nil);
+ STAssertFalse([[path parent] isRoot], nil);
+ STAssertTrue([[path parent] isDirectory], nil);
+ STAssertTrue([[[path parent] parent] isRoot], nil);
+
+ STAssertTrue([[path description] length] > 1, nil);
+}
+
+- (void)testCreationWithNonExistentPath {
+ GTMPath *path = nil;
+
+ path = [GTMPath pathWithFullPath:@" "];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@"/abcxyz"];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@"/etc/foo"];
+ STAssertNil(path, nil);
+
+ path = [GTMPath pathWithFullPath:@"/foo/bar/baz"];
+ STAssertNil(path, nil);
+}
+
+- (void)testDirectoryCreation {
+ GTMPath *tmp = [GTMPath pathWithFullPath:testDirectory_];
+ GTMPath *path = nil;
+
+ NSString *fooPath = [[tmp fullPath] stringByAppendingPathComponent:@"foo"];
+ path = [GTMPath pathWithFullPath:fooPath];
+ STAssertNil(path, nil);
+
+ path = [tmp createDirectoryName:@"foo" mode:0555];
+ STAssertNotNil(path, nil);
+ STAssertEqualObjects([path name], @"foo", nil);
+ // filePosixPermissions has odd return types in different SDKs, so we use
+ // STAssertTrue to avoid the macros type checks from choking us.
+ STAssertTrue([[path attributes] filePosixPermissions] == 0555, nil);
+ STAssertTrue([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+
+ // Trying to create a file where a dir already exists should fail
+ path = [tmp createFileName:@"foo" mode:0555];
+ STAssertNil(path, nil);
+
+ // Calling create again should succeed
+ path = [tmp createDirectoryName:@"foo" mode:0555];
+ STAssertNotNil(path, nil);
+ STAssertEqualObjects([path name], @"foo", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0555, nil);
+ STAssertTrue([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+
+ GTMPath *foo = [GTMPath pathWithFullPath:fooPath];
+ STAssertNotNil(foo, nil);
+ STAssertEqualObjects([path name], @"foo", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0555, nil);
+ STAssertTrue([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+}
+
+- (void)testFileCreation {
+ GTMPath *tmp = [GTMPath pathWithFullPath:testDirectory_];
+ GTMPath *path = nil;
+
+ NSString *fooPath = [[tmp fullPath] stringByAppendingPathComponent:@"foo"];
+ path = [GTMPath pathWithFullPath:fooPath];
+ STAssertNil(path, nil);
+
+ path = [tmp createFileName:@"foo" mode:0555];
+ STAssertNotNil(path, nil);
+ STAssertEqualObjects([path name], @"foo", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0555, nil);
+ STAssertFalse([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+
+ // Trying to create a dir where a file already exists should fail.
+ path = [tmp createDirectoryName:@"foo" mode:0555];
+ STAssertNil(path, nil);
+
+ // Calling create again should succeed
+ path = [tmp createFileName:@"foo" mode:0555];
+ STAssertNotNil(path, nil);
+ STAssertEqualObjects([path name], @"foo", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0555, nil);
+ STAssertFalse([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+
+ GTMPath *foo = [GTMPath pathWithFullPath:fooPath];
+ STAssertNotNil(foo, nil);
+ STAssertEqualObjects([path name], @"foo", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0555, nil);
+ STAssertFalse([path isDirectory], nil);
+ STAssertFalse([path isRoot], nil);
+
+ // Make sure we can't create a file/directory rooted off of |foo|, since it's
+ // not a directory.
+ path = [foo createFileName:@"bar" mode:0555];
+ STAssertNil(path, nil);
+ path = [foo createDirectoryName:@"bar" mode:0555];
+ STAssertNil(path, nil);
+}
+
+- (void)testHierarchyCreation {
+ GTMPath *tmp = [GTMPath pathWithFullPath:testDirectory_];
+ NSString *fooPath = [[tmp fullPath] stringByAppendingPathComponent:@"foo"];
+ GTMPath *path = [GTMPath pathWithFullPath:fooPath];
+ STAssertNil(path, nil);
+
+ path = [[[tmp createDirectoryName:@"foo" mode:0755]
+ createDirectoryName:@"bar" mode:0756]
+ createDirectoryName:@"baz" mode:0757];
+ STAssertNotNil(path, nil);
+
+ // Check "baz"
+ STAssertEqualObjects([path name], @"baz", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0757, nil);
+
+ // Check "bar"
+ path = [path parent];
+ STAssertEqualObjects([path name], @"bar", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0756, nil);
+
+ // Check "foo"
+ path = [path parent];
+ STAssertEqualObjects([path name], @"foo", nil);
+ STAssertTrue([[path attributes] filePosixPermissions] == 0755, nil);
+}
+
+@end
diff --git a/Foundation/GTMRegex.m b/Foundation/GTMRegex.m
index f183553..fb6e3a0 100644
--- a/Foundation/GTMRegex.m
+++ b/Foundation/GTMRegex.m
@@ -422,7 +422,7 @@ static NSString *const kReplacementPattern =
- (NSString *)description {
NSMutableString *result =
- [NSMutableString stringWithFormat:@"%@<%p> { pattern=\"%@\", rawNumSubPatterns=%z, options=(",
+ [NSMutableString stringWithFormat:@"%@<%p> { pattern=\"%@\", rawNumSubPatterns=%zd, options=(",
[self class], self, pattern_, regexData_.re_nsub];
if (options_) {
if (options_ & kGTMRegexOptionIgnoreCase)
diff --git a/Foundation/GTMSignalHandler.h b/Foundation/GTMSignalHandler.h
new file mode 100644
index 0000000..da3a3ea
--- /dev/null
+++ b/Foundation/GTMSignalHandler.h
@@ -0,0 +1,75 @@
+//
+// GTMSignalHandler.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>
+
+// GTMSignalHandler.
+//
+// This is a very simple, easy-to-use class for registering handlers that get
+// called when a specific signal is delivered. Also handy for ignoring
+// inconvenient signals. Ignoring SIGKILL is not support for what should be
+// obvious reasons.
+//
+// Example of how to catch SIGABRT and SIGTERM while ignring SIGWINCH:
+// GTMSignalHandler *abrt, *term, *winch;
+// abrt = [[GTMSignalHandler alloc]
+// initWithSignal:SIGABRT
+// target:self
+// handler:@selector(handleAbort:)];
+//
+// term = [[GTMSignalHandler alloc]
+// initWithSignal:SIGTERM
+// target:self
+// handler:@selector(handleTerm:)];
+//
+// winch = [[GTMSignalHandler alloc] initWithSignal:SIGWINCH
+// initWithSignal:SIGWINCH
+// target:nil
+// handler:NULL
+//
+// And then the signal handler has the triggered signal number boxed in an
+// NSNumber:
+// -(void)handleTerm:(NSNumber *)signo {
+// .. do stuff ..
+// }
+//
+// Release the handler when you're no longer interested in handling that signal.
+// Note that signal(SIG_IGN, signo) is performed on each signal handled by
+// objects of this class, and those do not get un-done.
+//
+// Multiple handlers for the same signal is NOT supported.
+//
+// kqueue() is used to handle the signals, and the default runloop for the first
+// signal handler is used for signal delivery, so keep that in mind when you're
+// using this class. This class explicitly does not handle arbitrary runloops
+// and threading.
+//
+@interface GTMSignalHandler : NSObject {
+ @private
+ int signo_;
+ __weak id target_;
+ SEL handler_;
+}
+
+// Returns a retained signal handler object that will invoke |handler| on the
+// |target| whenever a signal of number |signo| is delivered to the process.
+-(id)initWithSignal:(int)signo
+ target:(id)target
+ handler:(SEL)handler;
+
+@end // GTMSignalHandler
diff --git a/Foundation/GTMSignalHandler.m b/Foundation/GTMSignalHandler.m
new file mode 100644
index 0000000..f88adf6
--- /dev/null
+++ b/Foundation/GTMSignalHandler.m
@@ -0,0 +1,209 @@
+//
+// GTMSignalHandler.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 "GTMSignalHandler.h"
+#import "GTMDefines.h"
+
+#import <sys/event.h> // for kqueue() and kevent
+#import "GTMDebugSelectorValidation.h"
+
+// Simplifying assumption: No more than one handler for a particular signal is
+// alive at a time. When the second signal is registered, kqueue just updates
+// the info about the first signal, which makes -dealloc time complicated (what
+// happens when handler1(SIGUSR1) is released before handler2(SIGUSR1)?). This
+// could be solved by having one kqueue per signal, or keeping a list of
+// handlers interested in a particular signal, but not really worth it for apps
+// that register the handlers at startup and don't change them.
+
+
+// File descriptor for the kqueue that will hold all of our signal events.
+static int gSignalKQueueFileDescriptor;
+
+// A wrapper around the kqueue file descriptor so we can put it into a
+// runloop.
+static CFSocketRef gRunLoopSocket;
+
+
+@interface GTMSignalHandler (PrivateMethods)
+
+// Invoke |handler_| on the |target_|, passing a boxed |signo_|.
+- (void)notify;
+
+// Wrap the file descriptor in a CFSocket and add it to the runloop so that a
+// callback function will be called when there's activity on the descriptor. In
+// this case, we're interested in new stuff from the kqueue.
+- (void)addFileDescriptorMonitor:(int)fd;
+
+// Add ourselves to our global kqueue.
+- (void)registerWithKQueue;
+
+// Remove ourseves from our global kqueue.
+- (void)unregisterWithKQueue;
+
+@end // PrivateMethods
+
+
+@implementation GTMSignalHandler
+
+-(id)init {
+ // Folks shouldn't call init directly, so they get what they deserve.
+ return [self initWithSignal:0 target:nil handler:NULL];
+} // init
+
+
+- (id)initWithSignal:(int)signo
+ target:(id)target
+ handler:(SEL)handler {
+
+ if ((self = [super init])) {
+
+ if (signo == 0) {
+ [self release];
+ return nil;
+ }
+
+ signo_ = signo;
+ target_ = target; // Don't retain since target will most likely retain us.
+ handler_ = handler;
+ GTMAssertSelectorNilOrImplementedWithArguments(target_,
+ handler_,
+ @encode(NSNumber *),
+ NULL);
+
+ // We're handling this signal via kqueue, so turn off the usual signal
+ // handling.
+ signal(signo_, SIG_IGN);
+
+ if (handler != NULL) {
+ [self registerWithKQueue];
+ }
+ }
+ return self;
+} // initWithSignal
+
+- (void)finalize {
+ [self unregisterWithKQueue];
+
+ [super finalize];
+
+} // finalize
+
+- (void)dealloc {
+ [self unregisterWithKQueue];
+
+ [super dealloc];
+
+} // dealloc
+
+
+// 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(gSignalKQueueFileDescriptor, NULL, 0, &event, 1, NULL) == -1) {
+ _GTMDevLog(@"could not pick up kqueue event. Errno %d", errno); // COV_NF_LINE
+ } else {
+ GTMSignalHandler *handler = (GTMSignalHandler *)event.udata;
+ [handler notify];
+ }
+
+} // SocketCallBack
+
+
+// Cribbed from Advanced Mac OS X Programming
+- (void)addFileDescriptorMonitor:(int)fd {
+ 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;
+
+} // addFileDescriptorMonitor
+
+
+- (void)registerWithKQueue {
+
+ // Make sure we have our kqueue.
+ if (gSignalKQueueFileDescriptor == 0) {
+ gSignalKQueueFileDescriptor = kqueue();
+
+ if (gSignalKQueueFileDescriptor == -1) {
+ _GTMDevLog(@"could not make signal kqueue. Errno %d", errno); // COV_NF_LINE
+ return; // COV_NF_LINE
+ }
+
+ // Add the kqueue file descriptor to the runloop.
+ [self addFileDescriptorMonitor:gSignalKQueueFileDescriptor];
+ }
+
+ // Add a new event for the signal.
+ struct kevent filter;
+ EV_SET(&filter, signo_, EVFILT_SIGNAL, EV_ADD | EV_ENABLE | EV_CLEAR,
+ 0, 0, self);
+
+ const struct timespec noWait = { 0, 0 };
+ if (kevent(gSignalKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) != 0) {
+ _GTMDevLog(@"could not add event for signal %d. Errno %d", signo_, errno); // COV_NF_LINE
+ }
+
+} // registerWithKQueue
+
+
+- (void)unregisterWithKQueue {
+ // Short-circuit cases where we didn't actually register a kqueue event.
+ if (signo_ == 0) return;
+ if (handler_ == 0) return;
+
+ struct kevent filter;
+ EV_SET(&filter, signo_, EVFILT_SIGNAL, EV_DELETE, 0, 0, self);
+
+ const struct timespec noWait = { 0, 0 };
+ if (kevent(gSignalKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) != 0) {
+ _GTMDevLog(@"could not remove event for signal %d. Errno %d", signo_, errno); // COV_NF_LINE
+ }
+
+} // unregisterWithKQueue
+
+
+- (void)notify {
+ [target_ performSelector:handler_
+ withObject:[NSNumber numberWithInt:signo_]];
+} // notify
+
+@end // GTMSignalHandler
diff --git a/Foundation/GTMSignalHandlerTest.m b/Foundation/GTMSignalHandlerTest.m
new file mode 100644
index 0000000..9dcabf4
--- /dev/null
+++ b/Foundation/GTMSignalHandlerTest.m
@@ -0,0 +1,143 @@
+//
+// GTMSignalHandlerTest.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 "GTMSignalHandler.h"
+#import "GTMSenTestCase.h"
+
+@interface GTMSignalHandlerTest : GTMTestCase
+@end // GTMSignalHandlerTest
+
+@interface SignalCounter : NSObject {
+ @public
+ int signalCount_;
+ int lastSeenSignal_;
+}
+- (int)count;
+- (int)lastSeen;
+- (void)countSignal:(NSNumber *)signo;
++ (id)signalCounter;
+@end // SignalCounter
+
+@implementation SignalCounter
++ (id)signalCounter {
+ return [[[[self class] alloc] init] autorelease];
+}
+- (int)count {
+ return signalCount_;
+}
+- (int)lastSeen {
+ return lastSeenSignal_;
+}
+// Count the number of times this signal handler has fired.
+- (void)countSignal:(NSNumber *)signo {
+ signalCount_++;
+ lastSeenSignal_ = [signo intValue];
+} // countSignal
+@end // SignalCounter
+
+@implementation GTMSignalHandlerTest
+
+// Spin the run loop so that the kqueue event notifications will get delivered.
+- (void)giveSomeLove {
+ NSDate *endTime = [NSDate dateWithTimeIntervalSinceNow:0.5];
+ [[NSRunLoop currentRunLoop] runUntilDate:endTime];
+} // giveSomeLove
+
+
+- (void)testNillage {
+ GTMSignalHandler *handler;
+
+ // Just an init should return nil.
+ handler = [[[GTMSignalHandler alloc] init] autorelease];
+ STAssertNil(handler, nil);
+
+ // Zero signal should return nil as well.
+ handler = [[[GTMSignalHandler alloc]
+ initWithSignal:0
+ target:self
+ handler:@selector(nomnomnom:)] autorelease];
+ STAssertNil(handler, nil);
+
+} // testNillage
+
+
+- (void)testSingleHandler {
+ SignalCounter *counter = [SignalCounter signalCounter];
+ STAssertNotNil(counter, nil);
+
+ GTMSignalHandler *handler = [[GTMSignalHandler alloc]
+ initWithSignal:SIGWINCH
+ target:counter
+ handler:@selector(countSignal:)];
+ STAssertNotNil(handler, nil);
+ raise(SIGWINCH);
+ [self giveSomeLove];
+
+ STAssertEquals([counter count], 1, nil);
+ STAssertEquals([counter lastSeen], SIGWINCH, nil);
+
+ raise(SIGWINCH);
+ [self giveSomeLove];
+
+ STAssertEquals([counter count], 2, nil);
+ STAssertEquals([counter lastSeen], SIGWINCH, nil);
+
+ // create a second one to make sure we're seding data where we want
+ SignalCounter *counter2 = [SignalCounter signalCounter];
+ STAssertNotNil(counter2, nil);
+ [[[GTMSignalHandler alloc] initWithSignal:SIGUSR1
+ target:counter2
+ handler:@selector(countSignal:)] autorelease];
+
+ raise(SIGUSR1);
+ [self giveSomeLove];
+
+ STAssertEquals([counter count], 2, nil);
+ STAssertEquals([counter lastSeen], SIGWINCH, nil);
+ STAssertEquals([counter2 count], 1, nil);
+ STAssertEquals([counter2 lastSeen], SIGUSR1, nil);
+
+ [handler release];
+
+ // The signal is still ignored (so we shouldn't die), but the
+ // the handler method should not get called.
+ raise(SIGWINCH);
+
+ STAssertEquals([counter count], 2, nil);
+ STAssertEquals([counter lastSeen], SIGWINCH, nil);
+ STAssertEquals([counter2 count], 1, nil);
+ STAssertEquals([counter2 lastSeen], SIGUSR1, nil);
+
+} // testSingleHandler
+
+
+- (void)testIgnore {
+ SignalCounter *counter = [SignalCounter signalCounter];
+ STAssertNotNil(counter, nil);
+
+ [[[GTMSignalHandler alloc] initWithSignal:SIGUSR1
+ target:counter
+ handler:NULL] autorelease];
+
+ raise(SIGUSR1);
+ [self giveSomeLove];
+ STAssertEquals([counter count], 0, nil);
+
+} // testIgnore
+
+@end // GTMSignalHandlerTest
diff --git a/Foundation/GTMStackTraceTest.m b/Foundation/GTMStackTraceTest.m
index 16c6273..edcb851 100644
--- a/Foundation/GTMStackTraceTest.m
+++ b/Foundation/GTMStackTraceTest.m
@@ -16,7 +16,6 @@
// the License.
//
-#import <SenTestingKit/SenTestingKit.h>
#import <Foundation/Foundation.h>
#import "GTMStackTrace.h"
#import "GTMSenTestCase.h"
diff --git a/Foundation/GTMValidatingContainers.h b/Foundation/GTMValidatingContainers.h
new file mode 100644
index 0000000..1b11f1b
--- /dev/null
+++ b/Foundation/GTMValidatingContainers.h
@@ -0,0 +1,195 @@
+//
+// GTMValidatingContainers.h
+//
+// Mutable containers that do verification of objects being added to them
+// at runtime. Support for arrays, dictionaries and sets.
+//
+// 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.
+//
+
+// GTMValidatingContainers are a set of mutable container classes that allow
+// you to have a selector on a target that is called to verify that the objects
+// being put into the container are valid. This can be controlled at compile
+// time so that you don't take the performance hit in a release build using the
+// GTM_CONTAINERS_VALIDATE macro.
+// We have supplied validators for simple cases such as kindOfClass or
+// conformsToProtocol. See GTMKindOfClassValidator et al. for details.
+//
+// Example of usage:
+// id target = [GTMKindOfClassValidator validateAgainstClass:[NSString class]];
+// SEL selector = @selector(validateObject:forContainer:);
+// GTMValidatingArray *array = [GTMValidatingArray validatingArrayWithTarget:target
+// selector:selector];
+// [array addObject:@"foo"]; // Will be good
+// [array addObject:[NSNumber numberWithInt:2]; // Will fail
+//
+// By setting the GTM_CONTAINERS_VALIDATION_FAILED_LOG and
+// GTM_CONTAINERS_VALIDATION_FAILED_ASSERT macros you can control what happens
+// when a validation fails. If you implement your own validators, you may want
+// to control their internals using the same macros for consistency.
+//
+// Note that the validating collection types retain their targets.
+
+#import <Foundation/Foundation.h>
+#import "GTMDefines.h"
+
+// By default we only validate containers in debug. If you want to validate
+// in release as well, #define GTM_CONTAINERS_VALIDATE in a prefix or build
+// settings.
+#ifndef GTM_CONTAINERS_VALIDATE
+#if DEBUG
+#define GTM_CONTAINERS_VALIDATE 1
+#else // DEBUG
+#define GTM_CONTAINERS_VALIDATE 0
+#endif // DEBUG
+#endif // GTM_CONTAINERS_VALIDATE
+
+// If GTM_CONTAINERS_VALIDATE is on, and log and assert are both turned off
+// (see below), the object that failed validation will just not be added
+// to the container.
+
+// If you don't want log to occur on validation failure define
+// GTM_CONTAINERS_VALIDATION_FAILED_LOG to 0 in a prefix or build settings.
+#ifndef GTM_CONTAINERS_VALIDATION_FAILED_LOG
+#define GTM_CONTAINERS_VALIDATION_FAILED_LOG GTM_CONTAINERS_VALIDATE
+#endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG
+
+// If you don't want an assert to occur on validation failure define
+// GTM_CONTAINERS_VALIDATION_FAILED_ASSERT to 0 in a prefix or build settings.
+#ifndef GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+#define GTM_CONTAINERS_VALIDATION_FAILED_ASSERT GTM_CONTAINERS_VALIDATE
+#endif // GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+
+// Sometimes you get a container back from somebody else and want to validate
+// that it contains what you think it contains. _GTMValidateContainer
+// allows you to do exactly that. _GTMValidateContainer... give you specialty
+// functions for doing common types of validations. These all inline to nothing
+// if GTM_CONTAINERS_VALIDATE is not defined.
+#if GTM_CONTAINERS_VALIDATE
+void _GTMValidateContainer(id container, id target, SEL selector);
+void _GTMValidateContainerContainsKindOfClass(id container, Class cls);
+void _GTMValidateContainerContainsMemberOfClass(id container, Class cls);
+void _GTMValidateContainerConformsToProtocol(id container, Protocol *prot);
+void _GTMValidateContainerItemsRespondToSelector(id container, SEL sel);
+#else
+inline void _GTMValidateContainer(id container, id target, SEL selector) {
+}
+inline void _GTMValidateContainerContainsKindOfClass(id container, Class cls) {
+}
+inline void _GTMValidateContainerContainsMemberOfClass(id container,
+ Class cls) {
+}
+inline void _GTMValidateContainerConformsToProtocol(id container,
+ Protocol *prot) {
+}
+inline void _GTMValidateContainerItemsRespondToSelector(id container,
+ SEL sel) {
+}
+#endif
+
+
+// See comments near top of file for class description.
+@interface GTMValidatingArray : NSMutableArray {
+#if GTM_CONTAINERS_VALIDATE
+ NSMutableArray *embeddedContainer_;
+ id target_;
+ SEL selector_;
+#endif // #if GTM_CONTAINERS_VALIDATE
+}
++ (id)validatingArrayWithTarget:(id)target selector:(SEL)sel;
++ (id)validatingArrayWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel;
+- (id)initValidatingWithTarget:(id)target selector:(SEL)sel;
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel;
+@end
+
+// See comments near top of file for class description.
+@interface GTMValidatingDictionary : NSMutableDictionary {
+#if GTM_CONTAINERS_VALIDATE
+ NSMutableDictionary *embeddedContainer_;
+ id target_;
+ SEL selector_;
+#endif // #if GTM_CONTAINERS_VALIDATE
+}
++ (id)validatingDictionaryWithTarget:(id)target selector:(SEL)sel;
++ (id)validatingDictionaryWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel;
+- (id)initValidatingWithTarget:(id)target selector:(SEL)sel;
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel;
+@end
+
+// See comments near top of file for class description.
+@interface GTMValidatingSet : NSMutableSet {
+#if GTM_CONTAINERS_VALIDATE
+ NSMutableSet *embeddedContainer_;
+ id target_;
+ SEL selector_;
+#endif // #if GTM_CONTAINERS_VALIDATE
+}
++ (id)validatingSetWithTarget:(id)target selector:(SEL)sel;
++ (id)validatingSetWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel;
+- (id)initValidatingWithTarget:(id)target selector:(SEL)sel;
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel;
+@end
+
+#pragma mark -
+#pragma mark Simple Common Validators
+// See comments near top of file for examples of how these are used.
+@protocol GTMContainerValidatorProtocol
+- (BOOL)validateObject:(id)object forContainer:(id)container;
+@end
+
+// Validates that a given object is a kind of class (instance of class or an
+// instance of any class that inherits from that class)
+@interface GTMKindOfClassValidator : NSObject <GTMContainerValidatorProtocol> {
+ Class cls_;
+}
++ (id)validateAgainstClass:(Class)cls;
+- (id)initWithClass:(Class)cls;
+@end
+
+// Validates that a given object is a member of class (exact instance of class)
+@interface GTMMemberOfClassValidator : NSObject <GTMContainerValidatorProtocol> {
+ Class cls_;
+}
++ (id)validateAgainstClass:(Class)cls;
+- (id)initWithClass:(Class)cls;
+@end
+
+// Validates that a given object conforms to a protocol
+@interface GTMConformsToProtocolValidator : NSObject <GTMContainerValidatorProtocol> {
+ Protocol* prot_;
+}
++ (id)validateAgainstProtocol:(Protocol*)prot;
+- (id)initWithProtocol:(Protocol*)prot;
+@end
+
+// Validates that a given object responds to a given selector
+@interface GTMRespondsToSelectorValidator : NSObject <GTMContainerValidatorProtocol> {
+ SEL sel_;
+}
++ (id)validateAgainstSelector:(SEL)sel;
+- (id)initWithSelector:(SEL)sel;
+@end
diff --git a/Foundation/GTMValidatingContainers.m b/Foundation/GTMValidatingContainers.m
new file mode 100644
index 0000000..f46ba3f
--- /dev/null
+++ b/Foundation/GTMValidatingContainers.m
@@ -0,0 +1,476 @@
+//
+// GTMValidatingContainers.m
+//
+// Mutable containers that do verification of objects being added to them
+// at runtime. Support for arrays, dictionaries and sets.
+//
+// Documentation on subclassing class clusters (which we are doing) is here:
+// http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_9.html#//apple_ref/doc/uid/TP40002974-CH4-DontLinkElementID_105
+//
+// 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 "GTMValidatingContainers.h"
+
+#if GTM_CONTAINERS_VALIDATE
+
+#import "GTMDebugSelectorValidation.h"
+#if GTM_IPHONE_SDK
+#import <objc/message.h>
+#import <objc/runtime.h>
+#else // GTM_IPHONE_SDK
+#import <objc/objc-runtime.h>
+#endif // GTM_IPHONE_SDK
+
+static inline BOOL VerifyObjectWithTargetAndSelectorForContainer(id anObject,
+ id target,
+ SEL selector,
+ id container) {
+ // We must take care here, since Intel leaves junk in high bytes of return
+ // register for predicates that return BOOL.
+ // For details see:
+ // http://developer.apple.com/documentation/MacOSX/Conceptual/universal_binary/universal_binary_tips/chapter_5_section_23.html
+ // and
+ // http://www.red-sweater.com/blog/320/abusing-objective-c-with-class#comment-83187
+ BOOL isGood = ((BOOL (*)(id, SEL, id, id))objc_msgSend)(target, selector,
+ anObject, container);
+ if (!isGood) {
+#if GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ _GTMDevAssert(isGood, @"%@ failed container verification for %@",
+ anObject, [container description]);
+#endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG
+#if GTM_CONTAINERS_VALIDATION_FAILED_LOG
+ _GTMDevLog(@"%@ failed container verification for %@", anObject,
+ [container description]);
+#endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG
+ }
+ return isGood;
+}
+
+static inline void VerifySelectorOnTarget(SEL sel, id target) {
+ GTMAssertSelectorNilOrImplementedWithReturnTypeAndArguments(target,
+ sel,
+ @encode(BOOL),
+ @encode(id),
+ @encode(id),
+ nil);
+}
+
+void _GTMValidateContainerContainsKindOfClass(id container, Class cls) {
+ GTMKindOfClassValidator *validator;
+ validator = [GTMKindOfClassValidator validateAgainstClass:cls];
+ _GTMValidateContainer(container,
+ validator,
+ @selector(validateObject:forContainer:));
+}
+
+void _GTMValidateContainerContainsMemberOfClass(id container, Class cls) {
+ GTMMemberOfClassValidator *validator;
+ validator = [GTMMemberOfClassValidator validateAgainstClass:cls];
+ _GTMValidateContainer(container,
+ validator,
+ @selector(validateObject:forContainer:));
+}
+
+void _GTMValidateContainerConformsToProtocol(id container, Protocol* prot) {
+ GTMConformsToProtocolValidator *validator;
+ validator = [GTMConformsToProtocolValidator validateAgainstProtocol:prot];
+ _GTMValidateContainer(container,
+ validator,
+ @selector(validateObject:forContainer:));
+}
+
+void _GTMValidateContainerItemsRespondToSelector(id container, SEL sel) {
+ GTMRespondsToSelectorValidator *validator;
+ validator = [GTMRespondsToSelectorValidator validateAgainstSelector:sel];
+ _GTMValidateContainer(container,
+ validator,
+ @selector(validateObject:forContainer:));
+}
+
+void _GTMValidateContainer(id container, id target, SEL selector) {
+ if ([container respondsToSelector:@selector(objectEnumerator)]) {
+ NSEnumerator *enumerator = [container objectEnumerator];
+ id val;
+ while ((val = [enumerator nextObject])) {
+ VerifyObjectWithTargetAndSelectorForContainer(val,
+ target,
+ selector,
+ container);
+ }
+ } else {
+#if GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ _GTMDevAssert(0, @"container %@ does not respond to -objectEnumerator",
+ [container description]);
+#endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG
+#if GTM_CONTAINERS_VALIDATION_FAILED_LOG
+ _GTMDevLog(@"container does not respont to -objectEnumerator: %@",
+ [container description]);
+#endif // GTM_CONTAINERS_VALIDATION_FAILED_LOG
+ }
+}
+#endif // GTM_CONTAINERS_VALIDATE
+
+@implementation GTMValidatingArray
+
++ (id)validatingArrayWithTarget:(id)target selector:(SEL)sel {
+ return [self validatingArrayWithCapacity:0 target:target selector:sel];
+}
+
++ (id)validatingArrayWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ return [[[[self class] alloc] initValidatingWithCapacity:0
+ target:target
+ selector:sel] autorelease];
+}
+
+- (id)initValidatingWithTarget:(id)target selector:(SEL)sel {
+ return [self initValidatingWithCapacity:0 target:target selector:sel];
+}
+
+#if GTM_CONTAINERS_VALIDATE
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ if ((self = [super init])) {
+ embeddedContainer_ = [[NSMutableArray alloc] initWithCapacity:capacity];
+ target_ = [target retain];
+ selector_ = sel;
+ VerifySelectorOnTarget(selector_, target_);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [embeddedContainer_ release];
+ [target_ release];
+ [super dealloc];
+}
+
+- (NSUInteger)count {
+ return [embeddedContainer_ count];
+}
+
+- (id)objectAtIndex:(NSUInteger)idx {
+ return [embeddedContainer_ objectAtIndex:idx];
+}
+
+- (void)addObject:(id)anObject {
+ if (VerifyObjectWithTargetAndSelectorForContainer(anObject, target_,
+ selector_, self)) {
+ [embeddedContainer_ addObject:anObject];
+ }
+}
+
+- (void)insertObject:(id)anObject atIndex:(NSUInteger)idx {
+ if (VerifyObjectWithTargetAndSelectorForContainer(anObject, target_,
+ selector_, self)) {
+ [embeddedContainer_ insertObject:anObject atIndex:idx];
+ }
+}
+
+- (void)removeLastObject {
+ [embeddedContainer_ removeLastObject];
+}
+
+- (void)removeObjectAtIndex:(NSUInteger)idx {
+ [embeddedContainer_ removeObjectAtIndex:idx];
+}
+
+- (void)replaceObjectAtIndex:(NSUInteger)idx withObject:(id)anObject {
+ if (VerifyObjectWithTargetAndSelectorForContainer(anObject, target_,
+ selector_, self)) {
+ [embeddedContainer_ replaceObjectAtIndex:idx withObject:anObject];
+ }
+}
+
+- (NSString*)description {
+ return [NSString stringWithFormat:@"%@ - %@",
+ NSStringFromClass([self class]),
+ [embeddedContainer_ description]];
+}
+
+#else // GTM_CONTAINERS_VALIDATE
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ [self release];
+ return [[NSMutableArray alloc] initWithCapacity:capacity];
+}
+#endif // GTM_CONTAINERS_VALIDATE
+@end
+
+@implementation GTMValidatingDictionary
++ (id)validatingDictionaryWithTarget:(id)target selector:(SEL)sel {
+ return [self validatingDictionaryWithCapacity:0 target:target selector:sel];
+}
+
++ (id)validatingDictionaryWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ return [[[[self class] alloc] initValidatingWithCapacity:0
+ target:target
+ selector:sel] autorelease];
+}
+
+- (id)initValidatingWithTarget:(id)target selector:(SEL)sel {
+ return [self initValidatingWithCapacity:0 target:target selector:sel];
+}
+
+#if GTM_CONTAINERS_VALIDATE
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ if ((self = [super init])) {
+ embeddedContainer_ = [[NSMutableDictionary alloc] initWithCapacity:capacity];
+ target_ = [target retain];
+ selector_ = sel;
+ VerifySelectorOnTarget(selector_, target_);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [target_ release];
+ [embeddedContainer_ release];
+ [super dealloc];
+}
+
+- (NSUInteger)count {
+ return [embeddedContainer_ count];
+}
+
+- (NSEnumerator *)keyEnumerator {
+ return [embeddedContainer_ keyEnumerator];
+}
+
+- (id)objectForKey:(id)aKey {
+ return [embeddedContainer_ objectForKey:aKey];
+}
+
+- (void)removeObjectForKey:(id)aKey {
+ [embeddedContainer_ removeObjectForKey:aKey];
+}
+
+- (void)setObject:(id)anObject forKey:(id)aKey {
+ if (VerifyObjectWithTargetAndSelectorForContainer(anObject, target_,
+ selector_, self)) {
+ [embeddedContainer_ setObject:anObject forKey:aKey];
+ }
+}
+
+- (NSString*)description {
+ return [NSString stringWithFormat:@"%@ - %@",
+ NSStringFromClass([self class]),
+ [embeddedContainer_ description]];
+}
+
+#else // GTM_CONTAINERS_VALIDATE
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ [self release];
+ return [[NSMutableDictionary alloc] initWithCapacity:capacity];
+
+}
+#endif // GTM_CONTAINERS_VALIDATE
+@end
+
+@implementation GTMValidatingSet
++ (id)validatingSetWithTarget:(id)target selector:(SEL)sel {
+ return [self validatingSetWithCapacity:0 target:target selector:sel];
+}
+
++ (id)validatingSetWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ return [[[[self class] alloc] initValidatingWithCapacity:0
+ target:target
+ selector:sel] autorelease];
+}
+- (id)initValidatingWithTarget:(id)target selector:(SEL)sel {
+ return [self initValidatingWithCapacity:0 target:target selector:sel];
+}
+
+#if GTM_CONTAINERS_VALIDATE
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ if ((self = [super init])) {
+ embeddedContainer_ = [[NSMutableSet alloc] initWithCapacity:capacity];
+ target_ = [target retain];
+ selector_ = sel;
+ VerifySelectorOnTarget(selector_, target_);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [target_ release];
+ [embeddedContainer_ release];
+ [super dealloc];
+}
+
+- (NSUInteger)count {
+ return [embeddedContainer_ count];
+}
+
+- (id)member:(id)object {
+ return [embeddedContainer_ member:object];
+}
+
+- (NSEnumerator *)objectEnumerator {
+ return [embeddedContainer_ objectEnumerator];
+}
+
+- (void)addObject:(id)object {
+ if (object && VerifyObjectWithTargetAndSelectorForContainer(object,
+ target_,
+ selector_,
+ self)) {
+ [embeddedContainer_ addObject:object];
+ }
+}
+
+- (void)removeObject:(id)object {
+ [embeddedContainer_ removeObject:object];
+}
+
+- (NSString*)description {
+ return [NSString stringWithFormat:@"%@ - %@",
+ NSStringFromClass([self class]),
+ [embeddedContainer_ description]];
+}
+
+#else // GTM_CONTAINERS_VALIDATE
+- (id)initValidatingWithCapacity:(NSUInteger)capacity
+ target:(id)target
+ selector:(SEL)sel {
+ [self release];
+ return [[NSMutableSet alloc] initWithCapacity:capacity];
+}
+#endif // GTM_CONTAINERS_VALIDATE
+@end
+
+#pragma mark -
+#pragma mark Simple Common Validators
+@implementation GTMKindOfClassValidator
++ (id)validateAgainstClass:(Class)cls {
+ return [[[[self class] alloc] initWithClass:cls] autorelease];
+}
+
+- (id)initWithClass:(Class)cls {
+#if GTM_CONTAINERS_VALIDATE
+ if ((self = [super init])) {
+ if (!cls) {
+ _GTMDevLog(@"nil class");
+ [self release];
+ return nil;
+ }
+ cls_ = cls;
+ }
+ return self;
+#else // GTM_CONTAINERS_VALIDATE
+ [self release];
+ return nil;
+#endif // GTM_CONTAINERS_VALIDATE
+}
+
+- (BOOL)validateObject:(id)object forContainer:(id)container {
+ return [object isKindOfClass:cls_];
+}
+@end
+
+@implementation GTMMemberOfClassValidator
++ (id)validateAgainstClass:(Class)cls {
+ return [[[[self class] alloc] initWithClass:cls] autorelease];
+}
+
+- (id)initWithClass:(Class)cls {
+#if GTM_CONTAINERS_VALIDATE
+ if ((self = [super init])) {
+ if (!cls) {
+ _GTMDevLog(@"nil class");
+ [self release];
+ return nil;
+ }
+ cls_ = cls;
+ }
+ return self;
+#else // GTM_CONTAINERS_VALIDATE
+ [self release];
+ return nil;
+#endif // GTM_CONTAINERS_VALIDATE
+}
+
+- (BOOL)validateObject:(id)object forContainer:(id)container {
+ return [object isMemberOfClass:cls_];
+}
+@end
+
+@implementation GTMConformsToProtocolValidator
++ (id)validateAgainstProtocol:(Protocol*)prot {
+ return [[[[self class] alloc] initWithProtocol:prot] autorelease];
+}
+
+- (id)initWithProtocol:(Protocol*)prot {
+#if GTM_CONTAINERS_VALIDATE
+ if ((self = [super init])) {
+ if (!prot) {
+ _GTMDevLog(@"nil protocol");
+ [self release];
+ return nil;
+ }
+ prot_ = prot;
+ }
+ return self;
+#else // GTM_CONTAINERS_VALIDATE
+ [self release];
+ return nil;
+#endif // GTM_CONTAINERS_VALIDATE
+}
+
+- (BOOL)validateObject:(id)object forContainer:(id)container {
+ return [object conformsToProtocol:prot_];
+}
+@end
+
+@implementation GTMRespondsToSelectorValidator
++ (id)validateAgainstSelector:(SEL)sel {
+ return [[[[self class] alloc] initWithSelector:sel] autorelease];
+}
+
+- (id)initWithSelector:(SEL)sel {
+#if GTM_CONTAINERS_VALIDATE
+ if ((self = [super init])) {
+ if (!sel) {
+ _GTMDevLog(@"nil selector");
+ [self release];
+ return nil;
+ }
+ sel_ = sel;
+ }
+ return self;
+#else // GTM_CONTAINERS_VALIDATE
+ [self release];
+ return nil;
+#endif // GTM_CONTAINERS_VALIDATE
+}
+
+- (BOOL)validateObject:(id)object forContainer:(id)container {
+ return [object respondsToSelector:sel_];
+}
+@end
diff --git a/Foundation/GTMValidatingContainersTest.m b/Foundation/GTMValidatingContainersTest.m
new file mode 100644
index 0000000..a819787
--- /dev/null
+++ b/Foundation/GTMValidatingContainersTest.m
@@ -0,0 +1,367 @@
+//
+// GTMValidatingContainersTest.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 "GTMValidatingContainers.h"
+#import "GTMSenTestCase.h"
+#import "GTMUnitTestDevLog.h"
+
+#pragma mark Test Support Declarations
+@protocol GTMVCTestProtocol
+@end
+
+@interface GTMVCTestClass : NSObject
++ (id)instance;
+@end
+
+@interface GTMVCTestSubClass : GTMVCTestClass <GTMVCTestProtocol>
+- (void)foo;
+@end
+
+@interface GTMVCValidatorTests : GTMTestCase {
+ GTMVCTestClass *testClass_;
+ GTMVCTestSubClass *testSubClass_;
+}
+@end
+
+@interface GTMVCContainerTests : GTMVCValidatorTests {
+ GTMConformsToProtocolValidator *validator_;
+ SEL selector_;
+}
+@end
+
+@interface GTMVCArrayTests : GTMVCContainerTests
+@end
+
+@interface GTMVCDictionaryTests : GTMVCContainerTests
+@end
+
+@interface GTMVCSetTests : GTMVCContainerTests
+@end
+
+@interface GTMValidateContainerTests : GTMTestCase
+@end
+
+#pragma mark -
+#pragma mark Test Support Definitions
+
+@implementation GTMVCTestClass
++ (id)instance {
+ return [[[[self class] alloc] init] autorelease];
+}
+
+- (NSString*)description {
+ return NSStringFromClass([self class]);
+}
+@end
+
+@implementation GTMVCTestSubClass
+- (void)foo {
+}
+@end
+
+@implementation GTMVCContainerTests
+- (void)setUp {
+ [super setUp];
+ Protocol *prot = @protocol(GTMVCTestProtocol);
+ validator_ = [[GTMConformsToProtocolValidator alloc] initWithProtocol:prot];
+ selector_ = @selector(validateObject:forContainer:);
+}
+
+- (void)tearDown {
+ [validator_ release];
+ [super tearDown];
+}
+@end
+
+@implementation GTMVCValidatorTests
+- (void)setUp {
+ [super setUp];
+ testClass_ = [[GTMVCTestClass alloc] init];
+ testSubClass_ = [[GTMVCTestSubClass alloc] init];
+}
+
+- (void)tearDown {
+ [testClass_ release];
+ [testSubClass_ release];
+ [super tearDown];
+}
+
+- (void)testKindOfClassValidator {
+#if GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ [GTMUnitTestDevLog expectString:@"nil class"];
+ GTMKindOfClassValidator *validator;
+ validator = [GTMKindOfClassValidator validateAgainstClass:nil];
+ STAssertNil(validator, @"should be nil");
+
+ Class cls = [GTMVCTestClass class];
+ validator = [GTMKindOfClassValidator validateAgainstClass:cls];
+ STAssertNotNil(validator, @"should be valid");
+
+ BOOL isGood = [validator validateObject:testClass_ forContainer:nil];
+ STAssertTrue(isGood, @"should be validated");
+
+ isGood = [validator validateObject:testSubClass_ forContainer:nil];
+ STAssertTrue(isGood, @"should be validated");
+
+ isGood = [validator validateObject:[NSNumber numberWithInt:0]
+ forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+#else // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ GTMKindOfClassValidator *validator;
+ validator = [GTMKindOfClassValidator validateAgainstClass:nil];
+ STAssertNil(validator, @"should be nil");
+
+ Class cls = [GTMVCTestClass class];
+ validator = [GTMKindOfClassValidator validateAgainstClass:cls];
+ STAssertNil(validator, @"should be nil");
+#endif // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+}
+
+- (void)testMemberOfClassValidator {
+#if GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ [GTMUnitTestDevLog expectString:@"nil class"];
+ GTMMemberOfClassValidator *validator;
+ validator = [GTMMemberOfClassValidator validateAgainstClass:nil];
+ STAssertNil(validator, @"should be nil");
+
+ Class cls = [GTMVCTestClass class];
+ validator = [GTMMemberOfClassValidator validateAgainstClass:cls];
+ STAssertNotNil(validator, @"should be valid");
+
+ BOOL isGood = [validator validateObject:testClass_ forContainer:nil];
+ STAssertTrue(isGood, @"should be validated");
+
+ isGood = [validator validateObject:testSubClass_ forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+
+ isGood = [validator validateObject:nil forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+
+ isGood = [validator validateObject:[NSNumber numberWithInt:0]
+ forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+#else // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ GTMMemberOfClassValidator *validator;
+ validator = [GTMMemberOfClassValidator validateAgainstClass:nil];
+ STAssertNil(validator, @"should be nil");
+
+ Class cls = [GTMVCTestClass class];
+ validator = [GTMMemberOfClassValidator validateAgainstClass:cls];
+ STAssertNil(validator, @"should be nil");
+#endif // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+}
+
+- (void)testConformsToProtocolValidator {
+#if GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ [GTMUnitTestDevLog expectString:@"nil protocol"];
+ GTMConformsToProtocolValidator *validator;
+ validator = [GTMConformsToProtocolValidator validateAgainstProtocol:nil];
+ STAssertNil(validator, @"should be nil");
+
+ Protocol *prot = @protocol(GTMVCTestProtocol);
+ validator = [GTMConformsToProtocolValidator validateAgainstProtocol:prot];
+ STAssertNotNil(validator, @"should be valid");
+
+ BOOL isGood = [validator validateObject:testClass_ forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+
+ isGood = [validator validateObject:testSubClass_ forContainer:nil];
+ STAssertTrue(isGood, @"should succeed");
+
+ isGood = [validator validateObject:nil forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+#else // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ GTMConformsToProtocolValidator *validator;
+ validator = [GTMConformsToProtocolValidator validateAgainstProtocol:nil];
+ STAssertNil(validator, @"should be nil");
+
+ Protocol *prot = @protocol(GTMVCTestProtocol);
+ validator = [GTMConformsToProtocolValidator validateAgainstProtocol:prot];
+ STAssertNil(validator, @"should be nil");
+#endif // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+}
+
+- (void)testRespondsToSelectorValidator {
+#if GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ [GTMUnitTestDevLog expectString:@"nil selector"];
+ GTMRespondsToSelectorValidator *validator;
+ validator = [GTMRespondsToSelectorValidator validateAgainstSelector:nil];
+ STAssertNil(validator, @"should be nil");
+
+ SEL sel = @selector(foo);
+ validator = [GTMRespondsToSelectorValidator validateAgainstSelector:sel];
+ STAssertNotNil(validator, @"should be valid");
+
+ BOOL isGood = [validator validateObject:testClass_ forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+
+ isGood = [validator validateObject:testSubClass_ forContainer:nil];
+ STAssertTrue(isGood, @"should succeed");
+
+ isGood = [validator validateObject:nil forContainer:nil];
+ STAssertFalse(isGood, @"should fail");
+#else // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+ GTMRespondsToSelectorValidator *validator;
+ validator = [GTMRespondsToSelectorValidator validateAgainstSelector:nil];
+ STAssertNil(validator, @"should be nil");
+
+ SEL sel = @selector(foo);
+ validator = [GTMRespondsToSelectorValidator validateAgainstSelector:sel];
+ STAssertNil(validator, @"should be nil");
+#endif // GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
+}
+
+
+@end
+
+@implementation GTMVCArrayTests
+- (void)testContainer {
+ GTMValidatingArray *array;
+ array = [GTMValidatingArray validatingArrayWithTarget:validator_
+ selector:selector_];
+ STAssertNotNil(array, @"should be valid");
+
+ array = [[[GTMValidatingArray alloc] initValidatingWithTarget:validator_
+ selector:selector_] autorelease];
+ STAssertNotNil(array, @"should be valid");
+
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for GTMValidatingArray .*"];
+ [array addObject:testSubClass_];
+ [array addObject:testClass_];
+ STAssertEquals([array objectAtIndex:0], testSubClass_, @"");
+
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for GTMValidatingArray .*"];
+ [array insertObject:testClass_ atIndex:0];
+ [array insertObject:testSubClass_ atIndex:0];
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for GTMValidatingArray .*"];
+ [array replaceObjectAtIndex:0 withObject:testClass_];
+ [array replaceObjectAtIndex:0 withObject:testSubClass_];
+ [array removeLastObject];
+ [array removeObjectAtIndex:0];
+ NSUInteger expectedCount = 0U;
+#if !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+ // If we're not validating, we don't expect any logs
+ [GTMUnitTestDevLog resetExpectedLogs];
+ expectedCount = 2U;
+#endif // !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+ STAssertEquals([array count], expectedCount, @"should have no objects left");
+
+}
+@end
+
+@implementation GTMVCDictionaryTests
+- (void)testContainer {
+ GTMValidatingDictionary *dictionary;
+ dictionary = [GTMValidatingDictionary validatingDictionaryWithTarget:validator_
+ selector:selector_];
+ STAssertNotNil(dictionary, @"should be valid");
+
+ dictionary = [[[GTMValidatingDictionary alloc] initValidatingWithTarget:validator_
+ selector:selector_] autorelease];
+ STAssertNotNil(dictionary, @"should be valid");
+
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for GTMValidatingDictionary .*"];
+ [dictionary setObject:testClass_ forKey:@"Key1"];
+ [dictionary setObject:testSubClass_ forKey:@"Key2"];
+ STAssertEquals([dictionary objectForKey:@"Key2"], testSubClass_, @"");
+ STAssertNotNil([dictionary keyEnumerator], @"");
+
+ [dictionary removeObjectForKey:@"Key2"];
+ [dictionary removeObjectForKey:@"Key1"];
+ STAssertEquals([dictionary count], (NSUInteger)0, @"should have no objects left");
+#if !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+ // If we're not validating, we don't expect any logs
+ [GTMUnitTestDevLog resetExpectedLogs];
+#endif // !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+}
+@end
+
+@implementation GTMVCSetTests
+- (void)testContainer {
+ GTMValidatingSet *set;
+ set = [GTMValidatingSet validatingSetWithTarget:validator_
+ selector:selector_];
+ STAssertNotNil(set, @"should be valid");
+
+ set = [[[GTMValidatingSet alloc] initValidatingWithTarget:validator_
+ selector:selector_] autorelease];
+ STAssertNotNil(set, @"should be valid");
+
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for GTMValidatingSet .*"];
+ [set addObject:testClass_];
+ [set addObject:testSubClass_];
+ STAssertEqualObjects([set member:testSubClass_], testSubClass_, @"");
+ STAssertNotNil([set objectEnumerator], @"");
+
+ [set removeObject:testClass_];
+ [set removeObject:testSubClass_];
+#if !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+ // If we're not validating, we don't expect any logs
+ [GTMUnitTestDevLog resetExpectedLogs];
+#endif // !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+ STAssertEquals([set count], (NSUInteger)0, @"should have no objects left");
+}
+@end
+
+@implementation GTMValidateContainerTests
+- (void)testValidatingContainers {
+ NSDictionary *homogenousDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [GTMVCTestSubClass instance], @"key1",
+ [GTMVCTestSubClass instance], @"key2",
+ nil];
+ NSDictionary *heterogenousDict = [NSDictionary dictionaryWithObjectsAndKeys:
+ [GTMVCTestClass instance], @"key1",
+ [GTMVCTestSubClass instance], @"key2",
+ nil];
+
+ // Test bad container
+ [GTMUnitTestDevLog expectPattern:@"container does not respont to -objectEnumerator: .*"];
+ _GTMValidateContainerContainsKindOfClass([NSString string],
+ [GTMVCTestSubClass class]);
+
+ _GTMValidateContainerContainsKindOfClass(homogenousDict,
+ [GTMVCTestSubClass class]);
+ _GTMValidateContainerContainsKindOfClass(heterogenousDict,
+ [GTMVCTestClass class]);
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for .*"];
+ _GTMValidateContainerContainsKindOfClass(heterogenousDict,
+ [GTMVCTestSubClass class]);
+
+ _GTMValidateContainerContainsMemberOfClass(homogenousDict,
+ [GTMVCTestSubClass class]);
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestSubClass failed container verification for .*"];
+ _GTMValidateContainerContainsMemberOfClass(heterogenousDict,
+ [GTMVCTestClass class]);
+
+ _GTMValidateContainerConformsToProtocol(homogenousDict,
+ @protocol(GTMVCTestProtocol));
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for .*"];
+ _GTMValidateContainerConformsToProtocol(heterogenousDict,
+ @protocol(GTMVCTestProtocol));
+
+ _GTMValidateContainerItemsRespondToSelector(homogenousDict,
+ @selector(foo));
+ [GTMUnitTestDevLog expectPattern:@"GTMVCTestClass failed container verification for .*"];
+ _GTMValidateContainerItemsRespondToSelector(heterogenousDict,
+ @selector(foo));
+#if !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+ // If we're not validating, we don't expect any logs
+ [GTMUnitTestDevLog resetExpectedLogs];
+#endif // !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
+}
+@end