| /******************************************************************************* |
| * Copyright (c) 2014, 2015 Google Inc 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: |
| * Thirumala Reddy Mutchukota - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.core.tests.runtime.jobs; |
| |
| import java.util.*; |
| import junit.framework.AssertionFailedError; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.*; |
| import org.eclipse.core.tests.harness.*; |
| |
| /** |
| * Tests for {@link JobGroup}. |
| */ |
| public class JobGroupTest extends AbstractJobTest { |
| private IJobManager manager; |
| private FussyProgressProvider progressProvider; |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| manager = Job.getJobManager(); |
| progressProvider = new FussyProgressProvider(); |
| manager.setProgressProvider(progressProvider); |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| super.tearDown(); |
| progressProvider.sanityCheck(); |
| manager.setProgressProvider(null); |
| } |
| |
| public void testThrottlingWhenAllJobsAreKnown() { |
| final int NUM_JOBS = 100; |
| final int MAX_THREADS = 10; |
| TestJob[] jobs = new TestJob[NUM_JOBS]; |
| final JobGroup jobGroup = new JobGroup("JobGroup", MAX_THREADS, NUM_JOBS); |
| final int[] maxThreadsUsed = new int[1]; |
| final TestBarrier barrier = new TestBarrier(); |
| |
| // Create and schedule the long running test jobs. |
| for (int i = 0; i < NUM_JOBS; i++) { |
| jobs[i] = new TestJob("TestJob", 1000000, 10); |
| jobs[i].setJobGroup(jobGroup); |
| jobs[i].schedule(); |
| } |
| |
| maxThreadsUsed[0] = 0; |
| // Use a thread to record the maximum number of running jobs and |
| // cancel the running jobs so that the waiting jobs will be scheduled. |
| final Thread t = new Thread(() -> { |
| barrier.setStatus(TestBarrier.STATUS_RUNNING); |
| while (jobGroup.getState() != JobGroup.NONE) { |
| List<TestJob> runningJobs = new ArrayList<>(); |
| for (Job activeJob : jobGroup.getActiveJobs()) { |
| if (activeJob.getState() == Job.RUNNING) { |
| runningJobs.add((TestJob) activeJob); |
| } |
| } |
| int runningJobsSize = runningJobs.size(); |
| if (runningJobsSize > maxThreadsUsed[0]) { |
| maxThreadsUsed[0] = runningJobsSize; |
| } |
| for (Job runningJob : runningJobs) { |
| runningJob.cancel(); |
| waitForCompletion(runningJob); |
| } |
| } |
| barrier.setStatus(TestBarrier.STATUS_DONE); |
| }); |
| |
| assertEquals("1.0", JobGroup.ACTIVE, jobGroup.getState()); |
| // Start the thread and wait for it to complete. |
| t.start(); |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| barrier.waitForStatus(TestBarrier.STATUS_DONE); |
| |
| assertEquals("2.0", JobGroup.NONE, jobGroup.getState()); |
| assertTrue("3.0", maxThreadsUsed[0] > 0); |
| assertTrue("4.0", maxThreadsUsed[0] <= MAX_THREADS); |
| } |
| |
| public void testSeedJobsWhenAllJobsAreKnown() { |
| final int NUM_SEED_JOBS = 10; |
| final JobGroup jobGroup = new JobGroup("JobGroup", 1, NUM_SEED_JOBS); |
| |
| for (int i = 1; i <= NUM_SEED_JOBS; i++) { |
| // Create and schedule a long running test job. |
| TestJob job = new TestJob("TestJob", 1000000, 10); |
| job.setJobGroup(jobGroup); |
| job.schedule(); |
| waitForStart(job); |
| // Job group should be in the ACTIVE state with one active job. |
| assertEquals("1." + i, 1, jobGroup.getActiveJobs().size()); |
| assertEquals("2." + i, JobGroup.ACTIVE, jobGroup.getState()); |
| // Cancel the test job and wait for it to finish. |
| job.cancel(); |
| waitForCompletion(job); |
| // Verify that the group does not contain any active jobs. |
| assertEquals("3." + i, 0, jobGroup.getActiveJobs().size()); |
| // Verify that the group will be in the ACTIVE state even when there are no active jobs |
| // and transitions to NONE state only after all the seed jobs are completed. |
| if (i < NUM_SEED_JOBS) { |
| assertEquals("4." + i, JobGroup.ACTIVE, jobGroup.getState()); |
| } else { |
| waitForCompletion(jobGroup); |
| assertEquals("4." + i, JobGroup.NONE, jobGroup.getState()); |
| } |
| } |
| } |
| |
| public void testSeedJobsWhenSeedJobsAddNewJobs() { |
| final int NUM_SEED_JOBS = 10; |
| final int NUM_CHILD_JOBS = 10; |
| final JobGroup jobGroup = new JobGroup("JobGroup", 10, NUM_SEED_JOBS); |
| |
| for (int i = 1; i <= NUM_SEED_JOBS; i++) { |
| // Create and schedule a seed job, which creates and schedules the |
| // long running child jobs belonging to the same group. |
| // An example usage would be a directory digger that starts |
| // with a set of root directories. |
| Job job = new Job("SeedJob") { |
| @Override |
| public IStatus run(IProgressMonitor monitor) { |
| for (int j = 0; j < NUM_CHILD_JOBS; j++) { |
| TestJob childJob = new TestJob("ChildTestJob", 1000000, 10); |
| childJob.setJobGroup(getJobGroup()); |
| childJob.schedule(); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| job.setJobGroup(jobGroup); |
| job.schedule(); |
| waitForCompletion(job); |
| // Job group should be in the ACTIVE state with the active child jobs. |
| assertEquals("1." + i, NUM_CHILD_JOBS, jobGroup.getActiveJobs().size()); |
| assertEquals("2." + i, JobGroup.ACTIVE, jobGroup.getState()); |
| // Cancel all the active child jobs and wait for them to finish. |
| for (Job activeJob : jobGroup.getActiveJobs()) { |
| activeJob.cancel(); |
| waitForCompletion(activeJob); |
| } |
| // Verify that the group does not contain any active jobs. |
| assertEquals("3." + i, 0, jobGroup.getActiveJobs().size()); |
| // Verify that the group will be in the ACTIVE state even when there are no active jobs |
| // and transitions to NONE state only after all the seed jobs are completed. |
| if (i < NUM_SEED_JOBS) { |
| assertEquals("4." + i, JobGroup.ACTIVE, jobGroup.getState()); |
| } else { |
| waitForCompletion(jobGroup); |
| assertEquals("4." + i, JobGroup.NONE, jobGroup.getState()); |
| } |
| } |
| } |
| |
| public void testSeedJobsWithRepeatingJobs() { |
| final int NUM_SEED_JOBS = 10; |
| final int REPEATING_COUNT = 5; |
| final JobGroup jobGroup = new JobGroup("JobGroup", 1, NUM_SEED_JOBS); |
| |
| RepeatingJob[] jobs = new RepeatingJob[NUM_SEED_JOBS]; |
| for (int i = 0; i < NUM_SEED_JOBS; i++) { |
| RepeatingJob job = new RepeatingJob("RepeatingJob", REPEATING_COUNT); |
| jobs[i] = job; |
| job.setJobGroup(jobGroup); |
| job.schedule(); |
| } |
| waitForCompletion(jobGroup); |
| for (int i = 0; i < NUM_SEED_JOBS; i++) { |
| // Verify that all the repeating jobs have run the expected number of times, |
| // which only happen when the job group treats the multiple executions of |
| // a repeating job as a single seed job. |
| assertEquals("1." + i, REPEATING_COUNT, jobs[i].getRunCount()); |
| } |
| } |
| |
| public void testCancel() { |
| final int NUM_JOBS = 20; |
| TestJob[] jobs = new TestJob[NUM_JOBS]; |
| // Create two different job groups. The cancel operation is performed and tested on the |
| // firstJobGroup. The secondJobGroup is used to make sure that the presence of a job group |
| // will not affect the working of another job group. |
| final JobGroup firstJobGroup = new JobGroup("FirstJobGroup", 1, NUM_JOBS / 2); |
| final JobGroup secondJobGroup = new JobGroup("SecondJobGroup", 1, NUM_JOBS / 2); |
| |
| for (int i = 0; i < NUM_JOBS; i++) { |
| // Assign half the jobs to the first group, the other half to the second group. |
| if (i % 2 == 0) { |
| jobs[i] = new TestJob("TestFirstJobGroup", 1000000, 10); |
| jobs[i].setJobGroup(firstJobGroup); |
| } else { |
| jobs[i] = new TestJob("TestSecondJobGroup", 1000000, 10); |
| jobs[i].setJobGroup(secondJobGroup); |
| } |
| jobs[i].schedule(); |
| } |
| |
| waitForStart(jobs[0]); |
| assertState("1.0", jobs[0], Job.RUNNING); |
| |
| waitForStart(jobs[1]); |
| assertState("2.0", jobs[1], Job.RUNNING); |
| |
| // Verify that the first two jobs are running and the rest are waiting. |
| for (int i = 2; i < NUM_JOBS; i++) { |
| assertState("1." + i, jobs[i], Job.WAITING); |
| } |
| |
| // Cancel the first group of jobs. |
| firstJobGroup.cancel(); |
| waitForCompletion(firstJobGroup); |
| |
| // Verify that the the previously running job is canceled and moved to NONE state. |
| assertState("2.0", jobs[0], Job.NONE); |
| |
| for (int i = 2; i < NUM_JOBS; i++) { |
| // Verify that all other jobs in the first group are also canceled and moved to NONE state. |
| if (jobs[i].getJobGroup() == firstJobGroup) { |
| 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); |
| } else { // Verify that all other jobs in the second groups are in the waiting state. |
| assertState("3." + i, jobs[i], Job.WAITING); |
| } |
| } |
| |
| // Cancel the second group of jobs. |
| secondJobGroup.cancel(); |
| waitForCompletion(secondJobGroup); |
| |
| // Verify that the running job from the second group is canceled to moved to NONE state. |
| assertState("7.0", jobs[1], Job.NONE); |
| |
| for (int i = 0; i < NUM_JOBS; i++) { |
| // Verify that all the jobs are canceled and moved to NONE state. |
| assertState("8." + i, jobs[i], Job.NONE); |
| } |
| } |
| |
| public void testGetActiveJobs() { |
| final int NUM_JOBS = 20; |
| final int JOBS_PER_GROUP = NUM_JOBS / 5; |
| TestJob[] jobs = new TestJob[NUM_JOBS]; |
| // Create five different job groups. |
| final JobGroup firstJobGroup = new JobGroup("FirstJobGroup", 1, JOBS_PER_GROUP); |
| final JobGroup secondJobGroup = new JobGroup("SecondJobGroup", 1, JOBS_PER_GROUP); |
| final JobGroup thirdJobGroup = new JobGroup("ThirdJobGroup", 1, JOBS_PER_GROUP); |
| final JobGroup fourthJobGroup = new JobGroup("FourthJobGrroup", 1, JOBS_PER_GROUP); |
| final JobGroup fifthJobGroup = new JobGroup("FifthJobGroup", 1, JOBS_PER_GROUP); |
| |
| for (int i = 0; i < NUM_JOBS; i++) { |
| if (i % 5 == 0) { |
| jobs[i] = new TestJob("TestFirstJobGroup", 1000000, 10); |
| jobs[i].setJobGroup(firstJobGroup); |
| } else if (i % 5 == 1) { |
| jobs[i] = new TestJob("TestSecondJobGroup", 1000000, 10); |
| jobs[i].setJobGroup(secondJobGroup); |
| } else if (i % 5 == 2) { |
| jobs[i] = new TestJob("TestThirdJobGroup", 1000000, 10); |
| jobs[i].setJobGroup(thirdJobGroup); |
| } else if (i % 5 == 3) { |
| jobs[i] = new TestJob("TestFourthJobGroup", 1000000, 10); |
| jobs[i].setJobGroup(fourthJobGroup); |
| } else { |
| jobs[i] = new TestJob("TestFifthJobGroup", 1000000, 10); |
| jobs[i].setJobGroup(fifthJobGroup); |
| } |
| jobs[i].schedule(); |
| } |
| |
| for (int i = 0; i < 5; i++) { |
| waitForStart(jobs[i]); |
| } |
| |
| // Try finding all jobs by supplying the NULL parameter. |
| // Note: Running the test framework may cause other system jobs to run, |
| // so check that the jobs started by this test are a subset of all running jobs. |
| HashSet<Job> testJobs = new HashSet<>(); |
| testJobs.addAll(Arrays.asList(jobs)); |
| Job[] allJobs = manager.find(null); |
| assertTrue("1.0", allJobs.length >= NUM_JOBS); |
| for (int i = 0; i < allJobs.length; i++) { |
| // Only test jobs that we know about. |
| if (testJobs.remove(allJobs[i])) { |
| JobGroup group = allJobs[i].getJobGroup(); |
| assertTrue("1." + i, (group == firstJobGroup || group == secondJobGroup || group == thirdJobGroup || group == fourthJobGroup || group == fifthJobGroup)); |
| } |
| } |
| assertEquals("1.2", 0, testJobs.size()); |
| |
| List<Job> activeJobs; |
| |
| // Try finding all jobs from the first job group. |
| activeJobs = firstJobGroup.getActiveJobs(); |
| assertEquals("2.0", 4, activeJobs.size()); |
| for (int i = 0; i < activeJobs.size(); i++) { |
| assertEquals("2." + (i + 1), firstJobGroup, activeJobs.get(i).getJobGroup()); |
| } |
| |
| // Try finding all jobs from the second job group. |
| activeJobs = secondJobGroup.getActiveJobs(); |
| assertEquals("3.0", 4, activeJobs.size()); |
| for (int i = 0; i < activeJobs.size(); i++) { |
| assertEquals("3." + (i + 1), secondJobGroup, activeJobs.get(i).getJobGroup()); |
| } |
| |
| // Try finding all jobs from the third job group. |
| activeJobs = thirdJobGroup.getActiveJobs(); |
| assertEquals("4.0", 4, activeJobs.size()); |
| for (int i = 0; i < activeJobs.size(); i++) { |
| assertEquals("4." + (i + 1), thirdJobGroup, activeJobs.get(i).getJobGroup()); |
| } |
| |
| // Try finding all jobs from the fourth job group. |
| activeJobs = fourthJobGroup.getActiveJobs(); |
| assertEquals("5.0", 4, activeJobs.size()); |
| for (int i = 0; i < activeJobs.size(); i++) { |
| assertEquals("5." + (i + 1), fourthJobGroup, activeJobs.get(i).getJobGroup()); |
| } |
| |
| // Try finding all jobs from the fifth job group. |
| activeJobs = fifthJobGroup.getActiveJobs(); |
| assertEquals("6.0", 4, activeJobs.size()); |
| for (int i = 0; i < activeJobs.size(); i++) { |
| assertEquals("6." + (i + 1), fifthJobGroup, activeJobs.get(i).getJobGroup()); |
| } |
| |
| // The first job should still be running. |
| for (int i = 0; i < 5; i++) { |
| assertState("7.0", jobs[i], Job.RUNNING); |
| } |
| |
| // Cancel the first job group. |
| firstJobGroup.cancel(); |
| waitForCompletion(firstJobGroup); |
| |
| // First job group should not contain any active jobs. |
| activeJobs = firstJobGroup.getActiveJobs(); |
| assertEquals("7.2", 0, activeJobs.size()); |
| |
| // Cancel the second job group. |
| secondJobGroup.cancel(); |
| waitForCompletion(secondJobGroup); |
| // Second job group should not contain any active jobs. |
| activeJobs = secondJobGroup.getActiveJobs(); |
| assertEquals("9.0", 0, activeJobs.size()); |
| |
| // Cancel the fourth job group. |
| fourthJobGroup.cancel(); |
| waitForCompletion(fourthJobGroup); |
| // Fourth job group should not contain any active jobs. |
| activeJobs = fourthJobGroup.getActiveJobs(); |
| assertEquals("9.1", 0, activeJobs.size()); |
| |
| // Finding all jobs by supplying the NULL parameter should return at least 8 jobs |
| // (4 from the 3rd family, and 4 from the 5th family) |
| // Note: Running the test framework may cause other system jobs to run, |
| // so check that the expected jobs started by this test are a subset of all running jobs. |
| testJobs.addAll(Arrays.asList(jobs)); |
| allJobs = manager.find(null); |
| assertTrue("11.0", allJobs.length >= 8); |
| for (int i = 0; i < allJobs.length; i++) { |
| // Only test jobs that we know about. |
| if (testJobs.remove(allJobs[i])) { |
| JobGroup group = allJobs[i].getJobGroup(); |
| assertTrue("11." + (i + 1), (group == thirdJobGroup || group == fifthJobGroup)); |
| } |
| } |
| |
| assertEquals("11.2", 12, testJobs.size()); |
| testJobs.clear(); |
| |
| // Cancel the fifth and third job groups. |
| fifthJobGroup.cancel(); |
| waitForCompletion(fifthJobGroup); |
| thirdJobGroup.cancel(); |
| waitForCompletion(thirdJobGroup); |
| |
| // Verify that all jobs are canceled and moved to NONE state |
| for (int i = 0; i < NUM_JOBS; i++) { |
| assertState("12." + i, jobs[i], Job.NONE); |
| } |
| |
| // Finding all jobs should return no jobs from our job groups. |
| // Note: Running the test framework may cause other system jobs to run, |
| // so check that there no jobs started by this test are present in all running jobs. |
| testJobs.addAll(Arrays.asList(jobs)); |
| allJobs = manager.find(null); |
| for (int i = 0; i < allJobs.length; i++) { |
| // Verify that no jobs that we know about are found (they should have all been removed) |
| if (testJobs.remove(allJobs[i])) { |
| assertTrue("14." + i, false); |
| } |
| } |
| assertEquals("15.0", NUM_JOBS, testJobs.size()); |
| testJobs.clear(); |
| } |
| |
| public void testJoinWithoutTimeout() { |
| 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 job groups. The join operation is performed and tested on the |
| // firstJobGroup. The secondJobGroup is used to make sure that the presence of a job group |
| // will not affect the working of another job group. |
| final JobGroup firstJobGroup = new JobGroup("FirstJobGroup", 1, NUM_JOBS / 2); |
| final JobGroup secondJobGroup = new JobGroup("SecondJobGroup", 1, NUM_JOBS / 2); |
| for (int i = 0; i < NUM_JOBS; i++) { |
| // Assign half the jobs to the first group, the other half to the second group. |
| if (i % 2 == 0) { |
| jobs[i] = new TestJob("TestFirstJobGroup", 10, 10); |
| jobs[i].setJobGroup(firstJobGroup); |
| jobs[i].schedule(1000000); |
| } else { |
| jobs[i] = new TestJob("TestSecondJobGroup", 1000000, 10); |
| jobs[i].setJobGroup(secondJobGroup); |
| 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; |
| firstJobGroup.join(0, null); |
| } catch (OperationCanceledException | InterruptedException e) { |
| // ignore |
| } |
| status[0] = TestBarrier.STATUS_DONE; |
| }); |
| |
| // Start the thread that will join the first group 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 |
| for (Job job : firstJobGroup.getActiveJobs()) { |
| job.wakeUp(); |
| } |
| |
| int i = 0; |
| for (; i < 100; i++) { |
| int currentStatus = status[0]; |
| List<Job> result = firstJobGroup.getActiveJobs(); |
| |
| // Verify that when the thread is complete then all jobs must be done. |
| if (currentStatus == TestBarrier.STATUS_DONE) { |
| assertEquals("1." + i, 0, result.size()); |
| break; |
| } |
| sleep(100); |
| } |
| assertTrue("2.0", i < 100); |
| |
| // Cancel the second job group. |
| secondJobGroup.cancel(); |
| waitForCompletion(secondJobGroup); |
| |
| // Verify that all the jobs are now in the NONE state. |
| for (int j = 0; j < NUM_JOBS; j++) { |
| assertState("3." + j, jobs[j], Job.NONE); |
| } |
| } |
| |
| public void testJoinWithTimeout() { |
| 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 job groups. The join operation is performed and tested on the |
| // firstJobGroup. The secondJobGroup is used to make sure that the presence of a job group |
| // will not affect the working of another job group. |
| final JobGroup firstJobGroup = new JobGroup("FirstJobGroup", 5, NUM_JOBS / 2); |
| final JobGroup secondJobGroup = new JobGroup("SecondJobGroup", 5, NUM_JOBS / 2); |
| for (int i = 0; i < NUM_JOBS; i++) { |
| // Assign half the jobs to the first group, the other half to the second group. |
| if (i % 2 == 0) { |
| jobs[i] = new TestJob("TestFirstGroup", 1000000, 10); |
| jobs[i].setJobGroup(firstJobGroup); |
| jobs[i].schedule(); |
| } else { |
| jobs[i] = new TestJob("TestSecondGroup", 1000000, 10); |
| jobs[i].setJobGroup(secondJobGroup); |
| jobs[i].schedule(); |
| } |
| |
| } |
| |
| final long timeout = 1000; |
| final long duration[] = {-1}; |
| |
| Thread t = new Thread(() -> { |
| status[0] = TestBarrier.STATUS_START; |
| try { |
| long start = System.currentTimeMillis(); |
| firstJobGroup.join(timeout, null); |
| duration[0] = System.currentTimeMillis() - start; |
| } catch (OperationCanceledException | InterruptedException e) { |
| // ignore |
| } |
| status[0] = TestBarrier.STATUS_DONE; |
| }); |
| |
| // Start the thread that will join the first job group and be blocked until the join call is returned. |
| t.start(); |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_START); |
| int i = 0; |
| for (; i < 11; i++) { |
| if (status[0] == TestBarrier.STATUS_DONE) { |
| // Verify that the join call is blocked for at least for the duration of given timeout. |
| assertTrue("1.0 duration: " + duration + " timeout: " + timeout, duration[0] >= timeout); |
| break; |
| } |
| sleep(100); |
| } |
| // Verify that the join call is returned is finished with in reasonable time of 1100 ms (given timeout + 100ms). |
| assertTrue("2.0", i < 11); |
| |
| // Cancel both job groups. |
| firstJobGroup.cancel(); |
| waitForCompletion(firstJobGroup); |
| secondJobGroup.cancel(); |
| waitForCompletion(secondJobGroup); |
| |
| // Verify that all the jobs are now in the NONE state. |
| for (int j = 0; j < NUM_JOBS; j++) { |
| assertState("3." + j, jobs[j], Job.NONE); |
| } |
| } |
| |
| /** |
| * Tests joining on a job group, and then canceling the jobs that are blocking the join call. |
| */ |
| public void testJoinWithCancelingJobs() { |
| 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 job groups. The join operation is performed and tested on the |
| // firstJobGroup. The secondJobGroup is used to make sure that the presence of a job group |
| // will not affect the working of another job group. |
| final JobGroup firstJobGroup = new JobGroup("FirstJobGroup", 1, NUM_JOBS / 2); |
| final JobGroup secondJobGroup = new JobGroup("SecondJobGroup", 1, NUM_JOBS / 2); |
| for (int i = 0; i < NUM_JOBS; i++) { |
| // Assign half the jobs to the first group, the other half to the second group. |
| if (i % 2 == 0) { |
| jobs[i] = new TestJob("TestFirstJobGroup", 1000000, 10); |
| jobs[i].setJobGroup(firstJobGroup); |
| } else { |
| jobs[i] = new TestJob("TestSecondJobGroup", 1000000, 10); |
| jobs[i].setJobGroup(secondJobGroup); |
| } |
| 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; |
| firstJobGroup.join(0, null); |
| } catch (OperationCanceledException | InterruptedException e) { |
| // ignore |
| } |
| status[0] = TestBarrier.STATUS_DONE; |
| }); |
| |
| // Start the thread that will join the first job group. It will be blocked |
| // until all jobs in the first group 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); |
| assertEquals("2.1", TestBarrier.STATUS_RUNNING, status[0]); |
| |
| // Cancel the first job group. The join call should be unblocked when |
| // all the jobs are canceled. |
| firstJobGroup.cancel(); |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_DONE); |
| |
| // Verify that there are no active jobs in the the first group. |
| assertEquals("2.2", 0, firstJobGroup.getActiveJobs().size()); |
| |
| // Cancel the second job group. |
| secondJobGroup.cancel(); |
| waitForCompletion(secondJobGroup); |
| |
| // Verify that all the jobs are now in the NONE state. |
| for (int j = 0; j < NUM_JOBS; j++) { |
| assertState("3." + j, jobs[j], Job.NONE); |
| } |
| } |
| |
| /** |
| * Tests joining on a job group, and then canceling the monitor. |
| */ |
| public void testJoinWithCancelingMonitor() { |
| 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 canceler = new FussyProgressMonitor(); |
| // Create two different job groups. The join operation is performed and tested on the |
| // firstJobGroup. The secondJobGroup is used to make sure that the presence of a job group |
| // will not affect the working of another job group. |
| final JobGroup firstJobGroup = new JobGroup("FirstJobGroup", 1, NUM_JOBS / 2); |
| final JobGroup secondJobGroup = new JobGroup("SecondJobGroup", 1, NUM_JOBS / 2); |
| for (int i = 0; i < NUM_JOBS; i++) { |
| // Assign half the jobs to the first group, the other half to the second group. |
| if (i % 2 == 0) { |
| jobs[i] = new TestJob("TestFirstJobGroup", 1000000, 10); |
| jobs[i].setJobGroup(firstJobGroup); |
| } else { |
| jobs[i] = new TestJob("TestSecondJobGroup", 1000000, 10); |
| jobs[i].setJobGroup(secondJobGroup); |
| } |
| 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; |
| firstJobGroup.join(0, canceler); |
| } catch (OperationCanceledException | InterruptedException e) { |
| // ignore |
| } |
| status[0] = TestBarrier.STATUS_DONE; |
| }); |
| |
| // Start the thread that will join the first job group. It will be blocked |
| // until the monitor is 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); |
| assertEquals("2.1", TestBarrier.STATUS_RUNNING, status[0]); |
| |
| // Cancel the monitor that is attached to the join call. |
| canceler.setCanceled(true); |
| TestBarrier.waitForStatus(status, 0, TestBarrier.STATUS_DONE); |
| |
| // The first job in the first group should still be running. |
| assertState("2.2", jobs[0], Job.RUNNING); |
| assertEquals("2.3", TestBarrier.STATUS_DONE, status[0]); |
| assertTrue("2.4", firstJobGroup.getActiveJobs().size() > 0); |
| |
| // Cancel both job groups. |
| secondJobGroup.cancel(); |
| waitForCompletion(secondJobGroup); |
| firstJobGroup.cancel(); |
| waitForCompletion(firstJobGroup); |
| |
| // Verify that all the jobs are now in the NONE state. |
| for (int j = 0; j < NUM_JOBS; j++) { |
| assertState("3." + j, jobs[j], Job.NONE); |
| } |
| } |
| |
| /** |
| * Tests joining a job that repeats in a loop. |
| */ |
| public void testJoinWithRepeatingJobs() { |
| JobGroup jobGroup = new JobGroup("JobGroup", 1, 1); |
| int count = 25; |
| RepeatingJob job = new RepeatingJob("RepeatingJob", count); |
| job.setJobGroup(jobGroup); |
| job.schedule(); |
| try { |
| jobGroup.join(0, null); |
| } catch (OperationCanceledException e) { |
| fail("1.0", e); |
| } catch (InterruptedException e) { |
| fail("1.1", e); |
| } |
| // Verify that the job has run the expected number of times. |
| assertEquals("1.2", count, job.getRunCount()); |
| } |
| |
| /** |
| * Tests that joining a job from another job that is in the same job group |
| * yields an IllegalStateException. |
| */ |
| public void testJoiningAJobInTheSameJobGroupFails() { |
| JobGroup jobGroup = new JobGroup("JobGroup", 2, 2); |
| final TestJob firstJob = new TestJob("FirstJob", 1000000, 10); |
| firstJob.setJobGroup(jobGroup); |
| firstJob.schedule(); |
| waitForStart(firstJob); |
| |
| final boolean joinFailed[] = {false}; |
| Job secondJob = new Job("SecondJob") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| try { |
| firstJob.join(); |
| } catch (InterruptedException ie) { |
| // ignore |
| } catch (IllegalStateException ise) { |
| // expected |
| joinFailed[0] = true; |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| secondJob.setJobGroup(jobGroup); |
| secondJob.schedule(); |
| waitForCompletion(secondJob); |
| assertEquals("1.0", true, joinFailed[0]); |
| |
| firstJob.cancel(); |
| waitForCompletion(jobGroup); |
| } |
| |
| /** |
| * Tests that the progress is reported on the monitor used for join. |
| */ |
| public void testJoinWithProgressMonitor() { |
| final int NUM_JOBS = 100; |
| JobGroup jobGroup = new JobGroup("JobGroup", 10, NUM_JOBS); |
| final TestBarrier barrier = new TestBarrier(); |
| for (int i = 0; i < NUM_JOBS; i++) { |
| TestJob testJob = new TestJob("TestJob", 10, 10) { |
| @Override |
| public IStatus run(IProgressMonitor monitor) { |
| barrier.waitForStatus(TestBarrier.STATUS_START); |
| return super.run(monitor); |
| } |
| }; |
| testJob.setJobGroup(jobGroup); |
| testJob.schedule(); |
| } |
| FussyProgressMonitor monitor = new FussyProgressMonitor(); |
| barrier.setStatus(TestBarrier.STATUS_START); |
| try { |
| jobGroup.join(0, monitor); |
| } catch (OperationCanceledException | InterruptedException e) { |
| // ignore |
| } |
| // Check the progress reporting on monitor. |
| monitor.sanityCheck(); |
| monitor.assertUsedUp(); |
| } |
| |
| /** |
| * Tested scenario: |
| * - Create and add a WaitingJob to the JobGroup and schedule it when the job manager is suspended |
| * - Join on the JobGroup when the job manager is suspended |
| * |
| * Expected result: |
| * The join call on the JobGroup should not wait for the WaitingJob as the WaitingJob is not going |
| * to be executed when the job manger is suspended. |
| */ |
| public void testJoinWithJobManagerSuspended_1() throws InterruptedException { |
| final JobGroup jobGroup = new JobGroup("JobGroup", 1, 1); |
| final TestBarrier barrier = new TestBarrier(); |
| final int[] groupJobsCount = new int[] {-1}; |
| final TestJob waiting = new TestJob("WaitingJob", 1000000, 10); |
| waiting.setJobGroup(jobGroup); |
| final TestJob running = new TestJob("RunningJob", 200, 10); |
| running.setJobGroup(jobGroup); |
| Job job = new Job("MainJob") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| barrier.setStatus(TestBarrier.STATUS_START); |
| try { |
| running.schedule(); |
| // Wait until the running job is actually running. |
| waitForStart(running); |
| // Suspend before join. |
| manager.suspend(); |
| waiting.schedule(); |
| running.join(); |
| jobGroup.join(0, null); |
| groupJobsCount[0] = jobGroup.getActiveJobs().size(); |
| } catch (InterruptedException e) { |
| // ignore |
| } finally { |
| // clean up |
| waiting.cancel(); |
| try { |
| waiting.join(); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| manager.resume(); |
| } |
| barrier.setStatus(TestBarrier.STATUS_DONE); |
| return Status.OK_STATUS; |
| } |
| }; |
| try { |
| job.schedule(); |
| barrier.waitForStatus(TestBarrier.STATUS_DONE); |
| assertEquals(1, groupJobsCount[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(); |
| } |
| } |
| |
| /** |
| * Tested scenario: |
| * - Join on the JobGroup when the job manager is NOT suspended |
| * - Create and add a WaitingJob to the JobGroup and schedule it when the job manager is suspended |
| * |
| * Expected result: |
| * The join call on the JobGroup should not wait for the WaitingJob as the WaitingJob is not going |
| * to be executed when the job manger is suspended. |
| */ |
| public void testJoinWithJobManagerSuspended_2() throws InterruptedException { |
| final JobGroup jobGroup = new JobGroup("JobGroup", 1, 1); |
| final TestBarrier barrier = new TestBarrier(); |
| final int[] groupJobsCount = new int[] {-1}; |
| final TestJob waiting = new TestJob("WaitingJob", 1000000, 10); |
| waiting.setJobGroup(jobGroup); |
| final TestJob running = new TestJob("RunningJob", 1000000, 10); |
| running.setJobGroup(jobGroup); |
| |
| final Thread t = new Thread(() -> { |
| barrier.setStatus(TestBarrier.STATUS_RUNNING); |
| try { |
| jobGroup.join(0, null); |
| } catch (OperationCanceledException | InterruptedException e) { |
| // ignore |
| } |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| }); |
| Job job = new Job("MainJob") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| barrier.setStatus(TestBarrier.STATUS_START); |
| try { |
| running.schedule(); |
| // wait until the running job is actually running |
| waitForStart(running); |
| t.start(); |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| // suspend before scheduling new job |
| manager.suspend(); |
| waiting.schedule(); |
| running.cancel(); |
| barrier.waitForStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| groupJobsCount[0] = jobGroup.getActiveJobs().size(); |
| barrier.setStatus(TestBarrier.STATUS_DONE); |
| } finally { |
| // clean up |
| waiting.cancel(); |
| try { |
| waiting.join(); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| manager.resume(); |
| } |
| return Status.OK_STATUS; |
| } |
| }; |
| try { |
| job.schedule(); |
| barrier.waitForStatus(TestBarrier.STATUS_DONE); |
| assertEquals(1, groupJobsCount[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(); |
| } |
| } |
| |
| /** |
| * Tested scenario: |
| * - Join on the JobGroup when the job manager is NOT suspended |
| * - Create and add a WaitingJob to the JobGroup and schedule it when the job manager is suspended |
| * - Resume the job manager which causes the waiting job to start |
| * |
| * Expected result: |
| * The join call on the JobGroup should wait for the WaitingJob as the WaitingJob was started |
| * to execute before the join ended. |
| */ |
| public void testJoinWithJobManagerSuspended_3() throws InterruptedException { |
| final JobGroup jobGroup = new JobGroup("JobGroup", 1, 1); |
| final TestBarrier barrier = new TestBarrier(); |
| final int[] groupJobsCount = new int[] {-1}; |
| final TestJob waiting = new TestJob("waiting job", 1000000, 10); |
| waiting.setJobGroup(jobGroup); |
| final TestJob running = new TestJob("running job", 1000000, 10); |
| running.setJobGroup(jobGroup); |
| |
| final Thread t = new Thread(() -> { |
| barrier.setStatus(TestBarrier.STATUS_RUNNING); |
| try { |
| jobGroup.join(0, null); |
| } catch (OperationCanceledException | InterruptedException e) { |
| // ignore |
| } |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| }); |
| Job job = new Job("MainJob") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| barrier.setStatus(TestBarrier.STATUS_START); |
| |
| running.schedule(); |
| // Wait until the running job is actually running. |
| waitForStart(running); |
| // Start the thread to make join call. |
| t.start(); |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| // Suspend before scheduling the waiting job. |
| manager.suspend(); |
| waiting.schedule(); |
| manager.resume(); |
| running.cancel(); |
| waitForStart(waiting); |
| waiting.cancel(); |
| barrier.waitForStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| groupJobsCount[0] = jobGroup.getActiveJobs().size(); |
| barrier.setStatus(TestBarrier.STATUS_DONE); |
| |
| return Status.OK_STATUS; |
| } |
| }; |
| try { |
| job.schedule(); |
| barrier.waitForStatus(TestBarrier.STATUS_DONE); |
| assertEquals(0, groupJobsCount[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(); |
| } |
| } |
| |
| /** |
| * Tested scenario: |
| * - Add a failing job to the JobGroup between passing jobs. |
| * |
| * Expected result: |
| * The JobGroup should be canceled when the failing job is completed, because by default |
| * a job group is canceled when a job belonging to the group is failed. |
| */ |
| public void testShouldCancel_1() { |
| final int NUM_SEED_JOBS = 10; |
| final int NUM_ADDITIONAL_JOBs = 10; |
| final Job jobs[] = new Job[NUM_SEED_JOBS + NUM_ADDITIONAL_JOBs]; |
| final JobGroup jobGroup = new JobGroup("JobGroup", NUM_SEED_JOBS, NUM_SEED_JOBS); |
| for (int i = 0; i < NUM_SEED_JOBS - 1; i++) { |
| TestJob job = new TestJob("TestJob", 1000000, 10); |
| jobs[i] = job; |
| job.setJobGroup(jobGroup); |
| job.schedule(); |
| waitForStart(job); |
| } |
| // Verify that all the test jobs are running. |
| assertEquals("1.0", JobGroup.ACTIVE, jobGroup.getState()); |
| assertEquals("1.1", NUM_SEED_JOBS - 1, jobGroup.getActiveJobs().size()); |
| for (int i = 0; i < NUM_SEED_JOBS - 1; i++) { |
| assertState("2." + i, jobs[i], Job.RUNNING); |
| } |
| |
| final TestBarrier barrier = new TestBarrier(); |
| Job failedJob = new Job("FailedJob") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_RUN); |
| barrier.waitForStatus(TestBarrier.STATUS_RUNNING); |
| return new Status(IStatus.ERROR, "org.eclipse.core.jobs", "Error"); |
| } |
| }; |
| jobs[NUM_SEED_JOBS - 1] = failedJob; |
| failedJob.setJobGroup(jobGroup); |
| failedJob.schedule(); |
| barrier.waitForStatus(TestBarrier.STATUS_WAIT_FOR_RUN); |
| |
| // Verify that the failing job also started running. |
| assertEquals("3.0", NUM_SEED_JOBS, jobGroup.getActiveJobs().size()); |
| assertState("3.1", failedJob, Job.RUNNING); |
| |
| for (int i = NUM_SEED_JOBS; i < NUM_SEED_JOBS + NUM_ADDITIONAL_JOBs; i++) { |
| Job job = new TestJob("AdditionalJob", 1000000, 10); |
| jobs[i] = job; |
| job.setJobGroup(jobGroup); |
| job.schedule(); |
| } |
| |
| // Verify that all the jobs are active. |
| assertEquals("4.0", NUM_SEED_JOBS + NUM_ADDITIONAL_JOBs, jobGroup.getActiveJobs().size()); |
| for (int i = NUM_SEED_JOBS; i < NUM_SEED_JOBS + NUM_ADDITIONAL_JOBs; i++) { |
| assertState("5." + i, jobs[i], Job.WAITING); |
| } |
| // Allow the failing job to complete. |
| barrier.setStatus(TestBarrier.STATUS_RUNNING); |
| // wait for the job group to complete. |
| waitForCompletion(jobGroup); |
| // Verify that all the jobs are moved to NONE state. Also verify that the failing job failed, |
| // other running jobs got canceled and the waiting jobs are never allowed to run. |
| for (int i = 0; i < NUM_SEED_JOBS + NUM_ADDITIONAL_JOBs; i++) { |
| assertState("6." + i, jobs[i], Job.NONE); |
| if (i < NUM_SEED_JOBS - 1) { |
| assertEquals("6." + i, IStatus.CANCEL, jobs[i].getResult().getSeverity()); |
| } else if (i == NUM_SEED_JOBS - 1) { |
| assertEquals("6." + i, IStatus.ERROR, jobs[i].getResult().getSeverity()); |
| } else if (i == NUM_SEED_JOBS) { |
| // This job might have been started running before the group gets canceled. |
| IStatus result = jobs[i].getResult(); |
| if (result != null) { |
| assertEquals("6." + i, IStatus.CANCEL, result.getSeverity()); |
| } |
| } else { |
| assertEquals("6." + i, null, jobs[i].getResult()); |
| } |
| } |
| } |
| |
| /** |
| * Tested scenario: |
| * - Record the number of times the JobGroup.shouldCancel method is invoked. |
| * |
| * Expected result: |
| * The shouldCancel method of the JobGroup should be called after the completion of every job |
| * belonging to that group except the last one (shouldCancel method is not called after the |
| * completion of the last job in the jobGroup as there are no jobs left to cancel). |
| */ |
| public void testShouldCancel_2() { |
| final int NUM_JOBS = 10; |
| final int numShouldCancelCalled[] = {0}; |
| final JobGroup jobGroup = new JobGroup("JobGroup", 1, NUM_JOBS) { |
| @Override |
| protected boolean shouldCancel(IStatus lastCompletedJobResult, int numberOfFailedJobs, int numberOfCanceledJobs) { |
| numShouldCancelCalled[0]++; |
| return super.shouldCancel(lastCompletedJobResult, numberOfFailedJobs, numberOfCanceledJobs); |
| } |
| }; |
| for (int i = 0; i < NUM_JOBS; i++) { |
| Job job = new TestJob("TestJob", 10, 10); |
| job.setJobGroup(jobGroup); |
| job.schedule(); |
| } |
| waitForCompletion(jobGroup); |
| assertEquals("1.0", NUM_JOBS - 1, numShouldCancelCalled[0]); |
| } |
| |
| /** |
| * Tested scenario: |
| * - Record the number of times the JobGroup.shouldCancel method is invoked and all the values passed to it |
| * - Always return false from the shouldCancel method of the JobGroup to avoid the group cancellation due to failed jobs |
| * |
| * Expected result: |
| * The shouldCancel method of the JobGroup should be called with appropriate values after |
| * the completion of every job that belongs to that group except the last one |
| * (the shouldCancel method is not called after the completion of the last job in the |
| * jobGroup as there are no jobs left to cancel). |
| */ |
| public void testShouldCancel_3() { |
| final int status[] = {IStatus.OK, IStatus.INFO, IStatus.WARNING, IStatus.ERROR, IStatus.CANCEL, IStatus.OK}; |
| final int numShouldCancelCalled[] = {0}; |
| final int failedJobsCount[] = {0}; |
| final int canceledJobsCount[] = {0}; |
| final IStatus completedJobResult[] = new Status[1]; |
| final TestBarrier barrier = new TestBarrier(); |
| |
| final JobGroup jobGroup = new JobGroup("JobGroup", 1, status.length) { |
| @Override |
| protected boolean shouldCancel(IStatus lastCompletedJobResult, int numberOfFailedJobs, int numberOfCanceledJobs) { |
| numShouldCancelCalled[0]++; |
| failedJobsCount[0] = numberOfFailedJobs; |
| canceledJobsCount[0] = numberOfCanceledJobs; |
| completedJobResult[0] = lastCompletedJobResult; |
| barrier.setStatus(TestBarrier.STATUS_DONE); |
| return false; |
| } |
| }; |
| |
| for (int i = 0; i < status.length; i++) { |
| final int jobNumber = i; |
| final IStatus returnedStatus[] = new IStatus[1]; |
| Job job = new TestJob("TestJob", 10, 10) { |
| @Override |
| public IStatus run(IProgressMonitor monitor) { |
| super.run(monitor); |
| returnedStatus[0] = new Status(status[jobNumber], "org.eclipse.core.jobs", "Job " + jobNumber); |
| return returnedStatus[0]; |
| } |
| }; |
| job.setJobGroup(jobGroup); |
| barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE); |
| job.schedule(); |
| |
| // shouldCancel method will not be invoked for the last job. |
| if (i == status.length - 1) { |
| continue; |
| } |
| |
| barrier.waitForStatus(TestBarrier.STATUS_DONE); |
| // Verify that the shouldCancel method is called with appropriate values. |
| assertEquals("1." + i, i + 1, numShouldCancelCalled[0]); |
| assertEquals("2." + i, returnedStatus[0], completedJobResult[0]); |
| if (i < 3) { |
| assertEquals("3." + i, 0, failedJobsCount[0]); |
| } else { |
| assertEquals("3." + i, 1, failedJobsCount[0]); |
| } |
| if (i < 4) { |
| assertEquals("4." + i, 0, canceledJobsCount[0]); |
| } else { |
| assertEquals("4." + i, 1, canceledJobsCount[0]); |
| } |
| } |
| waitForCompletion(jobGroup); |
| } |
| |
| /** |
| * Tested scenario: |
| * - JobGroup.shouldCancel returns true after certain jobs are completed. |
| * |
| * Expected result: |
| * The remaining jobs are canceled in a reasonable time after the shouldCancel method of the |
| * JobGroup returns true. |
| */ |
| public void testShouldCancel_4() { |
| final int NUM_JOBS = 1000; |
| final int NUM_JOBS_LIMIT = 100; |
| final int numShouldCancelCalled[] = {0}; |
| final TestBarrier barrier = new TestBarrier(); |
| final JobGroup jobGroup = new JobGroup("JobGroup", 10, NUM_JOBS) { |
| @Override |
| protected boolean shouldCancel(IStatus lastCompletedJobResult, int numberOfFailedJobs, int numberOfCanceledJobs) { |
| numShouldCancelCalled[0]++; |
| if (numShouldCancelCalled[0] == NUM_JOBS_LIMIT) { |
| return true; |
| } |
| return false; |
| } |
| }; |
| for (int i = 0; i < NUM_JOBS; i++) { |
| final int jobNumber = i; |
| Job job = new TestJob("TestJob", 10, 10) { |
| @Override |
| public IStatus run(IProgressMonitor monitor) { |
| barrier.waitForStatus(TestBarrier.STATUS_START); |
| super.run(monitor); |
| return new Status(IStatus.INFO, "org.eclipse.core.jobs", "Job " + jobNumber); |
| } |
| }; |
| job.setJobGroup(jobGroup); |
| job.schedule(); |
| } |
| // Allow the jobs to proceed to run. |
| barrier.setStatus(TestBarrier.STATUS_START); |
| waitForCompletion(jobGroup); |
| assertTrue("1.0", numShouldCancelCalled[0] >= NUM_JOBS_LIMIT); |
| // Verify that the group is canceled in a reasonable time, |
| // i.e only 10 jobs are allowed to run after the shouldCancel method returned true. |
| assertTrue("2.0", numShouldCancelCalled[0] < NUM_JOBS_LIMIT + 10); |
| } |
| |
| public void testDefaultComputeGroupResult() { |
| final int status[] = {IStatus.OK, IStatus.INFO, IStatus.WARNING, IStatus.ERROR, IStatus.CANCEL}; |
| final JobGroup jobGroup = new JobGroup("JobGroup", 1, status.length) { |
| @Override |
| protected boolean shouldCancel(IStatus lastCompletedJobResult, int numberOfFailedJobs, int numberOfCanceledJobs) { |
| // Return false always so that the group will not be canceled due to failed jobs. |
| return false; |
| } |
| }; |
| |
| for (int i = 0; i < status.length; i++) { |
| final int jobNumber = i; |
| Job job = new TestJob("TestJob", 10, 10) { |
| @Override |
| public IStatus run(IProgressMonitor monitor) { |
| super.run(monitor); |
| return new Status(status[jobNumber], "org.eclipse.core.jobs", "Job " + jobNumber); |
| } |
| }; |
| job.setJobGroup(jobGroup); |
| job.schedule(); |
| } |
| waitForCompletion(jobGroup); |
| IStatus[] jobResults = jobGroup.getResult().getChildren(); |
| // Verify that the group result contains all the job results except the OK statuses. |
| assertEquals("1.0", status.length - 1, jobResults.length); |
| for (int i = 1; i < status.length; i++) { |
| assertEquals("2." + i, status[i], jobResults[i - 1].getSeverity()); |
| } |
| } |
| |
| public void testCustomComputeGroupResult() { |
| final MultiStatus returnedGroupResult[] = new MultiStatus[1]; |
| final IStatus originalJobResults[][] = {new IStatus[0]}; |
| final int status[] = {IStatus.OK, IStatus.INFO, IStatus.WARNING, IStatus.ERROR, IStatus.CANCEL}; |
| final JobGroup jobGroup = new JobGroup("group", 1, status.length) { |
| @Override |
| protected boolean shouldCancel(IStatus lastCompletedJobResult, int numberOfFailedJobs, int numberOfCanceledJobs) { |
| return false; |
| } |
| |
| @Override |
| protected MultiStatus computeGroupResult(List<IStatus> jobResults) { |
| // Record the original job results and return a dummy groupresult. |
| originalJobResults[0] = jobResults.toArray(new IStatus[jobResults.size()]); |
| returnedGroupResult[0] = new MultiStatus("org.eclipse.core.jobs", 0, new IStatus[0], "custom result", null); |
| return returnedGroupResult[0]; |
| } |
| }; |
| |
| for (int i = 0; i < status.length; i++) { |
| final int jobNumber = i; |
| Job job = new TestJob("TestJob", 10, 10) { |
| @Override |
| public IStatus run(IProgressMonitor monitor) { |
| super.run(monitor); |
| return new Status(status[jobNumber], "org.eclipse.core.jobs", "Job " + jobNumber); |
| } |
| }; |
| job.setJobGroup(jobGroup); |
| job.schedule(); |
| } |
| waitForCompletion(jobGroup); |
| // Verify that the compute group result is called with all the completed job results. |
| assertEquals("2.0", status.length, originalJobResults[0].length); |
| for (int i = 0; i < status.length; i++) { |
| assertEquals("3." + i, status[i], originalJobResults[0][i].getSeverity()); |
| } |
| // Verify that JobGroup.getResult returns the status returned by JobGroup.computeGroupResult method. |
| assertEquals("4.0", returnedGroupResult[0], jobGroup.getResult()); |
| } |
| |
| // https://bugs.eclipse.org/461621 |
| public void testSlowComputeGroupResult() throws Exception { |
| final JobGroup jobGroup = new JobGroup("group", 1, 1) { |
| @Override |
| protected MultiStatus computeGroupResult(List<IStatus> jobResults) { |
| sleep(500); |
| return new MultiStatus("org.eclipse.core.jobs", 0, new IStatus[0], "custom result", null); |
| } |
| }; |
| |
| Job job = new Job("TestJob") { |
| @Override |
| public IStatus run(IProgressMonitor monitor) { |
| return Status.OK_STATUS; |
| } |
| }; |
| job.setJobGroup(jobGroup); |
| job.schedule(); |
| waitForCompletion(job, 100); |
| |
| boolean completed = jobGroup.join(1000, null); |
| assertTrue("2.0", completed); |
| MultiStatus result = jobGroup.getResult(); |
| assertNotNull("3.0", result); |
| } |
| |
| /** |
| * Tests that job groups work fine with normal jobs that are not belonging to any group. |
| */ |
| public void testJobGroupAlongWithNormalJobs() { |
| final int NUM_GROUP_JOBS = 1000; |
| final int NUM_NORMAL_JOBS = 100; |
| JobGroup jobGroup = new JobGroup("JobGroup", 1, NUM_GROUP_JOBS); |
| for (int i = 0; i < NUM_GROUP_JOBS; i++) { |
| TestJob testJob = new TestJob("GroupJob", 1000000, 10); |
| testJob.setJobGroup(jobGroup); |
| testJob.schedule(); |
| } |
| assertEquals("1.0", JobGroup.ACTIVE, jobGroup.getState()); |
| assertEquals("2.0", NUM_GROUP_JOBS, jobGroup.getActiveJobs().size()); |
| |
| TestJob normalJobs[] = new TestJob[NUM_NORMAL_JOBS]; |
| for (int i = 0; i < NUM_NORMAL_JOBS; i++) { |
| TestJob testJob = new TestJob("NormalJob", 10, 10); |
| normalJobs[i] = testJob; |
| testJob.schedule(); |
| } |
| for (int i = 0; i < NUM_NORMAL_JOBS; i++) { |
| waitForCompletion(normalJobs[i]); |
| } |
| |
| // Tests that the normal jobs are completed fine while the group jobs are still running. |
| assertEquals("3.0", JobGroup.ACTIVE, jobGroup.getState()); |
| assertEquals("4.0", NUM_GROUP_JOBS, jobGroup.getActiveJobs().size()); |
| jobGroup.cancel(); |
| waitForCompletion(jobGroup); |
| } |
| |
| /** |
| * Tests that the JobManager publishes a final job group status to IJobChangeListeners. |
| */ |
| public void testJobManagerPublishesJobGroupResults() { |
| final int NUM_GROUP_JOBS = 3; |
| final String GROUP_NAME = "TestJobGroup"; |
| final JobGroup jobGroup = new JobGroup(GROUP_NAME, 1, NUM_GROUP_JOBS); |
| |
| // Record job completion events for all jobs in this job group. |
| final List<IJobChangeEvent> events = new ArrayList<>(NUM_GROUP_JOBS); |
| IJobChangeListener listener = new JobChangeAdapter() { |
| @Override |
| public void done(IJobChangeEvent event) { |
| if (event.getJob().getJobGroup() == jobGroup) { |
| events.add(event); |
| } |
| } |
| }; |
| manager.addJobChangeListener(listener); |
| |
| // Execute all jobs in the job group and validate that the last job includes the job group result. |
| try { |
| for (int i = 0; i < NUM_GROUP_JOBS; i++) { |
| TestJob testJob = new TestJob("GroupJob", 10, 1); |
| testJob.setJobGroup(jobGroup); |
| testJob.schedule(); |
| } |
| waitForCompletion(jobGroup); |
| |
| assertEquals("Should have seen as many job completion events as the count of jobs in the job group.", NUM_GROUP_JOBS, events.size()); |
| for (int i = 0; i < NUM_GROUP_JOBS; i++) { |
| IJobChangeEvent event = events.get(i); |
| assertNotNull("All job completion events should have a job status.", event.getResult()); |
| if (i < NUM_GROUP_JOBS - 1) { |
| assertNull("Only the last job competion event shoud have a job group status.", event.getJobGroupResult()); |
| } else { |
| assertNotNull("The last job competion event shoud have a job group status.", event.getJobGroupResult()); |
| } |
| } |
| } finally { |
| manager.removeJobChangeListener(listener); |
| } |
| } |
| |
| private 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); |
| } |
| } |
| |
| 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"; |
| } |
| |
| private void waitForStart(TestJob job) { |
| int i = 0; |
| while (job.getRunCount() < 1) { |
| Thread.yield(); |
| sleep(100); |
| Thread.yield(); |
| // Sanity test to avoid hanging tests. |
| if (i++ >= 100) { |
| dumpState(); |
| fail("Timeout waiting for job to start. Job: " + job + ", state: " + job.getState()); |
| } |
| } |
| } |
| |
| private void waitForCompletion(JobGroup jobGroup) { |
| int i = 0; |
| while (jobGroup.getState() != JobGroup.NONE) { |
| Thread.yield(); |
| sleep(100); |
| Thread.yield(); |
| // Sanity test to avoid hanging tests. |
| if (i++ >= 100) { |
| dumpState(); |
| fail("Timeout waiting for job group " + jobGroup.getName() + " to be completed"); |
| } |
| } |
| } |
| } |