/*
* 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.execution;
import com.google.jstestdriver.idea.RemoteTestResultReceiver;
import com.google.jstestdriver.idea.execution.tree.RemoteTestListener;
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.junit.RuntimeConfigurationProducer;
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 com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.concurrency.FutureResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import static com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil.attachRunner;
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)
*/
class TestRunnerState extends CommandLineState {
private static final ExecutorService testResultReceiverExecutor =
newSingleThreadExecutor(namedThreadFactory("remoteTestResultReceiver-%d"));
private static final Logger logger = Logger.getInstance(TestRunnerState.class.getCanonicalName());
// TODO(alexeagle): needs to be configurable?
private static final int testResultPort = 10998;
private final JstdRunConfiguration myRunConfiguration;
private final Project myProject;
private final List<VirtualFile> myConfigVirtualFiles;
private static ThreadFactory namedThreadFactory(final String threadName) {
return new ThreadFactory() {
final AtomicInteger cnt = new AtomicInteger(0);
@Override public Thread newThread(Runnable r) {
int num = cnt.incrementAndGet();
Thread thread = Executors.defaultThreadFactory().newThread(r);
thread.setName(String.format(threadName, num));
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
logger.error("Uncaught exception on " + t, e);
}
});
return thread;
}};
}
public TestRunnerState(JstdRunConfiguration runConfiguration, Project project,
ExecutionEnvironment env) {
super(env);
myRunConfiguration = runConfiguration;
myProject = project;
myConfigVirtualFiles = JstdClientCommandLineBuilder.INSTANCE.collectVirtualFiles(runConfiguration.getRunSettings(), project);
}
@Override
@Nullable
public ExecutionResult execute(@NotNull Executor executor, @NotNull ProgramRunner runner) throws ExecutionException {
final TestConsoleProperties testConsoleProperties =
new SMTRunnerConsoleProperties(
new RuntimeConfigurationProducer.DelegatingRuntimeConfiguration<JstdRunConfiguration>(myRunConfiguration),
"jsTestDriver",
executor
);
FutureResult<ProcessData> processDataFuture = new FutureResult<ProcessData>();
TestListenerContext context = new TestListenerContext(processDataFuture);
RemoteTestListener listener = createRemoteTestListener(context);
CountDownLatch receivingSocketOpen = new CountDownLatch(1);
Future<?> testResultReceiverFuture = testResultReceiverExecutor.submit(
new RemoteTestResultReceiver(listener, testResultPort, receivingSocketOpen));
// TODO (ssimonchik) try get test results from process output stream without sockets
try {
receivingSocketOpen.await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new ExecutionException("Thread has been interrupted unexpectedly", e);
}
if (testResultReceiverFuture.isDone()) {
throw new ExecutionException("RemoteTestResultReceiver exits unexpectedly. See log for details");
}
ProcessHandler processHandler = startProcess();
BaseTestsOutputConsoleView consoleView =
attachRunner(myProject.getName(), processHandler, testConsoleProperties, getRunnerSettings(), getConfigurationSettings());
processDataFuture.set(new ProcessData((SMTRunnerConsoleView) consoleView, processHandler));
return new DefaultExecutionResult(context.consoleView(), context.processHandler(),
createActions(context.consoleView(), context.processHandler()));
}
private RemoteTestListener createRemoteTestListener(TestListenerContext context) {
VirtualFile virtualFile = null;
if (myRunConfiguration.getRunSettings().isAllInDirectory()) {
File directory = new File(myRunConfiguration.getRunSettings().getDirectory());
virtualFile = LocalFileSystem.getInstance().findFileByIoFile(directory);
}
return new RemoteTestListener(context, virtualFile);
}
@NotNull
@Override
protected ProcessHandler startProcess() throws ExecutionException {
GeneralCommandLine commandLine = JstdClientCommandLineBuilder.INSTANCE.buildCommandLine(
myRunConfiguration.getRunSettings(),
testResultPort,
myConfigVirtualFiles
);
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;
}
}
}