/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* 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.jetbrains.lang.dart.coverage;
import com.intellij.coverage.CoverageExecutor;
import com.intellij.coverage.CoverageHelper;
import com.intellij.coverage.CoverageRunnerData;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.*;
import com.intellij.execution.configurations.coverage.CoverageEnabledConfiguration;
import com.intellij.execution.process.*;
import com.intellij.execution.runners.DefaultProgramRunnerKt;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.runners.GenericProgramRunner;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.jetbrains.lang.dart.DartBundle;
import com.jetbrains.lang.dart.ide.runner.server.DartCommandLineRunConfiguration;
import com.jetbrains.lang.dart.ide.runner.server.DartCommandLineRunningState;
import com.jetbrains.lang.dart.sdk.DartSdk;
import com.jetbrains.lang.dart.sdk.DartSdkUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class DartCoverageProgramRunner extends GenericProgramRunner {
private static final Logger LOG = Logger.getInstance(DartCoverageProgramRunner.class.getName());
private static final String ID = "DartCoverageProgramRunner";
private boolean myCoveragePackageActivated;
@NotNull
@Override
public String getRunnerId() {
return ID;
}
@Override
public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
return executorId.equals(CoverageExecutor.EXECUTOR_ID) && profile instanceof DartCommandLineRunConfiguration;
}
@Override
public RunnerSettings createConfigurationData(final ConfigurationInfoProvider settingsProvider) {
return new CoverageRunnerData();
}
@Nullable
@Override
protected RunContentDescriptor doExecute(final @NotNull RunProfileState state,
final @NotNull ExecutionEnvironment env) throws ExecutionException {
FileDocumentManager.getInstance().saveAllDocuments();
final DartCommandLineRunConfiguration runConfiguration = (DartCommandLineRunConfiguration)env.getRunProfile();
final DartSdk sdk = DartSdk.getDartSdk(runConfiguration.getProject());
if (sdk == null) {
throw new ExecutionException(DartBundle.message("dart.sdk.is.not.configured"));
}
final String dartPubPath = DartSdkUtil.getPubPath(sdk);
final RunContentDescriptor result = DefaultProgramRunnerKt.executeState(state, env, this);
if (result == null) {
return null;
}
if (!myCoveragePackageActivated && !activateCoverage(runConfiguration.getProject(), dartPubPath)) {
throw new ExecutionException("Cannot activate pub package 'coverage'.");
}
final ProcessHandler dartAppProcessHandler = result.getProcessHandler();
if (dartAppProcessHandler != null) {
((DartCommandLineRunningState)state)
.addObservatoryUrlConsumer(observatoryUrl -> startCollectingCoverage(env, dartAppProcessHandler, observatoryUrl));
}
return result;
}
private static void startCollectingCoverage(@NotNull final ExecutionEnvironment env,
@NotNull final ProcessHandler dartAppProcessHandler,
@NotNull final String observatoryUrl) {
final DartCommandLineRunConfiguration dartRC = (DartCommandLineRunConfiguration)env.getRunProfile();
final DartCoverageEnabledConfiguration coverageConfiguration =
(DartCoverageEnabledConfiguration)CoverageEnabledConfiguration.getOrCreate(dartRC);
final String coverageFilePath = coverageConfiguration.getCoverageFilePath();
final DartSdk sdk = DartSdk.getDartSdk(env.getProject());
LOG.assertTrue(sdk != null);
final GeneralCommandLine cmdline = new GeneralCommandLine().withExePath(DartSdkUtil.getPubPath(sdk))
.withParameters("global", "run", "coverage:collect_coverage",
"--uri", observatoryUrl,
"--out", coverageFilePath,
"--resume-isolates",
"--wait-paused");
try {
final ProcessHandler coverageProcess = new OSProcessHandler(cmdline);
coverageProcess.addProcessListener(new ProcessAdapter() {
@Override
public void onTextAvailable(@NotNull final ProcessEvent event, @NotNull final Key outputType) {
LOG.debug(event.getText());
}
});
coverageProcess.startNotify();
coverageConfiguration.setCoverageProcess(coverageProcess);
CoverageHelper.attachToProcess(dartRC, dartAppProcessHandler, env.getRunnerSettings());
}
catch (ExecutionException e) {
LOG.error(e);
}
}
private boolean activateCoverage(@NotNull final Project project, @NotNull final String dartPubPath) {
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
if (indicator != null) {
indicator.setIndeterminate(true);
}
try {
// 'pub global list' is fast, let's run it first to find out if coverage package is already activated.
// Following 'pub global activate' is long and may be cancelled by user
checkIfCoverageActivated(dartPubPath);
// run 'pub global activate' regardless of activation status, because it checks for the coverage package update
final ProcessOutput activateOutput = new CapturingProcessHandler(
new GeneralCommandLine().withExePath(dartPubPath).withParameters("global", "activate", "coverage").withRedirectErrorStream(true))
.runProcessWithProgressIndicator(ProgressManager.getInstance().getProgressIndicator());
if (activateOutput.getExitCode() != 0) {
LOG.warn("'pub global activate coverage' exit code: " + activateOutput.getExitCode() +
", stdout:\n" + activateOutput.getStdout());
}
if (!myCoveragePackageActivated) {
checkIfCoverageActivated(dartPubPath);
}
}
catch (ExecutionException e) {
LOG.warn(e);
}
}, "Activating Coverage Package...", true, project);
// Even if 'pub global activate' process has been cancelled we can proceed if coverage already activated
return myCoveragePackageActivated;
}
private void checkIfCoverageActivated(@NotNull final String dartPubPath) throws ExecutionException {
final ProcessOutput listOutput = new CapturingProcessHandler(
new GeneralCommandLine().withExePath(dartPubPath).withParameters("global", "list").withRedirectErrorStream(true))
.runProcessWithProgressIndicator(ProgressManager.getInstance().getProgressIndicator());
final String listOutputStdout = listOutput.getStdout();
if (listOutput.getExitCode() == 0) {
if (listOutputStdout.startsWith("coverage ") || listOutputStdout.contains("\ncoverage ")) {
myCoveragePackageActivated = true;
}
}
else {
LOG.warn("'pub global list' exit code: " + listOutput.getExitCode() + ", stdout:\n" + listOutputStdout);
}
}
}