| #!/usr/bin/env python |
| # Copyright 2016 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Command-line interface for probe utilities.""" |
| |
| from __future__ import print_function |
| |
| import argparse |
| import logging |
| import sys |
| |
| import factory_common # pylint: disable=unused-import |
| from cros.factory.probe import function |
| from cros.factory.probe.lib import probe_function |
| from cros.factory.probe import probe_utils |
| from cros.factory.probe import search |
| from cros.factory.utils import json_utils |
| |
| |
| _sub_cmd_list = [] |
| |
| |
| def RegisterCommand(cls): |
| """Registers the SubCommand class. |
| |
| It is the decorator for SubCommand class. The registered class will be added |
| to the argument parser. |
| """ |
| _sub_cmd_list.append(cls) |
| return cls |
| |
| |
| class SubCommand(object): |
| """The sub-command class.""" |
| |
| # The sub-command string. Derived class should override it. |
| CMD_NAME = '' |
| |
| @classmethod |
| def AddArgumentToParser(cls, subparsers): |
| """Adds the argument parser of the sub-command to the subparsers. |
| |
| Args: |
| subparsers: the sub-parsers of the root argument parser. |
| """ |
| # Set the docstring of the class as the description. |
| subparser = subparsers.add_parser( |
| cls.CMD_NAME, |
| description=cls.__doc__, |
| formatter_class=argparse.RawDescriptionHelpFormatter) |
| subparser.set_defaults(_Command=cls.EvalCommand) |
| cls._AddArgument(subparser) |
| |
| @classmethod |
| def _AddArgument(cls, parser): |
| """Adds the argument parser of the sub-command to the parser. |
| |
| Args: |
| parser: a argparse.ArgumentParser object. |
| """ |
| raise NotImplementedError |
| |
| @classmethod |
| def EvalCommand(cls, options): |
| """The method is the main function of the sub-command. |
| |
| This method will be evaluated if the sub-command is chosen. |
| |
| Args: |
| options: the options returned from the argument parser. |
| """ |
| raise NotImplementedError |
| |
| |
| @RegisterCommand |
| class EvalFunctionCmd(SubCommand): |
| """Evaluates a probe function.""" |
| CMD_NAME = 'eval-function' |
| |
| @classmethod |
| def _AddArgument(cls, parser): |
| function.LoadFunctions() |
| func_list = [func_name for func_name in function.GetRegisteredFunctions() |
| if issubclass(function.GetFunctionClass(func_name), |
| probe_function.ProbeFunction)] |
| func_parsers = parser.add_subparsers() |
| for func_name in func_list: |
| func_cls = function.GetFunctionClass(func_name) |
| func_parser = func_parsers.add_parser( |
| func_name, description=func_cls.__doc__, |
| formatter_class=argparse.RawDescriptionHelpFormatter) |
| func_parser.set_defaults(func_cls=func_cls) |
| for arg in func_cls.ARGS: |
| arg.AddToParser(func_parser) |
| |
| @classmethod |
| def EvalCommand(cls, options): |
| required_args = [arg.name for arg in options.func_cls.ARGS] |
| func_args = {key: val for key, val in vars(options).items() |
| if key in required_args} |
| results = options.func_cls(**func_args)() |
| OutputResults(results, options) |
| |
| |
| @RegisterCommand |
| class ProbeCmd(SubCommand): |
| """Probe the result according to the configuration file. |
| |
| The format of the config file: |
| { |
| <Component category> : { |
| <Component name> : { |
| "eval" : <Function expression>, |
| "expect" : <Rule expression> |
| } |
| } |
| } |
| |
| The format of the results: |
| { |
| <Component category> : { |
| <Component name> : [ <Matched result>, ... ] |
| } |
| } |
| """ |
| CMD_NAME = 'probe' |
| |
| @classmethod |
| def _AddArgument(cls, parser): |
| parser.add_argument('--config-file', default=None, |
| help='The path of probe statement.') |
| parser.add_argument('--include-generic', default=False, action='store_true', |
| help='Load the generic probe statement. ' |
| 'If "--config-file" argument is not assigned, then ' |
| 'this argument will be enabled automatically.') |
| parser.add_argument('--include-volatile', default=False, |
| action='store_true', |
| help='Load the volatile probe statement. ' |
| 'If "--config-file" argument is not assigned, then ' |
| 'this argument will be enabled automatically.') |
| parser.add_argument('--comps', default=None, nargs='*', type=str, |
| help='Specify a list of class of components to probe ' |
| 'instead of probing all components listed in the probe ' |
| 'statement.') |
| parser.add_argument('--approx-match', default=False, action='store_true', |
| help='Use ApproxMatch function to match and find ' |
| 'closest hardwares.') |
| parser.add_argument('--max-mismatch', default=1, type=int, |
| help='A number of mismatched rules at most when ' |
| 'enabling --approx-match') |
| |
| @classmethod |
| def EvalCommand(cls, options): |
| if options.config_file is None and not options.include_volatile: |
| logging.info('No config file is assigned. ' |
| 'Force to load the generic probe statement.') |
| options.include_generic = True |
| |
| probe_statement = probe_utils.GenerateProbeStatement( |
| config_file=options.config_file, |
| include_generic=options.include_generic, |
| include_volatile=options.include_volatile) |
| |
| OutputResults(probe_utils.Probe(probe_statement, options.comps, |
| approx_match=options.approx_match, |
| max_mismatch=options.max_mismatch), options) |
| |
| |
| @RegisterCommand |
| class SearchCmd(SubCommand): |
| """Search the components in generic way. |
| |
| We can use this command to find common components, and generate its probe |
| statement. |
| """ |
| CMD_NAME = 'search' |
| |
| @classmethod |
| def _AddArgument(cls, parser): |
| parser.add_argument('comps', metavar='COMP', nargs='*', |
| help='The components to be searched.') |
| |
| @classmethod |
| def EvalCommand(cls, options): |
| comps = set(options.comps) |
| if not comps: |
| comps = search.GetGenericComponentClasses() |
| results = {} |
| for comp_cls in comps: |
| if comp_cls not in search.GetGenericComponentClasses(): |
| logging.error('Component [%s] cannot be searched.', comp_cls) |
| logging.info('Search component [%s].', comp_cls) |
| results.update(search.GenerateProbeStatement(comp_cls)) |
| OutputResults(results, options) |
| |
| |
| def OutputResults(results, options): |
| """Output the results of the sub-command.""" |
| output_str = json_utils.DumpStr(results, pretty=True) |
| if options.output_file == '-': # Output to stdout. |
| print(output_str) |
| else: |
| with open(options.output_file, 'w') as f: |
| f.write(output_str) |
| |
| |
| def ParseOptions(): |
| """Creates the argument parser and returns the parsed options.""" |
| # Create the root argument parser. |
| arg_parser = argparse.ArgumentParser( |
| description=sys.modules[__name__].__doc__) |
| arg_parser.add_argument('-v', '--verbose', default=False, action='store_true', |
| help='Enable verbose output.') |
| arg_parser.add_argument('--output-file', default='-', |
| help='Write the output to a file.') |
| |
| # Add the argument parser of registered sub-commands. |
| subparsers = arg_parser.add_subparsers() |
| for sub_cmd in _sub_cmd_list: |
| sub_cmd.AddArgumentToParser(subparsers) |
| |
| # Parse the argument. |
| return arg_parser.parse_args() |
| |
| |
| def SetRootLogger(verbose): |
| # If logging methods are called before basicConfig is called, a default |
| # handler will be added into the root logger and ignore basicConfig. |
| # Remove it if exists. |
| root = logging.getLogger() |
| if root.handlers: |
| for handler in root.handlers: |
| root.removeHandler(handler) |
| |
| # Send logging to stderr to keep stdout only containing the results. |
| level = logging.DEBUG if verbose else logging.INFO |
| logging.basicConfig(level=level, stream=sys.stderr) |
| |
| |
| def Main(): |
| options = ParseOptions() |
| SetRootLogger(options.verbose) |
| options._Command(options) # pylint: disable=protected-access |
| |
| |
| if __name__ == '__main__': |
| Main() |