Last active
August 29, 2015 14:22
-
-
Save daj/3a6600702a4e9af8a934 to your computer and use it in GitHub Desktop.
Example IdlingResource for use with the JobManager in https://github.com/yigit/android-priority-jobqueue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Used by Espresso tests to tell when it should wait because some background work needs to be done. | |
* | |
* Based on advice from the priority-job-manager maintainer: | |
* https://github.com/path/android-priority-jobqueue/issues/49#issuecomment-45583487 | |
*/ | |
public class JobManagerIdlingResource implements IdlingResource { | |
private static final String TAG = "JobManagerIdlingResource"; | |
private final CBEngineContext mCBEngineContext; | |
private final JobManager mJobManager; | |
private ResourceCallback mResourceCallback; | |
private IdleCheckJob mIdleCheckJob; | |
public JobManagerIdlingResource(CBEngineContext cbEngineContext) { | |
mCBEngineContext = cbEngineContext; | |
mJobManager = cbEngineContext.getJobManager(); | |
} | |
@Override | |
public String getName() { | |
return JobManagerIdlingResource.class.getName(); | |
} | |
@Override | |
public boolean isIdleNow() { | |
if (CBBaseJob.getRunningJobCount() == 0) { | |
return true; | |
} else { | |
if (mResourceCallback != null) { | |
if (mIdleCheckJob == null) { | |
Log.d(TAG, "Not currently idle, no watcher in job queue, add one now"); | |
addIdleWatcherJob(mResourceCallback); | |
} else { | |
// Don't add another watcher as one is already in the queue | |
Log.d(TAG, "Not currently idle, watcher already in job queue"); | |
} | |
} | |
return false; | |
} | |
} | |
@Override | |
public void registerIdleTransitionCallback(final ResourceCallback callback) { | |
mResourceCallback = callback; | |
if (!isIdleNow()) { | |
addIdleWatcherJob(callback); | |
} | |
} | |
private long addIdleWatcherJob(final ResourceCallback callback) { | |
mIdleCheckJob = new IdleCheckJob(mCBEngineContext, callback); | |
return mJobManager.addJob(mIdleCheckJob); | |
} | |
/** | |
* IdlingResource requires us to give an asynchronous callback to Espresso to tell it when the | |
* application is idle. The easiest way to do this is to add a job to the queue, and recheck | |
* the idle state when the job completes (the alternative is polling, which nobody wants to | |
* see). | |
* | |
* This implementation is based on: | |
* https://github.com/dpreussler/android-gluten/blob/master/src/main/java/de/jodamob/android/espresso/PriorityJobQueueIdleMonitor.java | |
*/ | |
private final class IdleCheckJob extends CBBaseJob { | |
private final ResourceCallback callback; | |
private static final long serialVersionUID = -7531425496860713384L; | |
private IdleCheckJob(CBEngineContext cbEngineContext, ResourceCallback callback) { | |
// Ensure the checker job has the lowest priority so it gives all other jobs a chance | |
// to finish first | |
super(cbEngineContext, CBRestApiProtobuf.PRIORITY_LOW); | |
this.callback = callback; | |
} | |
@Override | |
public void onAdded() { } | |
@Override | |
protected void onCancel() { | |
mIdleCheckJob = null; | |
notifyOrReAdd(callback); | |
} | |
@Override | |
public void onRun() { | |
mIdleCheckJob = null; | |
// If we DID call super.onRun(), this is the only time we would want to call it BEFORE | |
// doing our work (since notifyOrReAdd needs to check the queue count) | |
notifyOrReAdd(callback); | |
} | |
private void notifyOrReAdd(final ResourceCallback callback) { | |
if (isIdleNow()) { | |
LogIt.d(TAG, "Now idle, call onTransitionToIdle"); | |
callback.onTransitionToIdle(); | |
} else { | |
addIdleWatcherJob(callback); | |
} | |
} | |
@Override | |
protected boolean shouldReRunOnThrowable(Throwable arg0) { | |
return false; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* My real test extends my own base test class, which is an AndroidTestCase. I've tried | |
* to only show the most interesting parts of the code here. | |
*/ | |
public class JobManagerIdlingResourceTest extends AndroidTestCase implements IdlingResource.ResourceCallback { | |
protected CountDownLatch mLatch = new CountDownLatch(1); | |
protected BaseMockRestApi mMockApi; | |
protected JobManagerIdlingResource mIdlingResourceMonitor; | |
private boolean mDidCallbackHappen; | |
@Override | |
protected void setUp() throws Exception { | |
super.setUp(); | |
mDidCallbackHappen = false; | |
mMockApi = new MockRestApi200EmptyProtobufSuccess(); | |
mCBEngineContext = CBEngineContext.getTestCBEngineContext(); | |
mIdlingResourceMonitor = new JobManagerIdlingResource(mCBEngineContext); | |
mIdlingResourceMonitor.registerIdleTransitionCallback(this); | |
} | |
public void testTransitionToIdleCalled() { | |
resetLatch(); | |
mMockApi.setLatch(mLatch); | |
setMockRestApi(mMockApi); | |
assertTrue(mIdlingResourceMonitor.isIdleNow()); | |
assertFalse(mDidCallbackHappen); | |
CBBaseJob job = new IngredientsSearchJob(mCBEngineContext, "blah"); | |
mCBEngineContext.getJobManager().addJob(job); | |
assertFalse(mIdlingResourceMonitor.isIdleNow()); | |
assertFalse(mDidCallbackHappen); | |
// Now release the latch to let it finish | |
mLatch.countDown(); | |
// Wait for the callback to happen | |
Condition condition = new Condition() { | |
@Override | |
public boolean isSatisfied() { | |
return mDidCallbackHappen == true; | |
} | |
}; | |
assertCondition(condition); | |
} | |
@Override | |
public void onTransitionToIdle() { | |
mDidCallbackHappen = true; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* All my JobManager jobs extend this base class. | |
*/ | |
public abstract class MyBaseJob extends Job { | |
protected CBEngineContext mCBEngineContext; | |
private static AtomicInteger sRunningJobCount = new AtomicInteger(0); | |
/** | |
* @return the number of jobs that are waiting to run, or are running in the JobManager. | |
*/ | |
public static int getRunningJobCount() { | |
return sRunningJobCount.get(); | |
} | |
public MyBaseJob(CBEngineContext cbEngineContext) { | |
// We do not set our REST jobs to requireNetwork() as otherwise our jobs will not be | |
// started until a network is available. For all user initiated actions, we want them | |
// to fail quickly if there is no network. | |
super(new Params(MyConstants.PRIORITY_HIGH)); | |
mCBEngineContext = cbEngineContext; | |
} | |
/** | |
* Subclasses must call super.onAdded() BEFORE doing anything else. | |
*/ | |
@Override | |
public void onAdded() { | |
sRunningJobCount.incrementAndGet(); | |
} | |
/** | |
* Subclasses must call super.onRun() AFTER completing all their steps! | |
*/ | |
@Override | |
public void onRun() { | |
sRunningJobCount.decrementAndGet(); | |
} | |
@Override | |
protected boolean shouldReRunOnThrowable(Throwable throwable) { | |
// Do not retry if an error occurs in onRun (this will trigger a call to onCancel) | |
return false; | |
} | |
/** | |
* Subclasses must call super.onCancel() AFTER completing all their steps! | |
*/ | |
@Override | |
protected void onCancel() { | |
// Job has exceeded retry attempts or shouldReRunOnThrowable() returned false | |
sRunningJobCount.decrementAndGet(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The biggest caveat is all the super.onRun() calls in the derived job classes need to be called at the end of the call to ensure running job count is decreased.