/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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
 *
 *      https://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.
 *
 */

package org.apache.tools.ant.taskdefs.optional.junit;

import java.io.OutputStream;
import java.lang.reflect.Method;

import junit.framework.AssertionFailedError;
import junit.framework.Test;

/**
 * Formatter that doesn't create any output but tries to invoke the
 * tearDown method on a testcase if that test was forked and caused a
 * timeout or VM crash.
 *
 * <p>This formatter has some limitations, for details see the
 * &lt;junit&gt; task's manual.</p>
 *
 * @since Ant 1.8.0
 */
public class TearDownOnVmCrash implements JUnitResultFormatter {

    private String suiteName;

    /**
     * Records the suite's name to later determine the class to invoke
     * tearDown on.
     */
    @Override
    public void startTestSuite(final JUnitTest suite) {
        suiteName = suite.getName();
        if (suiteName != null && suiteName.endsWith(JUnitTask.NAME_OF_DUMMY_TEST)) {
            // no way to know which class caused the timeout
            suiteName = null;
        }
    }

    /**
     * Only invoke tearDown if the suite is known and not the dummy
     * test we get when a Batch fails and the error is an actual
     * error generated by Ant.
     */
    @Override
    public void addError(final Test fakeTest, final Throwable t) {
        if (suiteName != null && fakeTest instanceof JUnitTaskMirrorImpl.VmExitErrorTest) {
            tearDown();
        }
    }

    // no need to implement the rest
    public void addFailure(Test test, Throwable t) {
    }

    @Override
    public void addFailure(Test test, AssertionFailedError t) {
    }

    @Override
    public void startTest(Test test) {
    }

    @Override
    public void endTest(Test test) {
    }

    @Override
    public void endTestSuite(JUnitTest suite) {
    }

    @Override
    public void setOutput(OutputStream out) {
    }

    @Override
    public void setSystemOutput(String out) {
    }

    @Override
    public void setSystemError(String err) {
    }

    private void tearDown() {
        try {
            // first try to load the class and let's hope it is on our
            // classpath
            Class<?> testClass = null;
            if (Thread.currentThread().getContextClassLoader() != null) {
                try {
                    testClass = Thread.currentThread().getContextClassLoader()
                        .loadClass(suiteName);
                } catch (ClassNotFoundException cnfe) {
                    // ignore
                }
            }
            if (testClass == null && getClass().getClassLoader() != null) {
                try {
                    testClass = getClass().getClassLoader().loadClass(suiteName);
                } catch (ClassNotFoundException cnfe) {
                    // ignore
                }
            }
            if (testClass == null) {
                // fall back to system classloader
                testClass = Class.forName(suiteName);
            }

            // if the test has a suite method, then we can't know
            // which test of the executed suite timed out, ignore it
            try {
                // check if there is a suite method
                testClass.getMethod("suite");
                return;
            } catch (NoSuchMethodException e) {
                // no suite method
            }

            // a loadable class and no suite method
            // no reason to check for JUnit 4 since JUnit4TestAdapter
            // doesn't have any tearDown method.

            try {
                Method td = testClass.getMethod("tearDown");
                if (td.getReturnType() == Void.TYPE) {
                    td.invoke(testClass.getDeclaredConstructor().newInstance());
                }
            } catch (NoSuchMethodException nsme) {
                // no tearDown, fine
            }

        } catch (ClassNotFoundException cnfe) {
            // class probably is not in our classpath, there is
            // nothing we can do
        } catch (Throwable t) {
            System.err.println("Caught an exception while trying to invoke"
                               + " tearDown: " + t.getMessage());
        }
    }
}
