package com.path.android.jobqueue.test.jobqueue; import com.path.android.jobqueue.JobHolder; import com.path.android.jobqueue.JobManager; import com.path.android.jobqueue.JobQueue; import com.path.android.jobqueue.Params; import com.path.android.jobqueue.test.TestBase; import com.path.android.jobqueue.test.jobs.DummyJob; import com.path.android.jobqueue.test.util.JobQueueFactory; import org.fest.reflect.core.*; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.*; import org.junit.Ignore; import org.junit.Test; import java.util.Arrays; import java.util.concurrent.TimeUnit; @Ignore public abstract class JobQueueTestBase extends TestBase { JobQueueFactory currentFactory; public JobQueueTestBase(JobQueueFactory factory) { currentFactory = factory; } @Test public void testBasicAddRemoveCount() throws Exception { final int ADD_COUNT = 6; JobQueue jobQueue = createNewJobQueue(); assertThat((int) jobQueue.count(), equalTo(0)); assertThat(jobQueue.nextJobAndIncRunCount(true, null), nullValue()); for (int i = 0; i < ADD_COUNT; i++) { JobHolder holder = createNewJobHolder(); jobQueue.insert(holder); assertThat((int) jobQueue.count(), equalTo(i + 1)); assertThat(holder.getId(), notNullValue()); jobQueue.insertOrReplace(holder); assertThat((int) jobQueue.count(), equalTo(i + 1)); } JobHolder firstHolder = jobQueue.nextJobAndIncRunCount(true, null); assertThat(firstHolder.getRunCount(), equalTo(1)); //size should be down 1 assertThat((int) jobQueue.count(), equalTo(ADD_COUNT - 1)); //should return another job JobHolder secondHolder = jobQueue.nextJobAndIncRunCount(true, null); assertThat(secondHolder.getRunCount(), equalTo(1)); //size should be down 2 assertThat((int) jobQueue.count(), equalTo(ADD_COUNT - 2)); //second holder and first holder should have different ids assertThat(firstHolder.getId(), not(secondHolder.getId())); jobQueue.remove(secondHolder); assertThat((int) jobQueue.count(), equalTo(ADD_COUNT - 2)); jobQueue.remove(secondHolder); //non existed job removed, count should be the same assertThat((int) jobQueue.count(), equalTo(ADD_COUNT - 2)); jobQueue.remove(firstHolder); assertThat((int) jobQueue.count(), equalTo(ADD_COUNT - 2)); } @Test public void testPriority() throws Exception { int JOB_LIMIT = 20; JobQueue jobQueue = createNewJobQueue(); //create and add JOB_LIMIT jobs with random priority for (int i = 0; i < JOB_LIMIT; i++) { jobQueue.insert(createNewJobHolder(new Params((int) (Math.random() * 10)))); } //ensure we get jobs in correct priority order int minPriority = Integer.MAX_VALUE; for (int i = 0; i < JOB_LIMIT; i++) { JobHolder holder = jobQueue.nextJobAndIncRunCount(true, null); assertThat(holder.getPriority() <= minPriority, is(true)); } assertThat(jobQueue.nextJobAndIncRunCount(true, null), nullValue()); } @Test public void testDelayUntilWithPriority() throws Exception { JobQueue jobQueue = createNewJobQueue(); long now = System.nanoTime(); JobHolder lowPriorityHolder = createNewJobHolderWithDelayUntil(new Params(5), now + 10000 * JobManager.NS_PER_MS); JobHolder highPriorityHolder = createNewJobHolderWithDelayUntil(new Params(10), now + 20000 * JobManager.NS_PER_MS); jobQueue.insert(lowPriorityHolder); jobQueue.insert(highPriorityHolder); assertThat("when asked, if lower priority job has less delay until, we should return it", jobQueue.getNextJobDelayUntilNs(true), equalTo(lowPriorityHolder.getDelayUntilNs())); } @Test public void testGroupId() throws Exception { JobQueue jobQueue = createNewJobQueue(); long jobId1 = jobQueue.insert(createNewJobHolder(new Params(0).groupBy("group1"))); long jobId2 = jobQueue.insert(createNewJobHolder(new Params(0).groupBy("group1"))); long jobId3 = jobQueue.insert(createNewJobHolder(new Params(0).groupBy("group2"))); long jobId4 = jobQueue.insert(createNewJobHolder(new Params(0).groupBy("group2"))); long jobId5 = jobQueue.insert(createNewJobHolder(new Params(0).groupBy("group1"))); JobHolder holder1 = jobQueue.nextJobAndIncRunCount(true, Arrays.asList(new String[]{"group2"})); assertThat("first jobs should be from group group2 if group1 is excluded", holder1.getBaseJob().getRunGroupId(), equalTo("group1")); assertThat("correct job should be returned if groupId is provided", holder1.getId(), equalTo(jobId1)); assertThat("no jobs should be returned if all groups are excluded", jobQueue.nextJobAndIncRunCount(true, Arrays.asList(new String[]{"group1", "group2"})), is(nullValue())); long jobId6 = jobQueue.insert(createNewJobHolder(new Params(0))); assertThat("both groups are disabled, null group job should be returned", jobQueue.nextJobAndIncRunCount(true, Arrays.asList(new String[]{"group1", "group2"})).getId(), is(jobId6)); assertThat("if group1 is excluded, next job should be from group2", jobQueue.nextJobAndIncRunCount(true, Arrays.asList(new String[]{"group1"})).getBaseJob().getRunGroupId() , equalTo("group2")); //to test re-run case, add the job back in jobQueue.insertOrReplace(holder1); //ask for it again, should return the same holder because it is grouped JobHolder holder2 = jobQueue.nextJobAndIncRunCount(true, null); assertThat("for grouped jobs, re-fetching job should work fine", holder2.getId(), equalTo(holder1.getId())); JobHolder holder3 = jobQueue.nextJobAndIncRunCount(true, Arrays.asList(new String[]{"group1"})); assertThat("if a group it excluded, next available from another group should be returned", holder3.getId(), equalTo(jobId4)); //add two more non-grouped jobs long jobId7 = jobQueue.insert(createNewJobHolder(new Params(0))); long jobId8 = jobQueue.insert(createNewJobHolder(new Params(0))); JobHolder holder4 = jobQueue.nextJobAndIncRunCount(true, Arrays.asList(new String[]{"group1", "group2"})); assertThat("if all grouped jobs are excluded, non-grouped jobs should be returned", holder4.getId(), equalTo(jobId7)); jobQueue.insertOrReplace(holder4); //for non-grouped jobs, run counts should be respected assertThat("if all grouped jobs are excluded, re-inserted highest priority job should still be returned", jobQueue.nextJobAndIncRunCount(true, Arrays.asList(new String[]{"group1", "group2"})).getId(), equalTo(jobId7)); } @Test public void testDueDelayUntilWithPriority() throws Exception { JobQueue jobQueue = createNewJobQueue(); long now = System.nanoTime(); JobHolder lowPriorityHolder = createNewJobHolderWithDelayUntil(new Params(5),now - 1000 * JobManager.NS_PER_MS); JobHolder highPriorityHolder = createNewJobHolderWithDelayUntil(new Params(10), now - 10000 * JobManager.NS_PER_MS); jobQueue.insert(lowPriorityHolder); jobQueue.insert(highPriorityHolder); long soonJobDelay = 2000; JobHolder highestPriorityDelayedJob = createNewJobHolderWithDelayUntil(new Params(12), now + soonJobDelay * JobManager.NS_PER_MS); long highestPriorityDelayedJobId = jobQueue.insert(highestPriorityDelayedJob); assertThat("when asked, if job's due has passed, highest priority jobs's delay until should be " + "returned", jobQueue.getNextJobDelayUntilNs(true), equalTo(highPriorityHolder.getDelayUntilNs())); //make sure soon job is valid now Thread.sleep(soonJobDelay); assertThat("when a job's time come, it should be returned", jobQueue.nextJobAndIncRunCount(true, null).getId(), equalTo(highestPriorityDelayedJobId)); } @Test public void testDelayUntil() throws Exception { JobQueue jobQueue = createNewJobQueue(); long now = System.nanoTime(); JobHolder networkJobHolder = createNewJobHolderWithDelayUntil(new Params(0).requireNetwork(), now + 200000 * JobManager.NS_PER_MS); JobHolder noNetworkJobHolder = createNewJobHolderWithDelayUntil(new Params(0), now + 500000 * JobManager.NS_PER_MS); jobQueue.insert(networkJobHolder); jobQueue.insert(noNetworkJobHolder); assertThat("if there is no network, delay until should be provided for no network job", jobQueue.getNextJobDelayUntilNs(false), equalTo(noNetworkJobHolder.getDelayUntilNs())); assertThat("if there is network, delay until should be provided for network job because it is " + "sooner", jobQueue.getNextJobDelayUntilNs(true), equalTo(networkJobHolder.getDelayUntilNs())); JobHolder noNetworkJobHolder2 = createNewJobHolderWithDelayUntil(new Params(0), now + 100000 * JobManager.NS_PER_MS); jobQueue.insert(noNetworkJobHolder2); assertThat("if there is network, any job's delay until should be returned", jobQueue.getNextJobDelayUntilNs(true), equalTo(noNetworkJobHolder2.getDelayUntilNs())); } @Test public void testTruncate() throws Exception { JobQueue jobQueue = createNewJobQueue(); final int LIMIT = 20; for(int i = 0; i < LIMIT; i ++) { jobQueue.insert(createNewJobHolder()); } assertThat("queue should have all jobs", jobQueue.count(), equalTo(LIMIT)); jobQueue.clear(); assertThat("after clear, queue should be empty", jobQueue.count(), equalTo(0)); for(int i = 0; i < LIMIT; i ++) { jobQueue.insert(createNewJobHolder()); } assertThat("if we add jobs again, count should match", jobQueue.count(), equalTo(LIMIT)); } @Test public void testPriorityWithDelayedJobs() throws Exception { JobQueue jobQueue = createNewJobQueue(); JobHolder delayedPriority_5 = createNewJobHolder(new Params(5)); org.fest.reflect.field.Invoker<Long> delayUntilField = getDelayUntilNsField(delayedPriority_5); delayUntilField.set(System.nanoTime() - 1000); JobHolder delayedPriority_2 = createNewJobHolder(new Params(2)); delayUntilField = getDelayUntilNsField(delayedPriority_2); delayUntilField.set(System.nanoTime() - 500); JobHolder nonDelayedPriority_6 = createNewJobHolder(new Params(6)); JobHolder nonDelayedPriority_3 = createNewJobHolder(new Params(3)); JobHolder nonDelayedPriority_2 = createNewJobHolder(new Params(2)); jobQueue.insert(delayedPriority_5); jobQueue.insert(delayedPriority_2); jobQueue.insert(nonDelayedPriority_6); jobQueue.insert(nonDelayedPriority_2); jobQueue.insert(nonDelayedPriority_3); int lastPriority = Integer.MAX_VALUE; for(int i = 0; i < 5; i++) { JobHolder next = jobQueue.nextJobAndIncRunCount(true, null); assertThat("next job should not be null", next, notNullValue()); assertThat("next job's priority should be lower then previous for job " + i, next.getPriority() <= lastPriority, is(true)); lastPriority = next.getPriority(); } } private org.fest.reflect.field.Invoker<Long> getDelayUntilNsField(JobHolder jobHolder) { return Reflection.field("delayUntilNs").ofType(long.class).in(jobHolder); } private org.fest.reflect.field.Invoker<Integer> getPriorityField(Params params) { return Reflection.field("priority").ofType(int.class).in(params); } private org.fest.reflect.field.Invoker<Long> getDelayMsField(Params params) { return Reflection.field("delayMs").ofType(long.class).in(params); } private org.fest.reflect.field.Invoker<String> getGroupIdField(Params params) { return Reflection.field("groupId").ofType(String.class).in(params); } @Test public void testSessionId() throws Exception { long sessionId = (long) (Math.random() * 100000); JobQueue jobQueue = createNewJobQueueWithSessionId(sessionId); JobHolder jobHolder = createNewJobHolder(); jobQueue.insert(jobHolder); jobHolder = jobQueue.nextJobAndIncRunCount(true, null); assertThat("session id should be attached to next job", jobHolder.getRunningSessionId(), equalTo(sessionId)); } @Test public void testPriorityWithReAdd() throws Exception { int JOB_LIMIT = 20; JobQueue jobQueue = createNewJobQueue(); //create and add JOB_LIMIT jobs with random priority for (int i = 0; i < JOB_LIMIT; i++) { jobQueue.insert(createNewJobHolder(new Params((int) (Math.random() * 10)))); } //ensure we get jobs in correct priority order int minPriority = Integer.MAX_VALUE; for (int i = 0; i < JOB_LIMIT; i++) { JobHolder holder = jobQueue.nextJobAndIncRunCount(true, null); assertThat(holder.getPriority() <= minPriority, is(true)); jobQueue.insertOrReplace(holder); } assertThat(jobQueue.nextJobAndIncRunCount(true, null), notNullValue()); } @Test public void testRemove() throws Exception { JobQueue jobQueue = createNewJobQueue(); JobHolder holder = createNewJobHolder(); jobQueue.insert(holder); Long jobId = holder.getId(); assertThat(jobQueue.nextJobAndIncRunCount(true, null).getId(), equalTo(jobId)); assertThat(jobQueue.nextJobAndIncRunCount(true, null), is(nullValue())); jobQueue.remove(holder); assertThat(jobQueue.nextJobAndIncRunCount(true, null), is(nullValue())); } @Test public void testNetwork() throws Exception { JobQueue jobQueue = createNewJobQueue(); JobHolder jobHolder = createNewJobHolder(new Params(0)); jobQueue.insert(jobHolder); assertThat("no network job should be returned even if there is no netowrk", jobQueue.nextJobAndIncRunCount(false, null), notNullValue()); jobQueue.remove(jobHolder); jobHolder = createNewJobHolder(new Params(0).requireNetwork()); assertThat("if there isn't any network, job with network requirement should not return", jobQueue.nextJobAndIncRunCount(false, null), nullValue()); assertThat("if there is network, job with network requirement should be returned", jobQueue.nextJobAndIncRunCount(true, null), nullValue()); jobQueue.remove(jobHolder); jobHolder = createNewJobHolder(new Params(1)); JobHolder jobHolder2 = createNewJobHolder(new Params(5).requireNetwork()); long firstJobId = jobQueue.insert(jobHolder); long secondJobId = jobQueue.insert(jobHolder2); JobHolder retrieved = jobQueue.nextJobAndIncRunCount(false, null); assertThat("one job should be returned w/o network", retrieved, notNullValue()); if(retrieved != null) { assertThat("no network job should be returned although it has lower priority", retrieved.getId(), equalTo(firstJobId)); } assertThat("no other job should be returned w/o network", jobQueue.nextJobAndIncRunCount(false, null), nullValue()); retrieved = jobQueue.nextJobAndIncRunCount(true, null); assertThat("if network is back, network requiring job should be returned", retrieved, notNullValue()); if(retrieved != null) { assertThat("when there is network, network job should be returned", retrieved.getId(), equalTo(secondJobId)); } //add first job back jobQueue.insertOrReplace(jobHolder); //add second job back jobQueue.insertOrReplace(jobHolder2); retrieved = jobQueue.nextJobAndIncRunCount(true, null); assertThat("if network is back, job w/ higher priority should be returned", retrieved, notNullValue()); if(retrieved != null) { assertThat("if network is back, job w/ higher priority should be returned", retrieved.getId(), equalTo(secondJobId)); } jobQueue.insertOrReplace(jobHolder2); JobHolder highestPriorityJob = createNewJobHolder(new Params(10)); long highestPriorityJobId = jobQueue.insert(highestPriorityJob); retrieved = jobQueue.nextJobAndIncRunCount(true, null); assertThat("w/ or w/o network, highest priority should be returned", retrieved, notNullValue()); if(retrieved != null) { assertThat("w/ or w/o network, highest priority should be returned", retrieved.getId(), equalTo(highestPriorityJobId)); } //TODO test delay until } @Test public void testCountReadyJobs() throws Exception { JobQueue jobQueue = createNewJobQueue(); assertThat("initial count should be 0 for ready jobs", jobQueue.countReadyJobs(true, null), equalTo(0)); //add some jobs jobQueue.insert(createNewJobHolder()); jobQueue.insert(createNewJobHolder(new Params(0).requireNetwork())); long now = System.nanoTime(); long delay = 1000; jobQueue.insert(createNewJobHolderWithDelayUntil(new Params(0), now + TimeUnit.MILLISECONDS.toNanos(delay))); assertThat("ready count should be 1 if there is no network", jobQueue.countReadyJobs(false, null), equalTo(1)); assertThat("ready count should be 2 if there is network", jobQueue.countReadyJobs(true, null), equalTo(2)); Thread.sleep(delay); assertThat("when needed delay time passes, ready count should be 3", jobQueue.countReadyJobs(true, null), equalTo(3)); assertThat("when needed delay time passes but no network, ready count should be 2", jobQueue.countReadyJobs(false, null), equalTo(2)); jobQueue.insert(createNewJobHolder(new Params(5).groupBy("group1"))); jobQueue.insert(createNewJobHolder(new Params(5).groupBy("group1"))); assertThat("when more than 1 job from same group is created, ready jobs should increment only by 1", jobQueue.countReadyJobs(true, null), equalTo(4)); assertThat("excluding groups should work", jobQueue.countReadyJobs(true, Arrays.asList(new String[]{"group1"})), equalTo(3)); assertThat("giving a non-existing group should not fool the count", jobQueue.countReadyJobs(true, Arrays.asList(new String[]{"group3423"})), equalTo(4)); jobQueue.insert(createNewJobHolder(new Params(3).groupBy("group2"))); assertThat("when a job from another group is added, ready job count should inc", jobQueue.countReadyJobs(true, null), equalTo(5)); now = System.nanoTime(); jobQueue.insert(createNewJobHolderWithDelayUntil(new Params(3).groupBy("group3"), now + TimeUnit.MILLISECONDS.toNanos(delay))); assertThat("when a delayed job from another group is added, ready count should not change", jobQueue.countReadyJobs(true, null), equalTo(5)); jobQueue.insert(createNewJobHolder(new Params(3).groupBy("group3"))); assertThat("when another job from delayed group is added, ready job count should inc", jobQueue.countReadyJobs(true, null), equalTo(6)); Thread.sleep(delay); assertThat("when delay passes and a job from existing group becomes available, ready job count should not change", jobQueue.countReadyJobs(true, null), equalTo(6)); assertThat("when some groups are excluded, count should be correct", jobQueue.countReadyJobs(true, Arrays.asList(new String[]{"group1", "group3"})), equalTo(4)); //jobs w/ same group id but with different persistence constraints should not fool the count now = System.nanoTime(); jobQueue.insert(createNewJobHolderWithDelayUntil(new Params(0).persist().groupBy("group10"), now + 1000)); jobQueue.insert(createNewJobHolderWithDelayUntil(new Params(0).groupBy("group10"), now + 1000)); jobQueue.insert(createNewJobHolderWithDelayUntil(new Params(0).persist().groupBy("group10"), now - 1000)); jobQueue.insert(createNewJobHolderWithDelayUntil(new Params(0).groupBy("group10"), now - 1000)); assertThat("when many jobs are added w/ different constraints but same group id, ready count should not be fooled", jobQueue.countReadyJobs(true, Arrays.asList(new String[]{"group1", "group3"})), equalTo(5)); assertThat("when many jobs are added w/ different constraints but same group id, ready count should not be fooled", jobQueue.countReadyJobs(true, null), equalTo(7)); assertThat("when many jobs are added w/ different constraints but same group id, ready count should not be fooled", jobQueue.countReadyJobs(false, Arrays.asList(new String[]{"group1", "group3"})), equalTo(4)); } @Test public void testJobFields() throws Exception { long sessionId = (long) (Math.random() * 1000); JobQueue jobQueue = createNewJobQueueWithSessionId(sessionId); JobHolder jobHolder = createNewJobHolder(); int priority = (int) (Math.random() * 1000); jobHolder.setPriority(priority); DummyJob dummyJob = new DummyJob(new Params(0)); jobHolder.setBaseJob(dummyJob); int runCount = (int) (Math.random() * 10); jobHolder.setRunCount(runCount); long id = jobQueue.insert(jobHolder); for (int i = 0; i < 2; i++) { JobHolder received = jobQueue.nextJobAndIncRunCount(true, null); assertThat("job id should be preserved", received.getId(), equalTo(id)); assertThat("job priority should be preserved", received.getPriority(), equalTo(priority)); assertThat("job session id should be assigned", received.getRunningSessionId(), equalTo(sessionId)); assertThat("job run count should be incremented", received.getRunCount(), equalTo(runCount + i + 1)); jobQueue.insertOrReplace(received); } } private void assertJob(JobQueue jobQueue, String msg, long id, /*nullable*/ JobHolder holder) { if(holder == null) { assertThat(msg, jobQueue.findJobById(id), nullValue()); return; } assertThat(msg + "(existence check)", jobQueue.findJobById(id), notNullValue()); assertThat(msg + "(id check)", jobQueue.findJobById(id).getId(), is(holder.getId())); } @Test public void testFindJobHolderById() { JobQueue jobQueue = createNewJobQueue(); assertJob(jobQueue, "non existing job (negative id)", -4, null); assertJob(jobQueue, "non existing job (positive id)", +4, null); final int LIMIT = 100; JobHolder[] holders = new JobHolder[LIMIT]; long[] ids = new long[LIMIT]; for(int i = 0; i < LIMIT; i++) { holders[i] = createNewJobHolder(new Params((int) (Math.random() * 50)).setPersistent(Math.random() < .5).setRequiresNetwork(Math.random() < .5)); ids[i] = jobQueue.insert(holders[i]); assertJob(jobQueue, "job by id should work for inserted job", ids[i], holders[i]); } final int REMOVE_CNT = LIMIT / 2; for(int i = 0; i < REMOVE_CNT; i++) { int ind = (int) (Math.random() * LIMIT); if(holders[ind] == null) { continue; } //remove some randomly, up to half jobQueue.remove(holders[ind]); holders[ind] = null; } //re-query all, ensure we can still find non-removed jobs and not find removed jobs for(int i = 0; i < LIMIT; i++) { if(holders[i] != null) { assertJob(jobQueue, "if job is still in the Q, it should be returned", ids[i], holders[i]); //re add job jobQueue.insertOrReplace(holders[i]); //re-test after re-add assertJob(jobQueue, "after re-insert, if job is still in the Q, it should be returned", ids[i], holders[i]); } else { assertJob(jobQueue, "removed job should not be returned in id query", ids[i], null); } } jobQueue.clear(); for(int i = 0; i < LIMIT; i++) { assertJob(jobQueue, "after clear, find by id should return null", ids[i], null); } } protected JobHolder createNewJobHolder() { return createNewJobHolder(new Params(0)); } protected JobHolder createNewJobHolder(Params params) { long delay = getDelayMsField(params).get(); return new JobHolder(null, getPriorityField(params).get(), getGroupIdField(params).get(), 0, new DummyJob(params), System.nanoTime(), delay > 0 ? System.nanoTime() + delay * JobManager.NS_PER_MS : JobManager.NOT_DELAYED_JOB_DELAY, JobManager.NOT_RUNNING_SESSION_ID); } private JobHolder createNewJobHolderWithDelayUntil(Params params, long delayUntil) { JobHolder jobHolder = createNewJobHolder(params); getDelayUntilNsField(jobHolder).set(delayUntil); return jobHolder; } protected JobQueue createNewJobQueue() { return createNewJobQueueWithSessionId(System.nanoTime()); } private JobQueue createNewJobQueueWithSessionId(Long sessionId) { return currentFactory.createNew(sessionId, "id_" + sessionId); } }