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