diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/ParallelBuildChainTest.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/ParallelBuildChainTest.java index 4ba3baf3f8f..45ca950e3ec 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/ParallelBuildChainTest.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/ParallelBuildChainTest.java @@ -13,10 +13,32 @@ *******************************************************************************/ package org.eclipse.core.tests.internal.builders; -import java.util.*; +import static org.eclipse.core.tests.resources.TestUtil.waitForCondition; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo; +import static org.hamcrest.number.OrderingComparison.lessThanOrEqualTo; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.eclipse.core.internal.events.BuildCommand; -import org.eclipse.core.resources.*; -import org.eclipse.core.runtime.*; +import org.eclipse.core.resources.IBuildConfiguration; +import org.eclipse.core.resources.ICommand; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IWorkspaceDescription; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.IncrementalProjectBuilder; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobGroup; import org.eclipse.core.tests.harness.TestBarrier2; @@ -25,8 +47,54 @@ import org.junit.Test; public class ParallelBuildChainTest extends AbstractBuilderTest { + private static final int NUMBER_OF_PROJECTS_TO_CREATE_AT_ONCE = 3; + + private static final int MAXIMUM_NUMBER_OF_CONCURRENT_BUILDS = 3; + + private static final int TIMEOUT_IN_MILLIS = 60_000; + + private static enum BuildDurationType { + /* + * Immediately finishing build + */ + IMMEDIATE, - private static final int LONG_BUILD_DURATION = 1000; + /** + * Short running build + */ + SHORT_RUNNING, + + /** + * Long running build (will usually not end during test run) + */ + LONG_RUNNING; + + public int getDurationInMillis() { + switch (this) { + case LONG_RUNNING: + return 30_000; + case SHORT_RUNNING: + return 300; + case IMMEDIATE: + default: + return 0; + } + } + + @Override + public String toString() { + switch (this) { + case IMMEDIATE: + return "immediateBuild"; + case LONG_RUNNING: + return "longRunningBuild"; + case SHORT_RUNNING: + return "shortRunningBuild"; + } + throw new UnsupportedOperationException(); + } + + } public ParallelBuildChainTest(String name) { super(name); @@ -35,338 +103,386 @@ public ParallelBuildChainTest(String name) { @Override protected void setUp() throws Exception { super.setUp(); + setWorkspaceMaxNumberOfConcurrentBuilds(); + setAutoBuilding(false); + } + + @Override + protected void tearDown() throws Exception { + cleanup(); + super.tearDown(); + TimerBuilder.abortCurrentBuilds(); + } + + private void setWorkspaceMaxNumberOfConcurrentBuilds() throws CoreException { IWorkspaceDescription description = getWorkspace().getDescription(); - description.setMaxConcurrentBuilds(3); + description.setMaxConcurrentBuilds(MAXIMUM_NUMBER_OF_CONCURRENT_BUILDS); getWorkspace().setDescription(description); - setAutoBuilding(false); - TimerBuilder.reset(); - IWorkspaceRoot root = getWorkspace().getRoot(); - IProject projectInstantaneousBuild1 = root.getProject("projectInstantaneousBuild1"); - IProject projectLongBuild1 = root.getProject("projectLongBuild1"); - IProject projectInstantaneousBuild2 = root.getProject("projectInstantaneousBuild2"); - IProject projectLongBuild2 = root.getProject("projectLongBuild2"); - IProject projectInstantaneousBuild3 = root.getProject("projectInstantaneousBuild3"); - IProject projectLongBuild3 = root.getProject("projectLongBuild3"); - ensureExistsInWorkspace(new IResource[] {projectInstantaneousBuild1, projectInstantaneousBuild2, projectInstantaneousBuild3, projectLongBuild1, projectLongBuild2, projectLongBuild3}, true); - configureTimerBuilder(projectInstantaneousBuild1, 0); - configureTimerBuilder(projectInstantaneousBuild2, 0); - configureTimerBuilder(projectInstantaneousBuild3, 0); - configureTimerBuilder(projectLongBuild1, LONG_BUILD_DURATION); - configureTimerBuilder(projectLongBuild2, LONG_BUILD_DURATION); - configureTimerBuilder(projectLongBuild3, LONG_BUILD_DURATION); - } - - private void configureTimerBuilder(IProject project, int duration) throws CoreException { - BuildCommand buildCommand = new BuildCommand(); - buildCommand.setBuilderName(TimerBuilder.BUILDER_NAME); - Map arguments = new HashMap<>(2, (float) 1.); - arguments.put(TimerBuilder.DURATION_ARG, Integer.toString(duration)); - arguments.put(TimerBuilder.RULE_TYPE_ARG, TimerBuilder.RuleType.NO_CONFLICT.toString()); - buildCommand.setArguments(arguments); - IProjectDescription projectDescription = project.getDescription(); - projectDescription.setBuildSpec(new ICommand[] {buildCommand}); - project.setDescription(projectDescription, getMonitor()); } - public IProject[] projectWithLongRunningBuilds() { - return Arrays.stream(getWorkspace().getRoot().getProjects()).filter(project -> { - ICommand[] commands; - try { - commands = project.getDescription().getBuildSpec(); - return commands.length > 0 && commands[0].getBuilderName().equals(TimerBuilder.BUILDER_NAME) - && Integer.parseInt(commands[0].getArguments().get(TimerBuilder.DURATION_ARG)) > 0; - } catch (CoreException e) { - fail(e.getMessage(), e); - return false; - } - }).toArray(IProject[]::new); - } - - private void setTimerBuilderSchedulingRuleForAllProjects(TimerBuilder.RuleType type, IProgressMonitor monitor) throws CoreException { - for (IProject project : getWorkspace().getRoot().getProjects()) { - IProjectDescription projectDescription = project.getDescription(); - BuildCommand command = (BuildCommand) projectDescription.getBuildSpec()[0]; - Map args = command.getArguments(); - if (args == null) { - args = Collections.singletonMap(TimerBuilder.RULE_TYPE_ARG, type.toString()); - } else { - args.put(TimerBuilder.RULE_TYPE_ARG, type.toString()); - } - command.setArguments(args); - projectDescription.setBuildSpec(new ICommand[] {command}); - project.setDescription(projectDescription, getMonitor()); - } + @Test + public void testIndividualProjectBuilds_NoConflictRule() throws Exception { + createMultipleTestProjects(BuildDurationType.IMMEDIATE, RuleType.NO_CONFLICT); + var longRunningProjects = createMultipleTestProjects(BuildDurationType.LONG_RUNNING, RuleType.NO_CONFLICT); + executeIndividualFullProjectBuilds(() -> { + assertBuildsToStart(getAllProjects()); + assertMinimumNumberOfSimultaneousBuilds(longRunningProjects.size()); + }); } @Test - public void testIndividualProjectBuildsInParallelNoConflict() throws CoreException, OperationCanceledException, InterruptedException { - long duration = System.currentTimeMillis(); - JobGroup group = new JobGroup("Build Group", 5, getWorkspace().getRoot().getProjects().length); - for (IProject project : getWorkspace().getRoot().getProjects()) { - Job job = new Job("Building " + project) { - @Override - public IStatus run(IProgressMonitor monitor) { - try { - project.build(IncrementalProjectBuilder.FULL_BUILD, monitor); - return Status.OK_STATUS; - } catch (CoreException e) { - return new Status(IStatus.ERROR, "org.eclipse.core.tests.resources", e.getMessage(), e); - } - } - }; - job.setJobGroup(group); - job.schedule(); - } - Assert.assertTrue("Timeout, most likely a deadlock", group.join(5000, getMonitor())); - duration = System.currentTimeMillis() - duration; - assertEquals(getWorkspace().getRoot().getProjects().length, TimerBuilder.getTotalBuilds()); - assertTrue(TimerBuilder.getMaxSimultaneousBuilds() >= 3); - assertTrue(duration < projectWithLongRunningBuilds().length * LONG_BUILD_DURATION); + public void testIndividualProjectBuilds_ProjectRelaxedRule() throws Exception { + createMultipleTestProjects(BuildDurationType.IMMEDIATE, RuleType.CURRENT_PROJECT_RELAXED); + var longRunningProjects = createMultipleTestProjects(BuildDurationType.LONG_RUNNING, + RuleType.CURRENT_PROJECT_RELAXED); + executeIndividualFullProjectBuilds(() -> { + assertBuildsToStart(getAllProjects()); + assertMinimumNumberOfSimultaneousBuilds(longRunningProjects.size()); + }); } @Test - public void testIndividualProjectBuildsInParallelProjectScheduling() throws CoreException, OperationCanceledException, InterruptedException { - setTimerBuilderSchedulingRuleForAllProjects(RuleType.CURRENT_PROJECT_RELAXED, getMonitor()); - long duration = System.currentTimeMillis(); - JobGroup group = new JobGroup("Build Group", 5, getWorkspace().getRoot().getProjects().length); - for (IProject project : getWorkspace().getRoot().getProjects()) { - Job job = new Job("Building " + project) { - @Override - public IStatus run(IProgressMonitor monitor) { - try { - project.build(IncrementalProjectBuilder.FULL_BUILD, monitor); - return Status.OK_STATUS; - } catch (CoreException e) { - return new Status(IStatus.ERROR, "org.eclipse.core.tests.resources", e.getMessage(), e); - } - } - }; - job.setJobGroup(group); - job.schedule(); - } - Assert.assertTrue("Timeout, most likely a deadlock", group.join(5000, getMonitor())); - duration = System.currentTimeMillis() - duration; - assertEquals(getWorkspace().getRoot().getProjects().length, TimerBuilder.getTotalBuilds()); - assertTrue(TimerBuilder.getMaxSimultaneousBuilds() >= 3); - assertTrue(duration < projectWithLongRunningBuilds().length * LONG_BUILD_DURATION); + public void testWorkspaceBuild_NoConflictRule() throws Exception { + createMultipleTestProjects(BuildDurationType.IMMEDIATE, RuleType.NO_CONFLICT); + var longRunningBuildProjects = createMultipleTestProjects(BuildDurationType.LONG_RUNNING, RuleType.NO_CONFLICT); + executeIncrementalWorkspaceBuild(() -> { + assertBuildsToStart(longRunningBuildProjects); + assertMinimumNumberOfSimultaneousBuilds(longRunningBuildProjects.size()); + assertMaximumNumberOfWorkspaceBuilds(); + }); } @Test - public void testWorkspaceBuildConfigParrallelProjectRule() throws CoreException, OperationCanceledException, InterruptedException { - setTimerBuilderSchedulingRuleForAllProjects(RuleType.CURRENT_PROJECT, getMonitor()); - long duration = System.currentTimeMillis(); - Job job = new Job("Workspace Build") { - @Override - protected IStatus run(IProgressMonitor monitor) { - try { - getWorkspace().build( - Arrays.stream(getWorkspace().getRoot().getProjects()).map(p -> { - try { - return p.getActiveBuildConfig(); - } catch (CoreException e) { - fail(e.getMessage(), e); - return null; - } - }).toArray(IBuildConfiguration[]::new), - IncrementalProjectBuilder.INCREMENTAL_BUILD, - true, - getMonitor()); - return Status.OK_STATUS; - } catch (CoreException e) { - return new Status(IStatus.ERROR, "org.eclipse.core.tests.resources", e.getMessage(), e); - } - } - }; - job.schedule(); - Assert.assertTrue("Timeout, most likely a deadlock", job.join(5000, getMonitor())); - duration = System.currentTimeMillis() - duration; - assertEquals(getWorkspace().getRoot().getProjects().length, TimerBuilder.getTotalBuilds()); - assertTrue(TimerBuilder.getMaxSimultaneousBuilds() > 1); - assertTrue(TimerBuilder.getMaxSimultaneousBuilds() <= getWorkspace().getDescription().getMaxConcurrentBuilds()); - assertTrue(duration < projectWithLongRunningBuilds().length * LONG_BUILD_DURATION); + public void testWorkspaceBuild_NoConflictRule_WithBuildConfigurations() throws Exception { + createMultipleTestProjects(BuildDurationType.IMMEDIATE, RuleType.NO_CONFLICT); + var longRunningBuildProjects = createMultipleTestProjects(BuildDurationType.LONG_RUNNING, RuleType.NO_CONFLICT); + IBuildConfiguration[] buildConfigurations = getBuildConfigurations(getAllProjects()); + executeIncrementalWorkspaceBuild(buildConfigurations, () -> { + assertBuildsToStart(longRunningBuildProjects); + assertMinimumNumberOfSimultaneousBuilds(longRunningBuildProjects.size()); + assertMaximumNumberOfWorkspaceBuilds(); + }); } @Test - public void testWorkspaceParrallelBuildNoConflict() throws CoreException, OperationCanceledException, InterruptedException { - setTimerBuilderSchedulingRuleForAllProjects(RuleType.NO_CONFLICT, getMonitor()); - long duration = System.currentTimeMillis(); - Job job = new Job("Workspace Build") { - @Override - protected IStatus run(IProgressMonitor monitor) { - try { - getWorkspace().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor()); - return Status.OK_STATUS; - } catch (CoreException e) { - return new Status(IStatus.ERROR, "org.eclipse.core.tests.resources", e.getMessage(), e); - } - } - }; - job.schedule(); - Assert.assertTrue("Timeout, most likely a deadlock", job.join(5000, getMonitor())); - duration = System.currentTimeMillis() - duration; - assertEquals(getWorkspace().getRoot().getProjects().length, TimerBuilder.getTotalBuilds()); - assertTrue(TimerBuilder.getMaxSimultaneousBuilds() > 1); - assertTrue(TimerBuilder.getMaxSimultaneousBuilds() <= getWorkspace().getDescription().getMaxConcurrentBuilds()); - assertTrue(duration < projectWithLongRunningBuilds().length * LONG_BUILD_DURATION); + public void testWorkspaceBuild_ProjectRule() throws Exception { + createMultipleTestProjects(BuildDurationType.IMMEDIATE, RuleType.CURRENT_PROJECT); + var longRunningProjects = createMultipleTestProjects(BuildDurationType.LONG_RUNNING, RuleType.CURRENT_PROJECT); + executeIncrementalWorkspaceBuild(() -> { + assertBuildsToStart(longRunningProjects); + assertMinimumNumberOfSimultaneousBuilds(longRunningProjects.size()); + assertMaximumNumberOfWorkspaceBuilds(); + }); } @Test - public void testWorkspaceParrallelBuildConflictingRules() throws CoreException, OperationCanceledException, InterruptedException { - setTimerBuilderSchedulingRuleForAllProjects(RuleType.WORKSPACE_ROOT, getMonitor()); - long duration = System.currentTimeMillis(); - Job job = new Job("Workspace Build") { - @Override - protected IStatus run(IProgressMonitor monitor) { - try { - getWorkspace().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor()); - return Status.OK_STATUS; - } catch (CoreException e) { - return new Status(IStatus.ERROR, "org.eclipse.core.tests.resources", e.getMessage(), e); - } - } - }; - job.schedule(); - Assert.assertTrue("Timeout, most likely a deadlock", job.join(5000, getMonitor())); - duration = System.currentTimeMillis() - duration; - assertEquals(getWorkspace().getRoot().getProjects().length, TimerBuilder.getTotalBuilds()); - assertEquals(1, TimerBuilder.getMaxSimultaneousBuilds()); - assertTrue( - "Running " + projectWithLongRunningBuilds().length + " conflicting jobs of duration " - + LONG_BUILD_DURATION + " should have taken more than " + duration, - duration > projectWithLongRunningBuilds().length * LONG_BUILD_DURATION); + public void testWorkspaceBuild_ProjectRule_WithBuildConfigurations() throws Exception { + createMultipleTestProjects(BuildDurationType.IMMEDIATE, RuleType.CURRENT_PROJECT); + var longRunningBuildProjects = createMultipleTestProjects(BuildDurationType.LONG_RUNNING, + RuleType.CURRENT_PROJECT); + IBuildConfiguration[] buildConfigurations = getBuildConfigurations(getAllProjects()); + executeIncrementalWorkspaceBuild(buildConfigurations, () -> { + assertBuildsToStart(longRunningBuildProjects); + assertMinimumNumberOfSimultaneousBuilds(longRunningBuildProjects.size()); + assertMaximumNumberOfWorkspaceBuilds(); + }); } @Test - public void testWorkspaceParrallelBuildCurrentProject() throws CoreException, OperationCanceledException, InterruptedException { - TestBarrier2 waitForRunningJobBarrier = new TestBarrier2(); - setTimerBuilderSchedulingRuleForAllProjects(RuleType.CURRENT_PROJECT, getMonitor()); - long duration = System.currentTimeMillis(); - Job job = new Job("Workspace Build") { - @Override - protected IStatus run(IProgressMonitor monitor) { - try { - waitForRunningJobBarrier.setStatus(TestBarrier2.STATUS_RUNNING); - getWorkspace().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor()); - return Status.OK_STATUS; - } catch (CoreException e) { - return new Status(IStatus.ERROR, "org.eclipse.core.tests.resources", e.getMessage(), e); - } - } - }; + public void testWorkspaceBuild_ConflictingRule() throws Exception { + int millisToWaitForUnexpectedParallelBuild = 3_000; + var longRunningProjects = createMultipleTestProjects(BuildDurationType.LONG_RUNNING, RuleType.WORKSPACE_ROOT); + executeIncrementalWorkspaceBuild(() -> { + waitForCondition(() -> TimerBuilder.getStartedProjectBuilds().size() > 1, + millisToWaitForUnexpectedParallelBuild); + assertThat( + "all build jobs have started in time although infinitely running builds with conflicting rules exist", + TimerBuilder.getStartedProjectBuilds(), not(containsInAnyOrder(longRunningProjects))); + assertMaximumNumberOfSimultaneousBuilds(1); + }); + } - job.schedule(); - waitForRunningJobBarrier.waitForStatus(TestBarrier2.STATUS_RUNNING); - Assert.assertTrue("Timeout, most likely a deadlock", job.join(20000, getMonitor())); - duration = System.currentTimeMillis() - duration; - assertEquals(getWorkspace().getRoot().getProjects().length, TimerBuilder.getTotalBuilds()); - assertTrue(TimerBuilder.getMaxSimultaneousBuilds() > 1); - assertTrue(TimerBuilder.getMaxSimultaneousBuilds() <= getWorkspace().getDescription().getMaxConcurrentBuilds()); - assertTrue(duration < projectWithLongRunningBuilds().length * LONG_BUILD_DURATION); - } - - public void testDependentProjectsBuildSequentially() throws Exception { - IProject[] allProjects = getWorkspace().getRoot().getProjects(); - for (int i = 1; i < allProjects.length; i++) { - IProject project = allProjects[i]; - IProjectDescription desc = project.getDescription(); - desc.setReferencedProjects(new IProject[] {allProjects[i - 1]}); - project.setDescription(desc, getMonitor()); + public void testWorkspaceBuild_DependentProjects() throws Exception { + createMultipleTestProjects(BuildDurationType.IMMEDIATE, RuleType.NO_CONFLICT); + var shortRunningProjects = createMultipleTestProjects(BuildDurationType.SHORT_RUNNING, RuleType.NO_CONFLICT); + var projectsToBuild = getAllProjects(); + makeProjectsDependOnEachOther(projectsToBuild); + int minimumExecutionTimeInMillis = shortRunningProjects.size() + * BuildDurationType.SHORT_RUNNING.getDurationInMillis(); + ExpectedExecutionTime expectedExecutionTime = ExpectedExecutionTime + .captureFromCurrentTime(minimumExecutionTimeInMillis); + executeIncrementalWorkspaceBuild(() -> { + assertBuildsToFinish(projectsToBuild); + expectedExecutionTime.assertMinimumExecutionTimeReached(); + assertMaximumNumberOfSimultaneousBuilds(1); + assertSequentialBuildEventsForProjects(projectsToBuild); + }); + } + + public void testWorkspaceBuild_DependentProjects_ProjectSubset() throws Exception { + var immediateBuiltProjects = createMultipleTestProjects(BuildDurationType.IMMEDIATE, RuleType.NO_CONFLICT); + var shortRunningProjects = createMultipleTestProjects(BuildDurationType.SHORT_RUNNING, RuleType.NO_CONFLICT); + var projectsToBuild = List.of(immediateBuiltProjects.get(0), + immediateBuiltProjects.get(immediateBuiltProjects.size() - 1), shortRunningProjects.get(0), + shortRunningProjects.get(shortRunningProjects.size() - 1)); + makeProjectsDependOnEachOther(projectsToBuild); + IBuildConfiguration[] selectedBuildConfigurations = getBuildConfigurations(projectsToBuild); + int minimumExecutionTimeInMillis = 2 * BuildDurationType.SHORT_RUNNING.getDurationInMillis(); + ExpectedExecutionTime expectedExecutionTime = ExpectedExecutionTime + .captureFromCurrentTime(minimumExecutionTimeInMillis); + executeIncrementalWorkspaceBuild(selectedBuildConfigurations, () -> { + assertBuildsToFinish(projectsToBuild); + expectedExecutionTime.assertMinimumExecutionTimeReached(); + assertMaximumNumberOfSimultaneousBuilds(1); + assertSequentialBuildEventsForProjects(projectsToBuild); + }); + } + + public void testWorkspaceBuild_DependentProjectBuildConfigurations() throws Exception { + createMultipleTestProjects(BuildDurationType.IMMEDIATE, RuleType.NO_CONFLICT); + var shortRunningProjects = createMultipleTestProjects(BuildDurationType.SHORT_RUNNING, RuleType.NO_CONFLICT); + var projectsToBuild = getAllProjects(); + makeProjectBuildConfigurationsDependOnEachOther(projectsToBuild); + int minimumExecutionTimeInMillis = shortRunningProjects.size() + * BuildDurationType.SHORT_RUNNING.getDurationInMillis(); + ExpectedExecutionTime expectedExecutionTime = ExpectedExecutionTime + .captureFromCurrentTime(minimumExecutionTimeInMillis); + executeIncrementalWorkspaceBuild(() -> { + assertBuildsToFinish(projectsToBuild); + expectedExecutionTime.assertMinimumExecutionTimeReached(); + assertMaximumNumberOfSimultaneousBuilds(1); + assertSequentialBuildEventsForProjects(projectsToBuild); + }); + } + + public void testWorkspaceBuild_DependentProjectBuildConfigurations_ProjectSubset() throws Exception { + var immediateBuiltProjects = createMultipleTestProjects(BuildDurationType.IMMEDIATE, RuleType.NO_CONFLICT); + var shortRunningProjects = createMultipleTestProjects(BuildDurationType.SHORT_RUNNING, RuleType.NO_CONFLICT); + var projectsToBuild = List.of(immediateBuiltProjects.get(0), + immediateBuiltProjects.get(immediateBuiltProjects.size() - 1), shortRunningProjects.get(0), + shortRunningProjects.get(shortRunningProjects.size() - 1)); + makeProjectBuildConfigurationsDependOnEachOther(getAllProjects()); + IBuildConfiguration[] selectedBuildConfigurations = getBuildConfigurations(projectsToBuild); + int minimumExecutionTimeInMillis = 2 * BuildDurationType.SHORT_RUNNING.getDurationInMillis(); + ExpectedExecutionTime expectedExecutionTime = ExpectedExecutionTime + .captureFromCurrentTime(minimumExecutionTimeInMillis); + executeIncrementalWorkspaceBuild(selectedBuildConfigurations, () -> { + assertBuildsToFinish(projectsToBuild); + expectedExecutionTime.assertMinimumExecutionTimeReached(); + assertMaximumNumberOfSimultaneousBuilds(1); + assertSequentialBuildEventsForProjects(projectsToBuild); + }); + } + + private List getAllProjects() { + return Arrays.asList(getWorkspace().getRoot().getProjects()); + } + + private static IBuildConfiguration[] getBuildConfigurations(List projects) throws CoreException { + IBuildConfiguration[] buildConfigurations = new IBuildConfiguration[projects.size()]; + for (int projectNumber = 0; projectNumber < projects.size(); projectNumber++) { + buildConfigurations[projectNumber] = projects.get(projectNumber).getActiveBuildConfig(); } - setTimerBuilderSchedulingRuleForAllProjects(RuleType.NO_CONFLICT, getMonitor()); - long duration = System.currentTimeMillis(); - Job job = new Job("Workspace Build") { - @Override - protected IStatus run(IProgressMonitor monitor) { - try { - getWorkspace().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor()); - return Status.OK_STATUS; - } catch (CoreException e) { - return new Status(IStatus.ERROR, "org.eclipse.core.tests.resources", e.getMessage(), e); - } - } - }; - job.schedule(); - Assert.assertTrue("Timeout, most likely a deadlock", job.join(5000, getMonitor())); - duration = System.currentTimeMillis() - duration; - assertEquals(allProjects.length, TimerBuilder.getTotalBuilds()); - assertEquals(1, TimerBuilder.getMaxSimultaneousBuilds()); - assertTrue(duration > projectWithLongRunningBuilds().length * LONG_BUILD_DURATION); - assertEquals(sequentialBuildEvents(allProjects), TimerBuilder.events); - } - - private List sequentialBuildEvents(IProject[] allProjects) { - List res = new ArrayList<>(allProjects.length * 2); - for (IProject project : allProjects) { - res.add(TimerBuilder.buildStartEvent(project)); - res.add(TimerBuilder.buildCompleteEvent(project)); + return buildConfigurations; + } + + private List createMultipleTestProjects(BuildDurationType buildDurationType, RuleType ruleType) + throws CoreException { + List result = new ArrayList<>(); + for (int projectNumber = 0; projectNumber < NUMBER_OF_PROJECTS_TO_CREATE_AT_ONCE; projectNumber++) { + result.add(createTestProject(buildDurationType, ruleType)); } - return res; + return result; + } + + private IProject createTestProject(BuildDurationType buildDurationType, RuleType ruleType) throws CoreException { + String projectName = createUniqueProjectName(buildDurationType.toString()); + IWorkspaceRoot root = getWorkspace().getRoot(); + IProject project = root.getProject(projectName); + ensureExistsInWorkspace(project, true); + configureTimerBuilder(project, buildDurationType.getDurationInMillis(), ruleType); + return project; } - public void testDependentBuildConfigBuildSequentially() throws Exception { - IProject[] allProjects = getWorkspace().getRoot().getProjects(); - for (int i = 1; i < allProjects.length; i++) { - IProject project = allProjects[i]; + private String createUniqueProjectName(String projectPrefix) { + int suffix = 0; + IWorkspaceRoot root = getWorkspace().getRoot(); + while (root.getProject(projectPrefix + "Project" + suffix).exists()) { + suffix++; + } + return projectPrefix + "Project" + suffix; + } + + private void configureTimerBuilder(IProject project, int duration, RuleType ruleType) throws CoreException { + BuildCommand buildCommand = new BuildCommand(); + buildCommand.setBuilderName(TimerBuilder.BUILDER_NAME); + Map arguments = new HashMap<>(); + arguments.put(TimerBuilder.DURATION_ARG, Integer.toString(duration)); + arguments.put(TimerBuilder.RULE_TYPE_ARG, ruleType.toString()); + buildCommand.setArguments(arguments); + IProjectDescription projectDescription = project.getDescription(); + projectDescription.setBuildSpec(new ICommand[] { buildCommand }); + project.setDescription(projectDescription, getMonitor()); + } + + private void makeProjectsDependOnEachOther(List projects) throws CoreException { + for (int projectNumber = 1; projectNumber < projects.size(); projectNumber++) { + IProject project = projects.get(projectNumber); IProjectDescription desc = project.getDescription(); - desc.setBuildConfigReferences(project.getActiveBuildConfig().getName(), new IBuildConfiguration[] {allProjects[i - 1].getActiveBuildConfig()}); + desc.setReferencedProjects(new IProject[] { projects.get(projectNumber - 1) }); project.setDescription(desc, getMonitor()); } - setTimerBuilderSchedulingRuleForAllProjects(RuleType.NO_CONFLICT, getMonitor()); - long duration = System.currentTimeMillis(); + } + + private void makeProjectBuildConfigurationsDependOnEachOther(List projects) throws CoreException { + for (int projectNumber = 1; projectNumber < projects.size(); projectNumber++) { + IProject project = projects.get(projectNumber); + IProjectDescription description = project.getDescription(); + description.setBuildConfigReferences(project.getActiveBuildConfig().getName(), + new IBuildConfiguration[] { projects.get(projectNumber - 1).getActiveBuildConfig() }); + project.setDescription(description, getMonitor()); + } + } + + private void executeIncrementalWorkspaceBuild(Runnable executeWhileRunningBuild) throws Exception { + executeIncrementalWorkspaceBuild(null, executeWhileRunningBuild); + } + + private void executeIncrementalWorkspaceBuild(IBuildConfiguration[] buildConfigurations, + Runnable executeWhileRunningBuild) throws Exception { + int expectedNumberOfBuilds = buildConfigurations != null ? buildConfigurations.length : getAllProjects().size(); + TimerBuilder.setExpectedNumberOfBuilds(expectedNumberOfBuilds); + TestBarrier2 waitForRunningJobBarrier = new TestBarrier2(); Job job = new Job("Workspace Build") { @Override protected IStatus run(IProgressMonitor monitor) { try { - getWorkspace().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor()); + waitForRunningJobBarrier.setStatus(TestBarrier2.STATUS_RUNNING); + if (buildConfigurations != null) { + getWorkspace().build(buildConfigurations, IncrementalProjectBuilder.INCREMENTAL_BUILD, false, + getMonitor()); + } else { + getWorkspace().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, getMonitor()); + } return Status.OK_STATUS; } catch (CoreException e) { - return new Status(IStatus.ERROR, "org.eclipse.core.tests.resources", e.getMessage(), e); + return new Status(IStatus.ERROR, PI_RESOURCES_TESTS, e.getMessage(), e); } + } }; job.schedule(); - Assert.assertTrue("Timeout, most likely a deadlock", job.join(0, getMonitor())); - duration = System.currentTimeMillis() - duration; - assertEquals(allProjects.length, TimerBuilder.getTotalBuilds()); - assertEquals(1, TimerBuilder.getMaxSimultaneousBuilds()); - assertTrue(duration > projectWithLongRunningBuilds().length * LONG_BUILD_DURATION); - assertEquals(sequentialBuildEvents(allProjects), TimerBuilder.events); - } - - public void testDependentBuildConfigsSubset() throws Exception { - setTimerBuilderSchedulingRuleForAllProjects(RuleType.NO_CONFLICT, getMonitor()); - IProject[] allProjects = getWorkspace().getRoot().getProjects(); - for (int i = 1; i < allProjects.length; i++) { - IProject project = allProjects[i]; - IProjectDescription desc = project.getDescription(); - desc.setBuildConfigReferences(project.getActiveBuildConfig().getName(), - new IBuildConfiguration[] { allProjects[i - 1].getActiveBuildConfig() }); - project.setDescription(desc, getMonitor()); + waitForRunningJobBarrier.waitForStatus(TestBarrier2.STATUS_RUNNING); + try { + executeWhileRunningBuild.run(); + } finally { + TimerBuilder.abortCurrentBuilds(); + job.cancel(); + boolean joinSuccessful = job.join(TIMEOUT_IN_MILLIS, getMonitor()); + Assert.assertTrue("timeout occurred when waiting for job that runs the build to finish", joinSuccessful); } - long duration = System.currentTimeMillis(); - Job job = new Job("Workspace Build") { - @Override - protected IStatus run(IProgressMonitor monitor) { - try { - getWorkspace().build(new IBuildConfiguration[] {allProjects[0].getActiveBuildConfig(), allProjects[2].getActiveBuildConfig(), allProjects[3].getActiveBuildConfig(), allProjects[5].getActiveBuildConfig(),}, IncrementalProjectBuilder.INCREMENTAL_BUILD, false, getMonitor()); - return Status.OK_STATUS; - } catch (CoreException e) { - return new Status(IStatus.ERROR, "org.eclipse.core.tests.resources", e.getMessage(), e); + } + + private void executeIndividualFullProjectBuilds(Runnable executeWhileRunningBuild) throws Exception { + int maximumThreadsForJobGroup = 5; + List projects = getAllProjects(); + TimerBuilder.setExpectedNumberOfBuilds(projects.size()); + JobGroup jobGroup = new JobGroup("Build Group", maximumThreadsForJobGroup, projects.size()); + Map waitForRunningJobBarriers = new HashMap<>(); + for (IProject project : projects) { + waitForRunningJobBarriers.put(project, new TestBarrier2()); + Job job = new Job("Building " + project.getName()) { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + waitForRunningJobBarriers.get(project).setStatus(TestBarrier2.STATUS_RUNNING); + project.build(IncrementalProjectBuilder.FULL_BUILD, getMonitor()); + return Status.OK_STATUS; + } catch (CoreException e) { + return new Status(IStatus.ERROR, PI_RESOURCES_TESTS, e.getMessage(), e); + } + } - } - }; - job.schedule(); - Assert.assertTrue("Timeout, most likely a deadlock", job.join(0, getMonitor())); - duration = System.currentTimeMillis() - duration; - assertEquals(allProjects.length - 2, TimerBuilder.getTotalBuilds()); - assertEquals(Arrays.asList(TimerBuilder.buildStartEvent(allProjects[0]), - TimerBuilder.buildCompleteEvent(allProjects[0]), - TimerBuilder.buildStartEvent(allProjects[2]), - TimerBuilder.buildCompleteEvent(allProjects[2]), - TimerBuilder.buildStartEvent(allProjects[3]), - TimerBuilder.buildCompleteEvent(allProjects[3]), - TimerBuilder.buildStartEvent(allProjects[5]), - TimerBuilder.buildCompleteEvent(allProjects[5]) - ), TimerBuilder.events); + }; + job.setJobGroup(jobGroup); + job.schedule(); + } + for (TestBarrier2 barrier : waitForRunningJobBarriers.values()) { + barrier.waitForStatus(TestBarrier2.STATUS_RUNNING); + } + try { + executeWhileRunningBuild.run(); + } finally { + TimerBuilder.abortCurrentBuilds(); + jobGroup.cancel(); + boolean joinSuccessful = jobGroup.join(TIMEOUT_IN_MILLIS, getMonitor()); + Assert.assertTrue("timeout occurred when waiting for job group that runs the builds to finish", + joinSuccessful); + } } + + private void assertMinimumNumberOfSimultaneousBuilds(int minimumNumberOfSimulaneousBuilds) { + assertThat("too few builds have run in parallel", TimerBuilder.getMaximumNumberOfSimultaneousBuilds(), + greaterThanOrEqualTo(minimumNumberOfSimulaneousBuilds)); + } + + private void assertMaximumNumberOfSimultaneousBuilds(int maximumNumberOfSimulaneousBuilds) { + assertThat("too many builds have run in parallel", TimerBuilder.getMaximumNumberOfSimultaneousBuilds(), + lessThanOrEqualTo(maximumNumberOfSimulaneousBuilds)); + } + + private void assertMaximumNumberOfWorkspaceBuilds() { + assertThat("too many workspace builds have run in parallel", + TimerBuilder.getMaximumNumberOfSimultaneousBuilds(), + lessThanOrEqualTo(getWorkspace().getDescription().getMaxConcurrentBuilds())); + } + + private void assertBuildsToStart(List projects) { + waitForCondition(() -> TimerBuilder.getStartedProjectBuilds().containsAll(projects), TIMEOUT_IN_MILLIS); + assertThat("not all build jobs have started in time", TimerBuilder.getStartedProjectBuilds(), + hasItems(projects.toArray(IProject[]::new))); + } + + private static class ExpectedExecutionTime { + final long startTimeInNs = System.nanoTime(); + final long minimumExecutionTimeInMillis; + + private ExpectedExecutionTime(int minimumExecutionTimeInMillis) { + this.minimumExecutionTimeInMillis = minimumExecutionTimeInMillis; + } + + private long getExecutionTimeInMillis() { + return (int) ((System.nanoTime() - startTimeInNs) / 1_000_000); + } + + void assertMinimumExecutionTimeReached() { + assertThat("build was faster than the expected execution time (in milliseconds)", + getExecutionTimeInMillis(), greaterThanOrEqualTo(minimumExecutionTimeInMillis)); + } + + static ExpectedExecutionTime captureFromCurrentTime(int minimumExecutionTimeInMillis) { + return new ExpectedExecutionTime(minimumExecutionTimeInMillis); + } + } + + private void assertBuildsToFinish(List projects) { + waitForCondition(() -> TimerBuilder.getFinishedProjectBuilds().containsAll(projects), TIMEOUT_IN_MILLIS); + assertThat("not all build jobs have finished in time", TimerBuilder.getFinishedProjectBuilds(), + hasItems(projects.toArray(IProject[]::new))); + } + + private void assertSequentialBuildEventsForProjects(Iterable projects) { + assertThat("unexpected order of build events occurred", TimerBuilder.getBuildEvents(), + equalTo(getExpectedSequentialBuildEvents(projects))); + } + + private Iterable getExpectedSequentialBuildEvents(Iterable projects) { + List res = new ArrayList<>(); + for (IProject project : projects) { + res.add(TimerBuilder.createStartEvent(project)); + res.add(TimerBuilder.createCompleteEvent(project)); + } + return res; + } + } diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/TimerBuilder.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/TimerBuilder.java index 2ead71a8513..6545c6e2ac6 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/TimerBuilder.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/TimerBuilder.java @@ -13,8 +13,17 @@ *******************************************************************************/ package org.eclipse.core.tests.internal.builders; -import java.util.*; -import org.eclipse.core.resources.*; +import static org.eclipse.core.tests.resources.TestUtil.waitForCondition; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IncrementalProjectBuilder; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.jobs.ISchedulingRule; @@ -27,10 +36,74 @@ public class TimerBuilder extends IncrementalProjectBuilder { public static final String DURATION_ARG = "duration"; public static final String RULE_TYPE_ARG = "ruleType"; - private static int totalBuilds = 0; - private static int currentBuilds = 0; - private static int maxSimultaneousBuilds = 0; - public static final List events = Collections.synchronizedList(new ArrayList<>()); + private static BuildExecutionState executionState = new BuildExecutionState(-1); + + private static class BuildExecutionState { + private final int expectedNumberOfBuilds; + private final List events = Collections.synchronizedList(new ArrayList<>()); + private volatile boolean shallAbort = false; + private volatile int maxSimultaneousBuilds = 0; + private volatile int currentlyRunningBuilds = 0; + + private BuildExecutionState(int expectedNumberOfBuilds) { + this.expectedNumberOfBuilds = expectedNumberOfBuilds; + } + + private synchronized boolean isExecuting() { + return getProjectBuilds(BuildEventType.FINISH).size() < executionState.expectedNumberOfBuilds; + } + + private synchronized List getProjectBuilds(BuildEventType eventType) { + return events.stream().filter(event -> event.eventType == eventType) + .map(event -> event.project).toList(); + } + + private synchronized void startedExecutingProject(IProject project) { + currentlyRunningBuilds++; + maxSimultaneousBuilds = Math.max(currentlyRunningBuilds, maxSimultaneousBuilds); + events.add(new BuildEvent(project, BuildEventType.START)); + } + + private synchronized void endedExcecutingProject(IProject project) { + currentlyRunningBuilds--; + events.add(new BuildEvent(project, BuildEventType.FINISH)); + notifyAll(); + } + + private synchronized void abortAndWaitForAllBuilds() { + shallAbort = true; + while (isExecuting()) { + try { + wait(); + } catch (InterruptedException e) { + } + } + } + + } + + private enum BuildEventType { + START, FINISH; + } + + private static class BuildEvent { + private final IProject project; + + private final BuildEventType eventType; + + public BuildEvent(IProject project, BuildEventType event) { + this.project = project; + this.eventType = event; + } + + @Override + public boolean equals(Object other) { + if (other instanceof BuildEvent otherEvent) { + return project == otherEvent.project && eventType == otherEvent.eventType; + } + return false; + } + } public static enum RuleType { NO_CONFLICT, CURRENT_PROJECT, WORKSPACE_ROOT, CURRENT_PROJECT_RELAXED; @@ -63,34 +136,18 @@ public boolean contains(ISchedulingRule rule) { @Override protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { - synchronized (TimerBuilder.class) { - totalBuilds++; - currentBuilds++; - maxSimultaneousBuilds = Math.max(currentBuilds, maxSimultaneousBuilds); - events.add(buildStartEvent(getProject())); - } - int duration = 0; + assertNotEquals("no expected number of builds has been set", -1, executionState.expectedNumberOfBuilds); + executionState.startedExecutingProject(getProject()); try { - duration = Integer.parseInt(args.get(DURATION_ARG)); - Thread.sleep(duration); + int durationInMillis = Integer.parseInt(args.get(DURATION_ARG)); + waitForCondition(() -> executionState.shallAbort, durationInMillis); } catch (Exception ex) { ex.printStackTrace(); } - synchronized (TimerBuilder.class) { - currentBuilds--; - events.add(buildCompleteEvent(getProject())); - } + executionState.endedExcecutingProject(getProject()); return new IProject[] {getProject()}; } - public static Object buildCompleteEvent(IProject project) { - return "Compete " + project.getName(); - } - - public static Object buildStartEvent(IProject project) { - return "Started " + project.getName(); - } - @Override public ISchedulingRule getRule(int trigger, Map args) { if (args != null) { @@ -109,24 +166,41 @@ public ISchedulingRule getRule(int trigger, Map args) { return noConflictRule; } - public static int getTotalBuilds() { - synchronized (TimerBuilder.class) { - return totalBuilds; - } + public static List getStartedProjectBuilds() { + return executionState.getProjectBuilds(BuildEventType.START); } - public static int getMaxSimultaneousBuilds() { - synchronized (TimerBuilder.class) { - return maxSimultaneousBuilds; - } + public static List getFinishedProjectBuilds() { + return executionState.getProjectBuilds(BuildEventType.FINISH); } - public static void reset() { - synchronized (TimerBuilder.class) { - totalBuilds = 0; - currentBuilds = 0; - maxSimultaneousBuilds = 0; - events.clear(); - } + public static int getMaximumNumberOfSimultaneousBuilds() { + return executionState.maxSimultaneousBuilds; } + + public static Iterable getBuildEvents() { + return new ArrayList<>(executionState.events); + } + + /** + * Resets the tracked execution states. Asserts that no execution is still + * running. + */ + public static void setExpectedNumberOfBuilds(int expectedNumberOfBuilds) { + assertFalse("builds are still running while resetting TimerBuilder", executionState.isExecuting()); + executionState = new BuildExecutionState(expectedNumberOfBuilds); + } + + public static void abortCurrentBuilds() { + executionState.abortAndWaitForAllBuilds(); + } + + public static BuildEvent createStartEvent(IProject project) { + return new BuildEvent(project, BuildEventType.START); + } + + public static BuildEvent createCompleteEvent(IProject project) { + return new BuildEvent(project, BuildEventType.FINISH); + } + } diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/TestUtil.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/TestUtil.java index d4880992158..4c65575a6f3 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/TestUtil.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/TestUtil.java @@ -16,10 +16,13 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.eclipse.core.internal.runtime.InternalPlatform; import org.eclipse.core.internal.utils.StringPoolJob; -import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.junit.Assert; @@ -57,6 +60,27 @@ public static void log(int severity, String owner, String message, Throwable... InternalPlatform.getDefault().getLog(Platform.getBundle("org.eclipse.core.tests.resources")).log(status); } + /** + * Waits until the given condition is fulfilled or the given timeout is reached. + * + * @param condition + * the condition to validate periodically + * @param timeoutInMilliseconds + * the timeout for abortion in case the condition is not fulfilled + * @return {@code true} if the condition was fulfilled and {@code false} if the + * timeout value was reached + */ + public static boolean waitForCondition(Supplier condition, int timeoutInMilliseconds) { + long timeoutInNs = timeoutInMilliseconds * 1_000_000L; + long startTimeInNs = System.nanoTime(); + long durationInNs = 0; + while (!condition.get() && durationInNs < timeoutInNs) { + Thread.yield(); + durationInNs = System.nanoTime() - startTimeInNs; + } + return condition.get(); + } + /** * Utility for waiting until the execution of jobs of any family has finished or timeout is reached. If no jobs are running, the method waits * given minimum wait time. While this method is waiting for jobs, UI events are processed.