NSDate *startDate;
- unsigned classesWithTests;
- unsigned classesWithoutTests;
- unsigned methodsWithTests;
unsigned testsRun;
unsigned testsPassed;
unsigned testsFailed;
BOOL expectFailures;
//! low-level exception handling inversion: should only be used during WOTest self-testing
+ //! due to problems with the low-level exception handling this API may eventually be deprecated and removed
unsigned lowLevelExceptionsExpected;
unsigned lowLevelExceptionsUnexpected;
BOOL expectLowLevelExceptions;
- //! Optionally refrain from handling low level exceptions
- BOOL handlesLowLevelExceptions;
-
//! Internal use only: used for keeping track of whether low-level exception handlers have been installed or not
//! Necessary because tested methods may change the low-level-exception-catching status mid-test
BOOL lowLevelExceptionHandlerInstalled;
/*! \endgroup */
#pragma mark -
-#pragma mark Accessors
+#pragma mark Properties
-//! \name Accessors
+//! \name Properties
//! \startgroup
-- (NSDate *)startDate;
-- (unsigned)classesWithTests;
-- (unsigned)classesWithoutTests;
-- (unsigned)methodsWithTests;
-- (unsigned)testsRun;
-- (unsigned)testsPassed;
-- (unsigned)testsFailed;
+// BUG: Objective-C 2.0 bug, can't use "readonly" pattern, see implementation file for more details
+@property(/*readonly,*/ copy) NSDate *startDate;
+@property/*(readonly)*/ unsigned testsRun;
+@property/*(readonly)*/ unsigned testsPassed;
+@property/*(readonly)*/ unsigned testsFailed;
// TODO: need sense inversion here as well (for uncaught exceptions) for self-testing purposes
-- (unsigned)uncaughtExceptions;
-
-- (unsigned)testsFailedExpected;
-- (unsigned)testsPassedUnexpected;
-- (BOOL)expectFailures;
-- (void)setExpectFailures:(BOOL)aValue;
-
-- (unsigned)lowLevelExceptionsExpected;
-- (unsigned)lowLevelExceptionsUnexpected;
-- (BOOL)expectLowLevelExceptions;
-- (void)setExpectLowLevelExceptions:(BOOL)aValue;
-
-- (BOOL)handlesLowLevelExceptions;
-- (void)setHandlesLowLevelExceptions:(BOOL)aValue;
+@property/*(readonly)*/ unsigned uncaughtExceptions;
+@property/*(readonly)*/ unsigned testsFailedExpected;
+@property/*(readonly)*/ unsigned testsPassedUnexpected;
-- (unsigned)verbosity;
-- (void)setVerbosity:(unsigned)aVerbosity;
+@property BOOL expectFailures;
-- (unsigned)trimInitialPathComponents;
-- (void)setTrimInitialPathComponents:(unsigned)aTrimInitialPathComponents;
+@property/*(readonly)*/ unsigned lowLevelExceptionsExpected;
+@property/*(readonly)*/ unsigned lowLevelExceptionsUnexpected;
-- (NSString *)lastReportedFile;
-- (void)setLastReportedFile:(NSString *)aLastReportedFile;
+@property BOOL expectLowLevelExceptions;
-- (BOOL)warnsAboutSignComparisons;
-- (void)setWarnsAboutSignComparisons:(BOOL)aWarnsAboutSignComparisons;
+@property unsigned verbosity;
+@property unsigned trimInitialPathComponents;
+@property(/*readonly,*/ copy) NSString *lastReportedFile;
+@property/*(readonly)*/ int lastReportedLine;
+@property BOOL warnsAboutSignComparisons;
//! \endgroup
-@end
\ No newline at end of file
+@end
/*! Check to see that the start date has been recorded. If it has not, record it. */
- (void)checkStartDate;
-- (void)setStartDate:(NSDate *)aStartDate;
-
/*! Helper method for optionally trimming path names before printing them to the console. */
- (NSString *)trimmedPath:(char *)path;
+#pragma mark -
+#pragma mark Properties
+
+//! \name Properties
+//! Public properties previously declared readonly have a private readwrite implementation internally to the class.
+//! \startgroup
+
+// BUG: Objective-C 2.0 bug; the documentation would suggest that redeclaring all the attributes is not necessary
+// "You can re-declare properties in a subclass, and you can repeat properties' attributes in whole or in part"
+// "The same holds true for properties declared in a category"
+// "the property's attributes must only be repeated in whole or part"
+// but warns:
+// no 'assign', 'retain', or 'copy' attribute is specified - 'assign' is assumed
+// furthermore, it appears that the readwrite override does not actually cause a setter to be synthesized further down
+// despite what the docs say:
+// "In the case of a category redeclaration, that the property was redeclared prior any @synthesize statement will cause the setter to be synthesized"
+// we get unrecognized selector exceptions:
+// *** -[WOTest setTestsFailedExpected:]: unrecognized selector sent to instance 0x102eee0)
+// so for now must abandon the pattern
+//@property(readwrite, assign) NSDate *startDate;
+//@property(readwrite) unsigned testsRun;
+//@property(readwrite) unsigned testsPassed;
+//@property(readwrite) unsigned testsFailed;
+//@property(readwrite) unsigned uncaughtExceptions;
+//@property(readwrite) unsigned testsFailedExpected;
+//@property(readwrite) unsigned testsPassedUnexpected;
+//@property(readwrite) unsigned lowLevelExceptionsExpected;
+//@property(readwrite) unsigned lowLevelExceptionsUnexpected;
+
+// BUG: as above (shouldn't have to explicitly re-declare "assign" here)
+//@property(readwrite, assign) NSString *lastReportedFile;
+//@property(readwrite) int lastReportedLine;
+
+//! \endgroup
+
@end
@implementation WOTest
if (!WOTestSharedInstance) // first time here
{
if ((self = [super init]))
- {
// once-off initialization and setting of defaults:
- self->handlesLowLevelExceptions = YES;
self->warnsAboutSignComparisons = YES;
- }
WOTestSharedInstance = self;
}
else
{
@synchronized (self)
{
- if ([self startDate] == nil)
- [self setStartDate:[NSDate date]];
+ if (self.startDate == nil)
+ self.startDate = [NSDate date];
}
}
}
@catch (WOTestLowLevelException *lowLevelException)
{
- if ([self expectLowLevelExceptions])
+ if (self.expectLowLevelExceptions)
{
[self writeStatus:[lowLevelException reason]]; // expected low-level exceptions are not an error
- lowLevelExceptionsExpected++;
+ self.lowLevelExceptionsExpected++;
}
else
{
[self writeError:[lowLevelException reason]]; // unexpected low-level exceptions are an error
[self writeLastKnownLocation];
noTestFailed = NO;
- lowLevelExceptionsUnexpected++;
+ self.lowLevelExceptionsUnexpected++;
}
}
@catch (id e)
method];
[self writeLastKnownLocation];
noTestFailed = NO;
- uncaughtExceptions++;
+ self.uncaughtExceptions++;
}
@finally
{
NSStringFromClass(aClass)];
[self writeLastKnownLocation];
noTestFailed = NO;
- uncaughtExceptions++;
+ self.uncaughtExceptions++;
}
@finally
{
[NSArray arrayWithObjects: @"Protocol", @"List", @"Object", @"_NSZombie", nil] : // 10.3
[NSArray arrayWithObjects: @"Protocol", @"List", @"Object", @"_NSZombie", @"NSATSGlyphGenerator", nil]; // 10.4
- if ([self verbosity] > 1)
+ if (self.verbosity > 1)
_WOLog(@"Examining classes for WOTest protocol compliance");
for (int i = 0; i < newNumClasses; i++)
if ([excludedClasses containsObject:className])
{
excludedClassCount++;
- if ([self verbosity] > 1)
+ if (self.verbosity > 1)
_WOLog(@"Skipping class %@ (appears in exclusion list)", className);
}
else if ([NSObject WOTest_instancesOfClass:aClass conformToProtocol:@protocol(WOTest)])
{
conformingClassCount++;
[testableClasses addObject:className];
- if ([self verbosity] > 0)
+ if (self.verbosity > 0)
_WOLog(@"Class %@ complies with the WOTest protocol", className);
}
else
{
nonconformingClassCount++;
- if ([self verbosity] > 1)
+ if (self.verbosity > 1)
_WOLog(@"Class %@ does not comply with the WOTest protocol", className);
}
}
{
exceptionCount++;
// a number of classes are known to provoke exceptions:
- if ([self verbosity] > 1)
+ if (self.verbosity > 1)
_WOLog(@"Cannot test protocol compliance for class %@ (caught exception)", className);
continue;
}
[self checkStartDate]; // just in case no tests were run, make sure that startDate is non-nil
double successRate = 0.0;
double failureRate = 0.0;
- if (testsRun > 0) // watch out for divide-by-zero if no tests run
+ if (self.testsRun > 0) // watch out for divide-by-zero if no tests run
{
- successRate = ((double)(testsPassed + testsFailedExpected) / (double)testsRun) * 100.0;
- failureRate = ((double)(testsFailed + testsPassedUnexpected) / (double)testsRun) * 100.0;
+ successRate = ((double)(self.testsPassed + self.testsFailedExpected) / (double)self.testsRun) * 100.0;
+ failureRate = ((double)(self.testsFailed + self.testsPassedUnexpected) / (double)self.testsRun) * 100.0;
}
_WOLog(@"Run summary:\n"
@"Tests run: %d\n"
@"Uncaught exceptions: %d\n"
@"Low-level exceptions (crashers): %d + %d expected\n"
@"Total run time: %.2f seconds\n",
- testsRun,
- testsPassed, testsFailedExpected, successRate,
- testsFailed, testsPassedUnexpected, failureRate,
- uncaughtExceptions,
- lowLevelExceptionsUnexpected, lowLevelExceptionsExpected,
- -[[self startDate] timeIntervalSinceNow]);
-
- if (testsRun == 0)
+ self.testsRun,
+ self.testsPassed, self.testsFailedExpected, successRate,
+ self.testsFailed, self.testsPassedUnexpected, failureRate,
+ self.uncaughtExceptions,
+ self.lowLevelExceptionsUnexpected, self.lowLevelExceptionsExpected,
+ -[self.startDate timeIntervalSinceNow]);
+
+ if (self.testsRun == 0)
_WOLog(@"warning: no tests were run\n");
// TODO: make Growl notifications optional
// TODO: add options for showing coalesced growl notifications showing individual test failures (with path and line info)
// TODO: make clicking on notification bring Xcode to the front, or open the file with the last failure in it etc
NSString *status = [NSString stringWithFormat:@"%d tests passed, %d tests failed",
- testsPassed + testsFailedExpected, testsFailed + testsPassedUnexpected];
+ self.testsPassed + self.testsFailedExpected, self.testsFailed + self.testsPassedUnexpected];
if ([self testsWereSuccessful])
[self growlNotifyTitle:@"WOTest run successful" message:status isWarning:NO sticky:NO];
}
// reset start date
- [self setStartDate:nil];
+ self.startDate = nil;
}
- (BOOL)testsWereSuccessful
{
- return ((testsFailed + testsPassedUnexpected + uncaughtExceptions + lowLevelExceptionsUnexpected) == 0);
+ return ((self.testsFailed + self.testsPassedUnexpected + self.uncaughtExceptions + self.lowLevelExceptionsUnexpected) == 0);
}
#pragma mark -
NSParameterAssert(path != NULL);
NSString *pathString = [NSString stringWithUTF8String:path];
- unsigned trim = [self trimInitialPathComponents];
+ unsigned trim = self.trimInitialPathComponents;
if (trim == 0) return pathString;
if (![pathString isAbsolutePath]) return pathString; // only trim absolute paths
NSArray *components = [pathString pathComponents]; // note: Cocoa returns "/" here as an additional first component
return [NSString pathWithComponents:[components subarrayWithRange:NSMakeRange(trim + 1, count - trim - 1)]];
}
-- (void)writePassed:(BOOL)passed
- inFile:(char *)path
- atLine:(int)line
- message:(NSString *)message, ...
+- (void)writePassed:(BOOL)passed inFile:(char *)path atLine:(int)line message:(NSString *)message, ...
{
- testsRun++;
+ self.testsRun++;
va_list args;
va_start(args, message);
NSString *string = [NSString WOTest_stringWithFormat:message arguments:args];
va_end(args);
- if ([self expectFailures]) // invert sense of tests (ie. failure is good)
+ if (self.expectFailures) // invert sense of tests (ie. failure is good)
{
- if (passed) // passed: bad
+ if (passed) // passed: bad
{
[self writeErrorInFile:path atLine:line message:[NSString stringWithFormat:@"Passed (unexpected pass): %@", string]];
- testsPassedUnexpected++;
+ self.testsPassedUnexpected++;
}
- else // failed: good
+ else // failed: good
{
[self writeStatusInFile:path atLine:line message:[NSString stringWithFormat:@"Failed (expected failure): %@", string]];
- testsFailedExpected++;
+ self.testsFailedExpected++;
}
}
- else // normal handling (ie. passing is good, failing is bad)
+ else // normal handling (ie. passing is good, failing is bad)
{
- if (passed) // passed: good
+ if (passed) // passed: good
{
[self writeStatusInFile:path atLine:line message:[NSString stringWithFormat:@"Passed: %@", string]];
- testsPassed++;
+ self.testsPassed++;
}
- else // failed: bad
+ else // failed: bad
{
[self writeErrorInFile:path atLine:line message:[NSString stringWithFormat:@"Failed: %@", string]];
- testsFailed++;
+ self.testsFailed++;
}
}
}
- (void)cacheFile:(char *)path line:(int)line
{
- [self setLastReportedFile:[self trimmedPath:path]];
- lastReportedLine = line;
+ self.lastReportedFile = [self trimmedPath:path];
+ self.lastReportedLine = line;
}
- (void)writeLastKnownLocation
{
- NSString *path = [self lastReportedFile];
+ NSString *path = self.lastReportedFile;
if (path)
- _WOLog(@"%@:%d: last known location was %@:%d", path, lastReportedLine, path, lastReportedLine);
+ _WOLog(@"%@:%d: last known location was %@:%d", path, self.lastReportedLine, path, self.lastReportedLine);
}
- (void)writeErrorInFile:(char *)path atLine:(int)line message:(NSString *)message, ...
- (void)writeUncaughtException:(NSString *)info inFile:(char *)path atLine:(int)line
{
_WOLog(@"%@:%d: error: uncaught exception during test execution: %@", [self trimmedPath:path], line, info);
- uncaughtExceptions++;
+ self.uncaughtExceptions++;
}
- (void)writeStatusInFile:(char *)path atLine:(int)line message:(NSString *)message, ...
}
@catch (id e) {
[self writeErrorInFile:path atLine:line message:@"uncaught exception (%@)", [NSException WOTest_descriptionForException:e]];
- uncaughtExceptions++;
+ self.uncaughtExceptions++;
}
BOOL expectedTruncated, actualTruncated;
[self writePassed:equal inFile:path atLine:line message:@"expected %@, got %@", WO_DESC(expected), WO_DESC(actual)];
}
@catch (id e) {
[self writeErrorInFile:path atLine:line message:@"uncaught exception (%@)", [NSException WOTest_descriptionForException:e]];
- uncaughtExceptions++;
+ self.uncaughtExceptions++;
}
BOOL expectedTruncated, actualTruncated;
[self writePassed:(!equal) inFile:path atLine:line message:@"expected (not) %@, got %@", WO_DESC(expected), WO_DESC(actual)];
}
@catch (id e) {
[self writeErrorInFile:path atLine:line message:@"uncaught exception (%@)", [NSException WOTest_descriptionForException:e]];
- uncaughtExceptions++;
+ self.uncaughtExceptions++;
}
BOOL expectedTruncated, actualTruncated;
[self writePassed:greaterThan inFile:path atLine:line message:@"expected > %@, got %@", WO_DESC(expected), WO_DESC(actual)];
}
@catch (id e) {
[self writeErrorInFile:path atLine:line message:@"uncaught exception (%@)", [NSException WOTest_descriptionForException:e]];
- uncaughtExceptions++;
+ self.uncaughtExceptions++;
}
BOOL expectedTruncated, actualTruncated;
[self writePassed:notGreaterThan inFile:path atLine:line message:@"expected <= %@, got %@", WO_DESC(expected), WO_DESC(actual)];
}
@catch (id e) {
[self writeErrorInFile:path atLine:line message:@"uncaught exception (%@)", [NSException WOTest_descriptionForException:e]];
- uncaughtExceptions++;
+ self.uncaughtExceptions++;
}
BOOL expectedTruncated, actualTruncated;
[self writePassed:lessThan inFile:path atLine:line message:@"expected < %@, got %@", WO_DESC(expected), WO_DESC(actual)];
}
@catch (id e) {
[self writeErrorInFile:path atLine:line message:@"uncaught exception (%@)", [NSException WOTest_descriptionForException:e]];
- uncaughtExceptions++;
+ self.uncaughtExceptions++;
}
BOOL expectedTruncated, actualTruncated;
[self writePassed:notLessThan inFile:path atLine:line message:@"expected >= %@, got %@", WO_DESC(expected), WO_DESC(actual)];
}
#pragma mark -
-#pragma mark Accessors
-
-- (NSDate *)startDate
-{
- return [[startDate retain] autorelease];
-}
-
-- (void)setStartDate:(NSDate *)aStartDate
-{
- [aStartDate retain];
- [startDate release];
- startDate = aStartDate;
-}
-
-- (unsigned)classesWithTests
-{
- return classesWithTests;
-}
-
-- (unsigned)classesWithoutTests
-{
- return classesWithoutTests;
-}
-
-- (unsigned)methodsWithTests
-{
- return methodsWithTests;
-}
-
-- (unsigned)testsRun
-{
- return testsRun;
-}
-
-- (unsigned)testsPassed
-{
- return testsPassed;
-}
-
-- (unsigned)testsFailed
-{
- return testsFailed;
-}
-
-- (unsigned)uncaughtExceptions
-{
- return uncaughtExceptions;
-}
-
-- (unsigned)testsFailedExpected
-{
- return testsFailedExpected;
-}
-
-- (unsigned)testsPassedUnexpected
-{
- return testsPassedUnexpected;
-}
-
-- (BOOL)expectFailures
-{
- return expectFailures;
-}
-
-- (void)setExpectFailures:(BOOL)aValue
-{
- expectFailures = aValue;
-}
-
-- (unsigned)lowLevelExceptionsExpected
-{
- return lowLevelExceptionsExpected;
-}
-
-- (unsigned)lowLevelExceptionsUnexpected
-{
- return lowLevelExceptionsUnexpected;
-}
-
-- (BOOL)expectLowLevelExceptions
-{
- return expectLowLevelExceptions;
-}
-
-- (void)setExpectLowLevelExceptions:(BOOL)aValue
-{
- expectLowLevelExceptions = aValue;
-}
-
-- (BOOL)handlesLowLevelExceptions
-{
- return handlesLowLevelExceptions;
-}
-
-- (void)setHandlesLowLevelExceptions:(BOOL)aValue
-{
- handlesLowLevelExceptions = aValue;
-}
-
-- (unsigned)verbosity
-{
- return verbosity;
-}
-
-- (void)setVerbosity:(unsigned)aVerbosity
-{
- verbosity = aVerbosity;
-}
-
-- (unsigned)trimInitialPathComponents
-{
- return trimInitialPathComponents;
-}
-
-- (void)setTrimInitialPathComponents:(unsigned)aTrimInitialPathComponents
-{
- trimInitialPathComponents = aTrimInitialPathComponents;
-}
-
-- (NSString *)lastReportedFile
-{
- return [[lastReportedFile retain] autorelease];
-}
-
-- (void)setLastReportedFile:(NSString *)aLastReportedFile
-{
- if (lastReportedFile != aLastReportedFile)
- {
- [aLastReportedFile retain];
- [lastReportedFile release];
- lastReportedFile = aLastReportedFile;
- }
-}
-
-- (BOOL)warnsAboutSignComparisons
-{
- return warnsAboutSignComparisons;
-}
-
-- (void)setWarnsAboutSignComparisons:(BOOL)aWarnsAboutSignComparisons
-{
- warnsAboutSignComparisons = aWarnsAboutSignComparisons;
-}
+#pragma mark Properties
+
+@synthesize startDate;
+@synthesize testsRun;
+@synthesize testsPassed;
+@synthesize testsFailed;
+@synthesize uncaughtExceptions;
+@synthesize testsFailedExpected;
+@synthesize testsPassedUnexpected;
+@synthesize expectFailures;
+@synthesize lowLevelExceptionsExpected;
+@synthesize lowLevelExceptionsUnexpected;
+@synthesize expectLowLevelExceptions;
+@synthesize verbosity;
+@synthesize trimInitialPathComponents;
+@synthesize lastReportedFile;
+@synthesize lastReportedLine;
+@synthesize warnsAboutSignComparisons;
@end