Home | History | Annotate | Download | only in sensoroperations
      1 /*
      2  * Copyright (C) 2013 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.hardware.cts.helpers.sensoroperations;
     18 
     19 import junit.framework.Assert;
     20 
     21 import android.hardware.cts.helpers.SensorStats;
     22 import android.hardware.cts.helpers.reporting.ISensorTestNode;
     23 import android.os.SystemClock;
     24 
     25 import java.util.ArrayList;
     26 import java.util.List;
     27 import java.util.concurrent.Callable;
     28 import java.util.concurrent.ExecutionException;
     29 import java.util.concurrent.Future;
     30 import java.util.concurrent.LinkedBlockingQueue;
     31 import java.util.concurrent.ThreadPoolExecutor;
     32 import java.util.concurrent.TimeUnit;
     33 import java.util.concurrent.TimeoutException;
     34 
     35 /**
     36  * A {@link SensorOperation} that executes a set of children {@link SensorOperation}s in parallel.
     37  * The children are run in parallel but are given an index label in the order they are added. This
     38  * class can be combined to compose complex {@link SensorOperation}s.
     39  */
     40 public class ParallelSensorOperation extends SensorOperation {
     41     public static final String STATS_TAG = "parallel";
     42 
     43     private final ArrayList<SensorOperation> mOperations = new ArrayList<>();
     44     private final Long mTimeout;
     45     private final TimeUnit mTimeUnit;
     46 
     47     /**
     48      * Constructor for the {@link ParallelSensorOperation} without a timeout.
     49      */
     50     // TODO: sensor tests must always provide a timeout to prevent tests from running forever
     51     public ParallelSensorOperation() {
     52         mTimeout = null;
     53         mTimeUnit = null;
     54     }
     55 
     56     /**
     57      * Constructor for the {@link ParallelSensorOperation} with a timeout.
     58      */
     59     public ParallelSensorOperation(long timeout, TimeUnit timeUnit) {
     60         if (timeUnit == null) {
     61             throw new IllegalArgumentException("Arguments cannot be null");
     62         }
     63         mTimeout = timeout;
     64         mTimeUnit = timeUnit;
     65     }
     66 
     67     /**
     68      * Add a set of {@link SensorOperation}s.
     69      */
     70     public void add(SensorOperation ... operations) {
     71         for (SensorOperation operation : operations) {
     72             if (operation == null) {
     73                 throw new IllegalArgumentException("Arguments cannot be null");
     74             }
     75             mOperations.add(operation);
     76         }
     77     }
     78 
     79     /**
     80      * Executes the {@link SensorOperation}s in parallel. If an exception occurs one or more
     81      * operations, the first exception will be thrown once all operations are completed.
     82      */
     83     @Override
     84     public void execute(final ISensorTestNode parent) throws InterruptedException {
     85         int operationsCount = mOperations.size();
     86         ThreadPoolExecutor executor = new ThreadPoolExecutor(
     87                 operationsCount,
     88                 operationsCount,
     89                 1 /* keepAliveTime */,
     90                 TimeUnit.SECONDS,
     91                 new LinkedBlockingQueue<Runnable>());
     92         executor.allowCoreThreadTimeOut(true);
     93         executor.prestartAllCoreThreads();
     94 
     95         final ISensorTestNode currentNode = asTestNode(parent);
     96         ArrayList<Future<SensorOperation>> futures = new ArrayList<>();
     97         for (final SensorOperation operation : mOperations) {
     98             Future<SensorOperation> future = executor.submit(new Callable<SensorOperation>() {
     99                 @Override
    100                 public SensorOperation call() throws Exception {
    101                     operation.execute(currentNode);
    102                     return operation;
    103                 }
    104             });
    105             futures.add(future);
    106         }
    107 
    108         Long executionTimeNs = null;
    109         if (mTimeout != null) {
    110             executionTimeNs = SystemClock.elapsedRealtimeNanos()
    111                     + TimeUnit.NANOSECONDS.convert(mTimeout, mTimeUnit);
    112         }
    113 
    114         boolean hasAssertionErrors = false;
    115         ArrayList<Integer> timeoutIndices = new ArrayList<>();
    116         ArrayList<Throwable> exceptions = new ArrayList<>();
    117         for (int i = 0; i < operationsCount; ++i) {
    118             Future<SensorOperation> future = futures.get(i);
    119             try {
    120                 SensorOperation operation = getFutureResult(future, executionTimeNs);
    121                 addSensorStats(STATS_TAG, i, operation.getStats());
    122             } catch (ExecutionException e) {
    123                 // extract the exception thrown by the worker thread
    124                 Throwable cause = e.getCause();
    125                 hasAssertionErrors |= (cause instanceof AssertionError);
    126                 exceptions.add(e.getCause());
    127                 addSensorStats(STATS_TAG, i, mOperations.get(i).getStats());
    128             } catch (TimeoutException e) {
    129                 // we log, but we also need to interrupt the operation to terminate cleanly
    130                 timeoutIndices.add(i);
    131                 future.cancel(true /* mayInterruptIfRunning */);
    132             } catch (InterruptedException e) {
    133                 // clean-up after ourselves by interrupting all the worker threads, and propagate
    134                 // the interruption status, so we stop the outer loop as well
    135                 executor.shutdownNow();
    136                 throw e;
    137             }
    138         }
    139 
    140         String summary = getSummaryMessage(exceptions, timeoutIndices);
    141         if (hasAssertionErrors) {
    142             getStats().addValue(SensorStats.ERROR, summary);
    143         }
    144         if (!exceptions.isEmpty() || !timeoutIndices.isEmpty()) {
    145             Assert.fail(summary);
    146         }
    147     }
    148 
    149     /**
    150      * {@inheritDoc}
    151      */
    152     @Override
    153     public ParallelSensorOperation clone() {
    154         ParallelSensorOperation operation = new ParallelSensorOperation();
    155         for (SensorOperation subOperation : mOperations) {
    156             operation.add(subOperation.clone());
    157         }
    158         return operation;
    159     }
    160 
    161     /**
    162      * Helper method that waits for a {@link Future} to complete, and returns its result.
    163      */
    164     private SensorOperation getFutureResult(Future<SensorOperation> future, Long timeoutNs)
    165             throws ExecutionException, TimeoutException, InterruptedException {
    166         if (timeoutNs == null) {
    167             return future.get();
    168         }
    169         // cap timeout to 1ns so that join doesn't block indefinitely
    170         long waitTimeNs = Math.max(timeoutNs - SystemClock.elapsedRealtimeNanos(), 1);
    171         return future.get(waitTimeNs, TimeUnit.NANOSECONDS);
    172     }
    173 
    174     /**
    175      * Helper method for joining the exception and timeout messages used in assertions.
    176      */
    177     private String getSummaryMessage(List<Throwable> exceptions, List<Integer> timeoutIndices) {
    178         StringBuilder sb = new StringBuilder();
    179         for (Throwable exception : exceptions) {
    180             sb.append(exception.toString()).append(", ");
    181         }
    182 
    183         if (!timeoutIndices.isEmpty()) {
    184             sb.append("Operation");
    185             if (timeoutIndices.size() != 1) {
    186                 sb.append("s");
    187             }
    188             sb.append(" [");
    189             for (Integer index : timeoutIndices) {
    190                 sb.append(index).append(", ");
    191             }
    192             sb.append("] timed out");
    193         }
    194 
    195         return sb.toString();
    196     }
    197 }
    198