/* * Copyright (c) 2015 the original author or authors. * 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: * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation */ package eclipsebuild.testing; import eclipsebuild.Constants; import eclipsebuild.TestBundlePlugin; import org.eclipse.jdt.internal.junit.model.ITestRunListener2; import org.eclipse.jdt.internal.junit.model.RemoteTestRunnerClient; import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.file.FileTree; import org.gradle.api.internal.file.FileResolver; import org.gradle.api.internal.tasks.testing.*; import org.gradle.api.internal.tasks.testing.detection.TestExecuter; import org.gradle.api.internal.tasks.testing.detection.TestFrameworkDetector; import org.gradle.api.internal.tasks.testing.processors.TestMainAction; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; import org.gradle.api.tasks.testing.Test; import org.gradle.api.tasks.testing.TestOutputEvent; import org.gradle.internal.TrueTimeProvider; import org.gradle.internal.progress.OperationIdGenerator; import org.gradle.process.ExecResult; import org.gradle.process.internal.DefaultJavaExecAction; import org.gradle.process.internal.JavaExecAction; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public final class EclipseTestExecuter implements TestExecuter { private static final Logger LOGGER = Logging.getLogger(EclipseTestExecuter.class); private final Project project; public EclipseTestExecuter(Project project) { this.project = project; } @Override public void execute(Test test, TestResultProcessor testResultProcessor) { LOGGER.info("Executing tests in Eclipse"); int pdeTestPort = new PDETestPortLocator().locatePDETestPortNumber(); if (pdeTestPort == -1) { throw new GradleException("Cannot allocate port for PDE test run"); } LOGGER.info("Will use port {} to communicate with Eclipse.", pdeTestPort); runPDETestsInEclipse(test, testResultProcessor, pdeTestPort); } private EclipseTestExtension getExtension(Test testTask) { return (EclipseTestExtension) testTask.getProject().getExtensions().findByName("eclipseTest"); } private void runPDETestsInEclipse(final Test testTask, final TestResultProcessor testResultProcessor, final int pdeTestPort) { ExecutorService threadPool = Executors.newFixedThreadPool(2); File runDir = new File(testTask.getProject().getBuildDir(), testTask.getName()); File testEclipseDir = new File(this.project.property("buildDir") + "/eclipseTest/eclipse"); // File configIniFile = getInputs().getFiles().getSingleFile(); File configIniFile = new File(testEclipseDir, "configuration/config.ini"); assert configIniFile.exists(); File runPluginsDir = new File(testEclipseDir, "plugins"); LOGGER.info("Eclipse test directory is {}", runPluginsDir.getPath()); File equinoxLauncherFile = getEquinoxLauncherFile(testEclipseDir); LOGGER.info("equinox launcher file {}", equinoxLauncherFile); final JavaExecAction javaExecHandleBuilder = new DefaultJavaExecAction(getFileResolver(testTask)); javaExecHandleBuilder.setClasspath(this.project.files(equinoxLauncherFile)); javaExecHandleBuilder.setMain("org.eclipse.equinox.launcher.Main"); String javaHome = getExtension(testTask).getTestEclipseJavaHome(); File executable = new File(javaHome, "bin/java"); if (executable.exists()) { javaExecHandleBuilder.setExecutable(executable); } else { LOGGER.warn("Java executable doesn't exist: " + executable.getAbsolutePath()); } List<String> programArgs = new ArrayList<String>(); programArgs.add("-os"); programArgs.add(Constants.getOs()); programArgs.add("-ws"); programArgs.add(Constants.getWs()); programArgs.add("-arch"); programArgs.add(Constants.getArch()); if (getExtension(testTask).isConsoleLog()) { programArgs.add("-consoleLog"); } File optionsFile = getExtension(testTask).getOptionsFile(); if (optionsFile != null) { programArgs.add("-debug"); programArgs.add(optionsFile.getAbsolutePath()); } programArgs.add("-version"); programArgs.add("4"); programArgs.add("-port"); programArgs.add(Integer.toString(pdeTestPort)); programArgs.add("-testLoaderClass"); programArgs.add("org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader"); programArgs.add("-loaderpluginname"); programArgs.add("org.eclipse.jdt.junit4.runtime"); programArgs.add("-classNames"); for (String clzName : collectTestNames(testTask)) { programArgs.add(clzName); } programArgs.add("-application"); programArgs.add(getExtension(testTask).getApplicationName()); programArgs.add("-product org.eclipse.platform.ide"); // alternatively can use URI for -data and -configuration (file:///path/to/dir/) programArgs.add("-data"); programArgs.add(runDir.getAbsolutePath() + File.separator + "workspace"); programArgs.add("-configuration"); programArgs.add(configIniFile.getParentFile().getAbsolutePath()); programArgs.add("-testpluginname"); String fragmentHost = getExtension(testTask).getFragmentHost(); if (fragmentHost != null) { programArgs.add(fragmentHost); } else { programArgs.add(this.project.getName()); } javaExecHandleBuilder.setArgs(programArgs); javaExecHandleBuilder.setSystemProperties(testTask.getSystemProperties()); javaExecHandleBuilder.setEnvironment(testTask.getEnvironment()); // TODO this should be specified when creating the task (to allow override in build script) List<String> jvmArgs = new ArrayList<String>(); jvmArgs.add("-XX:MaxPermSize=256m"); jvmArgs.add("-Xms40m"); jvmArgs.add("-Xmx1024m"); // uncomment to debug spawned Eclipse instance // jvmArgs.add("-Xdebug"); // jvmArgs.add("-Xrunjdwp:transport=dt_socket,address=8998,server=y"); if (Constants.getOs().equals("macosx")) { jvmArgs.add("-XstartOnFirstThread"); } javaExecHandleBuilder.setJvmArgs(jvmArgs); javaExecHandleBuilder.setWorkingDir(this.project.getBuildDir()); final CountDownLatch latch = new CountDownLatch(1); Future<?> eclipseJob = threadPool.submit(new Runnable() { @Override public void run() { try { ExecResult execResult = javaExecHandleBuilder.execute(); execResult.assertNormalExitValue(); } catch (Exception e) { e.printStackTrace(); } finally { latch.countDown(); } } }); // TODO final String suiteName = this.project.getName(); Future<?> testCollectorJob = threadPool.submit(new Runnable() { @Override public void run() { EclipseTestListener pdeTestListener = new EclipseTestListener(testResultProcessor, testTask, suiteName, this); new RemoteTestRunnerClient().startListening(new ITestRunListener2[] { pdeTestListener }, pdeTestPort); LOGGER.info("Listening on port " + pdeTestPort + " for test suite " + suiteName + " results ..."); synchronized (this) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException(e); } finally { latch.countDown(); } } } }); try { latch.await(getExtension(testTask).getTestTimeoutSeconds(), TimeUnit.SECONDS); // short chance to do cleanup eclipseJob.get(15, TimeUnit.SECONDS); testCollectorJob.get(15, TimeUnit.SECONDS); } catch (Exception e) { throw new GradleException("Test execution failed", e); } } private File getEquinoxLauncherFile(File testEclipseDir) { File[] plugins = new File(testEclipseDir, "plugins").listFiles(); for (File plugin : plugins) { if (plugin.getName().startsWith("org.eclipse.equinox.launcher_")) { return plugin; } } return null; } private FileResolver getFileResolver(Test testTask) { return testTask.getProject().getPlugins().findPlugin(TestBundlePlugin.class).fileResolver; } private List<String> collectTestNames(Test testTask) { ClassNameCollectingProcessor processor = new ClassNameCollectingProcessor(); Runnable detector; final FileTree testClassFiles = testTask.getCandidateClassFiles(); if (testTask.isScanForTestClasses()) { TestFrameworkDetector testFrameworkDetector = testTask.getTestFramework().getDetector(); testFrameworkDetector.setTestClassesDirectory(testTask.getTestClassesDir()); testFrameworkDetector.setTestClasspath(testTask.getClasspath()); detector = new EclipsePluginTestClassScanner(testClassFiles, processor); } else { detector = new EclipsePluginTestClassScanner(testClassFiles, processor); } final Object testTaskOperationId = OperationIdGenerator.generateId(testTask); new TestMainAction(detector, processor, new NoOpTestResultProcessor(), new TrueTimeProvider(), testTaskOperationId, testTask.getPath(), String.format("Gradle Eclipse Test Run %s", testTask.getPath())).run(); LOGGER.info("collected test class names: {}", processor.classNames); return processor.classNames; } public static final class NoOpTestResultProcessor implements TestResultProcessor { @Override public void started(TestDescriptorInternal testDescriptorInternal, TestStartEvent testStartEvent) { } @Override public void completed(Object o, TestCompleteEvent testCompleteEvent) { } @Override public void output(Object o, TestOutputEvent testOutputEvent) { } @Override public void failure(Object o, Throwable throwable) { } } private class ClassNameCollectingProcessor implements TestClassProcessor { public List<String> classNames = new ArrayList<String>(); @Override public void startProcessing(TestResultProcessor testResultProcessor) { // no-op } @Override public void processTestClass(TestClassRunInfo testClassRunInfo) { this.classNames.add(testClassRunInfo.getTestClassName()); } @Override public void stop() { // no-op } } }