/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.plugin.testing.ide; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.che.api.core.model.machine.Machine; import org.eclipse.che.api.machine.shared.dto.execagent.ProcessStartResponseDto; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.PromiseError; import org.eclipse.che.api.promises.client.PromiseProvider; import org.eclipse.che.api.promises.client.js.Executor; import org.eclipse.che.api.promises.client.js.Executor.ExecutorBody; import org.eclipse.che.api.promises.client.js.JsPromiseError; import org.eclipse.che.api.promises.client.js.RejectFunction; import org.eclipse.che.api.promises.client.js.ResolveFunction; import org.eclipse.che.api.testing.shared.TestResult; import org.eclipse.che.ide.MimeType; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.command.CommandImpl; import org.eclipse.che.ide.api.command.CommandManager; import org.eclipse.che.ide.api.machine.ExecAgentCommandManager; import org.eclipse.che.ide.api.machine.execagent.ExecAgentConsumer; import org.eclipse.che.ide.api.macro.MacroProcessor; import org.eclipse.che.ide.api.notification.StatusNotification; import org.eclipse.che.ide.command.goal.TestGoal; import org.eclipse.che.ide.dto.DtoFactory; import org.eclipse.che.ide.console.CommandConsoleFactory; import org.eclipse.che.ide.console.CommandOutputConsole; import org.eclipse.che.ide.processes.panel.ProcessesPanelPresenter; import org.eclipse.che.ide.rest.AsyncRequestFactory; import org.eclipse.che.ide.rest.DtoUnmarshallerFactory; import org.eclipse.che.ide.rest.HTTPHeader; import com.google.gwt.http.client.URL; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; import com.google.inject.Inject; import com.google.inject.Singleton; import static org.eclipse.che.api.workspace.shared.Constants.COMMAND_PREVIEW_URL_ATTRIBUTE_NAME; /** * Client for calling test services * * @author Mirage Abeysekara * @author David Festal */ @Singleton public class TestServiceClient { private final static RegExp mavenCleanBuildPattern = RegExp.compile("(.*)mvn +clean +install +(\\-f +\\$\\{current\\.project\\.path\\}.*)"); public static final String PROJECT_BUILD_NOT_STARTED_MESSAGE = "The project build could not be started (see Build output). " + "Test run is cancelled.\n" + "You should probably check the settings of the 'test-compile' command."; public static final String PROJECT_BUILD_FAILED_MESSAGE = "The project build failed (see Build output). " + "Test run is cancelled.\n" + "You might want to check the settings of the 'test-compile' command."; public static final String EXECUTING_TESTS_MESSAGE = "Executing test session."; private final AppContext appContext; private final AsyncRequestFactory asyncRequestFactory; private final DtoUnmarshallerFactory dtoUnmarshallerFactory; private final CommandManager commandManager; private final ExecAgentCommandManager execAgentCommandManager; private final PromiseProvider promiseProvider; private final MacroProcessor macroProcessor; private final CommandConsoleFactory commandConsoleFactory; private final ProcessesPanelPresenter processesPanelPresenter; private final TestGoal testGoal; @Inject public TestServiceClient(AppContext appContext, AsyncRequestFactory asyncRequestFactory, DtoUnmarshallerFactory dtoUnmarshallerFactory, DtoFactory dtoFactory, CommandManager commandManager, ExecAgentCommandManager execAgentCommandManager, PromiseProvider promiseProvider, MacroProcessor macroProcessor, CommandConsoleFactory commandConsoleFactory, ProcessesPanelPresenter processesPanelPresenter, TestGoal testGoal) { this.appContext = appContext; this.asyncRequestFactory = asyncRequestFactory; this.dtoUnmarshallerFactory = dtoUnmarshallerFactory; this.commandManager = commandManager; this.execAgentCommandManager = execAgentCommandManager; this.promiseProvider = promiseProvider; this.macroProcessor = macroProcessor; this.commandConsoleFactory = commandConsoleFactory; this.processesPanelPresenter = processesPanelPresenter; this.testGoal = testGoal; } public Promise<CommandImpl> getOrCreateTestCompileCommand() { List<CommandImpl> commands = commandManager.getCommands(); for (CommandImpl command : commands) { if (command.getName() != null && command.getName().startsWith("test-compile") && "mvn".equals(command.getType())) { return promiseProvider.resolve(command); } } for (CommandImpl command : commands) { if ("build".equals(command.getName()) && "mvn".equals(command.getType())) { String commandLine = command.getCommandLine(); MatchResult result = mavenCleanBuildPattern.exec(commandLine); if (result != null) { String testCompileCommandLine = mavenCleanBuildPattern.replace(commandLine, "$1mvn test-compile $2"); return commandManager.createCommand(testGoal.getId(), "mvn", "test-compile", testCompileCommandLine, new HashMap<String, String>()); } } } return promiseProvider.resolve(null); } public Promise<TestResult> getTestResult(String projectPath, String testFramework, Map<String, String> parameters) { return getTestResult(projectPath, testFramework, parameters, null); } Promise<TestResult> promiseFromExecutorBody(ExecutorBody<TestResult> executorBody) { return promiseProvider.create(Executor.create(executorBody)); } PromiseError promiseFromThrowable(Throwable t) { return JsPromiseError.create(t); } Promise<TestResult> runTestsAfterCompilation(String projectPath, String testFramework, Map<String, String> parameters, StatusNotification statusNotification, Promise<CommandImpl> compileCommand) { return compileCommand.thenPromise(command -> { final Machine machine; if (command == null) { machine = null; } else { machine = appContext.getDevMachine().getDescriptor(); } if (machine == null) { if (statusNotification != null) { statusNotification.setContent("Executing the tests without preliminary compilation."); } return sendTests(projectPath, testFramework, parameters); } if (statusNotification != null) { statusNotification.setContent("Compiling the project before starting the test session."); } return promiseFromExecutorBody(new ExecutorBody<TestResult>() { boolean compiled = false; @Override public void apply(final ResolveFunction<TestResult> resolve, RejectFunction reject) { macroProcessor.expandMacros(command.getCommandLine()).then(new Operation<String>() { @Override public void apply(String expandedCommandLine) throws OperationException { Map<String, String> attributes = new HashMap<>(); attributes.putAll(command.getAttributes()); attributes.remove(COMMAND_PREVIEW_URL_ATTRIBUTE_NAME); CommandImpl expandedCommand = new CommandImpl(command.getName(), expandedCommandLine, command.getType(), attributes); final CommandOutputConsole console = commandConsoleFactory.create(expandedCommand, machine); final String machineId = machine.getId(); processesPanelPresenter.addCommandOutput(machineId, console); ExecAgentConsumer<ProcessStartResponseDto> processPromise = execAgentCommandManager.startProcess(machineId, expandedCommand); processPromise.then(startResonse -> { if (!startResonse.getAlive()) { reject.apply(promiseFromThrowable(new Throwable(PROJECT_BUILD_NOT_STARTED_MESSAGE))); } }).thenIfProcessStartedEvent(console.getProcessStartedConsumer()).thenIfProcessStdErrEvent(evt -> { if (evt.getText().contains("BUILD SUCCESS")) { compiled = true; } console.getStdErrConsumer().accept(evt); }).thenIfProcessStdOutEvent(evt -> { if (evt.getText().contains("BUILD SUCCESS")) { compiled = true; } console.getStdOutConsumer().accept(evt); }).thenIfProcessDiedEvent(evt -> { console.getProcessDiedConsumer().accept(evt); if (compiled) { if (statusNotification != null) { statusNotification.setContent(EXECUTING_TESTS_MESSAGE); } sendTests(projectPath, testFramework, parameters).then(new Operation<TestResult>() { @Override public void apply(TestResult result) throws OperationException { resolve.apply(result); } }, new Operation<PromiseError>() { @Override public void apply(PromiseError error) throws OperationException { reject.apply(error); } }); } else { reject.apply(promiseFromThrowable(new Throwable(PROJECT_BUILD_FAILED_MESSAGE))); } }); } }); } }); }); } public Promise<TestResult> getTestResult(String projectPath, String testFramework, Map<String, String> parameters, StatusNotification statusNotification) { return runTestsAfterCompilation(projectPath, testFramework, parameters, statusNotification, getOrCreateTestCompileCommand()); } public Promise<TestResult> sendTests(String projectPath, String testFramework, Map<String, String> parameters) { StringBuilder sb = new StringBuilder(); if (parameters != null) { for (Map.Entry<String, String> e : parameters.entrySet()) { if (sb.length() > 0) { sb.append('&'); } sb.append(URL.encode(e.getKey())).append('=').append(URL.encode(e.getValue())); } } String url = appContext.getDevMachine().getWsAgentBaseUrl() + "/che/testing/run/?projectPath=" + projectPath + "&testFramework=" + testFramework + "&" + sb.toString(); return asyncRequestFactory.createGetRequest(url).header(HTTPHeader.ACCEPT, MimeType.APPLICATION_JSON) .send(dtoUnmarshallerFactory.newUnmarshaller(TestResult.class)); } }