/*******************************************************************************
* Copyright (c) 2017 RedHat, Inc.
* 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:
* RedHat, Inc. - initial commit
*******************************************************************************/
package org.eclipse.che.plugin.testing.ide;
import com.google.gwtmockito.GwtMockitoTestRunner;
import org.eclipse.che.api.core.model.machine.Command;
import org.eclipse.che.api.core.model.machine.Machine;
import org.eclipse.che.api.machine.shared.dto.execagent.ProcessStartResponseDto;
import org.eclipse.che.api.machine.shared.dto.execagent.event.DtoWithPid;
import org.eclipse.che.api.machine.shared.dto.execagent.event.ProcessDiedEventDto;
import org.eclipse.che.api.machine.shared.dto.execagent.event.ProcessStartedEventDto;
import org.eclipse.che.api.machine.shared.dto.execagent.event.ProcessStdErrEventDto;
import org.eclipse.che.api.machine.shared.dto.execagent.event.ProcessStdOutEventDto;
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.testing.shared.TestResult;
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.DevMachine;
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.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Arrays.asList;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyMapOf;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Test for the TestServiceClient class.
*
* @author David Festal
*/
@RunWith(GwtMockitoTestRunner.class)
public class TestServiceClientTest implements MockitoPrinter {
// Context
@Mock
private AppContext appContext;
@Mock
private AsyncRequestFactory asyncRequestFactory;
@Mock
private DtoUnmarshallerFactory dtoUnmarshallerFactory;
@Mock
private CommandManager commandManager;
@Mock
private ExecAgentCommandManager execAgentCommandManager;
@Mock
private PromiseProvider promiseProvider;
@Mock
private MacroProcessor macroProcessor;
@Mock
private CommandConsoleFactory commandConsoleFactory;
@Mock
private ProcessesPanelPresenter processesPanelPresenter;
@Mock
private DtoFactory dtoFactory;
@Mock
private TestGoal testGoal;
@Mock
private StatusNotification statusNotification;
@Mock
private DevMachine devMachine;
@Mock
private Machine machine;
@Mock
private CommandOutputConsole commandOutputConsole;
private TestServiceClient testServiceClient = null;
@Spy
private final List<DtoWithPid> consoleEvents = new ArrayList<>();
private static final String rootOfProjects = "/projects";
private Map<String, String> parameters = new HashMap<>();
private String testFramework = "junit";
private String projectPath = "sampleProject";
@SuppressWarnings("rawtypes")
private Map<Class< ? >, Operation> operationsOnProcessEvents = new HashMap<>();
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
testServiceClient = spy(new TestServiceClient(appContext, asyncRequestFactory, dtoUnmarshallerFactory, dtoFactory, commandManager,
execAgentCommandManager, promiseProvider, macroProcessor, commandConsoleFactory,
processesPanelPresenter, testGoal));
doReturn(new PromiseMocker<TestResult>().getPromise()).when(testServiceClient).sendTests(anyString(), anyString(),
anyMapOf(String.class, String.class));
doAnswer(new FunctionAnswer<Executor.ExecutorBody<TestResult>, Promise<TestResult>>(executorBody -> {
ExecutorPromiseMocker<TestResult> mocker = new ExecutorPromiseMocker<TestResult>(executorBody,
(testResult, thisMocker) -> {
thisMocker.applyOnThenOperation(testResult);
return null;
},
(promiseError, thisMocker) -> {
thisMocker.applyOnCatchErrorOperation(promiseError);
return null;
});
executorBody.apply(mocker.getResolveFunction(), mocker.getRejectFunction());
return mocker.getPromise();
})).when(testServiceClient).promiseFromExecutorBody(Matchers.<Executor.ExecutorBody<TestResult>> any());
doAnswer(new FunctionAnswer<Throwable, PromiseError>(throwable -> {
PromiseError promiseError = mock(PromiseError.class);
when(promiseError.getCause()).thenReturn(throwable);
return promiseError;
})).when(testServiceClient).promiseFromThrowable(any(Throwable.class));
when(appContext.getDevMachine()).thenReturn(devMachine);
when(machine.getId()).thenReturn("DevMachineId");
doAnswer(new FunctionAnswer<String, Promise<String>>(commandLine -> {
String processedCommandLine = commandLine.replace("${current.project.path}", rootOfProjects + "/" + projectPath);
return new PromiseMocker<String>().applyOnThenOperation(processedCommandLine).getPromise();
})).when(macroProcessor).expandMacros(anyString());
when(commandConsoleFactory.create(any(CommandImpl.class), any(Machine.class))).then(createCall -> {
CommandOutputConsole commandOutputConsole = mock(CommandOutputConsole.class);
when(commandOutputConsole.getProcessStartedConsumer()).thenReturn(processStartedEvent -> {
consoleEvents.add(processStartedEvent);
});
when(commandOutputConsole.getProcessDiedConsumer()).thenReturn(processDiedEvent -> {
consoleEvents.add(processDiedEvent);
});
when(commandOutputConsole.getStdErrConsumer()).thenReturn(processStdErrEvent -> {
consoleEvents.add(processStdErrEvent);
});
when(commandOutputConsole.getStdOutConsumer()).thenReturn(processStdOutEvent -> {
consoleEvents.add(processStdOutEvent);
});
return commandOutputConsole;
});
consoleEvents.clear();
when(execAgentCommandManager.startProcess(anyString(), any(Command.class))).then(startProcessCall -> {
@SuppressWarnings("unchecked")
ExecAgentConsumer<ProcessStartResponseDto> execAgentConsumer =
(ExecAgentConsumer<ProcessStartResponseDto>)mock(ExecAgentConsumer.class);
class ProcessEventForward<DtoType> extends FunctionAnswer<Operation<DtoType>, ExecAgentConsumer<ProcessStartResponseDto>> {
public ProcessEventForward(Class<DtoType> dtoClass) {
super(new java.util.function.Function<Operation<DtoType>, ExecAgentConsumer<ProcessStartResponseDto>>() {
@Override
public ExecAgentConsumer<ProcessStartResponseDto> apply(Operation<DtoType> op) {
operationsOnProcessEvents.put(dtoClass, op);
return execAgentConsumer;
}
});
}
}
when(execAgentConsumer.then(any())).then(new ProcessEventForward<>(ProcessStartResponseDto.class));
when(execAgentConsumer.thenIfProcessStartedEvent(any())).then(new ProcessEventForward<>(ProcessStartedEventDto.class));
when(execAgentConsumer.thenIfProcessDiedEvent(any())).then(new ProcessEventForward<>(ProcessDiedEventDto.class));
when(execAgentConsumer.thenIfProcessStdErrEvent(any())).then(new ProcessEventForward<>(ProcessStdErrEventDto.class));
when(execAgentConsumer.thenIfProcessStdOutEvent(any())).then(new ProcessEventForward<>(ProcessStdOutEventDto.class));
return execAgentConsumer;
});
operationsOnProcessEvents.clear();
when(testGoal.getId()).thenReturn("Test");
}
@SuppressWarnings("unchecked")
private void triggerProcessEvents(DtoWithPid... processEvents) {
for (DtoWithPid event : processEvents) {
operationsOnProcessEvents.entrySet().stream().filter(entry -> {
return entry.getKey().isAssignableFrom(event.getClass());
}).map(Map.Entry::getValue).forEach(op -> {
try {
op.apply(event);
} catch (OperationException e) {
e.printStackTrace();
}
});
}
}
@Test
public void createCompileCommandFromStandardMavenCommands() {
when(commandManager.getCommands()).thenReturn(asList(new CommandImpl("run",
"mvn run -f ${current.project.path}",
"mvn"),
new CommandImpl("build",
"mvn clean install -f ${current.project.path}",
"mvn")));
testServiceClient.getOrCreateTestCompileCommand();
verify(commandManager).createCommand("Test",
"mvn",
"test-compile",
"mvn test-compile -f ${current.project.path}",
Collections.emptyMap());
}
@Test
public void createCompileCommandFromSCLEnabledMavenBuildCommand() {
when(commandManager.getCommands()).thenReturn(asList(new CommandImpl("build",
"scl enable rh-maven33 'mvn clean install -f ${current.project.path}'",
"mvn")));
testServiceClient.getOrCreateTestCompileCommand();
verify(commandManager).createCommand("Test",
"mvn",
"test-compile",
"scl enable rh-maven33 'mvn test-compile -f ${current.project.path}'",
Collections.emptyMap());
}
@Test
public void reuseExistingCompileCommand() {
CommandImpl existingCompileCommand = new CommandImpl("test-compile",
"mvn test-compile -f ${current.project.path}",
"mvn");
when(commandManager.getCommands()).thenReturn(asList(new CommandImpl("run",
"mvn run -f ${current.project.path}",
"mvn"),
new CommandImpl("build",
"mvn clean install -f ${current.project.path}",
"mvn"),
existingCompileCommand));
testServiceClient.getOrCreateTestCompileCommand();
verify(promiseProvider).resolve(existingCompileCommand);
}
@Test
public void noBuildCommand() {
when(commandManager.getCommands()).thenReturn(asList(new CommandImpl("customBuild",
"mvn clean install -f ${current.project.path}",
"mvn")));
testServiceClient.getOrCreateTestCompileCommand();
verify(promiseProvider).resolve(null);
}
@Test
public void buildCommandNotAMavenCommand() {
when(commandManager.getCommands()).thenReturn(asList(new CommandImpl("build",
"mvn clean install -f ${current.project.path}",
"someOtherType")));
testServiceClient.getOrCreateTestCompileCommand();
verify(promiseProvider).resolve(null);
}
@Test
public void mavenBuildCommandHasNoCleanInstallPart() {
when(commandManager.getCommands()).thenReturn(asList(new CommandImpl("build",
"mvn clean SomeOtherGoalInTeMiddle install -f ${current.project.path}",
"mvn")));
testServiceClient.getOrCreateTestCompileCommand();
verify(promiseProvider).resolve(null);
}
private Promise<CommandImpl> createCommandPromise(CommandImpl command) {
return new PromiseMocker<CommandImpl>().applyOnThenPromise(command).getPromise();
}
@Test
public void runTestsDirectlyBecauseNoCompilationCommand() {
Promise<CommandImpl> compileCommandPromise = createCommandPromise(null);
testServiceClient.runTestsAfterCompilation(projectPath, testFramework, parameters, statusNotification, compileCommandPromise);
verify(statusNotification).setContent("Executing the tests without preliminary compilation.");
verify(execAgentCommandManager, never()).startProcess(anyString(), Matchers.<Command> any());
verify(testServiceClient).sendTests(projectPath, testFramework, parameters);
}
@Test
public void runTestsDirectlyBecauseNoDevMachine() {
Promise<CommandImpl> compileCommandPromise = createCommandPromise(new CommandImpl("test-compile",
"mvn test-compile -f ${current.project.path}",
"mvn"));
when(devMachine.getDescriptor()).thenReturn(null);
testServiceClient.runTestsAfterCompilation(projectPath, testFramework, parameters, statusNotification, compileCommandPromise);
verify(statusNotification).setContent("Executing the tests without preliminary compilation.");
verify(execAgentCommandManager, never()).startProcess(anyString(), Matchers.<Command> any());
verify(testServiceClient).sendTests(projectPath, testFramework, parameters);
}
private ProcessStartResponseDto processStartResponse(boolean alive) {
ProcessStartResponseDto event = mock(ProcessStartResponseDto.class);
when(event.getAlive()).thenReturn(alive);
return event;
}
private ProcessStartedEventDto processStarted() {
ProcessStartedEventDto event = mock(ProcessStartedEventDto.class);
when(event.toString()).thenReturn("Started");
return event;
}
private ProcessDiedEventDto processDied() {
ProcessDiedEventDto event = mock(ProcessDiedEventDto.class);
when(event.toString()).thenReturn("Died");
return event;
}
private ProcessStdErrEventDto processStdErr(final String text) {
ProcessStdErrEventDto event = mock(ProcessStdErrEventDto.class);
when(event.getText()).thenReturn(text);
when(event.toString()).thenReturn("StdErr - " + text);
return event;
}
private ProcessStdOutEventDto processStdOut(String text) {
ProcessStdOutEventDto event = mock(ProcessStdOutEventDto.class);
when(event.getText()).thenReturn(text);
when(event.toString()).thenReturn("StdOut - " + text);
return event;
}
@Test
public void cancelledTestsBecauseCompilationNotStarted() {
Promise<CommandImpl> compileCommandPromise = createCommandPromise(new CommandImpl(
"test-compile",
"mvn test-compile -f ${current.project.path}",
"mvn"));
when(devMachine.getDescriptor()).thenReturn(machine);
Promise<TestResult> result = testServiceClient.runTestsAfterCompilation(projectPath, testFramework, parameters, statusNotification,
compileCommandPromise);
triggerProcessEvents(processStartResponse(false));
verify(testServiceClient, never()).sendTests(anyString(), anyString(), anyMapOf(String.class, String.class));
verify(statusNotification).setContent("Compiling the project before starting the test session.");
result.catchError(new Operation<PromiseError>() {
@Override
public void apply(PromiseError promiseError) throws OperationException {
Throwable cause = promiseError.getCause();
Assert.assertNotNull(cause);
Assert.assertEquals(TestServiceClient.PROJECT_BUILD_NOT_STARTED_MESSAGE, cause.getMessage());
}
});
}
@Test
public void cancelledTestsBecauseCompilationFailed() {
Promise<CommandImpl> compileCommandPromise = createCommandPromise(new CommandImpl(
"test-compile",
"mvn test-compile -f ${current.project.path}",
"mvn"));
when(devMachine.getDescriptor()).thenReturn(machine);
Promise<TestResult> result = testServiceClient.runTestsAfterCompilation(projectPath, testFramework, parameters, statusNotification,
compileCommandPromise);
triggerProcessEvents(processStartResponse(true),
processStarted(),
processStdErr("A small warning"),
processStdOut("BUILD FAILURE"),
processDied());
verify(testServiceClient, never()).sendTests(anyString(), anyString(), anyMapOf(String.class, String.class));
verify(statusNotification).setContent("Compiling the project before starting the test session.");
result.catchError(new Operation<PromiseError>() {
@Override
public void apply(PromiseError promiseError) throws OperationException {
Throwable cause = promiseError.getCause();
Assert.assertNotNull(cause);
Assert.assertEquals(TestServiceClient.PROJECT_BUILD_FAILED_MESSAGE, cause.getMessage());
}
});
}
@Test
@Ignore
public void sucessfulTestsAfterCompilation() {
Promise<CommandImpl> compileCommandPromise = createCommandPromise(new CommandImpl(
"test-compile",
"mvn test-compile -f ${current.project.path}",
"mvn"));
when(devMachine.getDescriptor()).thenReturn(machine);
Promise<TestResult> resultPromise = testServiceClient.runTestsAfterCompilation(projectPath, testFramework, parameters,
statusNotification,
compileCommandPromise);
triggerProcessEvents(processStartResponse(true),
processStarted(),
processStdErr("A small warning"),
processStdOut("BUILD SUCCESS"),
processDied());
verify(testServiceClient).sendTests(projectPath, testFramework, parameters);
verify(statusNotification).setContent("Compiling the project before starting the test session.");
verify(execAgentCommandManager).startProcess(
"DevMachineId",
new CommandImpl(
"test-compile",
"mvn test-compile -f " + rootOfProjects + "/" + projectPath,
"mvn"));
verify(statusNotification).setContent(TestServiceClient.EXECUTING_TESTS_MESSAGE);
resultPromise.then(testResult -> {
Assert.assertNotNull(testResult);
});
ArrayList<String> eventStrings = new ArrayList<>();
for (DtoWithPid event : consoleEvents) {
eventStrings.add(event.toString());
}
Assert.assertEquals(eventStrings,
Arrays.asList("Started",
"StdErr - A small warning",
"StdOut - BUILD SUCCESS",
"Died"));
}
}