/*******************************************************************************
* Copyright (c) 2012 VMWare, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMWare, Inc. - initial API and implementation
*******************************************************************************/
package org.grails.ide.eclipse.test.util;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.internal.junit.launcher.ITestFinder;
import org.eclipse.jdt.internal.junit.launcher.ITestKind;
import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
import org.eclipse.jdt.internal.junit.launcher.JUnitMigrationDelegate;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
import org.eclipse.jdt.internal.junit.model.TestCaseElement;
import org.eclipse.jdt.internal.junit.model.TestElement;
import org.eclipse.jdt.internal.junit.model.TestRunSession;
import org.eclipse.jdt.junit.JUnitCore;
import org.eclipse.jdt.junit.TestRunListener;
import org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate;
import org.eclipse.jdt.junit.model.ITestElement;
import org.eclipse.jdt.junit.model.ITestElementContainer;
import org.eclipse.jdt.junit.model.ITestRunSession;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.springsource.ide.eclipse.commons.frameworks.test.util.ACondition;
import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil;
/**
* @author Kris De Volder
*
* @since 2.9
*/
public class AbstractGrailsJUnitIntegrationsTest extends GrailsTest {
public static class MockTestRunListener extends TestRunListener {
public ITestRunSession session = null; //captured when the session ends.
@Override
public void sessionFinished(ITestRunSession session) {
assertNull(this.session);
this.session = session;
}
}
public static ITestFinder getJUnit4TestFinder() {
String testKind = TestKindRegistry.JUNIT4_TEST_KIND_ID;
return getTestFinder(testKind);
}
public static ITestFinder getTestFinder(String testKind) {
TestKindRegistry registry = TestKindRegistry.getDefault();
ITestKind junit4kind = registry.getKind(testKind);
ITestFinder finder = junit4kind.getFinder();
return finder;
}
/**
* Constructs a class loader based on the given javaProject's resolved runtime classpath, using
* a launch configuration equivalent to the one created by "run as >> JUnit test" launch shortcut.
*/
public static URLClassLoader getRuntimeClassLoader(IJavaProject javaProject)
throws CoreException, MalformedURLException {
ILaunchConfigurationWorkingCopy wc = createLaunchConfiguration(javaProject);
JUnitLaunchConfigurationDelegate delegate = new JUnitLaunchConfigurationDelegate();
String[] classpath = delegate.getClasspath(wc);
// System.out.println(">>> Creating JUnit runtime classloader with entries:");
URL[] classpathURLs = new URL[classpath.length];
for (int i = 0; i < classpathURLs.length; i++) {
// System.out.println(classpath[i]);
classpathURLs[i] = new File(classpath[i]).toURI().toURL();
}
// System.out.println("<<< Creating JUnit runtime classloader");
URLClassLoader classLoader = new URLClassLoader(classpathURLs);
return classLoader;
}
/**
* COPIED from JUnitLaunchShortcut... create a JUnit lauch config just like the one the JUnit UI would.
*/
public static ILaunchConfigurationWorkingCopy createLaunchConfiguration(IJavaElement element)
throws CoreException {
final String testName;
final String mainTypeQualifiedName;
final String containerHandleId;
switch (element.getElementType()) {
case IJavaElement.JAVA_PROJECT:
case IJavaElement.PACKAGE_FRAGMENT_ROOT:
case IJavaElement.PACKAGE_FRAGMENT: {
String name= element.getElementName();
containerHandleId= element.getHandleIdentifier();
mainTypeQualifiedName= "";
testName= name.substring(name.lastIndexOf(IPath.SEPARATOR) + 1);
}
break;
case IJavaElement.TYPE: {
containerHandleId= "";
mainTypeQualifiedName= ((IType) element).getFullyQualifiedName('.'); // don't replace, fix for binary inner types
testName= element.getElementName();
}
break;
case IJavaElement.METHOD: {
IMethod method= (IMethod) element;
containerHandleId= "";
mainTypeQualifiedName= method.getDeclaringType().getFullyQualifiedName('.');
testName= method.getDeclaringType().getElementName() + '.' + method.getElementName();
}
break;
default:
throw new IllegalArgumentException("Invalid element type to create a launch configuration: " + element.getClass().getName()); //$NON-NLS-1$
}
String testKindId= TestKindRegistry.getContainerTestKindId(element);
ILaunchConfigurationType configType= getLaunchManager().getLaunchConfigurationType(JUnitLaunchConfigurationConstants.ID_JUNIT_APPLICATION);
ILaunchConfigurationWorkingCopy wc= configType.newInstance(null, getLaunchManager().generateLaunchConfigurationName(testName));
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, mainTypeQualifiedName);
wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, element.getJavaProject().getElementName());
wc.setAttribute(JUnitLaunchConfigurationConstants.ATTR_KEEPRUNNING, false);
wc.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER, containerHandleId);
wc.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_RUNNER_KIND, testKindId);
JUnitMigrationDelegate.mapResources(wc);
//AssertionVMArg.setArgDefault(wc);
if (element instanceof IMethod) {
wc.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME, element.getElementName()); // only set for methods
}
return wc;
}
/**
* COPIED from JUnitLaunchShortcut... create a JUnit launch config just like the one the JUnit UI would.
*/
public static ILaunchManager getLaunchManager() {
return DebugPlugin.getDefault().getLaunchManager();
}
/**
* Assert that a given test run session contains a certain test, which failed, and who's
* stacktrace contains a given String.
* <p>
* The test is identified by a 'path' where each segment of the path is the name of a Test element in
* the test tree, leading to a test of interest.
*/
public static void assertTestFailure(ITestRunSession session, String expectedFailureSnippet,
String... pathToTest) {
ITestElement node = findTestNode(session, pathToTest, 0);
TestCaseElement element = (TestCaseElement) node;
assertEquals(TestElement.Status.FAILURE, element.getStatus());
assertContains(expectedFailureSnippet, element.getTrace());
}
public static ITestElement findTestNode(ITestElement node, String[] pathToTest, int pos) {
if (pos<pathToTest.length) {
String head = pathToTest[pos];
if (node instanceof ITestElementContainer) {
ITestElementContainer container = (ITestElementContainer) node;
for (ITestElement child : container.getChildren()) {
if (head.equals(getName(child))) {
return findTestNode(child, pathToTest, pos+1);
}
System.out.println(child);
}
}
fail("Test not found: "+head);
} else {
return node;
}
return null; //Unreachable because of 'fail' calls above, but Java compiler doesn't know this.
}
public static String getName(ITestElement child) {
if (child instanceof TestElement) {
String name = ((TestElement) child).getTestName();
int chop = name.indexOf('(');
if (chop>=0) {
name = name.substring(0, chop);
}
return name;
}
return null;
}
public static TestRunSession runAsJUnit(IJavaElement element) throws CoreException, Exception, DebugException {
StsTestUtil.assertNoErrors(element.getJavaProject().getProject()); //This is to avoid trying to run a project with errors...
// Trying to do so would hang the test when debug UI pops up a dialog.
ILaunchConfigurationWorkingCopy launchConf = createLaunchConfiguration(element);
final MockTestRunListener listener = new MockTestRunListener();
JUnitCore.addTestRunListener(listener);
try {
final ILaunch launch = launchConf.launch(ILaunchManager.RUN_MODE, new NullProgressMonitor(), true);
new ACondition() {
@Override
public boolean test() throws Exception {
return listener.session!=null && launch.isTerminated();
}
}.waitFor(80000);
assertEquals(0, launch.getProcesses()[0].getExitValue());
} finally {
JUnitCore.removeTestRunListener(listener);
}
return (TestRunSession) listener.session;
}
}