/*
* Copyright 2009 Google Inc.
*
* Licensed 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
*
* http://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 com.google.jstestdriver.idea;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import com.google.inject.Module;
import com.google.jstestdriver.JsTestDriverServer;
import com.google.jstestdriver.idea.ui.ToolPanel;
import com.intellij.execution.DefaultExecutionResult;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.ExecutionResult;
import com.intellij.execution.Executor;
import com.intellij.execution.configurations.CommandLineState;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.runners.ProgramRunner;
import com.intellij.execution.testframework.TestConsoleProperties;
import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties;
import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView;
import com.intellij.execution.testframework.ui.BaseTestsOutputConsoleView;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Set;
import java.util.concurrent.*;
import static com.google.common.collect.Lists.transform;
import static com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil.attachRunner;
import static com.intellij.util.PathUtil.getJarPathForClass;
import static java.io.File.pathSeparator;
import static java.io.File.separatorChar;
import static java.util.Arrays.asList;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
/**
* Encapsulates the execution state of the test runner. The IDE will create and run an instance of this class when the
* user requests to run the tests. We in turn launch a new Java process which will execute the tests.
*
* @author alexeagle@google.com (Alex Eagle)
*/
public class TestRunnerState extends CommandLineState {
private final JSTestDriverConfiguration jsTestDriverConfiguration;
protected final Project project;
private final ExecutorService attachExecutor = newSingleThreadExecutor(namedThreadFactory("testProcessLauncher"));
private final ExecutorService testResultReceiverExecutor =
newSingleThreadExecutor(namedThreadFactory("remoteTestResultReceiver-%d"));
private final Logger logger = Logger.getInstance(TestRunnerState.class.getCanonicalName());
// TODO(alexeagle): needs to be configurable?
private static final int testResultPort = 10998;
private ThreadFactory namedThreadFactory(final String threadName) {
return new ThreadFactory() {
@Override public Thread newThread(Runnable r) {
Thread thread = Executors.defaultThreadFactory().newThread(r);
thread.setName(threadName);
return thread;
}};
}
public TestRunnerState(JSTestDriverConfiguration jsTestDriverConfiguration, Project project,
ExecutionEnvironment env) {
super(env);
this.jsTestDriverConfiguration = jsTestDriverConfiguration;
this.project = project;
}
protected GeneralCommandLine createGeneralCommandLine() throws ExecutionException {
final String serverURL = (jsTestDriverConfiguration.getServerType() == ServerType.INTERNAL ?
"http://localhost:" + ToolPanel.serverPort :
jsTestDriverConfiguration.getServerAddress());
final File configFile = new File(jsTestDriverConfiguration.getSettingsFile());
return new GeneralCommandLine() {{
setWorkingDirectory(configFile.getParentFile());
setExePath(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java");
// uncomment this if you want to debug jsTestDriver code in the test-runner process
//addParameter("-Xdebug");
//addParameter("-Xrunjdwp:transport=dt_socket,address=5000,server=y,suspend=y");
addParameter("-cp");
addParameter(buildClasspath());
addParameter(TestRunner.class.getName());
addParameter(serverURL);
addParameter(jsTestDriverConfiguration.getSettingsFile());
addParameter(String.valueOf(testResultPort));
}};
}
private static final Function<File, String> getAbsolutePath = new Function<File, String>() {
@Override public String apply(File file) {
return file.getAbsolutePath();
}};
private String buildClasspath() {
Set<String> classpath = Sets.newHashSet();
String pathToJstd = getJarPathForClass(JsTestDriverServer.class);
boolean isRunningInIde = !pathToJstd.endsWith(".jar");
if (isRunningInIde) {
// JSTD compiled code is in a classes/ folder
classpath.add(pathToJstd);
String pathToLibJar = getJarPathForClass(Module.class);
File[] libs = new File(pathToLibJar.substring(0, pathToLibJar.lastIndexOf(separatorChar))).listFiles();
classpath.addAll(transform(asList(libs), getAbsolutePath));
} else {
// JSTD is in a jar next to other libraries
File[] libs = new File(pathToJstd.substring(0, pathToJstd.lastIndexOf(separatorChar))).listFiles();
classpath.addAll(transform(asList(libs), getAbsolutePath));
}
return Joiner.on(pathSeparator).join(classpath);
}
@Nullable
public ExecutionResult execute(@NotNull Executor executor, @NotNull ProgramRunner runner) throws ExecutionException {
final TestConsoleProperties testConsoleProperties =
new SMTRunnerConsoleProperties(jsTestDriverConfiguration, "jsTestDriver", executor);
final CountDownLatch receivingSocketOpen = new CountDownLatch(1);
Future<ProcessData> data = attachExecutor.submit(new Callable<ProcessData>() {
public ProcessData call() throws Exception {
// Let the receiver start before we write anything to it.
receivingSocketOpen.await();
ProcessHandler processHandler = startProcess();
BaseTestsOutputConsoleView consoleView =
attachRunner(project.getName(), processHandler, testConsoleProperties, getRunnerSettings(), getConfigurationSettings());
return new ProcessData((SMTRunnerConsoleView) consoleView, processHandler);
}
});
TestListenerContext context = new TestListenerContext(data);
final RemoteTestListener listener = new RemoteTestListener(context);
testResultReceiverExecutor.submit(
new RemoteTestResultReceiver(listener, testResultPort, receivingSocketOpen));
return new DefaultExecutionResult(context.consoleView(), context.processHandler(),
createActions(context.consoleView(), context.processHandler()));
}
@Override
protected ProcessHandler startProcess() throws ExecutionException {
GeneralCommandLine commandLine = createGeneralCommandLine();
logger.info("Running JSTestDriver: " + commandLine.getCommandLineString());
return new OSProcessHandler(commandLine.createProcess(), "");
}
static class ProcessData {
final SMTRunnerConsoleView consoleView;
final ProcessHandler processHandler;
public ProcessData(SMTRunnerConsoleView consoleView, ProcessHandler processHandler) {
this.consoleView = consoleView;
this.processHandler = processHandler;
}
}
}