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