| /******************************************************************************* |
| * Copyright (c) 2003, 2018 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM - Initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.core.tests.runtime.jobs; |
| |
| import java.util.*; |
| import java.util.concurrent.Semaphore; |
| import junit.framework.AssertionFailedError; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.*; |
| import org.eclipse.core.tests.harness.*; |
| |
| /** |
| * Tests the API of the class IJobManager |
| */ |
| public class IJobManagerTest extends AbstractJobManagerTest { |
| class TestJobListener extends JobChangeAdapter { |
| private Set<Job> scheduled = Collections.synchronizedSet(new HashSet<Job>()); |
| |
| public void cancelAllJobs() { |
| Job[] jobs = scheduled.toArray(new Job[0]); |
| for (Job job : jobs) { |
| job.cancel(); |
| } |
| } |
| |
| @Override |
| public void done(IJobChangeEvent event) { |
| synchronized (IJobManagerTest.this) { |
| if (scheduled.remove(event.getJob())) { |
| //wake up the waitForCompletion method |
| completedJobs++; |
| IJobManagerTest.this.notify(); |
| } |
| } |
| } |
| |
| @Override |
| public void scheduled(IJobChangeEvent event) { |
| Job job = event.getJob(); |
| synchronized (IJobManagerTest.this) { |
| if (job instanceof TestJob) { |
| scheduledJobs++; |
| scheduled.add(job); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Tests that are timing sensitive cannot be released in automated tests. |
| * Set this flag to true to do manual timing sanity tests |
| */ |
| private static final boolean PEDANTIC = false; |
| |
| protected int completedJobs; |
| private IJobChangeListener[] jobListeners; |
| |
| protected int scheduledJobs; |
| |
| public IJobManagerTest() { |
| super(""); |
| } |
| |
| public IJobManagerTest(String name) { |
| super(name); |
| } |
| |
| /** |
| * Asserts the current job state |
| */ |
| public void assertState(String msg, Job job, int expectedState) { |
| int actualState = job.getState(); |
| if (actualState != expectedState) { |
| assertTrue(msg + ": expected state: " + printState(expectedState) + " actual state: " + printState(actualState), false); |
| } |
| } |
| |
| /** |
| * Cancels a list of jobs |
| */ |
| protected void cancel(ArrayList<Job> jobs) { |
| for (Job job : jobs) { |
| job.cancel(); |
| } |
| } |
| |
| private String printState(int state) { |
| switch (state) { |
| case Job.NONE : |
| return "NONE"; |
| case Job.WAITING : |
| return "WAITING"; |
| case Job.SLEEPING : |
| return "SLEEPING"; |
| case Job.RUNNING : |
| return "RUNNING"; |
| } |
| return "UNKNOWN"; |
| } |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| completedJobs = 0; |
| scheduledJobs = 0; |
| jobListeners = new IJobChangeListener[] {/* new VerboseJobListener(),*/ |
| new TestJobListener()}; |
| for (IJobChangeListener jobListener : jobListeners) { |
| manager.addJobChangeListener(jobListener); |
| } |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| for (IJobChangeListener jobListener : jobListeners) { |
| if (jobListener instanceof TestJobListener) { |
| ((TestJobListener) jobListener).cancelAllJobs(); |
| } |
| } |
| waitForCompletion(); |
| for (IJobChangeListener jobListener : jobListeners) { |
| manager.removeJobChangeListener(jobListener); |
| } |
| super.tearDown(); |
| // manager.startup(); |
| } |
| |
| public void testBadGlobalListener() { |
| final int[] status = new int[] {-1}; |
| Job job = new Job("testBadGlobalListener") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| status[0] = TestBarrier.STATUS_RUNNING; |
| return Status.OK_STATUS; |
| } |
| }; |
| IJobChangeListener listener = new JobChangeAdapter() { |
| @Override |
| public void running(IJobChangeEvent event) { |
| throw new Error("Thrown from bad global listener"); |
| } |
| }; |
| try { |
| Job.getJobManager().addJobChangeListener(listener); |
| job.schedule(); |
| TestBarrier.waitForStatus(status, TestBarrier.STATUS_RUNNING); |
| } finally { |
| Job.getJobManager().removeJobChangeListener(listener); |
| } |
| } |
| |
| public void testBadLocalListener() { |
| final int[] status = new int[] {-1}; |
| Job job = new Job("testBadLocalListener") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| status[0] = TestBarrier.STATUS_RUNNING; |
| return Status.OK_STATUS; |
| } |
| }; |
| IJobChangeListener listener = new JobChangeAdapter() { |
| @Override |
| public void running(IJobChangeEvent event) { |
| throw new Error("Thrown from bad local listener"); |
| } |
| }; |
| try { |
| job.addJobChangeListener(listener); |
| job.schedule(); |
| TestBarrier.waitForStatus(status, TestBarrier.STATUS_RUNNING); |
| } finally { |
| job.removeJobChangeListener(listener); |
| } |
| } |
| |
| public void testBeginInvalidNestedRules() { |
| final ISchedulingRule root = new PathRule("/"); |
| final ISchedulingRule invalid = new ISchedulingRule() { |
| @Override |
| public boolean isConflicting(ISchedulingRule rule) { |
| return this == rule; |
| } |
| |
| @Override |
| public boolean contains(ISchedulingRule rule) { |
| return this == rule || root.contains(rule); |
| } |
| }; |
| try { |
| Job.getJobManager().beginRule(invalid, null); |
| try { |
| Job.getJobManager().beginRule(root, null); |
| fail("1.0"); |
| } catch (IllegalArgumentException e) { |
| // expected |
| } finally { |
| Job.getJobManager().endRule(root); |
| } |
| } finally { |
| Job.getJobManager().endRule(invalid); |
| } |
| } |
| |
| /** |
| * Tests that if we call beginRule with a monitor that has already been |
| * cancelled, it won't try to obtain the rule. |
| */ |
| public void testCancellationPriorToBeginRuleWontHoldRule() throws Exception { |
| final Semaphore mainThreadSemaphore = new Semaphore(0); |
| final Semaphore lockSemaphore = new Semaphore(0); |
| final PathRule rule = new PathRule("testBeginRuleNoEnd"); |
| IProgressMonitor cancelledMonitor = SubMonitor.convert(null); |
| cancelledMonitor.setCanceled(true); |
| |
| // Create a job that will hold the lock until the semaphore is signaled |
| Job job = Job.create("", monitor -> { |
| mainThreadSemaphore.release(); |
| try { |
| lockSemaphore.acquire(); |
| } catch (InterruptedException e) { |
| } |
| }); |
| job.setRule(rule); |
| job.schedule(); |
| |
| // Block until the job acquires the lock |
| mainThreadSemaphore.acquire(); |
| boolean canceledExceptionThrown = false; |
| try { |
| // This will deadlock if it attempts to acquire the rule, and will |
| // throw an OCE without doing anything if it is working correctly. |
| manager.beginRule(rule, cancelledMonitor); |
| } catch (OperationCanceledException e) { |
| canceledExceptionThrown = true; |
| } finally { |
| // Code which follows the recommended pattern documented in |
| // beginRule will call endRule even if beginRule threw an OCE. |
| // Verify that calling endRule in this situation won't throw any |
| // exceptions. |
| manager.endRule(rule); |
| } |
| lockSemaphore.release(); |
| boolean interrupted = Thread.interrupted(); |
| assertTrue("An OperationCancelledException should have been thrown", canceledExceptionThrown); |
| assertFalse("The Thread.interrupted() state leaked", interrupted); |
| } |
| |
| /** |
| * Tests that if our monitor is cancelled while we're waiting on beginRule, |
| * it will stop waiting, will throw an {@link OperationCanceledException}, |
| * and will clear the Thread.interrupted() flag. |
| */ |
| public void testCancellationWhileWaitingOnRule() throws Exception { |
| final Semaphore mainThreadSemaphore = new Semaphore(0); |
| final Semaphore lockSemaphore = new Semaphore(0); |
| final PathRule rule = new PathRule("testBeginRuleNoEnd"); |
| final NullProgressMonitor rootMonitor = new NullProgressMonitor(); |
| // We use a SubMonitor here to work around a special case in the |
| // JobManager code that ignores NullProgressMonitor. |
| IProgressMonitor nestedMonitor = SubMonitor.convert(rootMonitor); |
| nestedMonitor.setCanceled(false); |
| |
| // Create a job that will hold the lock until the semaphore is signalled |
| Job job = Job.create("", monitor -> { |
| mainThreadSemaphore.release(); |
| try { |
| lockSemaphore.acquire(); |
| } catch (InterruptedException e) { |
| } |
| }); |
| job.setRule(rule); |
| job.schedule(); |
| |
| // Block until the job acquires the lock |
| mainThreadSemaphore.acquire(); |
| |
| // Create a job that will cancel our monitor in 100ms |
| Job cancellationJob = Job.create("", monitor -> { |
| rootMonitor.setCanceled(true); |
| }); |
| cancellationJob.schedule(100); |
| |
| boolean canceledExceptionThrown = false; |
| // Now try to obtain the rule that is currently held by "job". |
| try { |
| manager.beginRule(rule, nestedMonitor); |
| } catch (OperationCanceledException e) { |
| canceledExceptionThrown = true; |
| } finally { |
| // Code which follows the recommended pattern documented in |
| // beginRule will call endRule even if beginRule threw an OCE. |
| // Verify that calling endRule in this situation won't throw any |
| // exceptions. |
| manager.endRule(rule); |
| } |
| lockSemaphore.release(); |
| boolean interrupted = Thread.interrupted(); |
| assertTrue("An OperationCancelledException should have been thrown", canceledExceptionThrown); |
| assertFalse("The THread.interrupted() state leaked", interrupted); |
| } |
| |
| /** |
| * Tests running a job that begins a rule but never ends it |
| */ |
| public void testBeginRuleNoEnd() { |
| final PathRule rule = new PathRule("testBeginRuleNoEnd"); |
| Job job = new Job("testBeginRuleNoEnd") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| monitor.beginTask(getName(), 1); |
| try { |
| Job.getJobManager().beginRule(rule, null); |
| monitor.worked(1); |
| } finally { |
| monitor.done(); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| job.schedule(); |
| try { |
| job.join(); |
| } catch (InterruptedException e) { |
| fail("4.99", e); |
| } |
| //another thread should be able to access the rule now |
| try { |
| manager.beginRule(rule, null); |
| } finally { |
| manager.endRule(rule); |
| } |
| } |
| |
| public void testBug48073() { |
| ISchedulingRule ruleA = new PathRule("/testBug48073"); |
| ISchedulingRule ruleB = new PathRule("/testBug48073/B"); |
| ISchedulingRule ruleC = new PathRule("/testBug48073/C"); |
| TestJob jobA = new TestJob("Job1", 1000, 100); |
| TestJob jobB = new TestJob("Job2", 1000, 100); |
| TestJob jobC = new TestJob("Job3", 1000, 100); |
| jobA.setRule(ruleA); |
| jobB.setRule(ruleB); |
| jobC.setRule(ruleC); |
| |
| //B should be running, A blocked by B and C blocked by A |
| jobB.schedule(); |
| sleep(100); |
| jobA.schedule(); |
| sleep(100); |
| jobC.schedule(); |
| |
| //cancel and restart A |
| jobA.cancel(); |
| jobA.schedule(); |
| |
| //cancel all jobs |
| jobA.cancel(); |
| jobC.cancel(); |
| jobB.cancel(); |
| } |
| |
| /** |
| * Regression test for bug 57656 |
| */ |
| public void testBug57656() { |
| TestJob jobA = new TestJob("Job1"); |
| TestJob jobB = new TestJob("Job2"); |
| //schedule jobA |
| jobA.schedule(5000); |
| //schedule jobB so it gets behind jobA in the queue |
| jobB.schedule(10000); |
| //now put jobA to sleep indefinitely |
| jobA.sleep(); |
| //jobB should still run within ten seconds |
| waitForCompletion(jobB, 30000); |
| } |
| |
| /** |
| * This is a regression test for bug 71448. IJobManager.currentJob was not |
| * returning the correct value when executed in a thread that is performing |
| * asynchronous completion of a job (i.e., a UI Job) |
| */ |
| public void testCurrentJob() { |
| final Thread[] thread = new Thread[1]; |
| final boolean[] done = new boolean[] {false}; |
| final boolean[] success = new boolean[] {false}; |
| //create a job that will complete asynchronously |
| final Job job = new Job("Test Job") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| setThread(thread[0]); |
| done[0] = true; |
| return ASYNC_FINISH; |
| } |
| }; |
| //create and run a thread that will run and finish the asynchronous job |
| Runnable r = () -> { |
| job.schedule(); |
| // wait for job to start running |
| while (!done[0]) { |
| try { |
| Thread.sleep(100); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| } |
| // job should now be finishing asynchronously in this thread |
| success[0] = job == Job.getJobManager().currentJob(); |
| job.done(Status.OK_STATUS); |
| }; |
| thread[0] = new Thread(r); |
| thread[0].start(); |
| try { |
| thread[0].join(); |
| } catch (InterruptedException e) { |
| //ignore |
| } |
| //assert that currentJob returned the correct value |
| assertTrue("1.0", success[0]); |
| } |
| |
| /** |
| * Tests for {@link IJobManager#currentRule()}. |
| */ |
| public void testCurrentRule() { |
| //first test when not running in a job |
| runRuleSequence(); |
| |
| //next test in a job with no rule of its own |
| final List<AssertionFailedError> errors = new ArrayList<>(); |
| Job sequenceJob = new Job("testCurrentRule") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| runRuleSequence(); |
| } catch (AssertionFailedError e) { |
| errors.add(e); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| sequenceJob.schedule(); |
| waitForCompletion(sequenceJob); |
| if (!errors.isEmpty()) { |
| throw errors.iterator().next(); |
| } |
| |
| //now test in a job that has a scheduling rule |
| ISchedulingRule jobRule = new PathRule("/testCurrentRule"); |
| sequenceJob.setRule(jobRule); |
| sequenceJob.schedule(); |
| waitForCompletion(sequenceJob); |
| if (!errors.isEmpty()) { |
| throw errors.iterator().next(); |
| } |
| |
| } |
| |
| /** |
| * Helper method for testing {@link IJobManager#currentRule()}. |
| */ |
| protected void runRuleSequence() { |
| if (runRuleSequenceInJobWithRule()) { |
| return; |
| } |
| ISchedulingRule parent = new PathRule("/testCurrentRule/parent"); |
| ISchedulingRule child = new PathRule("/testCurrentRule/parent/child"); |
| assertNull(manager.currentRule()); |
| manager.beginRule(null, null); |
| assertNull(manager.currentRule()); |
| manager.endRule(null); |
| assertNull(manager.currentRule()); |
| manager.beginRule(parent, null); |
| assertEquals(parent, manager.currentRule()); |
| //nested null rule |
| manager.beginRule(null, null); |
| assertEquals(parent, manager.currentRule()); |
| //nested non-null rule |
| manager.beginRule(child, null); |
| assertEquals(parent, manager.currentRule()); |
| manager.endRule(child); |
| assertEquals(parent, manager.currentRule()); |
| manager.endRule(null); |
| assertEquals(parent, manager.currentRule()); |
| manager.endRule(parent); |
| assertNull(manager.currentRule()); |
| } |
| |
| /** |
| * Runs a sequence of begin/end rules and asserts that the |
| * job rule is always returned by {@link IJobManager#currentRule()}. |
| * Returns <code>false</code> if not invoked from within a job with |
| * a scheduling rule. |
| */ |
| private boolean runRuleSequenceInJobWithRule() { |
| Job currentJob = manager.currentJob(); |
| if (currentJob == null) { |
| return false; |
| } |
| ISchedulingRule jobRule = currentJob.getRule(); |
| if (jobRule == null) { |
| return false; |
| } |
| //we are in a job with a rule, so now run our rule sequence |
| ISchedulingRule parent = new PathRule("/testCurrentRule/parent"); |
| ISchedulingRule child = new PathRule("/testCurrentRule/parent/child"); |
| assertEquals(jobRule, manager.currentRule()); |
| manager.beginRule(null, null); |
| assertEquals(jobRule, manager.currentRule()); |
| manager.endRule(null); |
| assertEquals(jobRule, manager.currentRule()); |
| manager.beginRule(parent, null); |
| assertEquals(jobRule, manager.currentRule()); |
| //nested null rule |
| manager.beginRule(null, null); |
| assertEquals(jobRule, manager.currentRule()); |
| //nested non-null rule |
| manager.beginRule(child, null); |
| assertEquals(jobRule, manager.currentRule()); |
| manager.endRule(child); |
| assertEquals(jobRule, manager.currentRule()); |
| manager.endRule(null); |
| assertEquals(jobRule, manager.currentRule()); |
| manager.endRule(parent); |
| assertEquals(jobRule, manager.currentRule()); |
| return true; |
| } |
| |
| public void testDelayedJob() { |
| //schedule a delayed job and ensure it doesn't start until instructed |
| int[] sleepTimes = new int[] {0, 10, 50, 100, 500, 1000, 2000, 2500}; |
| for (int i = 0; i < sleepTimes.length; i++) { |
| long start = now(); |
| TestJob job = new TestJob("Noop", 0, 0); |
| assertEquals("1.0", 0, job.getRunCount()); |
| job.schedule(sleepTimes[i]); |
| waitForCompletion(); |
| assertEquals("1.1." + i, 1, job.getRunCount()); |
| long duration = now() - start; |
| assertTrue("1.2: duration: " + duration + " sleep: " + sleepTimes[i], duration >= sleepTimes[i]); |
| //a no-op job shouldn't take any real time |
| if (PEDANTIC) { |
| assertTrue("1.3: duration: " + duration + " sleep: " + sleepTimes[i], duration < sleepTimes[i] + 1000); |
| } |
| } |
| } |
| |
| public void testJobFamilyCancel() { |
| //test the cancellation of a family of jobs |
| final int NUM_JOBS = 20; |
| TestJob[] jobs = new TestJob[NUM_JOBS]; |
| //create two different families of jobs |
| TestJobFamily first = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| TestJobFamily second = new TestJobFamily(TestJobFamily.TYPE_TWO); |
| //need a scheduling rule so that the jobs would be executed one by one |
| ISchedulingRule rule = new IdentityRule(); |
| |
| for (int i = 0; i < NUM_JOBS; i++) { |
| //assign half the jobs to the first family, the other half to the second family |
| if (i % 2 == 0) { |
| jobs[i] = new FamilyTestJob("TestFirstFamily", 1000000, 10, TestJobFamily.TYPE_ONE); |
| } else { |
| /*if(i%2 == 1)*/ |
| jobs[i] = new FamilyTestJob("TestSecondFamily", 1000000, 10, TestJobFamily.TYPE_TWO); |
| } |
| jobs[i].setRule(rule); |
| jobs[i].schedule(); |
| } |
| |
| waitForStart(jobs[0]); |
| |
| assertState("1.0", jobs[0], Job.RUNNING); |
| |
| //first job is running, the rest are waiting |
| for (int i = 1; i < NUM_JOBS; i++) { |
| assertState("1." + i, jobs[i], Job.WAITING); |
| } |
| |
| //cancel the first family of jobs |
| manager.cancel(first); |
| waitForFamilyCancel(jobs, first); |
| |
| //the previously running job should have no state |
| assertState("2.0", jobs[0], Job.NONE); |
| //the first job from the second family should now be running |
| waitForStart(jobs[1]); |
| |
| for (int i = 2; i < NUM_JOBS; i++) { |
| //all other jobs in the first family should be removed from the waiting queue |
| //no operations can be performed on these jobs until they are scheduled with the manager again |
| if (jobs[i].belongsTo(first)) { |
| assertState("2." + i, jobs[i], Job.NONE); |
| jobs[i].wakeUp(); |
| assertState("2." + i, jobs[i], Job.NONE); |
| jobs[i].sleep(); |
| assertState("2." + i, jobs[i], Job.NONE); |
| } |
| //all other jobs in the second family should still be in the waiting queue |
| else { |
| assertState("3." + i, jobs[i], Job.WAITING); |
| } |
| } |
| |
| for (int i = 2; i < NUM_JOBS; i++) { |
| //all the jobs in the second family that are waiting to start can now be set to sleep |
| if (jobs[i].belongsTo(second)) { |
| assertState("4." + i, jobs[i], Job.WAITING); |
| assertTrue("5." + i, jobs[i].sleep()); |
| assertState("6." + i, jobs[i], Job.SLEEPING); |
| } |
| } |
| //cancel the second family of jobs |
| manager.cancel(second); |
| waitForFamilyCancel(jobs, second); |
| |
| //the second job should now have no state |
| assertState("7.0", jobs[1], Job.NONE); |
| |
| for (int i = 0; i < NUM_JOBS; i++) { |
| //all jobs should now be in the NONE state |
| assertState("8." + i, jobs[i], Job.NONE); |
| } |
| } |
| |
| public void testJobFamilyFind() { |
| //test of finding jobs based on the job family they belong to |
| final int NUM_JOBS = 20; |
| TestJob[] jobs = new TestJob[NUM_JOBS]; |
| //create five different families of jobs |
| TestJobFamily first = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| TestJobFamily second = new TestJobFamily(TestJobFamily.TYPE_TWO); |
| TestJobFamily third = new TestJobFamily(TestJobFamily.TYPE_THREE); |
| TestJobFamily fourth = new TestJobFamily(TestJobFamily.TYPE_FOUR); |
| TestJobFamily fifth = new TestJobFamily(TestJobFamily.TYPE_FIVE); |
| |
| //need a scheduling rule so that the jobs would be executed one by one |
| ISchedulingRule rule = new IdentityRule(); |
| |
| for (int i = 0; i < NUM_JOBS; i++) { |
| //assign four jobs to each family |
| switch (i % 5) { |
| case 0: |
| jobs[i] = new FamilyTestJob("TestFirstFamily", 1000000, 10, TestJobFamily.TYPE_ONE); |
| break; |
| case 1: |
| jobs[i] = new FamilyTestJob("TestSecondFamily", 1000000, 10, TestJobFamily.TYPE_TWO); |
| break; |
| case 2: |
| jobs[i] = new FamilyTestJob("TestThirdFamily", 1000000, 10, TestJobFamily.TYPE_THREE); |
| break; |
| case 3: |
| jobs[i] = new FamilyTestJob("TestFourthFamily", 1000000, 10, TestJobFamily.TYPE_FOUR); |
| break; |
| default: |
| /*if(i%5 == 4)*/ |
| jobs[i] = new FamilyTestJob("TestFifthFamily", 1000000, 10, TestJobFamily.TYPE_FIVE); |
| break; |
| } |
| |
| jobs[i].setRule(rule); |
| jobs[i].schedule(); |
| } |
| |
| waitForStart(jobs[0]); |
| |
| //try finding all jobs by supplying the NULL parameter |
| //note that this might find other jobs that are running as a side-effect of the test |
| //suites running, such as snapshot |
| HashSet<Job> allJobs = new HashSet<>(); |
| allJobs.addAll(Arrays.asList(jobs)); |
| Job[] result = manager.find(null); |
| assertTrue("1.0", result.length >= NUM_JOBS); |
| for (int i = 0; i < result.length; i++) { |
| //only test jobs that we know about |
| if (allJobs.remove(result[i])) { |
| assertTrue("1." + i, (result[i].belongsTo(first) || result[i].belongsTo(second) || result[i].belongsTo(third) || result[i].belongsTo(fourth) || result[i].belongsTo(fifth))); |
| } |
| } |
| assertEquals("1.2", 0, allJobs.size()); |
| |
| //try finding all jobs from the first family |
| result = manager.find(first); |
| assertTrue("2.0", result.length == 4); |
| for (int i = 0; i < result.length; i++) { |
| assertTrue("2." + (i + 1), result[i].belongsTo(first)); |
| } |
| |
| //try finding all jobs from the second family |
| result = manager.find(second); |
| assertTrue("3.0", result.length == 4); |
| for (int i = 0; i < result.length; i++) { |
| assertTrue("3." + (i + 1), result[i].belongsTo(second)); |
| } |
| |
| //try finding all jobs from the third family |
| result = manager.find(third); |
| assertTrue("4.0", result.length == 4); |
| for (int i = 0; i < result.length; i++) { |
| assertTrue("4." + (i + 1), result[i].belongsTo(third)); |
| } |
| |
| //try finding all jobs from the fourth family |
| result = manager.find(fourth); |
| assertTrue("5.0", result.length == 4); |
| for (int i = 0; i < result.length; i++) { |
| assertTrue("5." + (i + 1), result[i].belongsTo(fourth)); |
| } |
| |
| //try finding all jobs from the fifth family |
| result = manager.find(fifth); |
| assertTrue("6.0", result.length == 4); |
| for (int i = 0; i < result.length; i++) { |
| assertTrue("6." + (i + 1), result[i].belongsTo(fifth)); |
| } |
| |
| //the first job should still be running |
| assertState("7.0", jobs[0], Job.RUNNING); |
| |
| //put the second family of jobs to sleep |
| manager.sleep(second); |
| |
| //cancel the first family of jobs |
| manager.cancel(first); |
| |
| //the third job should start running |
| waitForStart(jobs[2]); |
| assertState("7.1", jobs[2], Job.RUNNING); |
| |
| //finding all jobs from the first family should return an empty array |
| result = manager.find(first); |
| assertEquals("7.2", 0, result.length); |
| |
| //finding all jobs from the second family should return all the jobs (they are just sleeping) |
| result = manager.find(second); |
| assertTrue("8.0", result.length == 4); |
| for (int i = 0; i < result.length; i++) { |
| assertTrue("8." + (i + 1), result[i].belongsTo(second)); |
| } |
| |
| //cancel the second family of jobs |
| manager.cancel(second); |
| //finding all jobs from the second family should now return an empty array |
| result = manager.find(second); |
| assertEquals("9.0", 0, result.length); |
| |
| //cancel the fourth family of jobs |
| manager.cancel(fourth); |
| //finding all jobs from the fourth family should now return an empty array |
| result = manager.find(fourth); |
| assertEquals("9.1", 0, result.length); |
| |
| //put the third family of jobs to sleep |
| manager.sleep(third); |
| //the first job from the third family should still be running |
| assertState("9.2", jobs[2], Job.RUNNING); |
| //wake up the last job from the third family |
| jobs[NUM_JOBS - 3].wakeUp(); |
| //it should now be in the WAITING state |
| assertState("9.3", jobs[NUM_JOBS - 3], Job.WAITING); |
| |
| //finding all jobs from the third family should return all 4 jobs (1 is running, 1 is waiting, 2 are sleeping) |
| result = manager.find(third); |
| assertTrue("10.0", result.length == 4); |
| for (int i = 0; i < result.length; i++) { |
| assertTrue("10." + (i + 1), result[i].belongsTo(third)); |
| } |
| |
| //finding all jobs by supplying the NULL parameter should return 8 jobs (4 from the 3rd family, and 4 from the 5th family) |
| //note that this might find other jobs that are running as a side-effect of the test |
| //suites running, such as snapshot |
| allJobs.addAll(Arrays.asList(jobs)); |
| result = manager.find(null); |
| assertTrue("11.0", result.length >= 8); |
| for (int i = 0; i < result.length; i++) { |
| //only test jobs that we know about |
| if (allJobs.remove(result[i])) { |
| assertTrue("11." + (i + 1), (result[i].belongsTo(third) || result[i].belongsTo(fifth))); |
| } |
| } |
| |
| assertEquals("11.2", 12, allJobs.size()); |
| allJobs.clear(); |
| |
| //cancel the fifth family of jobs |
| manager.cancel(fifth); |
| //cancel the third family of jobs |
| manager.cancel(third); |
| waitForFamilyCancel(jobs, third); |
| |
| //all jobs should now be in the NONE state |
| for (int i = 0; i < NUM_JOBS; i++) { |
| assertState("12." + i, jobs[i], Job.NONE); |
| } |
| |
| //finding all jobs should return an empty array |
| //note that this might find other jobs that are running as a side-effect of the test |
| //suites running, such as snapshot |
| allJobs.addAll(Arrays.asList(jobs)); |
| result = manager.find(null); |
| assertTrue("13.0", result.length >= 0); |
| |
| for (int i = 0; i < result.length; i++) { |
| //test jobs that we know about should not be found (they should have all been removed) |
| if (allJobs.remove(result[i])) { |
| assertTrue("14." + i, false); |
| } |
| } |
| assertEquals("15.0", NUM_JOBS, allJobs.size()); |
| allJobs.clear(); |
| } |
| |
| public void testJobFamilyJoin() { |
| //test the join method on a family of jobs |
| final int[] status = new int[1]; |
| status[0] = TestBarrier.STATUS_WAIT_FOR_START; |
| final int NUM_JOBS = 20; |
| Job[] jobs = new Job[NUM_JOBS]; |
| //create two different families of jobs |
| final TestJobFamily first = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| final TestJobFamily second = new TestJobFamily(TestJobFamily.TYPE_TWO); |
| //need two scheduling rule so that jobs in each family would be executing one by one |
| ISchedulingRule rule1 = new IdentityRule(); |
| ISchedulingRule rule2 = new IdentityRule(); |
| for (int i = 0; i < NUM_JOBS; i++) { |
| //assign half the jobs to the first family, the other half to the second family |
| if (i % 2 == 0) { |
| jobs[i] = new FamilyTestJob("TestFirstFamily", 10, 10, TestJobFamily.TYPE_ONE); |
| jobs[i].setRule(rule1); |
| jobs[i].schedule(1000000); |
| } else /*if(i%2 == 1)*/{ |
| jobs[i] = new FamilyTestJob("TestSecondFamily", 1000000, 10, TestJobFamily.TYPE_TWO); |
| jobs[i].setRule(rule2); |
| jobs[i].schedule(); |
| } |
| |
| } |
| |
| Thread t = new Thread(() -> { |
| status[0] = TestBarrier.STATUS_START; |
| try { |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_WAIT_FOR_RUN); |
| status[0] = TestBarrier.STATUS_RUNNING; |
| manager.join(first, null); |
| } catch (OperationCanceledException | InterruptedException e) { |
| // ignore |
| } |
| status[0] = TestBarrier.STATUS_DONE; |
| }); |
| |
| //start the thread that will join the first family of jobs and be blocked until they finish execution |
| t.start(); |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_START); |
| status[0] = TestBarrier.STATUS_WAIT_FOR_RUN; |
| //wake up the first family of jobs |
| manager.wakeUp(first); |
| |
| int i = 0; |
| for (; i < 100; i++) { |
| int currentStatus = status[0]; |
| Job[] result = manager.find(first); |
| |
| //if the thread is complete then all jobs must be done |
| if (currentStatus == TestBarrier.STATUS_DONE) { |
| assertTrue("2." + i, result.length == 0); |
| break; |
| } |
| sleep(100); |
| } |
| assertTrue("2.0", i < 100); |
| |
| //cancel the second family of jobs |
| manager.cancel(second); |
| waitForFamilyCancel(jobs, second); |
| |
| //all the jobs should now be in the NONE state |
| for (int j = 0; j < NUM_JOBS; j++) { |
| assertState("3." + j, jobs[j], Job.NONE); |
| } |
| } |
| |
| public void testJobFamilyJoinCancelJobs() { |
| //test the join method on a family of jobs, then cancel the jobs that are blocking the join call |
| final int[] status = new int[1]; |
| status[0] = TestBarrier.STATUS_WAIT_FOR_START; |
| final int NUM_JOBS = 20; |
| TestJob[] jobs = new TestJob[NUM_JOBS]; |
| //create two different families of jobs |
| final TestJobFamily first = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| final TestJobFamily second = new TestJobFamily(TestJobFamily.TYPE_TWO); |
| //need two scheduling rule so that jobs in each family would be executing one by one |
| ISchedulingRule rule1 = new IdentityRule(); |
| ISchedulingRule rule2 = new IdentityRule(); |
| for (int i = 0; i < NUM_JOBS; i++) { |
| //assign half the jobs to the first family, the other half to the second family |
| if (i % 2 == 0) { |
| jobs[i] = new FamilyTestJob("TestFirstFamily", 1000000, 10, TestJobFamily.TYPE_ONE); |
| jobs[i].setRule(rule1); |
| } else /*if(i%2 == 1)*/{ |
| jobs[i] = new FamilyTestJob("TestSecondFamily", 1000000, 10, TestJobFamily.TYPE_TWO); |
| jobs[i].setRule(rule2); |
| } |
| jobs[i].schedule(); |
| |
| } |
| |
| Thread t = new Thread(() -> { |
| status[0] = TestBarrier.STATUS_START; |
| try { |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_WAIT_FOR_RUN); |
| status[0] = TestBarrier.STATUS_RUNNING; |
| manager.join(first, null); |
| } catch (OperationCanceledException | InterruptedException e) { |
| // ignore |
| } |
| status[0] = TestBarrier.STATUS_DONE; |
| }); |
| |
| //start the thread that will join the first family of jobs |
| //it will be blocked until the all jobs in the first family finish execution or are canceled |
| t.start(); |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_START); |
| status[0] = TestBarrier.STATUS_WAIT_FOR_RUN; |
| waitForStart(jobs[0]); |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_RUNNING); |
| |
| assertState("2.0", jobs[0], Job.RUNNING); |
| assertTrue("2.1", status[0] == TestBarrier.STATUS_RUNNING); |
| |
| //cancel the first family of jobs |
| //the join call should be unblocked when all the jobs are canceled |
| manager.cancel(first); |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_DONE); |
| |
| //all jobs in the first family should be removed from the manager |
| assertTrue("2.2", manager.find(first).length == 0); |
| |
| //cancel the second family of jobs |
| manager.cancel(second); |
| waitForFamilyCancel(jobs, second); |
| |
| //all the jobs should now be in the NONE state |
| for (int j = 0; j < NUM_JOBS; j++) { |
| assertState("3." + j, jobs[j], Job.NONE); |
| } |
| } |
| |
| public void testJobFamilyJoinCancelManager() { |
| //test the join method on a family of jobs, then cancel the call |
| final int[] status = new int[1]; |
| status[0] = TestBarrier.STATUS_WAIT_FOR_START; |
| final int NUM_JOBS = 20; |
| TestJob[] jobs = new TestJob[NUM_JOBS]; |
| //create a progress monitor to cancel the join call |
| final IProgressMonitor canceller = new FussyProgressMonitor(); |
| //create two different families of jobs |
| final TestJobFamily first = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| final TestJobFamily second = new TestJobFamily(TestJobFamily.TYPE_TWO); |
| //need two scheduling rule so that jobs in each family would be executing one by one |
| ISchedulingRule rule1 = new IdentityRule(); |
| ISchedulingRule rule2 = new IdentityRule(); |
| for (int i = 0; i < NUM_JOBS; i++) { |
| //assign half the jobs to the first family, the other half to the second family |
| if (i % 2 == 0) { |
| jobs[i] = new FamilyTestJob("TestFirstFamily", 1000000, 10, TestJobFamily.TYPE_ONE); |
| jobs[i].setRule(rule1); |
| } else /*if(i%2 == 1)*/{ |
| jobs[i] = new FamilyTestJob("TestSecondFamily", 1000000, 10, TestJobFamily.TYPE_TWO); |
| jobs[i].setRule(rule2); |
| } |
| jobs[i].schedule(); |
| |
| } |
| |
| Thread t = new Thread(() -> { |
| status[0] = TestBarrier.STATUS_START; |
| try { |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_WAIT_FOR_RUN); |
| status[0] = TestBarrier.STATUS_RUNNING; |
| manager.join(first, canceller); |
| } catch (OperationCanceledException | InterruptedException e) { |
| // ignore |
| } |
| status[0] = TestBarrier.STATUS_DONE; |
| }); |
| |
| //start the thread that will join the first family of jobs |
| //it will be blocked until the cancel call is made to the thread |
| t.start(); |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_START); |
| status[0] = TestBarrier.STATUS_WAIT_FOR_RUN; |
| waitForStart(jobs[0]); |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_RUNNING); |
| |
| assertState("2.0", jobs[0], Job.RUNNING); |
| assertTrue("2.1", status[0] == TestBarrier.STATUS_RUNNING); |
| |
| //cancel the monitor that is attached to the join call |
| canceller.setCanceled(true); |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_DONE); |
| |
| //the first job in the first family should still be running |
| assertState("2.2", jobs[0], Job.RUNNING); |
| assertTrue("2.3", status[0] == TestBarrier.STATUS_DONE); |
| assertTrue("2.4", manager.find(first).length > 0); |
| |
| //cancel the second family of jobs |
| manager.cancel(second); |
| waitForFamilyCancel(jobs, second); |
| |
| //cancel the first family of jobs |
| manager.cancel(first); |
| waitForFamilyCancel(jobs, first); |
| |
| //all the jobs should now be in the NONE state |
| for (int j = 0; j < NUM_JOBS; j++) { |
| assertState("3." + j, jobs[j], Job.NONE); |
| } |
| } |
| |
| /** |
| * Asserts that the LockListener is called correctly during invocation of |
| * {@link IJobManager#join(Object, IProgressMonitor)}. |
| * See bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=195839. |
| */ |
| public void testJobFamilyJoinLockListener() { |
| final TestJobFamily family = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| int count = 5; |
| Job[] jobs = new Job[count]; |
| for (int i = 0; i < jobs.length; i++) { |
| jobs[i] = new FamilyTestJob("TestJobFamilyJoinLockListener" + i, 5, 500, family.getType()); |
| jobs[i].schedule(); |
| } |
| TestLockListener lockListener = new TestLockListener(); |
| try { |
| manager.setLockListener(lockListener); |
| manager.join(family, new FussyProgressMonitor()); |
| } catch (OperationCanceledException | InterruptedException e) { |
| fail("4.99", e); |
| } finally { |
| manager.setLockListener(null); |
| } |
| lockListener.assertNotWaiting("1.0"); |
| } |
| |
| public void testJobFamilyJoinNothing() { |
| //test joining a bogus family, and the monitor should be used up |
| try { |
| final FussyProgressMonitor monitor = new FussyProgressMonitor(); |
| monitor.prepare(); |
| manager.join(new Object(), monitor); |
| monitor.sanityCheck(); |
| monitor.assertUsedUp(); |
| } catch (OperationCanceledException | InterruptedException e) { |
| fail("4.99", e); |
| } |
| } |
| |
| /** |
| * Tests joining a job that repeats in a loop |
| */ |
| public void testJobFamilyJoinRepeating() { |
| Object family = new Object(); |
| int count = 25; |
| RepeatingJob job = new RepeatingJob("testJobFamilyJoinRepeating", count); |
| job.setFamily(family); |
| job.schedule(); |
| try { |
| Job.getJobManager().join(family, null); |
| } catch (OperationCanceledException e) { |
| fail("1.0", e); |
| } catch (InterruptedException e) { |
| fail("1.1", e); |
| } |
| //ensure the job has run the expected number of times |
| assertEquals("1.2", count, job.getRunCount()); |
| } |
| |
| /** |
| * Tests joining a job family that repeats but returns false to shouldSchedule |
| */ |
| public void testJobFamilyJoinShouldSchedule() { |
| Object family = new Object(); |
| final int count = 1; |
| RepeatingJob job = new RepeatingJob("testJobFamilyJoinShouldSchedule", count) { |
| @Override |
| public boolean shouldSchedule() { |
| return shouldRun(); |
| } |
| }; |
| job.setFamily(family); |
| job.schedule(); |
| try { |
| Job.getJobManager().join(family, null); |
| } catch (OperationCanceledException e) { |
| fail("1.0", e); |
| } catch (InterruptedException e) { |
| fail("1.1", e); |
| } |
| //ensure the job has run the expected number of times |
| assertEquals("1.2", count, job.getRunCount()); |
| } |
| |
| /** |
| * Tests simple usage of the IJobManager.join() method. |
| */ |
| public void testJobFamilyJoinSimple() { |
| //test the join method on a family of jobs that is empty |
| final int[] status = new int[1]; |
| status[0] = TestBarrier.STATUS_WAIT_FOR_START; |
| final int NUM_JOBS = 20; |
| TestJob[] jobs = new TestJob[NUM_JOBS]; |
| //create three different families of jobs |
| final TestJobFamily first = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| final TestJobFamily second = new TestJobFamily(TestJobFamily.TYPE_TWO); |
| final TestJobFamily third = new TestJobFamily(TestJobFamily.TYPE_THREE); |
| //need two scheduling rule so that jobs in each family would be executing one by one |
| ISchedulingRule rule1 = new IdentityRule(); |
| ISchedulingRule rule2 = new IdentityRule(); |
| for (int i = 0; i < NUM_JOBS; i++) { |
| //assign half the jobs to the first family, the other half to the second family |
| if (i % 2 == 0) { |
| jobs[i] = new FamilyTestJob("TestFirstFamily", 1000000, 10, TestJobFamily.TYPE_ONE); |
| jobs[i].setRule(rule1); |
| } else /*if(i%2 == 1)*/{ |
| jobs[i] = new FamilyTestJob("TestSecondFamily", 1000000, 10, TestJobFamily.TYPE_TWO); |
| jobs[i].setRule(rule2); |
| } |
| |
| jobs[i].schedule(); |
| } |
| |
| Thread t = new Thread(() -> { |
| status[0] = TestBarrier.STATUS_START; |
| try { |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_WAIT_FOR_RUN); |
| status[0] = TestBarrier.STATUS_RUNNING; |
| manager.join(third, null); |
| } catch (OperationCanceledException | InterruptedException e) { |
| // ignore |
| } |
| status[0] = TestBarrier.STATUS_DONE; |
| }); |
| |
| //try joining the third family of jobs, which is empty |
| //join method should return without blocking |
| waitForStart(jobs[0]); |
| t.start(); |
| |
| //let the thread execute the join call |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_START); |
| assertTrue("1.0", status[0] == TestBarrier.STATUS_START); |
| long startTime = now(); |
| status[0] = TestBarrier.STATUS_WAIT_FOR_RUN; |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_DONE); |
| long endTime = now(); |
| |
| assertTrue("2.0", status[0] == TestBarrier.STATUS_DONE); |
| assertTrue("2.1", endTime > startTime); |
| |
| //the join call should take no actual time (join call should not block thread at all) |
| if (PEDANTIC) { |
| assertTrue("2.2 start time: " + startTime + " end time: " + endTime, (endTime - startTime) < 300); |
| } |
| |
| //cancel all jobs |
| manager.cancel(first); |
| manager.cancel(second); |
| waitForFamilyCancel(jobs, first); |
| waitForFamilyCancel(jobs, second); |
| |
| //all the jobs should now be in the NONE state |
| for (int j = 0; j < NUM_JOBS; j++) { |
| assertState("3." + j, jobs[j], Job.NONE); |
| } |
| } |
| |
| /** |
| * Tests scenario 1 described in https://bugs.eclipse.org/bugs/show_bug.cgi?id=403271#c0: |
| * - join is called when job manager is suspended |
| * - waiting job is scheduled when job manager is suspended |
| * In this scenario main job should not wait for the waiting job. |
| */ |
| public void testJobFamilyJoinWhenSuspended_1() throws InterruptedException { |
| final Object family = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| final int[] familyJobsCount = new int[] {-1}; |
| final TestBarrier barrier = new TestBarrier(); |
| final Job waiting = new FamilyTestJob("waiting job", 1000000, 10, TestJobFamily.TYPE_ONE); |
| final Job running = new FamilyTestJob("running job", 200, 10, TestJobFamily.TYPE_ONE); |
| final IJobChangeListener listener = new JobChangeAdapter() { |
| @Override |
| public void done(IJobChangeEvent event) { |
| if (event.getJob() == running) { |
| barrier.waitForStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| } |
| } |
| |
| @Override |
| public void running(IJobChangeEvent event) { |
| if (event.getJob() == running) { |
| barrier.setStatus(TestBarrier.STATUS_RUNNING); |
| } |
| } |
| }; |
| Job job = new Job("main job") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| manager.addJobChangeListener(listener); |
| running.schedule(); |
| // wait until running job is actually running |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| manager.setLockListener(new LockListener() { |
| private boolean scheduled = false; |
| |
| @Override |
| public boolean aboutToWait(Thread lockOwner) { |
| // aboutToWait will be called when main job will start joining the running job |
| if (!scheduled) { |
| waiting.schedule(); |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| } |
| return super.aboutToWait(lockOwner); |
| } |
| }); |
| // suspend before join |
| manager.suspend(); |
| manager.join(family, null); |
| familyJobsCount[0] = manager.find(family).length; |
| barrier.setStatus(TestBarrier.STATUS_DONE); |
| } catch (InterruptedException e) { |
| // ignore |
| } finally { |
| // clean up |
| manager.removeJobChangeListener(listener); |
| manager.setLockListener(null); |
| running.cancel(); |
| waiting.cancel(); |
| try { |
| running.join(); |
| waiting.join(); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| manager.resume(); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| try { |
| job.schedule(); |
| barrier.waitForStatus(TestBarrier.STATUS_DONE); |
| assertEquals(1, familyJobsCount[0]); |
| } catch (AssertionFailedError e) { |
| // interrupt to avoid deadlock and perform cleanup |
| job.getThread().interrupt(); |
| // re-throw since the test failed |
| throw e; |
| } finally { |
| // wait until cleanup is done |
| job.join(); |
| } |
| } |
| |
| /** |
| * Tests scenario 2 - verifies if the suspended flag is checked each time a job is scheduled: |
| * - join is called when job manager is NOT suspended |
| * - waiting job is scheduled when job manager is suspended |
| * In this scenario main job should not wait for the waiting job. |
| */ |
| public void testJobFamilyJoinWhenSuspended_2() throws InterruptedException { |
| final Object family = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| final int[] familyJobsCount = new int[] {-1}; |
| final TestBarrier barrier = new TestBarrier(); |
| final Job waiting = new FamilyTestJob("waiting job", 1000000, 10, TestJobFamily.TYPE_ONE); |
| final Job running = new FamilyTestJob("running job", 200, 10, TestJobFamily.TYPE_ONE); |
| final IJobChangeListener listener = new JobChangeAdapter() { |
| @Override |
| public void done(IJobChangeEvent event) { |
| if (event.getJob() == running) { |
| barrier.waitForStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| } |
| } |
| |
| @Override |
| public void running(IJobChangeEvent event) { |
| if (event.getJob() == running) { |
| barrier.setStatus(TestBarrier.STATUS_RUNNING); |
| } |
| } |
| }; |
| Job job = new Job("main job") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| manager.addJobChangeListener(listener); |
| running.schedule(); |
| // wait until running job is actually running |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| manager.setLockListener(new LockListener() { |
| private boolean scheduled = false; |
| |
| @Override |
| public boolean aboutToWait(Thread lockOwner) { |
| // aboutToWait will be called when main job will start joining the running job |
| if (!scheduled) { |
| // suspend before scheduling new job |
| getJobManager().suspend(); |
| waiting.schedule(); |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| } |
| return super.aboutToWait(lockOwner); |
| } |
| }); |
| manager.join(family, null); |
| familyJobsCount[0] = manager.find(family).length; |
| barrier.setStatus(TestBarrier.STATUS_DONE); |
| } catch (InterruptedException e) { |
| // ignore |
| } finally { |
| // clean up |
| manager.removeJobChangeListener(listener); |
| manager.setLockListener(null); |
| running.cancel(); |
| waiting.cancel(); |
| try { |
| running.join(); |
| waiting.join(); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| manager.resume(); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| try { |
| job.schedule(); |
| barrier.waitForStatus(TestBarrier.STATUS_DONE); |
| assertEquals(1, familyJobsCount[0]); |
| } catch (AssertionFailedError e) { |
| // interrupt to avoid deadlock and perform cleanup |
| job.getThread().interrupt(); |
| // re-throw since the test failed |
| throw e; |
| } finally { |
| // wait until cleanup is done |
| job.join(); |
| } |
| } |
| |
| /** |
| * Tests scenario 3: |
| * - join is called when job manager is NOT suspended |
| * - waiting job is scheduled when job manager is suspended |
| * - job manager is resumed causing waiting job to start |
| * In this scenario main thread should wait for the waiting job since the job was started before the join ended. |
| */ |
| public void testJobFamilyJoinWhenSuspended_3() throws InterruptedException { |
| final Object family = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| final TestBarrier barrier = new TestBarrier(); |
| final Job waiting = new FamilyTestJob("waiting job", 400, 10, TestJobFamily.TYPE_ONE); |
| final Job running = new FamilyTestJob("running job", 200, 10, TestJobFamily.TYPE_ONE); |
| final IJobChangeListener listener = new JobChangeAdapter() { |
| @Override |
| public void done(IJobChangeEvent event) { |
| if (event.getJob() == running) { |
| barrier.waitForStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| } |
| } |
| |
| @Override |
| public void running(IJobChangeEvent event) { |
| if (event.getJob() == running) { |
| barrier.setStatus(TestBarrier.STATUS_RUNNING); |
| } else if (event.getJob() == waiting) { |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| } |
| } |
| }; |
| try { |
| manager.addJobChangeListener(listener); |
| running.schedule(); |
| // wait until the running job is actually running |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| manager.setLockListener(new LockListener() { |
| private boolean scheduled = false; |
| |
| @Override |
| public boolean aboutToWait(Thread lockOwner) { |
| // aboutToWait will be called when main thread will start joining the running job |
| if (!scheduled) { |
| // suspend before scheduling the waiting job |
| manager.suspend(); |
| waiting.schedule(); |
| // resume to start the waiting job |
| manager.resume(); |
| scheduled = true; |
| } |
| return super.aboutToWait(lockOwner); |
| } |
| }); |
| manager.join(family, null); |
| assertEquals(0, manager.find(family).length); |
| } finally { |
| // clean up |
| manager.removeJobChangeListener(listener); |
| manager.setLockListener(null); |
| running.cancel(); |
| waiting.cancel(); |
| running.join(); |
| waiting.join(); |
| manager.resume(); |
| } |
| } |
| |
| public void testJobFamilyNULL() { |
| //test methods that accept the null job family (i.e. all jobs) |
| final int NUM_JOBS = 20; |
| TestJob[] jobs = new TestJob[NUM_JOBS]; |
| //create two different families of jobs |
| TestJobFamily first = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| TestJobFamily second = new TestJobFamily(TestJobFamily.TYPE_TWO); |
| //need one common scheduling rule so that the jobs would be executed one by one |
| ISchedulingRule rule = new IdentityRule(); |
| for (int i = 0; i < NUM_JOBS; i++) { |
| //assign half the jobs to the first family, the other half to the second family |
| if (i % 2 == 0) { |
| jobs[i] = new FamilyTestJob("TestFirstFamily", 1000000, 10, TestJobFamily.TYPE_ONE); |
| } else { |
| /*if(i%2 == 1)*/ |
| jobs[i] = new FamilyTestJob("TestSecondFamily", 1000000, 10, TestJobFamily.TYPE_TWO); |
| } |
| |
| jobs[i].setRule(rule); |
| jobs[i].schedule(); |
| } |
| |
| waitForStart(jobs[0]); |
| assertState("1.0", jobs[0], Job.RUNNING); |
| |
| //put all jobs to sleep |
| manager.sleep(null); |
| //the first job should still be running |
| assertState("2.0", jobs[0], Job.RUNNING); |
| |
| //all the other jobs should be sleeping |
| for (int i = 1; i < NUM_JOBS; i++) { |
| assertState("2." + i, jobs[i], Job.SLEEPING); |
| } |
| |
| //wake up all the jobs |
| manager.wakeUp(null); |
| //the first job should still be running |
| assertState("3.0", jobs[0], Job.RUNNING); |
| |
| //all the other jobs should be waiting |
| for (int i = 1; i < NUM_JOBS; i++) { |
| assertState("3." + i, jobs[i], Job.WAITING); |
| } |
| |
| //cancel all the jobs |
| manager.cancel(first); |
| manager.cancel(second); |
| waitForFamilyCancel(jobs, first); |
| waitForFamilyCancel(jobs, second); |
| |
| //all the jobs should now be in the NONE state |
| for (int i = 0; i < NUM_JOBS; i++) { |
| assertState("4." + i, jobs[i], Job.NONE); |
| } |
| |
| } |
| |
| public void testJobFamilySleep() { |
| //test the sleep method on a family of jobs |
| final int NUM_JOBS = 20; |
| TestJob[] jobs = new TestJob[NUM_JOBS]; |
| //create two different families of jobs |
| TestJobFamily first = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| TestJobFamily second = new TestJobFamily(TestJobFamily.TYPE_TWO); |
| //need a common scheduling rule so that the jobs would be executed one by one |
| ISchedulingRule rule = new IdentityRule(); |
| for (int i = 0; i < NUM_JOBS; i++) { |
| //assign half the jobs to the first family, the other half to the second family |
| if (i % 2 == 0) { |
| jobs[i] = new FamilyTestJob("TestFirstFamily", 1000000, 10, TestJobFamily.TYPE_ONE); |
| } else { |
| /*if(i%2 == 1)*/ |
| jobs[i] = new FamilyTestJob("TestSecondFamily", 1000000, 10, TestJobFamily.TYPE_TWO); |
| } |
| |
| jobs[i].setRule(rule); |
| jobs[i].schedule(); |
| } |
| |
| waitForStart(jobs[0]); |
| |
| assertState("1.0", jobs[0], Job.RUNNING); |
| |
| //first job is running, the rest are waiting |
| for (int i = 1; i < NUM_JOBS; i++) { |
| assertState("1." + i, jobs[i], Job.WAITING); |
| } |
| |
| //set the first family of jobs to sleep |
| manager.sleep(first); |
| |
| //the running job should still be running |
| assertState("2.0", jobs[0], Job.RUNNING); |
| |
| for (int i = 1; i < NUM_JOBS; i++) { |
| //all other jobs in the first family should be sleeping |
| //they can now be canceled |
| if (jobs[i].belongsTo(first)) { |
| assertState("2." + i, jobs[i], Job.SLEEPING); |
| jobs[i].cancel(); |
| } |
| //all jobs in the second family should still be in the waiting queue |
| else { |
| assertState("3." + i, jobs[i], Job.WAITING); |
| } |
| } |
| |
| manager.sleep(second); |
| //cancel the running job |
| jobs[0].cancel(); |
| waitForCancel(jobs[0]); |
| |
| //no job should now be running |
| assertTrue("4.0", manager.currentJob() == null); |
| |
| for (int i = 1; i < NUM_JOBS; i++) { |
| //all other jobs in the second family should be sleeping |
| //they can now be canceled |
| if (jobs[i].belongsTo(second)) { |
| assertState("4." + i, jobs[i], Job.SLEEPING); |
| jobs[i].cancel(); |
| } |
| } |
| |
| //all the jobs should now be in the NONE state |
| for (int i = 0; i < NUM_JOBS; i++) { |
| assertState("5." + i, jobs[i], Job.NONE); |
| } |
| } |
| |
| /** |
| * Tests the API method IJobManager.wakeUp(family) |
| */ |
| public void testJobFamilyWakeUp() { |
| final int JOBS_PER_FAMILY = 10; |
| //create two different families of jobs |
| Job[] family1 = new Job[JOBS_PER_FAMILY]; |
| Job[] family2 = new Job[JOBS_PER_FAMILY]; |
| TestJobFamily first = new TestJobFamily(TestJobFamily.TYPE_ONE); |
| TestJobFamily second = new TestJobFamily(TestJobFamily.TYPE_TWO); |
| //need one common scheduling rule so that the jobs would be executed one by one |
| ISchedulingRule rule = new IdentityRule(); |
| //create and schedule a seed job that will cause all others to be blocked |
| TestJob seedJob = new FamilyTestJob("SeedJob", 1000000, 10, TestJobFamily.TYPE_THREE); |
| seedJob.setRule(rule); |
| seedJob.schedule(); |
| waitForStart(seedJob); |
| assertState("1.0", seedJob, Job.RUNNING); |
| |
| //create jobs in first family and put them to sleep |
| for (int i = 0; i < JOBS_PER_FAMILY; i++) { |
| family1[i] = new FamilyTestJob("TestFirstFamily", 1000000, 10, TestJobFamily.TYPE_ONE); |
| family1[i].setRule(rule); |
| family1[i].schedule(); |
| assertState("1.1." + i, family1[i], Job.WAITING); |
| assertTrue("1.2." + i, family1[i].sleep()); |
| assertState("1.3." + i, family1[i], Job.SLEEPING); |
| } |
| //create jobs in second family and put them to sleep |
| for (int i = 0; i < JOBS_PER_FAMILY; i++) { |
| family2[i] = new FamilyTestJob("TestSecondFamily", 1000000, 10, TestJobFamily.TYPE_TWO); |
| family2[i].setRule(rule); |
| family2[i].schedule(); |
| assertState("2.1." + i, family2[i], Job.WAITING); |
| assertTrue("2.2." + i, family2[i].sleep()); |
| assertState("2.3." + i, family2[i], Job.SLEEPING); |
| } |
| |
| //cancel the seed job |
| seedJob.cancel(); |
| waitForCancel(seedJob); |
| assertState("3.0", seedJob, Job.NONE); |
| |
| //all family jobs should still be sleeping |
| for (int i = 0; i < JOBS_PER_FAMILY; i++) { |
| assertState("3.1." + i, family1[i], Job.SLEEPING); |
| } |
| for (int i = 0; i < JOBS_PER_FAMILY; i++) { |
| assertState("3.2." + i, family2[i], Job.SLEEPING); |
| } |
| |
| //wake-up the second family of jobs |
| manager.wakeUp(second); |
| |
| //jobs in the first family should still be in the sleep state |
| for (int i = 0; i < JOBS_PER_FAMILY; i++) { |
| assertState("4.1." + i, family1[i], Job.SLEEPING); |
| } |
| //ensure all jobs in second family are either running or waiting |
| int runningCount = 0; |
| for (int i = 0; i < JOBS_PER_FAMILY; i++) { |
| int state = family2[i].getState(); |
| if (state == Job.RUNNING) { |
| runningCount++; |
| } else if (state != Job.WAITING) { |
| assertTrue("4.2." + i + ": expected state: " + printState(Job.WAITING) + " actual state: " + printState(state), false); |
| } |
| } |
| //ensure only one job is running (it is possible that none have started yet) |
| assertTrue("4.running", runningCount <= 1); |
| |
| //cycle through the jobs in the second family and cancel them |
| for (int i = 0; i < JOBS_PER_FAMILY; i++) { |
| //the running job may not respond immediately |
| if (!family2[i].cancel()) { |
| waitForCancel(family2[i]); |
| } |
| assertState("5." + i, family2[i], Job.NONE); |
| } |
| |
| //all jobs in the first family should still be sleeping |
| for (int i = 0; i < JOBS_PER_FAMILY; i++) { |
| assertState("6.1." + i, family1[i], Job.SLEEPING); |
| } |
| |
| //wake up the first family |
| manager.wakeUp(first); |
| |
| //ensure all jobs in first family are either running or waiting |
| runningCount = 0; |
| for (int i = 0; i < JOBS_PER_FAMILY; i++) { |
| int state = family1[i].getState(); |
| if (state == Job.RUNNING) { |
| runningCount++; |
| } else if (state != Job.WAITING) { |
| assertTrue("7.1." + i + ": expected state: " + printState(Job.WAITING) + " actual state: " + printState(state), false); |
| } |
| } |
| //ensure only one job is running (it is possible that none have started yet) |
| assertTrue("7.running", runningCount <= 1); |
| |
| //cycle through the jobs in the first family and cancel them |
| for (int i = 0; i < JOBS_PER_FAMILY; i++) { |
| //the running job may not respond immediately |
| if (!family1[i].cancel()) { |
| waitForCancel(family1[i]); |
| } |
| assertState("8." + i, family1[i], Job.NONE); |
| } |
| |
| //all jobs should now be in the NONE state |
| for (int i = 0; i < JOBS_PER_FAMILY; i++) { |
| assertState("9.1." + i, family1[i], Job.NONE); |
| } |
| for (int i = 0; i < JOBS_PER_FAMILY; i++) { |
| assertState("9.2." + i, family2[i], Job.NONE); |
| } |
| } |
| |
| public void testMutexRule() { |
| final int JOB_COUNT = 10; |
| TestJob[] jobs = new TestJob[JOB_COUNT]; |
| ISchedulingRule mutex = new IdentityRule(); |
| for (int i = 0; i < JOB_COUNT; i++) { |
| jobs[i] = new TestJob("testMutexRule", 1000000, 10); |
| jobs[i].setRule(mutex); |
| jobs[i].schedule(); |
| } |
| //first job should be running, all others should be waiting |
| waitForStart(jobs[0]); |
| assertState("1.0", jobs[0], Job.RUNNING); |
| for (int i = 1; i < JOB_COUNT; i++) { |
| assertState("1.1." + i, jobs[i], Job.WAITING); |
| } |
| //cancel job i, then i+1 should run and all others should wait |
| for (int i = 0; i < JOB_COUNT - 1; i++) { |
| jobs[i].cancel(); |
| waitForStart(jobs[i + 1]); |
| assertState("2.0." + i, jobs[i + 1], Job.RUNNING); |
| for (int j = i + 2; j < JOB_COUNT; j++) { |
| assertState("2.1" + i + "." + j, jobs[j], Job.WAITING); |
| } |
| } |
| //cancel the final job |
| jobs[JOB_COUNT - 1].cancel(); |
| } |
| |
| public void testOrder() { |
| //ensure jobs are run in order from lowest to highest sleep time. |
| final List<Job> done = Collections.synchronizedList(new ArrayList<Job>()); |
| IJobChangeListener listener = new JobChangeAdapter() { |
| @Override |
| public void done(IJobChangeEvent event) { |
| if (event.getJob() instanceof TestJob) { |
| done.add(event.getJob()); |
| } |
| } |
| }; |
| int[] sleepTimes = new int[] {50, 250, 500, 800, 1000, 1500}; |
| Job[] jobs = new Job[sleepTimes.length]; |
| manager.addJobChangeListener(listener); |
| try { |
| for (int i = 0; i < sleepTimes.length; i++) { |
| jobs[i] = new TestJob("testOrder(" + i + ")", 1, 1); |
| } |
| for (int i = 0; i < sleepTimes.length; i++) { |
| jobs[i].schedule(sleepTimes[i]); |
| } |
| waitForCompletion(); |
| //make sure listener has had a chance to process the finished job |
| while (done.size() != jobs.length) { |
| Thread.yield(); |
| sleep(100); |
| } |
| Job[] doneOrder = done.toArray(new Job[done.size()]); |
| assertEquals("1.0", jobs.length, doneOrder.length); |
| for (int i = 0; i < doneOrder.length; i++) { |
| assertEquals("1.1." + i, jobs[i], doneOrder[i]); |
| } |
| } finally { |
| manager.removeJobChangeListener(listener); |
| } |
| } |
| |
| public void testReverseOrder() { |
| //ensure jobs are run in order from lowest to highest sleep time. |
| final List<Job> done = Collections.synchronizedList(new ArrayList<Job>()); |
| IJobChangeListener listener = new JobChangeAdapter() { |
| @Override |
| public void done(IJobChangeEvent event) { |
| if (event.getJob() instanceof TestJob) { |
| //add at start of list to get reverse order |
| done.add(0, event.getJob()); |
| } |
| } |
| }; |
| int[] sleepTimes = new int[] {4000, 3000, 2000, 1000, 500}; |
| Job[] jobs = new Job[sleepTimes.length]; |
| manager.addJobChangeListener(listener); |
| try { |
| for (int i = 0; i < sleepTimes.length; i++) { |
| jobs[i] = new TestJob("testReverseOrder(" + i + ")", 0, 1); |
| } |
| for (int i = 0; i < sleepTimes.length; i++) { |
| jobs[i].schedule(sleepTimes[i]); |
| } |
| waitForCompletion(); |
| //make sure listener has had a chance to process the finished job |
| while (done.size() != jobs.length) { |
| Thread.yield(); |
| sleep(100); |
| } |
| Job[] doneOrder = done.toArray(new Job[done.size()]); |
| assertEquals("1.0", jobs.length, doneOrder.length); |
| for (int i = 0; i < doneOrder.length; i++) { |
| assertEquals("1.1." + i, jobs[i], doneOrder[i]); |
| } |
| } finally { |
| manager.removeJobChangeListener(listener); |
| } |
| } |
| |
| /** |
| * Tests conditions where there is a race to schedule the same job multiple times. |
| */ |
| public void testScheduleRace() { |
| final int[] count = new int[1]; |
| final boolean[] running = new boolean[] {false}; |
| final boolean[] failure = new boolean[] {false}; |
| final Job testJob = new Job("testScheduleRace") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| synchronized (running) { |
| //indicate job is running, and assert the job is not already running |
| if (running[0]) { |
| failure[0] = true; |
| } else { |
| running[0] = true; |
| } |
| } |
| //sleep for awhile to let duplicate job start running |
| Thread.sleep(1000); |
| } catch (InterruptedException e) { |
| //ignore |
| } finally { |
| synchronized (running) { |
| running[0] = false; |
| } |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| testJob.addJobChangeListener(new JobChangeAdapter() { |
| @Override |
| public void scheduled(IJobChangeEvent event) { |
| while (count[0]++ < 2) { |
| testJob.schedule(); |
| } |
| } |
| }); |
| testJob.schedule(); |
| waitForCompletion(testJob, 5000); |
| assertTrue("1.0", !failure[0]); |
| } |
| |
| public void testSimple() { |
| final int JOB_COUNT = 10; |
| for (int i = 0; i < JOB_COUNT; i++) { |
| new TestJob("testSimple").schedule(); |
| } |
| waitForCompletion(); |
| // |
| for (int i = 0; i < JOB_COUNT; i++) { |
| new TestJob("testSimple").schedule(50); |
| } |
| waitForCompletion(); |
| } |
| |
| /** |
| * Tests setting various kinds of invalid rules on jobs. |
| */ |
| public void testSetInvalidRule() { |
| class InvalidRule implements ISchedulingRule { |
| @Override |
| public boolean isConflicting(ISchedulingRule rule) { |
| return false; |
| } |
| |
| @Override |
| public boolean contains(ISchedulingRule rule) { |
| return false; |
| } |
| } |
| |
| InvalidRule rule1 = new InvalidRule(); |
| InvalidRule rule2 = new InvalidRule(); |
| ISchedulingRule multi = MultiRule.combine(rule1, rule2); |
| |
| Job job = new Job("job with invalid rule") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| return Status.OK_STATUS; |
| } |
| }; |
| |
| try { |
| job.setRule(rule1); |
| fail("invalid rule"); |
| } catch (IllegalArgumentException e) { |
| //expected |
| } |
| try { |
| job.setRule(rule2); |
| fail("invalid rule"); |
| } catch (IllegalArgumentException e) { |
| //expected |
| } |
| try { |
| job.setRule(multi); |
| fail("invalid rule"); |
| } catch (IllegalArgumentException e) { |
| //expected |
| } |
| } |
| |
| public void testSleep() { |
| TestJob job = new TestJob("ParentJob", 10, 100); |
| //sleeping a job that isn't scheduled should have no effect |
| assertEquals("1.0", Job.NONE, job.getState()); |
| assertTrue("1.1", job.sleep()); |
| assertEquals("1.2", Job.NONE, job.getState()); |
| |
| //sleeping a job that is already running should not work |
| job.schedule(); |
| //give the job a chance to start |
| waitForStart(job); |
| assertState("2.0", job, Job.RUNNING); |
| assertTrue("2.1", !job.sleep()); |
| assertState("2.2", job, Job.RUNNING); |
| |
| waitForCompletion(); |
| |
| //sleeping a job that is already sleeping should make sure it never runs |
| job.schedule(500); |
| assertState("3.0", job, Job.SLEEPING); |
| assertTrue("3.1", job.sleep()); |
| assertState("3.2", job, Job.SLEEPING); |
| //wait awhile and ensure the job is still sleeping |
| Thread.yield(); |
| sleep(600); |
| Thread.yield(); |
| assertState("3.3", job, Job.SLEEPING); |
| assertTrue("3.4", job.cancel()); //should be possible to cancel a sleeping job |
| } |
| |
| public void testSleepOnWait() { |
| final ISchedulingRule rule = new PathRule("testSleepOnWait"); |
| TestJob blockingJob = new TestJob("Long Job", 1000000, 10); |
| blockingJob.setRule(rule); |
| blockingJob.schedule(); |
| |
| TestJob job = new TestJob("Long Job", 1000000, 10); |
| job.setRule(rule); |
| job.schedule(); |
| //we know this job is waiting, so putting it to sleep should prevent it from running |
| assertState("1.0", job, Job.WAITING); |
| assertTrue("1.1", job.sleep()); |
| assertState("1.2", job, Job.SLEEPING); |
| |
| //cancel the blocking job, thus freeing the pool for the waiting job |
| blockingJob.cancel(); |
| |
| //make sure the job is still sleeping |
| assertState("1.3", job, Job.SLEEPING); |
| |
| //now wake the job up |
| job.wakeUp(); |
| waitForStart(job); |
| assertState("2.0", job, Job.RUNNING); |
| |
| //finally cancel the job |
| job.cancel(); |
| waitForCompletion(job); |
| } |
| |
| public void testSuspend() { |
| assertTrue("1.0", !manager.isSuspended()); |
| manager.suspend(); |
| try { |
| assertTrue("1.1", manager.isSuspended()); |
| } finally { |
| manager.resume(); |
| } |
| assertTrue("1.1", !manager.isSuspended()); |
| } |
| |
| /** |
| * Tests the following sequence: |
| * [Thread[main,6,main]]Suspend rule: R/ |
| * [Thread[main,6,main]]Begin rule: R/ |
| * [Thread[Worker-3,5,main]]Begin rule: L/JUnit/junit/tests/framework/Failure.java |
| * [Thread[main,6,main]]End rule: R/ |
| * [Thread[main,6,main]]Resume rule: R/ |
| * [Thread[Worker-3,5,main]]End rule: L/JUnit/junit/tests/framework/Failure.java |
| * @deprecated tests deprecated API |
| */ |
| @Deprecated |
| public void testSuspendMismatchedBegins() { |
| PathRule rule1 = new PathRule("/TestSuspendMismatchedBegins"); |
| PathRule rule2 = new PathRule("/TestSuspendMismatchedBegins/Child"); |
| manager.suspend(rule1, null); |
| |
| //start a job that acquires a child rule |
| TestBarrier barrier = new TestBarrier(); |
| JobRuleRunner runner = new JobRuleRunner("TestSuspendJob", rule2, barrier, 1, true); |
| runner.schedule(); |
| barrier.waitForStatus(TestBarrier.STATUS_START); |
| //let the job start the rule |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_RUN); |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| |
| //now try to resume the rule in this thread |
| manager.resume(rule1); |
| |
| //finally let the test runner resume the rule |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| barrier.waitForStatus(TestBarrier.STATUS_DONE); |
| waitForCompletion(runner); |
| |
| } |
| |
| /** |
| * Tests IJobManager suspend and resume API |
| * @deprecated tests deprecated API |
| */ |
| @Deprecated |
| public void testSuspendMultiThreadAccess() { |
| PathRule rule1 = new PathRule("/TestSuspend"); |
| PathRule rule2 = new PathRule("/TestSuspend/Child"); |
| manager.suspend(rule1, null); |
| |
| //should not be able to run a job that uses the rule |
| Job job = new Job("TestSuspend") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| return Status.OK_STATUS; |
| } |
| }; |
| job.setRule(rule1); |
| job.schedule(); |
| //give the job a chance to run |
| sleep(200); |
| assertNull("1.0", job.getResult()); |
| |
| //should be able to run a thread that begins the rule |
| int[] status = new int[1]; |
| SimpleRuleRunner runner = new SimpleRuleRunner(rule1, status, null); |
| new Thread(runner).start(); |
| TestBarrier.waitForStatus(status, TestBarrier.STATUS_DONE); |
| |
| //should be able to run a thread that begins a conflicting rule |
| status[0] = 0; |
| runner = new SimpleRuleRunner(rule2, status, null); |
| new Thread(runner).start(); |
| TestBarrier.waitForStatus(status, TestBarrier.STATUS_DONE); |
| |
| //now begin the rule in this thread |
| manager.beginRule(rule1, null); |
| |
| //should still be able to run a thread that begins the rule |
| status[0] = 0; |
| runner = new SimpleRuleRunner(rule1, status, null); |
| new Thread(runner).start(); |
| TestBarrier.waitForStatus(status, TestBarrier.STATUS_DONE); |
| |
| //our job should still not have executed |
| sleep(100); |
| assertNull("1.1", job.getResult()); |
| |
| //even ending the rule in this thread should not allow the job to continue |
| manager.endRule(rule1); |
| sleep(100); |
| assertNull("1.2", job.getResult()); |
| |
| //should still be able to run a thread that begins the rule |
| status[0] = 0; |
| runner = new SimpleRuleRunner(rule1, status, null); |
| new Thread(runner).start(); |
| TestBarrier.waitForStatus(status, TestBarrier.STATUS_DONE); |
| |
| //finally resume the rule in this thread |
| manager.resume(rule1); |
| |
| //job should now complete |
| waitForCompletion(job); |
| |
| } |
| |
| /** |
| * Tests IJobManager#transfer(ISchedulingRule, Thread) failure conditions. |
| */ |
| public void testTransferFailure() { |
| PathRule rule = new PathRule("/testTransferFailure"); |
| PathRule subRule = new PathRule("/testTransferFailure/Sub"); |
| Thread other = new Thread(); |
| //can't transfer a rule this thread doesn't own it |
| try { |
| manager.transferRule(rule, other); |
| fail("1.0"); |
| } catch (RuntimeException e) { |
| //expected |
| } |
| try { |
| manager.beginRule(rule, null); |
| //can't transfer a child rule of a rule currently owned by the caller |
| try { |
| manager.transferRule(subRule, other); |
| fail("1.1"); |
| } catch (RuntimeException e) { |
| //expected |
| } |
| //TODO This test is failing |
| //can't transfer a rule when the destination already owns an unrelated rule |
| TestBarrier barrier = new TestBarrier(); |
| ISchedulingRule unrelatedRule = new PathRule("UnrelatedRule"); |
| JobRuleRunner ruleRunner = new JobRuleRunner("testTransferFailure", unrelatedRule, barrier, 1, false); |
| ruleRunner.schedule(); |
| //wait for runner to start |
| barrier.waitForStatus(TestBarrier.STATUS_START); |
| //let it acquire the rule |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_RUN); |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| //transferring the calling thread's rule to the background job should fail |
| //because the destination thread already owns a rule |
| try { |
| manager.transferRule(rule, ruleRunner.getThread()); |
| fail("1.2"); |
| } catch (RuntimeException e) { |
| //expected |
| } |
| //let the background job finish |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| barrier.waitForStatus(TestBarrier.STATUS_DONE); |
| try { |
| ruleRunner.join(); |
| } catch (InterruptedException e1) { |
| fail("1.99", e1); |
| } |
| } finally { |
| manager.endRule(rule); |
| } |
| } |
| |
| /** |
| * Tests transferring a scheduling rule from one job to another |
| */ |
| public void testTransferJobToJob() { |
| final PathRule ruleToTransfer = new PathRule("testTransferJobToJob"); |
| final TestBarrier barrier = new TestBarrier(); |
| final Thread[] sourceThread = new Thread[1]; |
| final Job destination = new Job("testTransferJobToJob.destination") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| barrier.setStatus(TestBarrier.STATUS_RUNNING); |
| barrier.waitForStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| return Status.OK_STATUS; |
| } |
| }; |
| final Job source = new Job("testTransferJobToJob.source") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| sourceThread[0] = Thread.currentThread(); |
| //schedule the destination job and wait until it is running |
| destination.schedule(); |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| IJobManagerTest.this.sleep(100); |
| |
| //transferring the rule will fail because it must have been acquired by beginRule |
| manager.transferRule(ruleToTransfer, destination.getThread()); |
| return Status.OK_STATUS; |
| } |
| }; |
| source.setRule(ruleToTransfer); |
| source.schedule(); |
| waitForCompletion(source); |
| //source job should have failed due to illegal use of transferRule |
| assertTrue("1.0", !source.getResult().isOK()); |
| assertTrue("1.1", source.getResult().getException() instanceof RuntimeException); |
| |
| //let the destination finish |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| waitForCompletion(destination); |
| if (!destination.getResult().isOK()) { |
| fail("1.2", destination.getResult().getException()); |
| } |
| } |
| |
| /** |
| * Tests transferring a scheduling rule to the same thread |
| */ |
| public void testTransferSameThread() { |
| PathRule rule = new PathRule("testTransferSameThread"); |
| try { |
| manager.beginRule(rule, null); |
| //transfer to same thread is ok |
| manager.transferRule(rule, Thread.currentThread()); |
| } catch (Exception e) { |
| fail("1.0", e); |
| } finally { |
| manager.endRule(rule); |
| } |
| } |
| |
| /** |
| * Simple test of rule transfer |
| */ |
| public void testTransferSimple() { |
| class RuleEnder implements Runnable { |
| Exception error; |
| private final ISchedulingRule rule; |
| |
| RuleEnder(ISchedulingRule rule) { |
| this.rule = rule; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| manager.endRule(rule); |
| } catch (Exception e) { |
| this.error = e; |
| } |
| } |
| } |
| PathRule rule = new PathRule("testTransferSimple"); |
| manager.beginRule(rule, null); |
| RuleEnder ender = new RuleEnder(rule); |
| Thread destination = new Thread(ender); |
| manager.transferRule(rule, destination); |
| destination.start(); |
| try { |
| destination.join(); |
| } catch (InterruptedException e) { |
| fail("1.99", e); |
| } |
| if (ender.error != null) { |
| fail("1.0", ender.error); |
| } |
| } |
| |
| /** |
| * Tests transferring a scheduling rule to a job and back again. |
| */ |
| public void testTransferToJob() { |
| final PathRule rule = new PathRule("testTransferToJob"); |
| final TestBarrier barrier = new TestBarrier(); |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_START); |
| final Exception[] failure = new Exception[1]; |
| final Thread testThread = Thread.currentThread(); |
| //create a job that the rule will be transferred to |
| Job job = new Job("testTransferSimple") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| barrier.setStatus(TestBarrier.STATUS_RUNNING); |
| barrier.waitForStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| |
| //sleep a little to ensure the test thread is waiting |
| IJobManagerTest.this.sleep(100); |
| //at this point we should own the rule so we can transfer it back |
| try { |
| manager.transferRule(rule, testThread); |
| } catch (RuntimeException e) { |
| //should not fail |
| failure[0] = e; |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| job.schedule(); |
| //wait until the job starts |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| |
| //now begin and transfer the rule |
| manager.beginRule(rule, null); |
| manager.transferRule(rule, job.getThread()); |
| |
| //kick the job to allow it to transfer the rule back |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| |
| //try to begin the rule again, which will block until the rule is transferred back |
| manager.beginRule(rule, null); |
| manager.endRule(rule); |
| |
| //ensure the job didn't fail, and finally end the rule to unwind the initial beginRule |
| if (failure[0] != null) { |
| fail("1.0", failure[0]); |
| } |
| try { |
| manager.endRule(rule); |
| } catch (Exception e) { |
| //we should own the rule so this shouldn't fail |
| fail("2.00", e); |
| } |
| } |
| |
| /** |
| * Tests transferring a scheduling rule to a job that is waiting for a child of |
| * the transferred rule. |
| */ |
| public void testTransferToJobWaitingOnChildRule() { |
| final PathRule rule = new PathRule("testTransferToJobWaitingOnChildRule"); |
| final TestBarrier barrier = new TestBarrier(); |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_START); |
| final Exception[] failure = new Exception[1]; |
| final Thread testThread = Thread.currentThread(); |
| //create a job that the rule will be transferred to |
| Job job = new Job("testTransferToJobWaitingOnChildRule") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| barrier.setStatus(TestBarrier.STATUS_RUNNING); |
| //this will block until the rule is transferred |
| PathRule child = new PathRule(rule.getFullPath().append("child")); |
| try { |
| manager.beginRule(child, null); |
| } finally { |
| manager.endRule(child); |
| } |
| //at this point we should own the rule so we can transfer it back |
| try { |
| manager.transferRule(rule, testThread); |
| } catch (RuntimeException e) { |
| //should not fail |
| failure[0] = e; |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| manager.beginRule(rule, null); |
| |
| job.schedule(); |
| //wait until the job starts |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| //wait a bit longer to ensure the job is blocked |
| try { |
| Thread.sleep(100); |
| } catch (InterruptedException e1) { |
| //ignore |
| } |
| |
| //now transfer the rule, allowing the job to complete |
| manager.transferRule(rule, job.getThread()); |
| waitForCompletion(job); |
| |
| //ensure the job didn't fail, and finally end the rule to assert we own it |
| if (failure[0] != null) { |
| fail("1.0", failure[0]); |
| } |
| try { |
| manager.endRule(rule); |
| } catch (Exception e) { |
| //we should own the rule so this shouldn't fail |
| fail("2.00", e); |
| } |
| } |
| |
| /** |
| * Tests transferring a scheduling rule to a job that is waiting for that rule. |
| */ |
| public void testTransferToWaitingJob() { |
| final PathRule rule = new PathRule("testTransferToWaitingJob"); |
| final TestBarrier barrier = new TestBarrier(); |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_START); |
| final Exception[] failure = new Exception[1]; |
| final Thread testThread = Thread.currentThread(); |
| //create a job that the rule will be transferred to |
| Job job = new Job("testTransferToWaitingJob") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| barrier.setStatus(TestBarrier.STATUS_RUNNING); |
| //this will block until the rule is transferred |
| try { |
| manager.beginRule(rule, null); |
| } finally { |
| manager.endRule(rule); |
| } |
| //at this point we should own the rule so we can transfer it back |
| try { |
| manager.transferRule(rule, testThread); |
| } catch (RuntimeException e) { |
| //should not fail |
| failure[0] = e; |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| manager.beginRule(rule, null); |
| |
| job.schedule(); |
| //wait until the job starts |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| //wait a bit longer to ensure the job is blocked |
| try { |
| Thread.sleep(100); |
| } catch (InterruptedException e1) { |
| //ignore |
| } |
| |
| //now transfer the rule, allowing the job to complete |
| manager.transferRule(rule, job.getThread()); |
| waitForCompletion(job); |
| |
| //ensure the job didn't fail, and finally end the rule to assert we own it |
| if (failure[0] != null) { |
| fail("1.0", failure[0]); |
| } |
| try { |
| manager.endRule(rule); |
| } catch (Exception e) { |
| //we should own the rule so this shouldn't fail |
| fail("2.00", e); |
| } |
| } |
| |
| /** |
| * Tests a batch of jobs that use two mutually exclusive rules. |
| */ |
| public void testTwoRules() { |
| final int JOB_COUNT = 10; |
| TestJob[] jobs = new TestJob[JOB_COUNT]; |
| ISchedulingRule evens = new IdentityRule(); |
| ISchedulingRule odds = new IdentityRule(); |
| for (int i = 0; i < JOB_COUNT; i++) { |
| jobs[i] = new TestJob("testSimpleRules", 1000000, 10); |
| jobs[i].setRule(((i & 0x1) == 0) ? evens : odds); |
| jobs[i].schedule(); |
| } |
| //first two jobs should be running, all others should be waiting |
| waitForStart(jobs[0]); |
| waitForStart(jobs[1]); |
| assertState("1.0", jobs[0], Job.RUNNING); |
| assertState("1.1", jobs[1], Job.RUNNING); |
| for (int i = 2; i < JOB_COUNT; i++) { |
| assertState("1.2." + i, jobs[i], Job.WAITING); |
| } |
| //cancel job i then i+1 and i+2 should run and all others should wait |
| for (int i = 0; i < JOB_COUNT; i++) { |
| jobs[i].cancel(); |
| try { |
| waitForStart(jobs[i + 1]); |
| assertState("2.0." + i, jobs[i + 1], Job.RUNNING); |
| waitForStart(jobs[i + 2]); |
| assertState("2.1." + i, jobs[i + 2], Job.RUNNING); |
| } catch (ArrayIndexOutOfBoundsException e) { |
| //ignore |
| } |
| for (int j = i + 3; j < JOB_COUNT; j++) { |
| assertState("2.2." + i + "." + j, jobs[j], Job.WAITING); |
| } |
| } |
| } |
| |
| /** |
| * A job has been canceled. Pause this thread so that a worker thread |
| * has a chance to receive the cancel event. |
| */ |
| private void waitForCancel(Job job) { |
| int i = 0; |
| while (job.getState() == Job.RUNNING) { |
| Thread.yield(); |
| sleep(100); |
| Thread.yield(); |
| //sanity test to avoid hanging tests |
| if (i++ > 1000) { |
| dumpState(); |
| assertTrue("Timeout waiting for job to cancel", false); |
| } |
| } |
| } |
| |
| private synchronized void waitForCompletion() { |
| int i = 0; |
| assertTrue("Jobs completed that weren't scheduled", completedJobs <= scheduledJobs); |
| while (completedJobs < scheduledJobs) { |
| try { |
| wait(500); |
| } catch (InterruptedException e) { |
| //ignore |
| } |
| //sanity test to avoid hanging tests |
| if (i++ > 1000) { |
| dumpState(); |
| assertTrue("Timeout waiting for job to complete", false); |
| } |
| } |
| } |
| |
| /** |
| * A family of jobs have been canceled. Pause this thread until all of the jobs |
| * in the family are canceled |
| */ |
| private void waitForFamilyCancel(Job[] jobs, TestJobFamily type) { |
| |
| for (Job job : jobs) { |
| int i = 0; |
| while (job.belongsTo(type) && (job.getState() != Job.NONE)) { |
| Thread.yield(); |
| sleep(100); |
| Thread.yield(); |
| //sanity test to avoid hanging tests |
| if (i++ > 100) { |
| dumpState(); |
| assertTrue("Timeout waiting for job in family " + type.getType() + "to be canceled ", false); |
| } |
| } |
| } |
| } |
| |
| private void waitForRunCount(TestJob job, int runCount) { |
| int i = 0; |
| while (job.getRunCount() < runCount) { |
| Thread.yield(); |
| sleep(100); |
| Thread.yield(); |
| //sanity test to avoid hanging tests |
| if (i++ >= 1000) { |
| dumpState(); |
| assertTrue("Timeout waiting for job to start. Job: " + job + ", state: " + job.getState(), false); |
| } |
| } |
| } |
| |
| /** |
| * A job has been scheduled. Pause this thread so that a worker thread |
| * has a chance to pick up the new job. |
| */ |
| private void waitForStart(TestJob job) { |
| waitForRunCount(job, 1); |
| } |
| } |