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 java.util.concurrent.atomic.AtomicReference; 31import vogar.Result; 32import vogar.TestProperties; 33import vogar.monitor.TargetMonitor; 34import vogar.target.junit.JUnitRunner; 35 36/** 37 * Runs an action, in process on the target. 38 */ 39public final class TestRunner { 40 41 protected final Properties properties; 42 43 protected final String qualifiedName; 44 protected final String qualifiedClassOrPackageName; 45 46 /** the monitor port if a monitor is expected, or null for no monitor */ 47 protected final Integer monitorPort; 48 49 /** use an atomic reference so the runner can null it out when it is encountered. */ 50 protected final AtomicReference<String> skipPastReference; 51 protected final int timeoutSeconds; 52 protected final List<Runner> runners; 53 private final boolean profile; 54 private final int profileDepth; 55 private final int profileInterval; 56 private final File profileFile; 57 private final boolean profileThreadGroup; 58 protected final String[] args; 59 private boolean useSocketMonitor; 60 61 public TestRunner(List<String> argsList) { 62 properties = loadProperties(); 63 qualifiedName = properties.getProperty(TestProperties.QUALIFIED_NAME); 64 qualifiedClassOrPackageName = properties.getProperty(TestProperties.TEST_CLASS_OR_PACKAGE); 65 timeoutSeconds = Integer.parseInt(properties.getProperty(TestProperties.TIMEOUT)); 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 boolean testOnly = Boolean.parseBoolean(properties.getProperty(TestProperties.TEST_ONLY)); 78 if (testOnly) { 79 runners = Arrays.asList((Runner)new JUnitRunner()); 80 } else { 81 runners = Arrays.asList(new JUnitRunner(), 82 new CaliperRunner(), 83 new MainRunner()); 84 } 85 for (Iterator<String> i = argsList.iterator(); i.hasNext(); ) { 86 String arg = i.next(); 87 if (arg.equals("--monitorPort")) { 88 i.remove(); 89 monitorPort = Integer.parseInt(i.next()); 90 i.remove(); 91 } 92 if (arg.equals("--skipPast")) { 93 i.remove(); 94 skipPast = i.next(); 95 i.remove(); 96 } 97 } 98 99 this.monitorPort = monitorPort; 100 this.skipPastReference = new AtomicReference<String>(skipPast); 101 this.profile = profile; 102 this.profileDepth = profileDepth; 103 this.profileInterval = profileInterval; 104 this.profileFile = profileFile; 105 this.profileThreadGroup = profileThreadGroup; 106 this.args = argsList.toArray(new String[argsList.size()]); 107 } 108 109 private Properties loadProperties() { 110 try { 111 InputStream in = getPropertiesStream(); 112 Properties properties = new Properties(); 113 properties.load(in); 114 in.close(); 115 return properties; 116 } catch (IOException e) { 117 throw new RuntimeException(e); 118 } 119 } 120 121 /** 122 * Configure this test runner to await an incoming socket connection when 123 * writing test results. Otherwise all communication happens over 124 * System.out. 125 */ 126 public void useSocketMonitor() { 127 this.useSocketMonitor = true; 128 } 129 130 /** 131 * Attempt to load the test properties file from both the application and system classloader. 132 * This is necessary because sometimes we run tests from the boot classpath. 133 */ 134 private InputStream getPropertiesStream() throws IOException { 135 for (Class<?> classToLoadFrom : new Class<?>[] { TestRunner.class, Object.class }) { 136 InputStream propertiesStream = classToLoadFrom.getResourceAsStream( 137 "/" + TestProperties.FILE); 138 if (propertiesStream != null) { 139 return propertiesStream; 140 } 141 } 142 throw new IOException(TestProperties.FILE + " missing!"); 143 } 144 145 /** 146 * Returns the class to run the test with based on {@param klass}. For instance, a class 147 * that extends junit.framework.TestCase should be run with JUnitSpec. 148 * 149 * Returns null if no such associated runner exists. 150 */ 151 private Class<?> runnerClass(Class<?> klass) { 152 for (Runner runner : runners) { 153 if (runner.supports(klass)) { 154 return runner.getClass(); 155 } 156 } 157 158 return null; 159 } 160 161 public void run() throws IOException { 162 final TargetMonitor monitor = useSocketMonitor 163 ? TargetMonitor.await(monitorPort) 164 : TargetMonitor.forPrintStream(System.out); 165 166 PrintStream monitorPrintStream = new PrintStreamDecorator(System.out) { 167 @Override public void print(String str) { 168 monitor.output(str != null ? str : "null"); 169 } 170 }; 171 System.setOut(monitorPrintStream); 172 System.setErr(monitorPrintStream); 173 174 try { 175 run(monitor); 176 } catch (Throwable internalError) { 177 internalError.printStackTrace(monitorPrintStream); 178 } finally { 179 monitor.close(); 180 } 181 } 182 183 public void run(final TargetMonitor monitor) { 184 TestEnvironment testEnvironment = new TestEnvironment(); 185 testEnvironment.reset(); 186 187 String classOrPackageName; 188 String qualification; 189 190 // Check whether the class or package is qualified and, if so, strip it off and pass it 191 // separately to the runners. For instance, may qualify a junit class by appending 192 // #method_name, where method_name is the name of a single test of the class to run. 193 int hash_position = qualifiedClassOrPackageName.indexOf("#"); 194 if (hash_position != -1) { 195 classOrPackageName = qualifiedClassOrPackageName.substring(0, hash_position); 196 qualification = qualifiedClassOrPackageName.substring(hash_position + 1); 197 } else { 198 classOrPackageName = qualifiedClassOrPackageName; 199 qualification = null; 200 } 201 202 Set<Class<?>> classes = new ClassFinder().find(classOrPackageName); 203 204 // if there is more than one class in the set, this must be a package. Since we're 205 // running everything in the package already, remove any class called AllTests. 206 if (classes.size() > 1) { 207 Set<Class<?>> toRemove = new HashSet<Class<?>>(); 208 for (Class<?> klass : classes) { 209 if (klass.getName().endsWith(".AllTests")) { 210 toRemove.add(klass); 211 } 212 } 213 classes.removeAll(toRemove); 214 } 215 216 217 Profiler profiler = null; 218 if (profile) { 219 try { 220 profiler = Profiler.getInstance(); 221 } catch (Exception e) { 222 System.out.println("Profiling is disabled: " + e); 223 } 224 } 225 if (profiler != null) { 226 profiler.setup(profileThreadGroup, profileDepth, profileInterval); 227 } 228 for (Class<?> klass : classes) { 229 Class<?> runnerClass = runnerClass(klass); 230 if (runnerClass == null) { 231 monitor.outcomeStarted(null, klass.getName(), qualifiedName); 232 System.out.println("Skipping " + klass.getName() 233 + ": no associated runner class"); 234 monitor.outcomeFinished(Result.UNSUPPORTED); 235 continue; 236 } 237 238 Runner runner; 239 try { 240 runner = (Runner) runnerClass.newInstance(); 241 runner.init(monitor, qualifiedName, qualification, klass, skipPastReference, 242 testEnvironment, timeoutSeconds, profile); 243 } catch (Exception e) { 244 monitor.outcomeStarted(null, qualifiedName, qualifiedName); 245 e.printStackTrace(); 246 monitor.outcomeFinished(Result.ERROR); 247 return; 248 } 249 boolean completedNormally = runner.run(qualifiedName, profiler, args); 250 if (!completedNormally) { 251 return; // let the caller start another process 252 } 253 } 254 if (profiler != null) { 255 profiler.shutdown(profileFile); 256 } 257 258 monitor.completedNormally(true); 259 } 260 261 public static void main(String[] args) throws IOException { 262 new TestRunner(new ArrayList<String>(Arrays.asList(args))).run(); 263 System.exit(0); 264 } 265 266} 267