CTS-Analysis of Integral Framework

Posted by han2754 on Fri, 16 Aug 2019 11:03:45 +0200

Links to the original text: https://blog.csdn.net/u011733869/article/details/78820041

Copyright Statement: This article is the original article of the blogger. It follows CC 4.0 by-sa Copyright Agreement. Please attach the link of origin and this Statement to reproduce it.
Links to this article: https://blog.csdn.net/u011733869/article/details/78820041
Catalog

  • Overview of the overall process
  • Main
  • Console
  • summary

From this article, we will introduce the working principle of the whole framework.

1. Overview of the overall process
Here is the outline flow chart of the whole testing framework, which mainly involves four threads:

  1. 1. main - Start the entrance
  2. 2. Console - Processing commands
  3. 3. Command Scheduler - Command Scheduler
  4. 4. Invocation Thrad - Execute commands

This chart is the outline flow of the overall operation, you can first look at a general understanding, and then look at each detail and then come back to re-prod.

2.main

Line number 1088
As a java program, the whole testing framework can run directly in eclipse, and the entry is in Console under com.android.tradefed.command package:

public static void main(final String[] mainArgs) throws InterruptedException,
        ConfigurationException {
    Console console = new Console();
    startConsole(console, mainArgs);
}



You can see that what you're doing here is very simple. You start a console in the main thread, so what exactly is this Console?

protected Console() {
    this(getReader());
}
// A series of variables are initialized in the constructor
Console(ConsoleReader reader) {
    super("TfConsole");
    mConsoleStartTime = System.currentTimeMillis();
    mConsoleReader = reader;
    if (reader != null) {
        mConsoleReader.addCompletor(
                new ConfigCompletor(getConfigurationFactory().getConfigList()));
    }
    // HelpList initialization
    List<String> genericHelp = new LinkedList<String>();
    // helpString: How to use each command support
    Map<String, String> commandHelp = new LinkedHashMap<String, String>();
    // Add commands that are supported by default, using the Regex Trie described earlier
    addDefaultCommands(mCommandTrie, genericHelp, commandHelp);
    // This is an empty method, mainly to facilitate subclass copying to add their own commands
    setCustomCommands(mCommandTrie, genericHelp, commandHelp);
    // Generating HelpList
    generateHelpListings(mCommandTrie, genericHelp, commandHelp);
}

public static void startConsole(Console console, String[] args) throws InterruptedException,
        ConfigurationException {
    // Create Global Configuration
    List<String> nonGlobalArgs = GlobalConfiguration.createGlobalConfiguration(args);
    console.setArgs(nonGlobalArgs);
    // Create a command scheduler
    console.setCommandScheduler(GlobalConfiguration.getInstance().getCommandScheduler());
    console.setKeyStoreFactory(GlobalConfiguration.getInstance().getKeyStoreFactory());
    //Set console thread to daemon thread
    console.setDaemon(true);
    // Start console thread
    console.start();
    // Wait for the CommandScheduler to get started before we exit the main thread.  See full
    // explanation near the top of #run()
    // Wait for Command Schedler to start, then exit
    console.awaitScheduler();
}



The main function above is to initialize some configurations and then start the console thread. Here's a look at the Global Configuration

public static List<String> createGlobalConfiguration(String[] args)
        throws ConfigurationException {
    synchronized (sInstanceLock) {
        if (sInstance != null) {
            throw new IllegalStateException("GlobalConfiguration is already initialized!");
        }
        List<String> nonGlobalArgs = new ArrayList<String>(args.length);
        // Instances for initializing Configuration Factory
        // Global Configuration and subsequent Configuration Sharing
        IConfigurationFactory configFactory = ConfigurationFactory.getInstance();
        String globalConfigPath = getGlobalConfigPath();
        // The emphasis is on this method, which creates configuration from parameters
        sInstance = configFactory.createGlobalConfigurationFromArgs(
                ArrayUtil.buildArray(new String[] {globalConfigPath}, args), nonGlobalArgs);
        // 
        if (!DEFAULT_EMPTY_CONFIG_NAME.equals(globalConfigPath)) {
            // Only print when using different from default
            System.out.format("Success!  Using global config \"%s\"\n", globalConfigPath);
        }
        // Validate that madatory options have been set
        sInstance.validateOptions();
        return nonGlobalArgs;
    }
}

private static String getGlobalConfigPath() {
    String path = System.getenv(GLOBAL_CONFIG_VARIABLE);
    if (path != null) {
        System.out.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.out.format("Attempting to use autodetected global config \"%s\".\n", path);
        return path;
    }
    return DEFAULT_EMPTY_CONFIG_NAME;
}

private static final String GLOBAL_CONFIG_VARIABLE = "TF_GLOBAL_CONFIG";
private static final String GLOBAL_CONFIG_FILENAME = "tf_global_config.xml";
// Empty embedded configuration available by default
private static final String DEFAULT_EMPTY_CONFIG_NAME = "empty";

// Key methods in configurationFactory, parsing parameters to create Iconfiguration
public IGlobalConfiguration createGlobalConfigurationFromArgs(String[] arrayArgs,
        List<String> remainingArgs) throws ConfigurationException {
    List<String> listArgs = new ArrayList<String>(arrayArgs.length);
    IGlobalConfiguration config = internalCreateGlobalConfigurationFromArgs(arrayArgs,
            listArgs);
    remainingArgs.addAll(config.setOptionsFromCommandLineArgs(listArgs));
    return config;
}

private IGlobalConfiguration internalCreateGlobalConfigurationFromArgs(String[] arrayArgs,
        List<String> optionArgsRef) throws ConfigurationException {
    if (arrayArgs.length == 0) {
        throw new ConfigurationException("Configuration to run was not specified");
    }
    optionArgsRef.addAll(Arrays.asList(arrayArgs));
    // first arg is config name
    final String configName = optionArgsRef.remove(0);
    ConfigurationDef configDef = getConfigurationDef(configName, true, null);
    return configDef.createGlobalConfiguration();
}
// Because the default is empty here, the detailed parsing logic is not introduced too much here.
// Because this logic is followed later when configurations are created for configuration files
// The process of parsing and loading will be described in detail according to the specific xml file.
// For now, you just need to know whether you actually invoked the construction method of GlobalConfiguration.
IGlobalConfiguration createGlobalConfiguration() throws ConfigurationException {
    IGlobalConfiguration config = new GlobalConfiguration(getName(), getDescription());
    for (Map.Entry<String, List<ConfigObjectDef>> objClassEntry : mObjectClassMap.entrySet()) {
        List<Object> objectList = new ArrayList<Object>(objClassEntry.getValue().size());
        for (ConfigObjectDef configDef : objClassEntry.getValue()) {
            Object configObject = createObject(objClassEntry.getKey(), configDef.mClassName);
            objectList.add(configObject);
        }
        config.setConfigurationObjectList(objClassEntry.getKey(), objectList);
    }
    for (OptionDef optionEntry : mOptionList) {
        config.injectOptionValue(optionEntry.name, optionEntry.key, optionEntry.value);
    }
    return config;
}



In fact, when initializing, we look up the default XML configuration file tf_global_config.xml to see if it exists or not, and then use the default configuration if it does not exist:

GlobalConfiguration(String name, String description) {
    mName = name;
    mDescription = description;
    // Configure map, string for globally configured components with key, and value as components
    mConfigMap = new LinkedHashMap<String, List<Object>>();
    // Configure map, key as the supported option
    mOptionMap = new MultiMap<String, String>();
    setHostOptions(new HostOptions());
    setDeviceRequirements(new DeviceSelectionOptions());
    // device management
    setDeviceManager(new DeviceManager());
    // Initialization command scheduler
    setCommandScheduler(new CommandScheduler());
    setKeyStoreFactory(new StubKeyStoreFactory());
    setShardingStrategy(new StrictShardHelper());
}

// Initialization of the most important command scheduler
public CommandScheduler() {
    // A subclass of Thread that sets the thread name
    super("CommandScheduler");  // set the thread name
    // command that has been parsed and ready to be executed
    mReadyCommands = new LinkedList<>();
    // The main function of the command being executed is to output warnings
    mUnscheduledWarning = new HashSet<>();
    // Timing scheduling command
    mSleepingCommands = new HashSet<>();
    // Executing command
    mExecutingCommands = new HashSet<>();
    // map of the command being executed
    mInvocationThreadMap = new HashMap<IInvocationContext, InvocationThread>();
    // use a ScheduledThreadPoolExecutorTimer as a single-threaded timer.
    // timer
    mCommandTimer = new ScheduledThreadPoolExecutor(1);
    // CountDownLatch, main thread waiting
    mRunLatch = new CountDownLatch(1);
}



The operation of GlobalConfiguration seems complex here, but by default, the configuration file does not exist, that is, the default configuration is used, and finally the construction method of GlobalConfiguration is invoked. Whether it's Global Configuration or Configuration, the construction is done through Configuration Factory, encapsulating objects by parsing xml files. When we introduce the creation of specific configurations later, we will introduce the loading process in detail according to the specific xml files.  
The main logic in main is to create Global Configuration and start console threads. One important thing is to set console to daemon threads.

daemon threads are threads that automatically exit when there are no other threads in the virtual machine.

Because the Console thread is designed to read user input, setting it as a daemon thread ensures that when other scheduling and running threads exit, the Console thread also exits. But in order to prevent the main thread from exiting without the start of other threads, the Console thread will exit directly, so the main will wait for the start of Command Scheduler and then exit, using CountDownLatch.

3.Console thread

public void run() {
    List<String> arrrgs = mMainArgs;
    if (mScheduler == null) {
        throw new IllegalStateException("command scheduler hasn't been set");
    }
    try {
        // Judgment Console
        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;
            }
        }
        // Start Command Scheduler first, and when Command Scheduler starts, main exits.
        mScheduler.start();
        mScheduler.await();
        String input = "";
        CaptureList groups = new CaptureList();
        String[] tokens;
        do { // loop
            if (arrrgs.isEmpty()) {
                // Read console input
                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 {
                    // Format the input commands, divide them into spaces, and load them into an array
                    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) {
                    final String cmd = ArrayUtil.join(" ", arrrgs);
                    mConsoleReader.getHistory().addToHistory(cmd);
                }
                tokens = arrrgs.toArray(new String[0]);
                if (arrrgs.get(0).matches(HELP_PATTERN)) {
                    // if started from command line for help, return to shell
                    mShouldExit = true;
                }
                arrrgs = Collections.emptyList();
            }
            // An important step is to extract a Runnable from the command's Regex Trie according to the command
            Runnable command = mCommandTrie.retrieve(groups, tokens);
            if (command != null) {
                // run method to execute this Runnable
                executeCmdRunnable(command, groups);
            } else {
                printLine(String.format(
                        "Unable to handle command '%s'.  Enter 'help' for help.", tokens[0]));
            }
            RunUtil.getDefault().sleep(100);
        } while (!mShouldExit);//When an exit command is entered, the loop exits
    } 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();
    }
}



As you can see at a glance, we have already introduced RegexTrie. When initializing, we load the supported commands into the trie. Here, we take the parameters input from the command line into the trie. If matched, the last one is a Runnable object to execute.  
Later we will take run cts.xml as an example. Although I use cts.xml here, it is only used in CTS testing framework, and here is the basic framework, but mainly because the use of xml files for example, will not involve specific ct-related content, just cts.xml as a common configuration file.  
As explained earlier, for run, this command is mainly added to the Command Scheduler queue.

// 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);
        }
        try {
            // Add commands to Command Schedler's command queue and wait for scheduling
            mScheduler.addCommand(flatArgs);
        } catch (ConfigurationException e) {
            printLine("Failed to run command: " + e.toString());
        }
    }
};



There seems to be only one simple addCommand, but it's important to note that at this point the parameters are still the ones we input from the command line. That is to say, until the command is scheduled and executed, there is still a very important step: parsing the load, which is also a very important part of the framework to complete injection.

4. Summary
This document mainly introduces the start of the basic framework. As a java program, starting from the main entrance, initialize the global configuration, then start Console to read the console output, parse the command from Command RegexTrie and start executing.

The next article focuses on the parsing and loading of configuration files.
--------------------- 
Copyright Statement: This article is the original article of CSDN blogger "glearn." It follows CC 4.0 by-sa copyright agreement. Please attach the link of origin and this statement for reproducing.
Links to the original text: https://blog.csdn.net/u011733869/article/details/78820041

Topics: xml Java Eclipse Android