# Debug

debug的入口在CtsConsole類,所以我們把第一個斷點打在249行:
~~~
Console console = new CtsConsole();
~~~
按F6再按F5進入到Console.startConsole方法中。

按F5進入GlobalConfiguration.createGlobalConfiguration方法中。

該方法內主要是讀取全局配置文件并設置IGlobalConfiguration接口對象sInstance。主要方法為95行讀取文件的getGlobalConfigPath():
~~~
private static String getGlobalConfigPath() throws ConfigurationException {
?? ??? ?String path = System.getenv(GLOBAL_CONFIG_VARIABLE);
?? ??? ?if (path != null) {
?? ??? ??? ?// don't actually check for accessibility here, since the variable
?? ??? ??? ?// might be specifying
?? ??? ??? ?// a java resource rather than a filename. Even so, this can help
?? ??? ??? ?// the user figure out
?? ??? ??? ?// which global config (if any) was picked up by TF.
?? ??? ??? ?System.err
?? ??? ??? ??? ??? ?.format("Attempting to use global config \"%s\" from variable $%s.\n",
?? ??? ??? ??? ??? ??? ??? ?path, GLOBAL_CONFIG_VARIABLE);
?? ??? ??? ?return path;
?? ??? ?}
?? ??? ?File file = new File(GLOBAL_CONFIG_FILENAME);
?? ??? ?if (file.exists()) {
?? ??? ??? ?path = file.getPath();
?? ??? ??? ?System.err.format(
?? ??? ??? ??? ??? ?"Attempting to use auto detected global config \"%s\".\n",
?? ??? ??? ??? ??? ?path);
?? ??? ??? ?System.err.flush();
?? ??? ??? ?return path;
?? ??? ?}
?? ??? ?// FIXME: search in tradefed.sh launch dir (or classpath?)
?? ??? ?return null;
?? ?}
~~~
**首先判斷是否設置了全局配置文件的系統變量,如果沒有設置,那直接在當前文件目錄下找tf_global_config.xml文件**。很顯然,本程序這些都沒有,所以該方法返回的結果應該是null。回到了createGlobalConfiguration(String[] args)方法中:
~~~
if (globalConfigPath != null) {
// Found a global config file; attempt to parse and use it
sInstance = configFactory.createGlobalConfigurationFromArgs(
ArrayUtil.buildArray(new String[] { globalConfigPath },
args), nonGlobalArgs);
System.err.format("Success! Using global config \"%s\"\n",
globalConfigPath);
} else {
// Use default global config
sInstance = new GlobalConfiguration();
nonGlobalArgs = Arrays.asList(args);
}
return nonGlobalArgs;
~~~
因為返回的路徑為null,所以直接跳轉到else語句塊中,new一個新對象,沒有設置任何屬性。最后將命令行參數封裝在list中返回,然后console設置參數,最終啟動線程來執行任務。所以第二個斷點要打在Console的run方法里,然后按F8進入run方法。

run方法中先做一些參數的判斷,如果為空,啟動CommandScheduler線程,里面會去從執行隊列中拿出隊首元素,如果取得的隊列為空就會結束。
如果參數不為空,除了啟動CommandScheduler線程外還會執行其他的操作,如下:
~~~
public void run() {
List<String> arrrgs = mMainArgs;
// Fallback, in case this isn't set already
if (mScheduler == null) {
mScheduler = new CommandScheduler();
}
try {
// Check System.console() since jline doesn't seem to consistently know whether or not
// the console is functional.
if (!isConsoleFunctional()) {
if (arrrgs.isEmpty()) {
printLine("No commands for non-interactive mode; exiting.");
// FIXME: need to run the scheduler here so that the things blocking on it
// FIXME: will be released.
mScheduler.start();
mScheduler.await();
return;
} else {
printLine("Non-interactive mode: Running initial command then exiting.");
mShouldExit = true;
}
}
// Wait for the CommandScheduler to start. It will hold the JVM open (since the Console
// thread is a Daemon thread), and also we require it to have started so that we can
// start processing user input.
mScheduler.start();
mScheduler.await();
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...");
mShouldExit = true;
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));
if (mConsoleReader != null) {
// Add the starting command as the first item in the console history
// FIXME: this will not properly escape commands that were properly escaped
// FIXME: on the commandline. That said, it will still be more convenient
// FIXME: than copying by hand.
final String cmd = ArrayUtil.join(" ", arrrgs);
mConsoleReader.getHistory().addToHistory(cmd);
}
tokens = arrrgs.toArray(new String[0]);
//置空
arrrgs = Collections.emptyList();
}
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();
} finally {
mScheduler.shutdown();
// Make sure that we don't quit with messages still in the buffers
System.err.flush();
System.out.flush();
}
}
~~~
上面這段代碼主要看846行左右的
~~~
executeCmdRunnable(command, groups);
~~~
我們來看這個方法里面的實現:
~~~
/**
* 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();
}
}
~~~
會發現程序會跳轉到
~~~
((ArgRunnable)command).run(groups);
~~~
然后再按F5就跳轉不進去了,這個時候程序進入到了

所以在這個地方打個斷點,重新啟動debug,會進入到這個地方。該方法調用了CommandScheduler.addCommand方法,進入該方法
~~~
/**
* {@inheritDoc}
*/
@Override
public boolean addCommand(String[] args, long totalExecTime) {
try {
//得到cts配置文件的信息
IConfiguration config = getConfigFactory().createConfigurationFromArgs(args);
//打印幫助信息,只打印Importance類型的option信息
if (config.getCommandOptions().isHelpMode()) {
getConfigFactory().printHelpForConfig(args, true, System.out);
System.out.flush();
//打印所有option信息
} else if (config.getCommandOptions().isFullHelpMode()) {
getConfigFactory().printHelpForConfig(args, false, System.out);
} else if (config.getCommandOptions().isDryRunMode()) {
if (config.getCommandOptions().isNoisyDryRunMode()) {
CLog.logAndDisplay(LogLevel.DEBUG, "DRY RUN: %s", Arrays.toString(args));
} else {
CLog.d("Dry run mode; skipping adding command: %s", Arrays.toString(args));
}
} else {
config.validateOptions();
if (config.getCommandOptions().runOnAllDevices()) {
addCommandForAllDevices(totalExecTime, args);
} else {
CommandTracker cmdTracker = createCommandTracker(args);
cmdTracker.incrementExecTime(totalExecTime);
ExecutableCommand cmdInstance = createExecutableCommand(cmdTracker, config, false);
addExecCommandToQueue(cmdInstance, 0);
}
return true;
}
} catch (ConfigurationException e) {
e.printStackTrace();
// FIXME: do this with jline somehow for ANSI support
// note: make sure not to log (aka record) this line, as (args) may contain passwords.
System.out.println(String.format("Error while processing args: %s",
Arrays.toString(args)));
System.out.println(e.getMessage());
System.out.println();
}
return false;
}
~~~
先來看第一行代碼:
~~~
IConfiguration config = getConfigFactory().createConfigurationFromArgs(args);
~~~
該方法會根據參數中的第二個參數來找到config目錄下的xml文件,讀取里面的內容,然后配置CTS框架的9大組件(這個內容放在下一篇文章講)。得到Config對象后,會判斷是全設備運行還是單個設備運行,默認是全設備運行。如果是單設備運行,需要指定設備的sn號,框架根據SN號來找到設備。最后將執行計劃放入到隊列中。

到此任務的添加就完成了。任務隊列不斷的接受新的任務,然后CommandScheduler的run方法里有一個循環,每次都取第一個任務出來執行。
~~~
try {
// Notify other threads that we're running.
mRunLatch.countDown();
IDeviceManager manager = getDeviceManager();
while (!isShutdown()) {
ExecutableCommand cmd = dequeueConfigCommand();
if (cmd != null) {
IDeviceSelection options = cmd.getConfiguration().getDeviceRequirements();
ITestDevice device = manager.allocateDevice(0, options);
if (device != null) {
// Spawn off a thread to perform the invocation
InvocationThread invThread = startInvocation(manager, device, cmd);
addInvocationThread(invThread);
if (cmd.isLoopMode()) {
addNewExecCommandToQueue(cmd.getCommandTracker());
}
} else {
// no device available for command, put back in queue
// increment exec time to ensure fair scheduling among commands when devices
// are scarce
cmd.getCommandTracker().incrementExecTime(1);
addExecCommandToQueue(cmd, NO_DEVICE_DELAY_TIME);
//CLog.logAndDisplay(LogLevel.ERROR,String.format("Can't find device %s.",options.getSerials()));
}
}
}
mCommandTimer.shutdown();
CLog.i("Waiting for invocation threads to complete");
List<InvocationThread> threadListCopy;
synchronized (this) {
threadListCopy = new ArrayList<InvocationThread>(mInvocationThreads.size());
threadListCopy.addAll(mInvocationThreads);
}
for (Thread thread : threadListCopy) {
waitForThread(thread);
}
closeRemoteClient();
if (mRemoteManager != null) {
mRemoteManager.cancel();
}
exit(manager);
cleanUp();
CLog.logAndDisplay(LogLevel.INFO, "All done");
} finally {
// Make sure that we don't quit with messages still in the buffers
System.err.flush();
System.out.flush();
}
~~~
到此任務的添加就算講完了,?[下一篇文章](http://blog.csdn.net/itfootball/article/details/com.android.cts.tradefed.result.IssueReporter@b279f3]%7D)解析一下是如何解析配置文件的。
- 前言
- (1)-windows下cts配置
- (2)-cts調試環境的搭建
- (3)-基礎庫tradefederation配置
- (4)-任務的添加
- (5)-9大組件配置
- (6)-任務的執行
- (7)-任務執行的調度室
- (8)-IBuildProvider
- (9)-IDeviceRecovery
- (10)-TestDeviceOptions
- (11)-ICommandOptions
- (12)-ITargetPreparer
- (13)-任務執行過程
- (14)-任務執行過程
- (15)-任務執行完
- (16)-logcat信息收集系統
- (17)-fastboot狀態監聽器
- (18)-設備恢復
- (19)-設備狀態的分類以及恢復模式的分類
- (20)-cts自身log系統
- (21)-測試結果收集系統
- (22)-自動檢測設備
- (23)-設備分類
- (24)-case的組織