com.technophobia.substeps.junit.launcher.SubstepsLaunchConfigurationDelegate.java Source code

Java tutorial

Introduction

Here is the source code for com.technophobia.substeps.junit.launcher.SubstepsLaunchConfigurationDelegate.java

Source

/*******************************************************************************
 * Copyright Technophobia Ltd 2012
 * 
 * This file is part of the Substeps Eclipse Plugin.
 * 
 * The Substeps Eclipse Plugin is free software: you can redistribute it and/or modify
 * it under the terms of the Eclipse Public License v1.0.
 * 
 * The Substeps Eclipse Plugin 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
 * Eclipse Public License for more details.
 * 
 * You should have received a copy of the Eclipse Public License
 * along with the Substeps Eclipse Plugin.  If not, see <http://www.eclipse.org/legal/epl-v10.html>.
 ******************************************************************************/
package com.technophobia.substeps.junit.launcher;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.launching.AbstractJavaLaunchConfigurationDelegate;
import org.eclipse.jdt.launching.ExecutionArguments;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.IVMRunner;
import org.eclipse.jdt.launching.SocketUtil;
import org.eclipse.jdt.launching.VMRunnerConfiguration;
import org.osgi.framework.Bundle;

import com.technophobia.eclipse.launcher.config.SubstepsLaunchConfigurationConstants;
import com.technophobia.substeps.FeatureEditorPlugin;
import com.technophobia.substeps.FeatureRunnerPlugin;
import com.technophobia.substeps.junit.launcher.config.SubstepsLaunchConfigWorkingCopyDecorator;
import com.technophobia.substeps.junit.ui.SubstepsFeatureMessages;
import com.technophobia.substeps.runner.RemoteTestRunner;
import com.technophobia.substeps.supplier.Callback1;
import com.technophobia.substeps.supplier.Predicate;
import com.technophobia.substeps.util.ModelOperation;
import com.technophobia.substeps.util.TemporaryModelEnhancer;

public class SubstepsLaunchConfigurationDelegate extends AbstractJavaLaunchConfigurationDelegate {

    private boolean keepAlive = false;
    private int port;
    private IMember[] testElements;

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.debug.core.model.ILaunchConfigurationDelegate#launch(org.
     * eclipse.debug.core.ILaunchConfiguration, java.lang.String,
     * org.eclipse.debug.core.ILaunch,
     * org.eclipse.core.runtime.IProgressMonitor)
     */
    @Override
    public synchronized void launch(final ILaunchConfiguration configuration, final String m, final ILaunch launch,
            final IProgressMonitor mtr) throws CoreException {
        final IProgressMonitor monitor;
        if (mtr == null) {
            monitor = new NullProgressMonitor();
        } else {
            monitor = mtr;
        }

        final String mode;
        if (m.equals(SubstepsLaunchConfigurationConstants.MODE_RUN_QUIETLY_MODE)) {
            launch.setAttribute(SubstepsLaunchConfigurationConstants.ATTR_NO_DISPLAY, "true"); //$NON-NLS-1$
            mode = ILaunchManager.RUN_MODE;
        } else {
            mode = m;
        }

        monitor.beginTask(MessageFormat.format("{0}...", configuration.getName()), 5); //$NON-NLS-1$
        // check for cancellation
        if (monitor.isCanceled()) {
            return;
        }

        try {
            monitor.subTask(
                    SubstepsFeatureMessages.SubstepsLaunchConfigurationDelegate_verifying_attributes_description);

            try {
                preLaunchCheck(configuration, launch, new SubProgressMonitor(monitor, 2));
            } catch (final CoreException e) {
                if (e.getStatus().getSeverity() == IStatus.CANCEL) {
                    monitor.setCanceled(true);
                    return;
                }
                throw e;
            }
            // check for cancellation
            if (monitor.isCanceled()) {
                return;
            }

            final TemporaryModelEnhancer<IJavaProject> modelEnhancer = new TemporaryModelEnhancer<IJavaProject>(
                    addSubstepsToClasspath(), removeSubstepsFromClasspath(),
                    doRunTests(mode, configuration, launch, monitor), isSubstepsNotOnClasspath(configuration));
            modelEnhancer.doOperationFor(getJavaProject(configuration));

        } finally {
            testElements = null;
            monitor.done();
        }
    }

    private Callback1<IJavaProject> addSubstepsToClasspath() {
        return new Callback1<IJavaProject>() {

            @Override
            public void doCallback(final IJavaProject project) {

                try {
                    final List<IClasspathEntry> newClasspath = new ArrayList<IClasspathEntry>(
                            Arrays.asList(project.getRawClasspath()));

                    final List<String> jarFiles = new SubstepJarProvider().junitRunnerJars();
                    for (final String jarFile : jarFiles) {
                        newClasspath.add(JavaCore.newLibraryEntry(new Path(jarFile), null, null));
                    }
                    project.setRawClasspath(newClasspath.toArray(new IClasspathEntry[newClasspath.size()]), null);
                } catch (final JavaModelException ex) {
                    FeatureRunnerPlugin.error("Could not add substeps jars to classpath", ex);
                }
            }
        };
    }

    private Callback1<IJavaProject> removeSubstepsFromClasspath() {
        return new Callback1<IJavaProject>() {

            @Override
            public void doCallback(final IJavaProject project) {
                try {
                    final List<IClasspathEntry> newClasspath = new ArrayList<IClasspathEntry>(
                            Arrays.asList(project.getRawClasspath()));

                    final List<String> jarFiles = new SubstepJarProvider().junitRunnerJars();
                    for (final String jarFile : jarFiles) {
                        newClasspath.remove(JavaCore.newLibraryEntry(new Path(jarFile), null, null));
                    }
                    project.setRawClasspath(newClasspath.toArray(new IClasspathEntry[newClasspath.size()]), null);
                } catch (final JavaModelException ex) {
                    FeatureRunnerPlugin.error("Could not remove substeps jars from classpath", ex);
                }
            }
        };
    }

    private ModelOperation<IJavaProject> doRunTests(final String mode, final ILaunchConfiguration configuration,
            final ILaunch launch, final IProgressMonitor monitor) {
        return new ModelOperation<IJavaProject>() {

            @Override
            public void doOperationOn(final IJavaProject t) throws CoreException {
                keepAlive = mode.equals(ILaunchManager.DEBUG_MODE)
                        && configuration.getAttribute(SubstepsLaunchConfigurationConstants.ATTR_KEEPRUNNING, false);
                port = evaluatePort();
                launch.setAttribute(SubstepsLaunchConfigurationConstants.ATTR_PORT, String.valueOf(port));
                testElements = evaluateTests(configuration, new SubProgressMonitor(monitor, 1));

                final String mainTypeName = verifyMainTypeName(configuration);
                final IVMRunner runner = getVMRunner(configuration, mode);

                final File workingDir = verifyWorkingDirectory(configuration);
                String workingDirName = null;
                if (workingDir != null) {
                    workingDirName = workingDir.getAbsolutePath();
                }

                // Environment variables
                final String[] envp = getEnvironment(configuration);

                final ArrayList<String> vmArguments = new ArrayList<String>();
                final ArrayList<String> programArguments = new ArrayList<String>();
                collectExecutionArguments(configuration, vmArguments, programArguments);

                // VM-specific attributes
                final Map<String, Object> vmAttributesMap = getVMSpecificAttributesMap(configuration);

                // Classpath
                final String[] classpath = getClasspath(configuration);

                // Create VM config
                final VMRunnerConfiguration runConfig = new VMRunnerConfiguration(mainTypeName, classpath);
                runConfig.setVMArguments(vmArguments.toArray(new String[vmArguments.size()]));
                runConfig.setProgramArguments(programArguments.toArray(new String[programArguments.size()]));
                runConfig.setEnvironment(envp);
                runConfig.setWorkingDirectory(workingDirName);
                runConfig.setVMSpecificAttributesMap(vmAttributesMap);

                // Bootpath
                runConfig.setBootClassPath(getBootpath(configuration));

                // check for cancellation
                if (monitor.isCanceled()) {
                    return;
                }

                // done the verification phase
                monitor.worked(1);

                monitor.subTask(
                        SubstepsFeatureMessages.SubstepsLaunchConfigurationDelegate_create_source_locator_description);
                // set the default source locator if required
                setDefaultSourceLocator(launch, configuration);
                monitor.worked(1);

                // Launch the configuration - 1 unit of work
                runner.run(runConfig, launch, monitor);

                // check for cancellation
                if (monitor.isCanceled()) {
                    return;
                }
            }
        };
    }

    private Predicate<IJavaProject> isSubstepsNotOnClasspath(final ILaunchConfiguration configuration) {
        return new Predicate<IJavaProject>() {

            @Override
            public boolean forModel(final IJavaProject project) {
                try {
                    final IJavaElement element = getMainElementFromProject(configuration, project);
                    return element == null;
                } catch (final JavaModelException ex) {
                    FeatureRunnerPlugin.error("Could not determine if substeps runner jars were on the classpath",
                            ex);
                } catch (final CoreException ex) {
                    FeatureRunnerPlugin.error("Could not determine if substeps runner jars were on the classpath",
                            ex);
                }
                return true;
            }
        };
    }

    private int evaluatePort() throws CoreException {
        final int p = SocketUtil.findFreePort();
        if (p == -1) {
            abort(SubstepsFeatureMessages.SubstepsLaunchConfigurationDelegate_error_no_socket, null,
                    IJavaLaunchConfigurationConstants.ERR_NO_SOCKET_AVAILABLE);
        }
        return p;
    }

    /**
     * Performs a check on the launch configuration's attributes. If an
     * attribute contains an invalid value, a {@link CoreException} with the
     * error is thrown.
     * 
     * @param configuration
     *            the launch configuration to verify
     * @param launch
     *            the launch to verify
     * @param monitor
     *            the progress monitor to use
     * @throws CoreException
     *             an exception is thrown when the verification fails
     */
    protected void preLaunchCheck(final ILaunchConfiguration configuration, final ILaunch launch,
            final IProgressMonitor monitor) throws CoreException {
        try {
            final IJavaProject javaProject = getJavaProject(configuration);
            if ((javaProject == null) || !javaProject.exists()) {
                abort(SubstepsFeatureMessages.SubstepsLaunchConfigurationDelegate_error_invalidproject, null,
                        IJavaLaunchConfigurationConstants.ERR_NOT_A_JAVA_PROJECT);
            }
        } finally {
            monitor.done();
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jdt.launching.AbstractJavaLaunchConfigurationDelegate#
     * verifyMainTypeName(org.eclipse.debug.core.ILaunchConfiguration)
     */
    @Override
    public String verifyMainTypeName(final ILaunchConfiguration configuration) throws CoreException {
        return RemoteTestRunner.class.getName();
    }

    /**
     * Evaluates all test elements selected by the given launch configuration.
     * The elements are of type {@link IType} or {@link IMethod}. At the moment
     * it is only possible to run a single method or a set of types, but not
     * mixed or more than one method at a time.
     * 
     * @param configuration
     *            the launch configuration to inspect
     * @param monitor
     *            the progress monitor
     * @return returns all types or methods that should be ran
     * @throws CoreException
     *             an exception is thrown when the search for tests failed
     */
    protected IMember[] evaluateTests(final ILaunchConfiguration configuration, final IProgressMonitor monitor)
            throws CoreException {
        final IJavaProject javaProject = getJavaProject(configuration);

        final IJavaElement testTarget = getTestTarget(configuration, javaProject);
        final String testMethodName = configuration
                .getAttribute(SubstepsLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME, ""); //$NON-NLS-1$
        if (testMethodName.length() > 0) {
            if (testTarget instanceof IType) {
                return new IMember[] { ((IType) testTarget).getMethod(testMethodName, new String[0]) };
            }
        }
        final HashSet<IMember> result = new HashSet<IMember>();
        result.add(testTarget.getJavaProject().findType(SubstepsLaunchConfigWorkingCopyDecorator.FEATURE_TEST));
        return result.toArray(new IMember[result.size()]);
    }

    /**
     * Collects all VM and program arguments. Implementors can modify and add
     * arguments.
     * 
     * @param configuration
     *            the configuration to collect the arguments for
     * @param vmArguments
     *            a {@link List} of {@link String} representing the resulting VM
     *            arguments
     * @param programArguments
     *            a {@link List} of {@link String} representing the resulting
     *            program arguments
     * @exception CoreException
     *                if unable to collect the execution arguments
     */
    protected void collectExecutionArguments(final ILaunchConfiguration configuration,
            final List<String> vmArguments, final List<String> programArguments) throws CoreException {

        // add program & VM arguments provided by getProgramArguments and
        // getVMArguments
        final String pgmArgs = getProgramArguments(configuration);
        final String vmArgs = getVMArguments(configuration);
        final ExecutionArguments execArgs = new ExecutionArguments(vmArgs, pgmArgs);
        vmArguments.addAll(Arrays.asList(execArgs.getVMArgumentsArray()));
        vmArguments.addAll(substepsVMArguments(configuration));
        programArguments.addAll(Arrays.asList(execArgs.getProgramArgumentsArray()));

        final String testFailureNames = configuration
                .getAttribute(SubstepsLaunchConfigurationConstants.ATTR_FAILURES_NAMES, ""); //$NON-NLS-1$

        programArguments.add("version=3");
        programArguments.add("port=" + String.valueOf(port));
        if (keepAlive)
            programArguments.add("keepalive"); //$NON-NLS-1$

        // final String testRunnerKind = getTestRunnerKind(configuration);

        //                    programArguments.add("-testLoaderClass"); //$NON-NLS-1$
        // programArguments.add(testRunnerKind.getLoaderClassName());
        //                    programArguments.add("-loaderpluginname"); //$NON-NLS-1$
        // programArguments.add(testRunnerKind.getLoaderPluginId());

        final IMember[] elements = this.testElements;

        // a test name was specified just run the single test
        if (elements.length == 1) {
            if (elements[0] instanceof IMethod) {
                final IMethod method = (IMethod) elements[0];
                programArguments.add("test=" + method.getDeclaringType().getFullyQualifiedName() + ':'
                        + method.getElementName());
            } else if (elements[0] instanceof IType) {
                final IType type = (IType) elements[0];
                programArguments.add("classNames=" + type.getFullyQualifiedName());
            } else {
                abort(SubstepsFeatureMessages.SubstepsLaunchConfigurationDelegate_error_wrong_input, null,
                        IJavaLaunchConfigurationConstants.ERR_UNSPECIFIED_MAIN_TYPE);
            }
        } else if (elements.length > 1) {
            final String fileName = createTestNamesFile(elements);
            programArguments.add("testNameFile=" + fileName);
        }
        if (testFailureNames.length() > 0) {
            programArguments.add("testfailures=" + testFailureNames);
        }
    }

    private Collection<String> substepsVMArguments(final ILaunchConfiguration configuration) {
        final Collection<String> results = new ArrayList<String>();
        final IProject project = projectFromConfig(configuration);

        if (project != null) {
            results.add("-DsubstepsFeatureFile=" + project.getLocation().addTrailingSeparator()
                    .append(getConfigAttribute(configuration, SubstepsFeatureLaunchShortcut.ATTR_FEATURE_FILE))
                    .toOSString());

            results.add("-DsubstepsFile=" + project.getLocation().addTrailingSeparator().append(
                    getConfigAttribute(configuration, SubstepsLaunchConfigurationConstants.ATTR_SUBSTEPS_FILE))
                    .toOSString());

            final Collection<String> stepImplementationClasses = FeatureEditorPlugin.instance()
                    .getStepImplementationProvider().stepImplementationClasses(project);
            results.add("-DsubstepsImplClasses=" + createStringFrom(stepImplementationClasses));

            results.add("-DsubstepsTags=--unimplemented");

            try {
                results.add("-DoutputFolder="
                        + getJavaProject(configuration).getOutputLocation().removeFirstSegments(1).toOSString());
            } catch (final JavaModelException e) {
                FeatureRunnerPlugin.log(e);
            } catch (final CoreException e) {
                FeatureRunnerPlugin.log(e);
            }

            // results.add(getConfigAttribute(configuration,
            // IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS));
        }
        return results;
    }

    private IProject projectFromConfig(final ILaunchConfiguration configuration) {
        try {
            return getJavaProject(configuration).getProject();
        } catch (final CoreException e) {
            FeatureRunnerPlugin.log(e);
            return null;
        }
    }

    private String getConfigAttribute(final ILaunchConfiguration configuration, final String configName) {
        try {
            return configuration.getAttribute(configName, "");
        } catch (final CoreException e) {
            FeatureRunnerPlugin.log(e);
            return "";
        }
    }

    private String createTestNamesFile(final IMember[] elements) throws CoreException {
        try {
            final File file = File.createTempFile("testNames", ".txt"); //$NON-NLS-1$ //$NON-NLS-2$
            file.deleteOnExit();
            BufferedWriter bw = null;
            try {
                bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8")); //$NON-NLS-1$
                for (int i = 0; i < elements.length; i++) {
                    if (elements[i] instanceof IType) {
                        final IType type = (IType) elements[i];
                        final String testName = type.getFullyQualifiedName();
                        bw.write(testName);
                        bw.newLine();
                    } else {
                        abort(SubstepsFeatureMessages.SubstepsLaunchConfigurationDelegate_error_wrong_input, null,
                                IJavaLaunchConfigurationConstants.ERR_UNSPECIFIED_MAIN_TYPE);
                    }
                }
            } finally {
                if (bw != null) {
                    bw.close();
                }
            }
            return file.getAbsolutePath();
        } catch (final IOException e) {
            throw new CoreException(new Status(IStatus.ERROR, FeatureRunnerPlugin.PLUGIN_ID, IStatus.ERROR, "", e)); //$NON-NLS-1$
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jdt.launching.AbstractJavaLaunchConfigurationDelegate#
     * getClasspath(org.eclipse.debug.core.ILaunchConfiguration)
     */
    @Override
    public String[] getClasspath(final ILaunchConfiguration configuration) throws CoreException {
        final String[] cp = super.getClasspath(configuration);

        Set<String> total = new LinkedHashSet<String>();

        // TODO might be a good idea to log out the classpath that we're firing the tests off with..??

        for (String cpElem : cp) {
            total.add(cpElem);
        }

        // This is the classpath to the test launcher plugin - this works whether in eclipse or tycho
        // and hopefully in a deployed plugin
        String thisPluginsClasspath = getPluginClasspath(FeatureRunnerPlugin.PLUGIN_ID);

        final List<String> junitEntries = new SubstepJarProvider().allSubstepJars();

        total.add(thisPluginsClasspath);
        total.addAll(junitEntries);

        return total.toArray(new String[total.size()]);
    }

    private String getPluginClasspath(String pluginId) {

        String finalUrl = null;

        final Bundle bundle = FeatureRunnerPlugin.instance().getBundle(pluginId);

        URL url = null;
        if (Platform.inDevelopmentMode()) {
            url = bundle.getEntry("target/classes");
        }

        if (url == null) {
            url = bundle.getEntry("/");
        }

        Assert.isNotNull(url, "url for plugin can't be null");

        try {
            finalUrl = FileLocator.toFileURL(url).getFile();
        } catch (IOException e) {

            e.printStackTrace();
        }

        return finalUrl;
    }

    private final IJavaElement getTestTarget(final ILaunchConfiguration configuration,
            final IJavaProject javaProject) throws CoreException {
        final String containerHandle = configuration
                .getAttribute(SubstepsLaunchConfigurationConstants.ATTR_TEST_CONTAINER, ""); //$NON-NLS-1$
        if (containerHandle.length() != 0) {
            final IJavaElement element = JavaCore.create(containerHandle);
            if (element == null || !element.exists()) {
                abort(SubstepsFeatureMessages.SubstepsLaunchConfigurationDelegate_error_input_element_deosn_not_exist,
                        null, IJavaLaunchConfigurationConstants.ERR_UNSPECIFIED_MAIN_TYPE);
            }
            return element;
        }
        final IJavaElement element = getMainElementFromProject(configuration, javaProject);
        if (element != null) {
            return element;
        }

        abort(SubstepsFeatureMessages.SubstepsLaunchConfigurationDelegate_input_type_does_not_exist, null,
                IJavaLaunchConfigurationConstants.ERR_UNSPECIFIED_MAIN_TYPE);
        return null; // not reachable
    }

    private IJavaElement getMainElementFromProject(final ILaunchConfiguration configuration,
            final IJavaProject javaProject) throws CoreException, JavaModelException {
        final String testTypeName = getMainTypeName(configuration);
        if (testTypeName != null && testTypeName.length() != 0) {
            final IType type = javaProject.findType(testTypeName);
            if (type != null && type.exists()) {
                return type;
            }
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.jdt.internal.junit.launcher.ITestFindingAbortHandler#abort
     * (java.lang.String, java.lang.Throwable, int)
     */
    @Override
    protected void abort(final String message, final Throwable exception, final int code) throws CoreException {
        throw new CoreException(new Status(IStatus.ERROR, FeatureRunnerPlugin.PLUGIN_ID, code, message, exception));
    }

    private String createStringFrom(final Collection<String> collection) {
        final StringBuilder sb = new StringBuilder();
        if (collection != null) {
            for (final String stepImpl : collection) {
                sb.append(stepImpl);
                sb.append(";");
            }
        }
        return sb.toString();
    }
}