diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerLaunchConfigurationDelegate.java b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerLaunchConfigurationDelegate.java index 89650b8fe0..d04d4b466f 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerLaunchConfigurationDelegate.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.localserver/src/com/google/cloud/tools/eclipse/appengine/localserver/server/LocalAppEngineServerLaunchConfigurationDelegate.java @@ -448,7 +448,8 @@ public void launch(ILaunchConfiguration configuration, String mode, final ILaunc generateServerRunConfiguration(configuration, server, mode, runnables); if (ILaunchManager.DEBUG_MODE.equals(mode)) { int debugPort = getDebugPort(); - setupDebugTarget(devServerRunConfiguration, launch, debugPort, monitor); + devServerRunConfiguration = + setupDebugTarget(devServerRunConfiguration, launch, debugPort, monitor); } IJavaProject javaProject = JavaCore.create(modules[0].getProject()); @@ -492,8 +493,13 @@ protected void openBrowserPage(final IServer server) { server.getName()); } - private void setupDebugTarget(RunConfiguration devServerRunConfiguration, ILaunch launch, - int debugPort, IProgressMonitor monitor) throws CoreException { + /** Set up the debug target to connect to the remote JVM. Returns the updated RunConfiguration. */ + private RunConfiguration setupDebugTarget( + RunConfiguration devServerRunConfiguration, + ILaunch launch, + int debugPort, + IProgressMonitor monitor) + throws CoreException { if (debugPort <= 0 || debugPort > 65535) { throw new IllegalArgumentException("Debug port is set to " + debugPort //$NON-NLS-1$ + ", should be between 1-65535"); //$NON-NLS-1$ @@ -511,7 +517,8 @@ private void setupDebugTarget(RunConfiguration devServerRunConfiguration, ILaunc JavaRuntime.getVMConnector(IJavaLaunchConfigurationConstants.ID_SOCKET_LISTEN_VM_CONNECTOR); if (connector == null) { abort("Cannot find Socket Listening connector", null, 0); //$NON-NLS-1$ - return; // keep JDT null analysis happy + // NOTREACHED + return null; // keep JDT null analysis happy } // Set JVM debugger connection parameters @@ -523,6 +530,7 @@ private void setupDebugTarget(RunConfiguration devServerRunConfiguration, ILaunc connectionParameters.put("timeout", Integer.toString(timeout)); //$NON-NLS-1$ connectionParameters.put("connectionLimit", "0"); //$NON-NLS-1$ //$NON-NLS-2$ connector.connect(connectionParameters, monitor, launch); + return devServerRunConfiguration; } private int getDebugPort() throws CoreException { diff --git a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/DebugNativeAppEngineStandardProjectTest.java b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/DebugNativeAppEngineStandardProjectTest.java index b2b0ee56b0..de302462f0 100644 --- a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/DebugNativeAppEngineStandardProjectTest.java +++ b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/DebugNativeAppEngineStandardProjectTest.java @@ -19,6 +19,7 @@ import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.widgetOfType; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.stringContainsInOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -35,6 +36,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.swt.custom.StyledText; @@ -59,15 +61,15 @@ public class DebugNativeAppEngineStandardProjectTest extends BaseProjectTest { private static final long TERMINATE_SERVER_TIMEOUT = 10000L; - @Rule - public ThreadDumpingWatchdog timer = new ThreadDumpingWatchdog(2, TimeUnit.MINUTES); + @Rule public ThreadDumpingWatchdog timer = new ThreadDumpingWatchdog(2, TimeUnit.MINUTES); /** * Launch a native application in debug mode and verify that: + * *
    - *
  1. it started,
  2. - *
  3. it can be terminated and removed from the launch list, and
  4. - *
  5. the process is actually terminated.
  6. + *
  7. it started, + *
  8. it can be terminated and removed from the launch list, and + *
  9. the process is actually terminated. *
*/ @Test @@ -80,8 +82,9 @@ public void testDebugLaunch() throws Exception { assertNoService(new URL("http://localhost:8080/hello")); - project = SwtBotAppEngineActions.createNativeWebAppProject(bot, "testapp_java8", null, - "app.engine.test", null /* runtime */); + project = + SwtBotAppEngineActions.createNativeWebAppProject( + bot, "testapp_java8", null, "app.engine.test", null /* runtime */); assertTrue(project.exists()); SWTBotTreeItem testProject = SwtBotProjectActions.selectProject(bot, "testapp_java8"); @@ -96,8 +99,10 @@ public void testDebugLaunch() throws Exception { SwtBotTestingUtilities.clickButtonAndWaitForWindowClose(bot, bot.button("Finish")); - bot.perspectiveById("org.eclipse.debug.ui.DebugPerspective").activate(); // IDebugUIConstants.ID_DEBUG_PERSPECTIVE - SWTBotView debugView = bot.viewById("org.eclipse.debug.ui.DebugView"); // IDebugUIConstants.ID_DEBUG_VIEW + bot.perspectiveById("org.eclipse.debug.ui.DebugPerspective") + .activate(); // IDebugUIConstants.ID_DEBUG_PERSPECTIVE + SWTBotView debugView = + bot.viewById("org.eclipse.debug.ui.DebugView"); // IDebugUIConstants.ID_DEBUG_VIEW debugView.show(); SWTBotTree launchTree = @@ -111,22 +116,30 @@ public void testDebugLaunch() throws Exception { SwtBotTreeUtilities.waitUntilTreeHasItems(bot, launchTree); SWTBotTreeItem[] allItems = launchTree.getAllItems(); - SwtBotTreeUtilities.waitUntilTreeContainsText(bot, allItems[0], - "App Engine Standard at localhost"); + SwtBotTreeUtilities.waitUntilTreeContainsText( + bot, allItems[0], "App Engine Standard at localhost"); - SWTBotView consoleView = bot.viewById("org.eclipse.ui.console.ConsoleView"); // IConsoleConstants.ID_CONSOLE_VIEW + SWTBotView consoleView = + bot.viewById("org.eclipse.ui.console.ConsoleView"); // IConsoleConstants.ID_CONSOLE_VIEW consoleView.show(); SwtBotTestingUtilities.waitUntilViewContentDescription( bot, consoleView, Matchers.containsString("App Engine Standard at localhost")); SWTBotStyledText consoleContents = new SWTBotStyledText(bot.widget(widgetOfType(StyledText.class), consoleView.getWidget())); - SwtBotTestingUtilities.waitUntilStyledTextContains(bot, - "Module instance default is running at http://localhost:8080", consoleContents); + SwtBotTestingUtilities.waitUntilStyledTextContains( + bot, "Module instance default is running at http://localhost:8080", consoleContents); // Server is now running - assertEquals("Hello App Engine!", + assertEquals( + "Hello App Engine!", getUrlContents(new URL("http://localhost:8080/hello"), (int) SWTBotPreferences.TIMEOUT)); + // Ensure debugger has connected by looking for well-known thread + debugView.show(); + assertTrue( + SwtBotTreeUtilities.hasChild( + bot, launchTree, stringContainsInOrder(Arrays.asList("Thread", "(Running)")))); + { SWTBotView serversView = bot.viewById("org.eclipse.wst.server.ui.ServersView"); serversView.show(); @@ -149,21 +162,21 @@ public void testDebugLaunch() throws Exception { SwtBotTreeUtilities.waitUntilTreeTextMatches( bot, allItems[0], containsString(""), TERMINATE_SERVER_TIMEOUT); assertNoService(new URL("http://localhost:8080/hello")); - assertTrue("App Engine console should mark as stopped", + assertTrue( + "App Engine console should mark as stopped", consoleView.getViewReference().getContentDescription().startsWith("")); assertFalse("Stop Server button should be disabled", stopServerButton.isEnabled()); // should also cause console to be discarded launchTree.contextMenu("Remove All Terminated").click(); SwtBotTreeUtilities.waitUntilTreeHasNoItems(bot, launchTree); - assertThat("App Engine console should be removed", + assertThat( + "App Engine console should be removed", consoleView.getViewReference().getContentDescription(), Matchers.is("No consoles to display at this time.")); } - /** - * Check that there is no remote service for the URL. - */ + /** Check that there is no remote service for the URL. */ private void assertNoService(URL url) { try { getUrlContents(url, 10); @@ -193,5 +206,4 @@ private static String getUrlContents(URL url, int timeoutInMilliseconds) throws } return content.toString().trim(); } - } diff --git a/plugins/com.google.cloud.tools.eclipse.swtbot/src/com/google/cloud/tools/eclipse/swtbot/SwtBotTreeUtilities.java b/plugins/com.google.cloud.tools.eclipse.swtbot/src/com/google/cloud/tools/eclipse/swtbot/SwtBotTreeUtilities.java index cf102c65af..d8b1b3d2b6 100644 --- a/plugins/com.google.cloud.tools.eclipse.swtbot/src/com/google/cloud/tools/eclipse/swtbot/SwtBotTreeUtilities.java +++ b/plugins/com.google.cloud.tools.eclipse.swtbot/src/com/google/cloud/tools/eclipse/swtbot/SwtBotTreeUtilities.java @@ -16,11 +16,18 @@ package com.google.cloud.tools.eclipse.swtbot; +import static org.junit.Assert.assertFalse; + import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import org.eclipse.swt.widgets.TreeItem; import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; +import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; +import org.eclipse.swtbot.swt.finder.results.Result; import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences; import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree; @@ -31,9 +38,7 @@ import org.hamcrest.Matchers; import org.hamcrest.StringDescription; -/** - * Utilities for manipulating trees. - */ +/** Utilities for manipulating trees. */ public class SwtBotTreeUtilities { /** @@ -42,17 +47,18 @@ public class SwtBotTreeUtilities { * @throws TimeoutException if no items appear within the default timeout */ public static SWTBotTreeItem waitUntilTreeHasItems(SWTWorkbenchBot bot, SWTBotTree tree) { - bot.waitUntil(new DefaultCondition() { - @Override - public String getFailureMessage() { - return "Tree items never appeared"; - } + bot.waitUntil( + new DefaultCondition() { + @Override + public String getFailureMessage() { + return "Tree items never appeared"; + } - @Override - public boolean test() throws Exception { - return tree.hasItems(); - } - }); + @Override + public boolean test() throws Exception { + return tree.hasItems(); + } + }); return tree.getAllItems()[0]; } @@ -122,21 +128,22 @@ public boolean test() throws Exception { /** * Wait until the given tree has not items. - * + * * @throws TimeoutException if no items appear within the default timeout */ public static void waitUntilTreeHasNoItems(SWTWorkbenchBot bot, final SWTBotTree tree) { - bot.waitUntil(new DefaultCondition() { - @Override - public String getFailureMessage() { - return "Tree items never disappeared"; - } + bot.waitUntil( + new DefaultCondition() { + @Override + public String getFailureMessage() { + return "Tree items never disappeared"; + } - @Override - public boolean test() throws Exception { - return !tree.hasItems(); - } - }); + @Override + public boolean test() throws Exception { + return !tree.hasItems(); + } + }); } /** @@ -227,4 +234,39 @@ public static SWTBotTreeItem select(SWTWorkbenchBot bot, SWTBotTree tree, String } return item.getNode(nodeNames[leafIndex]).select(); // throws WNFE } + + /** Expand the tree as necessary to find a child matching the given condition. */ + public static boolean hasChild(SWTWorkbenchBot bot, SWTBotTree tree, Matcher textMatcher) { + waitUntilTreeHasItems(bot, tree); + // perform breadth-first search; execute directly in SWT thread as the tree may otherwise + // be affected by thread changes + Result query = + () -> { + TreeItem[] items = tree.widget.getItems(); + for (TreeItem item : items) { + if (textMatcher.matches(item.getText())) { + return true; + } + } + LinkedList stack = new LinkedList<>(); + Collections.addAll(stack, items); + while (!stack.isEmpty()) { + TreeItem parent = stack.removeFirst(); + items = parent.getItems(); + // If this assertion fails, it may be due to + // https://github.com/GoogleCloudPlatform/google-cloud-eclipse/issues/2569 + // and may require applying the workaround to collapse and re-expand the node + assertFalse( + "workaround may be required", items.length == 1 && "".equals(items[0].getText())); + for (TreeItem item : items) { + if (textMatcher.matches(item.getText())) { + return true; + } + } + Collections.addAll(stack, items); + } + return false; + }; + return UIThreadRunnable.syncExec(query); + } } diff --git a/pom.xml b/pom.xml index baf81fe9fa..2bd19cf664 100644 --- a/pom.xml +++ b/pom.xml @@ -329,6 +329,7 @@ hourly 600 + false