blob: 6965676f633b76fc819f50c9423145e4672270ed [file] [log] [blame]
/*
* 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)
.debugPort(run.debugPort)
.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);
}
}