Home | History | Annotate | Download | only in testing
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.arch.core.executor.testing;
     18 
     19 import android.arch.core.executor.AppToolkitTaskExecutor;
     20 import android.arch.core.executor.DefaultTaskExecutor;
     21 import android.os.SystemClock;
     22 
     23 import org.junit.rules.TestWatcher;
     24 import org.junit.runner.Description;
     25 
     26 import java.util.concurrent.TimeUnit;
     27 import java.util.concurrent.TimeoutException;
     28 
     29 /**
     30  * A JUnit Test Rule that swaps the background executor used by the Architecture Components with a
     31  * different one which counts the tasks as they are start and finish.
     32  * <p>
     33  * You can use this rule for your host side tests that use Architecture Components.
     34  */
     35 public class CountingTaskExecutorRule extends TestWatcher {
     36     private final Object mCountLock = new Object();
     37     private int mTaskCount = 0;
     38 
     39     @Override
     40     protected void starting(Description description) {
     41         super.starting(description);
     42         AppToolkitTaskExecutor.getInstance().setDelegate(new DefaultTaskExecutor() {
     43             @Override
     44             public void executeOnDiskIO(Runnable runnable) {
     45                 super.executeOnDiskIO(new CountingRunnable(runnable));
     46             }
     47 
     48             @Override
     49             public void postToMainThread(Runnable runnable) {
     50                 super.postToMainThread(new CountingRunnable(runnable));
     51             }
     52         });
     53     }
     54 
     55     @Override
     56     protected void finished(Description description) {
     57         super.finished(description);
     58         AppToolkitTaskExecutor.getInstance().setDelegate(null);
     59     }
     60 
     61     private void increment() {
     62         synchronized (mCountLock) {
     63             mTaskCount++;
     64         }
     65     }
     66 
     67     private void decrement() {
     68         synchronized (mCountLock) {
     69             mTaskCount--;
     70             if (mTaskCount == 0) {
     71                 onIdle();
     72                 mCountLock.notifyAll();
     73             }
     74         }
     75     }
     76 
     77     /**
     78      * Called when the number of awaiting tasks reaches to 0.
     79      *
     80      * @see #isIdle()
     81      */
     82     protected void onIdle() {
     83 
     84     }
     85 
     86     /**
     87      * Returns false if there are tasks waiting to be executed, true otherwise.
     88      *
     89      * @return False if there are tasks waiting to be executed, true otherwise.
     90      *
     91      * @see #onIdle()
     92      */
     93     public boolean isIdle() {
     94         synchronized (mCountLock) {
     95             return mTaskCount == 0;
     96         }
     97     }
     98 
     99     /**
    100      * Waits until all active tasks are finished.
    101      *
    102      * @param time The duration to wait
    103      * @param timeUnit The time unit for the {@code time} parameter
    104      *
    105      * @throws InterruptedException If thread is interrupted while waiting
    106      * @throws TimeoutException If tasks cannot be drained at the given time
    107      */
    108     public void drainTasks(int time, TimeUnit timeUnit)
    109             throws InterruptedException, TimeoutException {
    110         long end = SystemClock.uptimeMillis() + timeUnit.toMillis(time);
    111         synchronized (mCountLock) {
    112             while (mTaskCount != 0) {
    113                 long now = SystemClock.uptimeMillis();
    114                 long remaining = end - now;
    115                 if (remaining > 0) {
    116                     mCountLock.wait(remaining);
    117                 } else {
    118                     throw new TimeoutException("could not drain tasks");
    119                 }
    120             }
    121         }
    122     }
    123 
    124     class CountingRunnable implements Runnable {
    125         final Runnable mWrapped;
    126 
    127         CountingRunnable(Runnable wrapped) {
    128             mWrapped = wrapped;
    129             increment();
    130         }
    131 
    132         @Override
    133         public void run() {
    134             try {
    135                 mWrapped.run();
    136             } finally {
    137                 decrement();
    138             }
    139         }
    140     }
    141 }
    142