/******************************************************************************* * Copyright (c) 2016 Red Hat. * 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: * Red Hat - Initial Contribution *******************************************************************************/ package org.eclipse.linuxtools.internal.docker.ui.launch; import static org.assertj.core.api.Assertions.assertThat; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.linuxtools.docker.core.DockerConnectionManager; import org.eclipse.linuxtools.docker.core.DockerException; import org.eclipse.linuxtools.internal.docker.core.DockerCompose; import org.eclipse.linuxtools.internal.docker.core.DockerConnection; import org.eclipse.linuxtools.internal.docker.core.ProcessLauncher; import org.eclipse.linuxtools.internal.docker.ui.consoles.ConsoleMessages; import org.eclipse.linuxtools.internal.docker.ui.testutils.CustomMatchers; import org.eclipse.linuxtools.internal.docker.ui.testutils.MockDockerClientFactory; import org.eclipse.linuxtools.internal.docker.ui.testutils.MockDockerConnectionFactory; import org.eclipse.linuxtools.internal.docker.ui.testutils.ProjectInitializationRule; import org.eclipse.linuxtools.internal.docker.ui.testutils.RunWithProject; import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.ButtonAssertions; import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.ClearConnectionManagerRule; import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.ClearLaunchConfigurationsRule; import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.CloseShellRule; import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.CloseWelcomePageRule; import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.ComboAssertions; import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.ConsoleViewRule; import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.DockerConnectionManagerUtils; import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.ProjectExplorerViewRule; import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.SWTUtils; import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.TextAssertions; import org.eclipse.linuxtools.internal.docker.ui.testutils.swt.ToolbarButtonAssertions; import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView; import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu; import org.eclipse.swtbot.swt.finder.widgets.SWTBotToolbarButton; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; import org.junit.Before; import org.junit.ClassRule; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import com.spotify.docker.client.DockerClient; /** * Testing the {@link DockerCompose} utility class using SWTBot. */ public class DockerComposeSWTBotTest { @ClassRule public static CloseWelcomePageRule closeWelcomePage = new CloseWelcomePageRule( CloseWelcomePageRule.DOCKER_PERSPECTIVE_ID); @Rule public ClearConnectionManagerRule clearConnectionManager = new ClearConnectionManagerRule(); @Rule public ProjectInitializationRule projectInit = new ProjectInitializationRule(); @Rule public ClearLaunchConfigurationsRule clearLaunchConfig = new ClearLaunchConfigurationsRule( IDockerComposeLaunchConfigurationConstants.CONFIG_TYPE_ID); @Rule public CloseShellRule closeShell = new CloseShellRule(IDialogConstants.CLOSE_LABEL); @Rule public ConsoleViewRule consoleViewRule = new ConsoleViewRule(); @Rule public ProjectExplorerViewRule projectExplorerViewRule = new ProjectExplorerViewRule(); private SWTWorkbenchBot bot = new SWTWorkbenchBot(); private CountDownLatch latch; @Before public void setupMockedProcessLauncher() throws DockerException, InterruptedException { // configure the 'docker-compose up' mocks with a CountDownLatch to // simulate a long-running process final ProcessLauncher mockProcessLauncher = Mockito.mock(ProcessLauncher.class, Mockito.RETURNS_DEEP_STUBS); DockerCompose.getInstance().setProcessLauncher(mockProcessLauncher); setupDockerComposeUpMockProcess(mockProcessLauncher); // configure the 'docker-compose stop' mocks which release the // CountDownLatch to halt the long-running process setupDockerComposeStopMockProcess(mockProcessLauncher); } private void setupDockerComposeUpMockProcess(final ProcessLauncher mockProcessLauncher) throws DockerException, InterruptedException { final Process mockDockerComposeUpProcess = Mockito.mock(Process.class); Mockito.when(mockDockerComposeUpProcess.getInputStream()) .thenReturn(new ByteArrayInputStream("up!\n".getBytes())); Mockito.when(mockDockerComposeUpProcess.getErrorStream()).thenReturn(new ByteArrayInputStream("".getBytes())); Mockito.when(mockDockerComposeUpProcess.getOutputStream()).thenReturn(new ByteArrayOutputStream()); Mockito.when(mockProcessLauncher.processBuilder(Matchers.anyString(), Matchers.eq(DockerCompose.getDockerComposeCommandName()), CustomMatchers.arrayContains("up")) .workingDir(Matchers.anyString()).start()).thenReturn(mockDockerComposeUpProcess); latch = new CountDownLatch(1); Mockito.when(mockDockerComposeUpProcess.waitFor()).then(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { latch.await(5, TimeUnit.SECONDS); return 0; } }); } private void setupDockerComposeStopMockProcess(final ProcessLauncher mockProcessLauncher) throws DockerException, InterruptedException { final Process mockDockerComposeStopProcess = Mockito.mock(Process.class); Mockito.when(mockDockerComposeStopProcess.getInputStream()) .thenReturn(new ByteArrayInputStream("stop\n".getBytes())); Mockito.when(mockDockerComposeStopProcess.getErrorStream()).thenReturn(new ByteArrayInputStream("".getBytes())); Mockito.when(mockDockerComposeStopProcess.getOutputStream()).thenReturn(new ByteArrayOutputStream()); Mockito.when(mockProcessLauncher.processBuilder(Matchers.anyString(), Matchers.eq(DockerCompose.getDockerComposeCommandName()), CustomMatchers.arrayContains("stop")) .workingDir(Matchers.anyString()).start()).thenReturn(mockDockerComposeStopProcess); Mockito.when(mockDockerComposeStopProcess.waitFor()).then(invocation -> { latch.countDown(); return 0; }); } /** * @return the {@link SWTBotMenu} for the "Run as > Docker Image Build" * shortcut */ private SWTBotMenu getRunAsDockerComposeContextMenu(final String projectName, final String dockerComposeFileName) { final SWTBotView projectExplorerBotView = this.projectExplorerViewRule.getProjectExplorerBotView(); // make sure the project explorer view is visible, in case it was hidden // by another view. projectExplorerBotView.setFocus(); final SWTBotTreeItem fooProjectTreeItem = SWTUtils.getTreeItem(projectExplorerBotView, projectName); assertThat(fooProjectTreeItem).isNotNull(); SWTUtils.syncExec(() -> fooProjectTreeItem.expand()); final SWTBotTreeItem dockerfileTreeItem = SWTUtils.getTreeItem(fooProjectTreeItem, dockerComposeFileName); assertThat(dockerfileTreeItem).isNotNull(); SWTUtils.select(dockerfileTreeItem); final SWTBotMenu runAsDockerComposeMenu = SWTUtils.getContextMenu( projectExplorerBotView.bot().tree(), "Run As", "1 Docker Compose"); return runAsDockerComposeMenu; } @Test @RunWithProject("foo") public void shouldDisableCommandOnFirstCallWhenMissingConnection() { // given no connection ClearConnectionManagerRule.removeAllConnections(DockerConnectionManager.getInstance()); // when SWTUtils.asyncExec(() -> getRunAsDockerComposeContextMenu("foo", "docker-compose.yml").click()); // then expect an error dialog because no Docker connection exists assertThat(bot.shell(LaunchMessages.getString("DockerComposeUpShortcut.no.connections.msg"))).isNotNull(); // closing the wizard SWTUtils.syncExec(() -> { bot.button("No").click(); }); } @Test @RunWithProject("foo") public void shouldStartDockerComposeFromScratch() throws CoreException { // given final DockerClient client = MockDockerClientFactory.build(); final DockerConnection dockerConnection = MockDockerConnectionFactory.from("Test", client) .withDefaultTCPConnectionSettings(); DockerConnectionManagerUtils.configureConnectionManager(dockerConnection); // when SWTUtils.asyncExec(() -> getRunAsDockerComposeContextMenu("foo", "docker-compose.yml").click()); // then confirm the connection bot.button("OK").click(); // wait for the job to run SWTUtils.waitForJobsToComplete(); // then expect the console to be displayed assertThat(SWTUtils.isConsoleViewVisible(this.bot)).isTrue(); // expect the 'stop' button to be enabled final SWTBotToolbarButton consoleToolbarStopButton = SWTUtils.getConsoleToolbarButtonWithTooltipText(bot, ConsoleMessages.getString("DockerComposeStopAction.tooltip")); ToolbarButtonAssertions.assertThat(consoleToolbarStopButton).isEnabled(); // verify that the launch configuration was saved final ILaunchConfiguration launchConfiguration = LaunchConfigurationUtils.getLaunchConfigurationByName( IDockerComposeLaunchConfigurationConstants.CONFIG_TYPE_ID, "Docker Compose [foo]"); assertThat(launchConfiguration).isNotNull(); // verify the latch assertThat(latch.getCount()).isEqualTo(1); } @Test @RunWithProject("foo") public void shouldStartDockerComposeWithExistingLaunchConfiguration() throws CoreException { // given final DockerClient client = MockDockerClientFactory.build(); final DockerConnection dockerConnection = MockDockerConnectionFactory.from("Test", client) .withDefaultTCPConnectionSettings(); DockerConnectionManagerUtils.configureConnectionManager(dockerConnection); final IFile dockerComposeScript = projectInit.getProject().getFile("docker-compose.yml"); LaunchConfigurationUtils.createDockerComposeUpLaunchConfiguration(dockerConnection, dockerComposeScript); // when SWTUtils.asyncExec(() -> getRunAsDockerComposeContextMenu("foo", "docker-compose.yml").click()); // then confirm the connection final SWTBotToolbarButton consoleToolbarStopButton = SWTUtils.getConsoleToolbarButtonWithTooltipText(bot, ConsoleMessages.getString("DockerComposeStopAction.tooltip")); ToolbarButtonAssertions.assertThat(consoleToolbarStopButton).isEnabled(); } @Test @RunWithProject("foo") public void shouldStopDockerCompose() throws CoreException { // given shouldStartDockerComposeFromScratch(); // when final SWTBotToolbarButton consoleToolbarStopButton = SWTUtils.getConsoleToolbarButtonWithTooltipText(bot, ConsoleMessages.getString("DockerComposeStopAction.tooltip")); ToolbarButtonAssertions.assertThat(consoleToolbarStopButton).isEnabled(); consoleToolbarStopButton.click(); // then // verify the latch is down assertThat(latch.getCount()).isEqualTo(0); // verify the stop button is disabled ToolbarButtonAssertions.assertThat(consoleToolbarStopButton).isNotEnabled(); } @Test @RunWithProject("foo") public void shouldRestartDockerCompose() throws InterruptedException, DockerException { // given final DockerClient client = MockDockerClientFactory.build(); final DockerConnection dockerConnection = MockDockerConnectionFactory.from("Test", client) .withDefaultTCPConnectionSettings(); DockerConnectionManagerUtils.configureConnectionManager(dockerConnection); // when starting without launch config SWTUtils.asyncExec(() -> getRunAsDockerComposeContextMenu("foo", "docker-compose.yml").click()); bot.button("OK").click(); // wait for the job to run SWTUtils.waitForJobsToComplete(); // when stopping final SWTBotToolbarButton consoleToolbarStopButton = SWTUtils.getConsoleToolbarButtonWithTooltipText(bot, ConsoleMessages.getString("DockerComposeStopAction.tooltip")); ToolbarButtonAssertions.assertThat(consoleToolbarStopButton).isEnabled(); consoleToolbarStopButton.click(); // redo the setup to get a new mock process setupMockedProcessLauncher(); // when restarting SWTUtils.asyncExec(() -> getRunAsDockerComposeContextMenu("foo", "docker-compose.yml").click()); // wait for the job to run SWTUtils.waitForJobsToComplete(); // then ToolbarButtonAssertions.assertThat(consoleToolbarStopButton).isEnabled(); } @Test @RunWithProject("foo") @Ignore // ignored for now because the "Run" menu from the toolbar remains // visible (on macOS) and this // has side-effects on the other tests that fail because the widgets are not // found. public void shouldValidateLaunchConfiguration() throws CoreException { // given final DockerClient client = MockDockerClientFactory.build(); final DockerConnection dockerConnection = MockDockerConnectionFactory.from("Test", client) .withDefaultTCPConnectionSettings(); DockerConnectionManagerUtils.configureConnectionManager(dockerConnection); final IFile dockerComposeScript = projectInit.getProject().getFile("docker-compose.yml"); LaunchConfigurationUtils.createDockerComposeUpLaunchConfiguration(dockerConnection, dockerComposeScript); // when bot.toolbarDropDownButtonWithTooltip("Run").menuItem("Run Configurations...").click(); final SWTBotTreeItem dockerComposeTreeItem = SWTUtils.expand(bot.tree(), "Docker Compose"); SWTUtils.select(dockerComposeTreeItem, "Docker Compose [foo]"); // verify that the config is set and the form can be closed with the // "OK" button ComboAssertions.assertThat(bot.comboBox(0)).isEnabled().itemSelected("Test"); TextAssertions.assertThat(bot.text(2)).isEnabled().textEquals("/foo"); ButtonAssertions.assertThat(bot.button("Run")).isEnabled(); } }