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