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