/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package test;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestResult;
import org.jboss.logging.Logger;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

/**
 * Extends the regular JUnit TestCase class to allow multiple test methods
 * to be run under a single test case class.
 * @author <a href="mailto:telrod@e2technologies.net">Tom Elrod</a>
 */
public class MultipleTestCase extends TestCase
{
    private List testMethods = new ArrayList();

    private static final Logger log = Logger.getLogger(MultipleTestCase.class);

    public MultipleTestCase(String name)
    {
        super(name);
        findTestMethods();
    }

    protected TestResult createResult()
    {
        return new MultipleTestResult();
    }

    public List getTestMethods()
    {
        return testMethods;
    }

    /**
     * Counts the number of test cases executed by run(TestResult result).
     */
    public int countTestCases()
    {
        return testMethods.size();
    }

    private void findTestMethods()
    {
        Class superClass = this.getClass();
        while(Test.class.isAssignableFrom(superClass))
        {
            Method[] methods = superClass.getDeclaredMethods();
            for(int i = 0; i < methods.length; i++)
            {
                addTestMethod(methods[i], testMethods);
            }
            superClass = superClass.getSuperclass();
        }
        if(testMethods.size() == 0)
        {
            log.error(this + " does not contain any test methods.");
            //TODO: Other than log, how should this be communicated? -TME
        }

    }

    private void addTestMethod(Method m, List names)
    {
        String name = m.getName();
        if(names.contains(name))
        {
            return;
        }
        if(!isPublicTestMethod(m))
        {
            if(isTestMethod(m))
            {
                log.warn("Method " + name + " is a test method, but must be made public.");
            }
            return;
        }
        names.add(name);
    }

    private boolean isPublicTestMethod(Method m)
    {
        return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
    }

    private boolean isTestMethod(Method m)
    {
        String name = m.getName();
        Class[] parameters = m.getParameterTypes();
        Class returnType = m.getReturnType();
        return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE);
    }

    public void runBare(String testMethod) throws Throwable
    {
        setUp();
        try
        {
            runTest(testMethod);
        }
        finally
        {
            tearDown();
        }
    }

    protected void runTest(String testMethod) throws Throwable
    {
        assertNotNull(testMethod);
        Method runMethod = null;
        try
        {
            // use getMethod to get all public inherited
            // methods. getDeclaredMethods returns all
            // methods of this class but excludes the
            // inherited ones.
            runMethod = getClass().getMethod(testMethod, null);
        }
        catch(NoSuchMethodException e)
        {
            fail("Method \"" + testMethod + "\" not found");
        }
        if(!Modifier.isPublic(runMethod.getModifiers()))
        {
            fail("Method \"" + testMethod + "\" should be public");
        }

        try
        {
            runMethod.invoke(this, new Class[0]);
        }
        catch(InvocationTargetException e)
        {
            e.fillInStackTrace();
            throw e.getTargetException();
        }
        catch(IllegalAccessException e)
        {
            e.fillInStackTrace();
            throw e;
        }
    }

}