blob: 7ec585e36925f3a459fab2c3704e06d22805abe2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2017 xored software, Inc. and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* xored software, Inc. - initial API and Implementation (Alex Panchenko)
*******************************************************************************/
package org.eclipse.dltk.ruby.testing.internal;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugModelPresentation;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.debug.ui.ILaunchShortcut;
import org.eclipse.dltk.compiler.util.Util;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.IMethod;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IModelElementVisitor;
import org.eclipse.dltk.core.IProjectFragment;
import org.eclipse.dltk.core.IScriptFolder;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.internal.testing.launcher.DLTKTestingMigrationDelegate;
import org.eclipse.dltk.launching.ScriptLaunchConfigurationConstants;
import org.eclipse.dltk.ruby.core.RubyNature;
import org.eclipse.dltk.testing.DLTKTestingConstants;
import org.eclipse.dltk.testing.ITestingEngine;
import org.eclipse.dltk.testing.TestingEngineDetectResult;
import org.eclipse.dltk.testing.TestingEngineManager;
import org.eclipse.dltk.ui.DLTKUIPlugin;
import org.eclipse.dltk.ui.ScriptElementLabels;
import org.eclipse.dltk.ui.util.ExceptionHandler;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
public class RubyTestingLaunchShortcut implements ILaunchShortcut {
/**
* Default constructor.
*/
public RubyTestingLaunchShortcut() {
}
@Override
public void launch(IEditorPart editor, String mode) {
IModelElement element = DLTKUIPlugin.getEditorInputModelElement(editor.getEditorInput());
if (element != null) {
launch(new Object[] { element }, mode);
} else {
showNoTestsFoundDialog();
}
}
@Override
public void launch(ISelection selection, String mode) {
if (selection instanceof IStructuredSelection) {
launch(((IStructuredSelection) selection).toArray(), mode);
} else {
showNoTestsFoundDialog();
}
}
private void launch(Object[] elements, String mode) {
try {
IModelElement elementToLaunch = null;
if (elements.length == 1) {
Object selected = elements[0];
if (selected instanceof IFolder) {
performLaunch((IFolder) selected, mode);
return;
}
if (!(selected instanceof IModelElement) && selected instanceof IAdaptable) {
selected = ((IAdaptable) selected).getAdapter(IModelElement.class);
}
if (selected instanceof IModelElement) {
IModelElement element = (IModelElement) selected;
switch (element.getElementType()) {
case IModelElement.SCRIPT_PROJECT: {
IProject project = ((IScriptProject) element).getProject();
List<ILaunchConfiguration> configs = new ArrayList<>();
IFolder specFolder = project.getFolder("test"); //$NON-NLS-1$
if (specFolder != null && specFolder.exists())
configs.add(findOrCreateLaunch(specFolder, mode));
specFolder = project.getFolder("spec"); //$NON-NLS-1$
if (specFolder != null && specFolder.exists())
configs.add(findOrCreateLaunch(specFolder, mode));
ILaunchConfiguration config = null;
if (configs.size() == 1) {
config = configs.get(0);
} else if (configs.size() > 1) {
config = chooseConfiguration(configs, mode);
}
if (config != null) {
if (config.getAttribute(DLTKTestingConstants.ATTR_ENGINE_ID, (String) null) == null) {
MessageDialog.openInformation(getShell(), Messages.RubyTestingLaunchShortcut_testLaunch,
Messages.RubyTestingLaunchShortcut_theSelectedLaunchConfigurationDoesntHaveATestingEngineConfigured);
return;
}
DebugUITools.launch(config, mode);
}
return;
}
case IModelElement.PROJECT_FRAGMENT:
case IModelElement.SCRIPT_FOLDER: {
performLaunch((IFolder) element.getResource(), mode);
return;
}
case IModelElement.SOURCE_MODULE:
case IModelElement.TYPE:
case IModelElement.METHOD:
elementToLaunch = element;
break;
}
}
}
if (elementToLaunch == null) {
showNoTestsFoundDialog();
return;
}
performLaunch(elementToLaunch, mode);
} catch (InterruptedException e) {
// OK, silently move on
} catch (CoreException e) {
ExceptionHandler.handle(e, getShell(), Messages.RubyTestingLaunchShortcut_testLaunch,
Messages.RubyTestingLaunchShortcut_testLaunchUnexpectedlyFailed);
}
}
private void showNoTestsFoundDialog() {
MessageDialog.openInformation(getShell(), Messages.RubyTestingLaunchShortcut_testLaunch,
Messages.RubyTestingLaunchShortcut_unableToLocateAnyTestsInTheSpecifiedSelection);
}
private void performLaunch(IModelElement element, String mode) throws InterruptedException, CoreException {
ILaunchConfigurationWorkingCopy temporary = createLaunchConfiguration(element);
if (temporary == null) {
return;
}
ILaunchConfiguration config = findExistingLaunchConfiguration(temporary, mode);
if (config == null) {
// no existing found: create a new one
final IResource resource = element.getUnderlyingResource();
if (resource != null) {
temporary.setMappedResources(new IResource[] { resource });
}
config = temporary.doSave();
} else {
config = DLTKTestingMigrationDelegate.fixMappedResources(config);
}
if (config.getAttribute(DLTKTestingConstants.ATTR_ENGINE_ID, (String) null) == null) {
MessageDialog.openInformation(getShell(), Messages.RubyTestingLaunchShortcut_testLaunch,
Messages.RubyTestingLaunchShortcut_theSelectedLaunchConfigurationDoesntHaveATestingEngineConfigured);
return;
}
DebugUITools.launch(config, mode);
}
private ILaunchConfiguration findOrCreateLaunch(IFolder folder, String mode)
throws InterruptedException, CoreException {
String name = folder.getName();
String testName = name.substring(name.lastIndexOf(IPath.SEPARATOR) + 1);
ILaunchConfigurationType configType = getLaunchManager()
.getLaunchConfigurationType(getLaunchConfigurationTypeId());
final ILaunchConfigurationWorkingCopy wc = configType.newInstance(null,
getLaunchManager().generateLaunchConfigurationName(testName));
wc.setAttribute(ScriptLaunchConfigurationConstants.ATTR_PROJECT_NAME, folder.getProject().getName());
// wc.setAttribute(ScriptLaunchConfigurationConstants.ATTR_TEST_NAME,
// EMPTY_STRING);
IModelElement element = DLTKCore.create(folder);
if (element != null) {
wc.setAttribute(DLTKTestingConstants.ATTR_TEST_CONTAINER, element.getHandleIdentifier());
// wc.setAttribute(ScriptLaunchConfigurationConstants.
// ATTR_TEST_ELEMENT_NAME, EMPTY_STRING);
final ITestingEngine[] engines = TestingEngineManager.getEngines(RubyNature.NATURE_ID);
element.accept(new IModelElementVisitor() {
private boolean detected;
@Override
public boolean visit(IModelElement element) {
if (detected)
return false;
if (element instanceof ISourceModule) {
TestingEngineDetectResult detection = TestingEngineManager.detect(engines,
(ISourceModule) element);
if (detection != null) {
wc.setAttribute(DLTKTestingConstants.ATTR_ENGINE_ID, detection.getEngine().getId());
detected = true;
return false;
}
}
return element instanceof IScriptFolder || element instanceof IProjectFragment
|| element instanceof IScriptProject;
}
});
}
wc.setAttribute(ScriptLaunchConfigurationConstants.ATTR_SCRIPT_NATURE, RubyNature.NATURE_ID);
ILaunchConfiguration config = findExistingLaunchConfiguration(wc, mode);
if (config == null) {
// no existing found: create a new one
wc.setMappedResources(new IResource[] { folder });
config = wc.doSave();
} else {
config = DLTKTestingMigrationDelegate.fixMappedResources(config);
}
return config;
}
private void performLaunch(IFolder folder, String mode) throws InterruptedException, CoreException {
ILaunchConfiguration config = findOrCreateLaunch(folder, mode);
if (config.getAttribute(DLTKTestingConstants.ATTR_ENGINE_ID, (String) null) == null) {
MessageDialog.openInformation(getShell(), Messages.RubyTestingLaunchShortcut_testLaunch,
Messages.RubyTestingLaunchShortcut_theSelectedLaunchConfigurationDoesntHaveATestingEngineConfigured);
return;
}
DebugUITools.launch(config, mode);
}
private Shell getShell() {
return DLTKUIPlugin.getActiveWorkbenchShell();
}
private ILaunchManager getLaunchManager() {
return DebugPlugin.getDefault().getLaunchManager();
}
/**
* Show a selection dialog that allows the user to choose one of the specified
* launch configurations. Return the chosen config, or <code>null</code> if the
* user cancelled the dialog.
*
* @param configList
* @param mode
* @return ILaunchConfiguration
* @throws InterruptedException
*/
private ILaunchConfiguration chooseConfiguration(List<ILaunchConfiguration> configList, String mode)
throws InterruptedException {
IDebugModelPresentation labelProvider = DebugUITools.newDebugModelPresentation();
ElementListSelectionDialog dialog = new ElementListSelectionDialog(getShell(), labelProvider);
dialog.setElements(configList.toArray());
dialog.setTitle(Messages.RubyTestingLaunchShortcut_selectTestConfiguration);
if (mode.equals(ILaunchManager.DEBUG_MODE)) {
dialog.setMessage(Messages.RubyTestingLaunchShortcut_selectConfigurationToDebug);
} else {
dialog.setMessage(Messages.RubyTestingLaunchShortcut_selectConfigurationToRun);
}
dialog.setMultipleSelection(false);
int result = dialog.open();
if (result == Window.OK) {
return (ILaunchConfiguration) dialog.getFirstResult();
}
throw new InterruptedException(); // cancelled by user
}
/**
* Returns the launch configuration type id of the launch configuration this
* shortcut will create. Clients can override this method to return the id of
* their launch configuration.
*
* @return the launch configuration type id of the launch configuration this
* shortcut will create
*/
protected String getLaunchConfigurationTypeId() {
return "org.eclipse.dltk.ruby.testing.launchConfig"; //$NON-NLS-1$
}
/**
* Creates a launch configuration working copy for the given element. The launch
* configuration type created will be of the type returned by
* {@link #getLaunchConfigurationTypeId}. The element type can only be of type
* {@link IJavaProject}, {@link IPackageFragmentRoot}, {@link IPackageFragment},
* {@link IType} or {@link IMethod}.
*
* Clients can extend this method (should call super) to configure additional
* attributes on the launch configuration working copy.
*
* @return a launch configuration working copy for the given element
*/
protected ILaunchConfigurationWorkingCopy createLaunchConfiguration(IModelElement element) throws CoreException {
String testFileName;
String name = ScriptElementLabels.getDefault().getTextLabel(element, ScriptElementLabels.F_FULLY_QUALIFIED);
String testName = name.substring(name.lastIndexOf(IPath.SEPARATOR) + 1);
switch (element.getElementType()) {
case IModelElement.SOURCE_MODULE:
case IModelElement.TYPE: {
testFileName = element.getResource().getProjectRelativePath().toPortableString();
}
break;
case IModelElement.METHOD: {
testFileName = element.getResource().getProjectRelativePath().toPortableString();
}
break;
default:
throw new IllegalArgumentException(
"Invalid element type to create a launch configuration: " + element.getClass().getName()); //$NON-NLS-1$
}
ILaunchConfigurationType configType = getLaunchManager()
.getLaunchConfigurationType(getLaunchConfigurationTypeId());
ILaunchConfigurationWorkingCopy wc = configType.newInstance(null,
getLaunchManager().generateLaunchConfigurationName(testName));
wc.setAttribute(ScriptLaunchConfigurationConstants.ATTR_PROJECT_NAME,
element.getScriptProject().getElementName());
wc.setAttribute(ScriptLaunchConfigurationConstants.ATTR_MAIN_SCRIPT_NAME, testFileName);
wc.setAttribute(ScriptLaunchConfigurationConstants.ATTR_SCRIPT_NATURE, RubyNature.NATURE_ID);
wc.setAttribute(IDebugUIConstants.ATTR_CAPTURE_IN_CONSOLE, "true"); //$NON-NLS-1$
// wc.setAttribute(XUnitLaunchConfigurationConstants.ATTR_TEST_NAME,
// testFileName);
// wc.setAttribute(ScriptLaunchConfigurationConstants.ATTR_CONTAINER_PATH
// , containerHandleId);
// wc.setAttribute(XUnitLaunchConfigurationConstants.
// ATTR_TEST_ELEMENT_NAME, testElementName);
// XUnitMigrationDelegate.mapResources(wc);
ITestingEngine[] engines = TestingEngineManager.getEngines(RubyNature.NATURE_ID);
ISourceModule module = (ISourceModule) element.getAncestor(IModelElement.SOURCE_MODULE);
TestingEngineDetectResult detection = TestingEngineManager.detect(engines, module);
if (detection != null) {
wc.setAttribute(DLTKTestingConstants.ATTR_ENGINE_ID, detection.getEngine().getId());
}
return wc;
}
/**
* Returns the attribute names of the attributes that are compared when looking
* for an existing similar launch configuration. Clients can override and
* replace to customize.
*
* @return the attribute names of the attributes that are compared
*/
protected String[] getAttributeNamesToCompare() {
return new String[] { ScriptLaunchConfigurationConstants.ATTR_PROJECT_NAME,
DLTKTestingConstants.ATTR_TEST_CONTAINER, ScriptLaunchConfigurationConstants.ATTR_MAIN_SCRIPT_NAME,
// IDLTKTestingConstants.ENGINE_ID_ATR,
ScriptLaunchConfigurationConstants.ATTR_SCRIPT_NATURE
// XUnitLaunchConfigurationConstants.ATTR_TEST_NAME,
// XUnitLaunchConfigurationConstants.ATTR_TEST_ELEMENT_NAME
};
}
private static boolean hasSameAttributes(ILaunchConfiguration config1, ILaunchConfiguration config2,
String[] attributeToCompare) {
try {
for (int i = 0; i < attributeToCompare.length; i++) {
String val1 = config1.getAttribute(attributeToCompare[i], Util.EMPTY_STRING);
String val2 = config2.getAttribute(attributeToCompare[i], Util.EMPTY_STRING);
if (!val1.equals(val2)) {
return false;
}
}
return true;
} catch (CoreException e) {
// ignore access problems here, return false
}
return false;
}
private ILaunchConfiguration findExistingLaunchConfiguration(ILaunchConfigurationWorkingCopy temporary, String mode)
throws InterruptedException, CoreException {
ILaunchConfigurationType configType = temporary.getType();
ILaunchConfiguration[] configs = getLaunchManager().getLaunchConfigurations(configType);
String[] attributeToCompare = getAttributeNamesToCompare();
ArrayList<ILaunchConfiguration> candidateConfigs = new ArrayList<>(configs.length);
for (int i = 0; i < configs.length; i++) {
ILaunchConfiguration config = configs[i];
if (hasSameAttributes(config, temporary, attributeToCompare)) {
candidateConfigs.add(config);
}
}
// If there are no existing configs associated with the IType, create
// one.
// If there is exactly one config associated with the IType, return it.
// Otherwise, if there is more than one config associated with the
// IType, prompt the
// user to choose one.
int candidateCount = candidateConfigs.size();
if (candidateCount == 0) {
return null;
} else if (candidateCount == 1) {
return candidateConfigs.get(0);
} else {
// Prompt the user to choose a config. A null result means the user
// cancelled the dialog, in which case this method returns null,
// since cancelling the dialog should also cancel launching
// anything.
ILaunchConfiguration config = chooseConfiguration(candidateConfigs, mode);
if (config != null) {
return config;
}
}
return null;
}
}