From d337a34335cc2838efb02bf1a4a119a834fb4053 Mon Sep 17 00:00:00 2001 From: Brian de Alwis Date: Wed, 8 Feb 2017 09:41:07 -0500 Subject: [PATCH] Workaround deadlock test issues in Oxygen M5 (#1348) * Defer WTP dependency graph updates during facet installation to avoid deadlocks (https://bugs.eclipse.org/511793) * Extract App Engine facet/runtime jobs and mark as JST jobs * Rework waitForIdle() -> waitForProjects() to reduce wait-for-completion times --- .../facets/StandardFacetInstallationTest.java | 6 +- .../facets/AppEngineStandardFacet.java | 14 +++- ...ppEngineStandardRuntimeChangeListener.java | 83 +++++++++++-------- .../facets/StandardFacetInstallDelegate.java | 18 +++- .../StandardFacetUninstallDelegate.java | 61 +++++++++----- .../appengine/facets/messages.properties | 4 +- ...avenBasedAppEngineStandardProjectTest.java | 4 +- ...ateMavenBasedAppEngineStandardProject.java | 9 +- ...CreateAppEngineStandardWtpProjectTest.java | 10 +-- .../appengine/BaseProjectTest.java | 18 +++- ...wMavenBasedAppEngineProjectWizardTest.java | 2 +- ...NewNativeAppEngineStandardProjectTest.java | 2 +- .../appengine/SwtBotAppEngineActions.java | 10 ++- .../META-INF/MANIFEST.MF | 3 +- .../swtbot/SwtBotWorkbenchActions.java | 29 ++----- .../test/util/project/ProjectUtils.java | 35 +++++--- .../test/util/project/TestProjectCreator.java | 5 +- pom.xml | 3 +- 18 files changed, 196 insertions(+), 120 deletions(-) diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.facets.test/src/com/google/cloud/tools/eclipse/appengine/facets/StandardFacetInstallationTest.java b/plugins/com.google.cloud.tools.eclipse.appengine.facets.test/src/com/google/cloud/tools/eclipse/appengine/facets/StandardFacetInstallationTest.java index ef74dfe8b4..5c3a30850c 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.facets.test/src/com/google/cloud/tools/eclipse/appengine/facets/StandardFacetInstallationTest.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.facets.test/src/com/google/cloud/tools/eclipse/appengine/facets/StandardFacetInstallationTest.java @@ -80,7 +80,7 @@ public void testStandardFacetInstallation() throws IOException, CoreException { assertTrue(correctAppEngineWebXml.exists()); assertFalse(wrongAppEngineWebXml.exists()); - ProjectUtils.waitUntilIdle(); // App Engine runtime is added via a Job, so wait. + ProjectUtils.waitForProjects(project); // App Engine runtime is added via a Job, so wait. IRuntime primaryRuntime = facetedProject.getPrimaryRuntime(); assertTrue(AppEngineStandardFacet.isAppEngineStandardRuntime(primaryRuntime)); } @@ -92,7 +92,7 @@ public void testStandardFacetInstallation_createsWebXml() throws CoreException { IFacetedProject facetedProject = ProjectFacetsManager.create(project); AppEngineStandardFacet.installAppEngineFacet(facetedProject, true, null); - ProjectUtils.waitUntilIdle(); // App Engine runtime is added via a Job, so wait. + ProjectUtils.waitForProjects(project); // App Engine runtime is added via a Job, so wait. assertTrue(project.getFile("src/main/webapp/WEB-INF/web.xml").exists()); } @@ -109,7 +109,7 @@ public void testStandardFacetInstallation_DoesNotOverwriteWebXml() IFacetedProject facetedProject = ProjectFacetsManager.create(project); AppEngineStandardFacet.installAppEngineFacet(facetedProject, true, null); - ProjectUtils.waitUntilIdle(); // App Engine runtime is added via a Job, so wait. + ProjectUtils.waitForProjects(project); // App Engine runtime is added via a Job, so wait. assertEmptyFile(webXml); } diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/AppEngineStandardFacet.java b/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/AppEngineStandardFacet.java index 052e055460..fdb77c42a1 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/AppEngineStandardFacet.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/AppEngineStandardFacet.java @@ -41,6 +41,7 @@ import org.eclipse.jst.j2ee.web.project.facet.WebFacetInstallDataModelProvider; import org.eclipse.jst.j2ee.web.project.facet.WebFacetUtils; import org.eclipse.jst.server.core.FacetUtil; +import org.eclipse.wst.common.componentcore.internal.builder.IDependencyGraph; import org.eclipse.wst.common.frameworks.datamodel.DataModelFactory; import org.eclipse.wst.common.frameworks.datamodel.IDataModel; import org.eclipse.wst.common.project.facet.core.IFacetedProject; @@ -147,7 +148,18 @@ public static void installAppEngineFacet(IFacetedProject facetedProject, Object config = null; facetInstallSet.add(new IFacetedProject.Action( IFacetedProject.Action.Type.INSTALL, appEngineFacetVersion, config)); - facetedProject.modify(facetInstallSet, subMonitor.newChild(100)); + + // Workaround deadlock bug described in Eclipse bug (https://bugs.eclipse.org/511793). + // There are graph update jobs triggered by the completion of the CreateProjectOperation + // above (from resource notifications) and from other resource changes from modifying the + // project facets. So we force the dependency graph to defer updates. + try { + IDependencyGraph.INSTANCE.preUpdate(); + + facetedProject.modify(facetInstallSet, subMonitor.newChild(100)); + } finally { + IDependencyGraph.INSTANCE.postUpdate(); + } } } diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/AppEngineStandardRuntimeChangeListener.java b/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/AppEngineStandardRuntimeChangeListener.java index c1536a68fb..da9746840a 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/AppEngineStandardRuntimeChangeListener.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/AppEngineStandardRuntimeChangeListener.java @@ -16,13 +16,14 @@ package com.google.cloud.tools.eclipse.appengine.facets; +import com.google.cloud.tools.eclipse.util.status.StatusUtil; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jst.j2ee.refactor.listeners.J2EEElementChangedListener; import org.eclipse.wst.common.project.facet.core.IFacetedProject; import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectEvent; import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectListener; @@ -35,6 +36,51 @@ */ public class AppEngineStandardRuntimeChangeListener implements IFacetedProjectListener { + /** A Job to install out App Engine facet given that we've added a runtime. */ + private static class InstallAppEngineFacetJob extends Job { + private final IFacetedProject facetedProject; + private final IRuntime newRuntime; + + private InstallAppEngineFacetJob(IFacetedProject facetedProject, + IRuntime newRuntime) { + super(Messages.getString("appengine.add.facet.to.project", + facetedProject.getProject().getName())); // $NON-NLS$ + this.facetedProject = facetedProject; + this.newRuntime = newRuntime; + } + + /** + * Mark this job as a component update job. Useful for our tests to ensure project configuration + * is complete. + */ + @Override + public boolean belongsTo(Object family) { + return J2EEElementChangedListener.PROJECT_COMPONENT_UPDATE_JOB_FAMILY.equals(family); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + IStatus installStatus = Status.OK_STATUS; + + try { + AppEngineStandardFacet.installAppEngineFacet(facetedProject, + false /* installDependentFacets */, monitor); + return installStatus; + } catch (CoreException ex1) { + // Displays missing constraints that prevented facet installation + installStatus = ex1.getStatus(); + + // Remove App Engine as primary runtime + try { + facetedProject.removeTargetedRuntime(newRuntime, monitor); + return installStatus; + } catch (CoreException ex2) { + return StatusUtil.merge(installStatus, ex2.getStatus()); + } + } + } + } + @Override public void handleEvent(IFacetedProjectEvent event) { // PRIMARY_RUNTIME_CHANGED occurs in scenarios such as selecting runtimes on the @@ -62,40 +108,7 @@ public void handleEvent(IFacetedProjectEvent event) { // Add the App Engine facet IProject project = facetedProject.getProject(); - Job addFacetJob = new Job("Add App Engine facet to " + project.getName()) { - - @Override - protected IStatus run(IProgressMonitor monitor) { - - IStatus installStatus = Status.OK_STATUS; - - try { - AppEngineStandardFacet.installAppEngineFacet(facetedProject, false /* installDependentFacets */, monitor); - return installStatus; - } catch (CoreException ex1) { - // Displays missing constraints that prevented facet installation - installStatus = ex1.getStatus(); - - // Remove App Engine as primary runtime - try { - facetedProject.removeTargetedRuntime(newRuntime, monitor); - return installStatus; - } catch (CoreException ex2) { - MultiStatus multiStatus; - if (installStatus instanceof MultiStatus) { - multiStatus = (MultiStatus) installStatus; - } else { - multiStatus = new MultiStatus(installStatus.getPlugin(), installStatus.getCode(), - installStatus.getMessage(), installStatus.getException()); - } - multiStatus.merge(ex2.getStatus()); - return multiStatus; - } - } - - } - - }; + Job addFacetJob = new InstallAppEngineFacetJob(facetedProject, newRuntime); addFacetJob.schedule(); } diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/StandardFacetInstallDelegate.java b/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/StandardFacetInstallDelegate.java index 1ac52dbf63..3724058471 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/StandardFacetInstallDelegate.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/StandardFacetInstallDelegate.java @@ -29,6 +29,7 @@ import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jst.j2ee.refactor.listeners.J2EEElementChangedListener; import org.eclipse.wst.common.project.facet.core.IFacetedProject; import org.eclipse.wst.common.project.facet.core.IProjectFacet; import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion; @@ -55,8 +56,7 @@ private void installAppEngineRuntimes(IProject project) throws CoreException { // Modifying targeted runtimes while installing/uninstalling facets is not allowed, // so schedule a job as a workaround. - Job installJob = new AppEngineRuntimeInstallJob( - "Install App Engine runtimes in " + project.getName(), facetedProject); + Job installJob = new AppEngineRuntimeInstallJob(facetedProject); // Schedule immediately so that it doesn't go into the SLEEPING state. Ensuring the job is // active is necessary for unit testing. installJob.schedule(); @@ -70,11 +70,21 @@ private static class AppEngineRuntimeInstallJob extends Job { private IFacetedProject facetedProject; - private AppEngineRuntimeInstallJob(String name, IFacetedProject facetedProject) { - super(name); + private AppEngineRuntimeInstallJob(IFacetedProject facetedProject) { + super(Messages.getString("appengine.install.runtime.to.project", // $NON-NLS$ + facetedProject.getProject().getName())); this.facetedProject = facetedProject; } + /** + * Mark this job as a component update job. Useful for our tests to ensure project configuration + * is complete. + */ + @Override + public boolean belongsTo(Object family) { + return J2EEElementChangedListener.PROJECT_COMPONENT_UPDATE_JOB_FAMILY.equals(family); + } + private void waitUntilJsdtIsFixedFacet() { try { IProjectFacet jsdtFacet = ProjectFacetsManager.getProjectFacet(JSDT_FACET_ID); diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/StandardFacetUninstallDelegate.java b/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/StandardFacetUninstallDelegate.java index ccbd5e1d77..9f736e3ece 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/StandardFacetUninstallDelegate.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/StandardFacetUninstallDelegate.java @@ -23,6 +23,7 @@ import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jst.j2ee.refactor.listeners.J2EEElementChangedListener; import org.eclipse.wst.common.project.facet.core.IDelegate; import org.eclipse.wst.common.project.facet.core.IFacetedProject; import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion; @@ -31,38 +32,52 @@ public class StandardFacetUninstallDelegate implements IDelegate { - @Override - public void execute(IProject project, IProjectFacetVersion version, Object config, - IProgressMonitor monitor) throws CoreException { - uninstallAppEngineRuntimes(project); - } - /** * Removes all the App Engine server runtimes from the list of targeted runtimes for * project. */ - private void uninstallAppEngineRuntimes(final IProject project) { - // Modifying targeted runtimes while installing/uninstalling facets is not allowed, - // so schedule a job as a workaround. - Job uninstallJob = new Job("Uninstall App Engine runtimes in " + project.getName()) { + private final class UninstallAppEngineRuntimesJob extends Job { + private final IFacetedProject facetedProject; + + private UninstallAppEngineRuntimesJob(IFacetedProject facetedProject) { + super(Messages.getString("appengine.remove.runtimes.from.project", // $NON-NLS$ + facetedProject.getProject().getName())); + this.facetedProject = facetedProject; + } - @Override - protected IStatus run(IProgressMonitor monitor) { - try { - IFacetedProject facetedProject = ProjectFacetsManager.create(project); - Set targetedRuntimes = facetedProject.getTargetedRuntimes(); + /** + * Mark this job as a component update job. Useful for our tests to ensure project configuration + * is complete. + */ + @Override + public boolean belongsTo(Object family) { + return J2EEElementChangedListener.PROJECT_COMPONENT_UPDATE_JOB_FAMILY.equals(family); + } - for (IRuntime targetedRuntime : targetedRuntimes) { - if (AppEngineStandardFacet.isAppEngineStandardRuntime(targetedRuntime)) { - facetedProject.removeTargetedRuntime(targetedRuntime, monitor); - } + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + Set targetedRuntimes = facetedProject.getTargetedRuntimes(); + + for (IRuntime targetedRuntime : targetedRuntimes) { + if (AppEngineStandardFacet.isAppEngineStandardRuntime(targetedRuntime)) { + facetedProject.removeTargetedRuntime(targetedRuntime, monitor); } - return Status.OK_STATUS; - } catch (CoreException ex) { - return ex.getStatus(); } + return Status.OK_STATUS; + } catch (CoreException ex) { + return ex.getStatus(); } - }; + } + } + + @Override + public void execute(IProject project, IProjectFacetVersion version, Object config, + IProgressMonitor monitor) throws CoreException { + // Modifying targeted runtimes while installing/uninstalling facets is not allowed, + // so schedule a job as a workaround. + IFacetedProject facetedProject = ProjectFacetsManager.create(project); + Job uninstallJob = new UninstallAppEngineRuntimesJob(facetedProject); uninstallJob.schedule(); } diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/messages.properties b/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/messages.properties index 9cd9a894a2..00d7c1a386 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/messages.properties +++ b/plugins/com.google.cloud.tools.eclipse.appengine.facets/src/com/google/cloud/tools/eclipse/appengine/facets/messages.properties @@ -4,7 +4,9 @@ cloud.sdk.out.of.date=Project setup failed because the Google Cloud SDK is too o appengine.java.component.missing=Project setup failed because the Cloud SDK App Engine Java \ component is not installed. Fix by running 'gcloud components install app-engine-java' on the \ command-line. - +appengine.add.facet.to.project=Add App Engine facet to "{0}" +appengine.install.runtime.to.project=Install App Engine runtimes in "{0}" +appengine.remove.runtimes.from.project=Remove App Engine runtimes from "{0}" project.conversion.error=Failed to convert project "{0}". web.facet.incompatible.title=Incompatible Dynamic Web Module Facet version web.facet.incompatible.message=Cannot convert project "{0}". App Engine Standard project requires \ diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.newproject.maven.test/src/com/google/cloud/tools/eclipse/appengine/newproject/maven/CreateMavenBasedAppEngineStandardProjectTest.java b/plugins/com.google.cloud.tools.eclipse.appengine.newproject.maven.test/src/com/google/cloud/tools/eclipse/appengine/newproject/maven/CreateMavenBasedAppEngineStandardProjectTest.java index 836a7a87b0..e55d65ec27 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.newproject.maven.test/src/com/google/cloud/tools/eclipse/appengine/newproject/maven/CreateMavenBasedAppEngineStandardProjectTest.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.newproject.maven.test/src/com/google/cloud/tools/eclipse/appengine/newproject/maven/CreateMavenBasedAppEngineStandardProjectTest.java @@ -18,6 +18,7 @@ import com.google.cloud.tools.eclipse.test.util.project.ProjectUtils; import java.lang.reflect.InvocationTargetException; +import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.m2e.core.project.IProjectConfigurationManager; @@ -45,7 +46,8 @@ public void testConstructor() operation.projectConfigurationManager = manager; operation.execute(monitor); - ProjectUtils.waitUntilIdle(); // App Engine runtime is added via a Job, so wait. + // App Engine runtime is added via a Job, so wait. + ProjectUtils.waitForProjects(operation.getArchetypeProjects().toArray(new IProject[0])); } } diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.newproject.maven/src/com/google/cloud/tools/eclipse/appengine/newproject/maven/CreateMavenBasedAppEngineStandardProject.java b/plugins/com.google.cloud.tools.eclipse.appengine.newproject.maven/src/com/google/cloud/tools/eclipse/appengine/newproject/maven/CreateMavenBasedAppEngineStandardProject.java index ca3f46df8b..6a913fca40 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.newproject.maven/src/com/google/cloud/tools/eclipse/appengine/newproject/maven/CreateMavenBasedAppEngineStandardProject.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.newproject.maven/src/com/google/cloud/tools/eclipse/appengine/newproject/maven/CreateMavenBasedAppEngineStandardProject.java @@ -51,6 +51,8 @@ public class CreateMavenBasedAppEngineStandardProject extends WorkspaceModifyOpe private IPath location; private Archetype archetype; private HashSet appEngineLibraryIds = new HashSet(); + + private List archetypeProjects; private IFile mostImportant; /** @@ -61,6 +63,10 @@ IFile getMostImportant() { return mostImportant; } + List getArchetypeProjects() { + return archetypeProjects; + } + @Override protected void execute(IProgressMonitor monitor) throws CoreException, InvocationTargetException, InterruptedException { @@ -97,7 +103,7 @@ protected void execute(IProgressMonitor monitor) ProjectImportConfiguration importConfiguration = new ProjectImportConfiguration(); String packageName = this.packageName == null || this.packageName.isEmpty() ? groupId : this.packageName; - List archetypeProjects = projectConfigurationManager.createArchetypeProjects(location, + archetypeProjects = projectConfigurationManager.createArchetypeProjects(location, archetype, groupId, artifactId, version, packageName, properties, importConfiguration, progress.newChild(40)); @@ -107,6 +113,7 @@ protected void execute(IProgressMonitor monitor) if (pom.exists()) { this.mostImportant = pom; } + IFacetedProject facetedProject = ProjectFacetsManager.create( project, true, loopMonitor.newChild(1)); AppEngineStandardFacet.installAppEngineFacet(facetedProject, diff --git a/plugins/com.google.cloud.tools.eclipse.appengine.newproject.test/src/com/google/cloud/tools/eclipse/appengine/newproject/CreateAppEngineStandardWtpProjectTest.java b/plugins/com.google.cloud.tools.eclipse.appengine.newproject.test/src/com/google/cloud/tools/eclipse/appengine/newproject/CreateAppEngineStandardWtpProjectTest.java index acd616a14f..980303e445 100644 --- a/plugins/com.google.cloud.tools.eclipse.appengine.newproject.test/src/com/google/cloud/tools/eclipse/appengine/newproject/CreateAppEngineStandardWtpProjectTest.java +++ b/plugins/com.google.cloud.tools.eclipse.appengine.newproject.test/src/com/google/cloud/tools/eclipse/appengine/newproject/CreateAppEngineStandardWtpProjectTest.java @@ -81,7 +81,7 @@ public void testUnitTestCreated() throws InvocationTargetException, CoreExceptio new CreateAppEngineStandardWtpProject(config, adaptable); creator.execute(new NullProgressMonitor()); - ProjectUtils.waitUntilIdle(); // App Engine runtime is added via a Job, so wait. + ProjectUtils.waitForProjects(project); // App Engine runtime is added via a Job, so wait. assertJunitAndHamcrestAreOnClasspath(); } @@ -107,7 +107,7 @@ public void testMostImportantFile() throws InvocationTargetException, CoreExcept new CreateAppEngineStandardWtpProject(config, adaptable); creator.execute(new NullProgressMonitor()); - ProjectUtils.waitUntilIdle(); // App Engine runtime is added via a Job, so wait. + ProjectUtils.waitForProjects(project); // App Engine runtime is added via a Job, so wait. assertEquals("HelloAppEngine.java", creator.getMostImportant().getName()); } @@ -115,7 +115,7 @@ public void testMostImportantFile() throws InvocationTargetException, CoreExcept public void testAppEngineRuntimeAdded() throws InvocationTargetException, CoreException { new CreateAppEngineStandardWtpProject(config, adaptable).execute(null /* monitor */); - ProjectUtils.waitUntilIdle(); // App Engine runtime is added via a Job, so wait. + ProjectUtils.waitForProjects(project); // App Engine runtime is added via a Job, so wait. IFacetedProject facetedProject = ProjectFacetsManager.create(project); IRuntime primaryRuntime = facetedProject.getPrimaryRuntime(); assertTrue(AppEngineStandardFacet.isAppEngineStandardRuntime(primaryRuntime)); @@ -124,7 +124,7 @@ public void testAppEngineRuntimeAdded() throws InvocationTargetException, CoreEx @Test public void testFaviconAdded() throws InvocationTargetException, CoreException { new CreateAppEngineStandardWtpProject(config, adaptable).execute(null /* monitor */); - ProjectUtils.waitUntilIdle(); + ProjectUtils.waitForProjects(project); // App Engine runtime is added via a Job, so wait. assertTrue("favicon.ico not found", project.getFile("src/main/webapp/favicon.ico").exists()); } @@ -136,7 +136,7 @@ public void testAppEngineLibrariesAdded() throws InvocationTargetException, Core new CreateAppEngineStandardWtpProject(config, adaptable); creator.execute(new NullProgressMonitor()); - ProjectUtils.waitUntilIdle(); // App Engine runtime is added via a Job, so wait. + ProjectUtils.waitForProjects(project); // App Engine runtime is added via a Job, so wait. assertAppEngineContainerOnClasspath(library); } diff --git a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/BaseProjectTest.java b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/BaseProjectTest.java index 5a3a64eb96..d1824bfc0d 100644 --- a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/BaseProjectTest.java +++ b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/BaseProjectTest.java @@ -23,6 +23,7 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; +import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; import org.eclipse.swtbot.swt.finder.widgets.TimeoutException; import org.junit.After; import org.junit.BeforeClass; @@ -41,14 +42,18 @@ public static void setUp() throws Exception { new CloudSdk.Builder().build().validateCloudSdk(); bot = new SWTWorkbenchBot(); - SwtBotWorkbenchActions.closeWelcome(bot); + try { + SwtBotWorkbenchActions.closeWelcome(bot); + } catch (WidgetNotFoundException ex) { + // may receive WNFE: "There is no active view" + } } @After public void tearDown() { if (project != null) { // ensure there are no jobs - SwtBotWorkbenchActions.waitForIdle(bot); + SwtBotWorkbenchActions.waitForProjects(bot, project); try { SwtBotProjectActions.deleteProject(bot, project.getName()); } catch (TimeoutException ex) { @@ -56,7 +61,14 @@ public void tearDown() { } project = null; } - bot.resetWorkbench(); + + // Avoid resetWorkbench() due to Eclipse bug 511729 on Oxygen + // bot.resetWorkbench(); + bot.saveAllEditors(); + bot.closeAllEditors(); + bot.resetActivePerspective(); + bot.defaultPerspective().activate(); + bot.resetActivePerspective(); } /** diff --git a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/NewMavenBasedAppEngineProjectWizardTest.java b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/NewMavenBasedAppEngineProjectWizardTest.java index 7b5ee41741..12e8eddfc8 100644 --- a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/NewMavenBasedAppEngineProjectWizardTest.java +++ b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/NewMavenBasedAppEngineProjectWizardTest.java @@ -106,7 +106,7 @@ private void createAndCheck(String artifactId, String location, Path projectFilePath = new Path(projectFile); assertTrue(project.exists(projectFilePath)); } - ProjectUtils.waitUntilIdle(); // App Engine runtime is added via a Job, so wait. + ProjectUtils.waitForProjects(project); // App Engine runtime is added via a Job, so wait. ProjectUtils.failIfBuildErrors("New Maven project has errors", project); } } diff --git a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/NewNativeAppEngineStandardProjectTest.java b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/NewNativeAppEngineStandardProjectTest.java index 031aa93439..ed409388fb 100644 --- a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/NewNativeAppEngineStandardProjectTest.java +++ b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/NewNativeAppEngineStandardProjectTest.java @@ -71,7 +71,7 @@ private void createAndCheck(String projectName, String packageName, String[] pro Path projectFilePath = new Path(projectFile); assertTrue(project.exists(projectFilePath)); } - ProjectUtils.waitUntilIdle(); // App Engine runtime is added via a Job, so wait. + ProjectUtils.waitForProjects(project); // App Engine runtime is added via a Job, so wait. ProjectUtils.failIfBuildErrors("New native project has errors", project); } } diff --git a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/SwtBotAppEngineActions.java b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/SwtBotAppEngineActions.java index a2006237da..ea662e60de 100644 --- a/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/SwtBotAppEngineActions.java +++ b/plugins/com.google.cloud.tools.eclipse.integration.appengine/src/com/google/cloud/tools/eclipse/integration/appengine/SwtBotAppEngineActions.java @@ -69,8 +69,9 @@ public static IProject createNativeWebAppProject(SWTWorkbenchBot bot, String pro SwtBotTimeoutManager.setTimeout(libraryResolutionTimeout); SwtBotTestingUtilities.clickButtonAndWaitForWindowChange(bot, bot.button("Finish")); SwtBotTimeoutManager.resetTimeout(); - SwtBotWorkbenchActions.waitForIdle(bot); - return waitUntilProjectExists(bot, getWorkspaceRoot().getProject(projectName)); + IProject project = waitUntilProjectExists(bot, getWorkspaceRoot().getProject(projectName)); + SwtBotWorkbenchActions.waitForProjects(bot, project); + return project; } /** Create a new project with the Maven-based Google App Engine Standard Java Project wizard */ @@ -107,8 +108,9 @@ public static IProject createMavenWebAppProject(SWTWorkbenchBot bot, String loca SwtBotTimeoutManager.setTimeout(mavenCompletionTimeout); SwtBotTestingUtilities.clickButtonAndWaitForWindowChange(bot, bot.button("Finish")); SwtBotTimeoutManager.resetTimeout(); - SwtBotWorkbenchActions.waitForIdle(bot); - return waitUntilProjectExists(bot, getWorkspaceRoot().getProject(artifactId)); + IProject project = waitUntilProjectExists(bot, getWorkspaceRoot().getProject(artifactId)); + SwtBotWorkbenchActions.waitForProjects(bot, project); + return project; } /** diff --git a/plugins/com.google.cloud.tools.eclipse.swtbot/META-INF/MANIFEST.MF b/plugins/com.google.cloud.tools.eclipse.swtbot/META-INF/MANIFEST.MF index a076b68aa3..b2898c626f 100644 --- a/plugins/com.google.cloud.tools.eclipse.swtbot/META-INF/MANIFEST.MF +++ b/plugins/com.google.cloud.tools.eclipse.swtbot/META-INF/MANIFEST.MF @@ -10,7 +10,8 @@ Require-Bundle: org.eclipse.swtbot.eclipse.core;bundle-version="2.3.0", org.eclipse.swtbot.forms.finder;bundle-version="2.3.0", org.eclipse.swtbot.go;bundle-version="2.3.0", org.eclipse.swtbot.swt.finder;bundle-version="2.3.0", - org.eclipse.core.resources + org.eclipse.core.resources, + com.google.cloud.tools.eclipse.test.util;bundle-version="0.1.0" Export-Package: com.google.cloud.tools.eclipse.swtbot; uses:="org.eclipse.swtbot.swt.finder.widgets, org.eclipse.swt.widgets, diff --git a/plugins/com.google.cloud.tools.eclipse.swtbot/src/com/google/cloud/tools/eclipse/swtbot/SwtBotWorkbenchActions.java b/plugins/com.google.cloud.tools.eclipse.swtbot/src/com/google/cloud/tools/eclipse/swtbot/SwtBotWorkbenchActions.java index 3b2aa9bbef..7de434729c 100644 --- a/plugins/com.google.cloud.tools.eclipse.swtbot/src/com/google/cloud/tools/eclipse/swtbot/SwtBotWorkbenchActions.java +++ b/plugins/com.google.cloud.tools.eclipse.swtbot/src/com/google/cloud/tools/eclipse/swtbot/SwtBotWorkbenchActions.java @@ -16,9 +16,9 @@ package com.google.cloud.tools.eclipse.swtbot; +import com.google.cloud.tools.eclipse.test.util.project.ProjectUtils; import java.util.List; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.resources.IProject; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; @@ -29,7 +29,6 @@ import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; import org.eclipse.swtbot.swt.finder.waits.ICondition; import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu; -import org.eclipse.wst.validation.ValidationFramework; import org.hamcrest.Matcher; /** @@ -66,24 +65,14 @@ public void run() { /** * Wait until all background tasks are complete. */ - public static void waitForIdle(SWTBot bot) { - while (!Job.getJobManager().isIdle()) { - try { - Job.getJobManager().join(ResourcesPlugin.FAMILY_MANUAL_BUILD, null); - Job.getJobManager().join(ResourcesPlugin.FAMILY_AUTO_BUILD, null); - // J2EEElementChangedListener.PROJECT_COMPONENT_UPDATE_JOB_FAMILY - Job.getJobManager().join("org.eclipse.jst.j2ee.refactor.component", null); - // ServerPlugin.SHUTDOWN_JOB_FAMILY - Job.getJobManager().join("org.eclipse.wst.server.core.family", null); - Job.getJobManager().join("org.eclipse.wst.server.ui.family", null); - ValidationFramework.getDefault().join(null); - } catch (InterruptedException ex) { - // interruption likely happened for a reason - return; + public static void waitForProjects(final SWTBot bot, IProject... projects) { + Runnable delayTactic = new Runnable() { + @Override + public void run() { + bot.sleep(300); } - - bot.sleep(300); - } + }; + ProjectUtils.waitForProjects(delayTactic, projects); } /** diff --git a/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/project/ProjectUtils.java b/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/project/ProjectUtils.java index 12b0902a56..2d80df1693 100644 --- a/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/project/ProjectUtils.java +++ b/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/project/ProjectUtils.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import com.google.common.base.Joiner; import java.io.File; import java.io.IOException; import java.net.URL; @@ -28,8 +29,6 @@ import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; - -import com.google.common.base.Joiner; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; @@ -110,7 +109,7 @@ public static List importProjects(Class clazz, String relativeLocat } // wait for any post-import operations too - waitUntilIdle(); + waitForProjects(); if (checkBuildErrors) { failIfBuildErrors("Imported projects have errors", projects); } @@ -162,7 +161,23 @@ private static String formatProblem(IMarker problem) { } /** Wait for any spawned jobs and builds to complete (e.g., validation jobs). */ - public static void waitUntilIdle() { + public static void waitForProjects(IProject... projects) { + Runnable delayTactic = new Runnable() { + public void run() { + Display display = Display.getCurrent(); + if (display != null) { + while (display.readAndDispatch()) { + /* spin */ + } + } + Thread.yield(); + } + }; + waitForProjects(delayTactic, projects); + } + + /** Wait for any spawned jobs and builds to complete (e.g., validation jobs). */ + public static void waitForProjects(Runnable delayTactic, IProject... projects) { try { do { Job.getJobManager().join(ResourcesPlugin.FAMILY_MANUAL_BUILD, null); @@ -174,15 +189,9 @@ public static void waitUntilIdle() { Job.getJobManager().join("org.eclipse.wst.server.ui.family", null); ValidationFramework.getDefault().join(null); - Display display = Display.getCurrent(); - if (display != null) { - while (display.readAndDispatch()) { - /* spin */ - } - } - Thread.yield(); - } while (!Job.getJobManager().isIdle()); - } catch (InterruptedException ex) { + delayTactic.run(); + } while (!getAllBuildErrors(projects).isEmpty()); + } catch (CoreException | InterruptedException ex) { throw new RuntimeException(ex); } } diff --git a/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/project/TestProjectCreator.java b/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/project/TestProjectCreator.java index 828404ef03..23e077e156 100644 --- a/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/project/TestProjectCreator.java +++ b/plugins/com.google.cloud.tools.eclipse.test.util/src/com/google/cloud/tools/eclipse/test/util/project/TestProjectCreator.java @@ -75,7 +75,7 @@ protected void before() throws Throwable { @Override protected void after() { // Wait for any jobs to complete as WTP validation runs without the workspace protection lock - ProjectUtils.waitUntilIdle(); + ProjectUtils.waitForProjects(javaProject.getProject()); try { javaProject.getProject().delete(true, null); } catch (CoreException e) { @@ -128,7 +128,8 @@ private void addFacets() throws CoreException { IFacetedProject facetedProject = ProjectFacetsManager.create(getProject()); for (IProjectFacetVersion projectFacetVersion : projectFacetVersions) { facetedProject.installProjectFacet(projectFacetVersion, null, null); - ProjectUtils.waitUntilIdle(); // App Engine runtime is added via a Job, so wait. + // App Engine runtime is added via a Job, so wait. + ProjectUtils.waitForProjects(getProject()); } } } diff --git a/pom.xml b/pom.xml index 0f688fdc50..977bb938ff 100644 --- a/pom.xml +++ b/pom.xml @@ -100,7 +100,7 @@ - 10000 + 30000 warning @@ -312,6 +312,7 @@ tycho-surefire-plugin ${tycho.version} + 600 -Dorg.eclipse.swtbot.search.timeout=${org.eclipse.swtbot.search.timeout} -Xms40m -Xmx1G -XX:MaxPermSize=512m -Djava.awt.headless=true ${os-jvm-flags}