diff options
Diffstat (limited to 'test-runner/android/test/TestRunner.java')
-rw-r--r-- | test-runner/android/test/TestRunner.java | 725 |
1 files changed, 725 insertions, 0 deletions
diff --git a/test-runner/android/test/TestRunner.java b/test-runner/android/test/TestRunner.java new file mode 100644 index 0000000..012df35 --- /dev/null +++ b/test-runner/android/test/TestRunner.java @@ -0,0 +1,725 @@ +/* + * Copyright (C) 2006 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; + +import android.content.Context; +import android.util.Log; +import android.os.Debug; +import android.os.SystemClock; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestSuite; +import junit.framework.TestListener; +import junit.framework.Test; +import junit.framework.TestResult; +import com.google.android.collect.Lists; + +/** + * Support class that actually runs a test. Android uses this class, + * and you probably will not need to instantiate, extend, or call this + * class yourself. See the full {@link android.test} package description + * to learn more about testing Android applications. + * + * {@hide} Not needed for 1.0 SDK. + */ +public class TestRunner implements PerformanceTestCase.Intermediates { + public static final int REGRESSION = 0; + public static final int PERFORMANCE = 1; + public static final int PROFILING = 2; + + public static final int CLEARSCREEN = 0; + private static final String TAG = "TestHarness"; + private Context mContext; + + private int mMode = REGRESSION; + + private List<Listener> mListeners = Lists.newArrayList(); + private int mPassed; + private int mFailed; + + private int mInternalIterations; + private long mStartTime; + private long mEndTime; + + private String mClassName; + + List<IntermediateTime> mIntermediates = null; + + private static Class mRunnableClass; + private static Class mJUnitClass; + + static { + try { + mRunnableClass = Class.forName("java.lang.Runnable", false, null); + mJUnitClass = Class.forName("junit.framework.TestCase", false, null); + } catch (ClassNotFoundException ex) { + throw new RuntimeException("shouldn't happen", ex); + } + } + + public class JunitTestSuite extends TestSuite implements TestListener { + boolean mError = false; + + public JunitTestSuite() { + super(); + } + + @Override + public void run(TestResult result) { + result.addListener(this); + super.run(result); + result.removeListener(this); + } + + /** + * Implemented method of the interface TestListener which will listen for the + * start of a test. + * + * @param test + */ + public void startTest(Test test) { + started(test.toString()); + } + + /** + * Implemented method of the interface TestListener which will listen for the + * end of the test. + * + * @param test + */ + public void endTest(Test test) { + finished(test.toString()); + if (!mError) { + passed(test.toString()); + } + } + + /** + * Implemented method of the interface TestListener which will listen for an + * mError while running the test. + * + * @param test + */ + public void addError(Test test, Throwable t) { + mError = true; + failed(test.toString(), t); + } + + public void addFailure(Test test, junit.framework.AssertionFailedError t) { + mError = true; + failed(test.toString(), t); + } + } + + /** + * Listener.performance() 'intermediates' argument is a list of these. + */ + public static class IntermediateTime { + public IntermediateTime(String name, long timeInNS) { + this.name = name; + this.timeInNS = timeInNS; + } + + public String name; + public long timeInNS; + } + + /** + * Support class that receives status on test progress. You should not need to + * extend this interface yourself. + */ + public interface Listener { + void started(String className); + void finished(String className); + void performance(String className, + long itemTimeNS, int iterations, + List<IntermediateTime> itermediates); + void passed(String className); + void failed(String className, Throwable execption); + } + + public TestRunner(Context context) { + mContext = context; + } + + public void addListener(Listener listener) { + mListeners.add(listener); + } + + public void startProfiling() { + File file = new File("/tmp/trace"); + file.mkdir(); + String base = "/tmp/trace/" + mClassName + ".dmtrace"; + Debug.startMethodTracing(base, 8 * 1024 * 1024); + } + + public void finishProfiling() { + Debug.stopMethodTracing(); + } + + private void started(String className) { + + int count = mListeners.size(); + for (int i = 0; i < count; i++) { + mListeners.get(i).started(className); + } + } + + private void finished(String className) { + int count = mListeners.size(); + for (int i = 0; i < count; i++) { + mListeners.get(i).finished(className); + } + } + + private void performance(String className, + long itemTimeNS, + int iterations, + List<IntermediateTime> intermediates) { + int count = mListeners.size(); + for (int i = 0; i < count; i++) { + mListeners.get(i).performance(className, + itemTimeNS, + iterations, + intermediates); + } + } + + public void passed(String className) { + mPassed++; + int count = mListeners.size(); + for (int i = 0; i < count; i++) { + mListeners.get(i).passed(className); + } + } + + public void failed(String className, Throwable exception) { + mFailed++; + int count = mListeners.size(); + for (int i = 0; i < count; i++) { + mListeners.get(i).failed(className, exception); + } + } + + public int passedCount() { + return mPassed; + } + + public int failedCount() { + return mFailed; + } + + public void run(String[] classes) { + for (String cl : classes) { + run(cl); + } + } + + public void setInternalIterations(int count) { + mInternalIterations = count; + } + + public void startTiming(boolean realTime) { + if (realTime) { + mStartTime = System.currentTimeMillis(); + } else { + mStartTime = SystemClock.currentThreadTimeMillis(); + } + } + + public void addIntermediate(String name) { + addIntermediate(name, (System.currentTimeMillis() - mStartTime) * 1000000); + } + + public void addIntermediate(String name, long timeInNS) { + mIntermediates.add(new IntermediateTime(name, timeInNS)); + } + + public void finishTiming(boolean realTime) { + if (realTime) { + mEndTime = System.currentTimeMillis(); + } else { + mEndTime = SystemClock.currentThreadTimeMillis(); + } + } + + public void setPerformanceMode(int mode) { + mMode = mode; + } + + private void missingTest(String className, Throwable e) { + started(className); + finished(className); + failed(className, e); + } + + /* + This class determines if more suites are added to this class then adds all individual + test classes to a test suite for run + */ + public void run(String className) { + try { + mClassName = className; + Class clazz = mContext.getClassLoader().loadClass(className); + Method method = getChildrenMethod(clazz); + if (method != null) { + String[] children = getChildren(method); + run(children); + } else if (mRunnableClass.isAssignableFrom(clazz)) { + Runnable test = (Runnable) clazz.newInstance(); + TestCase testcase = null; + if (test instanceof TestCase) { + testcase = (TestCase) test; + } + Throwable e = null; + boolean didSetup = false; + started(className); + try { + if (testcase != null) { + testcase.setUp(mContext); + didSetup = true; + } + if (mMode == PERFORMANCE) { + runInPerformanceMode(test, className, false, className); + } else if (mMode == PROFILING) { + //Need a way to mark a test to be run in profiling mode or not. + startProfiling(); + test.run(); + finishProfiling(); + } else { + test.run(); + } + } catch (Throwable ex) { + e = ex; + } + if (testcase != null && didSetup) { + try { + testcase.tearDown(); + } catch (Throwable ex) { + e = ex; + } + } + finished(className); + if (e == null) { + passed(className); + } else { + failed(className, e); + } + } else if (mJUnitClass.isAssignableFrom(clazz)) { + Throwable e = null; + //Create a Junit Suite. + JunitTestSuite suite = new JunitTestSuite(); + Method[] methods = getAllTestMethods(clazz); + for (Method m : methods) { + junit.framework.TestCase test = (junit.framework.TestCase) clazz.newInstance(); + test.setName(m.getName()); + + if (test instanceof AndroidTestCase) { + AndroidTestCase testcase = (AndroidTestCase) test; + try { + testcase.setContext(mContext); + testcase.setTestContext(mContext); + } catch (Exception ex) { + Log.i("TestHarness", ex.toString()); + } + } + suite.addTest(test); + } + if (mMode == PERFORMANCE) { + final int testCount = suite.testCount(); + + for (int j = 0; j < testCount; j++) { + Test test = suite.testAt(j); + started(test.toString()); + try { + runInPerformanceMode(test, className, true, test.toString()); + } catch (Throwable ex) { + e = ex; + } + finished(test.toString()); + if (e == null) { + passed(test.toString()); + } else { + failed(test.toString(), e); + } + } + } else if (mMode == PROFILING) { + //Need a way to mark a test to be run in profiling mode or not. + startProfiling(); + junit.textui.TestRunner.run(suite); + finishProfiling(); + } else { + junit.textui.TestRunner.run(suite); + } + } else { + System.out.println("Test wasn't Runnable and didn't have a" + + " children method: " + className); + } + } catch (ClassNotFoundException e) { + Log.e("ClassNotFoundException for " + className, e.toString()); + if (isJunitTest(className)) { + runSingleJunitTest(className); + } else { + missingTest(className, e); + } + } catch (InstantiationException e) { + System.out.println("InstantiationException for " + className); + missingTest(className, e); + } catch (IllegalAccessException e) { + System.out.println("IllegalAccessException for " + className); + missingTest(className, e); + } + } + + public void runInPerformanceMode(Object testCase, String className, boolean junitTest, + String testNameInDb) throws Exception { + boolean increaseIterations = true; + int iterations = 1; + long duration = 0; + mIntermediates = null; + + mInternalIterations = 1; + Class clazz = mContext.getClassLoader().loadClass(className); + Object perftest = clazz.newInstance(); + + PerformanceTestCase perftestcase = null; + if (perftest instanceof PerformanceTestCase) { + perftestcase = (PerformanceTestCase) perftest; + // only run the test if it is not marked as a performance only test + if (mMode == REGRESSION && perftestcase.isPerformanceOnly()) return; + } + + // First force GCs, to avoid GCs happening during out + // test and skewing its time. + Runtime.getRuntime().runFinalization(); + Runtime.getRuntime().gc(); + + if (perftestcase != null) { + mIntermediates = new ArrayList<IntermediateTime>(); + iterations = perftestcase.startPerformance(this); + if (iterations > 0) { + increaseIterations = false; + } else { + iterations = 1; + } + } + + // Pause briefly to let things settle down... + Thread.sleep(1000); + do { + mEndTime = 0; + if (increaseIterations) { + // Test case does not implement + // PerformanceTestCase or returned 0 iterations, + // so we take care of measure the whole test time. + mStartTime = SystemClock.currentThreadTimeMillis(); + } else { + // Try to make it obvious if the test case + // doesn't call startTiming(). + mStartTime = 0; + } + + if (junitTest) { + for (int i = 0; i < iterations; i++) { + junit.textui.TestRunner.run((junit.framework.Test) testCase); + } + } else { + Runnable test = (Runnable) testCase; + for (int i = 0; i < iterations; i++) { + test.run(); + } + } + + long endTime = mEndTime; + if (endTime == 0) { + endTime = SystemClock.currentThreadTimeMillis(); + } + + duration = endTime - mStartTime; + if (!increaseIterations) { + break; + } + if (duration <= 1) { + iterations *= 1000; + } else if (duration <= 10) { + iterations *= 100; + } else if (duration < 100) { + iterations *= 10; + } else if (duration < 1000) { + iterations *= (int) ((1000 / duration) + 2); + } else { + break; + } + } while (true); + + if (duration != 0) { + iterations *= mInternalIterations; + performance(testNameInDb, (duration * 1000000) / iterations, + iterations, mIntermediates); + } + } + + public void runSingleJunitTest(String className) { + Throwable excep = null; + int index = className.lastIndexOf('$'); + String testName = ""; + String originalClassName = className; + if (index >= 0) { + className = className.substring(0, index); + testName = originalClassName.substring(index + 1); + } + try { + Class clazz = mContext.getClassLoader().loadClass(className); + if (mJUnitClass.isAssignableFrom(clazz)) { + junit.framework.TestCase test = (junit.framework.TestCase) clazz.newInstance(); + JunitTestSuite newSuite = new JunitTestSuite(); + test.setName(testName); + + if (test instanceof AndroidTestCase) { + AndroidTestCase testcase = (AndroidTestCase) test; + try { + testcase.setContext(mContext); + } catch (Exception ex) { + Log.w(TAG, "Exception encountered while trying to set the context.", ex); + } + } + newSuite.addTest(test); + + if (mMode == PERFORMANCE) { + try { + started(test.toString()); + runInPerformanceMode(test, className, true, test.toString()); + finished(test.toString()); + if (excep == null) { + passed(test.toString()); + } else { + failed(test.toString(), excep); + } + } catch (Throwable ex) { + excep = ex; + } + + } else if (mMode == PROFILING) { + startProfiling(); + junit.textui.TestRunner.run(newSuite); + finishProfiling(); + } else { + junit.textui.TestRunner.run(newSuite); + } + } + } catch (ClassNotFoundException e) { + Log.e("TestHarness", "No test case to run", e); + } catch (IllegalAccessException e) { + Log.e("TestHarness", "Illegal Access Exception", e); + } catch (InstantiationException e) { + Log.e("TestHarness", "Instantiation Exception", e); + } + } + + public static Method getChildrenMethod(Class clazz) { + try { + return clazz.getMethod("children", (Class[]) null); + } catch (NoSuchMethodException e) { + } + + return null; + } + + public static Method getChildrenMethod(Context c, String className) { + try { + return getChildrenMethod(c.getClassLoader().loadClass(className)); + } catch (ClassNotFoundException e) { + } + return null; + } + + public static String[] getChildren(Context c, String className) { + Method m = getChildrenMethod(c, className); + String[] testChildren = getTestChildren(c, className); + if (m == null & testChildren == null) { + throw new RuntimeException("couldn't get children method for " + + className); + } + if (m != null) { + String[] children = getChildren(m); + if (testChildren != null) { + String[] allChildren = new String[testChildren.length + children.length]; + System.arraycopy(children, 0, allChildren, 0, children.length); + System.arraycopy(testChildren, 0, allChildren, children.length, testChildren.length); + return allChildren; + } else { + return children; + } + } else { + if (testChildren != null) { + return testChildren; + } + } + return null; + } + + public static String[] getChildren(Method m) { + try { + if (!Modifier.isStatic(m.getModifiers())) { + throw new RuntimeException("children method is not static"); + } + return (String[]) m.invoke(null, (Object[]) null); + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } + return new String[0]; + } + + public static String[] getTestChildren(Context c, String className) { + try { + Class clazz = c.getClassLoader().loadClass(className); + + if (mJUnitClass.isAssignableFrom(clazz)) { + return getTestChildren(clazz); + } + } catch (ClassNotFoundException e) { + Log.e("TestHarness", "No class found", e); + } + return null; + } + + public static String[] getTestChildren(Class clazz) { + Method[] methods = getAllTestMethods(clazz); + + String[] onScreenTestNames = new String[methods.length]; + int index = 0; + for (Method m : methods) { + onScreenTestNames[index] = clazz.getName() + "$" + m.getName(); + index++; + } + return onScreenTestNames; + } + + public static Method[] getAllTestMethods(Class clazz) { + Method[] allMethods = clazz.getDeclaredMethods(); + int numOfMethods = 0; + for (Method m : allMethods) { + boolean mTrue = isTestMethod(m); + if (mTrue) { + numOfMethods++; + } + } + int index = 0; + Method[] testMethods = new Method[numOfMethods]; + for (Method m : allMethods) { + boolean mTrue = isTestMethod(m); + if (mTrue) { + testMethods[index] = m; + index++; + } + } + return testMethods; + } + + private static boolean isTestMethod(Method m) { + return m.getName().startsWith("test") && + m.getReturnType() == void.class && + m.getParameterTypes().length == 0; + } + + public static int countJunitTests(Class clazz) { + Method[] allTestMethods = getAllTestMethods(clazz); + int numberofMethods = allTestMethods.length; + + return numberofMethods; + } + + public static boolean isTestSuite(Context c, String className) { + boolean childrenMethods = getChildrenMethod(c, className) != null; + + try { + Class clazz = c.getClassLoader().loadClass(className); + if (mJUnitClass.isAssignableFrom(clazz)) { + int numTests = countJunitTests(clazz); + if (numTests > 0) + childrenMethods = true; + } + } catch (ClassNotFoundException e) { + } + return childrenMethods; + } + + + public boolean isJunitTest(String className) { + int index = className.lastIndexOf('$'); + if (index >= 0) { + className = className.substring(0, index); + } + try { + Class clazz = mContext.getClassLoader().loadClass(className); + if (mJUnitClass.isAssignableFrom(clazz)) { + return true; + } + } catch (ClassNotFoundException e) { + } + return false; + } + + /** + * Returns the number of tests that will be run if you try to do this. + */ + public static int countTests(Context c, String className) { + try { + Class clazz = c.getClassLoader().loadClass(className); + Method method = getChildrenMethod(clazz); + if (method != null) { + + String[] children = getChildren(method); + int rv = 0; + for (String child : children) { + rv += countTests(c, child); + } + return rv; + } else if (mRunnableClass.isAssignableFrom(clazz)) { + return 1; + } else if (mJUnitClass.isAssignableFrom(clazz)) { + return countJunitTests(clazz); + } + } catch (ClassNotFoundException e) { + return 1; // this gets the count right, because either this test + // is missing, and it will fail when run or it is a single Junit test to be run. + } + return 0; + } + + /** + * Returns a title to display given the className of a test. + * <p/> + * <p>Currently this function just returns the portion of the + * class name after the last '.' + */ + public static String getTitle(String className) { + int indexDot = className.lastIndexOf('.'); + int indexDollar = className.lastIndexOf('$'); + int index = indexDot > indexDollar ? indexDot : indexDollar; + if (index >= 0) { + className = className.substring(index + 1); + } + return className; + } +} |