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