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