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 
17 package vogar.tasks;
18 
19 import java.io.File;
20 import java.io.IOException;
21 import vogar.Action;
22 import vogar.Classpath;
23 import vogar.Outcome;
24 import vogar.Result;
25 import vogar.Run;
26 import vogar.RunnerType;
27 import vogar.commands.Command;
28 import vogar.commands.VmCommandBuilder;
29 import vogar.monitor.HostMonitor;
30 import vogar.target.TestRunner;
31 
32 /**
33  * Executes a single action and then prints the result.
34  */
35 public 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 int timeoutSeconds;
49     private final Action action;
50     private final String actionName;
51     private Command currentCommand;
52     private String lastStartedOutcome;
53     private String lastFinishedOutcome;
54 
RunActionTask(Run run, Action action, boolean useLargeTimeout)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 
61         this.timeoutSeconds = useLargeTimeout
62                 ? run.largeTimeoutSeconds
63                 : run.smallTimeoutSeconds;
64     }
65 
isAction()66     @Override public boolean isAction() {
67         return true;
68     }
69 
execute()70     @Override protected Result execute() throws Exception {
71         run.console.action(actionName);
72 
73         while (true) {
74             /*
75              * If the target process failed midway through a set of
76              * outcomes, that's okay. We pickup right after the first
77              * outcome that wasn't completed.
78              */
79             String skipPast = lastStartedOutcome;
80             lastStartedOutcome = null;
81 
82             currentCommand = createActionCommand(action, skipPast, monitorPort(-1));
83             try {
84                 currentCommand.start();
85 
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      */
createActionCommand(Action action, String skipPast, int monitorPort)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 
155         // Forward specific parameters to Caliper.
156         if (run.runnerType.supportsCaliper()) {
157           // Forward timeout value to Caliper which has its own separate timeout.
158           vmCommandBuilder.args("--time-limit", String.format("%ds", timeoutSeconds));
159 
160           // This configuration runs about 15x faster than not having this configuration.
161           vmCommandBuilder.args(
162                   // Don't run GC before each measurement. That will take forever.
163                   "-Cinstrument.runtime.options.gcBeforeEach=false",
164                   // Warmup super-quick, don't take more than 1sec.
165                   "-Cinstrument.runtime.options.warmup=1s",
166                   // Don't measure things 9 times (default) because microbenchmark already
167                   // measure themselves millions of times.
168                   "-Cinstrument.runtime.options.measurements=1");
169         }
170         return vmCommandBuilder
171                 .temp(workingDirectory)
172                 .vmArgs(run.additionalVmArgs)
173                 .mainClass(TestRunner.class.getName())
174                 .args(run.targetArgs)
175                 .build(run.target);
176     }
177 
178     /**
179      * Returns true if this mode requires a socket connection for reading test
180      * results. Otherwise all communication happens over the output stream of
181      * the forked process.
182      */
useSocketMonitor()183     protected boolean useSocketMonitor() {
184         return false;
185     }
186 
monitorPort(int defaultValue)187     private int monitorPort(int defaultValue) {
188         return run.maxConcurrentActions == 1
189                 ? defaultValue
190                 : run.firstMonitorPort + runnerThreadId.get();
191     }
192 
start(String outcomeName)193     @Override public void start(String outcomeName) {
194         outcomeName = toQualifiedOutcomeName(outcomeName);
195         lastStartedOutcome = outcomeName;
196         if (run.runnerType.supportsCaliper()) {
197             run.console.verbose("running " + outcomeName + " with unlimited timeout");
198             Command command = currentCommand;
199             if (command != null && timeoutSeconds != 0) {
200                 command.scheduleTimeout(timeoutSeconds);
201             }
202             run.driver.recordResults = false;
203         } else {
204             run.driver.recordResults = true;
205         }
206     }
207 
output(String outcomeName, String output)208     @Override public void output(String outcomeName, String output) {
209         outcomeName = toQualifiedOutcomeName(outcomeName);
210         run.console.outcome(outcomeName);
211         run.console.streamOutput(outcomeName, output);
212     }
213 
finish(Outcome outcome)214     @Override public void finish(Outcome outcome) {
215         Command command = currentCommand;
216         if (command != null && timeoutSeconds != 0) {
217             command.scheduleTimeout(timeoutSeconds);
218         }
219         lastFinishedOutcome = toQualifiedOutcomeName(outcome.getName());
220         // TODO: support flexible timeouts for JUnit tests
221         run.driver.recordOutcome(new Outcome(lastFinishedOutcome, outcome.getResult(),
222                 outcome.getOutputLines()));
223     }
224 
225     /**
226      * Test suites that use main classes in the default package have lame
227      * outcome names like "Clear" rather than "com.foo.Bar.Clear". In that
228      * case, just replace the outcome name with the action name.
229      */
toQualifiedOutcomeName(String outcomeName)230     private String toQualifiedOutcomeName(String outcomeName) {
231         if (actionName.endsWith("." + outcomeName)
232                 && !outcomeName.contains(".") && !outcomeName.contains("#")) {
233             return actionName;
234         } else {
235             return outcomeName;
236         }
237     }
238 
print(String string)239     @Override public void print(String string) {
240         run.console.streamOutput(string);
241     }
242 }
243