/******************************************************************************* * Copyright (c) 2010, 2015 Broadcom Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * James Blackburn (Broadcom Corp.) - initial API and implementation * Alexander Kurtakov <akurtako@redhat.com> - Bug 459343 *******************************************************************************/ package org.eclipse.core.tests.internal.builders; import java.io.ByteArrayInputStream; import java.util.*; import junit.framework.Test; import junit.framework.TestSuite; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.jobs.*; import org.eclipse.core.tests.harness.TestBarrier; import org.eclipse.core.tests.internal.builders.TestBuilder.BuilderRuleCallback; /** * This class tests extended functionality (since 3.6) which allows * builders to be run with reduced (non-Workspace) scheduling rules. * * When one of these builders runs, other threads may modify the workspace * depending on the builder's scheduling rule */ public class RelaxedSchedRuleBuilderTest extends AbstractBuilderTest { public static Test suite() { return new TestSuite(RelaxedSchedRuleBuilderTest.class); } public RelaxedSchedRuleBuilderTest() { super(null); } public RelaxedSchedRuleBuilderTest(String name) { super(name); } @Override protected void setUp() throws Exception { super.setUp(); } @Override protected void tearDown() throws Exception { super.tearDown(); getWorkspace().getRoot().delete(true, null); TestBuilder builder = DeltaVerifierBuilder.getInstance(); if (builder != null) { builder.reset(); } builder = EmptyDeltaBuilder.getInstance(); if (builder != null) { builder.reset(); } builder = EmptyDeltaBuilder2.getInstance(); if (builder != null) { builder.reset(); } } /** * Test a simple builder with a relaxed scheduling rule * @throws Exception */ public void testBasicRelaxedSchedulingRules() throws Exception { String name = "TestRelaxed"; setAutoBuilding(false); final IProject project = getWorkspace().getRoot().getProject(name); create(project, false); addBuilder(project, EmptyDeltaBuilder.BUILDER_NAME); // Ensure the builder is instantiated project.build(IncrementalProjectBuilder.CLEAN_BUILD, getMonitor()); final TestBarrier tb = new TestBarrier(TestBarrier.STATUS_WAIT_FOR_START); // Create a builder set a null scheduling rule EmptyDeltaBuilder builder = EmptyDeltaBuilder.getInstance(); builder.setRuleCallback(new BuilderRuleCallback() { @Override public ISchedulingRule getRule(String name, IncrementalProjectBuilder builder, int trigger, Map<String, String> args) { return null; } @Override public IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { assertTrue(Job.getJobManager().currentRule() == null); tb.setStatus(TestBarrier.STATUS_START); while (!monitor.isCanceled()) { try { Thread.sleep(10); } catch (InterruptedException e) { // Don't care } } tb.setStatus(TestBarrier.STATUS_DONE); return super.build(kind, args, monitor); } }); Job j = new Job("build job") { @Override protected IStatus run(IProgressMonitor monitor) { try { project.build(IncrementalProjectBuilder.FULL_BUILD, monitor); } catch (CoreException e) { fail(); } return Status.OK_STATUS; } }; j.schedule(); // wait for build to be called tb.waitForStatus(TestBarrier.STATUS_START); // Should be able to write a file in the project create(project.getFile("foo.c"), false); assertTrue(project.getFile("foo.c").exists()); // Cancel the builder j.cancel(); tb.waitForStatus(TestBarrier.STATUS_DONE); } /** * Test two builders, each with relaxed scheduling rules, run in the same build operation * Each should be run with their own scheduling rule. * Tests: * Bug 306824 - null scheduling rule and non-null scheduling rule don't work together * Builders should have separate scheduling rules * @throws Exception */ public void testTwoBuildersRunInOneBuild() throws Exception { String name = "testTwoBuildersRunInOneBuild"; setAutoBuilding(false); final IProject project = getWorkspace().getRoot().getProject(name); create(project, false); IProjectDescription desc = project.getDescription(); desc.setBuildSpec(new ICommand[] {createCommand(desc, EmptyDeltaBuilder.BUILDER_NAME, "Project1Build1"), createCommand(desc, EmptyDeltaBuilder2.BUILDER_NAME, "Project1Build2")}); project.setDescription(desc, getMonitor()); // Ensure the builder is instantiated project.build(IncrementalProjectBuilder.CLEAN_BUILD, getMonitor()); final TestBarrier tb1 = new TestBarrier(TestBarrier.STATUS_WAIT_FOR_START); final TestBarrier tb2 = new TestBarrier(TestBarrier.STATUS_WAIT_FOR_START); // Create a builder set a null scheduling rule EmptyDeltaBuilder builder = EmptyDeltaBuilder.getInstance(); EmptyDeltaBuilder2 builder2 = EmptyDeltaBuilder2.getInstance(); // Set the rule call-back builder.setRuleCallback(new BuilderRuleCallback() { @Override public ISchedulingRule getRule(String name, IncrementalProjectBuilder builder, int trigger, Map<String, String> args) { tb1.setStatus(TestBarrier.STATUS_START); return project; } @Override public IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { // shared scheduling rule assertTrue(Job.getJobManager().currentRule().contains(project)); tb1.setStatus(TestBarrier.STATUS_RUNNING); tb1.waitForStatus(TestBarrier.STATUS_WAIT_FOR_DONE); tb1.setStatus(TestBarrier.STATUS_DONE); return super.build(kind, args, monitor); } }); // Set the rule call-back builder2.setRuleCallback(new BuilderRuleCallback() { @Override public ISchedulingRule getRule(String name, IncrementalProjectBuilder builder, int trigger, Map<String, String> args) { // get rule is called before starting tb2.setStatus(TestBarrier.STATUS_START); return null; } @Override public IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { // shared scheduling rule assertTrue(Job.getJobManager().currentRule() == null || Job.getJobManager().currentRule().contains(getWorkspace().getRoot())); tb2.setStatus(TestBarrier.STATUS_RUNNING); tb2.waitForStatus(TestBarrier.STATUS_WAIT_FOR_DONE); tb2.setStatus(TestBarrier.STATUS_DONE); return super.build(kind, args, monitor); } }); // Run the build Job j = new Job("build job1") { @Override protected IStatus run(IProgressMonitor monitor) { try { project.build(IncrementalProjectBuilder.FULL_BUILD, monitor); } catch (CoreException e) { fail(); } return Status.OK_STATUS; } }; j.schedule(); // Wait for the build to transition tb1.waitForStatus(TestBarrier.STATUS_RUNNING); tb1.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE); tb1.waitForStatus(TestBarrier.STATUS_DONE); tb2.waitForStatus(TestBarrier.STATUS_RUNNING); tb2.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE); tb2.waitForStatus(TestBarrier.STATUS_DONE); } HashSet<ISchedulingRule> getRulesAsSet(ISchedulingRule rule) { HashSet<ISchedulingRule> rules = new HashSet<>(); if (rule == null) { return rules; } if (rule instanceof MultiRule) { rules.addAll(Arrays.asList(((MultiRule) rule).getChildren())); } else { rules.add(rule); } return rules; } /** * As the builder is run with a relaxed scheduling rule, we ensure that any changes made before * the build is actually run are present in the delta. * Acquiring the scheduling rule must be done outside of the WS lock, so this tests that * a change which sneaks in during the window or the build thread acquiring its scheduling * rule, is correctly present in the builder's delta. * @throws Exception */ public void testBuilderDeltaUsingRelaxedRuleBug343256() throws Exception { String name = "testBuildDeltaUsingRelaxedRuleBug343256"; setAutoBuilding(false); final IProject project = getWorkspace().getRoot().getProject(name); final IFile foo = project.getFile("foo"); create(project, false); IProjectDescription desc = project.getDescription(); desc.setBuildSpec(new ICommand[] {createCommand(desc, EmptyDeltaBuilder.BUILDER_NAME, "Project1Build1")}); project.setDescription(desc, getMonitor()); // Ensure the builder is instantiated project.build(IncrementalProjectBuilder.FULL_BUILD, getMonitor()); final TestBarrier tb1 = new TestBarrier(TestBarrier.STATUS_WAIT_FOR_START); // Create a builder set a null scheduling rule EmptyDeltaBuilder builder = EmptyDeltaBuilder.getInstance(); // Set the rule call-back builder.setRuleCallback(new BuilderRuleCallback() { boolean called = false; @Override public ISchedulingRule getRule(String name, IncrementalProjectBuilder builder, int trigger, Map<String, String> args) { // Remove once Bug 331187 is fixed. // Currently #getRule is called twice when building a specific build configuration (so as to minimized change in // 3.7 end-game. As this test is trying to provoke a bug in the window between fetching a rule and applying it // to the build, we don't want to run the first time #getRule is called (in Workspace#build) if (!called) { called = true; return project; } // REMOVE tb1.setStatus(TestBarrier.STATUS_START); tb1.waitForStatus(TestBarrier.STATUS_WAIT_FOR_RUN); try { // Give the resource modification time be queued Thread.sleep(10); } catch (InterruptedException e) { fail(); } return project; } @Override public IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { // shared scheduling rule assertTrue(Job.getJobManager().currentRule().equals(project)); // assert that the delta contains the file foo IResourceDelta delta = getDelta(project); assertNotNull("1.1", delta); assertTrue("1.2", delta.getAffectedChildren().length == 1); assertTrue("1.3", delta.getAffectedChildren()[0].getResource().equals(foo)); tb1.setStatus(TestBarrier.STATUS_DONE); return super.build(kind, args, monitor); } }); // Run the incremental build Job j = new Job("IProject.build()") { @Override protected IStatus run(IProgressMonitor monitor) { try { getWorkspace().build(new IBuildConfiguration[] {project.getActiveBuildConfig()}, IncrementalProjectBuilder.INCREMENTAL_BUILD, true, monitor); } catch (CoreException e) { fail(); } return Status.OK_STATUS; } }; j.schedule(); // Wait for the build to transition to getRule tb1.waitForStatus(TestBarrier.STATUS_START); // Modify a file in the project j = new Job("IProject.build()") { @Override protected IStatus run(IProgressMonitor monitor) { tb1.setStatus(TestBarrier.STATUS_WAIT_FOR_RUN); ensureExistsInWorkspace(foo, new ByteArrayInputStream(new byte[0])); return Status.OK_STATUS; } }; j.schedule(); tb1.waitForStatus(TestBarrier.STATUS_DONE); } /** * Tests for regression in running the build with reduced scheduling rules. * @throws Exception */ public void testBug343256() throws Exception { String name = "testBug343256"; setAutoBuilding(false); final IProject project = getWorkspace().getRoot().getProject(name); create(project, false); IProjectDescription desc = project.getDescription(); desc.setBuildSpec(new ICommand[] {createCommand(desc, EmptyDeltaBuilder.BUILDER_NAME, "Project1Build1"), createCommand(desc, EmptyDeltaBuilder2.BUILDER_NAME, "Project1Build2")}); project.setDescription(desc, getMonitor()); // Ensure the builder is instantiated project.build(IncrementalProjectBuilder.CLEAN_BUILD, getMonitor()); final TestBarrier tb1 = new TestBarrier(TestBarrier.STATUS_WAIT_FOR_START); final TestBarrier tb2 = new TestBarrier(TestBarrier.STATUS_WAIT_FOR_START); // Scheduling rules to returng from #getRule final ISchedulingRule[] getRules = new ISchedulingRule[2]; final ISchedulingRule[] buildRules = new ISchedulingRule[2]; // Create a builder set a null scheduling rule EmptyDeltaBuilder builder = EmptyDeltaBuilder.getInstance(); EmptyDeltaBuilder2 builder2 = EmptyDeltaBuilder2.getInstance(); // Set the rule call-back builder.setRuleCallback(new BuilderRuleCallback() { @Override public ISchedulingRule getRule(String name, IncrementalProjectBuilder builder, int trigger, Map<String, String> args) { tb1.waitForStatus(TestBarrier.STATUS_START); return getRules[0]; } @Override public IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { HashSet<ISchedulingRule> h1 = getRulesAsSet(Job.getJobManager().currentRule()); HashSet<ISchedulingRule> h2 = getRulesAsSet(buildRules[0]); // shared scheduling rule assertTrue(h1.equals(h2)); tb1.setStatus(TestBarrier.STATUS_DONE); return super.build(kind, args, monitor); } }); // Set the rule call-back builder2.setRuleCallback(new BuilderRuleCallback() { @Override public ISchedulingRule getRule(String name, IncrementalProjectBuilder builder, int trigger, Map<String, String> args) { tb2.waitForStatus(TestBarrier.STATUS_START); return getRules[1]; } @Override public IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { HashSet<ISchedulingRule> h1 = getRulesAsSet(Job.getJobManager().currentRule()); HashSet<ISchedulingRule> h2 = getRulesAsSet(buildRules[1]); assertTrue(h1.equals(h2)); tb2.setStatus(TestBarrier.STATUS_DONE); return super.build(kind, args, monitor); } }); Job j; // Enable for Bug 331187 // // IProject.build() // j = new Job("IProject.build()") { // protected IStatus run(IProgressMonitor monitor) { // try { // project.build(IncrementalProjectBuilder.FULL_BUILD, monitor); // } catch (CoreException e) { // fail(e.toString()); // } // return Status.OK_STATUS; // } // }; // invokeTestBug343256(project, getRules, buildRules, tb1, tb2, j); // // IWorkspace.build() // j = new Job("IWorkspace.build()") { // protected IStatus run(IProgressMonitor monitor) { // try { // getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, monitor); // } catch (CoreException e) { // fail(e.toString()); // } // return Status.OK_STATUS; // } // }; // invokeTestBug343256(project, getRules, buildRules, tb1, tb2, j); // IWorkspace.build(IBuildConfiguration[],...) j = new Job("IWorkspace.build(IBuildConfiguration[],...)") { @Override protected IStatus run(IProgressMonitor monitor) { try { getWorkspace().build(new IBuildConfiguration[] {project.getActiveBuildConfig()}, IncrementalProjectBuilder.FULL_BUILD, true, monitor); } catch (CoreException e) { fail(e.toString()); } return Status.OK_STATUS; } }; invokeTestBug343256(project, getRules, buildRules, tb1, tb2, j); // Test Auto-build // j = new Job("Auto-build") { // protected IStatus run(IProgressMonitor monitor) { // try { // getWorkspace().build(IncrementalProjectBuilder.CLEAN_BUILD, getMonitor()); // } catch (CoreException e) { // fail(e.toString()); // } // return Status.OK_STATUS; // } // }; // // Auto-build // setAutoBuilding(true); // // Wait for the build to transition // invokeTestBug343256(project, getRules, buildRules, tb1, tb2, j); } /** * Helper method do invoke a set of tests on Bug343256 using the different sets of builder API */ private void invokeTestBug343256(IProject project, ISchedulingRule[] getRules, ISchedulingRule[] buildRules, TestBarrier tb1, TestBarrier tb2, Job j) { // Test 1 - build project sched rule getRules[0] = getRules[1] = project; buildRules[0] = buildRules[1] = new MultiRule(new ISchedulingRule[] {getRules[0]}); tb1.setStatus(TestBarrier.STATUS_START); tb2.setStatus(TestBarrier.STATUS_START); j.schedule(); tb1.waitForStatus(TestBarrier.STATUS_DONE); tb2.waitForStatus(TestBarrier.STATUS_DONE); // Test 2 - build null rule getRules[0] = getRules[1] = null; buildRules[0] = buildRules[1] = null; tb1.setStatus(TestBarrier.STATUS_START); tb2.setStatus(TestBarrier.STATUS_START); j.schedule(); tb1.waitForStatus(TestBarrier.STATUS_DONE); tb2.waitForStatus(TestBarrier.STATUS_DONE); // Test 3 - build mixed projects getRules[0] = buildRules[0] = project; getRules[1] = buildRules[1] = getWorkspace().getRoot().getProject("other"); tb1.setStatus(TestBarrier.STATUS_START); tb2.setStatus(TestBarrier.STATUS_START); j.schedule(); tb1.waitForStatus(TestBarrier.STATUS_DONE); tb2.waitForStatus(TestBarrier.STATUS_DONE); // Test 4 - build project + null getRules[0] = buildRules[0] = project; getRules[1] = buildRules[1] = null; // TODO: Fixed in Bug 331187 ; BuildManager#getRule is pessimistic when there's a mixed resource and null rule buildRules[0] = buildRules[1] = getWorkspace().getRoot(); tb1.setStatus(TestBarrier.STATUS_START); tb2.setStatus(TestBarrier.STATUS_START); j.schedule(); tb1.waitForStatus(TestBarrier.STATUS_DONE); tb2.waitForStatus(TestBarrier.STATUS_DONE); } }