diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/common/mac/GTMDefines.h | 279 | ||||
-rw-r--r-- | src/common/mac/GTMLogger.h | 128 | ||||
-rw-r--r-- | src/common/mac/GTMLogger.m | 371 | ||||
-rw-r--r-- | src/common/mac/testing/GTMSenTestCase.h | 904 | ||||
-rw-r--r-- | src/common/mac/testing/GTMSenTestCase.m | 174 |
5 files changed, 1256 insertions, 600 deletions
diff --git a/src/common/mac/GTMDefines.h b/src/common/mac/GTMDefines.h index b88193cd..b970d69c 100644 --- a/src/common/mac/GTMDefines.h +++ b/src/common/mac/GTMDefines.h @@ -1,4 +1,4 @@ -// +// // GTMDefines.h // // Copyright 2008 Google Inc. @@ -6,21 +6,29 @@ // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. // - + // ============================================================================ #include <AvailabilityMacros.h> #include <TargetConditionals.h> +#ifdef __OBJC__ +#include <Foundation/NSObjCRuntime.h> +#endif // __OBJC__ + +#if TARGET_OS_IPHONE +#include <Availability.h> +#endif // TARGET_OS_IPHONE + // Not all MAC_OS_X_VERSION_10_X macros defined in past SDKs #ifndef MAC_OS_X_VERSION_10_5 #define MAC_OS_X_VERSION_10_5 1050 @@ -28,6 +36,29 @@ #ifndef MAC_OS_X_VERSION_10_6 #define MAC_OS_X_VERSION_10_6 1060 #endif +#ifndef MAC_OS_X_VERSION_10_7 + #define MAC_OS_X_VERSION_10_7 1070 +#endif + +// Not all __IPHONE_X macros defined in past SDKs +#ifndef __IPHONE_3_0 + #define __IPHONE_3_0 30000 +#endif +#ifndef __IPHONE_3_1 + #define __IPHONE_3_1 30100 +#endif +#ifndef __IPHONE_3_2 + #define __IPHONE_3_2 30200 +#endif +#ifndef __IPHONE_4_0 + #define __IPHONE_4_0 40000 +#endif +#ifndef __IPHONE_4_3 + #define __IPHONE_4_3 40300 +#endif +#ifndef __IPHONE_5_0 + #define __IPHONE_5_0 50000 +#endif // ---------------------------------------------------------------------------- // CPP symbols that can be overridden in a prefix to control how the toolbox @@ -35,7 +66,7 @@ // ---------------------------------------------------------------------------- -// By setting the GTM_CONTAINERS_VALIDATION_FAILED_LOG and +// 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. @@ -47,7 +78,7 @@ // a few different actual definitions, so we're based off of the foundation // one. #if !defined(GTM_INLINE) - #if defined (__GNUC__) && (__GNUC__ == 4) + #if (defined (__GNUC__) && (__GNUC__ == 4)) || defined (__clang__) #define GTM_INLINE static __inline__ __attribute__((always_inline)) #else #define GTM_INLINE static __inline__ @@ -59,8 +90,12 @@ #if !defined (GTM_EXTERN) #if defined __cplusplus #define GTM_EXTERN extern "C" + #define GTM_EXTERN_C_BEGIN extern "C" { + #define GTM_EXTERN_C_END } #else #define GTM_EXTERN extern + #define GTM_EXTERN_C_BEGIN + #define GTM_EXTERN_C_END #endif #endif @@ -70,6 +105,12 @@ #define GTM_EXPORT __attribute__((visibility("default"))) #endif +// Give ourselves a consistent way of declaring something as unused. This +// doesn't use __unused because that is only supported in gcc 4.2 and greater. +#if !defined (GTM_UNUSED) +#define GTM_UNUSED(x) ((void)(x)) +#endif + // _GTMDevLog & _GTMDevAssert // // _GTMDevLog & _GTMDevAssert are meant to be a very lightweight shell for @@ -82,12 +123,12 @@ // _GTMDevLog log some error/problem in debug builds // _GTMDevAssert assert if conditon isn't met w/in a method/function // in all builds. -// +// // To replace this system, just provide different macro definitions in your // prefix header. Remember, any implementation you provide *must* be thread // safe since this could be called by anything in what ever situtation it has // been placed in. -// +// // We only define the simple macros if nothing else has defined this. #ifndef _GTMDevLog @@ -100,11 +141,6 @@ #endif // _GTMDevLog -// Declared here so that it can easily be used for logging tracking if -// necessary. See GTMUnitTestDevLog.h for details. -@class NSString; -GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...); - #ifndef _GTMDevAssert // we directly invoke the NSAssert handler so we can pass on the varargs // (NSAssert doesn't have a macro we can use that takes varargs) @@ -145,28 +181,6 @@ GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...); typedef char _GTMCompileAssertSymbol(__LINE__, msg) [ ((test) ? 1 : -1) ] #endif // _GTMCompileAssert -// Macro to allow fast enumeration when building for 10.5 or later, and -// reliance on NSEnumerator for 10.4. Remember, NSDictionary w/ FastEnumeration -// does keys, so pick the right thing, nothing is done on the FastEnumeration -// side to be sure you're getting what you wanted. -#ifndef GTM_FOREACH_OBJECT - #if TARGET_OS_IPHONE || (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) - #define GTM_FOREACH_OBJECT(element, collection) \ - for (element in collection) - #define GTM_FOREACH_KEY(element, collection) \ - for (element in collection) - #else - #define GTM_FOREACH_OBJECT(element, collection) \ - for (NSEnumerator * _ ## element ## _enum = [collection objectEnumerator]; \ - (element = [_ ## element ## _enum nextObject]) != nil; ) - #define GTM_FOREACH_KEY(element, collection) \ - for (NSEnumerator * _ ## element ## _enum = [collection keyEnumerator]; \ - (element = [_ ## element ## _enum nextObject]) != nil; ) - #endif -#endif - -// ============================================================================ - // ---------------------------------------------------------------------------- // CPP symbols defined based on the project settings so the GTM code has // simple things to test against w/o scattering the knowledge of project @@ -183,11 +197,26 @@ GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...); #else #define GTM_IPHONE_DEVICE 1 #endif // TARGET_IPHONE_SIMULATOR + // By default, GTM has provided it's own unittesting support, define this + // to use the support provided by Xcode, especially for the Xcode4 support + // for unittesting. + #ifndef GTM_IPHONE_USE_SENTEST + #define GTM_IPHONE_USE_SENTEST 0 + #endif #else // For MacOS specific stuff #define GTM_MACOS_SDK 1 #endif +// Some of our own availability macros +#if GTM_MACOS_SDK +#define GTM_AVAILABLE_ONLY_ON_IPHONE UNAVAILABLE_ATTRIBUTE +#define GTM_AVAILABLE_ONLY_ON_MACOS +#else +#define GTM_AVAILABLE_ONLY_ON_IPHONE +#define GTM_AVAILABLE_ONLY_ON_MACOS UNAVAILABLE_ATTRIBUTE +#endif + // Provide a symbol to include/exclude extra code for GC support. (This mainly // just controls the inclusion of finalize methods). #ifndef GTM_SUPPORT_GC @@ -197,7 +226,7 @@ GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...); #else // We can't find a symbol to tell if GC is supported/required, so best we // do on Mac targets is include it if we're on 10.5 or later. - #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4 + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 #define GTM_SUPPORT_GC 0 #else #define GTM_SUPPORT_GC 1 @@ -207,7 +236,7 @@ GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...); // To simplify support for 64bit (and Leopard in general), we provide the type // defines for non Leopard SDKs -#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4 +#if !(MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) // NSInteger/NSUInteger and Max/Mins #ifndef NSINTEGER_DEFINED #if __LP64__ || NS_BUILD_32_LIKE_64 @@ -238,4 +267,178 @@ GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...); #endif /* !defined(__LP64__) || !__LP64__ */ #define CGFLOAT_DEFINED 1 #endif // CGFLOAT_DEFINED -#endif // MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4 +#endif // MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + +// Some support for advanced clang static analysis functionality +// See http://clang-analyzer.llvm.org/annotations.html +#ifndef __has_feature // Optional. + #define __has_feature(x) 0 // Compatibility with non-clang compilers. +#endif + +#ifndef NS_RETURNS_RETAINED + #if __has_feature(attribute_ns_returns_retained) + #define NS_RETURNS_RETAINED __attribute__((ns_returns_retained)) + #else + #define NS_RETURNS_RETAINED + #endif +#endif + +#ifndef NS_RETURNS_NOT_RETAINED + #if __has_feature(attribute_ns_returns_not_retained) + #define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained)) + #else + #define NS_RETURNS_NOT_RETAINED + #endif +#endif + +#ifndef CF_RETURNS_RETAINED + #if __has_feature(attribute_cf_returns_retained) + #define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) + #else + #define CF_RETURNS_RETAINED + #endif +#endif + +#ifndef CF_RETURNS_NOT_RETAINED + #if __has_feature(attribute_cf_returns_not_retained) + #define CF_RETURNS_NOT_RETAINED __attribute__((cf_returns_not_retained)) + #else + #define CF_RETURNS_NOT_RETAINED + #endif +#endif + +#ifndef NS_CONSUMED + #if __has_feature(attribute_ns_consumed) + #define NS_CONSUMED __attribute__((ns_consumed)) + #else + #define NS_CONSUMED + #endif +#endif + +#ifndef CF_CONSUMED + #if __has_feature(attribute_cf_consumed) + #define CF_CONSUMED __attribute__((cf_consumed)) + #else + #define CF_CONSUMED + #endif +#endif + +#ifndef NS_CONSUMES_SELF + #if __has_feature(attribute_ns_consumes_self) + #define NS_CONSUMES_SELF __attribute__((ns_consumes_self)) + #else + #define NS_CONSUMES_SELF + #endif +#endif + +// Defined on 10.6 and above. +#ifndef NS_FORMAT_ARGUMENT + #define NS_FORMAT_ARGUMENT(A) +#endif + +// Defined on 10.6 and above. +#ifndef NS_FORMAT_FUNCTION + #define NS_FORMAT_FUNCTION(F,A) +#endif + +// Defined on 10.6 and above. +#ifndef CF_FORMAT_ARGUMENT + #define CF_FORMAT_ARGUMENT(A) +#endif + +// Defined on 10.6 and above. +#ifndef CF_FORMAT_FUNCTION + #define CF_FORMAT_FUNCTION(F,A) +#endif + +#ifndef GTM_NONNULL + #define GTM_NONNULL(x) __attribute__((nonnull(x))) +#endif + +// Invalidates the initializer from which it's called. +#ifndef GTMInvalidateInitializer + #if __has_feature(objc_arc) + #define GTMInvalidateInitializer() \ + do { \ + [self class]; /* Avoid warning of dead store to |self|. */ \ + _GTMDevAssert(NO, @"Invalid initializer."); \ + return nil; \ + } while (0) + #else + #define GTMInvalidateInitializer() \ + do { \ + [self release]; \ + _GTMDevAssert(NO, @"Invalid initializer."); \ + return nil; \ + } while (0) + #endif +#endif + +#ifdef __OBJC__ + +// Declared here so that it can easily be used for logging tracking if +// necessary. See GTMUnitTestDevLog.h for details. +@class NSString; +GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...) NS_FORMAT_FUNCTION(1, 2); + +// Macro to allow you to create NSStrings out of other macros. +// #define FOO foo +// NSString *fooString = GTM_NSSTRINGIFY(FOO); +#if !defined (GTM_NSSTRINGIFY) + #define GTM_NSSTRINGIFY_INNER(x) @#x + #define GTM_NSSTRINGIFY(x) GTM_NSSTRINGIFY_INNER(x) +#endif + +// Macro to allow fast enumeration when building for 10.5 or later, and +// reliance on NSEnumerator for 10.4. Remember, NSDictionary w/ FastEnumeration +// does keys, so pick the right thing, nothing is done on the FastEnumeration +// side to be sure you're getting what you wanted. +#ifndef GTM_FOREACH_OBJECT + #if TARGET_OS_IPHONE || !(MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5) + #define GTM_FOREACH_ENUMEREE(element, enumeration) \ + for (element in enumeration) + #define GTM_FOREACH_OBJECT(element, collection) \ + for (element in collection) + #define GTM_FOREACH_KEY(element, collection) \ + for (element in collection) + #else + #define GTM_FOREACH_ENUMEREE(element, enumeration) \ + for (NSEnumerator *_ ## element ## _enum = enumeration; \ + (element = [_ ## element ## _enum nextObject]) != nil; ) + #define GTM_FOREACH_OBJECT(element, collection) \ + GTM_FOREACH_ENUMEREE(element, [collection objectEnumerator]) + #define GTM_FOREACH_KEY(element, collection) \ + GTM_FOREACH_ENUMEREE(element, [collection keyEnumerator]) + #endif +#endif + +// ============================================================================ + +// To simplify support for both Leopard and Snow Leopard we declare +// the Snow Leopard protocols that we need here. +#if !defined(GTM_10_6_PROTOCOLS_DEFINED) && !(MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) +#define GTM_10_6_PROTOCOLS_DEFINED 1 +@protocol NSConnectionDelegate +@end +@protocol NSAnimationDelegate +@end +@protocol NSImageDelegate +@end +@protocol NSTabViewDelegate +@end +#endif // !defined(GTM_10_6_PROTOCOLS_DEFINED) && !(MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) + +// GTM_SEL_STRING is for specifying selector (usually property) names to KVC +// or KVO methods. +// In debug it will generate warnings for undeclared selectors if +// -Wunknown-selector is turned on. +// In release it will have no runtime overhead. +#ifndef GTM_SEL_STRING + #ifdef DEBUG + #define GTM_SEL_STRING(selName) NSStringFromSelector(@selector(selName)) + #else + #define GTM_SEL_STRING(selName) @#selName + #endif // DEBUG +#endif // GTM_SEL_STRING + +#endif // __OBJC__ diff --git a/src/common/mac/GTMLogger.h b/src/common/mac/GTMLogger.h index 7d52f01e..c4fd1402 100644 --- a/src/common/mac/GTMLogger.h +++ b/src/common/mac/GTMLogger.h @@ -6,9 +6,9 @@ // 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 @@ -18,15 +18,15 @@ // Key Abstractions // ---------------- -// -// This file declares multiple classes and protocols that are used by the +// +// This file declares multiple classes and protocols that are used by the // GTMLogger logging system. The 4 main abstractions used in this file are the // following: // // * logger (GTMLogger) - The main logging class that users interact with. It // has methods for logging at different levels and uses a log writer, a log // formatter, and a log filter to get the job done. -// +// // * log writer (GTMLogWriter) - Writes a given string to some log file, where // a "log file" can be a physical file on disk, a POST over HTTP to some URL, // or even some in-memory structure (e.g., a ring buffer). @@ -44,7 +44,7 @@ // flexibility to dynamically enable debug logging in Release builds. // // This file also declares some classes to handle the common log writer, log -// formatter, and log filter cases. Callers can also create their own writers, +// formatter, and log filter cases. Callers can also create their own writers, // formatters, and filters and they can even build them on top of the ones // declared here. Keep in mind that your custom writer/formatter/filter may be // called from multiple threads, so it must be thread-safe. @@ -55,12 +55,6 @@ // Predeclaration of used protocols that are declared later in this file. @protocol GTMLogWriter, GTMLogFormatter, GTMLogFilter; -#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 -#define CHECK_FORMAT_NSSTRING(a, b) __attribute__((format(__NSString__, a, b))) -#else -#define CHECK_FORMAT_NSSTRING(a, b) -#endif - // GTMLogger // // GTMLogger is the primary user-facing class for an object-oriented logging @@ -69,7 +63,7 @@ // sent to a GTMLogger to log a message, the message is formatted using the log // formatter, then the log filter is consulted to see if the message should be // logged, and if so, the message is sent to the log writer to be written out. -// +// // GTMLogger is intended to be a flexible and thread-safe logging solution. Its // flexibility comes from the fact that GTMLogger instances can be customized // with user defined formatters, filters, and writers. And these writers, @@ -77,7 +71,7 @@ // ways to suit the needs at hand. For example, multiple writers can be used at // the same time, and a GTMLogger instance can even be used as another // GTMLogger's writer. This allows for arbitrarily deep logging trees. -// +// // A standard GTMLogger uses a writer that sends messages to standard out, a // formatter that smacks a timestamp and a few other bits of interesting // information on the message, and a filter that filters out debug messages from @@ -85,13 +79,13 @@ // the following: // // 2007-12-30 10:29:24.177 myapp[4588/0xa07d0f60] [lvl=1] foo=<Foo: 0x123> -// +// // The output contains the date and time of the log message, the name of the // process followed by its process ID/thread ID, the log level at which the // message was logged (in the previous example the level was 1: // kGTMLoggerLevelDebug), and finally, the user-specified log message itself (in // this case, the log message was @"foo=%@", foo). -// +// // Multiple instances of GTMLogger can be created, each configured their own // way. Though GTMLogger is not a singleton (in the GoF sense), it does provide // access to a shared (i.e., globally accessible) GTMLogger instance. This makes @@ -113,10 +107,10 @@ // with behavior that many developers are currently used to. Note that this // means that GTMLoggerDebug(@"hi") will be compiled out of Release builds, but // [[GTMLogger sharedLogger] logDebug:@"hi"] will NOT be compiled out. -// +// // Standard loggers are created with the GTMLogLevelFilter log filter, which // filters out certain log messages based on log level, and some other settings. -// +// // In addition to the -logDebug:, -logInfo:, and -logError: methods defined on // GTMLogger itself, there are also C macros that make usage of the shared // GTMLogger instance very convenient. These macros are: @@ -146,7 +140,7 @@ // GTMLogger class directly in order to configure the shared logger, which all // of the code using the macros will be using. Again, this is just the typical // situation. -// +// // To be complete, there are cases where you may want to use GTMLogger directly, // or even create separate GTMLogger instances for some reason. That's fine, // too. @@ -160,14 +154,14 @@ // // GTMLoggerDebug(@"foo = %@", foo); // -// 2. The previous example is similar to the following. The major difference is +// 2. The previous example is similar to the following. The major difference is // that the previous call (example 1) will be compiled out of Release builds // but this statement will not be compiled out. // // [[GTMLogger sharedLogger] logDebug:@"foo = %@", foo]; // // 3. Send all logging output from the shared logger to a file. We do this by -// creating an NSFileHandle for writing associated with a file, and setting +// creating an NSFileHandle for writing associated with a file, and setting // that file handle as the logger's writer. // // NSFileHandle *f = [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log" @@ -185,12 +179,12 @@ // 5. Create a logger that writes to stdout and does NOT do any formatting to // the log message. This might be useful, for example, when writing a help // screen for a command-line tool to standard output. -// +// // GTMLogger *logger = [GTMLogger logger]; // [logger logInfo:@"%@ version 0.1 usage", progName]; // -// 6. Send log output to stdout AND to a log file. The trick here is that -// NSArrays function as composite log writers, which means when an array is +// 6. Send log output to stdout AND to a log file. The trick here is that +// NSArrays function as composite log writers, which means when an array is // set as the log writer, it forwards all logging messages to all of its // contained GTMLogWriters. // @@ -198,7 +192,7 @@ // NSArray *writers = [NSArray arrayWithObjects: // [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log" create:YES], // [NSFileHandle fileHandleWithStandardOutput], nil]; -// +// // GTMLogger *logger = [GTMLogger standardLogger]; // [logger setWriter:writers]; // [logger logInfo:@"hi"]; // Output goes to stdout and /tmp/f.log @@ -244,6 +238,10 @@ // Same as +standardLogger, but logs to stderr. + (id)standardLoggerWithStderr; +// Same as +standardLogger but levels >= kGTMLoggerLevelError are routed to +// stderr, everything else goes to stdout. ++ (id)standardLoggerWithStdoutAndStderr; + // Returns a new standard GTMLogger instance with a log writer that will // write to the file at |path|, and will use the GTMLogStandardFormatter and // GTMLogLevelFilter classes. If |path| does not exist, it will be created. @@ -274,20 +272,20 @@ // // Logs a message at the debug level (kGTMLoggerLevelDebug). -- (void)logDebug:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2); +- (void)logDebug:(NSString *)fmt, ... NS_FORMAT_FUNCTION(1, 2); // Logs a message at the info level (kGTMLoggerLevelInfo). -- (void)logInfo:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2); +- (void)logInfo:(NSString *)fmt, ... NS_FORMAT_FUNCTION(1, 2); // Logs a message at the error level (kGTMLoggerLevelError). -- (void)logError:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2); +- (void)logError:(NSString *)fmt, ... NS_FORMAT_FUNCTION(1, 2); // Logs a message at the assert level (kGTMLoggerLevelAssert). -- (void)logAssert:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2); +- (void)logAssert:(NSString *)fmt, ... NS_FORMAT_FUNCTION(1, 2); // // Accessors // -// Accessor methods for the log writer. If the log writer is set to nil, +// Accessor methods for the log writer. If the log writer is set to nil, // [NSFileHandle fileHandleWithStandardOutput] is used. - (id<GTMLogWriter>)writer; - (void)setWriter:(id<GTMLogWriter>)writer; @@ -306,20 +304,23 @@ @end // GTMLogger -// Helper functions that are used by the convenience GTMLogger*() macros that +// Helper functions that are used by the convenience GTMLogger*() macros that // enable the logging of function names. @interface GTMLogger (GTMLoggerMacroHelpers) - (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ... - CHECK_FORMAT_NSSTRING(2, 3); + NS_FORMAT_FUNCTION(2, 3); - (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ... - CHECK_FORMAT_NSSTRING(2, 3); + NS_FORMAT_FUNCTION(2, 3); - (void)logFuncError:(const char *)func msg:(NSString *)fmt, ... - CHECK_FORMAT_NSSTRING(2, 3); + NS_FORMAT_FUNCTION(2, 3); - (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ... - CHECK_FORMAT_NSSTRING(2, 3); + NS_FORMAT_FUNCTION(2, 3); @end // GTMLoggerMacroHelpers +// The convenience macros are only defined if they haven't already been defined. +#ifndef GTMLoggerInfo + // Convenience macros that log to the shared GTMLogger instance. These macros // are how users should typically log to GTMLogger. Notice that GTMLoggerDebug() // calls will be compiled out of non-Debug builds. @@ -339,6 +340,8 @@ #define GTMLoggerDebug(...) do {} while(0) #endif +#endif // !defined(GTMLoggerInfo) + // Log levels. typedef enum { kGTMLoggerLevelUnknown, @@ -365,7 +368,7 @@ typedef enum { // now becomes a valid log writer. Log messages are written to the file handle // with a newline appended. @interface NSFileHandle (GTMFileHandleLogWriter) <GTMLogWriter> -// Opens the file at |path| in append mode, and creates the file with |mode| +// Opens the file at |path| in append mode, and creates the file with |mode| // if it didn't previously exist. + (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode; @end // NSFileHandle @@ -379,7 +382,7 @@ typedef enum { // // This is useful in situations where you would like to send log output to // multiple log writers at the same time. Simply create an NSArray of the log -// writers you wish to use, then set the array as the "writer" for your +// writers you wish to use, then set the array as the "writer" for your // GTMLogger instance. @interface NSArray (GTMArrayCompositeLogWriter) <GTMLogWriter> @end // GTMArrayCompositeLogWriter @@ -390,7 +393,7 @@ typedef enum { // // This is useful when you want to configure a logger to log to a specific // writer with a specific formatter and/or filter. But you want to also compose -// that with a different log writer that may have its own formatter and/or +// that with a different log writer that may have its own formatter and/or // filter. @interface GTMLogger (GTMLoggerLogWriter) <GTMLogWriter> @end // GTMLoggerLogWriter @@ -407,14 +410,18 @@ typedef enum { - (NSString *)stringForFunc:(NSString *)func withFormat:(NSString *)fmt valist:(va_list)args - level:(GTMLoggerLevel)level; + level:(GTMLoggerLevel)level NS_FORMAT_FUNCTION(2, 0); @end // GTMLogFormatter -// A basic log formatter that formats a string the same way that NSLog (or +// A basic log formatter that formats a string the same way that NSLog (or // printf) would. It does not do anything fancy, nor does it add any data of its // own. @interface GTMLogBasicFormatter : NSObject <GTMLogFormatter> + +// Helper method for prettying C99 __func__ and GCC __PRETTY_FUNCTION__ +- (NSString *)prettyNameForFunc:(NSString *)func; + @end // GTMLogBasicFormatter @@ -450,9 +457,48 @@ typedef enum { @interface GTMLogLevelFilter : NSObject <GTMLogFilter> @end // GTMLogLevelFilter - // A simple log filter that does NOT filter anything out; // -filterAllowsMessage:level will always return YES. This can be a convenient // way to enable debug-level logging in release builds (if you so desire). @interface GTMLogNoFilter : NSObject <GTMLogFilter> @end // GTMLogNoFilter + + +// Base class for custom level filters. Not for direct use, use the minimum +// or maximum level subclasses below. +@interface GTMLogAllowedLevelFilter : NSObject <GTMLogFilter> { + @private + NSIndexSet *allowedLevels_; +} +@end + +// A log filter that allows you to set a minimum log level. Messages below this +// level will be filtered. +@interface GTMLogMininumLevelFilter : GTMLogAllowedLevelFilter + +// Designated initializer, logs at levels < |level| will be filtered. +- (id)initWithMinimumLevel:(GTMLoggerLevel)level; + +@end + +// A log filter that allows you to set a maximum log level. Messages whose level +// exceeds this level will be filtered. This is really only useful if you have +// a composite GTMLogger that is sending the other messages elsewhere. +@interface GTMLogMaximumLevelFilter : GTMLogAllowedLevelFilter + +// Designated initializer, logs at levels > |level| will be filtered. +- (id)initWithMaximumLevel:(GTMLoggerLevel)level; + +@end + + +// For subclasses only +@interface GTMLogger (PrivateMethods) + +- (void)logInternalFunc:(const char *)func + format:(NSString *)fmt + valist:(va_list)args + level:(GTMLoggerLevel)level NS_FORMAT_FUNCTION(2, 0); + +@end + diff --git a/src/common/mac/GTMLogger.m b/src/common/mac/GTMLogger.m index de941d2e..4b40747b 100644 --- a/src/common/mac/GTMLogger.m +++ b/src/common/mac/GTMLogger.m @@ -6,9 +6,9 @@ // 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 @@ -24,39 +24,30 @@ #import <pthread.h> -// Define a trivial assertion macro to avoid dependencies -#ifdef DEBUG - #define GTMLOGGER_ASSERT(expr) assert(expr) -#else - #define GTMLOGGER_ASSERT(expr) -#endif - - -@interface GTMLogger (PrivateMethods) - -- (void)logInternalFunc:(const char *)func - format:(NSString *)fmt - valist:(va_list)args - level:(GTMLoggerLevel)level; - -@end - +#if !defined(__clang__) && (__GNUC__*10+__GNUC_MINOR__ >= 42) +// Some versions of GCC (4.2 and below AFAIK) aren't great about supporting +// -Wmissing-format-attribute +// when the function is anything more complex than foo(NSString *fmt, ...). +// You see the error inside the function when you turn ... into va_args and +// attempt to call another function (like vsprintf for example). +// So we just shut off the warning for this file. We reenable it at the end. +#pragma GCC diagnostic ignored "-Wmissing-format-attribute" +#endif // !__clang__ -// Reference to the shared GTMLogger instance. This is not a singleton, it's +// Reference to the shared GTMLogger instance. This is not a singleton, it's // just an easy reference to one shared instance. static GTMLogger *gSharedLogger = nil; @implementation GTMLogger -// Returns a pointer to the shared logger instance. If none exists, a standard +// Returns a pointer to the shared logger instance. If none exists, a standard // logger is created and returned. + (id)sharedLogger { @synchronized(self) { if (gSharedLogger == nil) { gSharedLogger = [[self standardLogger] retain]; } - GTMLOGGER_ASSERT(gSharedLogger != nil); } return [[gSharedLogger retain] autorelease]; } @@ -69,24 +60,85 @@ static GTMLogger *gSharedLogger = nil; } + (id)standardLogger { - id<GTMLogWriter> writer = [NSFileHandle fileHandleWithStandardOutput]; - id<GTMLogFormatter> fr = [[[GTMLogStandardFormatter alloc] init] autorelease]; - id<GTMLogFilter> filter = [[[GTMLogLevelFilter alloc] init] autorelease]; - return [self loggerWithWriter:writer formatter:fr filter:filter]; + // Don't trust NSFileHandle not to throw + @try { + id<GTMLogWriter> writer = [NSFileHandle fileHandleWithStandardOutput]; + id<GTMLogFormatter> fr = [[[GTMLogStandardFormatter alloc] init] + autorelease]; + id<GTMLogFilter> filter = [[[GTMLogLevelFilter alloc] init] autorelease]; + return [[[self alloc] initWithWriter:writer + formatter:fr + filter:filter] autorelease]; + } + @catch (id e) { + // Ignored + } + return nil; } + (id)standardLoggerWithStderr { - id me = [self standardLogger]; - [me setWriter:[NSFileHandle fileHandleWithStandardError]]; - return me; + // Don't trust NSFileHandle not to throw + @try { + id me = [self standardLogger]; + [me setWriter:[NSFileHandle fileHandleWithStandardError]]; + return me; + } + @catch (id e) { + // Ignored + } + return nil; +} + ++ (id)standardLoggerWithStdoutAndStderr { + // We're going to take advantage of the GTMLogger to GTMLogWriter adaptor + // and create a composite logger that an outer "standard" logger can use + // as a writer. Our inner loggers should apply no formatting since the main + // logger does that and we want the caller to be able to change formatters + // or add writers without knowing the inner structure of our composite. + + // Don't trust NSFileHandle not to throw + @try { + GTMLogBasicFormatter *formatter = [[[GTMLogBasicFormatter alloc] init] + autorelease]; + GTMLogger *stdoutLogger = + [self loggerWithWriter:[NSFileHandle fileHandleWithStandardOutput] + formatter:formatter + filter:[[[GTMLogMaximumLevelFilter alloc] + initWithMaximumLevel:kGTMLoggerLevelInfo] + autorelease]]; + GTMLogger *stderrLogger = + [self loggerWithWriter:[NSFileHandle fileHandleWithStandardError] + formatter:formatter + filter:[[[GTMLogMininumLevelFilter alloc] + initWithMinimumLevel:kGTMLoggerLevelError] + autorelease]]; + GTMLogger *compositeWriter = + [self loggerWithWriter:[NSArray arrayWithObjects: + stdoutLogger, stderrLogger, nil] + formatter:formatter + filter:[[[GTMLogNoFilter alloc] init] autorelease]]; + GTMLogger *outerLogger = [self standardLogger]; + [outerLogger setWriter:compositeWriter]; + return outerLogger; + } + @catch (id e) { + // Ignored + } + return nil; } + (id)standardLoggerWithPath:(NSString *)path { - NSFileHandle *fh = [NSFileHandle fileHandleForLoggingAtPath:path mode:0644]; - if (fh == nil) return nil; - id me = [self standardLogger]; - [me setWriter:fh]; - return me; + @try { + NSFileHandle *fh = [NSFileHandle fileHandleForLoggingAtPath:path mode:0644]; + if (fh == nil) return nil; + id me = [self standardLogger]; + [me setWriter:fh]; + return me; + } + @catch (id e) { + // Ignored + } + return nil; } + (id)loggerWithWriter:(id<GTMLogWriter>)writer @@ -112,69 +164,85 @@ static GTMLogger *gSharedLogger = nil; [self setWriter:writer]; [self setFormatter:formatter]; [self setFilter:filter]; - GTMLOGGER_ASSERT(formatter_ != nil); - GTMLOGGER_ASSERT(filter_ != nil); - GTMLOGGER_ASSERT(writer_ != nil); } return self; } - (void)dealloc { - GTMLOGGER_ASSERT(writer_ != nil); - GTMLOGGER_ASSERT(formatter_ != nil); - GTMLOGGER_ASSERT(filter_ != nil); - [writer_ release]; - [formatter_ release]; - [filter_ release]; + // Unlikely, but |writer_| may be an NSFileHandle, which can throw + @try { + [formatter_ release]; + [filter_ release]; + [writer_ release]; + } + @catch (id e) { + // Ignored + } [super dealloc]; } - (id<GTMLogWriter>)writer { - GTMLOGGER_ASSERT(writer_ != nil); return [[writer_ retain] autorelease]; } - (void)setWriter:(id<GTMLogWriter>)writer { @synchronized(self) { [writer_ autorelease]; - if (writer == nil) - writer_ = [[NSFileHandle fileHandleWithStandardOutput] retain]; - else + writer_ = nil; + if (writer == nil) { + // Try to use stdout, but don't trust NSFileHandle + @try { + writer_ = [[NSFileHandle fileHandleWithStandardOutput] retain]; + } + @catch (id e) { + // Leave |writer_| nil + } + } else { writer_ = [writer retain]; + } } - GTMLOGGER_ASSERT(writer_ != nil); } - (id<GTMLogFormatter>)formatter { - GTMLOGGER_ASSERT(formatter_ != nil); return [[formatter_ retain] autorelease]; } - (void)setFormatter:(id<GTMLogFormatter>)formatter { @synchronized(self) { [formatter_ autorelease]; - if (formatter == nil) - formatter_ = [[GTMLogBasicFormatter alloc] init]; - else + formatter_ = nil; + if (formatter == nil) { + @try { + formatter_ = [[GTMLogBasicFormatter alloc] init]; + } + @catch (id e) { + // Leave |formatter_| nil + } + } else { formatter_ = [formatter retain]; + } } - GTMLOGGER_ASSERT(formatter_ != nil); } - (id<GTMLogFilter>)filter { - GTMLOGGER_ASSERT(filter_ != nil); return [[filter_ retain] autorelease]; } - (void)setFilter:(id<GTMLogFilter>)filter { @synchronized(self) { [filter_ autorelease]; - if (filter == nil) - filter_ = [[GTMLogNoFilter alloc] init]; - else + filter_ = nil; + if (filter == nil) { + @try { + filter_ = [[GTMLogNoFilter alloc] init]; + } + @catch (id e) { + // Leave |filter_| nil + } + } else { filter_ = [filter retain]; + } } - GTMLOGGER_ASSERT(filter_ != nil); } - (void)logDebug:(NSString *)fmt, ... { @@ -207,7 +275,6 @@ static GTMLogger *gSharedLogger = nil; @end // GTMLogger - @implementation GTMLogger (GTMLoggerMacroHelpers) - (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ... { @@ -240,24 +307,26 @@ static GTMLogger *gSharedLogger = nil; @end // GTMLoggerMacroHelpers - @implementation GTMLogger (PrivateMethods) - (void)logInternalFunc:(const char *)func format:(NSString *)fmt - valist:(va_list)args + valist:(va_list)args level:(GTMLoggerLevel)level { - GTMLOGGER_ASSERT(formatter_ != nil); - GTMLOGGER_ASSERT(filter_ != nil); - GTMLOGGER_ASSERT(writer_ != nil); - - NSString *fname = func ? [NSString stringWithUTF8String:func] : nil; - NSString *msg = [formatter_ stringForFunc:fname - withFormat:fmt - valist:args - level:level]; - if (msg && [filter_ filterAllowsMessage:msg level:level]) - [writer_ logMessage:msg level:level]; + // Primary point where logging happens, logging should never throw, catch + // everything. + @try { + NSString *fname = func ? [NSString stringWithUTF8String:func] : nil; + NSString *msg = [formatter_ stringForFunc:fname + withFormat:fmt + valist:args + level:level]; + if (msg && [filter_ filterAllowsMessage:msg level:level]) + [writer_ logMessage:msg level:level]; + } + @catch (id e) { + // Ignored + } } @end // PrivateMethods @@ -278,8 +347,16 @@ static GTMLogger *gSharedLogger = nil; - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level { @synchronized(self) { - NSString *line = [NSString stringWithFormat:@"%@\n", msg]; - [self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]]; + // Closed pipes should not generate exceptions in our caller. Catch here + // as well [GTMLogger logInternalFunc:...] so that an exception in this + // writer does not prevent other writers from having a chance. + @try { + NSString *line = [NSString stringWithFormat:@"%@\n", msg]; + [self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]]; + } + @catch (id e) { + // Ignored + } } } @@ -306,18 +383,18 @@ static GTMLogger *gSharedLogger = nil; - (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level { switch (level) { case kGTMLoggerLevelDebug: - [self logDebug:@"%@", msg]; + [self logDebug:@"%@", msg]; break; case kGTMLoggerLevelInfo: [self logInfo:@"%@", msg]; break; - case kGTMLoggerLevelError: + case kGTMLoggerLevelError: [self logError:@"%@", msg]; break; case kGTMLoggerLevelAssert: [self logAssert:@"%@", msg]; break; - default: + default: // Ignore the message. break; } @@ -328,19 +405,32 @@ static GTMLogger *gSharedLogger = nil; @implementation GTMLogBasicFormatter +- (NSString *)prettyNameForFunc:(NSString *)func { + NSString *name = [func stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSString *function = @"(unknown)"; + if ([name length]) { + if (// Objective C __func__ and __PRETTY_FUNCTION__ + [name hasPrefix:@"-["] || [name hasPrefix:@"+["] || + // C++ __PRETTY_FUNCTION__ and other preadorned formats + [name hasSuffix:@")"]) { + function = name; + } else { + // Assume C99 __func__ + function = [NSString stringWithFormat:@"%@()", name]; + } + } + return function; +} + - (NSString *)stringForFunc:(NSString *)func withFormat:(NSString *)fmt - valist:(va_list)args + valist:(va_list)args level:(GTMLoggerLevel)level { - // Performance note: since we always have to create a new NSString from the - // returned CFStringRef, we may want to do a quick check here to see if |fmt| - // contains a '%', and if not, simply return 'fmt'. - CFStringRef cfmsg = NULL; - cfmsg = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, - NULL, // format options - (CFStringRef)fmt, - args); - return GTMCFAutorelease(cfmsg); + // Performance note: We may want to do a quick check here to see if |fmt| + // contains a '%', and if not, simply return 'fmt'. + if (!(fmt && args)) return nil; + return [[[NSString alloc] initWithFormat:fmt arguments:args] autorelease]; } @end // GTMLogBasicFormatter @@ -355,6 +445,10 @@ static GTMLogger *gSharedLogger = nil; [dateFormatter_ setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"]; pname_ = [[[NSProcessInfo processInfo] processName] copy]; pid_ = [[NSProcessInfo processInfo] processIdentifier]; + if (!(dateFormatter_ && pname_)) { + [self release]; + return nil; + } } return self; } @@ -367,17 +461,17 @@ static GTMLogger *gSharedLogger = nil; - (NSString *)stringForFunc:(NSString *)func withFormat:(NSString *)fmt - valist:(va_list)args + valist:(va_list)args level:(GTMLoggerLevel)level { - GTMLOGGER_ASSERT(dateFormatter_ != nil); NSString *tstamp = nil; @synchronized (dateFormatter_) { tstamp = [dateFormatter_ stringFromDate:[NSDate date]]; } return [NSString stringWithFormat:@"%@ %@[%d/%p] [lvl=%d] %@ %@", - tstamp, pname_, pid_, pthread_self(), - level, (func ? func : @"(no func)"), - [super stringForFunc:func withFormat:fmt valist:args level:level]]; + tstamp, pname_, pid_, pthread_self(), + level, [self prettyNameForFunc:func], + // |super| has guard for nil |fmt| and |args| + [super stringForFunc:func withFormat:fmt valist:args level:level]]; } @end // GTMLogStandardFormatter @@ -391,14 +485,20 @@ static GTMLogger *gSharedLogger = nil; // COV_NF_START static BOOL IsVerboseLoggingEnabled(void) { static NSString *const kVerboseLoggingKey = @"GTMVerboseLogging"; - static char *env = NULL; - if (env == NULL) - env = getenv([kVerboseLoggingKey UTF8String]); - - if (env && env[0]) { - return (strtol(env, NULL, 10) != 0); + NSString *value = [[[NSProcessInfo processInfo] environment] + objectForKey:kVerboseLoggingKey]; + if (value) { + // Emulate [NSString boolValue] for pre-10.5 + value = [value stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if ([[value uppercaseString] hasPrefix:@"Y"] || + [[value uppercaseString] hasPrefix:@"T"] || + [value intValue]) { + return YES; + } else { + return NO; + } } - return [[NSUserDefaults standardUserDefaults] boolForKey:kVerboseLoggingKey]; } // COV_NF_END @@ -409,15 +509,15 @@ static BOOL IsVerboseLoggingEnabled(void) { #if DEBUG return YES; #endif - + BOOL allow = YES; - + switch (level) { case kGTMLoggerLevelDebug: allow = NO; break; case kGTMLoggerLevelInfo: - allow = (IsVerboseLoggingEnabled() == YES); + allow = IsVerboseLoggingEnabled(); break; case kGTMLoggerLevelError: allow = YES; @@ -443,3 +543,70 @@ static BOOL IsVerboseLoggingEnabled(void) { } @end // GTMLogNoFilter + + +@implementation GTMLogAllowedLevelFilter + +// Private designated initializer +- (id)initWithAllowedLevels:(NSIndexSet *)levels { + self = [super init]; + if (self != nil) { + allowedLevels_ = [levels retain]; + // Cap min/max level + if (!allowedLevels_ || + // NSIndexSet is unsigned so only check the high bound, but need to + // check both first and last index because NSIndexSet appears to allow + // wraparound. + ([allowedLevels_ firstIndex] > kGTMLoggerLevelAssert) || + ([allowedLevels_ lastIndex] > kGTMLoggerLevelAssert)) { + [self release]; + return nil; + } + } + return self; +} + +- (id)init { + // Allow all levels in default init + return [self initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange: + NSMakeRange(kGTMLoggerLevelUnknown, + (kGTMLoggerLevelAssert - kGTMLoggerLevelUnknown + 1))]]; +} + +- (void)dealloc { + [allowedLevels_ release]; + [super dealloc]; +} + +- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level { + return [allowedLevels_ containsIndex:level]; +} + +@end // GTMLogAllowedLevelFilter + + +@implementation GTMLogMininumLevelFilter + +- (id)initWithMinimumLevel:(GTMLoggerLevel)level { + return [super initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange: + NSMakeRange(level, + (kGTMLoggerLevelAssert - level + 1))]]; +} + +@end // GTMLogMininumLevelFilter + + +@implementation GTMLogMaximumLevelFilter + +- (id)initWithMaximumLevel:(GTMLoggerLevel)level { + return [super initWithAllowedLevels:[NSIndexSet indexSetWithIndexesInRange: + NSMakeRange(kGTMLoggerLevelUnknown, level + 1)]]; +} + +@end // GTMLogMaximumLevelFilter + +#if !defined(__clang__) && (__GNUC__*10+__GNUC_MINOR__ >= 42) +// See comment at top of file. +#pragma GCC diagnostic error "-Wmissing-format-attribute" +#endif // !__clang__ + diff --git a/src/common/mac/testing/GTMSenTestCase.h b/src/common/mac/testing/GTMSenTestCase.h index d425f597..ce3d9022 100644 --- a/src/common/mac/testing/GTMSenTestCase.h +++ b/src/common/mac/testing/GTMSenTestCase.h @@ -6,9 +6,9 @@ // 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 @@ -22,44 +22,60 @@ // Copyright (c) 1997-2005, Sen:te (Sente SA). All rights reserved. // // Use of this source code is governed by the following license: -// -// Redistribution and use in source and binary forms, with or without modification, +// +// Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: -// -// (1) Redistributions of source code must retain the above copyright notice, +// +// (1) Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. -// -// (2) Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation +// +// (2) Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL Sente SA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL Sente SA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +// // Note: this license is equivalent to the FreeBSD license. -// +// // This notice may not be removed from this file. -// Some extra test case macros that would have been convenient for SenTestingKit -// to provide. I didn't stick GTM in front of the Macro names, so that they would +// Some extra test case macros that would have been convenient for SenTestingKit +// to provide. I didn't stick GTM in front of the Macro names, so that they would // be easy to remember. #import "GTMDefines.h" -#if (!GTM_IPHONE_SDK) +#if (!GTM_IPHONE_SDK) || (GTM_IPHONE_USE_SENTEST) #import <SenTestingKit/SenTestingKit.h> #else #import <Foundation/Foundation.h> +#ifdef __cplusplus +extern "C" { +#endif + +#if defined __clang__ +// gcc and gcc-llvm do not allow you to use STAssert(blah, nil) with nil +// as a description if you have the NS_FORMAT_FUNCTION on. +// clang however will not compile without warnings if you don't have it. +NSString *STComposeString(NSString *, ...) NS_FORMAT_FUNCTION(1, 2); +#else NSString *STComposeString(NSString *, ...); +#endif // __clang__ + +#ifdef __cplusplus +} #endif +#endif // !GTM_IPHONE_SDK || GTM_IPHONE_USE_SENTEST + // Generates a failure when a1 != noErr // Args: // a1: should be either an OSErr or an OSStatus @@ -68,25 +84,24 @@ NSString *STComposeString(NSString *, ...); // ...: A variable number of arguments to the format string. Can be absent. #define STAssertNoErr(a1, description, ...) \ do { \ - @try {\ + @try { \ OSStatus a1value = (a1); \ if (a1value != noErr) { \ - NSString *_expression = [NSString stringWithFormat:@"Expected noErr, got %ld for (%s)", a1value, #a1]; \ - if (description) { \ - _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ - } \ - [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:_expression]]; \ + NSString *_expression = [NSString stringWithFormat:@"Expected noErr, got %ld for (%s)", (long)a1value, #a1]; \ + [self failWithException:([NSException failureInCondition:_expression \ + isTrue:NO \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)])]; \ } \ - }\ - @catch (id anException) {\ + } \ + @catch (id anException) { \ [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == noErr fails", #a1] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) // Generates a failure when a1 != a2 @@ -98,26 +113,25 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertErr(a1, a2, description, ...) \ do { \ - @try {\ + @try { \ OSStatus a1value = (a1); \ OSStatus a2value = (a2); \ if (a1value != a2value) { \ - NSString *_expression = [NSString stringWithFormat:@"Expected %s(%ld) but got %ld for (%s)", #a2, a2value, a1value, #a1]; \ - if (description) { \ - _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ - } \ - [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:_expression]]; \ + NSString *_expression = [NSString stringWithFormat:@"Expected %s(%ld) but got %ld for (%s)", #a2, (long)a2value, (long)a1value, #a1]; \ + [self failWithException:([NSException failureInCondition:_expression \ + isTrue:NO \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)])]; \ } \ - }\ - @catch (id anException) {\ + } \ + @catch (id anException) { \ [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == (%s) fails", #a1, #a2] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) @@ -129,25 +143,24 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertNotNULL(a1, description, ...) \ do { \ - @try {\ - const void* a1value = (a1); \ - if (a1value == NULL) { \ - NSString *_expression = [NSString stringWithFormat:@"(%s) != NULL", #a1]; \ - if (description) { \ - _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ - } \ - [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:_expression]]; \ + @try { \ + __typeof__(a1) a1value = (a1); \ + if (a1value == (__typeof__(a1))NULL) { \ + NSString *_expression = [NSString stringWithFormat:@"((%s) != NULL)", #a1]; \ + [self failWithException:([NSException failureInCondition:_expression \ + isTrue:NO \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)])]; \ } \ - }\ - @catch (id anException) {\ + } \ + @catch (id anException) { \ [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) != NULL fails", #a1] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) // Generates a failure when a1 is not NULL @@ -158,28 +171,27 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertNULL(a1, description, ...) \ do { \ - @try {\ - const void* a1value = (a1); \ - if (a1value != NULL) { \ - NSString *_expression = [NSString stringWithFormat:@"(%s) == NULL", #a1]; \ - if (description) { \ - _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ - } \ - [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:_expression]]; \ + @try { \ + __typeof__(a1) a1value = (a1); \ + if (a1value != (__typeof__(a1))NULL) { \ + NSString *_expression = [NSString stringWithFormat:@"((%s) == NULL)", #a1]; \ + [self failWithException:([NSException failureInCondition:_expression \ + isTrue:NO \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)])]; \ } \ - }\ - @catch (id anException) {\ + } \ + @catch (id anException) { \ [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == NULL fails", #a1] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) -// Generates a failure when a1 is equal to a2. This test is for C scalars, +// Generates a failure when a1 is equal to a2. This test is for C scalars, // structs and unions. // Args: // a1: argument 1 @@ -189,34 +201,33 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertNotEquals(a1, a2, description, ...) \ do { \ - @try {\ - if (@encode(__typeof__(a1)) != @encode(__typeof__(a2))) { \ + @try { \ + if (strcmp(@encode(__typeof__(a1)), @encode(__typeof__(a2)))) { \ [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:[[[NSString stringWithFormat:@"Type mismatch (%@/%@) -- ",@encode(__typeof__(a1)),@encode(__typeof__(a2))] stringByAppendingString:STComposeString(description, ##__VA_ARGS__)]]]; \ + withDescription:@"Type mismatch -- %@", STComposeString(description, ##__VA_ARGS__)]]; \ } else { \ __typeof__(a1) a1value = (a1); \ __typeof__(a2) a2value = (a2); \ NSValue *a1encoded = [NSValue value:&a1value withObjCType:@encode(__typeof__(a1))]; \ NSValue *a2encoded = [NSValue value:&a2value withObjCType:@encode(__typeof__(a2))]; \ if ([a1encoded isEqualToValue:a2encoded]) { \ - NSString *_expression = [NSString stringWithFormat:@"(%s) != (%s)", #a1, #a2]; \ - if (description) { \ - _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ - } \ - [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:_expression]]; \ - } \ + NSString *_expression = [NSString stringWithFormat:@"((%s) != (%s))", #a1, #a2]; \ + [self failWithException:([NSException failureInCondition:_expression \ + isTrue:NO \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)])]; \ + }\ } \ } \ - @catch (id anException) {\ + @catch (id anException) { \ [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) != (%s)", #a1, #a2] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) // Generates a failure when a1 is equal to a2. This test is for objects. @@ -226,32 +237,30 @@ do { \ // description: A format string as in the printf() function. Can be nil or // an empty string but must be present. // ...: A variable number of arguments to the format string. Can be absent. -#define STAssertNotEqualObjects(a1, a2, desc, ...) \ +#define STAssertNotEqualObjects(a1, a2, description, ...) \ do { \ @try {\ id a1value = (a1); \ id a2value = (a2); \ - if ( (@encode(__typeof__(a1value)) == @encode(id)) && \ - (@encode(__typeof__(a2value)) == @encode(id)) && \ - ![(id)a1value isEqual:(id)a2value] ) continue; \ - NSString *_expression = [NSString stringWithFormat:@"%s('%@') != %s('%@')", #a1, [a1 description], #a2, [a2 description]]; \ - if (desc) { \ - _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(desc, ##__VA_ARGS__)]; \ - } \ - [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:_expression]]; \ + if ( (strcmp(@encode(__typeof__(a1value)), @encode(id)) == 0) && \ + (strcmp(@encode(__typeof__(a2value)), @encode(id)) == 0) && \ + (![(id)a1value isEqual:(id)a2value]) ) continue; \ + [self failWithException:([NSException failureInEqualityBetweenObject:a1value \ + andObject:a2value \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)])]; \ }\ @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ - exception:anException \ - inFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:STComposeString(desc, ##__VA_ARGS__)]]; \ + [self failWithException:([NSException failureInRaise:[NSString stringWithFormat:@"(%s) != (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)])]; \ }\ } while(0) -// Generates a failure when a1 is not 'op' to a2. This test is for C scalars. +// Generates a failure when a1 is not 'op' to a2. This test is for C scalars. // Args: // a1: argument 1 // a2: argument 2 @@ -261,38 +270,37 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertOperation(a1, a2, op, description, ...) \ do { \ - @try {\ - if (@encode(__typeof__(a1)) != @encode(__typeof__(a2))) { \ + @try { \ + if (strcmp(@encode(__typeof__(a1)), @encode(__typeof__(a2)))) { \ [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:[[[NSString stringWithFormat:@"Type mismatch (%@/%@) -- ",@encode(__typeof__(a1)),@encode(__typeof__(a2))] stringByAppendingString:STComposeString(description, ##__VA_ARGS__)]]]; \ + withDescription:@"Type mismatch -- %@", STComposeString(description, ##__VA_ARGS__)]]; \ } else { \ __typeof__(a1) a1value = (a1); \ __typeof__(a2) a2value = (a2); \ if (!(a1value op a2value)) { \ double a1DoubleValue = a1value; \ double a2DoubleValue = a2value; \ - NSString *_expression = [NSString stringWithFormat:@"%s (%lg) %s %s (%lg)", #a1, a1DoubleValue, #op, #a2, a2DoubleValue]; \ - if (description) { \ - _expression = [NSString stringWithFormat:@"%@: %@", _expression, STComposeString(description, ##__VA_ARGS__)]; \ - } \ - [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ - atLine:__LINE__ \ - withDescription:_expression]]; \ + NSString *_expression = [NSString stringWithFormat:@"(%s (%lg) %s %s (%lg))", #a1, a1DoubleValue, #op, #a2, a2DoubleValue]; \ + [self failWithException:([NSException failureInCondition:_expression \ + isTrue:NO \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)])]; \ } \ } \ } \ - @catch (id anException) {\ + @catch (id anException) { \ [self failWithException:[NSException \ failureInRaise:[NSString stringWithFormat:@"(%s) %s (%s)", #a1, #op, #a2] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) -// Generates a failure when a1 is not > a2. This test is for C scalars. +// Generates a failure when a1 is not > a2. This test is for C scalars. // Args: // a1: argument 1 // a2: argument 2 @@ -303,7 +311,7 @@ do { \ #define STAssertGreaterThan(a1, a2, description, ...) \ STAssertOperation(a1, a2, >, description, ##__VA_ARGS__) -// Generates a failure when a1 is not >= a2. This test is for C scalars. +// Generates a failure when a1 is not >= a2. This test is for C scalars. // Args: // a1: argument 1 // a2: argument 2 @@ -314,7 +322,7 @@ do { \ #define STAssertGreaterThanOrEqual(a1, a2, description, ...) \ STAssertOperation(a1, a2, >=, description, ##__VA_ARGS__) -// Generates a failure when a1 is not < a2. This test is for C scalars. +// Generates a failure when a1 is not < a2. This test is for C scalars. // Args: // a1: argument 1 // a2: argument 2 @@ -325,7 +333,7 @@ do { \ #define STAssertLessThan(a1, a2, description, ...) \ STAssertOperation(a1, a2, <, description, ##__VA_ARGS__) -// Generates a failure when a1 is not <= a2. This test is for C scalars. +// Generates a failure when a1 is not <= a2. This test is for C scalars. // Args: // a1: argument 1 // a2: argument 2 @@ -349,26 +357,26 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertEqualStrings(a1, a2, description, ...) \ do { \ - @try {\ + @try { \ id a1value = (a1); \ id a2value = (a2); \ if (a1value == a2value) continue; \ if ([a1value isKindOfClass:[NSString class]] && \ [a2value isKindOfClass:[NSString class]] && \ [a1value compare:a2value options:0] == NSOrderedSame) continue; \ - [self failWithException:[NSException failureInEqualityBetweenObject: a1value \ - andObject: a2value \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + [self failWithException:[NSException failureInEqualityBetweenObject:a1value \ + andObject:a2value \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == (%s)", #a1, #a2] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) // Generates a failure when string a1 is equal to string a2. This call @@ -384,25 +392,25 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertNotEqualStrings(a1, a2, description, ...) \ do { \ - @try {\ + @try { \ id a1value = (a1); \ id a2value = (a2); \ if ([a1value isKindOfClass:[NSString class]] && \ [a2value isKindOfClass:[NSString class]] && \ [a1value compare:a2value options:0] != NSOrderedSame) continue; \ - [self failWithException:[NSException failureInEqualityBetweenObject: a1value \ - andObject: a2value \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ + [self failWithException:[NSException failureInEqualityBetweenObject:a1value \ + andObject:a2value \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) != (%s)", #a1, #a2] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) // Generates a failure when c-string a1 is not equal to c-string a2. @@ -414,24 +422,24 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertEqualCStrings(a1, a2, description, ...) \ do { \ - @try {\ + @try { \ const char* a1value = (a1); \ const char* a2value = (a2); \ if (a1value == a2value) continue; \ if (strcmp(a1value, a2value) == 0) continue; \ - [self failWithException:[NSException failureInEqualityBetweenObject: [NSString stringWithUTF8String:a1value] \ - andObject: [NSString stringWithUTF8String:a2value] \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + [self failWithException:[NSException failureInEqualityBetweenObject:[NSString stringWithUTF8String:a1value] \ + andObject:[NSString stringWithUTF8String:a2value] \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == (%s)", #a1, #a2] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) // Generates a failure when c-string a1 is equal to c-string a2. @@ -443,30 +451,97 @@ do { \ // ...: A variable number of arguments to the format string. Can be absent. #define STAssertNotEqualCStrings(a1, a2, description, ...) \ do { \ - @try {\ + @try { \ const char* a1value = (a1); \ const char* a2value = (a2); \ if (strcmp(a1value, a2value) != 0) continue; \ - [self failWithException:[NSException failureInEqualityBetweenObject: [NSString stringWithUTF8String:a1value] \ - andObject: [NSString stringWithUTF8String:a2value] \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != (%s)", #a1, #a2] \ + [self failWithException:[NSException failureInEqualityBetweenObject:[NSString stringWithUTF8String:a1value] \ + andObject:[NSString stringWithUTF8String:a2value] \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) != (%s)", #a1, #a2] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ +} while(0) + +/*" Generates a failure when a1 is not equal to a2 within + or - accuracy is false. + This test is for GLKit types (GLKVector, GLKMatrix) where small differences + could make these items not exactly equal. Do not use this version directly. + Use the explicit STAssertEqualGLKVectors and STAssertEqualGLKMatrices defined + below. + _{a1 The GLKType on the left.} + _{a2 The GLKType on the right.} + _{accuracy The maximum difference between a1 and a2 for these values to be + considered equal.} + _{description A format string as in the printf() function. Can be nil or + an empty string but must be present.} + _{... A variable number of arguments to the format string. Can be absent.} +"*/ + +#define STInternalAssertEqualGLKVectorsOrMatrices(a1, a2, accuracy, description, ...) \ +do { \ + @try { \ + if (strcmp(@encode(__typeof__(a1)), @encode(__typeof__(a2)))) { \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"Type mismatch -- %@", STComposeString(description, ##__VA_ARGS__)]]; \ + } else { \ + __typeof__(a1) a1GLKValue = (a1); \ + __typeof__(a2) a2GLKValue = (a2); \ + __typeof__(accuracy) accuracyvalue = (accuracy); \ + float *a1FloatValue = ((float*)&a1GLKValue); \ + float *a2FloatValue = ((float*)&a2GLKValue); \ + for (size_t i = 0; i < sizeof(__typeof__(a1)) / sizeof(float); ++i) { \ + float a1value = a1FloatValue[i]; \ + float a2value = a2FloatValue[i]; \ + if (STAbsoluteDifference(a1value, a2value) > accuracyvalue) { \ + NSMutableArray *strings = [NSMutableArray arrayWithCapacity:sizeof(a1) / sizeof(float)]; \ + NSString *string; \ + for (size_t j = 0; j < sizeof(__typeof__(a1)) / sizeof(float); ++j) { \ + string = [NSString stringWithFormat:@"(%0.3f == %0.3f)", a1FloatValue[j], a2FloatValue[j]]; \ + [strings addObject:string]; \ + } \ + string = [strings componentsJoinedByString:@", "]; \ + NSString *desc = STComposeString(description, ##__VA_ARGS__); \ + desc = [NSString stringWithFormat:@"%@ With Accuracy %0.3f: %@", string, (float)accuracyvalue, desc]; \ + [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", desc]]; \ + break; \ + } \ + } \ + } \ + } \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == (%s)", #a1, #a2] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) -#if GTM_IPHONE_SDK +#define STAssertEqualGLKVectors(a1, a2, accuracy, description, ...) \ + STInternalAssertEqualGLKVectorsOrMatrices(a1, a2, accuracy, description, ##__VA_ARGS__) + +#define STAssertEqualGLKMatrices(a1, a2, accuracy, description, ...) \ + STInternalAssertEqualGLKVectorsOrMatrices(a1, a2, accuracy, description, ##__VA_ARGS__) + +#define STAssertEqualGLKQuaternions(a1, a2, accuracy, description, ...) \ + STInternalAssertEqualGLKVectorsOrMatrices(a1, a2, accuracy, description, ##__VA_ARGS__) + +#if GTM_IPHONE_SDK && !GTM_IPHONE_USE_SENTEST +// When not using the Xcode provided version, define everything ourselves. // SENTE_BEGIN -/*" Generates a failure when !{ [a1 isEqualTo:a2] } is false - (or one is nil and the other is not). +/*" Generates a failure when !{ [a1 isEqualTo:a2] } is false + (or one is nil and the other is not). _{a1 The object on the left.} _{a2 The object on the right.} _{description A format string as in the printf() function. Can be nil or @@ -475,26 +550,26 @@ do { \ "*/ #define STAssertEqualObjects(a1, a2, description, ...) \ do { \ - @try {\ + @try { \ id a1value = (a1); \ id a2value = (a2); \ if (a1value == a2value) continue; \ - if ( (@encode(__typeof__(a1value)) == @encode(id)) && \ - (@encode(__typeof__(a2value)) == @encode(id)) && \ - [(id)a1value isEqual: (id)a2value] ) continue; \ - [self failWithException:[NSException failureInEqualityBetweenObject: a1value \ - andObject: a2value \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + if ((strcmp(@encode(__typeof__(a1value)), @encode(id)) == 0) && \ + (strcmp(@encode(__typeof__(a2value)), @encode(id)) == 0) && \ + [(id)a1value isEqual:(id)a2value]) continue; \ + [self failWithException:[NSException failureInEqualityBetweenObject:a1value \ + andObject:a2value \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == (%s)", #a1, #a2] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) @@ -508,40 +583,40 @@ do { \ "*/ #define STAssertEquals(a1, a2, description, ...) \ do { \ - @try {\ - if (@encode(__typeof__(a1)) != @encode(__typeof__(a2))) { \ + @try { \ + if (strcmp(@encode(__typeof__(a1)), @encode(__typeof__(a2)))) { \ [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:[[NSString stringWithFormat:@"Type mismatch (%@/%@) -- ",@encode(__typeof__(a1)),@encode(__typeof__(a2))] stringByAppendingString:STComposeString(description, ##__VA_ARGS__)]]]; \ + withDescription:@"Type mismatch -- %@", STComposeString(description, ##__VA_ARGS__)]]; \ } else { \ __typeof__(a1) a1value = (a1); \ __typeof__(a2) a2value = (a2); \ - NSValue *a1encoded = [NSValue value:&a1value withObjCType: @encode(__typeof__(a1))]; \ - NSValue *a2encoded = [NSValue value:&a2value withObjCType: @encode(__typeof__(a2))]; \ + NSValue *a1encoded = [NSValue value:&a1value withObjCType:@encode(__typeof__(a1))]; \ + NSValue *a2encoded = [NSValue value:&a2value withObjCType:@encode(__typeof__(a2))]; \ if (![a1encoded isEqualToValue:a2encoded]) { \ - [self failWithException:[NSException failureInEqualityBetweenValue: a1encoded \ - andValue: a2encoded \ - withAccuracy: nil \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + [self failWithException:[NSException failureInEqualityBetweenValue:a1encoded \ + andValue:a2encoded \ + withAccuracy:nil \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ } \ } \ } \ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == (%s)", #a1, #a2] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) #define STAbsoluteDifference(left,right) (MAX(left,right)-MIN(left,right)) -/*" Generates a failure when a1 is not equal to a2 within + or - accuracy is false. - This test is for scalars such as floats and doubles where small differences +/*" Generates a failure when a1 is not equal to a2 within + or - accuracy is false. + This test is for scalars such as floats and doubles where small differences could make these items not exactly equal, but also works for all scalars. _{a1 The scalar on the left.} _{a2 The scalar on the right.} @@ -554,11 +629,11 @@ do { \ #define STAssertEqualsWithAccuracy(a1, a2, accuracy, description, ...) \ do { \ - @try {\ - if (@encode(__typeof__(a1)) != @encode(__typeof__(a2))) { \ + @try { \ + if (strcmp(@encode(__typeof__(a1)), @encode(__typeof__(a2)))) { \ [self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:[[[NSString stringWithFormat:@"Type mismatch (%@/%@) -- ",@encode(__typeof__(a1)),@encode(__typeof__(a2))] stringByAppendingString:STComposeString(description, ##__VA_ARGS__)]]]; \ + withDescription:@"Type mismatch -- %@", STComposeString(description, ##__VA_ARGS__)]]; \ } else { \ __typeof__(a1) a1value = (a1); \ __typeof__(a2) a2value = (a2); \ @@ -567,35 +642,35 @@ do { \ NSValue *a1encoded = [NSValue value:&a1value withObjCType:@encode(__typeof__(a1))]; \ NSValue *a2encoded = [NSValue value:&a2value withObjCType:@encode(__typeof__(a2))]; \ NSValue *accuracyencoded = [NSValue value:&accuracyvalue withObjCType:@encode(__typeof__(accuracy))]; \ - [self failWithException:[NSException failureInEqualityBetweenValue: a1encoded \ - andValue: a2encoded \ - withAccuracy: accuracyencoded \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + [self failWithException:[NSException failureInEqualityBetweenValue:a1encoded \ + andValue:a2encoded \ + withAccuracy:accuracyencoded \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ } \ } \ } \ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == (%s)", #a1, #a2] \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == (%s)", #a1, #a2] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) -/*" Generates a failure unconditionally. +/*" Generates a failure unconditionally. _{description A format string as in the printf() function. Can be nil or an empty string but must be present.} _{... A variable number of arguments to the format string. Can be absent.} "*/ #define STFail(description, ...) \ -[self failWithException:[NSException failureInFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]] +[self failWithException:[NSException failureInFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]] @@ -607,25 +682,25 @@ do { \ "*/ #define STAssertNil(a1, description, ...) \ do { \ - @try {\ + @try { \ id a1value = (a1); \ if (a1value != nil) { \ - NSString *_a1 = [NSString stringWithUTF8String: #a1]; \ + NSString *_a1 = [NSString stringWithUTF8String:#a1]; \ NSString *_expression = [NSString stringWithFormat:@"((%@) == nil)", _a1]; \ - [self failWithException:[NSException failureInCondition: _expression \ - isTrue: NO \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + [self failWithException:[NSException failureInCondition:_expression \ + isTrue:NO \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ } \ - }\ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) == nil fails", #a1] \ + } \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) == nil fails", #a1] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) @@ -637,29 +712,29 @@ do { \ "*/ #define STAssertNotNil(a1, description, ...) \ do { \ - @try {\ + @try { \ id a1value = (a1); \ if (a1value == nil) { \ - NSString *_a1 = [NSString stringWithUTF8String: #a1]; \ + NSString *_a1 = [NSString stringWithUTF8String:#a1]; \ NSString *_expression = [NSString stringWithFormat:@"((%@) != nil)", _a1]; \ - [self failWithException:[NSException failureInCondition: _expression \ - isTrue: NO \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + [self failWithException:[NSException failureInCondition:_expression \ + isTrue:NO \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ } \ - }\ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) != nil fails", #a1] \ + } \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) != nil fails", #a1] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while(0) -/*" Generates a failure when expression evaluates to false. +/*" Generates a failure when expression evaluates to false. _{expr The expression that is tested.} _{description A format string as in the printf() function. Can be nil or an empty string but must be present.} @@ -667,20 +742,20 @@ do { \ "*/ #define STAssertTrue(expr, description, ...) \ do { \ - BOOL _evaluatedExpression = (expr);\ - if (!_evaluatedExpression) {\ - NSString *_expression = [NSString stringWithUTF8String: #expr];\ - [self failWithException:[NSException failureInCondition: _expression \ - isTrue: NO \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + BOOL _evaluatedExpression = (expr); \ + if (!_evaluatedExpression) { \ + NSString *_expression = [NSString stringWithUTF8String:#expr]; \ + [self failWithException:[NSException failureInCondition:_expression \ + isTrue:NO \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ } \ } while (0) -/*" Generates a failure when expression evaluates to false and in addition will - generate error messages if an exception is encountered. +/*" Generates a failure when expression evaluates to false and in addition will + generate error messages if an exception is encountered. _{expr The expression that is tested.} _{description A format string as in the printf() function. Can be nil or an empty string but must be present.} @@ -688,28 +763,28 @@ do { \ "*/ #define STAssertTrueNoThrow(expr, description, ...) \ do { \ - @try {\ - BOOL _evaluatedExpression = (expr);\ - if (!_evaluatedExpression) {\ - NSString *_expression = [NSString stringWithUTF8String: #expr];\ - [self failWithException:[NSException failureInCondition: _expression \ - isTrue: NO \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + @try { \ + BOOL _evaluatedExpression = (expr); \ + if (!_evaluatedExpression) { \ + NSString *_expression = [NSString stringWithUTF8String:#expr]; \ + [self failWithException:[NSException failureInCondition:_expression \ + isTrue:NO \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ } \ } \ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"(%s) ", #expr] \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"(%s) ", #expr] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while (0) -/*" Generates a failure when the expression evaluates to true. +/*" Generates a failure when the expression evaluates to true. _{expr The expression that is tested.} _{description A format string as in the printf() function. Can be nil or an empty string but must be present.} @@ -717,19 +792,19 @@ do { \ "*/ #define STAssertFalse(expr, description, ...) \ do { \ - BOOL _evaluatedExpression = (expr);\ - if (_evaluatedExpression) {\ - NSString *_expression = [NSString stringWithUTF8String: #expr];\ - [self failWithException:[NSException failureInCondition: _expression \ - isTrue: YES \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + BOOL _evaluatedExpression = (expr); \ + if (_evaluatedExpression) { \ + NSString *_expression = [NSString stringWithUTF8String:#expr]; \ + [self failWithException:[NSException failureInCondition:_expression \ + isTrue:YES \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ } \ } while (0) -/*" Generates a failure when the expression evaluates to true and in addition +/*" Generates a failure when the expression evaluates to true and in addition will generate error messages if an exception is encountered. _{expr The expression that is tested.} _{description A format string as in the printf() function. Can be nil or @@ -738,28 +813,28 @@ do { \ "*/ #define STAssertFalseNoThrow(expr, description, ...) \ do { \ - @try {\ - BOOL _evaluatedExpression = (expr);\ - if (_evaluatedExpression) {\ - NSString *_expression = [NSString stringWithUTF8String: #expr];\ - [self failWithException:[NSException failureInCondition: _expression \ - isTrue: YES \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + @try { \ + BOOL _evaluatedExpression = (expr); \ + if (_evaluatedExpression) { \ + NSString *_expression = [NSString stringWithUTF8String:#expr]; \ + [self failWithException:[NSException failureInCondition:_expression \ + isTrue:YES \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ } \ } \ - @catch (id anException) {\ - [self failWithException:[NSException failureInRaise:[NSString stringWithFormat: @"!(%s) ", #expr] \ + @catch (id anException) { \ + [self failWithException:[NSException failureInRaise:[NSString stringWithFormat:@"!(%s) ", #expr] \ exception:anException \ inFile:[NSString stringWithUTF8String:__FILE__] \ atLine:__LINE__ \ - withDescription:STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while (0) -/*" Generates a failure when expression does not throw an exception. +/*" Generates a failure when expression does not throw an exception. _{expression The expression that is evaluated.} _{description A format string as in the printf() function. Can be nil or an empty string but must be present.} @@ -768,21 +843,21 @@ do { \ #define STAssertThrows(expr, description, ...) \ do { \ @try { \ - (expr);\ + (expr); \ } \ @catch (id anException) { \ continue; \ - }\ - [self failWithException:[NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ - exception: nil \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + [self failWithException:[NSException failureInRaise:[NSString stringWithUTF8String:#expr] \ + exception:nil \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ } while (0) -/*" Generates a failure when expression does not throw an exception of a - specific class. +/*" Generates a failure when expression does not throw an exception of a + specific class. _{expression The expression that is evaluated.} _{specificException The specified class of the exception.} _{description A format string as in the printf() function. Can be nil or @@ -792,32 +867,32 @@ do { \ #define STAssertThrowsSpecific(expr, specificException, description, ...) \ do { \ @try { \ - (expr);\ + (expr); \ } \ @catch (specificException *anException) { \ continue; \ - }\ - @catch (id anException) {\ - NSString *_descrip = STComposeString(@"(Expected exception: %@) %@", NSStringFromClass([specificException class]), description);\ - [self failWithException:[NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ - exception: anException \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(_descrip, ##__VA_ARGS__)]]; \ + } \ + @catch (id anException) { \ + NSString *_descrip = STComposeString(@"(Expected exception: %@) %@", NSStringFromClass([specificException class]), description); \ + [self failWithException:[NSException failureInRaise:[NSString stringWithUTF8String:#expr] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(_descrip, ##__VA_ARGS__)]]; \ continue; \ - }\ - NSString *_descrip = STComposeString(@"(Expected exception: %@) %@", NSStringFromClass([specificException class]), description);\ - [self failWithException:[NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ - exception: nil \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(_descrip, ##__VA_ARGS__)]]; \ + } \ + NSString *_descrip = STComposeString(@"(Expected exception: %@) %@", NSStringFromClass([specificException class]), description); \ + [self failWithException:[NSException failureInRaise:[NSString stringWithUTF8String:#expr] \ + exception:nil \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(_descrip, ##__VA_ARGS__)]]; \ } while (0) -/*" Generates a failure when expression does not throw an exception of a +/*" Generates a failure when expression does not throw an exception of a specific class with a specific name. Useful for those frameworks like - AppKit or Foundation that throw generic NSException w/specific names + AppKit or Foundation that throw generic NSException w/specific names (NSInvalidArgumentException, etc). _{expression The expression that is evaluated.} _{specificException The specified class of the exception.} @@ -830,40 +905,40 @@ do { \ #define STAssertThrowsSpecificNamed(expr, specificException, aName, description, ...) \ do { \ @try { \ - (expr);\ + (expr); \ } \ @catch (specificException *anException) { \ - if ([aName isEqualToString: [anException name]]) continue; \ - NSString *_descrip = STComposeString(@"(Expected exception: %@ (name: %@)) %@", NSStringFromClass([specificException class]), aName, description);\ + if ([aName isEqualToString:[anException name]]) continue; \ + NSString *_descrip = STComposeString(@"(Expected exception: %@ (name: %@)) %@", NSStringFromClass([specificException class]), aName, description); \ [self failWithException: \ - [NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ - exception: anException \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(_descrip, ##__VA_ARGS__)]]; \ + [NSException failureInRaise:[NSString stringWithUTF8String:#expr] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(_descrip, ##__VA_ARGS__)]]; \ continue; \ - }\ - @catch (id anException) {\ - NSString *_descrip = STComposeString(@"(Expected exception: %@) %@", NSStringFromClass([specificException class]), description);\ + } \ + @catch (id anException) { \ + NSString *_descrip = STComposeString(@"(Expected exception: %@) %@", NSStringFromClass([specificException class]), description); \ [self failWithException: \ - [NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ - exception: anException \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(_descrip, ##__VA_ARGS__)]]; \ + [NSException failureInRaise:[NSString stringWithUTF8String:#expr] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(_descrip, ##__VA_ARGS__)]]; \ continue; \ - }\ - NSString *_descrip = STComposeString(@"(Expected exception: %@) %@", NSStringFromClass([specificException class]), description);\ + } \ + NSString *_descrip = STComposeString(@"(Expected exception: %@) %@", NSStringFromClass([specificException class]), description); \ [self failWithException: \ - [NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ - exception: nil \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(_descrip, ##__VA_ARGS__)]]; \ + [NSException failureInRaise:[NSString stringWithUTF8String:#expr] \ + exception:nil \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(_descrip, ##__VA_ARGS__)]]; \ } while (0) -/*" Generates a failure when expression does throw an exception. +/*" Generates a failure when expression does throw an exception. _{expression The expression that is evaluated.} _{description A format string as in the printf() function. Can be nil or an empty string but must be present.} @@ -872,15 +947,15 @@ do { \ #define STAssertNoThrow(expr, description, ...) \ do { \ @try { \ - (expr);\ + (expr); \ } \ @catch (id anException) { \ - [self failWithException:[NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ - exception: anException \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ - }\ + [self failWithException:[NSException failureInRaise:[NSString stringWithUTF8String:#expr] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ } while (0) @@ -895,24 +970,24 @@ do { \ #define STAssertNoThrowSpecific(expr, specificException, description, ...) \ do { \ @try { \ - (expr);\ + (expr); \ } \ @catch (specificException *anException) { \ - [self failWithException:[NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ - exception: anException \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(description, ##__VA_ARGS__)]]; \ - }\ - @catch (id anythingElse) {\ + [self failWithException:[NSException failureInRaise:[NSString stringWithUTF8String:#expr] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(description, ##__VA_ARGS__)]]; \ + } \ + @catch (id anythingElse) { \ ; \ - }\ + } \ } while (0) -/*" Generates a failure when expression does throw an exception of a +/*" Generates a failure when expression does throw an exception of a specific class with a specific name. Useful for those frameworks like - AppKit or Foundation that throw generic NSException w/specific names + AppKit or Foundation that throw generic NSException w/specific names (NSInvalidArgumentException, etc). _{expression The expression that is evaluated.} _{specificException The specified class of the exception.} @@ -925,69 +1000,77 @@ do { \ #define STAssertNoThrowSpecificNamed(expr, specificException, aName, description, ...) \ do { \ @try { \ - (expr);\ + (expr); \ } \ @catch (specificException *anException) { \ - if ([aName isEqualToString: [anException name]]) { \ - NSString *_descrip = STComposeString(@"(Expected exception: %@ (name: %@)) %@", NSStringFromClass([specificException class]), aName, description);\ + if ([aName isEqualToString:[anException name]]) { \ + NSString *_descrip = STComposeString(@"(Expected exception: %@ (name: %@)) %@", NSStringFromClass([specificException class]), aName, description); \ [self failWithException: \ - [NSException failureInRaise: [NSString stringWithUTF8String:#expr] \ - exception: anException \ - inFile: [NSString stringWithUTF8String:__FILE__] \ - atLine: __LINE__ \ - withDescription: STComposeString(_descrip, ##__VA_ARGS__)]]; \ + [NSException failureInRaise:[NSString stringWithUTF8String:#expr] \ + exception:anException \ + inFile:[NSString stringWithUTF8String:__FILE__] \ + atLine:__LINE__ \ + withDescription:@"%@", STComposeString(_descrip, ##__VA_ARGS__)]]; \ } \ continue; \ - }\ - @catch (id anythingElse) {\ + } \ + @catch (id anythingElse) { \ ; \ - }\ + } \ } while (0) @interface NSException (GTMSenTestAdditions) -+ (NSException *)failureInFile:(NSString *)filename - atLine:(int)lineNumber - withDescription:(NSString *)formatString, ...; -+ (NSException *)failureInCondition:(NSString *)condition - isTrue:(BOOL)isTrue - inFile:(NSString *)filename - atLine:(int)lineNumber - withDescription:(NSString *)formatString, ...; ++ (NSException *)failureInFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ... NS_FORMAT_FUNCTION(3, 4); ++ (NSException *)failureInCondition:(NSString *)condition + isTrue:(BOOL)isTrue + inFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ... NS_FORMAT_FUNCTION(5, 6); + (NSException *)failureInEqualityBetweenObject:(id)left andObject:(id)right inFile:(NSString *)filename atLine:(int)lineNumber - withDescription:(NSString *)formatString, ...; -+ (NSException *)failureInEqualityBetweenValue:(NSValue *)left - andValue:(NSValue *)right - withAccuracy:(NSValue *)accuracy - inFile:(NSString *)filename + withDescription:(NSString *)formatString, ... NS_FORMAT_FUNCTION(5, 6); ++ (NSException *)failureInEqualityBetweenValue:(NSValue *)left + andValue:(NSValue *)right + withAccuracy:(NSValue *)accuracy + inFile:(NSString *)filename atLine:(int) ineNumber - withDescription:(NSString *)formatString, ...; -+ (NSException *)failureInRaise:(NSString *)expression - inFile:(NSString *)filename + withDescription:(NSString *)formatString, ... NS_FORMAT_FUNCTION(6, 7); ++ (NSException *)failureInRaise:(NSString *)expression + inFile:(NSString *)filename atLine:(int)lineNumber - withDescription:(NSString *)formatString, ...; -+ (NSException *)failureInRaise:(NSString *)expression - exception:(NSException *)exception - inFile:(NSString *)filename - atLine:(int)lineNumber - withDescription:(NSString *)formatString, ...; + withDescription:(NSString *)formatString, ... NS_FORMAT_FUNCTION(4, 5); ++ (NSException *)failureInRaise:(NSString *)expression + exception:(NSException *)exception + inFile:(NSString *)filename + atLine:(int)lineNumber + withDescription:(NSString *)formatString, ... NS_FORMAT_FUNCTION(5, 6); @end // SENTE_END -@interface SenTestCase : NSObject { - SEL currentSelector_; -} - +@protocol SenTestCase ++ (id)testCaseWithInvocation:(NSInvocation *)anInvocation; +- (id)initWithInvocation:(NSInvocation *)anInvocation; - (void)setUp; - (void)invokeTest; - (void)tearDown; -- (void)performTest:(SEL)sel; +- (void)performTest; - (void)failWithException:(NSException*)exception; +- (NSInvocation *)invocation; +- (SEL)selector; ++ (NSArray *)testInvocations; +@end + +@interface SenTestCase : NSObject<SenTestCase> { + @private + NSInvocation *invocation_; +} @end GTM_EXTERN NSString *const SenTestFailureException; @@ -995,10 +1078,33 @@ GTM_EXTERN NSString *const SenTestFailureException; GTM_EXTERN NSString *const SenTestFilenameKey; GTM_EXTERN NSString *const SenTestLineNumberKey; -#endif // GTM_IPHONE_SDK +#endif // GTM_IPHONE_SDK && !GTM_IPHONE_USE_SENTEST // All unittest cases in GTM should inherit from GTMTestCase. It makes sure // to set up our logging system correctly to verify logging calls. // See GTMUnitTestDevLog.h for details @interface GTMTestCase : SenTestCase + +// Returns YES if this is an abstract testCase class as opposed to a concrete +// testCase class that you want tests run against. SenTestCase is not designed +// out of the box to handle an abstract class hierarchy descending from it with +// some concrete subclasses. In some cases we want all the "concrete" +// subclasses of an abstract subclass of SenTestCase to run a test, but we don't +// want that test to be run against an instance of an abstract subclass itself. +// By returning "YES" here, the tests defined by this class won't be run against +// an instance of this class. As an example class hierarchy: +// +// FooExtensionTestCase +// GTMTestCase <- ExtensionAbstractTestCase < +// BarExtensionTestCase +// +// So FooExtensionTestCase and BarExtensionTestCase inherit from +// ExtensionAbstractTestCase (and probably FooExtension and BarExtension inherit +// from a class named Extension). We want the tests in ExtensionAbstractTestCase +// to be run as part of FooExtensionTestCase and BarExtensionTestCase, but we +// don't want them run against ExtensionAbstractTestCase. The default +// implementation checks to see if the name of the class contains the word +// "AbstractTest" (case sensitive). ++ (BOOL)isAbstractTestCase; + @end diff --git a/src/common/mac/testing/GTMSenTestCase.m b/src/common/mac/testing/GTMSenTestCase.m index 99b9db07..5607c316 100644 --- a/src/common/mac/testing/GTMSenTestCase.m +++ b/src/common/mac/testing/GTMSenTestCase.m @@ -17,13 +17,20 @@ // #import "GTMSenTestCase.h" + #import <unistd.h> +#if GTM_IPHONE_SIMULATOR +#import <objc/message.h> +#endif + +#import "GTMObjC2Runtime.h" +#import "GTMUnitTestDevLog.h" #if !GTM_IPHONE_SDK #import "GTMGarbageCollection.h" #endif // !GTM_IPHONE_SDK -#if GTM_IPHONE_SDK +#if GTM_IPHONE_SDK && !GTM_IPHONE_USE_SENTEST #import <stdarg.h> @interface NSException (GTMSenTestPrivateAdditions) @@ -84,7 +91,7 @@ } NSString *reason = [NSString stringWithFormat:@"'%@' should be %s. %@", - condition, isTrue ? "TRUE" : "FALSE", testDescription]; + condition, isTrue ? "false" : "true", testDescription]; return [self failureInFile:filename atLine:lineNumber reason:reason]; } @@ -213,6 +220,22 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; @end @implementation SenTestCase ++ (id)testCaseWithInvocation:(NSInvocation *)anInvocation { + return [[[self alloc] initWithInvocation:anInvocation] autorelease]; +} + +- (id)initWithInvocation:(NSInvocation *)anInvocation { + if ((self = [super init])) { + invocation_ = [anInvocation retain]; + } + return self; +} + +- (void)dealloc { + [invocation_ release]; + [super dealloc]; +} + - (void)failWithException:(NSException*)exception { [exception raise]; } @@ -220,17 +243,24 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; - (void)setUp { } -- (void)performTest:(SEL)sel { - currentSelector_ = sel; +- (void)performTest { @try { [self invokeTest]; } @catch (NSException *exception) { [[self class] printException:exception - fromTestName:NSStringFromSelector(sel)]; + fromTestName:NSStringFromSelector([self selector])]; [exception raise]; } } +- (NSInvocation *)invocation { + return invocation_; +} + +- (SEL)selector { + return [invocation_ selector]; +} + + (void)printException:(NSException *)exception fromTestName:(NSString *)name { NSDictionary *userInfo = [exception userInfo]; NSString *filename = [userInfo objectForKey:SenTestFilenameKey]; @@ -261,7 +291,17 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; @try { [self setUp]; @try { - [self performSelector:currentSelector_]; + NSInvocation *invocation = [self invocation]; +#if GTM_IPHONE_SIMULATOR + // We don't call [invocation invokeWithTarget:self]; because of + // Radar 8081169: NSInvalidArgumentException can't be caught + // It turns out that on iOS4 (and 3.2) exceptions thrown inside an + // [invocation invoke] on the simulator cannot be caught. + // http://openradar.appspot.com/8081169 + objc_msgSend(self, [invocation selector]); +#else + [invocation invokeWithTarget:self]; +#endif } @catch (NSException *exception) { e = [exception retain]; } @@ -284,15 +324,84 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; - (NSString *)description { // This matches the description OCUnit would return to you - return [NSString stringWithFormat:@"-[%@ %@]", [self class], - NSStringFromSelector(currentSelector_)]; + return [NSString stringWithFormat:@"-[%@ %@]", [self class], + NSStringFromSelector([self selector])]; +} + +// Used for sorting methods below +static int MethodSort(id a, id b, void *context) { + NSInvocation *invocationA = a; + NSInvocation *invocationB = b; + const char *nameA = sel_getName([invocationA selector]); + const char *nameB = sel_getName([invocationB selector]); + return strcmp(nameA, nameB); } + + ++ (NSArray *)testInvocations { + NSMutableArray *invocations = nil; + // Need to walk all the way up the parent classes collecting methods (in case + // a test is a subclass of another test). + Class senTestCaseClass = [SenTestCase class]; + for (Class currentClass = self; + currentClass && (currentClass != senTestCaseClass); + currentClass = class_getSuperclass(currentClass)) { + unsigned int methodCount; + Method *methods = class_copyMethodList(currentClass, &methodCount); + if (methods) { + // This handles disposing of methods for us even if an exception should fly. + [NSData dataWithBytesNoCopy:methods + length:sizeof(Method) * methodCount]; + if (!invocations) { + invocations = [NSMutableArray arrayWithCapacity:methodCount]; + } + for (size_t i = 0; i < methodCount; ++i) { + Method currMethod = methods[i]; + SEL sel = method_getName(currMethod); + char *returnType = NULL; + const char *name = sel_getName(sel); + // If it starts with test, takes 2 args (target and sel) and returns + // void run it. + if (strstr(name, "test") == name) { + returnType = method_copyReturnType(currMethod); + if (returnType) { + // This handles disposing of returnType for us even if an + // exception should fly. Length +1 for the terminator, not that + // the length really matters here, as we never reference inside + // the data block. + [NSData dataWithBytesNoCopy:returnType + length:strlen(returnType) + 1]; + } + } + // TODO: If a test class is a subclass of another, and they reuse the + // same selector name (ie-subclass overrides it), this current loop + // and test here will cause cause it to get invoked twice. To fix this + // the selector would have to be checked against all the ones already + // added, so it only gets done once. + if (returnType // True if name starts with "test" + && strcmp(returnType, @encode(void)) == 0 + && method_getNumberOfArguments(currMethod) == 2) { + NSMethodSignature *sig = [self instanceMethodSignatureForSelector:sel]; + NSInvocation *invocation + = [NSInvocation invocationWithMethodSignature:sig]; + [invocation setSelector:sel]; + [invocations addObject:invocation]; + } + } + } + } + // Match SenTestKit and run everything in alphbetical order. + [invocations sortUsingFunction:MethodSort context:nil]; + return invocations; +} + @end -#endif // GTM_IPHONE_SDK +#endif // GTM_IPHONE_SDK && !GTM_IPHONE_USE_SENTEST @implementation GTMTestCase : SenTestCase - (void)invokeTest { + NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init]; Class devLogClass = NSClassFromString(@"GTMUnitTestDevLog"); if (devLogClass) { [devLogClass performSelector:@selector(enableTracking)]; @@ -304,19 +413,34 @@ NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey"; [devLogClass performSelector:@selector(verifyNoMoreLogsExpected)]; [devLogClass performSelector:@selector(disableTracking)]; } + [localPool drain]; } + ++ (BOOL)isAbstractTestCase { + NSString *name = NSStringFromClass(self); + return [name rangeOfString:@"AbstractTest"].location != NSNotFound; +} + ++ (NSArray *)testInvocations { + NSArray *invocations = nil; + if (![self isAbstractTestCase]) { + invocations = [super testInvocations]; + } + return invocations; +} + @end // Leak detection -#if !GTM_IPHONE_DEVICE +#if !GTM_IPHONE_DEVICE && !GTM_SUPPRESS_RUN_LEAKS_HOOK // Don't want to get leaks on the iPhone Device as the device doesn't // have 'leaks'. The simulator does though. // COV_NF_START // We don't have leak checking on by default, so this won't be hit. static void _GTMRunLeaks(void) { - // This is an atexit handler. It runs leaks for us to check if we are - // leaking anything in our tests. + // This is an atexit handler. It runs leaks for us to check if we are + // leaking anything in our tests. const char* cExclusionsEnv = getenv("GTM_LEAKS_SYMBOLS_TO_IGNORE"); NSMutableString *exclusions = [NSMutableString string]; if (cExclusionsEnv) { @@ -329,13 +453,21 @@ static void _GTMRunLeaks(void) { [exclusions appendFormat:@"-exclude \"%@\" ", exclusion]; } } - NSString *string - = [NSString stringWithFormat:@"/usr/bin/leaks %@%d" - @"| /usr/bin/sed -e 's/Leak: /Leaks:0: warning: Leak /'", + // Clearing out DYLD_ROOT_PATH because iPhone Simulator framework libraries + // are different from regular OS X libraries and leaks will fail to run + // because of missing symbols. Also capturing the output of leaks and then + // pipe rather than a direct pipe, because otherwise if leaks failed, + // the system() call will still be successful. Bug: + // http://code.google.com/p/google-toolbox-for-mac/issues/detail?id=56 + NSString *string + = [NSString stringWithFormat: + @"LeakOut=`DYLD_ROOT_PATH='' /usr/bin/leaks %@%d` &&" + @"echo \"$LeakOut\"|/usr/bin/sed -e 's/Leak: /Leaks:0: warning: Leak /'", exclusions, getpid()]; int ret = system([string UTF8String]); if (ret) { - fprintf(stderr, "%s:%d: Error: Unable to run leaks. 'system' returned: %d", + fprintf(stderr, + "%s:%d: Error: Unable to run leaks. 'system' returned: %d\n", __FILE__, __LINE__, ret); fflush(stderr); } @@ -355,12 +487,14 @@ static __attribute__((constructor)) void _GTMInstallLeaks(void) { fprintf(stderr, "Leak Checking Enabled\n"); fflush(stderr); int ret = atexit(&_GTMRunLeaks); - _GTMDevAssert(ret == 0, - @"Unable to install _GTMRunLeaks as an atexit handler (%d)", + // To avoid unused variable warning when _GTMDevAssert is stripped. + (void)ret; + _GTMDevAssert(ret == 0, + @"Unable to install _GTMRunLeaks as an atexit handler (%d)", errno); // COV_NF_END - } + } } } -#endif // !GTM_IPHONE_DEVICE +#endif // !GTM_IPHONE_DEVICE && !GTM_SUPPRESS_RUN_LEAKS_HOOK |