blob: 012df35010269ec088ca9713d37c350916a5fe55 [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.
Dmitri Plotnikov44a29dd2009-08-03 14:27:20 -070042 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043 * {@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
Dmitri Plotnikov44a29dd2009-08-03 14:27:20 -070087 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 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 */
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 */
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 */
123 public void addError(Test test, Throwable t) {
124 mError = true;
125 failed(test.toString(), t);
126 }
127
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 {
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 {
152 void started(String className);
153 void finished(String className);
154 void performance(String className,
155 long itemTimeNS, int iterations,
156 List<IntermediateTime> itermediates);
157 void passed(String className);
158 void failed(String className, Throwable execption);
159 }
160
161 public TestRunner(Context context) {
162 mContext = context;
163 }
164
165 public void addListener(Listener listener) {
166 mListeners.add(listener);
167 }
168
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
176 public void finishProfiling() {
177 Debug.stopMethodTracing();
178 }
179
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
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
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
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
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
224 public int passedCount() {
225 return mPassed;
226 }
227
228 public int failedCount() {
229 return mFailed;
230 }
231
232 public void run(String[] classes) {
233 for (String cl : classes) {
234 run(cl);
235 }
236 }
237
238 public void setInternalIterations(int count) {
239 mInternalIterations = count;
240 }
241
242 public void startTiming(boolean realTime) {
243 if (realTime) {
244 mStartTime = System.currentTimeMillis();
245 } else {
246 mStartTime = SystemClock.currentThreadTimeMillis();
247 }
248 }
249
250 public void addIntermediate(String name) {
251 addIntermediate(name, (System.currentTimeMillis() - mStartTime) * 1000000);
252 }
253
254 public void addIntermediate(String name, long timeInNS) {
255 mIntermediates.add(new IntermediateTime(name, timeInNS));
256 }
257
258 public void finishTiming(boolean realTime) {
259 if (realTime) {
260 mEndTime = System.currentTimeMillis();
261 } else {
262 mEndTime = SystemClock.currentThreadTimeMillis();
263 }
264 }
265
266 public void setPerformanceMode(int mode) {
267 mMode = mode;
268 }
269
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 */
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) {
Dmitri Plotnikov44a29dd2009-08-03 14:27:20 -0700305 //Need a way to mark a test to be run in profiling mode or not.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 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);
Dmitri Plotnikov44a29dd2009-08-03 14:27:20 -0700341 testcase.setTestContext(mContext);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 } 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
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
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
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
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
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
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
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
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
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
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
645 public static int countJunitTests(Class clazz) {
646 Method[] allTestMethods = getAllTestMethods(clazz);
647 int numberofMethods = allTestMethods.length;
648
649 return numberofMethods;
650 }
651
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
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 */
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
Dmitri Plotnikov44a29dd2009-08-03 14:27:20 -0700705 // is missing, and it will fail when run or it is a single Junit test to be run.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800706 }
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 */
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}