// Copyright 2015 ThoughtWorks, Inc. // This file is part of getgauge/Intellij-plugin. // getgauge/Intellij-plugin is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // getgauge/Intellij-plugin is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with getgauge/Intellij-plugin. If not, see <http://www.gnu.org/licenses/>. package com.thoughtworks.gauge.execution; import com.intellij.execution.CommonProgramRunConfigurationParameters; import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; import com.intellij.execution.application.ApplicationConfiguration; import com.intellij.execution.application.ApplicationConfigurationType; import com.intellij.execution.configuration.ConfigurationFactoryEx; import com.intellij.execution.configurations.*; import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.options.SettingsEditor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.JDOMExternalizer; import com.intellij.openapi.util.WriteExternalException; import com.jgoodies.common.base.Strings; import com.thoughtworks.gauge.Constants; import com.thoughtworks.gauge.GaugeConstant; import com.thoughtworks.gauge.exception.GaugeNotFoundException; import com.thoughtworks.gauge.settings.GaugeSettingsModel; import com.thoughtworks.gauge.util.GaugeUtil; import com.thoughtworks.gauge.util.SocketUtils; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.thoughtworks.gauge.GaugeConstant.ENV_FLAG; import static com.thoughtworks.gauge.GaugeConstant.GAUGE_DEBUG_OPTS_ENV; public class GaugeRunConfiguration extends LocatableConfigurationBase implements RunProfileWithCompileBeforeLaunchOption { public static final String SIMPLE_CONSOLE_FLAG = "--simple-console"; public static final String TAGS_FLAG = "--tags"; public static final String PARALLEL_FLAG = "--parallel"; private static final String PARALLEL_NODES_FLAG = "-n"; private static final String TABLE_ROWS_FLAG = "--table-rows"; public static final String GAUGE_CUSTOM_CLASSPATH = "gauge_custom_classpath"; public static final String SPEC_FILE_DELIMITER = "||"; private static final java.lang.String SPEC_FILE_DELIMITER_REGEX = "\\|\\|"; private static final Logger LOG = Logger.getInstance("#com.thoughtworks.gauge.execution.GaugeRunConfiguration"); private String specsToExecute; private Module module; private String environment; private String tags; private boolean execInParallel; private String parallelNodes; public ApplicationConfiguration programParameters; private String rowsRange; private String moduleName; public GaugeRunConfiguration(String name, Project project, ConfigurationFactoryEx configurationFactory) { super(project, configurationFactory, name); this.programParameters = new ApplicationConfiguration(name, project, ApplicationConfigurationType.getInstance()); } @NotNull @Override public SettingsEditor<? extends RunConfiguration> getConfigurationEditor() { return new GaugeExecutionConfigurationSettingsEditor(); } @Nullable @Override public RunProfileState getState(@NotNull Executor executor, @NotNull final ExecutionEnvironment env) throws ExecutionException { return new CommandLineState(env) { @NotNull @Override protected ProcessHandler startProcess() throws ExecutionException { GeneralCommandLine commandLine = new GeneralCommandLine(); try { GaugeSettingsModel settings = GaugeUtil.getGaugeSettings(); commandLine.setExePath(settings.getGaugePath()); Map<String, String> environment = commandLine.getEnvironment(); environment.put(Constants.GAUGE_ROOT, settings.getRootPath()); environment.put(Constants.GAUGE_HOME, settings.getHomePath()); } catch (GaugeNotFoundException e) { commandLine.setExePath(GaugeConstant.GAUGE); } finally { addFlags(commandLine, env); DebugInfo debugInfo = createDebugInfo(commandLine, env); return GaugeRunProcessHandler.runCommandLine(commandLine, debugInfo, getProject()); } } }; } private DebugInfo createDebugInfo(GeneralCommandLine commandLine, ExecutionEnvironment env) { if (isDebugExecution(env)) { String port = debugPort(); commandLine.getEnvironment().put(GAUGE_DEBUG_OPTS_ENV, port); return new DebugInfo(true, port); } return new DebugInfo(false, ""); } private String debugPort() { return String.valueOf(SocketUtils.findFreePortForApi()); } private void addFlags(GeneralCommandLine commandLine, ExecutionEnvironment env) { commandLine.addParameter(SIMPLE_CONSOLE_FLAG); if (!Strings.isBlank(tags)) { commandLine.addParameter(TAGS_FLAG); commandLine.addParameter(tags); } commandLine.setWorkDirectory(env.getProject().getBasePath()); Module module = getModule(); if (module != null) commandLine.setWorkDirectory(GaugeUtil.moduleDir(module)); if (!Strings.isBlank(environment)) { commandLine.addParameters(ENV_FLAG, environment); } addTableRowsRangeFlags(commandLine); addParallelExecFlags(commandLine, env); addProgramArguments(commandLine); addProjectClasspath(commandLine); if (!Strings.isBlank(specsToExecute)) { addSpecs(commandLine, specsToExecute); } } private boolean isDebugExecution(ExecutionEnvironment env) { return DefaultDebugExecutor.EXECUTOR_ID.equals(env.getExecutor().getId()); } private void addProjectClasspath(GeneralCommandLine commandLine) { Module module = getModule(); if (module != null) { String cp = GaugeUtil.classpathForModule(module); LOG.info(String.format("Setting `%s` to `%s`", GAUGE_CUSTOM_CLASSPATH, cp)); commandLine.getEnvironment().put(GAUGE_CUSTOM_CLASSPATH, cp); } } private void addTableRowsRangeFlags(GeneralCommandLine commandLine) { if (!Strings.isBlank(rowsRange)) { commandLine.addParameter(TABLE_ROWS_FLAG); commandLine.addParameter(rowsRange); } } private void addProgramArguments(GeneralCommandLine commandLine) { if (programParameters == null) { return; } String parameters = programParameters.getProgramParameters(); if (!Strings.isEmpty(parameters)) { commandLine.addParameters(programParameters.getProgramParameters().split(" ")); } Map<String, String> envs = programParameters.getEnvs(); if (!envs.isEmpty()) { commandLine.withEnvironment(envs); } if (!programParameters.getWorkingDirectory().isEmpty()) { commandLine.setWorkDirectory(new File(programParameters.getWorkingDirectory())); } } private void addParallelExecFlags(GeneralCommandLine commandLine, ExecutionEnvironment env) { if (parallelExec(env)) { commandLine.addParameter(PARALLEL_FLAG); try { if (!Strings.isEmpty(parallelNodes)) { int nodes = Integer.parseInt(this.parallelNodes); commandLine.addParameters(PARALLEL_NODES_FLAG, parallelNodes); } } catch (NumberFormatException e) { System.err.println("Incorrect number of parallel execution streams specified: " + parallelNodes); e.printStackTrace(); } } } private boolean parallelExec(ExecutionEnvironment env) { return execInParallel && !isDebugExecution(env); } private void addSpecs(GeneralCommandLine commandLine, String specsToExecute) { String[] specNames = specsToExecute.split(SPEC_FILE_DELIMITER_REGEX); for (String specName : specNames) { if (!specName.isEmpty()) { commandLine.addParameter(specName.trim()); } } } @Override public void readExternal(Element element) throws InvalidDataException { super.readExternal(element); environment = JDOMExternalizer.readString(element, "environment"); specsToExecute = JDOMExternalizer.readString(element, "specsToExecute"); tags = JDOMExternalizer.readString(element, "tags"); parallelNodes = JDOMExternalizer.readString(element, "parallelNodes"); execInParallel = JDOMExternalizer.readBoolean(element, "execInParallel"); programParameters.setProgramParameters(JDOMExternalizer.readString(element, "programParameters")); programParameters.setWorkingDirectory(JDOMExternalizer.readString(element, "workingDirectory")); this.moduleName = JDOMExternalizer.readString(element, "moduleName"); HashMap<String, String> envMap = new HashMap<>(); JDOMExternalizer.readMap(element, envMap, "envMap", "envMap"); programParameters.setEnvs(envMap); rowsRange = JDOMExternalizer.readString(element, "rowsRange"); } @Override public void writeExternal(Element element) throws WriteExternalException { super.writeExternal(element); JDOMExternalizer.write(element, "environment", environment); JDOMExternalizer.write(element, "specsToExecute", specsToExecute); JDOMExternalizer.write(element, "tags", tags); JDOMExternalizer.write(element, "parallelNodes", parallelNodes); JDOMExternalizer.write(element, "execInParallel", execInParallel); JDOMExternalizer.write(element, "programParameters", programParameters.getProgramParameters()); JDOMExternalizer.write(element, "workingDirectory", programParameters.getWorkingDirectory()); JDOMExternalizer.write(element, "moduleName", moduleName); JDOMExternalizer.writeMap(element, programParameters.getEnvs(), "envMap", "envMap"); JDOMExternalizer.write(element, "rowsRange", rowsRange); } @NotNull @Override public Module[] getModules() { return ModuleManager.getInstance(getProject()).getModules(); } public void setSpecsToExecute(String specsToExecute) { this.specsToExecute = specsToExecute; } public String getSpecsToExecute() { return specsToExecute; } public void setModule(Module module) { this.module = module; this.moduleName = module.getName(); } public Module getModule() { if (module == null) return ModuleManager.getInstance(getProject()).findModuleByName(this.moduleName); return module; } public String getEnvironment() { return environment; } public void setEnvironment(String environment) { this.environment = environment; } public String getTags() { return tags; } public void setTags(String tags) { this.tags = tags; } public void setSpecsArrayToExecute(List<String> specsArrayToExecute) { StringBuilder builder = new StringBuilder(""); for (String specName : specsArrayToExecute) { builder.append(specName); if (specsArrayToExecute.indexOf(specName) != specsArrayToExecute.size() - 1) { builder.append(SPEC_FILE_DELIMITER); } } setSpecsToExecute(builder.toString()); } public void setExecInParallel(boolean execInParallel) { this.execInParallel = execInParallel; } public boolean getExecInParallel() { return execInParallel; } public void setParallelNodes(String parallelNodes) { this.parallelNodes = parallelNodes; } public String getParallelNodes() { return parallelNodes; } public CommonProgramRunConfigurationParameters getProgramParameters() { return programParameters; } public String getRowsRange() { return rowsRange; } public void setRowsRange(String rowsRange) { this.rowsRange = rowsRange; } public class DebugInfo { private final boolean shouldDebug; private final String port; private String host = "localhost"; public DebugInfo(boolean shouldDebug, String port) { this.shouldDebug = shouldDebug; this.port = port; } public boolean shouldDebug() { return shouldDebug; } public String getPort() { return port; } public int getPortInt() { return Integer.parseInt(port); } public String getHost() { return host; } } }