1 /* 2 * Copyright (C) 2011 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 vogar.tasks; 18 19 import java.io.File; 20 import java.io.IOException; 21 import vogar.Action; 22 import vogar.Classpath; 23 import vogar.Outcome; 24 import vogar.Result; 25 import vogar.Run; 26 import vogar.RunnerType; 27 import vogar.commands.Command; 28 import vogar.commands.VmCommandBuilder; 29 import vogar.monitor.HostMonitor; 30 import vogar.target.TestRunner; 31 32 /** 33 * Executes a single action and then prints the result. 34 */ 35 public class RunActionTask extends Task implements HostMonitor.Handler { 36 /** 37 * Assign each runner thread a unique ID. Necessary so threads don't 38 * conflict when selecting a monitor port. 39 */ 40 private final ThreadLocal<Integer> runnerThreadId = new ThreadLocal<Integer>() { 41 private int next = 0; 42 @Override protected synchronized Integer initialValue() { 43 return next++; 44 } 45 }; 46 47 protected final Run run; 48 private final int timeoutSeconds; 49 private final Action action; 50 private final String actionName; 51 private Command currentCommand; 52 private String lastStartedOutcome; 53 private String lastFinishedOutcome; 54 RunActionTask(Run run, Action action, boolean useLargeTimeout)55 public RunActionTask(Run run, Action action, boolean useLargeTimeout) { 56 super("run " + action.getName()); 57 this.run = run; 58 this.action = action; 59 this.actionName = action.getName(); 60 61 this.timeoutSeconds = useLargeTimeout 62 ? run.largeTimeoutSeconds 63 : run.smallTimeoutSeconds; 64 } 65 isAction()66 @Override public boolean isAction() { 67 return true; 68 } 69 execute()70 @Override protected Result execute() throws Exception { 71 run.console.action(actionName); 72 73 while (true) { 74 /* 75 * If the target process failed midway through a set of 76 * outcomes, that's okay. We pickup right after the first 77 * outcome that wasn't completed. 78 */ 79 String skipPast = lastStartedOutcome; 80 lastStartedOutcome = null; 81 82 currentCommand = createActionCommand(action, skipPast, monitorPort(-1)); 83 try { 84 currentCommand.start(); 85 86 if (timeoutSeconds != 0) { 87 currentCommand.scheduleTimeout(timeoutSeconds); 88 } 89 90 HostMonitor hostMonitor = new HostMonitor(run.console, this); 91 boolean completedNormally = useSocketMonitor() 92 ? hostMonitor.attach(monitorPort(run.firstMonitorPort)) 93 : hostMonitor.followStream(currentCommand.getInputStream()); 94 95 if (completedNormally) { 96 return Result.SUCCESS; 97 } 98 99 String earlyResultOutcome; 100 boolean giveUp; 101 102 if (lastStartedOutcome == null || lastStartedOutcome.equals(actionName)) { 103 earlyResultOutcome = actionName; 104 giveUp = true; 105 } else if (!lastStartedOutcome.equals(lastFinishedOutcome)) { 106 earlyResultOutcome = lastStartedOutcome; 107 giveUp = false; 108 } else { 109 continue; 110 } 111 112 run.driver.addEarlyResult(new Outcome(earlyResultOutcome, Result.ERROR, 113 "Action " + action + " did not complete normally.\n" 114 + "timedOut=" + currentCommand.timedOut() + "\n" 115 + "lastStartedOutcome=" + lastStartedOutcome + "\n" 116 + "lastFinishedOutcome=" + lastFinishedOutcome + "\n" 117 + "command=" + currentCommand)); 118 119 if (giveUp) { 120 return Result.ERROR; 121 } 122 } catch (IOException e) { 123 // if the monitor breaks, assume the worst and don't retry 124 run.driver.addEarlyResult(new Outcome(actionName, Result.ERROR, e)); 125 return Result.ERROR; 126 } finally { 127 currentCommand.destroy(); 128 currentCommand = null; 129 } 130 } 131 } 132 133 /** 134 * Create the command that executes the action. 135 * 136 * @param skipPast the last outcome to skip, or null to run all outcomes. 137 * @param monitorPort the port to accept connections on, or -1 for the 138 */ createActionCommand(Action action, String skipPast, int monitorPort)139 public Command createActionCommand(Action action, String skipPast, int monitorPort) { 140 File workingDirectory = action.getUserDir(); 141 VmCommandBuilder vmCommandBuilder = run.mode.newVmCommandBuilder(action, workingDirectory); 142 Classpath runtimeClasspath = run.mode.getRuntimeClasspath(action); 143 if (run.useBootClasspath) { 144 vmCommandBuilder.bootClasspath(runtimeClasspath); 145 } else { 146 vmCommandBuilder.classpath(runtimeClasspath); 147 } 148 if (monitorPort != -1) { 149 vmCommandBuilder.args("--monitorPort", Integer.toString(monitorPort)); 150 } 151 if (skipPast != null) { 152 vmCommandBuilder.args("--skipPast", skipPast); 153 } 154 155 // Forward specific parameters to Caliper. 156 if (run.runnerType.supportsCaliper()) { 157 // Forward timeout value to Caliper which has its own separate timeout. 158 vmCommandBuilder.args("--time-limit", String.format("%ds", timeoutSeconds)); 159 160 // This configuration runs about 15x faster than not having this configuration. 161 vmCommandBuilder.args( 162 // Don't run GC before each measurement. That will take forever. 163 "-Cinstrument.runtime.options.gcBeforeEach=false", 164 // Warmup super-quick, don't take more than 1sec. 165 "-Cinstrument.runtime.options.warmup=1s", 166 // Don't measure things 9 times (default) because microbenchmark already 167 // measure themselves millions of times. 168 "-Cinstrument.runtime.options.measurements=1"); 169 } 170 return vmCommandBuilder 171 .temp(workingDirectory) 172 .vmArgs(run.additionalVmArgs) 173 .mainClass(TestRunner.class.getName()) 174 .args(run.targetArgs) 175 .build(run.target); 176 } 177 178 /** 179 * Returns true if this mode requires a socket connection for reading test 180 * results. Otherwise all communication happens over the output stream of 181 * the forked process. 182 */ useSocketMonitor()183 protected boolean useSocketMonitor() { 184 return false; 185 } 186 monitorPort(int defaultValue)187 private int monitorPort(int defaultValue) { 188 return run.maxConcurrentActions == 1 189 ? defaultValue 190 : run.firstMonitorPort + runnerThreadId.get(); 191 } 192 start(String outcomeName)193 @Override public void start(String outcomeName) { 194 outcomeName = toQualifiedOutcomeName(outcomeName); 195 lastStartedOutcome = outcomeName; 196 if (run.runnerType.supportsCaliper()) { 197 run.console.verbose("running " + outcomeName + " with unlimited timeout"); 198 Command command = currentCommand; 199 if (command != null && timeoutSeconds != 0) { 200 command.scheduleTimeout(timeoutSeconds); 201 } 202 run.driver.recordResults = false; 203 } else { 204 run.driver.recordResults = true; 205 } 206 } 207 output(String outcomeName, String output)208 @Override public void output(String outcomeName, String output) { 209 outcomeName = toQualifiedOutcomeName(outcomeName); 210 run.console.outcome(outcomeName); 211 run.console.streamOutput(outcomeName, output); 212 } 213 finish(Outcome outcome)214 @Override public void finish(Outcome outcome) { 215 Command command = currentCommand; 216 if (command != null && timeoutSeconds != 0) { 217 command.scheduleTimeout(timeoutSeconds); 218 } 219 lastFinishedOutcome = toQualifiedOutcomeName(outcome.getName()); 220 // TODO: support flexible timeouts for JUnit tests 221 run.driver.recordOutcome(new Outcome(lastFinishedOutcome, outcome.getResult(), 222 outcome.getOutputLines())); 223 } 224 225 /** 226 * Test suites that use main classes in the default package have lame 227 * outcome names like "Clear" rather than "com.foo.Bar.Clear". In that 228 * case, just replace the outcome name with the action name. 229 */ toQualifiedOutcomeName(String outcomeName)230 private String toQualifiedOutcomeName(String outcomeName) { 231 if (actionName.endsWith("." + outcomeName) 232 && !outcomeName.contains(".") && !outcomeName.contains("#")) { 233 return actionName; 234 } else { 235 return outcomeName; 236 } 237 } 238 print(String string)239 @Override public void print(String string) { 240 run.console.streamOutput(string); 241 } 242 } 243