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.Classpath; 23import vogar.Outcome; 24import vogar.Result; 25import vogar.Run; 26import vogar.commands.Command; 27import vogar.commands.VmCommandBuilder; 28import vogar.monitor.HostMonitor; 29import vogar.target.CaliperRunner; 30import vogar.target.TestRunner; 31 32/** 33 * Executes a single action and then prints the result. 34 */ 35public 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 boolean useLargeTimeout; 49 private final Action action; 50 private final String actionName; 51 private Command currentCommand; 52 private String lastStartedOutcome; 53 private String lastFinishedOutcome; 54 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 this.useLargeTimeout = useLargeTimeout; 61 } 62 63 @Override public boolean isAction() { 64 return true; 65 } 66 67 @Override protected Result execute() throws Exception { 68 run.console.action(actionName); 69 70 while (true) { 71 /* 72 * If the target process failed midway through a set of 73 * outcomes, that's okay. We pickup right after the first 74 * outcome that wasn't completed. 75 */ 76 String skipPast = lastStartedOutcome; 77 lastStartedOutcome = null; 78 79 currentCommand = createActionCommand(action, skipPast, monitorPort(-1)); 80 try { 81 currentCommand.start(); 82 83 int timeoutSeconds = useLargeTimeout 84 ? run.largeTimeoutSeconds 85 : run.smallTimeoutSeconds; 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 */ 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 return vmCommandBuilder 155 .temp(workingDirectory) 156 .debugPort(run.debugPort) 157 .vmArgs(run.additionalVmArgs) 158 .mainClass(TestRunner.class.getName()) 159 .args(run.targetArgs) 160 .build(run.target); 161 } 162 163 /** 164 * Returns true if this mode requires a socket connection for reading test 165 * results. Otherwise all communication happens over the output stream of 166 * the forked process. 167 */ 168 protected boolean useSocketMonitor() { 169 return false; 170 } 171 172 private int monitorPort(int defaultValue) { 173 return run.maxConcurrentActions == 1 174 ? defaultValue 175 : run.firstMonitorPort + runnerThreadId.get(); 176 } 177 178 @Override public void start(String outcomeName, String runnerClass) { 179 outcomeName = toQualifiedOutcomeName(outcomeName); 180 lastStartedOutcome = outcomeName; 181 // TODO add to Outcome knowledge about what class was used to run it 182 if (CaliperRunner.class.getName().equals(runnerClass)) { 183 if (!run.benchmark) { 184 throw new RuntimeException("you must use --benchmark when running Caliper " 185 + "benchmarks."); 186 } 187 run.console.verbose("running " + outcomeName + " with unlimited timeout"); 188 Command command = currentCommand; 189 if (command != null && run.smallTimeoutSeconds != 0) { 190 command.scheduleTimeout(run.smallTimeoutSeconds); 191 } 192 run.driver.recordResults = false; 193 } else { 194 run.driver.recordResults = true; 195 } 196 } 197 198 @Override public void output(String outcomeName, String output) { 199 outcomeName = toQualifiedOutcomeName(outcomeName); 200 run.console.outcome(outcomeName); 201 run.console.streamOutput(outcomeName, output); 202 } 203 204 @Override public void finish(Outcome outcome) { 205 Command command = currentCommand; 206 if (command != null && run.smallTimeoutSeconds != 0) { 207 command.scheduleTimeout(run.smallTimeoutSeconds); 208 } 209 lastFinishedOutcome = toQualifiedOutcomeName(outcome.getName()); 210 // TODO: support flexible timeouts for JUnit tests 211 run.driver.recordOutcome(new Outcome(lastFinishedOutcome, outcome.getResult(), 212 outcome.getOutputLines())); 213 } 214 215 /** 216 * Test suites that use main classes in the default package have lame 217 * outcome names like "Clear" rather than "com.foo.Bar.Clear". In that 218 * case, just replace the outcome name with the action name. 219 */ 220 private String toQualifiedOutcomeName(String outcomeName) { 221 if (actionName.endsWith("." + outcomeName) 222 && !outcomeName.contains(".") && !outcomeName.contains("#")) { 223 return actionName; 224 } else { 225 return outcomeName; 226 } 227 } 228 229 @Override public void print(String string) { 230 run.console.streamOutput(string); 231 } 232} 233