| /* |
| * Copyright (C) 2010 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 com.android.tradefed.testtype; |
| |
| import com.android.tradefed.build.BuildRetrievalError; |
| import com.android.tradefed.build.IBuildInfo; |
| import com.android.tradefed.config.ConfigurationException; |
| import com.android.tradefed.config.DynamicRemoteFileResolver; |
| import com.android.tradefed.config.IConfiguration; |
| import com.android.tradefed.config.IConfigurationReceiver; |
| import com.android.tradefed.config.Option; |
| import com.android.tradefed.config.Option.Importance; |
| import com.android.tradefed.config.OptionClass; |
| import com.android.tradefed.config.OptionCopier; |
| import com.android.tradefed.config.OptionSetter; |
| import com.android.tradefed.device.DeviceNotAvailableException; |
| import com.android.tradefed.device.ITestDevice; |
| import com.android.tradefed.device.metric.IMetricCollector; |
| import com.android.tradefed.device.metric.IMetricCollectorReceiver; |
| import com.android.tradefed.error.HarnessRuntimeException; |
| import com.android.tradefed.error.IHarnessException; |
| import com.android.tradefed.invoker.TestInformation; |
| import com.android.tradefed.invoker.logger.CurrentInvocation; |
| import com.android.tradefed.invoker.tracing.CloseableTraceScope; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; |
| import com.android.tradefed.result.FailureDescription; |
| import com.android.tradefed.result.ITestInvocationListener; |
| import com.android.tradefed.result.ResultForwarder; |
| import com.android.tradefed.result.TestDescription; |
| import com.android.tradefed.result.error.ErrorIdentifier; |
| import com.android.tradefed.result.error.InfraErrorIdentifier; |
| import com.android.tradefed.testtype.host.PrettyTestEventLogger; |
| import com.android.tradefed.testtype.junit4.CarryDnaeError; |
| import com.android.tradefed.testtype.junit4.ExceptionThrowingRunnerWrapper; |
| import com.android.tradefed.testtype.junit4.JUnit4ResultForwarder; |
| import com.android.tradefed.testtype.suite.ModuleDefinition; |
| import com.android.tradefed.util.FileUtil; |
| import com.android.tradefed.util.JUnit4TestFilter; |
| import com.android.tradefed.util.StreamUtil; |
| import com.android.tradefed.util.TestFilterHelper; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| |
| import junit.framework.Test; |
| import junit.framework.TestCase; |
| import junit.framework.TestSuite; |
| |
| import org.junit.Ignore; |
| import org.junit.internal.runners.ErrorReportingRunner; |
| import org.junit.runner.Description; |
| import org.junit.runner.JUnitCore; |
| import org.junit.runner.Request; |
| import org.junit.runner.RunWith; |
| import org.junit.runner.Runner; |
| import org.junit.runner.notification.RunNotifier; |
| import org.junit.runners.Suite.SuiteClasses; |
| |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.AnnotatedElement; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.time.Duration; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Deque; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.TimeUnit; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| import java.util.regex.Pattern; |
| |
| /** |
| * A test runner for JUnit host based tests. If the test to be run implements {@link IDeviceTest} |
| * this runner will pass a reference to the device. |
| */ |
| @OptionClass(alias = "host") |
| public class HostTest |
| implements IDeviceTest, |
| ITestFilterReceiver, |
| ITestAnnotationFilterReceiver, |
| IRemoteTest, |
| ITestCollector, |
| IBuildReceiver, |
| IAbiReceiver, |
| IShardableTest, |
| IRuntimeHintProvider, |
| IConfigurationReceiver { |
| |
| @Option(name = "class", description = "The JUnit test classes to run, in the format " |
| + "<package>.<class>. eg. \"com.android.foo.Bar\". This field can be repeated.", |
| importance = Importance.IF_UNSET) |
| private Set<String> mClasses = new LinkedHashSet<>(); |
| |
| @Option(name = "method", description = "The name of the method in the JUnit TestCase to run. " |
| + "eg. \"testFooBar\"", |
| importance = Importance.IF_UNSET) |
| private String mMethodName; |
| |
| @Option( |
| name = "jar", |
| description = "The jars containing the JUnit test class to run.", |
| importance = Importance.IF_UNSET |
| ) |
| private Set<String> mJars = new HashSet<>(); |
| |
| public static final String SET_OPTION_NAME = "set-option"; |
| public static final String SET_OPTION_DESC = |
| "Options to be passed down to the class under test, key and value should be separated" |
| + " by colon \":\"; for example, if class under test supports \"--iteration 1\"" |
| + " from a command line, it should be passed in as \"--set-option iteration:1\" or" |
| + " \"--set-option iteration:key=value\" for passing options to map. Values that" |
| + " contain \":\" or \"=\" can be escaped with a backslash. A particular class can" |
| + " be targeted by specifying it. \" --set-option <fully qualified class>:<option" |
| + " name>:<option value>\""; |
| |
| @Option(name = SET_OPTION_NAME, description = SET_OPTION_DESC) |
| private List<String> mKeyValueOptions = new ArrayList<>(); |
| |
| @Option(name = "include-annotation", |
| description = "The set of annotations a test must have to be run.") |
| private Set<String> mIncludeAnnotations = new HashSet<>(); |
| |
| @Option(name = "exclude-annotation", |
| description = "The set of annotations to exclude tests from running. A test must have " |
| + "none of the annotations in this list to run.") |
| private Set<String> mExcludeAnnotations = new HashSet<>(); |
| |
| /** |
| * It is strongly recommended that clients set include and exclude filters at the suite level |
| * via the ITestFilter interface rather than relying on include-filter and |
| * exclude-filter @Options. |
| */ |
| @Option( |
| name = "include-filter", |
| description = "The set of annotations a test must have to be run.") |
| private Set<String> mIncludeFilters = new HashSet<>(); |
| |
| /** |
| * It is strongly recommended that clients set include and exclude filters at the suite level |
| * via the ITestFilter interface rather than relying on include-filter and |
| * exclude-filter @Options. |
| */ |
| @Option( |
| name = "exclude-filter", |
| description = |
| "The set of annotations to exclude tests from running. A test must have " |
| + "none of the annotations in this list to run.") |
| private Set<String> mExcludeFilters = new HashSet<>(); |
| |
| @Option(name = "collect-tests-only", |
| description = "Only invoke the instrumentation to collect list of applicable test " |
| + "cases. All test run callbacks will be triggered, but test execution will " |
| + "not be actually carried out.") |
| private boolean mCollectTestsOnly = false; |
| |
| @Option( |
| name = "runtime-hint", |
| isTimeVal = true, |
| description = "The hint about the test's runtime." |
| ) |
| private long mRuntimeHint = 60000; // 1 minute |
| |
| enum ShardUnit { |
| CLASS, METHOD; |
| } |
| |
| @Option(name = "shard-unit", |
| description = "Shard by class or method") |
| private ShardUnit mShardUnit = ShardUnit.CLASS; |
| |
| @Option( |
| name = "enable-pretty-logs", |
| description = |
| "whether or not to enable a logging for each test start and end on both host and " |
| + "device side." |
| ) |
| private boolean mEnableHostDeviceLogs = true; |
| |
| @Option( |
| name = TestTimeoutEnforcer.TEST_CASE_TIMEOUT_OPTION, |
| description = TestTimeoutEnforcer.TEST_CASE_TIMEOUT_DESCRIPTION) |
| private Duration mTestCaseTimeout = Duration.ofSeconds(0L); |
| |
| private IConfiguration mConfig; |
| private ITestDevice mDevice; |
| private IBuildInfo mBuildInfo; |
| private IAbi mAbi; |
| private TestInformation mTestInfo; |
| private TestFilterHelper mFilterHelper; |
| private boolean mSkipTestClassCheck = false; |
| |
| private List<Object> mTestMethods; |
| private List<Class<?>> mLoadedClasses = new ArrayList<>(); |
| private List<URLClassLoader> mOpenClassLoaders = new ArrayList<>(); |
| |
| // Initialized as -1 to indicate that this value needs to be recalculated |
| // when test count is requested. |
| private int mNumTestCases = -1; |
| |
| private List<File> mJUnit4JarFiles = new ArrayList<>(); |
| |
| private static final String EXCLUDE_NO_TEST_FAILURE = "org.junit.runner.manipulation.Filter"; |
| private static final String TEST_FULL_NAME_FORMAT = "%s#%s"; |
| |
| /** Track the downloaded files. */ |
| private List<File> mDownloadedFiles = new ArrayList<>(); |
| |
| public HostTest() { |
| mFilterHelper = |
| new TestFilterHelper( |
| mIncludeFilters, mExcludeFilters, mIncludeAnnotations, mExcludeAnnotations); |
| } |
| |
| public void setTestInformation(TestInformation testInfo) { |
| mTestInfo = testInfo; |
| } |
| |
| @Override |
| public void setConfiguration(IConfiguration configuration) { |
| mConfig = configuration; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public ITestDevice getDevice() { |
| return mDevice; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setDevice(ITestDevice device) { |
| mDevice = device; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public long getRuntimeHint() { |
| return mRuntimeHint; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void setAbi(IAbi abi) { |
| mAbi = abi; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public IAbi getAbi() { |
| return mAbi; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setBuild(IBuildInfo buildInfo) { |
| mBuildInfo = buildInfo; |
| } |
| |
| /** |
| * Get the build info received by HostTest. |
| * |
| * @return the {@link IBuildInfo} |
| */ |
| protected IBuildInfo getBuild() { |
| return mBuildInfo; |
| } |
| |
| /** |
| * @return true if shard-unit is method; false otherwise |
| */ |
| private boolean shardUnitIsMethod() { |
| return ShardUnit.METHOD.equals(mShardUnit); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void addIncludeFilter(String filter) { |
| // If filters change, reset test count so we recompute it next time it's requested. |
| mNumTestCases = -1; |
| mFilterHelper.addIncludeFilter(filter); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void addAllIncludeFilters(Set<String> filters) { |
| mNumTestCases = -1; |
| mFilterHelper.addAllIncludeFilters(filters); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void clearIncludeFilters() { |
| mNumTestCases = -1; |
| mFilterHelper.clearIncludeFilters(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void addExcludeFilter(String filter) { |
| mNumTestCases = -1; |
| mFilterHelper.addExcludeFilter(filter); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Set<String> getIncludeFilters() { |
| return mFilterHelper.getIncludeFilters(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Set<String> getExcludeFilters() { |
| return mFilterHelper.getExcludeFilters(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void addAllExcludeFilters(Set<String> filters) { |
| mNumTestCases = -1; |
| mFilterHelper.addAllExcludeFilters(filters); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void clearExcludeFilters() { |
| mNumTestCases = -1; |
| mFilterHelper.clearExcludeFilters(); |
| } |
| |
| /** |
| * Return the number of test cases across all classes part of the tests |
| */ |
| public int countTestCases() { |
| if (mTestMethods != null) { |
| return mTestMethods.size(); |
| } else if (mNumTestCases >= 0) { |
| return mNumTestCases; |
| } |
| // Ensure filters are set in the helper |
| mFilterHelper.addAllIncludeAnnotation(mIncludeAnnotations); |
| mFilterHelper.addAllExcludeAnnotation(mExcludeAnnotations); |
| mFilterHelper.addAllIncludeFilters(mIncludeFilters); |
| mFilterHelper.addAllExcludeFilters(mExcludeFilters); |
| |
| int count = 0; |
| for (Class<?> classObj : getClasses()) { |
| if (IRemoteTest.class.isAssignableFrom(classObj) |
| || Test.class.isAssignableFrom(classObj)) { |
| TestSuite suite = collectTests(collectClasses(classObj)); |
| int suiteCount = suite.countTestCases(); |
| if (suiteCount == 0 |
| && IRemoteTest.class.isAssignableFrom(classObj) |
| && !Test.class.isAssignableFrom(classObj)) { |
| // If it's a pure IRemoteTest we count the run() as one test. |
| count++; |
| } else { |
| count += suiteCount; |
| } |
| } else if (hasJUnit4Annotation(classObj)) { |
| Request req = Request.aClass(classObj); |
| req = req.filterWith(new JUnit4TestFilter(mFilterHelper, mJUnit4JarFiles)); |
| Runner checkRunner = req.getRunner(); |
| // If no tests are remaining after filtering, checkRunner is ErrorReportingRunner. |
| // testCount() for ErrorReportingRunner returns 1, skip this classObj in this case. |
| if (checkRunner instanceof ErrorReportingRunner) { |
| if (!EXCLUDE_NO_TEST_FAILURE.equals( |
| checkRunner.getDescription().getClassName())) { |
| // If after filtering we have remaining tests that are malformed, we still |
| // count them toward the total number of tests. (each malformed class will |
| // count as 1 in the testCount()). |
| count += checkRunner.testCount(); |
| } |
| } else { |
| count += checkRunner.testCount(); |
| } |
| } else { |
| count++; |
| } |
| } |
| return mNumTestCases = count; |
| } |
| |
| /** |
| * Clear then set a class name to be run. |
| */ |
| protected void setClassName(String className) { |
| mClasses.clear(); |
| mClasses.add(className); |
| } |
| |
| @VisibleForTesting |
| public Set<String> getClassNames() { |
| return mClasses; |
| } |
| |
| void setMethodName(String methodName) { |
| mMethodName = methodName; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void addIncludeAnnotation(String annotation) { |
| mIncludeAnnotations.add(annotation); |
| mFilterHelper.addIncludeAnnotation(annotation); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void addAllIncludeAnnotation(Set<String> annotations) { |
| mIncludeAnnotations.addAll(annotations); |
| mFilterHelper.addAllIncludeAnnotation(annotations); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void addExcludeAnnotation(String notAnnotation) { |
| mExcludeAnnotations.add(notAnnotation); |
| mFilterHelper.addExcludeAnnotation(notAnnotation); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void addAllExcludeAnnotation(Set<String> notAnnotations) { |
| mExcludeAnnotations.addAll(notAnnotations); |
| mFilterHelper.addAllExcludeAnnotation(notAnnotations); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Set<String> getIncludeAnnotations() { |
| return mIncludeAnnotations; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Set<String> getExcludeAnnotations() { |
| return mExcludeAnnotations; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void clearIncludeAnnotations() { |
| mIncludeAnnotations.clear(); |
| mFilterHelper.clearIncludeAnnotations(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void clearExcludeAnnotations() { |
| mExcludeAnnotations.clear(); |
| mFilterHelper.clearExcludeAnnotations(); |
| } |
| |
| /** |
| * Helper to set the information of an object based on some of its type. |
| */ |
| private void setTestObjectInformation(Object testObj) { |
| if (testObj instanceof IBuildReceiver) { |
| if (mBuildInfo == null) { |
| throw new IllegalArgumentException("Missing build information"); |
| } |
| ((IBuildReceiver)testObj).setBuild(mBuildInfo); |
| } |
| if (testObj instanceof IDeviceTest) { |
| if (mDevice == null) { |
| throw new IllegalArgumentException("Missing device"); |
| } |
| ((IDeviceTest)testObj).setDevice(mDevice); |
| } |
| // We are more flexible about abi info since not always available. |
| if (testObj instanceof IAbiReceiver) { |
| ((IAbiReceiver)testObj).setAbi(mAbi); |
| } |
| if (testObj instanceof IInvocationContextReceiver) { |
| ((IInvocationContextReceiver) testObj).setInvocationContext(mTestInfo.getContext()); |
| } |
| if (testObj instanceof ITestInformationReceiver) { |
| ((ITestInformationReceiver) testObj).setTestInformation(mTestInfo); |
| } |
| // managed runner should have the same set-option to pass option too. |
| if (testObj instanceof ISetOptionReceiver) { |
| try { |
| OptionSetter setter = new OptionSetter(testObj); |
| for (String item : mKeyValueOptions) { |
| setter.setOptionValue(SET_OPTION_NAME, item); |
| } |
| } catch (ConfigurationException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| // TODO(olivernguyen): Clean this up after instrumenting runInstrumentationTests(...) API. |
| if (testObj instanceof IConfigurationReceiver) { |
| ((IConfigurationReceiver) testObj).setConfiguration(mConfig); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void run(TestInformation testInfo, ITestInvocationListener listener) |
| throws DeviceNotAvailableException { |
| mTestInfo = testInfo; |
| // Ensure filters are set in the helper |
| mFilterHelper.addAllIncludeAnnotation(mIncludeAnnotations); |
| mFilterHelper.addAllExcludeAnnotation(mExcludeAnnotations); |
| mFilterHelper.addAllIncludeFilters(mIncludeFilters); |
| mFilterHelper.addAllExcludeFilters(mExcludeFilters); |
| |
| try { |
| try { |
| List<Class<?>> classes = getClasses(); |
| if (!mSkipTestClassCheck) { |
| if (classes.isEmpty()) { |
| throw new HarnessRuntimeException( |
| "No '--class' option was specified.", |
| InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR); |
| } |
| } |
| if (mMethodName != null && classes.size() > 1) { |
| throw new HarnessRuntimeException( |
| String.format( |
| "'--method' only supports one '--class' name. Multiple were " |
| + "given: '%s'", |
| classes), |
| InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR); |
| } |
| } catch (RuntimeException e) { |
| listener.testRunStarted(this.getClass().getCanonicalName(), 0); |
| listener.testRunFailed(createFromException(e)); |
| listener.testRunEnded(0L, new HashMap<String, Metric>()); |
| return; |
| } |
| |
| // Add a pretty logger to the events to mark clearly start/end of test cases. |
| if (mEnableHostDeviceLogs) { |
| PrettyTestEventLogger logger = new PrettyTestEventLogger(mTestInfo.getDevices()); |
| listener = new ResultForwarder(logger, listener); |
| } |
| if (mTestMethods != null) { |
| runTestCases(listener); |
| } else { |
| runTestClasses(listener); |
| } |
| } finally { |
| mLoadedClasses.clear(); |
| for (URLClassLoader cl : mOpenClassLoaders) { |
| StreamUtil.close(cl); |
| } |
| mOpenClassLoaders.clear(); |
| } |
| } |
| |
| private void runTestClasses(ITestInvocationListener listener) |
| throws DeviceNotAvailableException { |
| for (Class<?> classObj : getClasses()) { |
| if (IRemoteTest.class.isAssignableFrom(classObj)) { |
| IRemoteTest test = (IRemoteTest) loadObject(classObj); |
| applyFilters(classObj, test); |
| runRemoteTest(listener, test); |
| } else if (Test.class.isAssignableFrom(classObj)) { |
| TestSuite junitTest = collectTests(collectClasses(classObj)); |
| // Resolve dynamic files for the junit3 test objects |
| Enumeration<Test> allTest = junitTest.tests(); |
| while (allTest.hasMoreElements()) { |
| Test testObj = allTest.nextElement(); |
| mDownloadedFiles.addAll(resolveRemoteFileForObject(testObj)); |
| } |
| try { |
| runJUnit3Tests(listener, junitTest, classObj.getName()); |
| } finally { |
| for (File f : mDownloadedFiles) { |
| FileUtil.recursiveDelete(f); |
| } |
| } |
| } else if (hasJUnit4Annotation(classObj)) { |
| // Include the method name filtering |
| Set<String> includes = mFilterHelper.getIncludeFilters(); |
| if (mMethodName != null) { |
| includes.add(String.format(TEST_FULL_NAME_FORMAT, classObj.getName(), |
| mMethodName)); |
| } |
| // Running in a full JUnit4 manner, no downgrade to JUnit3 {@link Test} |
| Request req = Request.aClass(classObj); |
| Runner checkRunner = null; |
| try { |
| req = req.filterWith(new JUnit4TestFilter(mFilterHelper, mJUnit4JarFiles)); |
| checkRunner = req.getRunner(); |
| } catch (RuntimeException e) { |
| listener.testRunStarted(classObj.getName(), 0); |
| listener.testRunFailed(createFromException(e)); |
| listener.testRunEnded(0L, new HashMap<String, Metric>()); |
| return; |
| } |
| runJUnit4Tests(listener, checkRunner, classObj.getName()); |
| } else { |
| throw new IllegalArgumentException( |
| String.format("%s is not a supported test", classObj.getName())); |
| } |
| } |
| } |
| |
| private void runTestCases(ITestInvocationListener listener) throws DeviceNotAvailableException { |
| Set<String> skippedTests = new LinkedHashSet<>(); |
| for (Object obj : getTestMethods()) { |
| if (IRemoteTest.class.isInstance(obj)) { |
| IRemoteTest test = (IRemoteTest) obj; |
| runRemoteTest(listener, test); |
| } else if (TestSuite.class.isInstance(obj)) { |
| TestSuite junitTest = (TestSuite) obj; |
| if (!runJUnit3Tests(listener, junitTest, junitTest.getName())) { |
| skippedTests.add(junitTest.getName()); |
| } |
| } else if (Description.class.isInstance(obj)) { |
| // Running in a full JUnit4 manner, no downgrade to JUnit3 {@link Test} |
| Description desc = (Description) obj; |
| Request req = Request.aClass(desc.getTestClass()); |
| Runner checkRunner = req.filterWith(desc).getRunner(); |
| try { |
| runJUnit4Tests(listener, checkRunner, desc.getClassName()); |
| } catch (RuntimeException e) { |
| listener.testRunStarted(desc.getClassName(), 0); |
| listener.testRunFailed(createFromException(e)); |
| listener.testRunEnded(0L, new HashMap<String, Metric>()); |
| throw e; |
| } |
| } else { |
| throw new IllegalArgumentException( |
| String.format("%s is not a supported test", obj)); |
| } |
| } |
| CLog.v("The following classes were skipped due to no test cases found: %s", skippedTests); |
| } |
| |
| private void runRemoteTest(ITestInvocationListener listener, IRemoteTest test) |
| throws DeviceNotAvailableException { |
| if (mCollectTestsOnly) { |
| // Collect only mode is propagated to the test. |
| if (test instanceof ITestCollector) { |
| ((ITestCollector) test).setCollectTestsOnly(true); |
| } else { |
| throw new IllegalArgumentException( |
| String.format( |
| "%s does not implement ITestCollector", test.getClass())); |
| } |
| } |
| // Set collectors for completeness but this isn't really supported as part of HostTest. |
| if (test instanceof IMetricCollectorReceiver) { |
| ((IMetricCollectorReceiver) test) |
| .setMetricCollectors(new ArrayList<IMetricCollector>()); |
| } |
| test.run(mTestInfo, listener); |
| } |
| |
| /** Returns True if some tests were executed, false otherwise. */ |
| private boolean runJUnit3Tests( |
| ITestInvocationListener listener, TestSuite junitTest, String className) |
| throws DeviceNotAvailableException { |
| if (mCollectTestsOnly) { |
| // Collect only mode, fake the junit test execution. |
| int testCount = junitTest.countTestCases(); |
| listener.testRunStarted(className, testCount); |
| HashMap<String, Metric> empty = new HashMap<>(); |
| for (int i = 0; i < testCount; i++) { |
| Test t = junitTest.testAt(i); |
| // Test does not have a getName method. |
| // using the toString format instead: <testName>(className) |
| String testName = t.toString().split("\\(")[0]; |
| TestDescription testId = new TestDescription(t.getClass().getName(), testName); |
| listener.testStarted(testId); |
| listener.testEnded(testId, empty); |
| } |
| HashMap<String, Metric> emptyMap = new HashMap<>(); |
| listener.testRunEnded(0, emptyMap); |
| if (testCount > 0) { |
| return true; |
| } else { |
| return false; |
| } |
| } else { |
| if (mTestCaseTimeout.toMillis() > 0L) { |
| listener = |
| new TestTimeoutEnforcer( |
| mTestCaseTimeout.toMillis(), TimeUnit.MILLISECONDS, listener); |
| } |
| try (CloseableTraceScope ignored = new CloseableTraceScope(className)) { |
| return JUnitRunUtil.runTest(listener, junitTest, className, mTestInfo); |
| } |
| } |
| } |
| |
| private void runJUnit4Tests( |
| ITestInvocationListener listener, Runner checkRunner, String className) |
| throws DeviceNotAvailableException { |
| JUnitCore runnerCore = new JUnitCore(); |
| if (mTestCaseTimeout.toMillis() > 0L) { |
| listener = |
| new TestTimeoutEnforcer( |
| mTestCaseTimeout.toMillis(), TimeUnit.MILLISECONDS, listener); |
| } |
| JUnit4ResultForwarder list = new JUnit4ResultForwarder(listener); |
| runnerCore.addListener(list); |
| |
| if (!(checkRunner instanceof ErrorReportingRunner)) { |
| // If no tests are remaining after filtering, it returns an Error Runner. |
| long startTime = System.currentTimeMillis(); |
| listener.testRunStarted(className, checkRunner.testCount()); |
| try (CloseableTraceScope ignore = new CloseableTraceScope(className)) { |
| if (mCollectTestsOnly) { |
| fakeDescriptionExecution(checkRunner.getDescription(), list); |
| } else { |
| setTestObjectInformation(checkRunner); |
| ExceptionThrowingRunnerWrapper runnerWrapper = |
| new ExceptionThrowingRunnerWrapper(checkRunner, mTestInfo); |
| runnerCore.run(runnerWrapper); |
| } |
| } catch (CarryDnaeError e) { |
| throw e.getDeviceNotAvailableException(); |
| } finally { |
| for (Description d : findIgnoredClass(checkRunner.getDescription())) { |
| TestDescription testDescription = |
| new TestDescription(d.getClassName(), "No Tests"); |
| listener.testStarted(testDescription); |
| listener.testIgnored(testDescription); |
| listener.testEnded(testDescription, new HashMap<String, Metric>()); |
| } |
| listener.testRunEnded( |
| System.currentTimeMillis() - startTime, new HashMap<String, Metric>()); |
| } |
| } else { |
| // Special case where filtering leaves no tests to run, we report no failure |
| // in this case. |
| if (EXCLUDE_NO_TEST_FAILURE.equals( |
| checkRunner.getDescription().getClassName())) { |
| listener.testRunStarted(className, 0); |
| listener.testRunEnded(0, new HashMap<String, Metric>()); |
| } else { |
| // Run the Error runner to get the failures from test classes. |
| listener.testRunStarted(className, checkRunner.testCount()); |
| RunNotifier failureNotifier = new RunNotifier(); |
| failureNotifier.addListener(list); |
| checkRunner.run(failureNotifier); |
| listener.testRunEnded(0, new HashMap<String, Metric>()); |
| } |
| } |
| } |
| |
| /** Search and return all the classes that are @Ignored */ |
| private List<Description> findIgnoredClass(Description description) { |
| List<Description> ignoredClass = new ArrayList<>(); |
| if (description.isSuite()) { |
| for (Description childDescription : description.getChildren()) { |
| ignoredClass.addAll(findIgnoredClass(childDescription)); |
| } |
| } else { |
| if (description.getMethodName() == null) { |
| for (Annotation a : description.getAnnotations()) { |
| if (a.annotationType() != null && a.annotationType().equals(Ignore.class)) { |
| ignoredClass.add(description); |
| break; |
| } |
| } |
| } |
| } |
| return ignoredClass; |
| } |
| |
| /** |
| * Helper to fake the execution of JUnit4 Tests, using the {@link Description} |
| */ |
| private void fakeDescriptionExecution(Description desc, JUnit4ResultForwarder listener) { |
| if (desc.getMethodName() == null || !desc.getChildren().isEmpty()) { |
| for (Description child : desc.getChildren()) { |
| fakeDescriptionExecution(child, listener); |
| } |
| } else { |
| try { |
| listener.testStarted(desc); |
| listener.testFinished(desc); |
| } catch (Exception e) { |
| // Should never happen |
| CLog.e(e); |
| } |
| } |
| } |
| |
| private Set<Class<?>> collectClasses(Class<?> classObj) { |
| Set<Class<?>> classes = new HashSet<>(); |
| if (TestSuite.class.isAssignableFrom(classObj)) { |
| TestSuite testObj = (TestSuite) loadObject(classObj); |
| classes.addAll(getClassesFromSuite(testObj)); |
| } else { |
| classes.add(classObj); |
| } |
| return classes; |
| } |
| |
| private Set<Class<?>> getClassesFromSuite(TestSuite suite) { |
| Set<Class<?>> classes = new HashSet<>(); |
| Enumeration<Test> tests = suite.tests(); |
| while (tests.hasMoreElements()) { |
| Test test = tests.nextElement(); |
| if (test instanceof TestSuite) { |
| classes.addAll(getClassesFromSuite((TestSuite) test)); |
| } else { |
| classes.addAll(collectClasses(test.getClass())); |
| } |
| } |
| return classes; |
| } |
| |
| private TestSuite collectTests(Set<Class<?>> classes) { |
| TestSuite suite = new TestSuite(); |
| for (Class<?> classObj : classes) { |
| String packageName = classObj.getPackage().getName(); |
| String className = classObj.getName(); |
| Method[] methods = null; |
| if (mMethodName == null) { |
| methods = classObj.getMethods(); |
| } else { |
| try { |
| methods = new Method[] { |
| classObj.getMethod(mMethodName, (Class[]) null) |
| }; |
| } catch (NoSuchMethodException e) { |
| throw new IllegalArgumentException( |
| String.format("Cannot find %s#%s", className, mMethodName), e); |
| } |
| } |
| |
| for (Method method : methods) { |
| if (!Modifier.isPublic(method.getModifiers()) |
| || !method.getReturnType().equals(Void.TYPE) |
| || method.getParameterTypes().length > 0 |
| || !method.getName().startsWith("test") |
| || !mFilterHelper.shouldRun(packageName, classObj, method)) { |
| continue; |
| } |
| Test testObj = (Test) loadObject(classObj, false); |
| if (testObj instanceof TestCase) { |
| ((TestCase)testObj).setName(method.getName()); |
| } |
| suite.addTest(testObj); |
| } |
| } |
| return suite; |
| } |
| |
| private List<Object> getTestMethods() throws IllegalArgumentException { |
| if (mTestMethods != null) { |
| return mTestMethods; |
| } |
| mTestMethods = new ArrayList<>(); |
| mFilterHelper.addAllIncludeAnnotation(mIncludeAnnotations); |
| mFilterHelper.addAllExcludeAnnotation(mExcludeAnnotations); |
| List<Class<?>> classes = getClasses(); |
| for (Class<?> classObj : classes) { |
| if (Test.class.isAssignableFrom(classObj)) { |
| TestSuite suite = collectTests(collectClasses(classObj)); |
| for (int i = 0; i < suite.testCount(); i++) { |
| TestSuite singletonSuite = new TestSuite(); |
| singletonSuite.setName(classObj.getName()); |
| Test testObj = suite.testAt(i); |
| singletonSuite.addTest(testObj); |
| if (IRemoteTest.class.isInstance(testObj)) { |
| setTestObjectInformation(testObj); |
| } |
| mTestMethods.add(singletonSuite); |
| } |
| } else if (IRemoteTest.class.isAssignableFrom(classObj)) { |
| // a pure IRemoteTest is considered a test method itself |
| IRemoteTest test = (IRemoteTest) loadObject(classObj); |
| applyFilters(classObj, test); |
| mTestMethods.add(test); |
| } else if (hasJUnit4Annotation(classObj)) { |
| // Running in a full JUnit4 manner, no downgrade to JUnit3 {@link Test} |
| Request req = Request.aClass(classObj); |
| // Include the method name filtering |
| Set<String> includes = mFilterHelper.getIncludeFilters(); |
| if (mMethodName != null) { |
| includes.add(String.format(TEST_FULL_NAME_FORMAT, classObj.getName(), |
| mMethodName)); |
| } |
| |
| req = req.filterWith(new JUnit4TestFilter(mFilterHelper, mJUnit4JarFiles)); |
| Runner checkRunner = req.getRunner(); |
| Deque<Description> descriptions = new ArrayDeque<>(); |
| descriptions.push(checkRunner.getDescription()); |
| while (!descriptions.isEmpty()) { |
| Description desc = descriptions.pop(); |
| if (desc.isTest()) { |
| mTestMethods.add(desc); |
| } |
| List<Description> children = desc.getChildren(); |
| Collections.reverse(children); |
| for (Description child : children) { |
| descriptions.push(child); |
| } |
| } |
| } else { |
| throw new IllegalArgumentException( |
| String.format("%s is not a supported test", classObj.getName())); |
| } |
| } |
| return mTestMethods; |
| } |
| |
| protected final List<Class<?>> getClasses() throws IllegalArgumentException { |
| if (!mLoadedClasses.isEmpty()) { |
| return mLoadedClasses; |
| } |
| // Use a set to avoid repeat between filters and jar search |
| Set<String> classNames = new HashSet<>(); |
| List<Class<?>> classes = mLoadedClasses; |
| for (String className : mClasses) { |
| if (classNames.contains(className)) { |
| continue; |
| } |
| IllegalArgumentException initialError = null; |
| try { |
| classes.add(Class.forName(className, true, getClassLoader())); |
| classNames.add(className); |
| } catch (ClassNotFoundException e) { |
| initialError = |
| new IllegalArgumentException( |
| String.format("Could not load Test class %s", className), e); |
| } |
| if (initialError != null) { |
| // Fallback search a jar for the module under tests if any. |
| String moduleName = |
| mTestInfo |
| .getContext() |
| .getAttributes() |
| .getUniqueMap() |
| .get(ModuleDefinition.MODULE_NAME); |
| if (moduleName != null) { |
| URLClassLoader cl = null; |
| try { |
| File f = getJarFile(moduleName + ".jar", mTestInfo); |
| URL[] urls = {f.toURI().toURL()}; |
| cl = URLClassLoader.newInstance(urls); |
| mJUnit4JarFiles.add(f); |
| Class<?> cls = cl.loadClass(className); |
| classes.add(cls); |
| classNames.add(className); |
| initialError = null; |
| mOpenClassLoaders.add(cl); |
| } catch (FileNotFoundException |
| | MalformedURLException |
| | ClassNotFoundException fallbackSearch) { |
| StreamUtil.close(cl); |
| CLog.e( |
| "Fallback search for a jar containing '%s' didn't work.Consider" |
| + " using --jar option directly instead of using --class", |
| className); |
| } |
| } |
| } |
| if (initialError != null) { |
| throw initialError; |
| } |
| } |
| URLClassLoader cl = null; |
| // Inspect for the jar files |
| for (String jarName : mJars) { |
| JarFile jarFile = null; |
| try { |
| File file = getJarFile(jarName, mTestInfo); |
| jarFile = new JarFile(file); |
| Enumeration<JarEntry> e = jarFile.entries(); |
| URL[] urls = {file.toURI().toURL()}; |
| cl = URLClassLoader.newInstance(urls); |
| mJUnit4JarFiles.add(file); |
| mOpenClassLoaders.add(cl); |
| |
| while (e.hasMoreElements()) { |
| JarEntry je = e.nextElement(); |
| if (je.isDirectory() |
| || !je.getName().endsWith(".class") |
| || je.getName().contains("$")) { |
| continue; |
| } |
| String className = getClassName(je.getName()); |
| if (classNames.contains(className)) { |
| continue; |
| } |
| try { |
| Class<?> cls = cl.loadClass(className); |
| int modifiers = cls.getModifiers(); |
| if ((IRemoteTest.class.isAssignableFrom(cls) |
| || Test.class.isAssignableFrom(cls) |
| || hasJUnit4Annotation(cls)) |
| && !Modifier.isStatic(modifiers) |
| && !Modifier.isPrivate(modifiers) |
| && !Modifier.isProtected(modifiers) |
| && !Modifier.isInterface(modifiers) |
| && !Modifier.isAbstract(modifiers)) { |
| if (!mClasses.isEmpty() && !mClasses.contains(className)) { |
| continue; |
| } |
| classes.add(cls); |
| classNames.add(className); |
| } |
| } catch (UnsupportedClassVersionError ucve) { |
| throw new IllegalArgumentException( |
| String.format( |
| "Could not load class %s from jar %s. Reason:\n%s", |
| className, jarName, StreamUtil.getStackTrace(ucve))); |
| } catch (ClassNotFoundException cnfe) { |
| throw new IllegalArgumentException( |
| String.format("Cannot find test class %s", className)); |
| } catch (IllegalAccessError | NoClassDefFoundError err) { |
| // IllegalAccessError can happen when the class or one of its super |
| // class/interfaces are package-private. We can't load such class from |
| // here (= outside of the package). Since our intention is not to load |
| // all classes in the jar, but to find our the main test classes, this |
| // can be safely skipped. |
| // NoClassDefFoundErrror is also okay because certain CTS test cases |
| // might statically link to a jar library (e.g. tools.jar from JDK) |
| // where certain internal classes in the library are referencing |
| // classes that are not available in the jar. Again, since our goal here |
| // is to find test classes, this can be safely skipped. |
| continue; |
| } |
| } |
| } catch (IOException e) { |
| CLog.e(e); |
| throw new IllegalArgumentException(e); |
| } finally { |
| StreamUtil.close(jarFile); |
| } |
| } |
| return classes; |
| } |
| |
| /** Returns the default classloader. */ |
| @VisibleForTesting |
| protected ClassLoader getClassLoader() { |
| return this.getClass().getClassLoader(); |
| } |
| |
| /** load the class object and set the test info (device, build). */ |
| protected Object loadObject(Class<?> classObj) { |
| return loadObject(classObj, true); |
| } |
| |
| /** |
| * Load the class object and set the test info if requested. |
| * |
| * @param classObj the class object to be loaded. |
| * @param setInfo True the test infos need to be set. |
| * @return The loaded object from the class. |
| */ |
| private Object loadObject(Class<?> classObj, boolean setInfo) throws IllegalArgumentException { |
| final String className = classObj.getName(); |
| try { |
| Object testObj = classObj.getDeclaredConstructor().newInstance(); |
| // set options |
| setOptionToLoadedObject(testObj, mKeyValueOptions); |
| // Set the test information if needed. |
| if (setInfo) { |
| setTestObjectInformation(testObj); |
| } |
| return testObj; |
| } catch (InstantiationException e) { |
| throw new IllegalArgumentException(String.format("Could not load Test class %s", |
| className), e); |
| } catch (IllegalAccessException e) { |
| throw new IllegalArgumentException(String.format("Could not load Test class %s", |
| className), e); |
| } catch (InvocationTargetException | NoSuchMethodException e) { |
| throw new IllegalArgumentException( |
| String.format("Could not load Test class %s", className), e); |
| } |
| } |
| |
| /** |
| * Helper for Device Runners to use to set options the same way as HostTest, from set-option. |
| * |
| * @param testObj the object that will receive the options. |
| * @param keyValueOptions the list of options formatted as HostTest set-option requires. |
| */ |
| public static void setOptionToLoadedObject(Object testObj, List<String> keyValueOptions) { |
| if (!keyValueOptions.isEmpty()) { |
| OptionSetter setter; |
| try { |
| setter = new OptionSetter(testObj); |
| } catch (ConfigurationException ce) { |
| CLog.e(ce); |
| throw new RuntimeException("error creating option setter", ce); |
| } |
| for (String item : keyValueOptions) { |
| // Support escaping ':' using lookbehind in the regex. The regex engine will |
| // step backwards to check for the escape char when it matches the delim char. |
| // If it doesn't find the escape char, then a match is registered. |
| String delim = ":"; |
| String esc = "\\"; |
| String regex = "(?<!" + Pattern.quote(esc) + ")" + Pattern.quote(delim); |
| String[] fields = item.split(regex); |
| String key; |
| String value; |
| if (fields.length == 3) { |
| String target = fields[0]; |
| if (testObj.getClass().getName().equals(target)) { |
| key = fields[1]; |
| value = |
| fields[2].replaceAll( |
| Pattern.quote(esc) + Pattern.quote(delim), delim); |
| } else { |
| // TODO: We should track that all targeted option end up assigned |
| // eventually. |
| CLog.d( |
| "Targeted option %s is not applicable to %s", |
| item, testObj.getClass().getName()); |
| continue; |
| } |
| } else if (fields.length == 2) { |
| key = fields[0]; |
| value = fields[1].replaceAll(Pattern.quote(esc) + Pattern.quote(delim), delim); |
| } else { |
| throw new RuntimeException(String.format("invalid option spec \"%s\"", item)); |
| } |
| try { |
| injectOption(setter, item, key, value); |
| } catch (ConfigurationException ce) { |
| CLog.e(ce); |
| throw new RuntimeException( |
| "error passing option '" |
| + item |
| + "' down to test class as key=" |
| + key |
| + " value=" |
| + value, |
| ce); |
| } |
| } |
| } |
| } |
| |
| private static void injectOption(OptionSetter setter, String origItem, String key, String value) |
| throws ConfigurationException { |
| String esc = "\\"; |
| String delim = "="; |
| String regex = "(?<!" + Pattern.quote(esc) + ")" + Pattern.quote(delim); |
| String escDelim = Pattern.quote(esc) + Pattern.quote(delim); |
| String[] values = value.split(regex); |
| if (values.length == 1) { |
| setter.setOptionValue(key, values[0].replaceAll(escDelim, delim)); |
| } else if (values.length == 2) { |
| setter.setOptionValue( |
| key, |
| values[0].replaceAll(escDelim, delim), |
| values[1].replaceAll(escDelim, delim)); |
| } else { |
| throw new RuntimeException( |
| String.format( |
| "set-option provided '%s' format is invalid. Only one " |
| + "'=' is allowed", |
| origItem)); |
| } |
| } |
| |
| /** |
| * Check if an elements that has annotation pass the filter. Exposed for unit testing. |
| * @param annotatedElement |
| * @return false if the test should not run. |
| */ |
| protected boolean shouldTestRun(AnnotatedElement annotatedElement) { |
| return mFilterHelper.shouldTestRun(annotatedElement); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setCollectTestsOnly(boolean shouldCollectTest) { |
| mCollectTestsOnly = shouldCollectTest; |
| } |
| |
| /** |
| * Helper to determine if we are dealing with a Test class with Junit4 annotations. |
| */ |
| protected boolean hasJUnit4Annotation(Class<?> classObj) { |
| if (classObj.isAnnotationPresent(SuiteClasses.class)) { |
| return true; |
| } |
| if (classObj.isAnnotationPresent(RunWith.class)) { |
| return true; |
| } |
| for (Method m : classObj.getMethods()) { |
| if (m.isAnnotationPresent(org.junit.Test.class)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Helper method to apply all the filters to an IRemoteTest. |
| */ |
| private void applyFilters(Class<?> classObj, IRemoteTest test) { |
| Set<String> includes = mFilterHelper.getIncludeFilters(); |
| if (mMethodName != null) { |
| includes.add(String.format(TEST_FULL_NAME_FORMAT, classObj.getName(), mMethodName)); |
| } |
| Set<String> excludes = mFilterHelper.getExcludeFilters(); |
| if (test instanceof ITestFilterReceiver) { |
| ((ITestFilterReceiver) test).addAllIncludeFilters(includes); |
| ((ITestFilterReceiver) test).addAllExcludeFilters(excludes); |
| } else if (!includes.isEmpty() || !excludes.isEmpty()) { |
| throw new IllegalArgumentException(String.format( |
| "%s does not implement ITestFilterReceiver", classObj.getName())); |
| } |
| if (test instanceof ITestAnnotationFilterReceiver) { |
| ((ITestAnnotationFilterReceiver) test).addAllIncludeAnnotation( |
| mIncludeAnnotations); |
| ((ITestAnnotationFilterReceiver) test).addAllExcludeAnnotation( |
| mExcludeAnnotations); |
| } |
| } |
| |
| /** We split by individual by either test class or method. */ |
| @Override |
| public Collection<IRemoteTest> split(Integer shardCount, TestInformation testInfo) { |
| if (shardCount == null) { |
| return null; |
| } |
| if (shardCount < 1) { |
| throw new IllegalArgumentException("Must have at least 1 shard"); |
| } |
| mTestInfo = testInfo; |
| List<IRemoteTest> listTests = new ArrayList<>(); |
| try { |
| List<Class<?>> classes = getClasses(); |
| if (classes.isEmpty()) { |
| throw new IllegalArgumentException("Missing Test class name"); |
| } |
| if (mMethodName != null && classes.size() > 1) { |
| throw new IllegalArgumentException("Method name given with multiple test classes"); |
| } |
| List<? extends Object> testObjects; |
| if (shardUnitIsMethod()) { |
| testObjects = getTestMethods(); |
| } else { |
| testObjects = classes; |
| // ignore shardCount when shard unit is class; |
| // simply shard by the number of classes |
| shardCount = testObjects.size(); |
| } |
| if (testObjects.size() == 1) { |
| return null; |
| } |
| int i = 0; |
| int numTotalTestCases = countTestCases(); |
| for (Object testObj : testObjects) { |
| Class<?> classObj = Class.class.isInstance(testObj) ? (Class<?>) testObj : null; |
| HostTest test; |
| if (i >= listTests.size()) { |
| test = createHostTest(classObj); |
| test.mRuntimeHint = 0; |
| // Carry over non-annotation filters to shards. |
| test.addAllExcludeFilters(mFilterHelper.getExcludeFilters()); |
| test.addAllIncludeFilters(mFilterHelper.getIncludeFilters()); |
| listTests.add(test); |
| } |
| test = (HostTest) listTests.get(i); |
| Collection<? extends Object> subTests; |
| if (classObj != null) { |
| test.addClassName(classObj.getName()); |
| test.mJars = this.mJars; |
| subTests = test.mClasses; |
| } else { |
| test.addTestMethod(testObj); |
| subTests = test.mTestMethods; |
| } |
| if (numTotalTestCases == 0) { |
| // In case there is no tests left |
| test.mRuntimeHint = 0L; |
| } else { |
| test.mRuntimeHint = mRuntimeHint * subTests.size() / numTotalTestCases; |
| } |
| i = (i + 1) % shardCount; |
| } |
| } finally { |
| mLoadedClasses.clear(); |
| for (URLClassLoader cl : mOpenClassLoaders) { |
| StreamUtil.close(cl); |
| } |
| mOpenClassLoaders.clear(); |
| } |
| return listTests; |
| } |
| |
| private void addTestMethod(Object testObject) { |
| if (mTestMethods == null) { |
| mTestMethods = new ArrayList<>(); |
| mClasses.clear(); |
| } |
| mTestMethods.add(testObject); |
| if (IRemoteTest.class.isInstance(testObject)) { |
| addClassName(testObject.getClass().getName()); |
| } else if (TestSuite.class.isInstance(testObject)) { |
| addClassName(((TestSuite)testObject).getName()); |
| } else if (Description.class.isInstance(testObject)) { |
| addClassName(((Description)testObject).getTestClass().getName()); |
| } |
| } |
| |
| /** |
| * Add a class to be ran by HostTest. |
| */ |
| private void addClassName(String className) { |
| mClasses.add(className); |
| } |
| |
| /** |
| * Helper to create a HostTest instance when sharding. Override to return any child from |
| * HostTest. |
| */ |
| protected HostTest createHostTest(Class<?> classObj) { |
| HostTest test; |
| try { |
| test = this.getClass().getDeclaredConstructor().newInstance(); |
| } catch (InstantiationException |
| | IllegalAccessException |
| | InvocationTargetException |
| | NoSuchMethodException e) { |
| throw new RuntimeException(e); |
| } |
| OptionCopier.copyOptionsNoThrow(this, test); |
| if (classObj != null) { |
| test.setClassName(classObj.getName()); |
| } |
| // clean the jar option since we are loading directly from classes after. |
| test.mJars = new HashSet<>(); |
| // Copy the abi if available |
| test.setAbi(mAbi); |
| return test; |
| } |
| |
| private String getClassName(String name) { |
| // -6 because of .class |
| return name.substring(0, name.length() - 6).replace('/', '.'); |
| } |
| |
| /** |
| * Inspect several location where the artifact are usually located for different use cases to |
| * find our jar. |
| */ |
| @VisibleForTesting |
| protected File getJarFile(String jarName, TestInformation testInfo) |
| throws FileNotFoundException { |
| return testInfo.getDependencyFile(jarName, /* target first*/ false); |
| } |
| |
| @VisibleForTesting |
| DynamicRemoteFileResolver createResolver() { |
| DynamicRemoteFileResolver resolver = new DynamicRemoteFileResolver(); |
| resolver.setDevice(mDevice); |
| resolver.addExtraArgs(mConfig.getCommandOptions().getDynamicDownloadArgs()); |
| return resolver; |
| } |
| |
| private Set<File> resolveRemoteFileForObject(Object obj) { |
| try (CloseableTraceScope ignore = new CloseableTraceScope("infra:resolveRemoteFiles")) { |
| OptionSetter setter = new OptionSetter(obj); |
| return setter.validateRemoteFilePath(createResolver()); |
| } catch (BuildRetrievalError | ConfigurationException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private FailureDescription createFromException(Throwable exception) { |
| FailureDescription failure = |
| CurrentInvocation.createFailure(StreamUtil.getStackTrace(exception), null) |
| .setCause(exception); |
| if (exception instanceof IHarnessException) { |
| ErrorIdentifier id = ((IHarnessException) exception).getErrorId(); |
| failure.setErrorIdentifier(id); |
| if (id != null) { |
| failure.setFailureStatus(id.status()); |
| } |
| failure.setOrigin(((IHarnessException) exception).getOrigin()); |
| } |
| return failure; |
| } |
| } |