1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.test;
18 
19 import android.content.Context;
20 import android.util.Log;
21 import android.os.Debug;
22 import android.os.SystemClock;
23 
24 import java.io.File;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Modifier;
28 import java.util.ArrayList;
29 import java.util.List;
30 
31 import junit.framework.TestSuite;
32 import junit.framework.TestListener;
33 import junit.framework.Test;
34 import junit.framework.TestResult;
35 import com.google.android.collect.Lists;
36 
37 /**
38  * Support class that actually runs a test. Android uses this class,
39  * and you probably will not need to instantiate, extend, or call this
40  * class yourself. See the full {@link android.test} package description
41  * to learn more about testing Android applications.
42  *
43  * {@hide} Not needed for 1.0 SDK.
44  */
45 public class TestRunner implements PerformanceTestCase.Intermediates {
46     public static final int REGRESSION = 0;
47     public static final int PERFORMANCE = 1;
48     public static final int PROFILING = 2;
49 
50     public static final int CLEARSCREEN = 0;
51     private static final String TAG = "TestHarness";
52     private Context mContext;
53 
54     private int mMode = REGRESSION;
55 
56     private List<Listener> mListeners = Lists.newArrayList();
57     private int mPassed;
58     private int mFailed;
59 
60     private int mInternalIterations;
61     private long mStartTime;
62     private long mEndTime;
63 
64     private String mClassName;
65 
66     List<IntermediateTime> mIntermediates = null;
67 
68     private static Class mRunnableClass;
69     private static Class mJUnitClass;
70 
71     static {
72         try {
73             mRunnableClass = Class.forName("java.lang.Runnable", false, null);
74             mJUnitClass = Class.forName("junit.framework.TestCase", false, null);
75         } catch (ClassNotFoundException ex) {
76             throw new RuntimeException("shouldn't happen", ex);
77         }
78     }
79 
80     public class JunitTestSuite extends TestSuite implements TestListener {
81         boolean mError = false;
82 
JunitTestSuite()83         public JunitTestSuite() {
84             super();
85         }
86 
87         @Override
run(TestResult result)88         public void run(TestResult result) {
89             result.addListener(this);
90             super.run(result);
91             result.removeListener(this);
92         }
93 
94         /**
95          * Implemented method of the interface TestListener which will listen for the
96          * start of a test.
97          *
98          * @param test
99          */
startTest(Test test)100         public void startTest(Test test) {
101             started(test.toString());
102         }
103 
104         /**
105          * Implemented method of the interface TestListener which will listen for the
106          * end of the test.
107          *
108          * @param test
109          */
endTest(Test test)110         public void endTest(Test test) {
111             finished(test.toString());
112             if (!mError) {
113                 passed(test.toString());
114             }
115         }
116 
117         /**
118          * Implemented method of the interface TestListener which will listen for an
119          * mError while running the test.
120          *
121          * @param test
122          */
addError(Test test, Throwable t)123         public void addError(Test test, Throwable t) {
124             mError = true;
125             failed(test.toString(), t);
126         }
127 
addFailure(Test test, junit.framework.AssertionFailedError t)128         public void addFailure(Test test, junit.framework.AssertionFailedError t) {
129             mError = true;
130             failed(test.toString(), t);
131         }
132     }
133 
134     /**
135      * Listener.performance() 'intermediates' argument is a list of these.
136      */
137     public static class IntermediateTime {
IntermediateTime(String name, long timeInNS)138         public IntermediateTime(String name, long timeInNS) {
139             this.name = name;
140             this.timeInNS = timeInNS;
141         }
142 
143         public String name;
144         public long timeInNS;
145     }
146 
147     /**
148      * Support class that receives status on test progress. You should not need to
149      * extend this interface yourself.
150      */
151     public interface Listener {
started(String className)152         void started(String className);
finished(String className)153         void finished(String className);
performance(String className, long itemTimeNS, int iterations, List<IntermediateTime> itermediates)154         void performance(String className,
155                 long itemTimeNS, int iterations,
156                 List<IntermediateTime> itermediates);
passed(String className)157         void passed(String className);
failed(String className, Throwable execption)158         void failed(String className, Throwable execption);
159     }
160 
TestRunner(Context context)161     public TestRunner(Context context) {
162         mContext = context;
163     }
164 
addListener(Listener listener)165     public void addListener(Listener listener) {
166         mListeners.add(listener);
167     }
168 
startProfiling()169     public void startProfiling() {
170         File file = new File("/tmp/trace");
171         file.mkdir();
172         String base = "/tmp/trace/" + mClassName + ".dmtrace";
173         Debug.startMethodTracing(base, 8 * 1024 * 1024);
174     }
175 
finishProfiling()176     public void finishProfiling() {
177         Debug.stopMethodTracing();
178     }
179 
started(String className)180     private void started(String className) {
181 
182         int count = mListeners.size();
183         for (int i = 0; i < count; i++) {
184             mListeners.get(i).started(className);
185         }
186     }
187 
finished(String className)188     private void finished(String className) {
189         int count = mListeners.size();
190         for (int i = 0; i < count; i++) {
191             mListeners.get(i).finished(className);
192         }
193     }
194 
performance(String className, long itemTimeNS, int iterations, List<IntermediateTime> intermediates)195     private void performance(String className,
196             long itemTimeNS,
197             int iterations,
198             List<IntermediateTime> intermediates) {
199         int count = mListeners.size();
200         for (int i = 0; i < count; i++) {
201             mListeners.get(i).performance(className,
202                     itemTimeNS,
203                     iterations,
204                     intermediates);
205         }
206     }
207 
passed(String className)208     public void passed(String className) {
209         mPassed++;
210         int count = mListeners.size();
211         for (int i = 0; i < count; i++) {
212             mListeners.get(i).passed(className);
213         }
214     }
215 
failed(String className, Throwable exception)216     public void failed(String className, Throwable exception) {
217         mFailed++;
218         int count = mListeners.size();
219         for (int i = 0; i < count; i++) {
220             mListeners.get(i).failed(className, exception);
221         }
222     }
223 
passedCount()224     public int passedCount() {
225         return mPassed;
226     }
227 
failedCount()228     public int failedCount() {
229         return mFailed;
230     }
231 
run(String[] classes)232     public void run(String[] classes) {
233         for (String cl : classes) {
234             run(cl);
235         }
236     }
237 
setInternalIterations(int count)238     public void setInternalIterations(int count) {
239         mInternalIterations = count;
240     }
241 
startTiming(boolean realTime)242     public void startTiming(boolean realTime) {
243         if (realTime) {
244             mStartTime = System.currentTimeMillis();
245         } else {
246             mStartTime = SystemClock.currentThreadTimeMillis();
247         }
248     }
249 
addIntermediate(String name)250     public void addIntermediate(String name) {
251         addIntermediate(name, (System.currentTimeMillis() - mStartTime) * 1000000);
252     }
253 
addIntermediate(String name, long timeInNS)254     public void addIntermediate(String name, long timeInNS) {
255         mIntermediates.add(new IntermediateTime(name, timeInNS));
256     }
257 
finishTiming(boolean realTime)258     public void finishTiming(boolean realTime) {
259         if (realTime) {
260             mEndTime = System.currentTimeMillis();
261         } else {
262             mEndTime = SystemClock.currentThreadTimeMillis();
263         }
264     }
265 
setPerformanceMode(int mode)266     public void setPerformanceMode(int mode) {
267         mMode = mode;
268     }
269 
missingTest(String className, Throwable e)270     private void missingTest(String className, Throwable e) {
271         started(className);
272         finished(className);
273         failed(className, e);
274     }
275 
276     /*
277     This class determines if more suites are added to this class then adds all individual
278     test classes to a test suite for run
279      */
run(String className)280     public void run(String className) {
281         try {
282             mClassName = className;
283             Class clazz = mContext.getClassLoader().loadClass(className);
284             Method method = getChildrenMethod(clazz);
285             if (method != null) {
286                 String[] children = getChildren(method);
287                 run(children);
288             } else if (mRunnableClass.isAssignableFrom(clazz)) {
289                 Runnable test = (Runnable) clazz.newInstance();
290                 TestCase testcase = null;
291                 if (test instanceof TestCase) {
292                     testcase = (TestCase) test;
293                 }
294                 Throwable e = null;
295                 boolean didSetup = false;
296                 started(className);
297                 try {
298                     if (testcase != null) {
299                         testcase.setUp(mContext);
300                         didSetup = true;
301                     }
302                     if (mMode == PERFORMANCE) {
303                         runInPerformanceMode(test, className, false, className);
304                     } else if (mMode == PROFILING) {
305                         //Need a way to mark a test to be run in profiling mode or not.
306                         startProfiling();
307                         test.run();
308                         finishProfiling();
309                     } else {
310                         test.run();
311                     }
312                 } catch (Throwable ex) {
313                     e = ex;
314                 }
315                 if (testcase != null && didSetup) {
316                     try {
317                         testcase.tearDown();
318                     } catch (Throwable ex) {
319                         e = ex;
320                     }
321                 }
322                 finished(className);
323                 if (e == null) {
324                     passed(className);
325                 } else {
326                     failed(className, e);
327                 }
328             } else if (mJUnitClass.isAssignableFrom(clazz)) {
329                 Throwable e = null;
330                 //Create a Junit Suite.
331                 JunitTestSuite suite = new JunitTestSuite();
332                 Method[] methods = getAllTestMethods(clazz);
333                 for (Method m : methods) {
334                     junit.framework.TestCase test = (junit.framework.TestCase) clazz.newInstance();
335                     test.setName(m.getName());
336 
337                     if (test instanceof AndroidTestCase) {
338                         AndroidTestCase testcase = (AndroidTestCase) test;
339                         try {
340                             testcase.setContext(mContext);
341                             testcase.setTestContext(mContext);
342                         } catch (Exception ex) {
343                             Log.i("TestHarness", ex.toString());
344                         }
345                     }
346                     suite.addTest(test);
347                 }
348                 if (mMode == PERFORMANCE) {
349                     final int testCount = suite.testCount();
350 
351                     for (int j = 0; j < testCount; j++) {
352                         Test test = suite.testAt(j);
353                         started(test.toString());
354                         try {
355                             runInPerformanceMode(test, className, true, test.toString());
356                         } catch (Throwable ex) {
357                             e = ex;
358                         }
359                         finished(test.toString());
360                         if (e == null) {
361                             passed(test.toString());
362                         } else {
363                             failed(test.toString(), e);
364                         }
365                     }
366                 } else if (mMode == PROFILING) {
367                     //Need a way to mark a test to be run in profiling mode or not.
368                     startProfiling();
369                     junit.textui.TestRunner.run(suite);
370                     finishProfiling();
371                 } else {
372                     junit.textui.TestRunner.run(suite);
373                 }
374             } else {
375                 System.out.println("Test wasn't Runnable and didn't have a"
376                         + " children method: " + className);
377             }
378         } catch (ClassNotFoundException e) {
379             Log.e("ClassNotFoundException for " + className, e.toString());
380             if (isJunitTest(className)) {
381                 runSingleJunitTest(className);
382             } else {
383                 missingTest(className, e);
384             }
385         } catch (InstantiationException e) {
386             System.out.println("InstantiationException for " + className);
387             missingTest(className, e);
388         } catch (IllegalAccessException e) {
389             System.out.println("IllegalAccessException for " + className);
390             missingTest(className, e);
391         }
392     }
393 
runInPerformanceMode(Object testCase, String className, boolean junitTest, String testNameInDb)394     public void runInPerformanceMode(Object testCase, String className, boolean junitTest,
395             String testNameInDb) throws Exception {
396         boolean increaseIterations = true;
397         int iterations = 1;
398         long duration = 0;
399         mIntermediates = null;
400 
401         mInternalIterations = 1;
402         Class clazz = mContext.getClassLoader().loadClass(className);
403         Object perftest = clazz.newInstance();
404 
405         PerformanceTestCase perftestcase = null;
406         if (perftest instanceof PerformanceTestCase) {
407             perftestcase = (PerformanceTestCase) perftest;
408             // only run the test if it is not marked as a performance only test
409             if (mMode == REGRESSION && perftestcase.isPerformanceOnly()) return;
410         }
411 
412         // First force GCs, to avoid GCs happening during out
413         // test and skewing its time.
414         Runtime.getRuntime().runFinalization();
415         Runtime.getRuntime().gc();
416 
417         if (perftestcase != null) {
418             mIntermediates = new ArrayList<IntermediateTime>();
419             iterations = perftestcase.startPerformance(this);
420             if (iterations > 0) {
421                 increaseIterations = false;
422             } else {
423                 iterations = 1;
424             }
425         }
426 
427         // Pause briefly to let things settle down...
428         Thread.sleep(1000);
429         do {
430             mEndTime = 0;
431             if (increaseIterations) {
432                 // Test case does not implement
433                 // PerformanceTestCase or returned 0 iterations,
434                 // so we take care of measure the whole test time.
435                 mStartTime = SystemClock.currentThreadTimeMillis();
436             } else {
437                 // Try to make it obvious if the test case
438                 // doesn't call startTiming().
439                 mStartTime = 0;
440             }
441 
442             if (junitTest) {
443                 for (int i = 0; i < iterations; i++) {
444                     junit.textui.TestRunner.run((junit.framework.Test) testCase);
445                 }
446             } else {
447                 Runnable test = (Runnable) testCase;
448                 for (int i = 0; i < iterations; i++) {
449                     test.run();
450                 }
451             }
452 
453             long endTime = mEndTime;
454             if (endTime == 0) {
455                 endTime = SystemClock.currentThreadTimeMillis();
456             }
457 
458             duration = endTime - mStartTime;
459             if (!increaseIterations) {
460                 break;
461             }
462             if (duration <= 1) {
463                 iterations *= 1000;
464             } else if (duration <= 10) {
465                 iterations *= 100;
466             } else if (duration < 100) {
467                 iterations *= 10;
468             } else if (duration < 1000) {
469                 iterations *= (int) ((1000 / duration) + 2);
470             } else {
471                 break;
472             }
473         } while (true);
474 
475         if (duration != 0) {
476             iterations *= mInternalIterations;
477             performance(testNameInDb, (duration * 1000000) / iterations,
478                     iterations, mIntermediates);
479         }
480     }
481 
runSingleJunitTest(String className)482     public void runSingleJunitTest(String className) {
483         Throwable excep = null;
484         int index = className.lastIndexOf('$');
485         String testName = "";
486         String originalClassName = className;
487         if (index >= 0) {
488             className = className.substring(0, index);
489             testName = originalClassName.substring(index + 1);
490         }
491         try {
492             Class clazz = mContext.getClassLoader().loadClass(className);
493             if (mJUnitClass.isAssignableFrom(clazz)) {
494                 junit.framework.TestCase test = (junit.framework.TestCase) clazz.newInstance();
495                 JunitTestSuite newSuite = new JunitTestSuite();
496                 test.setName(testName);
497 
498                 if (test instanceof AndroidTestCase) {
499                     AndroidTestCase testcase = (AndroidTestCase) test;
500                     try {
501                         testcase.setContext(mContext);
502                     } catch (Exception ex) {
503                         Log.w(TAG, "Exception encountered while trying to set the context.", ex);
504                     }
505                 }
506                 newSuite.addTest(test);
507 
508                 if (mMode == PERFORMANCE) {
509                     try {
510                         started(test.toString());
511                         runInPerformanceMode(test, className, true, test.toString());
512                         finished(test.toString());
513                         if (excep == null) {
514                             passed(test.toString());
515                         } else {
516                             failed(test.toString(), excep);
517                         }
518                     } catch (Throwable ex) {
519                         excep = ex;
520                     }
521 
522                 } else if (mMode == PROFILING) {
523                     startProfiling();
524                     junit.textui.TestRunner.run(newSuite);
525                     finishProfiling();
526                 } else {
527                     junit.textui.TestRunner.run(newSuite);
528                 }
529             }
530         } catch (ClassNotFoundException e) {
531             Log.e("TestHarness", "No test case to run", e);
532         } catch (IllegalAccessException e) {
533             Log.e("TestHarness", "Illegal Access Exception", e);
534         } catch (InstantiationException e) {
535             Log.e("TestHarness", "Instantiation Exception", e);
536         }
537     }
538 
getChildrenMethod(Class clazz)539     public static Method getChildrenMethod(Class clazz) {
540         try {
541             return clazz.getMethod("children", (Class[]) null);
542         } catch (NoSuchMethodException e) {
543         }
544 
545         return null;
546     }
547 
getChildrenMethod(Context c, String className)548     public static Method getChildrenMethod(Context c, String className) {
549         try {
550             return getChildrenMethod(c.getClassLoader().loadClass(className));
551         } catch (ClassNotFoundException e) {
552         }
553         return null;
554     }
555 
getChildren(Context c, String className)556     public static String[] getChildren(Context c, String className) {
557         Method m = getChildrenMethod(c, className);
558         String[] testChildren = getTestChildren(c, className);
559         if (m == null & testChildren == null) {
560             throw new RuntimeException("couldn't get children method for "
561                     + className);
562         }
563         if (m != null) {
564             String[] children = getChildren(m);
565             if (testChildren != null) {
566                 String[] allChildren = new String[testChildren.length + children.length];
567                 System.arraycopy(children, 0, allChildren, 0, children.length);
568                 System.arraycopy(testChildren, 0, allChildren, children.length, testChildren.length);
569                 return allChildren;
570             } else {
571                 return children;
572             }
573         } else {
574             if (testChildren != null) {
575                 return testChildren;
576             }
577         }
578         return null;
579     }
580 
getChildren(Method m)581     public static String[] getChildren(Method m) {
582         try {
583             if (!Modifier.isStatic(m.getModifiers())) {
584                 throw new RuntimeException("children method is not static");
585             }
586             return (String[]) m.invoke(null, (Object[]) null);
587         } catch (IllegalAccessException e) {
588         } catch (InvocationTargetException e) {
589         }
590         return new String[0];
591     }
592 
getTestChildren(Context c, String className)593     public static String[] getTestChildren(Context c, String className) {
594         try {
595             Class clazz = c.getClassLoader().loadClass(className);
596 
597             if (mJUnitClass.isAssignableFrom(clazz)) {
598                 return getTestChildren(clazz);
599             }
600         } catch (ClassNotFoundException e) {
601             Log.e("TestHarness", "No class found", e);
602         }
603         return null;
604     }
605 
getTestChildren(Class clazz)606     public static String[] getTestChildren(Class clazz) {
607         Method[] methods = getAllTestMethods(clazz);
608 
609         String[] onScreenTestNames = new String[methods.length];
610         int index = 0;
611         for (Method m : methods) {
612             onScreenTestNames[index] = clazz.getName() + "$" + m.getName();
613             index++;
614         }
615         return onScreenTestNames;
616     }
617 
getAllTestMethods(Class clazz)618     public static Method[] getAllTestMethods(Class clazz) {
619         Method[] allMethods = clazz.getDeclaredMethods();
620         int numOfMethods = 0;
621         for (Method m : allMethods) {
622             boolean mTrue = isTestMethod(m);
623             if (mTrue) {
624                 numOfMethods++;
625             }
626         }
627         int index = 0;
628         Method[] testMethods = new Method[numOfMethods];
629         for (Method m : allMethods) {
630             boolean mTrue = isTestMethod(m);
631             if (mTrue) {
632                 testMethods[index] = m;
633                 index++;
634             }
635         }
636         return testMethods;
637     }
638 
isTestMethod(Method m)639     private static boolean isTestMethod(Method m) {
640         return m.getName().startsWith("test") &&
641                 m.getReturnType() == void.class &&
642                 m.getParameterTypes().length == 0;
643     }
644 
countJunitTests(Class clazz)645     public static int countJunitTests(Class clazz) {
646         Method[] allTestMethods = getAllTestMethods(clazz);
647         int numberofMethods = allTestMethods.length;
648 
649         return numberofMethods;
650     }
651 
isTestSuite(Context c, String className)652     public static boolean isTestSuite(Context c, String className) {
653         boolean childrenMethods = getChildrenMethod(c, className) != null;
654 
655         try {
656             Class clazz = c.getClassLoader().loadClass(className);
657             if (mJUnitClass.isAssignableFrom(clazz)) {
658                 int numTests = countJunitTests(clazz);
659                 if (numTests > 0)
660                     childrenMethods = true;
661             }
662         } catch (ClassNotFoundException e) {
663         }
664         return childrenMethods;
665     }
666 
667 
isJunitTest(String className)668     public boolean isJunitTest(String className) {
669         int index = className.lastIndexOf('$');
670         if (index >= 0) {
671             className = className.substring(0, index);
672         }
673         try {
674             Class clazz = mContext.getClassLoader().loadClass(className);
675             if (mJUnitClass.isAssignableFrom(clazz)) {
676                 return true;
677             }
678         } catch (ClassNotFoundException e) {
679         }
680         return false;
681     }
682 
683     /**
684      * Returns the number of tests that will be run if you try to do this.
685      */
countTests(Context c, String className)686     public static int countTests(Context c, String className) {
687         try {
688             Class clazz = c.getClassLoader().loadClass(className);
689             Method method = getChildrenMethod(clazz);
690             if (method != null) {
691 
692                 String[] children = getChildren(method);
693                 int rv = 0;
694                 for (String child : children) {
695                     rv += countTests(c, child);
696                 }
697                 return rv;
698             } else if (mRunnableClass.isAssignableFrom(clazz)) {
699                 return 1;
700             } else if (mJUnitClass.isAssignableFrom(clazz)) {
701                 return countJunitTests(clazz);
702             }
703         } catch (ClassNotFoundException e) {
704             return 1; // this gets the count right, because either this test
705             // is missing, and it will fail when run or it is a single Junit test to be run.
706         }
707         return 0;
708     }
709 
710     /**
711      * Returns a title to display given the className of a test.
712      * <p/>
713      * <p>Currently this function just returns the portion of the
714      * class name after the last '.'
715      */
getTitle(String className)716     public static String getTitle(String className) {
717         int indexDot = className.lastIndexOf('.');
718         int indexDollar = className.lastIndexOf('$');
719         int index = indexDot > indexDollar ? indexDot : indexDollar;
720         if (index >= 0) {
721             className = className.substring(index + 1);
722         }
723         return className;
724     }
725 }
726