diff options
Diffstat (limited to 'test-runner/android/test/suitebuilder')
13 files changed, 1000 insertions, 0 deletions
diff --git a/test-runner/android/test/suitebuilder/AssignableFrom.java b/test-runner/android/test/suitebuilder/AssignableFrom.java new file mode 100644 index 0000000..38b4ee3 --- /dev/null +++ b/test-runner/android/test/suitebuilder/AssignableFrom.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2008 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.test.suitebuilder; + +import com.android.internal.util.Predicate; + +class AssignableFrom implements Predicate<TestMethod> { + + private final Class root; + + AssignableFrom(Class root) { + this.root = root; + } + + public boolean apply(TestMethod testMethod) { + return root.isAssignableFrom(testMethod.getEnclosingClass()); + } +} diff --git a/test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java b/test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java new file mode 100644 index 0000000..128396e --- /dev/null +++ b/test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008 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.test.suitebuilder; + +/** + * A suite builder that finds instrumentation tests. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class InstrumentationTestSuiteBuilder extends TestSuiteBuilder { + + public InstrumentationTestSuiteBuilder(Class clazz) { + this(clazz.getName(), clazz.getClassLoader()); + } + + + public InstrumentationTestSuiteBuilder(String name, ClassLoader classLoader) { + super(name, classLoader); + addRequirements(TestPredicates.SELECT_INSTRUMENTATION); + } +} diff --git a/test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java b/test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java new file mode 100644 index 0000000..01e7ec6 --- /dev/null +++ b/test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2008 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.test.suitebuilder; + +/** + * A suite builder that runs smoke tests. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class SmokeTestSuiteBuilder extends TestSuiteBuilder { + + public SmokeTestSuiteBuilder(Class clazz) { + this(clazz.getName(), clazz.getClassLoader()); + } + + + public SmokeTestSuiteBuilder(String name, ClassLoader classLoader) { + super(name, classLoader); + addRequirements(TestPredicates.SELECT_SMOKE); + } +} diff --git a/test-runner/android/test/suitebuilder/TestGrouping.java b/test-runner/android/test/suitebuilder/TestGrouping.java new file mode 100644 index 0000000..df6da70 --- /dev/null +++ b/test-runner/android/test/suitebuilder/TestGrouping.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2008 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.test.suitebuilder; + +import android.test.ClassPathPackageInfo; +import android.test.ClassPathPackageInfoSource; +import android.test.PackageInfoSources; +import android.util.Log; +import com.android.internal.util.Predicate; +import junit.framework.TestCase; + +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Represents a collection of test classes present on the classpath. You can add individual classes + * or entire packages. By default sub-packages are included recursively, but methods are + * provided to allow for arbitrary inclusion or exclusion of sub-packages. Typically a + * {@link TestGrouping} will have only one root package, but this is not a requirement. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class TestGrouping { + + SortedSet<Class<? extends TestCase>> testCaseClasses; + + public static final Comparator<Class<? extends TestCase>> SORT_BY_SIMPLE_NAME + = new SortBySimpleName(); + + public static final Comparator<Class<? extends TestCase>> SORT_BY_FULLY_QUALIFIED_NAME + = new SortByFullyQualifiedName(); + + protected String firstIncludedPackage = null; + private ClassLoader classLoader; + + public TestGrouping(Comparator<Class<? extends TestCase>> comparator) { + testCaseClasses = new TreeSet<Class<? extends TestCase>>(comparator); + } + + /** + * @return A list of all tests in the package, including small, medium, large, + * flaky, and suppressed tests. Includes sub-packages recursively. + */ + public List<TestMethod> getTests() { + List<TestMethod> testMethods = new ArrayList<TestMethod>(); + for (Class<? extends TestCase> testCase : testCaseClasses) { + for (Method testMethod : getTestMethods(testCase)) { + testMethods.add(new TestMethod(testMethod, testCase)); + } + } + return testMethods; + } + + protected List<Method> getTestMethods(Class<? extends TestCase> testCaseClass) { + List<Method> methods = Arrays.asList(testCaseClass.getMethods()); + return select(methods, new TestMethodPredicate()); + } + + SortedSet<Class<? extends TestCase>> getTestCaseClasses() { + return testCaseClasses; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TestGrouping other = (TestGrouping) o; + if (!this.testCaseClasses.equals(other.testCaseClasses)) { + return false; + } + return this.testCaseClasses.comparator().equals(other.testCaseClasses.comparator()); + } + + public int hashCode() { + return testCaseClasses.hashCode(); + } + + /** + * Include all tests in the given packages and all their sub-packages, unless otherwise + * specified. Each of the given packages must contain at least one test class, either directly + * or in a sub-package. + * + * @param packageNames Names of packages to add. + * @return The {@link TestGrouping} for method chaining. + */ + public TestGrouping addPackagesRecursive(String... packageNames) { + for (String packageName : packageNames) { + List<Class<? extends TestCase>> addedClasses = testCaseClassesInPackage(packageName); + if (addedClasses.isEmpty()) { + Log.w("TestGrouping", "Invalid Package: '" + packageName + + "' could not be found or has no tests"); + } + testCaseClasses.addAll(addedClasses); + if (firstIncludedPackage == null) { + firstIncludedPackage = packageName; + } + } + return this; + } + + /** + * Exclude all tests in the given packages and all their sub-packages, unless otherwise + * specified. + * + * @param packageNames Names of packages to remove. + * @return The {@link TestGrouping} for method chaining. + */ + public TestGrouping removePackagesRecursive(String... packageNames) { + for (String packageName : packageNames) { + testCaseClasses.removeAll(testCaseClassesInPackage(packageName)); + } + return this; + } + + /** + * @return The first package name passed to {@link #addPackagesRecursive(String[])}, or null + * if that method was never called. + */ + public String getFirstIncludedPackage() { + return firstIncludedPackage; + } + + private List<Class<? extends TestCase>> testCaseClassesInPackage(String packageName) { + ClassPathPackageInfoSource source = PackageInfoSources.forClassPath(classLoader); + ClassPathPackageInfo packageInfo = source.getPackageInfo(packageName); + + return selectTestClasses(packageInfo.getTopLevelClassesRecursive()); + } + + @SuppressWarnings("unchecked") + private List<Class<? extends TestCase>> selectTestClasses(Set<Class<?>> allClasses) { + List<Class<? extends TestCase>> testClasses = new ArrayList<Class<? extends TestCase>>(); + for (Class<?> testClass : select(allClasses, + new TestCasePredicate())) { + testClasses.add((Class<? extends TestCase>) testClass); + } + return testClasses; + } + + private <T> List<T> select(Collection<T> items, Predicate<T> predicate) { + ArrayList<T> selectedItems = new ArrayList<T>(); + for (T item : items) { + if (predicate.apply(item)) { + selectedItems.add(item); + } + } + return selectedItems; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + /** + * Sort classes by their simple names (i.e. without the package prefix), using + * their packages to sort classes with the same name. + */ + private static class SortBySimpleName + implements Comparator<Class<? extends TestCase>>, Serializable { + + public int compare(Class<? extends TestCase> class1, + Class<? extends TestCase> class2) { + int result = class1.getSimpleName().compareTo(class2.getSimpleName()); + if (result != 0) { + return result; + } + return class1.getName().compareTo(class2.getName()); + } + } + + /** + * Sort classes by their fully qualified names (i.e. with the package + * prefix). + */ + private static class SortByFullyQualifiedName + implements Comparator<Class<? extends TestCase>>, Serializable { + + public int compare(Class<? extends TestCase> class1, + Class<? extends TestCase> class2) { + return class1.getName().compareTo(class2.getName()); + } + } + + private static class TestCasePredicate implements Predicate<Class<?>> { + + public boolean apply(Class aClass) { + int modifiers = ((Class<?>) aClass).getModifiers(); + return TestCase.class.isAssignableFrom((Class<?>) aClass) + && Modifier.isPublic(modifiers) + && !Modifier.isAbstract(modifiers) + && hasValidConstructor((Class<?>) aClass); + } + + @SuppressWarnings("unchecked") + private boolean hasValidConstructor(java.lang.Class<?> aClass) { + // The cast below is not necessary with the Java 5 compiler, but necessary with the Java 6 compiler, + // where the return type of Class.getDeclaredConstructors() was changed + // from Constructor<T>[] to Constructor<?>[] + Constructor<? extends TestCase>[] constructors + = (Constructor<? extends TestCase>[]) aClass.getConstructors(); + for (Constructor<? extends TestCase> constructor : constructors) { + if (Modifier.isPublic(constructor.getModifiers())) { + java.lang.Class[] parameterTypes = constructor.getParameterTypes(); + if (parameterTypes.length == 0 || + (parameterTypes.length == 1 && parameterTypes[0] == String.class)) { + return true; + } + } + } + return false; + } + } + + private static class TestMethodPredicate implements Predicate<Method> { + + public boolean apply(Method method) { + return ((method.getParameterTypes().length == 0) && + (method.getName().startsWith("test")) && + (method.getReturnType().getSimpleName().equals("void"))); + } + } +} diff --git a/test-runner/android/test/suitebuilder/TestMethod.java b/test-runner/android/test/suitebuilder/TestMethod.java new file mode 100644 index 0000000..08568d5 --- /dev/null +++ b/test-runner/android/test/suitebuilder/TestMethod.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2008 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.test.suitebuilder; + +import junit.framework.TestCase; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Represents a test to be run. Can be constructed without instantiating the TestCase or even + * loading the class. + */ +public class TestMethod { + + private final String enclosingClassname; + private final String testMethodName; + private final Class<? extends TestCase> enclosingClass; + + public TestMethod(Method method, Class<? extends TestCase> enclosingClass) { + this(method.getName(), enclosingClass); + } + + public TestMethod(String methodName, Class<? extends TestCase> enclosingClass) { + this.enclosingClass = enclosingClass; + this.enclosingClassname = enclosingClass.getName(); + this.testMethodName = methodName; + } + + public TestMethod(TestCase testCase) { + this(testCase.getName(), testCase.getClass()); + } + + public String getName() { + return testMethodName; + } + + public String getEnclosingClassname() { + return enclosingClassname; + } + + public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { + try { + return getEnclosingClass().getMethod(getName()).getAnnotation(annotationClass); + } catch (NoSuchMethodException e) { + return null; + } + } + + @SuppressWarnings("unchecked") + public Class<? extends TestCase> getEnclosingClass() { + return enclosingClass; + } + + public TestCase createTest() + throws InvocationTargetException, IllegalAccessException, InstantiationException { + return instantiateTest(enclosingClass, testMethodName); + } + + @SuppressWarnings("unchecked") + private TestCase instantiateTest(Class testCaseClass, String testName) + throws InvocationTargetException, IllegalAccessException, InstantiationException { + Constructor[] constructors = testCaseClass.getConstructors(); + + if (constructors.length == 0) { + return instantiateTest(testCaseClass.getSuperclass(), testName); + } else { + for (Constructor constructor : constructors) { + Class[] params = constructor.getParameterTypes(); + if (noargsConstructor(params)) { + TestCase test = ((Constructor<? extends TestCase>) constructor).newInstance(); + // JUnit will run just the one test if you call + // {@link TestCase#setName(String)} + test.setName(testName); + return test; + } else if (singleStringConstructor(params)) { + return ((Constructor<? extends TestCase>) constructor) + .newInstance(testName); + } + } + } + throw new RuntimeException("Unable to locate a constructor for " + + testCaseClass.getName()); + } + + private boolean singleStringConstructor(Class[] params) { + return (params.length == 1) && (params[0].equals(String.class)); + } + + private boolean noargsConstructor(Class[] params) { + return params.length == 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TestMethod that = (TestMethod) o; + + if (enclosingClassname != null + ? !enclosingClassname.equals(that.enclosingClassname) + : that.enclosingClassname != null) { + return false; + } + if (testMethodName != null + ? !testMethodName.equals(that.testMethodName) + : that.testMethodName != null) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result; + result = (enclosingClassname != null ? enclosingClassname.hashCode() : 0); + result = 31 * result + (testMethodName != null ? testMethodName.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return enclosingClassname + "." + testMethodName; + } +} diff --git a/test-runner/android/test/suitebuilder/TestPredicates.java b/test-runner/android/test/suitebuilder/TestPredicates.java new file mode 100644 index 0000000..d814e0b --- /dev/null +++ b/test-runner/android/test/suitebuilder/TestPredicates.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2008 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.test.suitebuilder; + +import android.test.InstrumentationTestCase; +import android.test.PerformanceTestBase; +import android.test.suitebuilder.annotation.HasAnnotation; +import android.test.suitebuilder.annotation.Suppress; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.Smoke; +import com.android.internal.util.Predicate; +import com.android.internal.util.Predicates; + +/** + * {@hide} Not needed for 1.0 SDK. + */ +public class TestPredicates { + + public static final Predicate<TestMethod> SELECT_INSTRUMENTATION = + new AssignableFrom(InstrumentationTestCase.class); + public static final Predicate<TestMethod> REJECT_INSTRUMENTATION = + Predicates.not(SELECT_INSTRUMENTATION); + + public static final Predicate<TestMethod> SELECT_SMOKE = new HasAnnotation(Smoke.class); + public static final Predicate<TestMethod> SELECT_SMALL = new HasAnnotation(SmallTest.class); + public static final Predicate<TestMethod> SELECT_MEDIUM = new HasAnnotation(MediumTest.class); + public static final Predicate<TestMethod> SELECT_LARGE = new HasAnnotation(LargeTest.class); + public static final Predicate<TestMethod> REJECT_SUPPRESSED = + Predicates.not(new HasAnnotation(Suppress.class)); + public static final Predicate<TestMethod> REJECT_PERFORMANCE = + Predicates.not(new AssignableFrom(PerformanceTestBase.class)); + +} diff --git a/test-runner/android/test/suitebuilder/TestSuiteBuilder.java b/test-runner/android/test/suitebuilder/TestSuiteBuilder.java new file mode 100644 index 0000000..428905e --- /dev/null +++ b/test-runner/android/test/suitebuilder/TestSuiteBuilder.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2008 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.test.suitebuilder; + +import android.content.Context; +import android.test.AndroidTestRunner; +import android.test.TestCaseUtil; +import android.util.Log; +import com.android.internal.util.Predicate; +import com.google.android.collect.Lists; +import static android.test.suitebuilder.TestGrouping.SORT_BY_FULLY_QUALIFIED_NAME; +import static android.test.suitebuilder.TestPredicates.REJECT_SUPPRESSED; +import static android.test.suitebuilder.TestPredicates.REJECT_PERFORMANCE; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import java.lang.reflect.InvocationTargetException; +import java.util.Enumeration; +import java.util.List; +import java.util.Set; +import java.util.HashSet; +import java.util.ArrayList; +import java.util.Collections; + +/** + * Build suites based on a combination of included packages, excluded packages, + * and predicates that must be satisfied. + */ +public class TestSuiteBuilder { + + private Context context; + private final TestGrouping testGrouping = new TestGrouping(SORT_BY_FULLY_QUALIFIED_NAME); + private final Set<Predicate<TestMethod>> predicates = new HashSet<Predicate<TestMethod>>(); + private List<TestCase> testCases; + private TestSuite rootSuite; + private TestSuite suiteForCurrentClass; + private String currentClassname; + private String suiteName; + + /** + * The given name is automatically prefixed with the package containing the tests to be run. + * If more than one package is specified, the first is used. + * + * @param clazz Use the class from your .apk. Use the class name for the test suite name. + * Use the class' classloader in order to load classes for testing. + * This is needed when running in the emulator. + */ + public TestSuiteBuilder(Class clazz) { + this(clazz.getName(), clazz.getClassLoader()); + } + + public TestSuiteBuilder(String name, ClassLoader classLoader) { + this.suiteName = name; + this.testGrouping.setClassLoader(classLoader); + this.testCases = Lists.newArrayList(); + addRequirements(REJECT_SUPPRESSED); + } + + /** @hide pending API Council approval */ + public TestSuiteBuilder addTestClassByName(String testClassName, String testMethodName, + Context context) { + + AndroidTestRunner atr = new AndroidTestRunner(); + atr.setContext(context); + atr.setTestClassName(testClassName, testMethodName); + + this.testCases.addAll(atr.getTestCases()); + return this; + } + + /** @hide pending API Council approval */ + public TestSuiteBuilder addTestSuite(TestSuite testSuite) { + for (TestCase testCase : (List<TestCase>) TestCaseUtil.getTests(testSuite, true)) { + this.testCases.add(testCase); + } + return this; + } + + /** + * Include all tests that satisfy the requirements in the given packages and all sub-packages, + * unless otherwise specified. + * + * @param packageNames Names of packages to add. + * @return The builder for method chaining. + */ + public TestSuiteBuilder includePackages(String... packageNames) { + testGrouping.addPackagesRecursive(packageNames); + return this; + } + + /** + * Exclude all tests in the given packages and all sub-packages, unless otherwise specified. + * + * @param packageNames Names of packages to remove. + * @return The builder for method chaining. + */ + public TestSuiteBuilder excludePackages(String... packageNames) { + testGrouping.removePackagesRecursive(packageNames); + return this; + } + + /** + * Exclude tests that fail to satisfy all of the given predicates. + * + * @param predicates Predicates to add to the list of requirements. + * @return The builder for method chaining. + */ + public TestSuiteBuilder addRequirements(List<Predicate<TestMethod>> predicates) { + this.predicates.addAll(predicates); + return this; + } + + /** + * Include all junit tests that satisfy the requirements in the calling class' package and all + * sub-packages. + * + * @return The builder for method chaining. + */ + public final TestSuiteBuilder includeAllPackagesUnderHere() { + StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); + + String callingClassName = null; + String thisClassName = TestSuiteBuilder.class.getName(); + + // We want to get the package of this method's calling class. This method's calling class + // should be one level below this class in the stack trace. + for (int i = 0; i < stackTraceElements.length; i++) { + StackTraceElement element = stackTraceElements[i]; + if (thisClassName.equals(element.getClassName()) + && "includeAllPackagesUnderHere".equals(element.getMethodName())) { + // We've found this class in the call stack. The calling class must be the + // next class in the stack. + callingClassName = stackTraceElements[i + 1].getClassName(); + break; + } + } + + String packageName = parsePackageNameFromClassName(callingClassName); + return includePackages(packageName); + } + + /** + * Override the default name for the suite being built. This should generally be called if you + * call {@link #addRequirements(com.android.internal.util.Predicate[])} to make it clear which + * tests will be included. The name you specify is automatically prefixed with the package + * containing the tests to be run. If more than one package is specified, the first is used. + * + * @param newSuiteName Prefix of name to give the suite being built. + * @return The builder for method chaining. + */ + public TestSuiteBuilder named(String newSuiteName) { + suiteName = newSuiteName; + return this; + } + + /** + * Call this method once you've configured your builder as desired. + * + * @return The suite containing the requested tests. + */ + public final TestSuite build() { + rootSuite = new TestSuite(getSuiteName()); + + // Keep track of current class so we know when to create a new sub-suite. + currentClassname = null; + try { + for (TestMethod test : testGrouping.getTests()) { + if (satisfiesAllPredicates(test)) { + addTest(test); + } + } + if (testCases.size() > 0) { + for (TestCase testCase : testCases) { + if (satisfiesAllPredicates(new TestMethod(testCase))) { + addTest(testCase); + } + } + } + } catch (Exception exception) { + Log.i("TestSuiteBuilder", "Failed to create test.", exception); + TestSuite suite = new TestSuite(getSuiteName()); + suite.addTest(new FailedToCreateTests(exception)); + return suite; + } + return rootSuite; + } + + /** + * Subclasses use this method to determine the name of the suite. + * + * @return The package and suite name combined. + */ + protected String getSuiteName() { + return suiteName; + } + + /** + * Exclude tests that fail to satisfy all of the given predicates. If you call this method, you + * probably also want to call {@link #named(String)} to override the default suite name. + * + * @param predicates Predicates to add to the list of requirements. + * @return The builder for method chaining. + */ + public final TestSuiteBuilder addRequirements(Predicate<TestMethod>... predicates) { + ArrayList<Predicate<TestMethod>> list = new ArrayList<Predicate<TestMethod>>(); + Collections.addAll(list, predicates); + return addRequirements(list); + } + + /** + * A special {@link junit.framework.TestCase} used to indicate a failure during the build() + * step. + */ + public static class FailedToCreateTests extends TestCase { + private final Exception exception; + + public FailedToCreateTests(Exception exception) { + super("testSuiteConstructionFailed"); + this.exception = exception; + } + + public void testSuiteConstructionFailed() { + throw new RuntimeException("Exception during suite construction", exception); + } + } + + /** + * @return the test package that represents the packages that were included for our test suite. + * + * {@hide} Not needed for 1.0 SDK. + */ + protected TestGrouping getTestGrouping() { + return testGrouping; + } + + private boolean satisfiesAllPredicates(TestMethod test) { + for (Predicate<TestMethod> predicate : predicates) { + if (!predicate.apply(test)) { + return false; + } + } + return true; + } + + private void addTest(TestMethod testMethod) throws Exception { + addSuiteIfNecessary(testMethod.getEnclosingClassname()); + suiteForCurrentClass.addTest(testMethod.createTest()); + } + + private void addTest(Test test) { + addSuiteIfNecessary(test.getClass().getName()); + suiteForCurrentClass.addTest(test); + } + + private void addSuiteIfNecessary(String parentClassname) { + if (!parentClassname.equals(currentClassname)) { + currentClassname = parentClassname; + suiteForCurrentClass = new TestSuite(parentClassname); + rootSuite.addTest(suiteForCurrentClass); + } + } + + private static String parsePackageNameFromClassName(String className) { + return className.substring(0, className.lastIndexOf('.')); + } +} diff --git a/test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java b/test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java new file mode 100644 index 0000000..8cf4c86 --- /dev/null +++ b/test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2008 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.test.suitebuilder; + +/** + * A suite builder that finds unit tests. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class UnitTestSuiteBuilder extends TestSuiteBuilder { + + public UnitTestSuiteBuilder(Class clazz) { + this(clazz.getName(), clazz.getClassLoader()); + } + + + public UnitTestSuiteBuilder(String name, ClassLoader classLoader) { + super(name, classLoader); + addRequirements(TestPredicates.REJECT_INSTRUMENTATION); + addRequirements(TestPredicates.REJECT_PERFORMANCE); + } +} diff --git a/test-runner/android/test/suitebuilder/annotation/HasAnnotation.java b/test-runner/android/test/suitebuilder/annotation/HasAnnotation.java new file mode 100644 index 0000000..a2868fc --- /dev/null +++ b/test-runner/android/test/suitebuilder/annotation/HasAnnotation.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2008 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.test.suitebuilder.annotation; + +import static com.android.internal.util.Predicates.or; +import com.android.internal.util.Predicate; +import android.test.suitebuilder.TestMethod; + +import java.lang.annotation.Annotation; + +/** + * A predicate that checks to see if a {@link TestMethod} has a specific annotation, either on the + * method or on the containing class. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class HasAnnotation implements Predicate<TestMethod> { + + private Predicate<TestMethod> hasMethodOrClassAnnotation; + + public HasAnnotation(Class<? extends Annotation> annotationClass) { + this.hasMethodOrClassAnnotation = or( + new HasMethodAnnotation(annotationClass), + new HasClassAnnotation(annotationClass)); + } + + public boolean apply(TestMethod testMethod) { + return hasMethodOrClassAnnotation.apply(testMethod); + } +} diff --git a/test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java b/test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java new file mode 100644 index 0000000..ac76f4c --- /dev/null +++ b/test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008 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.test.suitebuilder.annotation; + +import java.lang.annotation.Annotation; + +import android.test.suitebuilder.TestMethod; +import com.android.internal.util.Predicate; + +/** + * A predicate that checks to see if a {@link android.test.suitebuilder.TestMethod} has a specific annotation on the + * containing class. Consider using the public {@link HasAnnotation} class instead of this class. + * + * {@hide} Not needed for 1.0 SDK. + */ +class HasClassAnnotation implements Predicate<TestMethod> { + + private Class<? extends Annotation> annotationClass; + + public HasClassAnnotation(Class<? extends Annotation> annotationClass) { + this.annotationClass = annotationClass; + } + + public boolean apply(TestMethod testMethod) { + return testMethod.getEnclosingClass().getAnnotation(annotationClass) != null; + } +} diff --git a/test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java b/test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java new file mode 100644 index 0000000..96bd922 --- /dev/null +++ b/test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008 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.test.suitebuilder.annotation; + +import com.android.internal.util.Predicate; +import android.test.suitebuilder.TestMethod; + +import java.lang.annotation.Annotation; + +/** + * A predicate that checks to see if a the method represented by {@link TestMethod} has a certain + * annotation on it. Consider using the public {@link HasAnnotation} class instead of this class. + * + * {@hide} Not needed for 1.0 SDK. + */ +class HasMethodAnnotation implements Predicate<TestMethod> { + + private final Class<? extends Annotation> annotationClass; + + public HasMethodAnnotation(Class<? extends Annotation> annotationClass) { + this.annotationClass = annotationClass; + } + + public boolean apply(TestMethod testMethod) { + return testMethod.getAnnotation(annotationClass) != null; + } +} diff --git a/test-runner/android/test/suitebuilder/annotation/package.html b/test-runner/android/test/suitebuilder/annotation/package.html new file mode 100644 index 0000000..ffba2e9 --- /dev/null +++ b/test-runner/android/test/suitebuilder/annotation/package.html @@ -0,0 +1,5 @@ +<HTML> +<BODY> +Utility classes supporting the test runner classes. +</BODY> +</HTML> diff --git a/test-runner/android/test/suitebuilder/package.html b/test-runner/android/test/suitebuilder/package.html new file mode 100644 index 0000000..ffba2e9 --- /dev/null +++ b/test-runner/android/test/suitebuilder/package.html @@ -0,0 +1,5 @@ +<HTML> +<BODY> +Utility classes supporting the test runner classes. +</BODY> +</HTML> |