| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package vogar.tasks; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import vogar.Action; |
| import vogar.Classpath; |
| import vogar.Outcome; |
| import vogar.Result; |
| import vogar.Run; |
| import vogar.RunnerType; |
| import vogar.commands.Command; |
| import vogar.commands.VmCommandBuilder; |
| import vogar.monitor.HostMonitor; |
| import vogar.target.TestRunner; |
| |
| /** |
| * Executes a single action and then prints the result. |
| */ |
| public class RunActionTask extends Task implements HostMonitor.Handler { |
| /** |
| * Assign each runner thread a unique ID. Necessary so threads don't |
| * conflict when selecting a monitor port. |
| */ |
| private final ThreadLocal<Integer> runnerThreadId = new ThreadLocal<Integer>() { |
| private int next = 0; |
| @Override protected synchronized Integer initialValue() { |
| return next++; |
| } |
| }; |
| |
| protected final Run run; |
| private final int timeoutSeconds; |
| private final Action action; |
| private final String actionName; |
| private Command currentCommand; |
| private String lastStartedOutcome; |
| private String lastFinishedOutcome; |
| |
| public RunActionTask(Run run, Action action, boolean useLargeTimeout) { |
| super("run " + action.getName()); |
| this.run = run; |
| this.action = action; |
| this.actionName = action.getName(); |
| |
| this.timeoutSeconds = useLargeTimeout |
| ? run.largeTimeoutSeconds |
| : run.smallTimeoutSeconds; |
| } |
| |
| @Override public boolean isAction() { |
| return true; |
| } |
| |
| @Override protected Result execute() throws Exception { |
| run.console.action(actionName); |
| |
| while (true) { |
| /* |
| * If the target process failed midway through a set of |
| * outcomes, that's okay. We pickup right after the first |
| * outcome that wasn't completed. |
| */ |
| String skipPast = lastStartedOutcome; |
| lastStartedOutcome = null; |
| |
| currentCommand = createActionCommand(action, skipPast, monitorPort(-1)); |
| try { |
| currentCommand.start(); |
| |
| if (timeoutSeconds != 0) { |
| currentCommand.scheduleTimeout(timeoutSeconds); |
| } |
| |
| HostMonitor hostMonitor = new HostMonitor(run.console, this); |
| boolean completedNormally = useSocketMonitor() |
| ? hostMonitor.attach(monitorPort(run.firstMonitorPort)) |
| : hostMonitor.followStream(currentCommand.getInputStream()); |
| |
| if (completedNormally) { |
| return Result.SUCCESS; |
| } |
| |
| String earlyResultOutcome; |
| boolean giveUp; |
| |
| if (lastStartedOutcome == null || lastStartedOutcome.equals(actionName)) { |
| earlyResultOutcome = actionName; |
| giveUp = true; |
| } else if (!lastStartedOutcome.equals(lastFinishedOutcome)) { |
| earlyResultOutcome = lastStartedOutcome; |
| giveUp = false; |
| } else { |
| continue; |
| } |
| |
| run.driver.addEarlyResult(new Outcome(earlyResultOutcome, Result.ERROR, |
| "Action " + action + " did not complete normally.\n" |
| + "timedOut=" + currentCommand.timedOut() + "\n" |
| + "lastStartedOutcome=" + lastStartedOutcome + "\n" |
| + "lastFinishedOutcome=" + lastFinishedOutcome + "\n" |
| + "command=" + currentCommand)); |
| |
| if (giveUp) { |
| return Result.ERROR; |
| } |
| } catch (IOException e) { |
| // if the monitor breaks, assume the worst and don't retry |
| run.driver.addEarlyResult(new Outcome(actionName, Result.ERROR, e)); |
| return Result.ERROR; |
| } finally { |
| currentCommand.destroy(); |
| currentCommand = null; |
| } |
| } |
| } |
| |
| /** |
| * Create the command that executes the action. |
| * |
| * @param skipPast the last outcome to skip, or null to run all outcomes. |
| * @param monitorPort the port to accept connections on, or -1 for the |
| */ |
| public Command createActionCommand(Action action, String skipPast, int monitorPort) { |
| File workingDirectory = action.getUserDir(); |
| VmCommandBuilder vmCommandBuilder = run.mode.newVmCommandBuilder(action, workingDirectory); |
| Classpath runtimeClasspath = run.mode.getRuntimeClasspath(action); |
| if (run.useBootClasspath) { |
| vmCommandBuilder.bootClasspath(runtimeClasspath); |
| } else { |
| vmCommandBuilder.classpath(runtimeClasspath); |
| } |
| if (monitorPort != -1) { |
| vmCommandBuilder.args("--monitorPort", Integer.toString(monitorPort)); |
| } |
| if (skipPast != null) { |
| vmCommandBuilder.args("--skipPast", skipPast); |
| } |
| |
| // Forward specific parameters to Caliper. |
| if (run.runnerType.supportsCaliper()) { |
| // Forward timeout value to Caliper which has its own separate timeout. |
| vmCommandBuilder.args("--time-limit", String.format("%ds", timeoutSeconds)); |
| |
| // This configuration runs about 15x faster than not having this configuration. |
| vmCommandBuilder.args( |
| // Don't run GC before each measurement. That will take forever. |
| "-Cinstrument.runtime.options.gcBeforeEach=false", |
| // Warmup super-quick, don't take more than 1sec. |
| "-Cinstrument.runtime.options.warmup=1s", |
| // Don't measure things 9 times (default) because microbenchmark already |
| // measure themselves millions of times. |
| "-Cinstrument.runtime.options.measurements=1"); |
| } |
| return vmCommandBuilder |
| .temp(workingDirectory) |
| .vmArgs(run.additionalVmArgs) |
| .mainClass(TestRunner.class.getName()) |
| .args(run.targetArgs) |
| .build(run.target); |
| } |
| |
| /** |
| * Returns true if this mode requires a socket connection for reading test |
| * results. Otherwise all communication happens over the output stream of |
| * the forked process. |
| */ |
| protected boolean useSocketMonitor() { |
| return false; |
| } |
| |
| private int monitorPort(int defaultValue) { |
| return run.maxConcurrentActions == 1 |
| ? defaultValue |
| : run.firstMonitorPort + runnerThreadId.get(); |
| } |
| |
| @Override public void start(String outcomeName) { |
| outcomeName = toQualifiedOutcomeName(outcomeName); |
| lastStartedOutcome = outcomeName; |
| if (run.runnerType.supportsCaliper()) { |
| run.console.verbose("running " + outcomeName + " with unlimited timeout"); |
| Command command = currentCommand; |
| if (command != null && timeoutSeconds != 0) { |
| command.scheduleTimeout(timeoutSeconds); |
| } |
| run.driver.recordResults = false; |
| } else { |
| run.driver.recordResults = true; |
| } |
| } |
| |
| @Override public void output(String outcomeName, String output) { |
| outcomeName = toQualifiedOutcomeName(outcomeName); |
| run.console.outcome(outcomeName); |
| run.console.streamOutput(outcomeName, output); |
| } |
| |
| @Override public void finish(Outcome outcome) { |
| Command command = currentCommand; |
| if (command != null && timeoutSeconds != 0) { |
| command.scheduleTimeout(timeoutSeconds); |
| } |
| lastFinishedOutcome = toQualifiedOutcomeName(outcome.getName()); |
| // TODO: support flexible timeouts for JUnit tests |
| run.driver.recordOutcome(new Outcome(lastFinishedOutcome, outcome.getResult(), |
| outcome.getOutputLines())); |
| } |
| |
| /** |
| * Test suites that use main classes in the default package have lame |
| * outcome names like "Clear" rather than "com.foo.Bar.Clear". In that |
| * case, just replace the outcome name with the action name. |
| */ |
| private String toQualifiedOutcomeName(String outcomeName) { |
| if (actionName.endsWith("." + outcomeName) |
| && !outcomeName.contains(".") && !outcomeName.contains("#")) { |
| return actionName; |
| } else { |
| return outcomeName; |
| } |
| } |
| |
| @Override public void print(String string) { |
| run.console.streamOutput(string); |
| } |
| } |