summaryrefslogtreecommitdiffstats
path: root/test-runner/android/test/suitebuilder
diff options
context:
space:
mode:
Diffstat (limited to 'test-runner/android/test/suitebuilder')
-rw-r--r--test-runner/android/test/suitebuilder/AssignableFrom.java32
-rw-r--r--test-runner/android/test/suitebuilder/InstrumentationTestSuiteBuilder.java35
-rw-r--r--test-runner/android/test/suitebuilder/SmokeTestSuiteBuilder.java35
-rw-r--r--test-runner/android/test/suitebuilder/TestGrouping.java249
-rw-r--r--test-runner/android/test/suitebuilder/TestMethod.java146
-rw-r--r--test-runner/android/test/suitebuilder/TestPredicates.java49
-rw-r--r--test-runner/android/test/suitebuilder/TestSuiteBuilder.java282
-rw-r--r--test-runner/android/test/suitebuilder/UnitTestSuiteBuilder.java36
-rw-r--r--test-runner/android/test/suitebuilder/annotation/HasAnnotation.java44
-rw-r--r--test-runner/android/test/suitebuilder/annotation/HasClassAnnotation.java41
-rw-r--r--test-runner/android/test/suitebuilder/annotation/HasMethodAnnotation.java41
-rw-r--r--test-runner/android/test/suitebuilder/annotation/package.html5
-rw-r--r--test-runner/android/test/suitebuilder/package.html5
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>