/* * Copyright (C) 2012 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.test.runner; import android.app.Instrumentation; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.Suppress; import android.util.Log; import com.android.test.runner.ClassPathScanner.ChainedClassNameFilter; import com.android.test.runner.ClassPathScanner.ExcludePackageNameFilter; import com.android.test.runner.ClassPathScanner.ExternalClassNameFilter; import org.junit.runner.Computer; import org.junit.runner.Description; import org.junit.runner.Request; import org.junit.runner.Runner; import org.junit.runner.manipulation.Filter; import org.junit.runners.model.InitializationError; import java.io.IOException; import java.io.PrintStream; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.Collection; import java.util.Collections; /** * Builds a {@link Request} from test classes in given apk paths, filtered on provided set of * restrictions. */ public class TestRequestBuilder { private static final String LOG_TAG = "TestRequestBuilder"; private String[] mApkPaths; private TestLoader mTestLoader; private Filter mFilter = new AnnotationExclusionFilter(Suppress.class); private PrintStream mWriter; private boolean mSkipExecution = false; /** * Filter that only runs tests whose method or class has been annotated with given filter. */ private static class AnnotationInclusionFilter extends Filter { private final Class extends Annotation> mAnnotationClass; AnnotationInclusionFilter(Class extends Annotation> annotation) { mAnnotationClass = annotation; } /** * {@inheritDoc} */ @Override public boolean shouldRun(Description description) { if (description.isTest()) { return description.getAnnotation(mAnnotationClass) != null || description.getTestClass().isAnnotationPresent(mAnnotationClass); } else { // don't filter out any test classes/suites, because their methods may have correct // annotation return true; } } /** * {@inheritDoc} */ @Override public String describe() { return String.format("annotation %s", mAnnotationClass.getName()); } } /** * Filter out tests whose method or class has been annotated with given filter. */ private static class AnnotationExclusionFilter extends Filter { private final Class extends Annotation> mAnnotationClass; AnnotationExclusionFilter(Class extends Annotation> annotation) { mAnnotationClass = annotation; } /** * {@inheritDoc} */ @Override public boolean shouldRun(Description description) { if (description.getTestClass().isAnnotationPresent(mAnnotationClass) || description.getAnnotation(mAnnotationClass) != null) { return false; } else { return true; } } /** * {@inheritDoc} */ @Override public String describe() { return String.format("not annotation %s", mAnnotationClass.getName()); } } public TestRequestBuilder(PrintStream writer, String... apkPaths) { mApkPaths = apkPaths; mTestLoader = new TestLoader(writer); } /** * Add a test class to be executed. All test methods in this class will be executed. * * @param className */ public void addTestClass(String className) { mTestLoader.loadClass(className); } /** * Adds a test method to run. *
* Currently only supports one test method to be run. */ public void addTestMethod(String testClassName, String testMethodName) { Class> clazz = mTestLoader.loadClass(testClassName); if (clazz != null) { mFilter = mFilter.intersect(Filter.matchMethodDescription( Description.createTestDescription(clazz, testMethodName))); } } /** * Run only tests with given size * @param testSize */ public void addTestSizeFilter(String testSize) { if ("small".equals(testSize)) { mFilter = mFilter.intersect(new AnnotationInclusionFilter(SmallTest.class)); } else if ("medium".equals(testSize)) { mFilter = mFilter.intersect(new AnnotationInclusionFilter(MediumTest.class)); } else if ("large".equals(testSize)) { mFilter = mFilter.intersect(new AnnotationInclusionFilter(LargeTest.class)); } else { Log.e(LOG_TAG, String.format("Unrecognized test size '%s'", testSize)); } } /** * Only run tests annotated with given annotation class. * * @param annotation the full class name of annotation */ public void addAnnotationInclusionFilter(String annotation) { Class extends Annotation> annotationClass = loadAnnotationClass(annotation); if (annotationClass != null) { mFilter = mFilter.intersect(new AnnotationInclusionFilter(annotationClass)); } } /** * Skip tests annotated with given annotation class. * * @param notAnnotation the full class name of annotation */ public void addAnnotationExclusionFilter(String notAnnotation) { Class extends Annotation> annotationClass = loadAnnotationClass(notAnnotation); if (annotationClass != null) { mFilter = mFilter.intersect(new AnnotationExclusionFilter(annotationClass)); } } /** * Build a request that will generate test started and test ended events, but will skip actual * test execution. */ public void setSkipExecution(boolean b) { mSkipExecution = b; } /** * Builds the {@link TestRequest} based on current contents of added classes and methods. * * If no classes have been explicitly added, will scan the classpath for all tests. * */ public TestRequest build(Instrumentation instr) { if (mTestLoader.isEmpty()) { // no class restrictions have been specified. Load all classes loadClassesFromClassPath(); } Request request = classes(instr, mSkipExecution, new Computer(), mTestLoader.getLoadedClasses().toArray(new Class[0])); return new TestRequest(mTestLoader.getLoadFailures(), request.filterWith(mFilter)); } /** * Create aRequest
that, when processed, will run all the tests
* in a set of classes.
*
* @param instr the {@link Instrumentation} to inject into any tests that require it
* @param computer Helps construct Runners from classes
* @param classes the classes containing the tests
* @return a Request
that will cause all tests in the classes to be run
*/
private static Request classes(Instrumentation instr, boolean skipExecution,
Computer computer, Class>... classes) {
try {
AndroidRunnerBuilder builder = new AndroidRunnerBuilder(true, instr, skipExecution);
Runner suite = computer.getSuite(builder, classes);
return Request.runner(suite);
} catch (InitializationError e) {
throw new RuntimeException(
"Suite constructor, called as above, should always complete");
}
}
private void loadClassesFromClassPath() {
Collection