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 @Deprecated 46 public 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