// $Id: UnitTestRunner.m,v 1.8 2005/05/18 06:46:56 will_mason Exp $
// vi: set ft=objc:

/*
 * ObjectiveLib - a library of containers and algorithms for Objective-C
 *
 * Copyright (c) 2004
 * Will Mason
 *
 * Portions:
 *
 * Copyright (c) 1994
 * Hewlett-Packard Company
 *
 * Copyright (c) 1996,1997
 * Silicon Graphics Computer Systems, Inc.
 *
 * Copyright (c) 1997
 * Moscow Center for SPARC Technology
 *
 * Copyright (c) 1999 
 * Boris Fomitchev
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * You may contact the author at will_mason@users.sourceforge.net.
 */

#import "UnitTestRunner.h"
#if defined(__NEXT_RUNTIME__)
#import <objc/objc-runtime.h>
#else
#import <objc/objc-api.h>
#endif

static NSMutableArray* testsInProcess;
static NSRecursiveLock* testInProcessLock;

@implementation UnitTestRunner

+ (void) popTestInProcess
{
    [testsInProcess removeLastObject];
    [testInProcessLock unlock];
}

+ (void) pushTestInProcess: (id)test
{
    [testInProcessLock lock];
    [testsInProcess addObject: test];
}

+ (id) testInProcess
{
    return [testsInProcess lastObject];
}

- (id) init
{
    return [self initWithOut: stdout err: stderr];
}

- (id) initWithOut: (FILE*)o err: (FILE*)e
{
    NSLock* locker;

    [super initWithOut: o err: e];
    missingTests = [[NSMutableArray alloc] initWithCapacity: 32];
    testsRun = 0;

    locker = [[NSLock alloc] init];
    [locker lock];
    if (testsInProcess == nil)
        testsInProcess = [[NSMutableArray alloc] initWithCapacity: 10];
    if (testInProcessLock == nil)
        testInProcessLock = [[NSRecursiveLock alloc] init];
    [locker unlock];
    [locker release];

    return self;
}

- (void) dealloc
{
    [missingTests release];
    [super dealloc];
}

#if defined(__NEXT_RUNTIME__)
- (void) automaticallyLoadTests
{
    int classCount = objc_getClassList(NULL, 0);
    Class* classes;
    int i;
    Class unitTestClass;

    if (classCount == 0)
        return;

    unitTestClass = (Class)objc_getClass("UnitTest");
    NSAssert(unitTestClass != NULL, @"The UnitTest class is missing!");
    if (unitTestClass == NULL)
        return;

    classes = malloc(classCount * sizeof(Class));
    objc_getClassList(classes, classCount);
    for (i = 0; i < classCount; i++)
    {
        if (strncmp(classes[i]->name, "NS", 2) &&
            classes[i]->name[0] != '_')
        {
            printf("%s\n", classes[i]->name);
        }
        if (strcmp(classes[i]->name, "CompositeUnitTest") != 0 &&
            strcmp(classes[i]->name, "UnitTestRunner") != 0 &&
            [self isClass: classes[i] aTypeOf: unitTestClass])
        {
            [self loadTestsForClass: classes[i]];
        }
    }
    free(classes);
}
#else
- (void) automaticallyLoadTests
{
    void* state;
    Class cur;
    int i;
    Class unitTestClass = objc_get_class("UnitTest");

    NSAssert(unitTestClass != NULL, @"The UnitTest class is missing!");
    if (unitTestClass == NULL)
        return;

    while ((cur = objc_next_class(&state)) != NULL)
    {
        if (strcmp(cur->name, "CompositeUnitTest") != 0 &&
            strcmp(cur->name, "UnitTestRunner") != 0 &&
            [self isClass: cur aTypeOf: unitTestClass])
        {
            [self loadTestsForClass: cur];
        }
    }
}
#endif

- (BOOL) isClass: (Class)target aTypeOf: (Class)cls
{
    Class current = target->super_class;

    while (current != NULL)
    {
        if (strcmp(current->name, cls->name) == 0)
            return YES;
        current = current->super_class;
    }
    return NO;
}

- (void) loadTestsForClass: (Class)cls
{
    id instance;

    if (cls != NULL && [self isClass: cls aTypeOf: [UnitTest class]])
    {
        instance = [[cls alloc] init];
        if (![instance preventAutoLoad])
            [self addUnitTest: instance];
        [instance release];
    }
}

- (void) loadTestsForClasses: (Class*)array
{
    Class cur = *array;

    while (cur != NULL)
    {
        [self loadTestsForClass: cur];
        cur = *++array;
    }
}

- (void) loadTestsForClassesWithNames: (NSArray*)names
{
    NSEnumerator* itor = [names objectEnumerator];
    id current;

    while (current = [itor nextObject])
#if defined(__NEXT_RUNTIME__)
        [self loadTestsForClass: objc_getClass([current cString])];
#else
        [self loadTestsForClass: objc_get_class([current cString])];
#endif
}

- (void) runAllTests
{
    [super runAllTests];
    testsRun += [self testCount];
}

- (BOOL) runTestWithName: (NSString*)name
{
    if (![super runTestWithName: name])
    {
        [missingTests addObject: name];
        return NO;
    }
    else
    {
        testsRun++;
        return YES;
    }
}

- (void) runTestsWithNames: (NSArray*)names
{
    NSEnumerator* itor = [names objectEnumerator];
    id current;

    while (current = [itor nextObject])
        [self runTestWithName: current];
}

- (void) summarize
{
    int errs = [self errorCount];
    int missings = [missingTests count];
    NSEnumerator* itor;
    id current;
    FILE* stream = [self outStream];

    fprintf(stream, "\n%i %s run.\n", testsRun, (testsRun == 1) ? "test" : "tests");
    if (errs == 0)
    {
        fprintf(stream, "\nNo errors.\n");
    }
    else
    {
        fprintf(stream, "\nThere %s %i %s. The tests that contained errors were:\n",
                (errs == 1) ? "was" : "were",
                errs,
                (errs == 1) ? "error" : "errors");
        [self listFailedTestsToStream: stream delimiter: ' '];
        fprintf(stream, "\n");
    }
    if (missings != 0)
    {
        fprintf(stream, "\n%i %s not found. The missing %s:\n",
                missings,
                (missings == 1) ? "test was" : "tests were",
                (missings == 1) ? "test was" : "tests were");
        itor = [missingTests objectEnumerator];
        while (current = [itor nextObject])
        {
            fprintf(stream, "%s\n", [current cString]);
        }
    }
    fprintf(stream, "\n");
}

@end
