| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.tradefed.command; |
| |
| import com.android.ddmlib.Log.LogLevel; |
| import com.android.tradefed.config.ConfigurationException; |
| import com.android.tradefed.config.ConfigurationFactory; |
| import com.android.tradefed.config.IConfigurationFactory; |
| import com.android.tradefed.device.DeviceManager; |
| import com.android.tradefed.device.IDeviceManager; |
| import com.android.tradefed.log.LogRegistry; |
| import com.android.tradefed.util.QuotationAwareTokenizer; |
| import com.android.tradefed.util.RegexTrie; |
| import com.android.tradefed.util.RunUtil; |
| |
| import jline.ConsoleReader; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Main TradeFederation console providing user with the interface to interact |
| * <p/> |
| * Current has empty implementation, but future support will include commands such as |
| * <ul> |
| * <li>add a configuration to test |
| * <li>list devices and their state |
| * <li>list invocations in progress |
| * <li>list configs in queue |
| * <li>dump invocation log to file/stdout |
| * <li>shutdown |
| * </ul> |
| */ |
| public class Console extends Thread { |
| |
| private static final String CONSOLE_PROMPT = "tf >"; |
| |
| protected static final String HELP_PATTERN = "\\?|h|help"; |
| protected static final String LIST_PATTERN = "l(?:ist)?"; |
| protected static final String DUMP_PATTERN = "d(?:ump)?"; |
| protected static final String RUN_PATTERN = "r(?:un)?"; |
| protected static final String EXIT_PATTERN = "(?:q|exit)"; |
| protected static final String SET_PATTERN = "s(?:et)?"; |
| protected static final String DEBUG_PATTERN = "debug"; |
| |
| protected final static String LINE_SEPARATOR = System.getProperty("line.separator"); |
| |
| /* FIXME: reimplement these somewhere |
| * @Option(name = "log-level-display", description = |
| * "minimum log level to display on stdout for global log") |
| * private String mLogLevelDisplay = null; |
| * |
| * @Option(name = "log-tag-display", description = |
| * "Log tag filter for global log. Always display logs with this tag on stdout") |
| * private Collection<String> mLogTagsDisplay = new HashSet<String>(); |
| * if (mLogLevelDisplay != null) { |
| * LogRegistry.getLogRegistry().setGlobalLogDisplayLevel(mLogLevelDisplay); |
| * } |
| * LogRegistry.getLogRegistry().setGlobalLogTagDisplay(mLogTagsDisplay); |
| */ |
| |
| protected ICommandScheduler mScheduler; |
| protected ConsoleReader mConsoleReader; |
| private RegexTrie<Runnable> mCommandTrie = new RegexTrie<Runnable>(); |
| private boolean mShouldExit = false; |
| private String[] mMainArgs = new String[] {}; |
| |
| /** A convenience type for List<List<String>> */ |
| @SuppressWarnings("serial") |
| protected static class CaptureList extends LinkedList<List<String>> { |
| CaptureList() { |
| super(); |
| } |
| |
| CaptureList(Collection<? extends List<String>> c) { |
| super(c); |
| } |
| } |
| |
| /** |
| * A {@link Runnable} with a {@code run} method that can take an argument |
| */ |
| protected abstract static class ArgRunnable<T> implements Runnable { |
| @Override |
| public void run() { |
| run(null); |
| } |
| |
| abstract public void run(T args); |
| } |
| |
| /** |
| * This is a sentinel class that will cause TF to shut down. This enables a user to get TF to |
| * shut down via the RegexTrie input handling mechanism. |
| */ |
| private class QuitRunnable implements Runnable { |
| @Override |
| public void run() { |
| printLine("Signalling command scheduler for shutdown."); |
| printLine("TF will exit without warning when remaining invocations complete."); |
| mScheduler.shutdown(); |
| } |
| } |
| |
| /** |
| * Like {@link QuitRunnable}, but attempts to harshly shut down current invocations by |
| * killing the adb connection |
| */ |
| private class ForceQuitRunnable extends QuitRunnable { |
| @Override |
| public void run() { |
| super.run(); |
| mScheduler.shutdownHard(); |
| } |
| } |
| |
| /** |
| * Retrieve the {@link RegexTrie} that defines the console behavior. Exposed for unit testing. |
| */ |
| RegexTrie<Runnable> getCommandTrie() { |
| return mCommandTrie; |
| } |
| |
| protected Console() { |
| this(new CommandScheduler()); |
| } |
| |
| /** |
| * Create a {@link Console} with given scheduler. Also, set up console command handling |
| * <p/> |
| * Exposed for unit testing |
| */ |
| Console(ICommandScheduler scheduler) { |
| super(); |
| mScheduler = scheduler; |
| try { |
| mConsoleReader = new ConsoleReader(); |
| } catch (IOException e) { |
| System.err.println("Unable to initialize ConsoleReader: " + e.getMessage()); |
| mConsoleReader = null; |
| } |
| |
| List<String> genericHelp = new LinkedList<String>(); |
| Map<String, String> commandHelp = new LinkedHashMap<String, String>(); |
| addDefaultCommands(mCommandTrie, genericHelp, commandHelp); |
| setCustomCommands(mCommandTrie, genericHelp, commandHelp); |
| generateHelpListings(mCommandTrie, genericHelp, commandHelp); |
| } |
| |
| /** |
| * A customization point that subclasses can use to alter which commands are available in the |
| * console. |
| * <p /> |
| * Implementations should modify the {@code genericHelp} and {@code commandHelp} variables to |
| * document what functionality they may have added, modified, or removed. |
| * |
| * @param trie The {@link RegexTrie} to add the commands to |
| * @param genericHelp A {@link List} of lines to print when the user runs the "help" command |
| * with no arguments. |
| * @param commandHelp A {@link Map} containing documentation for any new commands that may have |
| * been added. The key is a regular expression to use as a key for {@link RegexTrie}. |
| * The value should be a String containing the help text to print for that command. |
| */ |
| protected void setCustomCommands(RegexTrie<Runnable> trie, List<String> genericHelp, |
| Map<String, String> commandHelp) { |
| // Meant to be overridden by subclasses |
| } |
| |
| /** |
| * Generate help listings based on the contents of {@code genericHelp} and {@code commandHelp}. |
| * |
| * @param trie The {@link RegexTrie} to add the commands to |
| * @param genericHelp A {@link List} of lines to print when the user runs the "help" command |
| * with no arguments. |
| * @param commandHelp A {@link Map} containing documentation for any new commands that may have |
| * been added. The key is a regular expression to use as a key for {@link RegexTrie}. |
| * The value should be a String containing the help text to print for that command. |
| */ |
| void generateHelpListings(RegexTrie<Runnable> trie, List<String> genericHelp, |
| Map<String, String> commandHelp) { |
| final String genHelpString = getGenericHelpString(genericHelp); |
| final String helpPattern = "\\?|h|help"; |
| |
| final ArgRunnable<CaptureList> genericHelpRunnable = new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| printLine(genHelpString); |
| } |
| }; |
| trie.put(genericHelpRunnable, helpPattern); |
| |
| StringBuilder allHelpBuilder = new StringBuilder(); |
| |
| // Add help entries for everything listed in the commandHelp map |
| for (Map.Entry<String, String> helpPair : commandHelp.entrySet()) { |
| final String key = helpPair.getKey(); |
| final String helpText = helpPair.getValue(); |
| |
| trie.put(new Runnable() { |
| @Override |
| public void run() { |
| printLine(helpText); |
| } |
| }, helpPattern, key); |
| |
| allHelpBuilder.append(helpText); |
| allHelpBuilder.append(LINE_SEPARATOR); |
| } |
| |
| final String allHelpText = allHelpBuilder.toString(); |
| trie.put(new Runnable() { |
| @Override |
| public void run() { |
| printLine(allHelpText); |
| } |
| }, helpPattern, "all"); |
| |
| // Add a generic "not found" help message for everything else |
| trie.put(new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // Command will be the only capture in the second argument |
| // (first argument is helpPattern) |
| printLine(String.format( |
| "No help for '%s'; command is unknown or undocumented", |
| args.get(1).get(0))); |
| genericHelpRunnable.run(args); |
| } |
| }, helpPattern, null); |
| |
| // Add a fallback input handler |
| trie.put(new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| if (args.isEmpty()) { |
| // User hit <Enter> with a blank line |
| return; |
| } |
| |
| // Command will be the only capture in the first argument |
| printLine(String.format("Unknown command: '%s'", args.get(0).get(0))); |
| genericHelpRunnable.run(args); |
| } |
| }, (Pattern)null); |
| } |
| |
| /** |
| * Return the generic help string to display |
| * @param genericHelp |
| * @return |
| */ |
| protected String getGenericHelpString(List<String> genericHelp) { |
| return join(genericHelp); |
| } |
| |
| /** |
| * A utility function to return the arguments that were passed to an {@link ArgRunnable}. In |
| * particular, it expects all first-level elements of {@code cl} after {@code argIdx} to be |
| * singleton {@link List}s. It will then coalesce the first element of each of those singleton |
| * {@link List}s as a single {@link List}. |
| * |
| * @param argIdx The zero-based index of the first argument. |
| * @param cl The {@link CaptureList} of arguments that was passed to the {@link ArgRunnable} |
| * @return A flattened {@link List} of arguments that were passed to the {@link ArgRunnable} |
| * @throws IllegalArgumentException if the data isn't formatted as expected |
| * @throws IndexOutOfBoundsException if {@code argIdx} isn't consistent with {@code cl} |
| */ |
| static List<String> getFlatArgs(int argIdx, CaptureList cl) { |
| if (argIdx < 0 || argIdx >= cl.size()) { |
| throw new IndexOutOfBoundsException(String.format("argIdx is %d, cl size is %d", |
| argIdx, cl.size())); |
| } |
| |
| List<String> flat = new ArrayList<String>(cl.size() - argIdx); |
| ListIterator<List<String>> iter = cl.listIterator(argIdx); |
| while (iter.hasNext()) { |
| List<String> single = iter.next(); |
| int len = single.size(); |
| if (len != 1) { |
| throw new IllegalArgumentException(String.format( |
| "Expected a singleton List, but got a List with %d elements: %s", |
| len, single.toString())); |
| } |
| flat.add(single.get(0)); |
| } |
| |
| return flat; |
| } |
| |
| /** |
| * Add commands to create the default Console experience |
| * <p /> |
| * Adds relevant documentation to {@code genericHelp} and {@code commandHelp}. |
| * |
| * @param trie The {@link RegexTrie} to add the commands to |
| * @param genericHelp A {@link List} of lines to print when the user runs the "help" command |
| * with no arguments. |
| * @param commandHelp A {@link Map} containing documentation for any new commands that may have |
| * been added. The key is a regular expression to use as a key for {@link RegexTrie}. |
| * The value should be a String containing the help text to print for that command. |
| */ |
| void addDefaultCommands(RegexTrie<Runnable> trie, List<String> genericHelp, |
| Map<String, String> commandHelp) { |
| |
| |
| // Help commands |
| genericHelp.add("Enter 'q' or 'exit' to exit"); |
| genericHelp.add("Enter 'kill' to attempt to forcibly exit, by shutting down adb"); |
| genericHelp.add(""); |
| genericHelp.add("Enter 'help all' to see all embedded documentation at once."); |
| genericHelp.add(""); |
| genericHelp.add("Enter 'help list' for help with 'list' commands"); |
| genericHelp.add("Enter 'help run' for help with 'run' commands"); |
| genericHelp.add("Enter 'help dump' for help with 'dump' commands"); |
| genericHelp.add("Enter 'help set' for help with 'set' commands"); |
| genericHelp.add("Enter 'help debug' for help with 'debug' commands"); |
| |
| commandHelp.put(LIST_PATTERN, String.format( |
| "%s help:" + LINE_SEPARATOR + |
| "\ti[nvocations] List all invocation threads" + LINE_SEPARATOR + |
| "\td[evices] List all detected or known devices" + LINE_SEPARATOR + |
| "\tc[ommands] List all commands currently waiting to be executed" + |
| LINE_SEPARATOR + |
| "\tconfigs List all known configurations" + |
| LINE_SEPARATOR, LIST_PATTERN)); |
| |
| commandHelp.put(DUMP_PATTERN, String.format( |
| "%s help:" + LINE_SEPARATOR + |
| "\ts[tack] Dump the stack traces of all threads" + LINE_SEPARATOR + |
| "\tl[ogs] Dump the logs of all invocations to files" + LINE_SEPARATOR + |
| "\tc[onfig] <config> Dump the content of the specified config" + LINE_SEPARATOR, |
| DUMP_PATTERN)); |
| |
| commandHelp.put(RUN_PATTERN, String.format( |
| "%s help:" + LINE_SEPARATOR + |
| "\tcommand <config> [options] Run the specified command" + LINE_SEPARATOR + |
| "\t<config> [options] Shortcut for the above: run specified command" + |
| LINE_SEPARATOR + |
| "\tcmdfile <cmdfile.txt> Run the specified commandfile" + LINE_SEPARATOR + |
| "\tsingleCommand <config> [options] Run the specified command, and run 'exit' " + |
| "immediately afterward" + LINE_SEPARATOR, |
| RUN_PATTERN)); |
| |
| commandHelp.put(SET_PATTERN, String.format( |
| "%s help:" + LINE_SEPARATOR + |
| "\tlog-level-display <level> Sets the global display log level to <level>" + |
| LINE_SEPARATOR, |
| SET_PATTERN)); |
| |
| commandHelp.put(DEBUG_PATTERN, String.format( |
| "%s help:" + LINE_SEPARATOR + |
| "\tgc Attempt to force a GC" + LINE_SEPARATOR, |
| DEBUG_PATTERN)); |
| |
| // Handle quit commands |
| trie.put(new QuitRunnable(), EXIT_PATTERN); |
| trie.put(new ForceQuitRunnable(), "kill"); |
| |
| // List commands |
| trie.put(new Runnable() { |
| @Override |
| public void run() { |
| Collection<String> invs = mScheduler.listInvocations(); |
| int counter = 1; |
| |
| for (String inv : invs) { |
| printLine(String.format("Invocation %d: %s", counter++, inv)); |
| } |
| } |
| }, LIST_PATTERN, "i(?:nvocations)?"); |
| trie.put(new Runnable() { |
| @Override |
| public void run() { |
| IDeviceManager manager = DeviceManager.getInstance(); |
| manager.displayDevicesInfo(new PrintWriter(System.out, true)); |
| } |
| }, LIST_PATTERN, "d(?:evices)?"); |
| trie.put(new Runnable() { |
| @Override |
| public void run() { |
| Collection<String> commands = mScheduler.listCommands(); |
| int counter = 1; |
| |
| for (String cmd : commands) { |
| printLine(String.format("Command %d: %s", counter++, cmd)); |
| } |
| } |
| }, LIST_PATTERN, "c(?:ommands)?"); |
| trie.put(new Runnable() { |
| @Override |
| public void run() { |
| getConfigurationFactory().printHelp(System.out); |
| } |
| }, LIST_PATTERN, "configs"); |
| |
| |
| // Dump commands |
| trie.put(new Runnable() { |
| @Override |
| public void run() { |
| dumpStacks(); |
| } |
| }, DUMP_PATTERN, "s(?:tacks?)?"); |
| trie.put(new Runnable() { |
| @Override |
| public void run() { |
| dumpLogs(); |
| } |
| }, DUMP_PATTERN, "l(?:ogs?)?"); |
| ArgRunnable<CaptureList> dumpConfigRun = new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // Skip 2 tokens to get past dumpPattern and "config" |
| String configArg = args.get(2).get(0); |
| getConfigurationFactory().dumpConfig(configArg, System.out); |
| } |
| }; |
| trie.put(dumpConfigRun, DUMP_PATTERN, "c(?:onfig?)?", "(.*)"); |
| |
| |
| // Run commands |
| ArgRunnable<CaptureList> runRunCommand = new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // The second argument "command" may also be missing, if the |
| // caller used the shortcut. |
| int startIdx = 1; |
| if (args.get(1).isEmpty()) { |
| // Empty array (that is, not even containing an empty string) means that |
| // we matched and skipped /(?:singleC|c)ommand/ |
| startIdx = 2; |
| } |
| |
| String[] flatArgs = new String[args.size() - startIdx]; |
| for (int i = startIdx; i < args.size(); i++) { |
| flatArgs[i - startIdx] = args.get(i).get(0); |
| } |
| mScheduler.addCommand(flatArgs); |
| } |
| }; |
| trie.put(runRunCommand, RUN_PATTERN, "c(?:ommand)?", null); |
| trie.put(runRunCommand, RUN_PATTERN, null); |
| |
| ArgRunnable<CaptureList> runAndExitCommand = new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // Skip 2 tokens to get past runPattern and "singleCommand" |
| String[] flatArgs = new String[args.size() - 2]; |
| for (int i = 2; i < args.size(); i++) { |
| flatArgs[i - 2] = args.get(i).get(0); |
| } |
| NotifyingCommandListener cmdListener = new NotifyingCommandListener(); |
| cmdListener.setExpectedCalls(1); |
| if (mScheduler.addCommand(flatArgs, cmdListener)) { |
| try { |
| cmdListener.waitForExpectedCalls(); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| } |
| mScheduler.shutdown(); |
| // Intentionally kill the console before CommandScheduler finishes |
| mShouldExit = true; |
| } |
| }; |
| trie.put(runAndExitCommand, RUN_PATTERN, "s(?:ingleCommand)?", null); |
| |
| // Missing required argument: show help |
| // FIXME: fix this functionality |
| // trie.put(runHelpRun, runPattern, "(?:singleC|c)ommand"); |
| |
| ArgRunnable<CaptureList> runRunCmdfile = new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // Skip 2 tokens to get past runPattern and "cmdfile". We're guaranteed to have at |
| // least 3 tokens if we got #run. |
| int startIdx = 2; |
| List<String> flatArgs = getFlatArgs(startIdx, args); |
| String file = flatArgs.get(0); |
| List<String> extraArgs = flatArgs.subList(1, flatArgs.size()); |
| printLine(String.format("Attempting to run cmdfile %s with args %s", file, |
| extraArgs.toString())); |
| try { |
| createCommandFileParser().parseFile(new File(file), mScheduler); |
| } catch (IOException e) { |
| printLine(String.format("Failed to run %s: %s", file, e)); |
| } catch (ConfigurationException e) { |
| printLine(String.format("Failed to run %s: %s", file, e)); |
| } |
| } |
| }; |
| trie.put(runRunCmdfile, RUN_PATTERN, "cmdfile", "(.*)"); |
| trie.put(runRunCmdfile, RUN_PATTERN, "cmdfile", "(.*)", null); |
| // Missing required argument: show help |
| // FIXME: fix this functionality |
| //trie.put(runHelpRun, runPattern, "cmdfile"); |
| |
| // Set commands |
| ArgRunnable<CaptureList> runSetLog = new ArgRunnable<CaptureList>() { |
| @Override |
| public void run(CaptureList args) { |
| // Skip 2 tokens to get past "set" and "log-level-display" |
| String logLevel = args.get(2).get(0); |
| String currentLogLevel = LogRegistry.getLogRegistry().getGlobalLogDisplayLevel(); |
| if (LogLevel.getByString(logLevel) != null) { |
| LogRegistry.getLogRegistry().setGlobalLogDisplayLevel(logLevel); |
| // Make sure that the level was set. |
| currentLogLevel = LogRegistry.getLogRegistry().getGlobalLogDisplayLevel(); |
| if (currentLogLevel != null) { |
| printLine(String.format("Current logging set to '%s'.", currentLogLevel)); |
| } |
| } else { |
| if (currentLogLevel == null) { |
| printLine(String.format("Invalid log level '%s'.", logLevel)); |
| } else{ |
| printLine(String.format( |
| "Invalid log level '%s'; log level remains at '%s'.", |
| logLevel, currentLogLevel)); |
| } |
| } |
| } |
| }; |
| trie.put(runSetLog, SET_PATTERN, "log-level-display", "(.*)"); |
| |
| // Debug commands |
| trie.put(new Runnable() { |
| @Override |
| public void run() { |
| System.gc(); |
| } |
| }, DEBUG_PATTERN, "gc"); |
| } |
| |
| /** |
| * Convenience method to join string pieces into a single string, with newlines after each piece |
| * FIXME: add a join implementation to Util |
| */ |
| private static String join(List<String> pieces) { |
| StringBuilder sb = new StringBuilder(); |
| for (String piece : pieces) { |
| sb.append(piece); |
| sb.append(LINE_SEPARATOR); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Sets the ConsoleReader instance to use |
| * <p/> |
| * Exposed for unit testing |
| */ |
| void setConsoleReader(ConsoleReader reader) { |
| mConsoleReader = reader; |
| } |
| |
| /** |
| * Get input from the console |
| * |
| * @return A {@link String} containing the input to parse and run. Will return {@code null} if |
| * console is not available or user entered EOF ({@code ^D}). |
| */ |
| private String getConsoleInput() throws IOException { |
| if (mConsoleReader == null) { |
| // non-interactive mode |
| return null; |
| } else { |
| return mConsoleReader.readLine(getConsolePrompt()); |
| } |
| } |
| |
| /** |
| * @return the text {@link String} to display for the console prompt |
| */ |
| protected String getConsolePrompt() { |
| return CONSOLE_PROMPT; |
| } |
| |
| /** |
| * Display a line of text on console |
| * @param output |
| */ |
| protected void printLine(String output) { |
| if (mConsoleReader != null) { |
| try { |
| mConsoleReader.printString(output); |
| mConsoleReader.printNewline(); |
| } catch (IOException e) { |
| // not guaranteed to work, but worth a try |
| System.err.println("Console failed to print a message to stdout: " |
| + e.getMessage()); |
| } |
| } else { |
| System.out.println(output); |
| } |
| } |
| |
| /** |
| * Execute a command. |
| * <p /> |
| * Exposed for unit testing |
| */ |
| @SuppressWarnings("unchecked") |
| void executeCmdRunnable(Runnable command, CaptureList groups) { |
| if (command instanceof ArgRunnable) { |
| // FIXME: verify that command implements ArgRunnable<CaptureList> instead |
| // FIXME: of just ArgRunnable |
| ((ArgRunnable<CaptureList>)command).run(groups); |
| } else { |
| command.run(); |
| } |
| } |
| |
| /** |
| * The main method to launch the console. Will keep running until shutdown command is issued. |
| * |
| * @param args |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public void run() { |
| List<String> arrrgs = Arrays.asList(mMainArgs); |
| |
| try { |
| // Check System.console() since jline doesn't seem to consistently know whether or not |
| // the console is functional. |
| if (System.console() == null) { |
| mConsoleReader = null; |
| if (arrrgs.isEmpty()) { |
| printLine("No commands for non-interactive mode; exiting."); |
| return; |
| } else { |
| printLine("Running indefinitely in non-interactive mode."); |
| mShouldExit = true; |
| } |
| } |
| |
| mScheduler.start(); |
| |
| // Notify the main thread that the scheduler is started, and will hold the JVM open. |
| // This is necessary since the Console thread is a Daemon thread. If not for this, the |
| // main thread would exit before we got a chance to #start() mScheduler, at which point |
| // the JVM would shut down immediately. |
| synchronized(this) { |
| notify(); |
| } |
| |
| String input = ""; |
| CaptureList groups = new CaptureList(); |
| String[] tokens; |
| |
| // Note: since Console is a daemon thread, the JVM may exit without us actually leaving |
| // this read loop. This is by design. |
| do { |
| if (arrrgs.isEmpty()) { |
| input = getConsoleInput(); |
| |
| if (input == null) { |
| // Usually the result of getting EOF on the console |
| printLine(""); |
| printLine("Received EOF; quitting..."); |
| mScheduler.shutdown(); |
| break; |
| } |
| |
| tokens = null; |
| try { |
| tokens = QuotationAwareTokenizer.tokenizeLine(input); |
| } catch (IllegalArgumentException e) { |
| printLine(String.format("Invalid input: %s.", input)); |
| continue; |
| } |
| |
| if (tokens == null || tokens.length == 0) { |
| continue; |
| } |
| } else { |
| printLine(String.format("Using commandline arguments as starting command: %s", |
| arrrgs)); |
| tokens = arrrgs.toArray(new String[0]); |
| arrrgs = Collections.emptyList(); |
| } |
| |
| // TODO: think about having the modules themselves advertise their management |
| // TODO: interfaces |
| Runnable command = mCommandTrie.retrieve(groups, tokens); |
| if (command != null) { |
| executeCmdRunnable(command, groups); |
| } else { |
| printLine(String.format( |
| "Unable to handle command '%s'. Enter 'help' for help.", tokens[0])); |
| } |
| |
| RunUtil.getDefault().sleep(100); |
| } while (!mShouldExit); |
| } catch (Exception e) { |
| printLine("Console received an unexpected exception (shown below); shutting down TF."); |
| e.printStackTrace(); |
| mScheduler.shutdown(); |
| } |
| } |
| |
| /** |
| * Factory method for creating a {@link CommandFileParser}. |
| * <p/> |
| * Exposed for unit testing. |
| */ |
| CommandFileParser createCommandFileParser() { |
| return new CommandFileParser(); |
| } |
| |
| /** |
| * Method for getting a {@link IConfigurationFactory}. |
| * <p/> |
| * Exposed for unit testing. |
| */ |
| IConfigurationFactory getConfigurationFactory() { |
| return ConfigurationFactory.getInstance(); |
| } |
| |
| private void dumpStacks() { |
| Map<Thread, StackTraceElement[]> threadMap = Thread.getAllStackTraces(); |
| for (Map.Entry<Thread, StackTraceElement[]> threadEntry : threadMap.entrySet()) { |
| dumpThreadStack(threadEntry.getKey(), threadEntry.getValue()); |
| } |
| } |
| |
| private void dumpThreadStack(Thread thread, StackTraceElement[] trace) { |
| printLine(String.format("%s", thread)); |
| for (int i=0; i < trace.length; i++) { |
| printLine(String.format("\t%s", trace[i])); |
| } |
| printLine(""); |
| } |
| |
| private void dumpLogs() { |
| LogRegistry.getLogRegistry().dumpLogs(); |
| } |
| |
| public void setArgs(String[] mainArgs) { |
| mMainArgs = mainArgs; |
| } |
| |
| public static void main(final String[] mainArgs) throws InterruptedException { |
| Console console = new Console(); |
| startConsole(console, mainArgs); |
| } |
| |
| /** |
| * Starts the given tradefed console with given args |
| * |
| * @param console the {@link Console} to start |
| * @param args the command line arguments |
| */ |
| public static void startConsole(Console console, String[] args) throws InterruptedException { |
| console.setArgs(args); |
| console.setDaemon(true); |
| console.start(); |
| synchronized(console) { |
| // Wait for the console to get started before we exit the main thread. See full |
| // explanation near the top of #run() |
| console.wait(); |
| } |
| } |
| } |