blob: 11f09a999f817fb4c63cc1b3d92c3ebf4c049591 [file] [log] [blame]
#!/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()