| /******************************************************************************* |
| * Copyright (c) 2003, 2012 IBM 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: |
| * IBM - Initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.core.tests.runtime.jobs; |
| |
| import junit.framework.TestSuite; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.*; |
| import org.eclipse.core.tests.harness.FussyProgressMonitor; |
| import org.eclipse.core.tests.harness.TestBarrier; |
| |
| /** |
| * Tests API methods IJobManager.beginRule and IJobManager.endRule |
| */ |
| public class BeginEndRuleTest extends AbstractJobManagerTest { |
| |
| /** |
| * This runnable will try to end the given rule in the Job Manager |
| */ |
| private class RuleEnder implements Runnable { |
| private ISchedulingRule rule; |
| private int[] status; |
| |
| public RuleEnder(ISchedulingRule rule, int[] status) { |
| this.rule = rule; |
| this.status = status; |
| } |
| |
| public void run() { |
| try { |
| status[0] = TestBarrier.STATUS_RUNNING; |
| manager.endRule(rule); |
| fail("Ending Rule"); |
| |
| } catch (RuntimeException e) { |
| //should fail |
| } |
| |
| } |
| } |
| |
| public static TestSuite suite() { |
| return new TestSuite(BeginEndRuleTest.class); |
| // TestSuite suite = new TestSuite(); |
| // suite.addTest(new BeginEndRuleTest("testComplexRuleStarting")); |
| // return suite; |
| } |
| |
| public BeginEndRuleTest() { |
| super(); |
| } |
| |
| public BeginEndRuleTest(String name) { |
| super(name); |
| } |
| |
| public void testComplexRuleStarting() { |
| //test how the manager reacts when several different threads try to begin conflicting rules |
| final int NUM_THREADS = 3; |
| //array to communicate with the launched threads |
| final int[] status = {TestBarrier.STATUS_WAIT_FOR_START, TestBarrier.STATUS_WAIT_FOR_START, TestBarrier.STATUS_WAIT_FOR_START}; |
| //number of times to start each rule |
| int NUM_REPEATS = 10; |
| |
| Job[] jobs = new Job[NUM_THREADS]; |
| jobs[0] = new JobRuleRunner("ComplexJob1", new PathRule("/testComplexRuleStarting"), status, 0, NUM_REPEATS, true); |
| jobs[1] = new JobRuleRunner("ComplexJob2", new PathRule("/testComplexRuleStarting/B"), status, 1, NUM_REPEATS, true); |
| jobs[2] = new JobRuleRunner("ComplexJob3", new PathRule("/testComplexRuleStarting/B/C"), status, 2, NUM_REPEATS, true); |
| |
| //schedule the jobs |
| for (int i = 0; i < jobs.length; i++) |
| jobs[i].schedule(); |
| |
| //wait until all the jobs start |
| for (int i = 0; i < jobs.length; i++) |
| TestBarrier.waitForStatus(status, i, TestBarrier.STATUS_START); |
| |
| //all jobs should be running |
| //the status flag should be set to START |
| for (int i = 0; i < status.length; i++) { |
| assertEquals("1." + i, Job.RUNNING, jobs[i].getState()); |
| assertEquals("2." + i, TestBarrier.STATUS_START, status[i]); |
| } |
| |
| //the order that the jobs will be executed |
| int[] order = {0, 1, 2}; |
| |
| for (int j = 0; j < NUM_REPEATS; j++) { |
| //let the first job in the order run |
| status[order[0]] = TestBarrier.STATUS_WAIT_FOR_RUN; |
| //wait until the first job in the order reads the flag |
| TestBarrier.waitForStatus(status, order[0], TestBarrier.STATUS_RUNNING); |
| |
| //let all subsequent jobs run (they will be blocked) |
| //before starting next job, wait until previous job is blocked by JobManager |
| for (int i = 1; i < order.length; i++) { |
| status[order[i]] = TestBarrier.STATUS_WAIT_FOR_RUN; |
| TestBarrier.waitForStatus(status, order[i], TestBarrier.STATUS_BLOCKED); |
| } |
| |
| //the first job should be running, the remaining jobs should be waiting |
| assertEquals("3.0", TestBarrier.STATUS_RUNNING, status[order[0]]); |
| assertEquals("3.0", TestBarrier.STATUS_BLOCKED, status[order[1]]); |
| assertEquals("3.0", TestBarrier.STATUS_BLOCKED, status[order[2]]); |
| |
| //let the first job finish |
| status[order[0]] = TestBarrier.STATUS_WAIT_FOR_DONE; |
| TestBarrier.waitForStatus(status, order[0], TestBarrier.STATUS_DONE); |
| |
| //the remaining jobs will now compete for execution (order NOT guaranteed) |
| //let them both start and wait until they complete |
| int doneCount = 0; |
| while (doneCount < 2) { |
| if (status[order[1]] == TestBarrier.STATUS_RUNNING) { |
| status[order[1]] = TestBarrier.STATUS_WAIT_FOR_DONE; |
| TestBarrier.waitForStatus(status, order[1], TestBarrier.STATUS_DONE); |
| doneCount++; |
| } |
| if (status[order[2]] == TestBarrier.STATUS_RUNNING) { |
| status[order[2]] = TestBarrier.STATUS_WAIT_FOR_DONE; |
| TestBarrier.waitForStatus(status, order[2], TestBarrier.STATUS_DONE); |
| doneCount++; |
| } |
| } |
| //change the order of the jobs, nothing should change in the execution |
| int temp = order[0]; |
| order[0] = order[2]; |
| order[2] = order[1]; |
| order[1] = temp; |
| } |
| |
| //wait until all jobs are done |
| for (int i = 0; i < order.length; i++) { |
| waitForEnd(jobs[order[i]]); |
| } |
| |
| for (int i = 0; i < jobs.length; i++) { |
| //check that the final status of all jobs is correct |
| assertEquals("9." + i, TestBarrier.STATUS_DONE, status[i]); |
| assertEquals("10." + i, Job.NONE, jobs[i].getState()); |
| assertEquals("11." + i, IStatus.OK, jobs[i].getResult().getSeverity()); |
| } |
| } |
| |
| public void testSimpleRuleStarting() { |
| //start two jobs, each of which will begin and end a rule several times |
| //while one job starts a rule, the second job's call to begin rule should block that thread |
| //until the first job calls end rule |
| final int[] status = {TestBarrier.STATUS_WAIT_FOR_START, TestBarrier.STATUS_WAIT_FOR_START}; |
| //number of repetitions of beginning and ending the rule |
| final int NUM_REPEATS = 10; |
| Job[] jobs = new Job[2]; |
| jobs[0] = new JobRuleRunner("SimpleJob1", new PathRule("/testSimpleRuleStarting"), status, 0, NUM_REPEATS, false); |
| jobs[1] = new JobRuleRunner("SimpleJob2", new PathRule("/testSimpleRuleStarting/B"), status, 1, NUM_REPEATS, false); |
| |
| //schedule both jobs to start their execution |
| jobs[0].schedule(); |
| jobs[1].schedule(); |
| |
| //make sure both jobs are running and their respective run methods have been invoked |
| //waitForStart(jobs[1]); |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_START); |
| TestBarrier.waitForStatus(status, 1, TestBarrier.STATUS_START); |
| |
| assertEquals("2.0", Job.RUNNING, jobs[0].getState()); |
| assertEquals("2.1", Job.RUNNING, jobs[1].getState()); |
| assertEquals("2.2", TestBarrier.STATUS_START, status[0]); |
| assertEquals("2.3", TestBarrier.STATUS_START, status[1]); |
| |
| //the order of execution of the jobs (by their index in the status array) |
| int first = 0; |
| int second = 1; |
| |
| //now both jobs are waiting for the STATUS_WAIT_FOR_RUN flag |
| for (int j = 0; j < NUM_REPEATS; j++) { |
| //let the first job start executing |
| status[first] = TestBarrier.STATUS_WAIT_FOR_RUN; |
| |
| //wait for the first job to read the flag |
| TestBarrier.waitForStatus(status, first, TestBarrier.STATUS_RUNNING); |
| |
| //let the second job start, its thread will be blocked by the beginRule method |
| status[second] = TestBarrier.STATUS_WAIT_FOR_RUN; |
| |
| //only the first job should be running |
| //the other job should be blocked by the beginRule method |
| assertEquals("3.1", TestBarrier.STATUS_RUNNING, status[first]); |
| assertEquals("3.2", TestBarrier.STATUS_WAIT_FOR_RUN, status[second]); |
| |
| //let the first job finish execution and call endRule |
| //the second thread will then become unblocked |
| status[first] = TestBarrier.STATUS_WAIT_FOR_DONE; |
| |
| //wait until the first job is done |
| TestBarrier.waitForStatus(status, first, TestBarrier.STATUS_DONE); |
| |
| //now wait until the second job begins execution |
| TestBarrier.waitForStatus(status, second, TestBarrier.STATUS_RUNNING); |
| |
| //the first job is done, the second job is executing |
| assertEquals("4.1", TestBarrier.STATUS_DONE, status[first]); |
| assertEquals("4.2", TestBarrier.STATUS_RUNNING, status[second]); |
| |
| //let the second job finish execution |
| status[second] = TestBarrier.STATUS_WAIT_FOR_DONE; |
| |
| //wait until the second job is finished |
| TestBarrier.waitForStatus(status, second, TestBarrier.STATUS_DONE); |
| |
| //both jobs are done now |
| assertEquals("5.1", TestBarrier.STATUS_DONE, status[first]); |
| assertEquals("5.2", TestBarrier.STATUS_DONE, status[second]); |
| |
| //flip the order of execution of the jobs |
| int temp = first; |
| first = second; |
| second = temp; |
| } |
| |
| //wait until both jobs are done |
| waitForEnd(jobs[second]); |
| waitForEnd(jobs[first]); |
| |
| //check that the final status of both jobs is correct |
| assertEquals("6.1", TestBarrier.STATUS_DONE, status[0]); |
| assertEquals("6.2", TestBarrier.STATUS_DONE, status[1]); |
| assertEquals("6.3", Job.NONE, jobs[0].getState()); |
| assertEquals("6.4", Job.NONE, jobs[1].getState()); |
| assertEquals("6.5", IStatus.OK, jobs[0].getResult().getSeverity()); |
| assertEquals("6.6", IStatus.OK, jobs[1].getResult().getSeverity()); |
| } |
| |
| public void testComplexRuleContainment() { |
| ISchedulingRule rules[] = new ISchedulingRule[4]; |
| |
| rules[0] = new PathRule("/testComplexRuleContainment"); |
| rules[1] = new PathRule("/testComplexRuleContainment/B"); |
| rules[2] = new PathRule("/testComplexRuleContainment/B/C"); |
| rules[3] = new PathRule("/testComplexRuleContainment/D"); |
| |
| //adding multiple rules in correct order |
| int RULE_REPEATS = 10; |
| try { |
| for (int i = 0; i < rules.length - 1; i++) { |
| for (int j = 0; j < RULE_REPEATS; j++) { |
| manager.beginRule(rules[i], null); |
| } |
| } |
| for (int i = rules.length - 1; i > 0; i--) { |
| for (int j = 0; j < RULE_REPEATS; j++) { |
| manager.endRule(rules[i - 1]); |
| } |
| } |
| } catch (RuntimeException e) { |
| fail("4.0"); |
| } |
| |
| //adding rules in proper order, then adding a rule from a bypassed branch |
| //trying to end previous rules should not work |
| for (int i = 0; i < rules.length; i++) { |
| manager.beginRule(rules[i], null); |
| } |
| try { |
| manager.endRule(rules[2]); |
| fail("4.1"); |
| } catch (RuntimeException e) { |
| //should fail |
| try { |
| manager.endRule(rules[1]); |
| fail("4.2"); |
| } catch (RuntimeException e1) { |
| //should fail |
| try { |
| manager.endRule(rules[0]); |
| fail("4.3"); |
| } catch (RuntimeException e2) { |
| //should fail |
| } |
| } |
| } |
| for (int i = rules.length; i > 0; i--) { |
| manager.endRule(rules[i - 1]); |
| } |
| } |
| |
| public void _testEndNullRule() { |
| //see bug #43460 |
| //end null IScheduleRule without begin |
| try { |
| manager.endRule(null); |
| fail("1.1"); |
| } catch (RuntimeException e) { |
| //should fail |
| } |
| } |
| |
| public void testFailureCase() { |
| ISchedulingRule rule1 = new IdentityRule(); |
| ISchedulingRule rule2 = new IdentityRule(); |
| |
| //end without begin |
| try { |
| manager.endRule(rule1); |
| fail("1.0"); |
| } catch (RuntimeException e) { |
| //should fail |
| } |
| //simple mismatched begin/end |
| manager.beginRule(rule1, null); |
| try { |
| manager.endRule(rule2); |
| fail("1.2"); |
| } catch (RuntimeException e) { |
| //should fail |
| } |
| //should still be able to end the original rule |
| manager.endRule(rule1); |
| |
| //mismatched begin/end, ending a null rule |
| manager.beginRule(rule1, null); |
| try { |
| manager.endRule(null); |
| fail("1.3"); |
| } catch (RuntimeException e) { |
| //should fail |
| } |
| //should still be able to end the original rule |
| manager.endRule(rule1); |
| } |
| |
| /** |
| * Tests create a job with one scheduling rule, and then attempting |
| * to acquire an unrelated rule from within that job. |
| */ |
| public void testFailedNestRuleInJob() { |
| final ISchedulingRule rule1 = new PathRule("/testFailedNestRuleInJob/A/"); |
| final ISchedulingRule rule2 = new PathRule("/testFailedNestRuleInJob/B/"); |
| final Exception[] exception = new Exception[1]; |
| Job job = new Job("testFailedNestRuleInJob") { |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| try { |
| manager.beginRule(rule2, monitor); |
| } finally { |
| manager.endRule(rule2); |
| } |
| } catch (RuntimeException e) { |
| exception[0] = e; |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| job.setRule(rule1); |
| job.schedule(); |
| waitForEnd(job); |
| assertTrue("1.0", exception[0] != null); |
| assertTrue("1.1", exception[0].getMessage().indexOf("does not match outer scope rule") > 0); |
| } |
| |
| public void testNestedCase() { |
| ISchedulingRule rule1 = new PathRule("/testNestedCase"); |
| ISchedulingRule rule2 = new PathRule("/testNestedCase/B"); |
| |
| //ending an outer rule before an inner one |
| manager.beginRule(rule1, null); |
| manager.beginRule(rule2, null); |
| try { |
| manager.endRule(rule1); |
| fail("2.0"); |
| } catch (RuntimeException e) { |
| //should fail |
| } |
| manager.endRule(rule2); |
| manager.endRule(rule1); |
| |
| //ending a rule that is not open |
| manager.beginRule(rule1, null); |
| manager.beginRule(rule2, null); |
| try { |
| manager.endRule(null); |
| fail("2.1"); |
| } catch (RuntimeException e) { |
| //should fail |
| } |
| manager.endRule(rule2); |
| manager.endRule(rule1); |
| |
| //adding rules starting with null rule |
| try { |
| manager.beginRule(null, null); |
| manager.beginRule(rule1, null); |
| manager.endRule(rule1); |
| manager.beginRule(rule2, null); |
| manager.endRule(rule2); |
| manager.endRule(null); |
| } catch (RuntimeException e) { |
| //should not fail |
| fail("2.2"); |
| } |
| |
| //adding numerous instances of the same rule |
| int NUM_ADDITIONS = 100; |
| try { |
| for (int i = 0; i < NUM_ADDITIONS; i++) { |
| manager.beginRule(rule1, null); |
| } |
| for (int i = 0; i < NUM_ADDITIONS; i++) { |
| manager.endRule(rule1); |
| } |
| } catch (RuntimeException e) { |
| //should not fail |
| fail("2.3"); |
| } |
| |
| //adding numerous instances of the null rule |
| try { |
| for (int i = 0; i < NUM_ADDITIONS; i++) { |
| manager.beginRule(null, null); |
| } |
| manager.beginRule(rule1, null); |
| manager.endRule(rule1); |
| for (int i = 0; i < NUM_ADDITIONS; i++) { |
| manager.endRule(null); |
| } |
| } catch (RuntimeException e) { |
| //should not fail |
| fail("2.4"); |
| } |
| } |
| |
| /** |
| * Tests a failure where canceling an attempt to beginRule resulted in implicit jobs |
| * being recycled before they were finished. |
| */ |
| public void testBug44299() { |
| ISchedulingRule rule = new IdentityRule(); |
| FussyProgressMonitor monitor = new FussyProgressMonitor(); |
| manager.beginRule(rule, monitor); |
| int[] status = new int[1]; |
| SimpleRuleRunner runner = new SimpleRuleRunner(rule, status, monitor); |
| new Thread(runner).start(); |
| |
| //wait for the job to start |
| TestBarrier.waitForStatus(status, TestBarrier.STATUS_RUNNING); |
| //give the job a chance to enter the wait loop |
| try { |
| Thread.sleep(100); |
| } catch (InterruptedException e) { |
| //ignore |
| } |
| //cancel the monitor |
| monitor.setCanceled(true); |
| |
| TestBarrier.waitForStatus(status, TestBarrier.STATUS_DONE); |
| if (runner.exception != null) |
| fail("1.0", runner.exception); |
| |
| //finally clear the rule |
| manager.endRule(rule); |
| } |
| |
| public void testRuleContainment() { |
| ISchedulingRule rules[] = new ISchedulingRule[4]; |
| |
| rules[0] = new PathRule("/testRuleContainment"); |
| rules[1] = new PathRule("/testRuleContainment/B"); |
| rules[2] = new PathRule("/testRuleContainment/B/C"); |
| rules[3] = new PathRule("/testRuleContainment/D"); |
| |
| //simple addition of rules in incorrect containment order |
| manager.beginRule(rules[1], null); |
| try { |
| manager.beginRule(rules[0], null); |
| fail("3.0"); |
| } catch (RuntimeException e) { |
| //should fail |
| } finally { |
| //still need to end the rule |
| manager.endRule(rules[0]); |
| } |
| manager.endRule(rules[1]); |
| |
| //adding rules in proper order, then adding a rule from different hierarchy |
| manager.beginRule(rules[1], null); |
| manager.beginRule(rules[2], null); |
| try { |
| manager.beginRule(rules[3], null); |
| fail("3.2"); |
| } catch (RuntimeException e) { |
| //should fail |
| } finally { |
| manager.endRule(rules[3]); |
| } |
| //should still be able to end the rules |
| manager.endRule(rules[2]); |
| manager.endRule(rules[1]); |
| } |
| |
| public void testSimpleOtherThreadAccess() { |
| //ending a rule started on this thread from another thread |
| ISchedulingRule rule1 = new IdentityRule(); |
| int[] status = {TestBarrier.STATUS_START}; |
| Thread endingThread = new Thread(new RuleEnder(rule1, status)); |
| manager.beginRule(rule1, null); |
| |
| endingThread.start(); |
| TestBarrier.waitForStatus(status, TestBarrier.STATUS_RUNNING); |
| |
| try { |
| endingThread.join(); |
| } catch (InterruptedException e) { |
| //ignore |
| } |
| //the thread should be dead now |
| assertTrue("1.0", !endingThread.isAlive()); |
| |
| //should be able to end the rule from this thread |
| manager.endRule(rule1); |
| |
| //starting several rules on this thread, and trying to end them from other threads |
| ISchedulingRule rules[] = new ISchedulingRule[3]; |
| |
| rules[0] = new PathRule("/testSimpleOtherThreadAccess"); |
| rules[1] = new PathRule("/testSimpleOtherThreadAccess/B"); |
| rules[2] = new PathRule("/testSimpleOtherThreadAccess/C"); |
| |
| //end the rules right after starting them |
| for (int i = 0; i < rules.length; i++) { |
| manager.beginRule(rules[i], null); |
| status[0] = TestBarrier.STATUS_START; |
| Thread t = new Thread(new RuleEnder(rules[i], status)); |
| t.start(); |
| TestBarrier.waitForStatus(status, TestBarrier.STATUS_RUNNING); |
| try { |
| t.join(); |
| } catch (InterruptedException e1) { |
| //ignore |
| } |
| //the thread should be dead now |
| assertTrue("2." + i, !t.isAlive()); |
| } |
| |
| //try to end the rules when they are all started |
| for (int i = 0; i < rules.length; i++) { |
| status[0] = TestBarrier.STATUS_START; |
| Thread t = new Thread(new RuleEnder(rules[i], status)); |
| t.start(); |
| TestBarrier.waitForStatus(status, TestBarrier.STATUS_RUNNING); |
| try { |
| t.join(); |
| } catch (InterruptedException e1) { |
| //ignore |
| } |
| //the thread should be dead now |
| assertTrue("3." + i, !t.isAlive()); |
| } |
| |
| //try to end the rules after manager.endRule() has been called |
| for (int i = rules.length; i > 0; i--) { |
| manager.endRule(rules[i - 1]); |
| status[0] = TestBarrier.STATUS_START; |
| Thread t = new Thread(new RuleEnder(rules[i - 1], status)); |
| t.start(); |
| TestBarrier.waitForStatus(status, TestBarrier.STATUS_RUNNING); |
| try { |
| t.join(); |
| } catch (InterruptedException e1) { |
| //ignore |
| } |
| //the thread should be dead now |
| assertTrue("4." + i, !t.isAlive()); |
| } |
| } |
| |
| /** |
| * A job is running. Wait until it is finished. |
| */ |
| private void waitForEnd(Job job) { |
| int i = 0; |
| while (job.getState() != Job.NONE) { |
| Thread.yield(); |
| try { |
| Thread.sleep(100); |
| } catch (InterruptedException e) { |
| //ignore |
| } |
| Thread.yield(); |
| //sanity test to avoid hanging tests |
| assertTrue("Timeout waiting for job to end", i++ < 100); |
| } |
| } |
| |
| public void testIgnoreScheduleThreadJob() throws Exception { |
| final int[] count = new int[1]; |
| JobChangeAdapter a = new JobChangeAdapter() { |
| public void running(org.eclipse.core.runtime.jobs.IJobChangeEvent event) { |
| count[0]++; |
| } |
| }; |
| Job.getJobManager().addJobChangeListener(a); |
| IdentityRule rule = new IdentityRule(); |
| try { |
| Job.getJobManager().beginRule(rule, null); |
| Job.getJobManager().currentJob().schedule(); |
| } finally { |
| Job.getJobManager().endRule(rule); |
| } |
| Thread.sleep(250); |
| Job.getJobManager().removeJobChangeListener(a); |
| assertEquals("ThreadJob did not ignore reschedule", 0, count[0]); |
| } |
| |
| public void testRunThreadJobIsNotRescheduled() throws Exception { |
| final int[] count = new int[1]; |
| JobChangeAdapter a = new JobChangeAdapter() { |
| public void running(org.eclipse.core.runtime.jobs.IJobChangeEvent event) { |
| count[0]++; |
| } |
| }; |
| Job.getJobManager().addJobChangeListener(a); |
| IdentityRule rule = new IdentityRule(); |
| try { |
| Job.getJobManager().beginRule(rule, null); |
| } finally { |
| Job.getJobManager().endRule(rule); |
| } |
| Thread.sleep(250); |
| Job.getJobManager().removeJobChangeListener(a); |
| assertEquals("ThreadJob did not ignore reschedule", 0, count[0]); |
| } |
| |
| public void testRunNestedAcquireThreadIsNotRescheduled() throws Exception { |
| final PathRule rule = new PathRule(getName()); |
| final PathRule subRule = new PathRule(getName() + "/subRule"); |
| |
| final int[] count = new int[1]; |
| |
| final Job job = new Job(getName() + "acquire") { |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| Job.getJobManager().beginRule(subRule, null); |
| } finally { |
| Job.getJobManager().endRule(subRule); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| job.setRule(rule); |
| |
| JobChangeAdapter a = new JobChangeAdapter() { |
| public void running(org.eclipse.core.runtime.jobs.IJobChangeEvent event) { |
| if (event.getJob() == job) |
| return; |
| count[0]++; |
| } |
| }; |
| Job.getJobManager().addJobChangeListener(a); |
| job.schedule(); |
| Thread.sleep(250); |
| Job.getJobManager().removeJobChangeListener(a); |
| assertEquals("ThreadJob did not ignore reschedule", 0, count[0]); |
| } |
| |
| } |