Index: src/test/Makefile ================================================================== --- src/test/Makefile +++ src/test/Makefile @@ -8,11 +8,12 @@ OTOrderedDictionary.m \ OTTestCase.m INCLUDES := ${SRCS:.m=.h} \ ObjFWTest.h SRCS += OTAppDelegate.m \ - OTAssertionFailedException.m + OTAssertionFailedException.m \ + OTTestSkippedException.m includesubdir = ObjFWTest include ../../buildsys.mk Index: src/test/OTAppDelegate.m ================================================================== --- src/test/OTAppDelegate.m +++ src/test/OTAppDelegate.m @@ -24,13 +24,21 @@ #import "OFValue.h" #import "OTTestCase.h" #import "OTAssertionFailedException.h" +#import "OTTestSkippedException.h" @interface OTAppDelegate: OFObject @end + +enum Status { + StatusRunning, + StatusOk, + StatusFailed, + StatusSkipped +}; OF_APPLICATION_DELEGATE(OTAppDelegate) static bool isSubclassOfClass(Class class, Class superclass) @@ -127,46 +135,56 @@ return tests; } - (void)printStatusForTest: (SEL)test inClass: (Class)class - status: (int)status + status: (enum Status)status description: (OFString *)description { switch (status) { - case 0: + case StatusRunning: [OFStdOut setForegroundColor: [OFColor olive]]; [OFStdOut writeFormat: @"-[%@ ", class]; [OFStdOut setForegroundColor: [OFColor yellow]]; [OFStdOut writeFormat: @"%s", sel_getName(test)]; [OFStdOut setForegroundColor: [OFColor olive]]; [OFStdOut writeString: @"]: "]; break; - case 1: + case StatusOk: [OFStdOut setForegroundColor: [OFColor green]]; [OFStdOut writeFormat: @"\r-[%@ ", class]; [OFStdOut setForegroundColor: [OFColor lime]]; [OFStdOut writeFormat: @"%s", sel_getName(test)]; [OFStdOut setForegroundColor: [OFColor green]]; [OFStdOut writeLine: @"]: ok"]; break; - case 2: + case StatusFailed: [OFStdOut setForegroundColor: [OFColor maroon]]; [OFStdOut writeFormat: @"\r-[%@ ", class]; [OFStdOut setForegroundColor: [OFColor red]]; [OFStdOut writeFormat: @"%s", sel_getName(test)]; [OFStdOut setForegroundColor: [OFColor maroon]]; [OFStdOut writeLine: @"]: failed"]; [OFStdOut writeLine: description]; break; + case StatusSkipped: + [OFStdOut setForegroundColor: [OFColor gray]]; + [OFStdOut writeFormat: @"\r-[%@ ", class]; + [OFStdOut setForegroundColor: [OFColor silver]]; + [OFStdOut writeFormat: @"%s", sel_getName(test)]; + [OFStdOut setForegroundColor: [OFColor gray]]; + [OFStdOut writeLine: @"]: skipped"]; + if (description != nil) + [OFStdOut writeLine: description]; + break; } } - (void)applicationDidFinishLaunching: (OFNotification *)notification { OFSet OF_GENERIC(Class) *testClasses = [self testClasses]; - size_t numSucceeded = 0, numFailed = 0; + size_t numSucceeded = 0, numFailed = 0, numSkipped = 0; OFMutableDictionary *summaries = [OFMutableDictionary dictionary]; [OFStdOut setForegroundColor: [OFColor purple]]; [OFStdOut writeString: @"Found "]; [OFStdOut setForegroundColor: [OFColor fuchsia]]; @@ -183,16 +201,16 @@ [OFStdOut setForegroundColor: [OFColor aqua]]; [OFStdOut writeFormat: @"%@\n", class]; for (OFValue *test in [self testsInClass: class]) { void *pool = objc_autoreleasePoolPush(); - bool failed = false; + bool failed = false, skipped = false; OTTestCase *instance; [self printStatusForTest: test.pointerValue inClass: class - status: 0 + status: StatusRunning description: nil]; instance = [[[class alloc] init] autorelease]; @try { @@ -205,13 +223,19 @@ * If an assertion fails during a test, abort * the test. */ [self printStatusForTest: test.pointerValue inClass: class - status: 2 + status: StatusFailed description: e.description]; failed = true; + } @catch (OTTestSkippedException *e) { + [self printStatusForTest: test.pointerValue + inClass: class + status: StatusSkipped + description: e.description]; + skipped = true; } @try { [instance tearDown]; } @catch (OTAssertionFailedException *e) { /* @@ -222,24 +246,26 @@ SEL selector = test.pointerValue; OFString *description = e.description; [self printStatusForTest: selector inClass: class - status: 2 + status: StatusFailed description: description]; failed = true; } } - if (!failed) { + if (!failed && !skipped) { [self printStatusForTest: test.pointerValue inClass: class - status: 1 + status: StatusOk description: nil]; numSucceeded++; - } else + } else if (failed) numFailed++; + else if (skipped) + numSkipped++; objc_autoreleasePoolPop(pool); } summary = [class summary]; @@ -269,12 +295,17 @@ [OFStdOut writeFormat: @" test%s succeeded, ", (numSucceeded != 1 ? "s" : "")]; [OFStdOut setForegroundColor: [OFColor fuchsia]]; [OFStdOut writeFormat: @"%zu", numFailed]; [OFStdOut setForegroundColor: [OFColor purple]]; - [OFStdOut writeFormat: @" test%s failed\n", + [OFStdOut writeFormat: @" test%s failed, ", (numFailed != 1 ? "s" : "")]; + [OFStdOut setForegroundColor: [OFColor fuchsia]]; + [OFStdOut writeFormat: @"%zu", numSkipped]; + [OFStdOut setForegroundColor: [OFColor purple]]; + [OFStdOut writeFormat: @" test%s skipped\n", + (numSkipped != 1 ? "s" : "")]; [OFStdOut reset]; [OFApplication terminateWithStatus: (int)numFailed]; } @end Index: src/test/OTAssert.h ================================================================== --- src/test/OTAssert.h +++ src/test/OTAssert.h @@ -53,14 +53,17 @@ } @catch (exception *e) { \ OTThrown = true; \ } \ OTAssert(OTThrown, ## __VA_ARGS__); \ } +#define OTSkip(...) \ + OTSkipImpl(self, _cmd, @__FILE__, __LINE__, ## __VA_ARGS__, nil) #ifdef __cplusplus extern "C" { #endif extern void OTAssertImpl(id testCase, SEL test, bool condition, OFString *check, OFString *file, size_t line, ...); +extern void OTSkipImpl(id testCase, SEL test, OFString *file, size_t line, ...); #ifdef __cplusplus } #endif Index: src/test/OTAssert.m ================================================================== --- src/test/OTAssert.m +++ src/test/OTAssert.m @@ -16,10 +16,11 @@ #include "config.h" #import "OFString.h" #import "OTAssertionFailedException.h" +#import "OTTestSkippedException.h" void OTAssertImpl(id testCase, SEL test, bool condition, OFString *check, OFString *file, size_t line, ...) { @@ -41,5 +42,25 @@ va_end(arguments); @throw [OTAssertionFailedException exceptionWithCondition: check message: message]; } + +void +OTSkipImpl(id testCase, SEL test, OFString *file, size_t line, ...) +{ + va_list arguments; + OFConstantString *format; + OFString *message = nil; + + va_start(arguments, line); + format = va_arg(arguments, OFConstantString *); + + if (format != nil) + message = [[[OFString alloc] + initWithFormat: format + arguments: arguments] autorelease]; + + va_end(arguments); + + @throw [OTTestSkippedException exceptionWithMessage: message]; +} ADDED src/test/OTTestSkippedException.h Index: src/test/OTTestSkippedException.h ================================================================== --- src/test/OTTestSkippedException.h +++ src/test/OTTestSkippedException.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFException.h" +#import "OFString.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface OTTestSkippedException: OFException +{ + OFString *_Nullable _message; +} + +@property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFString *message; + ++ (instancetype)exceptionWithMessage: (nullable OFString *)message; ++ (instancetype)exception OF_UNAVAILABLE; +- (instancetype)initWithMessage: (nullable OFString *)message; +- (instancetype)init OF_UNAVAILABLE; +@end + +OF_ASSUME_NONNULL_END ADDED src/test/OTTestSkippedException.m Index: src/test/OTTestSkippedException.m ================================================================== --- src/test/OTTestSkippedException.m +++ src/test/OTTestSkippedException.m @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OTTestSkippedException.h" + +@implementation OTTestSkippedException +@synthesize message = _message; + ++ (instancetype)exceptionWithMessage: (OFString *)message +{ + return [[[self alloc] initWithMessage: message] autorelease]; +} + ++ (instancetype)exception +{ + OF_UNRECOGNIZED_SELECTOR +} + +- (instancetype)initWithMessage: (OFString *)message +{ + self = [super init]; + + @try { + _message = [message copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (void)dealloc +{ + [_message release]; + + [super dealloc]; +} + +- (OFString *)description +{ + if (_message != nil) + return [OFString stringWithFormat: @"Test skipped: %@", + _message]; + else + return nil; +} +@end