/*
 * Copyright 2006 Edward Kuns
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id: TestWithCustomTest.java 0000 2006-10-19 22:00:00Z ekuns $
 */
package org.castor.xmlctf;

import java.util.Iterator;
import java.util.List;

import junit.framework.TestCase;

import org.exolab.castor.tests.framework.testDescriptor.ConfigurationType;
import org.exolab.castor.tests.framework.testDescriptor.CustomTest;
import org.exolab.castor.tests.framework.testDescriptor.FailureType;
import org.exolab.castor.tests.framework.testDescriptor.types.FailureStepType;
import org.exolab.castor.xml.XMLContext;

/**
 * Implements a test case that tests code written by the XML source generator.
 * This class uses a user-provided test class to test the generated source.
 * <p>
 * Each user-provided test is allowed to return a Boolean, either a primitive or
 * a java.lang.Boolean -- it does not matter. If the user-provided test returns
 * a Boolean and it is false, then the test is considered to have failed. If the
 * user-provided test throws <i>or returns</i> a Throwable, it is considered to
 * have failed. If the user-provided test returns <b>anything else</b>
 * (including void) then the test is considered to have passed.
 * <p>
 * Note: Returning Throwable is a little bit cleaner than throwing an Exception,
 * but either is acceptable as a sign of test failure. This is because when a
 * Throwable is returned, if -printStack is in effect, then the CORRECT stack
 * trace can be displayed and not a stack dump from the refective invocation.
 * <p>
 * There is no requirement that the user-provided test implement any interface,
 * nor any requirement that the user-provided test return anything at all.
 * However, a test that returns "void" and that never throws an Exception is not
 * a very useful test as it can never fail.
 *
 * @author <a href="mailto:edward.kuns@aspect.com">Edward Kuns</a>
 * @version $Revision: 0000 $ $Date: $
 */
class TestWithCustomTest extends TestCase {
    /** We add this fixed string to the end of our testcase name. */
    private static final String CUSTOM = "_CustomTest";

    /** We delegate all actions to this test case. */
    private final XMLTestCase   _delegate;

    /** The failure object that is not null is the test intends to fail. */
    protected final FailureType _failure;
    /** True if the test is supposed to return failure or throw an Exception. */
    protected final boolean     _failureExpected;

    /**
     * Blank constructor for this test case. This contructor is not useful,
     * since no delegate test case is provided.
     *
     * @param name
     *            Name of our delegate test case
     */
    TestWithCustomTest(final String name) {
        super(name + CUSTOM);
        throw new IllegalArgumentException("You cannot use the name-only constructor");
    }

    /**
     * Constructs a test case that when invoked will delegate to the provided
     * test case.
     * @param name Name of our delegate test case
     * @param tc
     */
    TestWithCustomTest(final String name, final XMLTestCase tc) {
        super(name + CUSTOM);
        _delegate        = tc;
        _failure         = tc._failure;
        _failureExpected = _failure != null && _failure.getContent();
    }

    /**
     * Provides setup for our delegated test case, depending on the type of
     * test case we are delegating for.
     * @throws Exception if anything goes wrong during setup
     */
    protected void setUp() throws Exception {
        _delegate.setXMLContext(new XMLContext());

        if (_delegate instanceof MarshallingFrameworkTestCase) {
            ((MarshallingFrameworkTestCase)_delegate).setUp();
        } else if (_delegate instanceof SourceGeneratorTestCase) {
            ((SourceGeneratorTestCase)_delegate).setUp();
        }
    }

    /**
     * Provides tear down for our delegated test case, depending on the type of
     * test case we are delegating for.
     * @throws Exception if anything goes wrong during teardown
     */
    protected void tearDown() throws Exception {
        if (_delegate instanceof MarshallingFrameworkTestCase) {
            ((MarshallingFrameworkTestCase)_delegate).tearDown();
        } else if (_delegate instanceof SourceGeneratorTestCase) {
            ((SourceGeneratorTestCase)_delegate).tearDown();
        }
    }

    /**
     * Runs our test case using our delegate object where necessary.
     * @throws Exception when anything goes wrong (this is temporary)
     */
    public void runTest() {
        verbose("\n------------------------------");
        verbose("Run the custom test case");
        verbose("------------------------------\n");
        if (_delegate._skip) {
            verbose("-->Skipping the test");
            return;
        }

        List returnValues = null;

        try {
            CustomTest customTest = _delegate._unitTest.getCustomTest();
            ConfigurationType testConfig = customTest.getMethods();
            Object object = getTestObject(customTest.getTestClass());
            returnValues = _delegate.invokeEnumeratedMethods(object, testConfig);
        } catch (Exception e) {
            if (!_delegate.checkExceptionWasExpected(e, FailureStepType.CUSTOM_TEST)) {
                fail("Exception running the custom test " + e);
            }
            return;
        }

        // Loop over all our return values ... any FALSE or Throwable means a failure
        int count = 0;
        boolean testFailed = false;

        for (Iterator i = returnValues.iterator(); i.hasNext(); count++) {
            Object o = i.next();

            // If this test returned false and we DID NOT expect to fail, error!!!
            if (o instanceof Boolean && !((Boolean)o).booleanValue() && !_failureExpected) {
                // Mark failure, complain, but keep checking so we give ALL complaints
                testFailed = true;
                System.out.println("Custom test #" + count + " was expected to succeed, but returned false");
            }

            // If this test *returned* (not threw) an Exception, consider that a failure
            if (o instanceof Throwable && !_failureExpected) {
                testFailed = true;
                System.out.println("Custom test #" + count + " was expected to succeed, but returned Throwable");
                if (XMLTestCase._printStack) {
                    ((Throwable)o).printStackTrace();
                }
            }
        }

        // Did we fail to meet our expected result, either success or failure?
        if (testFailed ^ _failureExpected) {
            if (testFailed) {
                fail("The custom test failed");
            } else {
                fail("The custom test was expected to fail, but succeeded");
            }
        }
    }

    /**
     * Gets an instance of our test object, as configured.
     * @param testClassName name of the test class
     * @return an instance of our test object
     * @throws ClassNotFoundException when the test object's class cannot be found
     * @throws IllegalAccessException when the test object's constructor is private or protected
     * @throws InstantiationException when the test object is abstract or an interface
     */
    protected Object getTestObject(final String testClassName) throws ClassNotFoundException,
                                                 IllegalAccessException, InstantiationException {
        Class testObject = null;
        if (_delegate._test.getClassLoader() != null) {
            testObject = _delegate._test.getClassLoader().loadClass(testClassName);
        } else {
            testObject = this.getClass().getClassLoader().loadClass(testClassName);
        }
        return testObject.newInstance();
    }

    /**
     * print the message if in verbose mode.
     * @param message the message to print
     */
    private void verbose(final String message) {
        _delegate.verbose(message);
    }

}
