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 83 public JunitTestSuite() { 84 super(); 85 } 86 87 @Override 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 */ 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) { 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 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 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 */ 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