FakeXCTest.m   [plain text]


//
//  FakeXCTest
//
//  Copyright (c) 2014 Apple. All rights reserved.
//

#import "FakeXCTest.h"
#import <objc/runtime.h>
#import <stdarg.h>
#import <fnmatch.h>

static int testCaseResult = 0;
static char *testLimitedGlobingRule = NULL;

int (*XFakeXCTestCallback)(const char *fmt, va_list) = vprintf;

static void
FXCTPrintf(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    XFakeXCTestCallback(fmt, ap);
    va_end(ap);
}

void
FakeXCFailureHandler(XCTestCase * __unused test, BOOL __unused expected, const char * filePath,
                     NSUInteger lineNumber, NSString * __unused condition, NSString * format, ...)
{
    va_list ap;
    va_start(ap, format);
    NSString *errorString = [[NSString alloc] initWithFormat:format arguments:ap];
    va_end(ap);

    FXCTPrintf("FAILED assertion at: %s:%d %s\n", filePath, (int)lineNumber, [errorString UTF8String]);

#if !__has_feature(objc_arc)
    [errorString release];
#endif

    testCaseResult = 1;
}

static bool
checkLimited(const char *testname)
{
    return testLimitedGlobingRule == NULL ||
    fnmatch(testLimitedGlobingRule, testname, 0) == 0;
}

static bool
returnNull(Method method)
{
    bool res = false;
    char *ret = method_copyReturnType(method);
    if (ret && strcmp(ret, "v") == 0)
        res = true;
    free(ret);

    return res;
}

@implementation XCTest

+ (void)setLimit:(const char *)testLimit
{
    if (testLimitedGlobingRule)
        free(testLimitedGlobingRule);
    testLimitedGlobingRule = strdup(testLimit);
}

+ (int) runTests
{
    NSMutableArray *testsClasses = [NSMutableArray array];
    unsigned long ranTests = 0, failedTests = 0;
    Class xctestClass = [XCTestCase class];
    int numClasses;
    int superResult = 0;


    FXCTPrintf("[TEST] %s\n", getprogname());

    numClasses = objc_getClassList(NULL, 0);

    if (numClasses > 0 ) {
        Class *classes = NULL;

        classes = (Class *)malloc(sizeof(Class) * numClasses);
        numClasses = objc_getClassList(classes, numClasses);
        for (int i = 0; i < numClasses; i++) {

            Class s = classes[i];
            while ((s = class_getSuperclass(s)) != NULL) {
                if (s == xctestClass) {
                    [testsClasses addObject:classes[i]];
                    break;
                }
            }
        }
        free(classes);
    }

    for (Class testClass in testsClasses) {
        unsigned int numMethods, n;

        if (!class_getClassMethod(testClass, @selector(respondsToSelector:)))
            continue;

        /* get all methods conforming to void test...*/
        XCTestCase *tc = NULL;;

        @autoreleasepool {
            Method *methods = NULL;

            tc = [[testClass alloc] init];
            if (tc == NULL)
                continue;

            methods = class_copyMethodList(testClass, &numMethods);
            if (methods == NULL) {
#if !__has_feature(objc_arc)
                [tc release];
#endif
                continue;
            }

            for (n = 0; n < numMethods; n++) {

                SEL ms = method_getName(methods[n]);
                const char *mname = sel_getName(ms);
                if (strncmp("test", mname, 4) == 0
                    && method_getNumberOfArguments(methods[n]) == 2
                    && returnNull(methods[n])
                    && checkLimited(mname))
                {
                    testCaseResult = 0;

                    if ([tc respondsToSelector:@selector(setUp)])
                        [tc setUp];

                    ranTests++;

                    FXCTPrintf("[BEGIN] %s:%s\n", class_getName(testClass), mname);
                    @try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                        [tc performSelector:ms];
#pragma clang diagnostic pop
                    }
                    @catch (NSException *exception) {
                        FXCTPrintf("exception: %s: %s",
                                   [[exception name] UTF8String],
                                   [[exception name] UTF8String]);

                        testCaseResult = 1;
                    }
                    @finally {
                        FXCTPrintf("[%s] %s:%s\n",
                                   testCaseResult == 0 ? "PASS" : "FAIL",
                                   class_getName(testClass), mname);

                        if (testCaseResult) {
                            superResult++;
                            failedTests++;
                        }
                        if ([tc respondsToSelector:@selector(tearDown)])
                            [tc tearDown];
                    }
                }
            }
            free(methods);
#if !__has_feature(objc_arc)
            [tc release];
#endif
        }
    }
    FXCTPrintf("[SUMMARY]\n"
               "ran %ld tests %ld failed\n",
               ranTests, failedTests);
    
    return superResult;
}

@end