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