1/* 2 * Copyright (C) 2010 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 vogar.target.junit; 18 19import java.util.List; 20import java.util.concurrent.Callable; 21import java.util.concurrent.ExecutorService; 22import java.util.concurrent.Executors; 23import java.util.concurrent.Future; 24import java.util.concurrent.TimeUnit; 25import java.util.concurrent.TimeoutException; 26import java.util.concurrent.atomic.AtomicReference; 27 28import junit.framework.AssertionFailedError; 29import vogar.Result; 30import vogar.monitor.TargetMonitor; 31import vogar.target.Profiler; 32import vogar.target.Runner; 33import vogar.target.TestEnvironment; 34import vogar.util.Threads; 35 36/** 37 * Adapts a JUnit3 test for use by vogar. 38 */ 39public final class JUnitRunner implements Runner { 40 41 private TargetMonitor monitor; 42 private Class<?> testClass; 43 private String qualification; 44 private AtomicReference<String> skipPastReference; 45 private String actionName; 46 private TestEnvironment testEnvironment; 47 private int timeoutSeconds; 48 private boolean vmIsUnstable; 49 50 private final ExecutorService executor = Executors.newCachedThreadPool( 51 Threads.daemonThreadFactory("testrunner")); 52 53 public void init(TargetMonitor monitor, String actionName, String qualification, 54 Class<?> testClass, AtomicReference<String> skipPastReference, 55 TestEnvironment testEnvironment, int timeoutSeconds, boolean profile) { 56 this.monitor = monitor; 57 this.testClass = testClass; 58 this.qualification = qualification; 59 this.skipPastReference = skipPastReference; 60 this.actionName = actionName; 61 this.testEnvironment = testEnvironment; 62 this.timeoutSeconds = timeoutSeconds; 63 } 64 65 public boolean run(String actionName, Profiler profiler, String[] args) { 66 final List<VogarTest> tests; 67 if (Junit3.isJunit3Test(testClass)) { 68 tests = qualification != null 69 ? Junit3.classToVogarTests(testClass, qualification) 70 : Junit3.classToVogarTests(testClass, args); 71 } else if (Junit4.isJunit4Test(testClass)) { 72 tests = qualification != null 73 ? Junit4.classToVogarTests(testClass, qualification) 74 : Junit4.classToVogarTests(testClass, args); 75 } else { 76 throw new AssertionFailedError("Unknown JUnit type: " + testClass.getName()); 77 } 78 79 for (VogarTest test : tests) { 80 String skipPast = skipPastReference.get(); 81 if (skipPast != null) { 82 if (skipPast.equals(test.toString())) { 83 skipPastReference.set(null); 84 } 85 continue; 86 } 87 88 runWithTimeout(profiler, test); 89 90 if (vmIsUnstable) { 91 return false; 92 } 93 } 94 95 return true; 96 } 97 98 /** 99 * Runs the test on another thread. If the test completes before the 100 * timeout, this reports the result normally. But if the test times out, 101 * this reports the timeout stack trace and begins the process of killing 102 * this no-longer-trustworthy process. 103 */ 104 private void runWithTimeout(final Profiler profiler, final VogarTest test) { 105 testEnvironment.reset(); 106 monitor.outcomeStarted(JUnitRunner.this, test.toString(), actionName); 107 108 // Start the test on a background thread. 109 final AtomicReference<Thread> executingThreadReference = new AtomicReference<Thread>(); 110 Future<Throwable> result = executor.submit(new Callable<Throwable>() { 111 public Throwable call() throws Exception { 112 executingThreadReference.set(Thread.currentThread()); 113 try { 114 if (profiler != null) { 115 profiler.start(); 116 } 117 test.run(); 118 return null; 119 } catch (Throwable throwable) { 120 return throwable; 121 } finally { 122 if (profiler != null) { 123 profiler.stop(); 124 } 125 } 126 } 127 }); 128 129 // Wait until either the result arrives or the test times out. 130 Throwable thrown; 131 try { 132 thrown = timeoutSeconds == 0 133 ? result.get() 134 : result.get(timeoutSeconds, TimeUnit.SECONDS); 135 } catch (TimeoutException e) { 136 vmIsUnstable = true; 137 Thread executingThread = executingThreadReference.get(); 138 if (executingThread != null) { 139 executingThread.interrupt(); 140 e.setStackTrace(executingThread.getStackTrace()); 141 } 142 thrown = e; 143 } catch (Exception e) { 144 thrown = e; 145 } 146 147 if (thrown != null) { 148 prepareForDisplay(thrown); 149 thrown.printStackTrace(System.out); 150 monitor.outcomeFinished(Result.EXEC_FAILED); 151 } else { 152 monitor.outcomeFinished(Result.SUCCESS); 153 } 154 } 155 156 /** 157 * Strip vogar's lines from the stack trace. For example, we'd strip the 158 * first two Assert lines and everything after the testFoo() line in this 159 * stack trace: 160 * 161 * at junit.framework.Assert.fail(Assert.java:198) 162 * at junit.framework.Assert.assertEquals(Assert.java:56) 163 * at junit.framework.Assert.assertEquals(Assert.java:61) 164 * at libcore.java.net.FooTest.testFoo(FooTest.java:124) 165 * at java.lang.reflect.Method.invokeNative(Native Method) 166 * at java.lang.reflect.Method.invoke(Method.java:491) 167 * at vogar.target.junit.Junit$JUnitTest.run(Junit.java:214) 168 * at vogar.target.junit.JUnitRunner$1.call(JUnitRunner.java:112) 169 * at vogar.target.junit.JUnitRunner$1.call(JUnitRunner.java:105) 170 * at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) 171 * at java.util.concurrent.FutureTask.run(FutureTask.java:137) 172 * at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076) 173 * at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569) 174 * at java.lang.Thread.run(Thread.java:863) 175 */ 176 public void prepareForDisplay(Throwable t) { 177 StackTraceElement[] stackTraceElements = t.getStackTrace(); 178 boolean foundVogar = false; 179 180 int last = stackTraceElements.length - 1; 181 for (; last >= 0; last--) { 182 String className = stackTraceElements[last].getClassName(); 183 if (className.startsWith("vogar.target")) { 184 foundVogar = true; 185 } else if (foundVogar 186 && !className.startsWith("java.lang.reflect") 187 && !className.startsWith("sun.reflect") 188 && !className.startsWith("junit.framework")) { 189 if (last < stackTraceElements.length) { 190 last++; 191 } 192 break; 193 } 194 } 195 196 int first = 0; 197 for (; first < last; first++) { 198 String className = stackTraceElements[first].getClassName(); 199 if (!className.startsWith("junit.framework")) { 200 break; 201 } 202 } 203 204 if (first > 0) { 205 first--; // retain one assertSomething() line in the trace 206 } 207 208 if (first < last) { 209 // Arrays.copyOfRange() didn't exist on Froyo 210 StackTraceElement[] copyOfRange = new StackTraceElement[last - first]; 211 System.arraycopy(stackTraceElements, first, copyOfRange, 0, last - first); 212 t.setStackTrace(copyOfRange); 213 } 214 } 215 216 public boolean supports(Class<?> klass) { 217 return Junit3.isJunit3Test(klass) || Junit4.isJunit4Test(klass); 218 } 219} 220