| Index: base/test/android/javatests/src/org/chromium/base/test/BaseTestResult.java
|
| diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseTestResult.java b/base/test/android/javatests/src/org/chromium/base/test/BaseTestResult.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..5802f40107de7c82922678ab460d21148d4338ee
|
| --- /dev/null
|
| +++ b/base/test/android/javatests/src/org/chromium/base/test/BaseTestResult.java
|
| @@ -0,0 +1,360 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.base.test;
|
| +
|
| +import android.app.Instrumentation;
|
| +import android.content.Context;
|
| +import android.os.Bundle;
|
| +import android.os.SystemClock;
|
| +
|
| +import junit.framework.AssertionFailedError;
|
| +import junit.framework.TestCase;
|
| +import junit.framework.TestResult;
|
| +
|
| +import org.chromium.base.Log;
|
| +import org.chromium.base.test.util.CommandLineFlags;
|
| +import org.chromium.base.test.util.parameter.BaseParameter;
|
| +import org.chromium.base.test.util.parameter.Parameter;
|
| +import org.chromium.base.test.util.parameter.Parameterizable;
|
| +import org.chromium.base.test.util.parameter.ParameterizedTest;
|
| +
|
| +import java.io.PrintWriter;
|
| +import java.io.StringWriter;
|
| +import java.util.ArrayList;
|
| +import java.util.Arrays;
|
| +import java.util.Iterator;
|
| +import java.util.List;
|
| +import java.util.Map;
|
| +import java.util.Map.Entry;
|
| +
|
| +/**
|
| + * A test result that can skip tests.
|
| + */
|
| +public class BaseTestResult extends TestResult {
|
| + private static final String TAG = "cr.base.test";
|
| +
|
| + private static final int SLEEP_INTERVAL_MS = 50;
|
| + private static final int WAIT_DURATION_MS = 5000;
|
| +
|
| + private final Instrumentation mInstrumentation;
|
| + private final List<SkipCheck> mSkipChecks;
|
| +
|
| + /**
|
| + * Creates an instance of BaseTestResult.
|
| + */
|
| + public BaseTestResult(Instrumentation instrumentation) {
|
| + mSkipChecks = new ArrayList<>();
|
| + mInstrumentation = instrumentation;
|
| + }
|
| +
|
| + /**
|
| + * An interface for classes that check whether a test case should be skipped.
|
| + */
|
| + public interface SkipCheck {
|
| + /**
|
| + *
|
| + * Checks whether the given test case should be skipped.
|
| + *
|
| + * @param testCase The test case to check.
|
| + * @return Whether the test case should be skipped.
|
| + */
|
| + boolean shouldSkip(TestCase testCase);
|
| + }
|
| +
|
| + /**
|
| + * Adds a check for whether a test should run.
|
| + *
|
| + * @param skipCheck The check to add.
|
| + */
|
| + public void addSkipCheck(SkipCheck skipCheck) {
|
| + mSkipChecks.add(skipCheck);
|
| + }
|
| +
|
| + protected boolean shouldSkip(TestCase test) {
|
| + for (SkipCheck s : mSkipChecks) {
|
| + if (s.shouldSkip(test)) return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + @Override
|
| + protected void run(TestCase test) {
|
| + if (shouldSkip(test)) {
|
| + startTest(test);
|
| +
|
| + Bundle skipResult = new Bundle();
|
| + skipResult.putString("class", test.getClass().getName());
|
| + skipResult.putString("test", test.getName());
|
| + skipResult.putBoolean("test_skipped", true);
|
| + mInstrumentation.sendStatus(0, skipResult);
|
| +
|
| + endTest(test);
|
| + } else {
|
| + try {
|
| + CommandLineFlags.setUp(
|
| + getTargetContext(),
|
| + test.getClass().getMethod(test.getName()));
|
| + } catch (NoSuchMethodException e) {
|
| + Log.e(TAG, "Unable to set up CommandLineFlags", e);
|
| + }
|
| +
|
| + if (test instanceof Parameterizable) {
|
| + try {
|
| + runParameterized(test);
|
| + } catch (ThreadDeath e) {
|
| + Log.e(TAG, "Parameterized test run failed: %s", e);
|
| + }
|
| + } else {
|
| + super.run(test);
|
| + }
|
| + }
|
| + }
|
| +
|
| + @SuppressWarnings("unchecked")
|
| + private <T extends TestCase & Parameterizable> void runParameterized(TestCase test)
|
| + throws ThreadDeath {
|
| + T testCase = (T) test;
|
| +
|
| + // Prepare test.
|
| + Parameter.Reader parameterReader = new Parameter.Reader(test);
|
| + testCase.setParameterReader(parameterReader);
|
| + List<ParameterizedTest> parameterizedTests = parameterReader.getParameterizedTests();
|
| + List<ParameterError> errors = new ArrayList<>();
|
| + List<ParameterError> failures = new ArrayList<>();
|
| + Map<String, BaseParameter> availableParameters = testCase.getAvailableParameters();
|
| +
|
| + if (parameterizedTests.isEmpty()) {
|
| + super.run(test);
|
| + } else {
|
| + // Start test.
|
| + startTest(testCase);
|
| + for (ParameterizedTest parameterizedTest : parameterizedTests) {
|
| + parameterReader.setCurrentParameterizedTest(parameterizedTest);
|
| + try {
|
| + setUpParameters(availableParameters, parameterReader);
|
| + testCase.runBare();
|
| + tearDownParameters(availableParameters, parameterReader);
|
| + } catch (AssertionFailedError e) {
|
| + failures.add(new ParameterError(e, parameterizedTest));
|
| + } catch (ThreadDeath e) {
|
| + throw e;
|
| + } catch (Throwable e) {
|
| + errors.add(new ParameterError(e, parameterizedTest));
|
| + }
|
| + }
|
| +
|
| + // Generate failures and errors.
|
| + if (!failures.isEmpty()) {
|
| + addFailure(test, new ParameterizedTestFailure(failures));
|
| + }
|
| + if (!errors.isEmpty()) {
|
| + addError(test, new ParameterizedTestError(errors));
|
| + }
|
| +
|
| + // End test.
|
| + endTest(testCase);
|
| + }
|
| + }
|
| +
|
| + private static <T extends TestCase & Parameterizable> void setUpParameters(
|
| + Map<String, BaseParameter> availableParameters, Parameter.Reader reader)
|
| + throws Exception {
|
| + for (Entry<String, BaseParameter> entry : availableParameters.entrySet()) {
|
| + if (reader.getParameter(entry.getValue().getTag()) != null) {
|
| + entry.getValue().setUp();
|
| + }
|
| + }
|
| + }
|
| +
|
| + private static <T extends TestCase & Parameterizable> void tearDownParameters(
|
| + Map<String, BaseParameter> availableParameters, Parameter.Reader reader)
|
| + throws Exception {
|
| + for (Entry<String, BaseParameter> entry : availableParameters.entrySet()) {
|
| + if (reader.getParameter(entry.getValue().getTag()) != null) {
|
| + entry.getValue().tearDown();
|
| + }
|
| + }
|
| + }
|
| +
|
| + private static class ParameterError {
|
| + private final Throwable mThrowable;
|
| + private final ParameterizedTest mParameterizedTest;
|
| +
|
| + public ParameterError(Throwable throwable, ParameterizedTest parameterizedTest) {
|
| + mThrowable = throwable;
|
| + mParameterizedTest = parameterizedTest;
|
| + }
|
| +
|
| + private Throwable getThrowable() {
|
| + return mThrowable;
|
| + }
|
| +
|
| + private ParameterizedTest getParameterizedTest() {
|
| + return mParameterizedTest;
|
| + }
|
| + }
|
| +
|
| + private static class ParameterizedTestFailure extends AssertionFailedError {
|
| + public ParameterizedTestFailure(List<ParameterError> failures) {
|
| + super(new ParameterizedTestError(failures).toString());
|
| + }
|
| + }
|
| +
|
| + private static class ParameterizedTestError extends Exception {
|
| + private final List<ParameterError> mErrors;
|
| +
|
| + public ParameterizedTestError(List<ParameterError> errors) {
|
| + mErrors = errors;
|
| + }
|
| +
|
| + /**
|
| + * Error output is as follows.
|
| + *
|
| + * DEFINITIONS:
|
| + * {{ERROR}} is the standard error output from
|
| + * {@link ParameterError#getThrowable().toString()}.
|
| + * {{PARAMETER_TAG}} is the {@link Parameter#tag()} value associated with the parameter.
|
| + * {{ARGUMENT_NAME}} is the {@link Parameter.Argument#name()} associated with the argument.
|
| + * {{ARGUMENT_VALUE}} is the value associated with the {@link Parameter.Argument}. This can
|
| + * be a String, int, String[], or int[].
|
| + *
|
| + * With no {@link Parameter}:
|
| + * {{ERROR}} (with no parameters)
|
| + *
|
| + * With Single {@link Parameter} and no {@link Parameter.Argument}:
|
| + * {{ERROR}} (with parameters: {{PARAMETER_TAG}} with no arguments)
|
| + *
|
| + * With Single {@link Parameter} and one {@link Parameter.Argument}:
|
| + * {{ERROR}} (with parameters: {{PARAMETER_TAG}} with arguments:
|
| + * {{ARGUMENT_NAME}}={{ARGUMENT_VALUE}})
|
| + *
|
| + * With Single {@link Parameter} and multiple {@link Parameter.Argument}s:
|
| + * {{ERROR}} (with parameters: {{PARAMETER_TAG}} with arguments:
|
| + * {{ARGUMENT_NAME}}={{ARGUMENT_VALUE}}, {{ARGUMENT_NAME}}={{ARGUMENT_VALUE}}, ...)
|
| + *
|
| + * DEFINITION:
|
| + * {{PARAMETER_ERROR}} is the output of a single {@link Parameter}'s error. Format:
|
| + * {{PARAMETER_TAG}} with arguments: {{ARGUMENT_NAME}}={{ARGUMENT_NAME}}, ...
|
| + *
|
| + * With Multiple {@link Parameter}s:
|
| + * {{ERROR}} (with parameters: {{PARAMETER_ERROR}}; {{PARAMETER_ERROR}}; ...)
|
| + *
|
| + * There will be a trace after this. And this is shown for every possible {@link
|
| + * ParameterizedTest} that is failed in the {@link ParameterizedTest.Set} if there is one.
|
| + *
|
| + * @return the error message and trace of the test failures.
|
| + */
|
| + @Override
|
| + public String toString() {
|
| + if (mErrors.isEmpty()) return "\n";
|
| + StringBuilder builder = new StringBuilder();
|
| + Iterator<ParameterError> iter = mErrors.iterator();
|
| + if (iter.hasNext()) {
|
| + builder.append(createErrorBuilder(iter.next()));
|
| + }
|
| + while (iter.hasNext()) {
|
| + builder.append("\n").append(createErrorBuilder(iter.next()));
|
| + }
|
| + return builder.toString();
|
| + }
|
| +
|
| + private static StringBuilder createErrorBuilder(ParameterError error) {
|
| + StringBuilder builder = new StringBuilder("\n").append(error.getThrowable().toString());
|
| + List<Parameter> parameters =
|
| + Arrays.asList(error.getParameterizedTest().parameters());
|
| + if (parameters.isEmpty()) {
|
| + builder.append(" (with no parameters)");
|
| + } else {
|
| + Iterator<Parameter> iter = parameters.iterator();
|
| + builder.append(" (with parameters: ").append(createParameterBuilder(iter.next()));
|
| + while (iter.hasNext()) {
|
| + builder.append("; ").append(createParameterBuilder(iter.next()));
|
| + }
|
| + builder.append(")");
|
| + }
|
| + return builder.append("\n").append(trace(error));
|
| + }
|
| +
|
| + private static StringBuilder createParameterBuilder(Parameter parameter) {
|
| + StringBuilder builder = new StringBuilder(parameter.tag());
|
| + List<Parameter.Argument> arguments = Arrays.asList(parameter.arguments());
|
| + if (arguments.isEmpty()) {
|
| + builder.append(" with no arguments");
|
| + } else {
|
| + Iterator<Parameter.Argument> iter = arguments.iterator();
|
| + builder.append(" with arguments: ").append(createArgumentBuilder(iter.next()));
|
| + while (iter.hasNext()) {
|
| + builder.append(", ").append(createArgumentBuilder(iter.next()));
|
| + }
|
| + }
|
| + return builder;
|
| + }
|
| +
|
| + private static StringBuilder createArgumentBuilder(Parameter.Argument argument) {
|
| + StringBuilder builder = new StringBuilder(argument.name()).append("=");
|
| + if (!Parameter.ArgumentDefault.STRING.equals(argument.stringVar())) {
|
| + builder.append(argument.stringVar());
|
| + } else if (Parameter.ArgumentDefault.INT != argument.intVar()) {
|
| + builder.append(argument.intVar());
|
| + } else if (argument.stringArray().length > 0) {
|
| + builder.append(Arrays.toString(argument.stringArray()));
|
| + } else if (argument.intArray().length > 0) {
|
| + builder.append(Arrays.toString(argument.intArray()));
|
| + }
|
| + return builder;
|
| + }
|
| +
|
| + /**
|
| + * @return the trace without the error message
|
| + */
|
| + private static StringBuilder trace(ParameterError error) {
|
| + StringWriter stringWriter = new StringWriter();
|
| + PrintWriter writer = new PrintWriter(stringWriter);
|
| + error.getThrowable().printStackTrace(writer);
|
| + StringBuilder builder = new StringBuilder(stringWriter.getBuffer());
|
| + return trim(deleteFirstLine(builder));
|
| + }
|
| +
|
| + private static StringBuilder deleteFirstLine(StringBuilder builder) {
|
| + return builder.delete(0, builder.indexOf("\n") + 1);
|
| + }
|
| +
|
| + private static StringBuilder trim(StringBuilder sb) {
|
| + if (sb == null || sb.length() == 0) return sb;
|
| + for (int i = sb.length() - 1; i >= 0; i--) {
|
| + if (Character.isWhitespace(sb.charAt(i))) {
|
| + sb.deleteCharAt(i);
|
| + } else {
|
| + return sb;
|
| + }
|
| + }
|
| + return sb;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Gets the target context.
|
| + *
|
| + * On older versions of Android, getTargetContext() may initially return null, so we have to
|
| + * wait for it to become available.
|
| + *
|
| + * @return The target {@link Context} if available; null otherwise.
|
| + */
|
| + public Context getTargetContext() {
|
| + Context targetContext = mInstrumentation.getTargetContext();
|
| + try {
|
| + long startTime = SystemClock.uptimeMillis();
|
| + // TODO(jbudorick): Convert this to CriteriaHelper once that moves to base/.
|
| + while (targetContext == null
|
| + && SystemClock.uptimeMillis() - startTime < WAIT_DURATION_MS) {
|
| + Thread.sleep(SLEEP_INTERVAL_MS);
|
| + targetContext = mInstrumentation.getTargetContext();
|
| + }
|
| + } catch (InterruptedException e) {
|
| + Log.e(TAG, "Interrupted while attempting to initialize the command line.");
|
| + }
|
| + return targetContext;
|
| + }
|
| +}
|
|
|