| /* | |
| * 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; | |
| } | |
| } |