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 com.android.tradefed.command;
18 
19 import com.android.tradefed.clearcut.ClearcutClient;
20 import com.android.tradefed.clearcut.TerminateClearcutClient;
21 import com.android.tradefed.config.ConfigurationException;
22 import com.android.tradefed.config.GlobalConfiguration;
23 import com.android.tradefed.device.NoDeviceException;
24 import com.android.tradefed.util.FileUtil;
25 import com.android.tradefed.util.SerializationUtil;
26 
27 import com.google.common.annotations.VisibleForTesting;
28 
29 import org.json.JSONException;
30 import org.json.JSONObject;
31 
32 import java.io.File;
33 import java.io.IOException;
34 
35 /**
36  * An alternate TradeFederation entry point that will run command specified in command
37  * line arguments and then quit.
38  * <p/>
39  * Intended for use with a debugger and other non-interactive modes of operation.
40  * <p/>
41  * Expected arguments: [commands options] (config to run)
42  */
43 public class CommandRunner {
44     private ICommandScheduler mScheduler;
45     private ExitCode mErrorCode = ExitCode.NO_ERROR;
46 
47     public static final String EXCEPTION_KEY = "serialized_exception";
48     private static final long CHECK_DEVICE_TIMEOUT = 60000;
49 
CommandRunner()50     public CommandRunner() {}
51 
getErrorCode()52     public ExitCode getErrorCode() {
53         return mErrorCode;
54     }
55 
56     /**
57      * Initialize the required global configuration.
58      */
59     @VisibleForTesting
initGlobalConfig(String[] args)60     void initGlobalConfig(String[] args) throws ConfigurationException {
61         GlobalConfiguration.createGlobalConfiguration(args);
62         GlobalConfiguration.getInstance().setup();
63     }
64 
65     /** Get the {@link ICommandScheduler} instance from the global configuration. */
66     @VisibleForTesting
getCommandScheduler()67     ICommandScheduler getCommandScheduler() {
68         return GlobalConfiguration.getInstance().getCommandScheduler();
69     }
70 
71     /** Prints the exception stack to stderr. */
72     @VisibleForTesting
printStackTrace(Throwable e)73     void printStackTrace(Throwable e) {
74         e.printStackTrace();
75         File serializedException = null;
76         try {
77             serializedException = SerializationUtil.serialize(e);
78             JSONObject json = new JSONObject();
79             json.put(EXCEPTION_KEY, serializedException.getAbsolutePath());
80             System.err.println(json.toString());
81         } catch (IOException | JSONException io) {
82             io.printStackTrace();
83             FileUtil.deleteFile(serializedException);
84         }
85     }
86 
87     /** Returns the timeout after which to check for the command. */
88     @VisibleForTesting
getCheckDeviceTimeout()89     long getCheckDeviceTimeout() {
90         return CHECK_DEVICE_TIMEOUT;
91     }
92 
93     /**
94      * The main method to run the command.
95      *
96      * @param args the config name to run and its options
97      */
run(String[] args)98     public void run(String[] args) {
99         try {
100             initGlobalConfig(args);
101 
102             ClearcutClient client = new ClearcutClient();
103             Runtime.getRuntime().addShutdownHook(new TerminateClearcutClient(client));
104             client.notifyTradefedStartEvent();
105 
106             mScheduler = getCommandScheduler();
107             mScheduler.setClearcutClient(client);
108             mScheduler.start();
109             mScheduler.addCommand(args);
110         } catch (ConfigurationException e) {
111             printStackTrace(e);
112             mErrorCode = ExitCode.CONFIG_EXCEPTION;
113         } finally {
114             mScheduler.shutdownOnEmpty();
115         }
116         try {
117             mScheduler.join(getCheckDeviceTimeout());
118             // FIXME: if possible make the no_device allocated check deterministic.
119             // After 1 min we check if the command was executed.
120             if (mScheduler.getReadyCommandCount() > 0
121                     && mScheduler.getExecutingCommandCount() == 0) {
122                 printStackTrace(new NoDeviceException("No device was allocated for the command."));
123                 mErrorCode = ExitCode.NO_DEVICE_ALLOCATED;
124                 mScheduler.removeAllCommands();
125                 mScheduler.shutdown();
126                 return;
127             }
128             mScheduler.join();
129             // If no error code has been raised yet, we checked the invocation error code.
130             if (ExitCode.NO_ERROR.equals(mErrorCode)) {
131                 mErrorCode = mScheduler.getLastInvocationExitCode();
132             }
133         } catch (InterruptedException e) {
134             e.printStackTrace();
135             mErrorCode = ExitCode.THROWABLE_EXCEPTION;
136         } finally {
137             GlobalConfiguration.getInstance().cleanup();
138         }
139         if (!ExitCode.NO_ERROR.equals(mErrorCode)
140                 && mScheduler.getLastInvocationThrowable() != null) {
141             // Print error to the stderr so that it can be recovered.
142             printStackTrace(mScheduler.getLastInvocationThrowable());
143         }
144     }
145 
main(final String[] mainArgs)146     public static void main(final String[] mainArgs) {
147         CommandRunner console = new CommandRunner();
148         console.run(mainArgs);
149         System.exit(console.getErrorCode().getCodeValue());
150     }
151 
152     /**
153      * Error codes that are possible to exit with.
154      */
155     public static enum ExitCode {
156         NO_ERROR(0),
157         CONFIG_EXCEPTION(1),
158         NO_BUILD(2),
159         DEVICE_UNRESPONSIVE(3),
160         DEVICE_UNAVAILABLE(4),
161         FATAL_HOST_ERROR(5),
162         THROWABLE_EXCEPTION(6),
163         NO_DEVICE_ALLOCATED(7),
164         WRONG_JAVA_VERSION(8);
165 
166         private final int mCodeValue;
167 
ExitCode(int codeValue)168         ExitCode(int codeValue) {
169             mCodeValue = codeValue;
170         }
171 
getCodeValue()172         public int getCodeValue() {
173             return mCodeValue;
174         }
175     }
176 }
177