TestRunner.java revision a20d57c3defdf4faad67b052e29aaa8be099cae6
1/* 2 * Copyright (C) 2009 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; 18 19import java.io.File; 20import java.io.IOException; 21import java.io.InputStream; 22import java.io.PrintStream; 23import java.util.ArrayList; 24import java.util.Arrays; 25import java.util.HashSet; 26import java.util.Iterator; 27import java.util.List; 28import java.util.Properties; 29import java.util.Set; 30import vogar.Result; 31import vogar.TestProperties; 32import vogar.monitor.TargetMonitor; 33 34/** 35 * Runs an action, in process on the target. 36 */ 37public final class TestRunner { 38 39 protected final Properties properties; 40 41 protected final String qualifiedName; 42 protected final String qualifiedClassOrPackageName; 43 44 /** the monitor port if a monitor is expected, or null for no monitor */ 45 protected final Integer monitorPort; 46 protected final String skipPast; 47 protected final int timeoutSeconds; 48 protected final List<Runner> runners; 49 private final boolean profile; 50 private final int profileDepth; 51 private final int profileInterval; 52 private final File profileFile; 53 private final boolean profileThreadGroup; 54 protected final String[] args; 55 private boolean useSocketMonitor; 56 57 public TestRunner(List<String> argsList) { 58 properties = loadProperties(); 59 qualifiedName = properties.getProperty(TestProperties.QUALIFIED_NAME); 60 qualifiedClassOrPackageName = properties.getProperty(TestProperties.TEST_CLASS_OR_PACKAGE); 61 timeoutSeconds = Integer.parseInt(properties.getProperty(TestProperties.TIMEOUT)); 62 runners = Arrays.asList(new JUnitRunner(), 63 new JUnit4Runner(), 64 new CaliperRunner(), 65 new MainRunner()); 66 67 int monitorPort = Integer.parseInt(properties.getProperty(TestProperties.MONITOR_PORT)); 68 String skipPast = null; 69 boolean profile = Boolean.parseBoolean(properties.getProperty(TestProperties.PROFILE)); 70 int profileDepth = Integer.parseInt(properties.getProperty(TestProperties.PROFILE_DEPTH)); 71 int profileInterval 72 = Integer.parseInt(properties.getProperty(TestProperties.PROFILE_INTERVAL)); 73 File profileFile = new File(properties.getProperty(TestProperties.PROFILE_FILE)); 74 boolean profileThreadGroup 75 = Boolean.parseBoolean(properties.getProperty(TestProperties.PROFILE_THREAD_GROUP)); 76 77 for (Iterator<String> i = argsList.iterator(); i.hasNext(); ) { 78 String arg = i.next(); 79 if (arg.equals("--monitorPort")) { 80 i.remove(); 81 monitorPort = Integer.parseInt(i.next()); 82 i.remove(); 83 } 84 if (arg.equals("--skipPast")) { 85 i.remove(); 86 skipPast = i.next(); 87 i.remove(); 88 } 89 } 90 91 this.monitorPort = monitorPort; 92 this.skipPast = skipPast; 93 this.profile = profile; 94 this.profileDepth = profileDepth; 95 this.profileInterval = profileInterval; 96 this.profileFile = profileFile; 97 this.profileThreadGroup = profileThreadGroup; 98 this.args = argsList.toArray(new String[argsList.size()]); 99 } 100 101 private Properties loadProperties() { 102 try { 103 InputStream in = getPropertiesStream(); 104 Properties properties = new Properties(); 105 properties.load(in); 106 in.close(); 107 return properties; 108 } catch (IOException e) { 109 throw new RuntimeException(e); 110 } 111 } 112 113 /** 114 * Configure this test runner to await an incoming socket connection when 115 * writing test results. Otherwise all communication happens over 116 * System.out. 117 */ 118 public void useSocketMonitor() { 119 this.useSocketMonitor = true; 120 } 121 122 /** 123 * Attempt to load the test properties file from both the application and system classloader. 124 * This is necessary because sometimes we run tests from the boot classpath. 125 */ 126 private InputStream getPropertiesStream() throws IOException { 127 for (Class<?> classToLoadFrom : new Class<?>[] { TestRunner.class, Object.class }) { 128 InputStream propertiesStream = classToLoadFrom.getResourceAsStream( 129 "/" + TestProperties.FILE); 130 if (propertiesStream != null) { 131 return propertiesStream; 132 } 133 } 134 throw new IOException(TestProperties.FILE + " missing!"); 135 } 136 137 /** 138 * Returns the class to run the test with based on {@param klass}. For instance, a class 139 * that extends junit.framework.TestCase should be run with JUnitSpec. 140 * 141 * Returns null if no such associated runner exists. 142 */ 143 private Class<?> runnerClass(Class<?> klass) { 144 for (Runner runner : runners) { 145 if (runner.supports(klass)) { 146 return runner.getClass(); 147 } 148 } 149 150 return null; 151 } 152 153 public void run() throws IOException { 154 TargetMonitor monitor = useSocketMonitor 155 ? TargetMonitor.await(monitorPort) 156 : TargetMonitor.forPrintStream(System.out); 157 try { 158 run(monitor); 159 } finally { 160 monitor.close(); 161 } 162 } 163 164 public void run(final TargetMonitor monitor) { 165 PrintStream monitorPrintStream = new PrintStream(System.out) { 166 @Override public void print(long l) { 167 print(String.valueOf(l)); 168 } 169 170 @Override public void print(int i) { 171 print(String.valueOf(i)); 172 } 173 174 @Override public void print(float f) { 175 print(String.valueOf(f)); 176 } 177 178 @Override public void print(double d) { 179 print(String.valueOf(d)); 180 } 181 182 @Override public void print(char[] s) { 183 print(String.valueOf(s)); 184 } 185 186 @Override public void print(char c) { 187 print(String.valueOf(c)); 188 } 189 190 @Override public void print(Object obj) { 191 print(obj != null ? obj.toString() : "null"); 192 } 193 194 @Override public void print(String str) { 195 monitor.output(str != null ? str : "null"); 196 } 197 198 @Override public void println() { 199 print("\n"); 200 } 201 202 /** 203 * Although println() is documented to be equivalent to print() 204 * followed by println(), this isn't the behavior on HotSpot 205 * and we must manually override println(String) to ensure that 206 * newlines aren't dropped. 207 */ 208 @Override public void println(String s) { 209 print(s + "\n"); 210 } 211 212 @Override public void println(long l) { 213 println(String.valueOf(l)); 214 } 215 216 @Override public void println(int i) { 217 println(String.valueOf(i)); 218 } 219 220 @Override public void println(float f) { 221 println(String.valueOf(f)); 222 } 223 224 @Override public void println(double d) { 225 println(String.valueOf(d)); 226 } 227 228 @Override public void println(char[] s) { 229 println(String.valueOf(s)); 230 } 231 232 @Override public void println(char c) { 233 println(String.valueOf(c)); 234 } 235 236 @Override public void println(Object obj) { 237 println(obj != null ? obj.toString() : "null"); 238 } 239 }; 240 System.setOut(monitorPrintStream); 241 System.setErr(monitorPrintStream); 242 243 TestEnvironment testEnvironment = new TestEnvironment(); 244 testEnvironment.reset(); 245 246 String classOrPackageName; 247 String qualification; 248 249 // Check whether the class or package is qualified and, if so, strip it off and pass it 250 // separately to the runners. For instance, may qualify a junit class by appending 251 // #method_name, where method_name is the name of a single test of the class to run. 252 int hash_position = qualifiedClassOrPackageName.indexOf("#"); 253 if (hash_position != -1) { 254 classOrPackageName = qualifiedClassOrPackageName.substring(0, hash_position); 255 qualification = qualifiedClassOrPackageName.substring(hash_position + 1); 256 } else { 257 classOrPackageName = qualifiedClassOrPackageName; 258 qualification = null; 259 } 260 261 Set<Class<?>> classes = new ClassFinder().find(classOrPackageName); 262 263 // if there is more than one class in the set, this must be a package. Since we're 264 // running everything in the package already, remove any class called AllTests. 265 if (classes.size() > 1) { 266 Set<Class<?>> toRemove = new HashSet<Class<?>>(); 267 for (Class<?> klass : classes) { 268 if (klass.getName().endsWith(".AllTests")) { 269 toRemove.add(klass); 270 } 271 } 272 classes.removeAll(toRemove); 273 } 274 275 Profiler profiler = profile ? Profiler.getInstance() : null; 276 if (profiler != null) { 277 profiler.setup(profileThreadGroup, profileDepth, profileInterval); 278 } 279 for (Class<?> klass : classes) { 280 Class<?> runnerClass = runnerClass(klass); 281 if (runnerClass == null) { 282 monitor.outcomeStarted(null, klass.getName(), qualifiedName); 283 System.out.println("Skipping " + klass.getName() 284 + ": no associated runner class"); 285 monitor.outcomeFinished(Result.UNSUPPORTED); 286 continue; 287 } 288 289 Runner runner; 290 try { 291 runner = (Runner) runnerClass.newInstance(); 292 runner.init(monitor, qualifiedName, qualification, klass, testEnvironment, 293 timeoutSeconds, profile); 294 } catch (Exception e) { 295 monitor.outcomeStarted(null, qualifiedName, qualifiedName); 296 e.printStackTrace(); 297 monitor.outcomeFinished(Result.ERROR); 298 return; 299 } 300 boolean completedNormally = runner.run(qualifiedName, klass, skipPast, profiler, args); 301 if (!completedNormally) { 302 return; // let the caller start another process 303 } 304 } 305 if (profiler != null) { 306 profiler.shutdown(profileFile); 307 } 308 309 monitor.completedNormally(true); 310 } 311 312 public static void main(String[] args) throws IOException { 313 new TestRunner(new ArrayList<String>(Arrays.asList(args))).run(); 314 } 315} 316