blob: ed70b706857872fa1c9c6476c90293730a9db288 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.cts.helpers.sensoroperations;
import junit.framework.Assert;
import android.hardware.cts.helpers.SensorStats;
import android.hardware.cts.helpers.reporting.ISensorTestNode;
import android.os.SystemClock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* A {@link SensorOperation} that executes a set of children {@link SensorOperation}s in parallel.
* The children are run in parallel but are given an index label in the order they are added. This
* class can be combined to compose complex {@link SensorOperation}s.
*/
public class ParallelSensorOperation extends SensorOperation {
public static final String STATS_TAG = "parallel";
private final ArrayList<SensorOperation> mOperations = new ArrayList<>();
private final Long mTimeout;
private final TimeUnit mTimeUnit;
/**
* Constructor for the {@link ParallelSensorOperation} without a timeout.
*/
// TODO: sensor tests must always provide a timeout to prevent tests from running forever
public ParallelSensorOperation() {
mTimeout = null;
mTimeUnit = null;
}
/**
* Constructor for the {@link ParallelSensorOperation} with a timeout.
*/
public ParallelSensorOperation(long timeout, TimeUnit timeUnit) {
if (timeUnit == null) {
throw new IllegalArgumentException("Arguments cannot be null");
}
mTimeout = timeout;
mTimeUnit = timeUnit;
}
/**
* Add a set of {@link SensorOperation}s.
*/
public void add(SensorOperation ... operations) {
for (SensorOperation operation : operations) {
if (operation == null) {
throw new IllegalArgumentException("Arguments cannot be null");
}
mOperations.add(operation);
}
}
/**
* Executes the {@link SensorOperation}s in parallel. If an exception occurs one or more
* operations, the first exception will be thrown once all operations are completed.
*/
@Override
public void execute(final ISensorTestNode parent) throws InterruptedException {
int operationsCount = mOperations.size();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
operationsCount,
operationsCount,
1 /* keepAliveTime */,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>());
executor.allowCoreThreadTimeOut(true);
executor.prestartAllCoreThreads();
final ISensorTestNode currentNode = asTestNode(parent);
ArrayList<Future<SensorOperation>> futures = new ArrayList<>();
for (final SensorOperation operation : mOperations) {
Future<SensorOperation> future = executor.submit(new Callable<SensorOperation>() {
@Override
public SensorOperation call() throws Exception {
operation.execute(currentNode);
return operation;
}
});
futures.add(future);
}
Long executionTimeNs = null;
if (mTimeout != null) {
executionTimeNs = SystemClock.elapsedRealtimeNanos()
+ TimeUnit.NANOSECONDS.convert(mTimeout, mTimeUnit);
}
boolean hasAssertionErrors = false;
ArrayList<Integer> timeoutIndices = new ArrayList<>();
ArrayList<Throwable> exceptions = new ArrayList<>();
for (int i = 0; i < operationsCount; ++i) {
Future<SensorOperation> future = futures.get(i);
try {
SensorOperation operation = getFutureResult(future, executionTimeNs);
addSensorStats(STATS_TAG, i, operation.getStats());
} catch (ExecutionException e) {
// extract the exception thrown by the worker thread
Throwable cause = e.getCause();
hasAssertionErrors |= (cause instanceof AssertionError);
exceptions.add(e.getCause());
addSensorStats(STATS_TAG, i, mOperations.get(i).getStats());
} catch (TimeoutException e) {
// we log, but we also need to interrupt the operation to terminate cleanly
timeoutIndices.add(i);
future.cancel(true /* mayInterruptIfRunning */);
} catch (InterruptedException e) {
// clean-up after ourselves by interrupting all the worker threads, and propagate
// the interruption status, so we stop the outer loop as well
executor.shutdownNow();
throw e;
}
}
String summary = getSummaryMessage(exceptions, timeoutIndices);
if (hasAssertionErrors) {
getStats().addValue(SensorStats.ERROR, summary);
}
if (!exceptions.isEmpty() || !timeoutIndices.isEmpty()) {
Assert.fail(summary);
}
}
/**
* {@inheritDoc}
*/
@Override
public ParallelSensorOperation clone() {
ParallelSensorOperation operation = new ParallelSensorOperation();
for (SensorOperation subOperation : mOperations) {
operation.add(subOperation.clone());
}
return operation;
}
/**
* Helper method that waits for a {@link Future} to complete, and returns its result.
*/
private SensorOperation getFutureResult(Future<SensorOperation> future, Long timeoutNs)
throws ExecutionException, TimeoutException, InterruptedException {
if (timeoutNs == null) {
return future.get();
}
// cap timeout to 1ns so that join doesn't block indefinitely
long waitTimeNs = Math.max(timeoutNs - SystemClock.elapsedRealtimeNanos(), 1);
return future.get(waitTimeNs, TimeUnit.NANOSECONDS);
}
/**
* Helper method for joining the exception and timeout messages used in assertions.
*/
private String getSummaryMessage(List<Throwable> exceptions, List<Integer> timeoutIndices) {
StringBuilder sb = new StringBuilder();
for (Throwable exception : exceptions) {
sb.append(exception.toString()).append(", ");
}
if (!timeoutIndices.isEmpty()) {
sb.append("Operation");
if (timeoutIndices.size() != 1) {
sb.append("s");
}
sb.append(" [");
for (Integer index : timeoutIndices) {
sb.append(index).append(", ");
}
sb.append("] timed out");
}
return sb.toString();
}
}