diff options
-rw-r--r-- | DebugUtils/GTMMethodCheck.m | 195 | ||||
-rw-r--r-- | Foundation/GTMLogger.h | 1 | ||||
-rw-r--r-- | Foundation/GTMLogger.m | 45 | ||||
-rw-r--r-- | UnitTesting/GTMUnitTestDevLog.h | 33 | ||||
-rw-r--r-- | UnitTesting/GTMUnitTestDevLog.m | 9 |
5 files changed, 120 insertions, 163 deletions
diff --git a/DebugUtils/GTMMethodCheck.m b/DebugUtils/GTMMethodCheck.m index ac43a49..0ff795e 100644 --- a/DebugUtils/GTMMethodCheck.m +++ b/DebugUtils/GTMMethodCheck.m @@ -23,65 +23,6 @@ #import "GTMObjC2Runtime.h" #import <dlfcn.h> -// Checks to see if the cls passed in (or one of its superclasses) conforms -// to NSObject protocol. Inheriting from NSObject is the easiest way to do this -// but not all classes (i.e. NSProxy) inherit from NSObject. Also, some classes -// inherit from Object instead of NSObject which is fine, and we'll count as -// conforming to NSObject for our needs. -static BOOL ConformsToNSObjectProtocol(Class cls) { - // If we get nil, obviously doesn't conform. - if (!cls) return NO; - const char *className = class_getName(cls); - if (!className) return NO; - - // We're going to assume that all Apple classes will work - // (and aren't being checked) - // Note to apple: why doesn't obj-c have real namespaces instead of two - // letter hacks? If you name your own classes starting with NS this won't - // work for you. - // Some classes (like _NSZombie) start with _NS. - // On Leopard we have to look for CFObject as well. - // On iPhone we check Object as well - if ((strncmp(className, "NS", 2) == 0) - || (strncmp(className, "_NS", 3) == 0) - || (strncmp(className, "__NS", 4) == 0) - || (strcmp(className, "CFObject") == 0) - || (strcmp(className, "_CNZombie_") == 0) - || (strcmp(className, "FigIrisAutoTrimmerMotionSampleExport") == 0) - || (strcmp(className, "__IncompleteProtocol") == 0) - || (strcmp(className, "__ARCLite__") == 0) - || (strcmp(className, "WebMIMETypeRegistry") == 0) - || (strcmp(className, "Object") == 0) -#if GTM_IPHONE_SDK - || (strcmp(className, "UIKeyboardCandidateUtilities") == 0) - || (strcmp(className, "JSExport") == 0) -#endif - ) { - return YES; - } - - // iPhone and Mac OS X 10.8 with Obj-C 2 SDKs do not define the |Object| - // class, so we instead test for the |NSObject| class. -#if GTM_IPHONE_SDK || \ - (__OBJC2__ && defined(MAC_OS_X_VERSION_10_8) && \ - MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8) - // Iterate through all the protocols |cls| supports looking for NSObject. - if (cls == [NSObject class] - || class_conformsToProtocol(cls, @protocol(NSObject))) { - return YES; - } -#else - // Iterate through all the protocols |cls| supports looking for NSObject. - if (cls == [Object class] - || class_conformsToProtocol(cls, @protocol(NSObject))) { - return YES; - } -#endif - - // Recursively check the superclasses. - return ConformsToNSObjectProtocol(class_getSuperclass(cls)); -} - void GTMMethodCheckMethodChecker(void) { // Run through all the classes looking for class methods that are // prefixed with xxGMMethodCheckMethod. If it finds one, it calls it. @@ -95,84 +36,78 @@ void GTMMethodCheckMethodChecker(void) { // the copy in our local image. This will give us access to our local image // in the methodCheckerInfo structure. Dl_info methodCheckerInfo; - if (dladdr(GTMMethodCheckMethodChecker, - &methodCheckerInfo)) { - int numClasses = 0; - int newNumClasses = objc_getClassList(NULL, 0); - int i; - Class *classes = NULL; - while (numClasses < newNumClasses) { - numClasses = newNumClasses; - classes = (Class *)realloc(classes, sizeof(Class) * numClasses); - _GTMDevAssert(classes, @"Unable to allocate memory for classes"); - newNumClasses = objc_getClassList(classes, numClasses); + int foundMethodChecker = dladdr(GTMMethodCheckMethodChecker, + &methodCheckerInfo); + _GTMDevAssert(foundMethodChecker, @"GTMMethodCheckMethodChecker: Unable to " + @"get dladdr for GTMMethodCheckMethodChecker"); + int numClasses = 0; + int newNumClasses = objc_getClassList(NULL, 0); + int i; + Class *classes = NULL; + while (numClasses < newNumClasses) { + numClasses = newNumClasses; + classes = (Class *)realloc(classes, sizeof(Class) * numClasses); + _GTMDevAssert(classes, @"Unable to allocate memory for classes"); + newNumClasses = objc_getClassList(classes, numClasses); + } + for (i = 0; i < numClasses && classes; ++i) { + Class cls = classes[i]; + const char *className = class_getName(cls); + _GTMDevAssert(className, @"GTMMethodCheckMethodChecker: Unable to " + @"get className for class %d", i); + // Since we are looking for a class method (+xxGMMethodCheckMethod...) + // we need to query the isa pointer to see what methods it support, but + // send the method (if it's supported) to the class itself. + if (strcmp(className, "__ARCLite__") == 0) { + // __ARCLite__ is "magic" and does not have a metaClass. + continue; + } + Class metaClass = objc_getMetaClass(className); + _GTMDevAssert(metaClass, @"GTMMethodCheckMethodChecker: Unable to " + @"get metaClass for %s", className); + unsigned int count; + Method *methods = class_copyMethodList(metaClass, &count); + if (count == 0) { + continue; } - for (i = 0; i < numClasses && classes; ++i) { - Class cls = classes[i]; + _GTMDevAssert(methods, @"GTMMethodCheckMethodChecker: Unable to " + @"get methods for class %s", className); - // Since we are directly calling objc_msgSend, we need to conform to - // @protocol(NSObject), or else we will tumble into a _objc_msgForward - // recursive loop when we try and call a function by name. - if (!ConformsToNSObjectProtocol(cls)) { - // COV_NF_START - _GTMDevLog(@"GTMMethodCheckMethodChecker: Class %s does not conform " - "to @protocol(NSObject), so won't be checked", - class_getName(cls)); - continue; - // COV_NF_END - } - // Since we are looking for a class method (+xxGMMethodCheckMethod...) - // we need to query the isa pointer to see what methods it support, but - // send the method (if it's supported) to the class itself. - unsigned int count; - Class metaClass = objc_getMetaClass(class_getName(cls)); - Method *methods = class_copyMethodList(metaClass, &count); - unsigned int j; - for (j = 0; j < count; ++j) { - SEL selector = method_getName(methods[j]); - const char *name = sel_getName(selector); - if (strstr(name, "xxGTMMethodCheckMethod") == name) { - Dl_info methodInfo; - if (!dladdr(method_getImplementation(methods[j]), - &methodInfo)) { - // COV_NF_START - // Don't know how to force this case in a unittest - // Certainly hope we never see it. - _GTMDevLog(@"GTMMethodCheckMethodChecker: Unable to get dladdr " - "info for %s while introspecting +[%s %s]]", name, - class_getName(cls), name); - continue; - // COV_NF_END - } + unsigned int j; + for (j = 0; j < count; ++j) { + SEL selector = method_getName(methods[j]); + _GTMDevAssert(selector, @"GTMMethodCheckMethodChecker: Unable to " + @"get selector for method %d of %s", j, className); + const char *name = sel_getName(selector); + _GTMDevAssert(selector, @"GTMMethodCheckMethodChecker: Unable to " + @"get name for method %d of %s", j, className); + if (strstr(name, "xxGTMMethodCheckMethod") == name) { + Dl_info methodInfo; + IMP imp = method_getImplementation(methods[j]); + _GTMDevAssert(selector, @"GTMMethodCheckMethodChecker: Unable to " + @"get IMP for method %s of %s", name, className); + int foundMethod = dladdr(imp, &methodInfo); + _GTMDevAssert(foundMethod, @"GTMMethodCheckMethodChecker: Unable to " + @"get dladdr for method %s of %s", name, className); - // Check to make sure that the method we are checking comes from the - // same image that we are in. We compare the address of the local - // image (stored in |methodCheckerInfo| as noted above) with the - // address of the image which implements the method we want to - // check. If they match we continue. This does two things: - // a) minimizes the amount of calls we make to the xxxGTMMethodCheck - // methods. They should only be called once. - // b) prevents initializers for various classes being called too - // early - if (methodCheckerInfo.dli_fbase == methodInfo.dli_fbase) { - typedef void (*GTMMethodCheckMethod)(Class, SEL); - GTMMethodCheckMethod func = (GTMMethodCheckMethod)objc_msgSend; - func(cls, selector); - } + // Check to make sure that the method we are checking comes from the + // same image that we are in. We compare the address of the local + // image (stored in |methodCheckerInfo| as noted above) with the + // address of the image which implements the method we want to + // check. If they match we continue. This does two things: + // a) minimizes the amount of calls we make to the xxxGTMMethodCheck + // methods. They should only be called once. + // b) prevents initializers for various classes being called too + // early + if (methodCheckerInfo.dli_fbase == methodInfo.dli_fbase) { + void (*func)(id, SEL) = (void *)imp; + func(cls, selector); } } - free(methods); } - free(classes); - } else { - // COV_NF_START - // This means that we didn't find the GTMMethodCheckMethodChecker method - // Don't know how to force this case in a unittest. - // Certainly hope we never see it. - _GTMDevLog(@"GTMMethodCheckMethodChecker: Unable to get dladdr info " - "for GTMMethodCheckMethodChecker"); - // COV_NF_END + free(methods); } + free(classes); #if !defined(__has_feature) || !__has_feature(objc_arc) [pool drain]; #else diff --git a/Foundation/GTMLogger.h b/Foundation/GTMLogger.h index a776922..16f0eaf 100644 --- a/Foundation/GTMLogger.h +++ b/Foundation/GTMLogger.h @@ -457,6 +457,7 @@ typedef enum { @interface GTMLogLevelFilter : NSObject <GTMLogFilter> { @private BOOL verboseLoggingEnabled_; + NSUserDefaults *userDefaults_; } @end // GTMLogLevelFilter diff --git a/Foundation/GTMLogger.m b/Foundation/GTMLogger.m index 7c2a543..3ace20f 100644 --- a/Foundation/GTMLogger.m +++ b/Foundation/GTMLogger.m @@ -97,7 +97,7 @@ static GTMLogger *gSharedLogger = nil; // Don't trust NSFileHandle not to throw @try { - GTMLogBasicFormatter *formatter = [[[GTMLogBasicFormatter alloc] init] + GTMLogBasicFormatter *formatter = [[[GTMLogBasicFormatter alloc] init] autorelease]; GTMLogger *stdoutLogger = [self loggerWithWriter:[NSFileHandle fileHandleWithStandardOutput] @@ -475,12 +475,13 @@ static GTMLogger *gSharedLogger = nil; @end // GTMLogStandardFormatter +static NSString *const kVerboseLoggingKey = @"GTMVerboseLogging"; + // Check the environment and the user preferences for the GTMVerboseLogging key // to see if verbose logging has been enabled. The environment variable will // override the defaults setting, so check the environment first. // COV_NF_START -static BOOL IsVerboseLoggingEnabled(void) { - static NSString *const kVerboseLoggingKey = @"GTMVerboseLogging"; +static BOOL IsVerboseLoggingEnabled(NSUserDefaults *userDefaults) { NSString *value = [[[NSProcessInfo processInfo] environment] objectForKey:kVerboseLoggingKey]; if (value) { @@ -495,7 +496,7 @@ static BOOL IsVerboseLoggingEnabled(void) { return NO; } } - return [[NSUserDefaults standardUserDefaults] boolForKey:kVerboseLoggingKey]; + return [userDefaults boolForKey:kVerboseLoggingKey]; } // COV_NF_END @@ -504,21 +505,28 @@ static BOOL IsVerboseLoggingEnabled(void) { - (id)init { self = [super init]; if (self) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(defaultsChanged:) - name:NSUserDefaultsDidChangeNotification - object:nil]; - - verboseLoggingEnabled_ = IsVerboseLoggingEnabled(); + // Keep a reference to standardUserDefaults, avoiding a crash if client code calls + // "NSUserDefaults resetStandardUserDefaults" which releases it from memory. We are still + // notified of changes through our instance. Note: resetStandardUserDefaults does not actually + // clear settings: + // https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/index.html#//apple_ref/occ/clm/NSUserDefaults/resetStandardUserDefaults + // and so should only be called in test code if necessary. + userDefaults_ = [[NSUserDefaults standardUserDefaults] retain]; + [userDefaults_ addObserver:self + forKeyPath:kVerboseLoggingKey + options:NSKeyValueObservingOptionNew + context:nil]; + + verboseLoggingEnabled_ = IsVerboseLoggingEnabled(userDefaults_); } return self; } - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self - name:NSUserDefaultsDidChangeNotification - object:nil]; + [userDefaults_ removeObserver:self forKeyPath:kVerboseLoggingKey]; + [userDefaults_ release]; + [super dealloc]; } @@ -552,8 +560,14 @@ static BOOL IsVerboseLoggingEnabled(void) { return allow; } -- (void)defaultsChanged:(NSNotification *)note { - verboseLoggingEnabled_ = IsVerboseLoggingEnabled(); +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + if([keyPath isEqual:kVerboseLoggingKey]) { + verboseLoggingEnabled_ = IsVerboseLoggingEnabled(userDefaults_); + } } @end // GTMLogLevelFilter @@ -632,4 +646,3 @@ static BOOL IsVerboseLoggingEnabled(void) { // See comment at top of file. #pragma GCC diagnostic error "-Wmissing-format-attribute" #endif // !__clang__ - diff --git a/UnitTesting/GTMUnitTestDevLog.h b/UnitTesting/GTMUnitTestDevLog.h index f80743a..2be231e 100644 --- a/UnitTesting/GTMUnitTestDevLog.h +++ b/UnitTesting/GTMUnitTestDevLog.h @@ -1,14 +1,14 @@ // // GTMUnitTestDevLog.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 @@ -25,7 +25,7 @@ // to find unexpected logs in your output when running unittests. // In your unittests you tell GTMUnitTestDevLog what messages you expect your // test to spit out, and it will cause any that don't match to appear as errors -// in your unittest run output. You can match on exact strings or standard +// in your unittest run output. You can match on exact strings or standard // regexps. // Set GTM_SHOW_UNITTEST_DEVLOGS in the environment to show the logs that that // are expected and encountered. Otherwise they aren't display to keep the @@ -33,8 +33,8 @@ @interface GTMUnitTestDevLog : NSObject // Log a message -+ (void)log:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2); -+ (void)log:(NSString*)format args:(va_list)args NS_FORMAT_FUNCTION(1,0); ++ (void)log:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2); ++ (void)log:(NSString*)format args:(va_list)args NS_FORMAT_FUNCTION(1, 0); // Turn tracking on/off + (void)enableTracking; @@ -43,24 +43,25 @@ // Note that you are expecting a string that has an exact match. No need to // escape any pattern characters. -+ (void)expectString:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2); ++ (void)expectString:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2); // Note that you are expecting a pattern. Pattern characters that you want // exact matches on must be escaped. See [GTMRegex escapedPatternForString]. // Patterns match across newlines (kGTMRegexOptionSupressNewlineSupport) making // it easier to match output from the descriptions of NS collection types such // as NSArray and NSDictionary. -+ (void)expectPattern:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2); ++ (void)expectPattern:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2); // Note that you are expecting exactly 'n' strings -+ (void)expect:(NSUInteger)n - casesOfString:(NSString *)format, ... NS_FORMAT_FUNCTION(2,3); ++ (void)expect:(NSUInteger)n + casesOfString:(NSString *)format, ... NS_FORMAT_FUNCTION(2, 3); // Note that you are expecting exactly 'n' patterns -+ (void)expect:(NSUInteger)n - casesOfPattern:(NSString*)format, ... NS_FORMAT_FUNCTION(2,3); -+ (void)expect:(NSUInteger)n - casesOfPattern:(NSString*)format args:(va_list)args NS_FORMAT_FUNCTION(2,0); ++ (void)expect:(NSUInteger)n + casesOfPattern:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); ++ (void)expect:(NSUInteger)n + casesOfPattern:(NSString*)format + args:(va_list)args NS_FORMAT_FUNCTION(2, 0); // Call when you want to verify that you have matched all the logs you expect // to match. If your unittests inherit from GTMTestcase (like they should) you @@ -77,3 +78,7 @@ // ie-the expect requests don't count in release builds. @interface GTMUnitTestDevLogDebug : GTMUnitTestDevLog @end + +// The name of the exception that is raised when GTMUnitTestDevLog +// finds a problem. +GTM_EXTERN NSString *const GTMLogFailureException; diff --git a/UnitTesting/GTMUnitTestDevLog.m b/UnitTesting/GTMUnitTestDevLog.m index fd90396..7a12bb0 100644 --- a/UnitTesting/GTMUnitTestDevLog.m +++ b/UnitTesting/GTMUnitTestDevLog.m @@ -61,6 +61,8 @@ static inline void GTMInstallDebugAssertOutputHandler(void) {}; static inline void GTMUninstallDebugAssertOutputHandler(void) {}; #endif // GTM_IPHONE_SDK +NSString *const GTMLogFailureException = @"GTMLogFailureException"; + @interface GTMUnttestDevLogAssertionHandler : NSAssertionHandler @end @@ -182,12 +184,13 @@ static BOOL gTrackingEnabled = NO; [patterns removeObjectAtIndex:0]; } if (logError) { + if (regex) { - [NSException raise:SenTestFailureException + [NSException raise:GTMLogFailureException format:@"Unexpected log: %@\nExpected: %@", logString, regex]; } else { - [NSException raise:SenTestFailureException + [NSException raise:GTMLogFailureException format:@"Unexpected log: %@", logString]; } } else { @@ -262,7 +265,7 @@ casesOfPattern:(NSString*)format if ([patterns count] > 0) { NSMutableArray *patternsCopy = [[patterns copy] autorelease]; [self resetExpectedLogs]; - [NSException raise:SenTestFailureException + [NSException raise:GTMLogFailureException format:@"Logs still expected %@", patternsCopy]; } } |