tests,python: Upgrading testlib to function with Python2
Change-Id: I9926b1507e9069ae8564c31bdd377b2b916462a2 Issue-on: https://gem5.atlassian.net/browse/GEM5-395 Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/29088 Reviewed-by: Bobby R. Bruce <bbruce@ucdavis.edu> Maintainer: Bobby R. Bruce <bbruce@ucdavis.edu> Tested-by: kokoro <noreply+kokoro@google.com>
This commit is contained in:
759
ext/testlib/configuration.py
Normal file
759
ext/testlib/configuration.py
Normal file
@@ -0,0 +1,759 @@
|
||||
# Copyright (c) 2020 ARM Limited
|
||||
# All rights reserved
|
||||
#
|
||||
# The license below extends only to copyright in the software and shall
|
||||
# not be construed as granting a license to any other intellectual
|
||||
# property including but not limited to intellectual property relating
|
||||
# to a hardware implementation of the functionality of the software
|
||||
# licensed hereunder. You may use the software subject to the license
|
||||
# terms below provided that you ensure that this notice is replicated
|
||||
# unmodified and in its entirety in all distributions of the software,
|
||||
# modified or unmodified, in source code or in binary form.
|
||||
#
|
||||
# Copyright (c) 2017 Mark D. Hill and David A. Wood
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met: redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer;
|
||||
# redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution;
|
||||
# neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# Authors: Sean Wilson
|
||||
|
||||
'''
|
||||
Global configuration module which exposes two types of configuration
|
||||
variables:
|
||||
|
||||
1. config
|
||||
2. constants (Also attached to the config variable as an attribute)
|
||||
|
||||
The main motivation for this module is to have a centralized location for
|
||||
defaults and configuration by command line and files for the test framework.
|
||||
|
||||
A secondary goal is to reduce programming errors by providing common constant
|
||||
strings and values as python attributes to simplify detection of typos.
|
||||
A simple typo in a string can take a lot of debugging to uncover the issue,
|
||||
attribute errors are easier to notice and most autocompletion systems detect
|
||||
them.
|
||||
|
||||
The config variable is initialzed by calling :func:`initialize_config`.
|
||||
Before this point only ``constants`` will be availaible. This is to ensure
|
||||
that library function writers never accidentally get stale config attributes.
|
||||
|
||||
Program arguments/flag arguments are available from the config as attributes.
|
||||
If an attribute was not set by the command line or the optional config file,
|
||||
then it will fallback to the `_defaults` value, if still the value is not
|
||||
found an AttributeError will be raised.
|
||||
|
||||
:func define_defaults:
|
||||
Provided by the config if the attribute is not found in the config or
|
||||
commandline. For instance, if we are using the list command fixtures might
|
||||
not be able to count on the build_dir being provided since we aren't going
|
||||
to build anything.
|
||||
|
||||
:var constants:
|
||||
Values not directly exposed by the config, but are attached to the object
|
||||
for centralized access. I.E. you can reach them with
|
||||
:code:`config.constants.attribute`. These should be used for setting
|
||||
common string names used across the test framework.
|
||||
:code:`_defaults.build_dir = None` Once this module has been imported
|
||||
constants should not be modified and their base attributes are frozen.
|
||||
'''
|
||||
import abc
|
||||
import argparse
|
||||
import copy
|
||||
import os
|
||||
import re
|
||||
|
||||
from six import add_metaclass
|
||||
from six.moves import configparser as ConfigParser
|
||||
from pickle import HIGHEST_PROTOCOL as highest_pickle_protocol
|
||||
|
||||
from testlib.helper import absdirpath, AttrDict, FrozenAttrDict
|
||||
|
||||
class UninitialzedAttributeException(Exception):
|
||||
'''
|
||||
Signals that an attribute in the config file was not initialized.
|
||||
'''
|
||||
pass
|
||||
|
||||
class UninitializedConfigException(Exception):
|
||||
'''
|
||||
Signals that the config was not initialized before trying to access an
|
||||
attribute.
|
||||
'''
|
||||
pass
|
||||
|
||||
class TagRegex(object):
|
||||
def __init__(self, include, regex):
|
||||
self.include = include
|
||||
self.regex = re.compile(regex)
|
||||
|
||||
def __str__(self):
|
||||
type_ = 'Include' if self.include else 'Remove'
|
||||
return '%10s: %s' % (type_, self.regex.pattern)
|
||||
|
||||
class _Config(object):
|
||||
_initialized = False
|
||||
|
||||
__shared_dict = {}
|
||||
|
||||
constants = AttrDict()
|
||||
_defaults = AttrDict()
|
||||
_config = {}
|
||||
|
||||
_cli_args = {}
|
||||
_post_processors = {}
|
||||
|
||||
def __init__(self):
|
||||
# This object will act as if it were a singleton.
|
||||
self.__dict__ = self.__shared_dict
|
||||
|
||||
def _init(self, parser):
|
||||
self._parse_commandline_args(parser)
|
||||
self._run_post_processors()
|
||||
self._initialized = True
|
||||
|
||||
def _init_with_dicts(self, config, defaults):
|
||||
self._config = config
|
||||
self._defaults = defaults
|
||||
self._initialized = True
|
||||
|
||||
def _add_post_processor(self, attr, post_processor):
|
||||
'''
|
||||
:param attr: Attribute to pass to and recieve from the
|
||||
:func:`post_processor`.
|
||||
|
||||
:param post_processor: A callback functions called in a chain to
|
||||
perform additional setup for a config argument. Should return a
|
||||
tuple containing the new value for the config attr.
|
||||
'''
|
||||
if attr not in self._post_processors:
|
||||
self._post_processors[attr] = []
|
||||
self._post_processors[attr].append(post_processor)
|
||||
|
||||
def _set(self, name, value):
|
||||
self._config[name] = value
|
||||
|
||||
def _parse_commandline_args(self, parser):
|
||||
args = parser.parse_args()
|
||||
|
||||
self._config_file_args = {}
|
||||
|
||||
for attr in dir(args):
|
||||
# Ignore non-argument attributes.
|
||||
if not attr.startswith('_'):
|
||||
self._config_file_args[attr] = getattr(args, attr)
|
||||
self._config.update(self._config_file_args)
|
||||
|
||||
def _run_post_processors(self):
|
||||
for attr, callbacks in self._post_processors.items():
|
||||
newval = self._lookup_val(attr)
|
||||
for callback in callbacks:
|
||||
newval = callback(newval)
|
||||
if newval is not None:
|
||||
newval = newval[0]
|
||||
self._set(attr, newval)
|
||||
|
||||
|
||||
def _lookup_val(self, attr):
|
||||
'''
|
||||
Get the attribute from the config or fallback to defaults.
|
||||
|
||||
:returns: If the value is not stored return None. Otherwise a tuple
|
||||
containing the value.
|
||||
'''
|
||||
if attr in self._config:
|
||||
return (self._config[attr],)
|
||||
elif hasattr(self._defaults, attr):
|
||||
return (getattr(self._defaults, attr),)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr in dir(super(_Config, self)):
|
||||
return getattr(super(_Config, self), attr)
|
||||
elif not self._initialized:
|
||||
raise UninitializedConfigException(
|
||||
'Cannot directly access elements from the config before it is'
|
||||
' initialized')
|
||||
else:
|
||||
val = self._lookup_val(attr)
|
||||
if val is not None:
|
||||
return val[0]
|
||||
else:
|
||||
raise UninitialzedAttributeException(
|
||||
'%s was not initialzed in the config.' % attr)
|
||||
|
||||
def get_tags(self):
|
||||
d = {typ: set(self.__getattr__(typ))
|
||||
for typ in self.constants.supported_tags}
|
||||
if any(map(lambda vals: bool(vals), d.values())):
|
||||
return d
|
||||
else:
|
||||
return {}
|
||||
|
||||
def define_defaults(defaults):
|
||||
'''
|
||||
Defaults are provided by the config if the attribute is not found in the
|
||||
config or commandline. For instance, if we are using the list command
|
||||
fixtures might not be able to count on the build_dir being provided since
|
||||
we aren't going to build anything.
|
||||
'''
|
||||
defaults.base_dir = os.path.abspath(os.path.join(absdirpath(__file__),
|
||||
os.pardir,
|
||||
os.pardir))
|
||||
defaults.result_path = os.path.join(os.getcwd(), '.testing-results')
|
||||
defaults.list_only_failed = False
|
||||
defaults.resource_url = 'http://dist.gem5.org/dist/v20'
|
||||
|
||||
def define_constants(constants):
|
||||
'''
|
||||
'constants' are values not directly exposed by the config, but are attached
|
||||
to the object for centralized access. These should be used for setting
|
||||
common string names used across the test framework. A simple typo in
|
||||
a string can take a lot of debugging to uncover the issue, attribute errors
|
||||
are easier to notice and most autocompletion systems detect them.
|
||||
'''
|
||||
constants.system_out_name = 'system-out'
|
||||
constants.system_err_name = 'system-err'
|
||||
|
||||
constants.isa_tag_type = 'isa'
|
||||
constants.x86_tag = 'X86'
|
||||
constants.sparc_tag = 'SPARC'
|
||||
constants.riscv_tag = 'RISCV'
|
||||
constants.arm_tag = 'ARM'
|
||||
constants.mips_tag = 'MIPS'
|
||||
constants.power_tag = 'POWER'
|
||||
constants.null_tag = 'NULL'
|
||||
|
||||
constants.variant_tag_type = 'variant'
|
||||
constants.opt_tag = 'opt'
|
||||
constants.debug_tag = 'debug'
|
||||
constants.fast_tag = 'fast'
|
||||
|
||||
constants.length_tag_type = 'length'
|
||||
constants.quick_tag = 'quick'
|
||||
constants.long_tag = 'long'
|
||||
|
||||
constants.host_isa_tag_type = 'host'
|
||||
constants.host_x86_64_tag = 'x86_64'
|
||||
constants.host_i386_tag = 'i386'
|
||||
constants.host_arm_tag = 'aarch64'
|
||||
|
||||
constants.supported_tags = {
|
||||
constants.isa_tag_type : (
|
||||
constants.x86_tag,
|
||||
constants.sparc_tag,
|
||||
constants.riscv_tag,
|
||||
constants.arm_tag,
|
||||
constants.mips_tag,
|
||||
constants.power_tag,
|
||||
constants.null_tag,
|
||||
),
|
||||
constants.variant_tag_type: (
|
||||
constants.opt_tag,
|
||||
constants.debug_tag,
|
||||
constants.fast_tag,
|
||||
),
|
||||
constants.length_tag_type: (
|
||||
constants.quick_tag,
|
||||
constants.long_tag,
|
||||
),
|
||||
constants.host_isa_tag_type: (
|
||||
constants.host_x86_64_tag,
|
||||
constants.host_i386_tag,
|
||||
constants.host_arm_tag,
|
||||
),
|
||||
}
|
||||
|
||||
# Binding target ISA with host ISA. This is useful for the
|
||||
# case where host ISA and target ISA need to coincide
|
||||
constants.target_host = {
|
||||
constants.arm_tag : (constants.host_arm_tag,),
|
||||
constants.x86_tag : (constants.host_x86_64_tag, constants.host_i386_tag),
|
||||
constants.sparc_tag : (constants.host_x86_64_tag, constants.host_i386_tag),
|
||||
constants.riscv_tag : (constants.host_x86_64_tag, constants.host_i386_tag),
|
||||
constants.mips_tag : (constants.host_x86_64_tag, constants.host_i386_tag),
|
||||
constants.power_tag : (constants.host_x86_64_tag, constants.host_i386_tag),
|
||||
constants.null_tag : (None,)
|
||||
}
|
||||
|
||||
constants.supported_isas = constants.supported_tags['isa']
|
||||
constants.supported_variants = constants.supported_tags['variant']
|
||||
constants.supported_lengths = constants.supported_tags['length']
|
||||
constants.supported_hosts = constants.supported_tags['host']
|
||||
|
||||
constants.tempdir_fixture_name = 'tempdir'
|
||||
constants.gem5_simulation_stderr = 'simerr'
|
||||
constants.gem5_simulation_stdout = 'simout'
|
||||
constants.gem5_simulation_stats = 'stats.txt'
|
||||
constants.gem5_simulation_config_ini = 'config.ini'
|
||||
constants.gem5_simulation_config_json = 'config.json'
|
||||
constants.gem5_returncode_fixture_name = 'gem5-returncode'
|
||||
constants.gem5_binary_fixture_name = 'gem5'
|
||||
constants.xml_filename = 'results.xml'
|
||||
constants.pickle_filename = 'results.pickle'
|
||||
constants.pickle_protocol = highest_pickle_protocol
|
||||
|
||||
# The root directory which all test names will be based off of.
|
||||
constants.testing_base = absdirpath(os.path.join(absdirpath(__file__),
|
||||
os.pardir))
|
||||
|
||||
def define_post_processors(config):
|
||||
'''
|
||||
post_processors are used to do final configuration of variables. This is
|
||||
useful if there is a dynamically set default, or some function that needs
|
||||
to be applied after parsing in order to set a configration value.
|
||||
|
||||
Post processors must accept a single argument that will either be a tuple
|
||||
containing the already set config value or ``None`` if the config value
|
||||
has not been set to anything. They must return the modified value in the
|
||||
same format.
|
||||
'''
|
||||
|
||||
def set_default_build_dir(build_dir):
|
||||
'''
|
||||
Post-processor to set the default build_dir based on the base_dir.
|
||||
|
||||
.. seealso :func:`~_Config._add_post_processor`
|
||||
'''
|
||||
if not build_dir or build_dir[0] is None:
|
||||
base_dir = config._lookup_val('base_dir')[0]
|
||||
build_dir = (os.path.join(base_dir, 'build'),)
|
||||
return build_dir
|
||||
|
||||
def fix_verbosity_hack(verbose):
|
||||
return (verbose[0].val,)
|
||||
|
||||
def threads_as_int(threads):
|
||||
if threads is not None:
|
||||
return (int(threads[0]),)
|
||||
|
||||
def test_threads_as_int(test_threads):
|
||||
if test_threads is not None:
|
||||
return (int(test_threads[0]),)
|
||||
|
||||
def default_isa(isa):
|
||||
if not isa[0]:
|
||||
return [constants.supported_tags[constants.isa_tag_type]]
|
||||
else:
|
||||
return isa
|
||||
|
||||
def default_variant(variant):
|
||||
if not variant[0]:
|
||||
# Default variant is only opt. No need to run tests with multiple
|
||||
# different compilation targets
|
||||
return [[constants.opt_tag]]
|
||||
else:
|
||||
return variant
|
||||
|
||||
def default_length(length):
|
||||
if not length[0]:
|
||||
return [[constants.quick_tag]]
|
||||
else:
|
||||
return length
|
||||
|
||||
def default_host(host):
|
||||
if not host[0]:
|
||||
try:
|
||||
import platform
|
||||
host_machine = platform.machine()
|
||||
if host_machine not in constants.supported_hosts:
|
||||
raise ValueError("Invalid host machine")
|
||||
return [[host_machine]]
|
||||
except:
|
||||
return [[constants.host_x86_64_tag]]
|
||||
else:
|
||||
return host
|
||||
|
||||
def compile_tag_regex(positional_tags):
|
||||
if not positional_tags:
|
||||
return positional_tags
|
||||
else:
|
||||
new_positional_tags_list = []
|
||||
positional_tags = positional_tags[0]
|
||||
|
||||
for flag, regex in positional_tags:
|
||||
if flag == 'exclude_tags':
|
||||
tag_regex = TagRegex(False, regex)
|
||||
elif flag == 'include_tags':
|
||||
tag_regex = TagRegex(True, regex)
|
||||
else:
|
||||
raise ValueError('Unsupported flag.')
|
||||
new_positional_tags_list.append(tag_regex)
|
||||
|
||||
return (new_positional_tags_list,)
|
||||
|
||||
config._add_post_processor('build_dir', set_default_build_dir)
|
||||
config._add_post_processor('verbose', fix_verbosity_hack)
|
||||
config._add_post_processor('isa', default_isa)
|
||||
config._add_post_processor('variant', default_variant)
|
||||
config._add_post_processor('length', default_length)
|
||||
config._add_post_processor('host', default_host)
|
||||
config._add_post_processor('threads', threads_as_int)
|
||||
config._add_post_processor('test_threads', test_threads_as_int)
|
||||
config._add_post_processor(StorePositionalTagsAction.position_kword,
|
||||
compile_tag_regex)
|
||||
class Argument(object):
|
||||
'''
|
||||
Class represents a cli argument/flag for a argparse parser.
|
||||
|
||||
:attr name: The long name of this object that will be stored in the arg
|
||||
output by the final parser.
|
||||
'''
|
||||
def __init__(self, *flags, **kwargs):
|
||||
self.flags = flags
|
||||
self.kwargs = kwargs
|
||||
|
||||
if len(flags) == 0:
|
||||
raise ValueError("Need at least one argument.")
|
||||
elif 'dest' in kwargs:
|
||||
self.name = kwargs['dest']
|
||||
elif len(flags) > 1 or flags[0].startswith('-'):
|
||||
for flag in flags:
|
||||
if not flag.startswith('-'):
|
||||
raise ValueError("invalid option string %s: must start"
|
||||
"with a character '-'" % flag)
|
||||
|
||||
if flag.startswith('--'):
|
||||
if not hasattr(self, 'name'):
|
||||
self.name = flag.lstrip('-')
|
||||
|
||||
if not hasattr(self, 'name'):
|
||||
self.name = flags[0].lstrip('-')
|
||||
self.name = self.name.replace('-', '_')
|
||||
|
||||
def add_to(self, parser):
|
||||
'''Add this argument to the given parser.'''
|
||||
parser.add_argument(*self.flags, **self.kwargs)
|
||||
|
||||
def copy(self):
|
||||
'''Copy this argument so you might modify any of its kwargs.'''
|
||||
return copy.deepcopy(self)
|
||||
|
||||
|
||||
class _StickyInt:
|
||||
'''
|
||||
A class that is used to cheat the verbosity count incrementer by
|
||||
pretending to be an int. This makes the int stay on the heap and eat other
|
||||
real numbers when they are added to it.
|
||||
|
||||
We use this so we can allow the verbose flag to be provided before or after
|
||||
the subcommand. This likely has no utility outside of this use case.
|
||||
'''
|
||||
def __init__(self, val=0):
|
||||
self.val = val
|
||||
self.type = int
|
||||
def __add__(self, other):
|
||||
self.val += other
|
||||
return self
|
||||
|
||||
common_args = NotImplemented
|
||||
|
||||
class StorePositionAction(argparse.Action):
|
||||
'''Base class for classes wishing to create namespaces where
|
||||
arguments are stored in the order provided via the command line.
|
||||
'''
|
||||
position_kword = 'positional'
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
if not self.position_kword in namespace:
|
||||
setattr(namespace, self.position_kword, [])
|
||||
previous = getattr(namespace, self.position_kword)
|
||||
previous.append((self.dest, values))
|
||||
setattr(namespace, self.position_kword, previous)
|
||||
|
||||
class StorePositionalTagsAction(StorePositionAction):
|
||||
position_kword = 'tag_filters'
|
||||
|
||||
def define_common_args(config):
|
||||
'''
|
||||
Common args are arguments which are likely to be simular between different
|
||||
subcommands, so they are available to all by placing their definitions
|
||||
here.
|
||||
'''
|
||||
global common_args
|
||||
|
||||
# A list of common arguments/flags used across cli parsers.
|
||||
common_args = [
|
||||
Argument(
|
||||
'directory',
|
||||
nargs='?',
|
||||
default=os.getcwd(),
|
||||
help='Directory to start searching for tests in'),
|
||||
Argument(
|
||||
'--exclude-tags',
|
||||
action=StorePositionalTagsAction,
|
||||
help='A tag comparison used to select tests.'),
|
||||
Argument(
|
||||
'--include-tags',
|
||||
action=StorePositionalTagsAction,
|
||||
help='A tag comparison used to select tests.'),
|
||||
Argument(
|
||||
'--isa',
|
||||
action='append',
|
||||
default=[],
|
||||
help="Only tests that are valid with one of these ISAs. "
|
||||
"Comma separated."),
|
||||
Argument(
|
||||
'--variant',
|
||||
action='append',
|
||||
default=[],
|
||||
help="Only tests that are valid with one of these binary variants"
|
||||
"(e.g., opt, debug). Comma separated."),
|
||||
Argument(
|
||||
'--length',
|
||||
action='append',
|
||||
default=[],
|
||||
help="Only tests that are one of these lengths. Comma separated."),
|
||||
Argument(
|
||||
'--host',
|
||||
action='append',
|
||||
default=[],
|
||||
help="Only tests that are meant to runnable on the selected host"),
|
||||
Argument(
|
||||
'--uid',
|
||||
action='store',
|
||||
default=None,
|
||||
help='UID of a specific test item to run.'),
|
||||
Argument(
|
||||
'--build-dir',
|
||||
action='store',
|
||||
help='Build directory for SCons'),
|
||||
Argument(
|
||||
'--base-dir',
|
||||
action='store',
|
||||
default=config._defaults.base_dir,
|
||||
help='Directory to change to in order to exec scons.'),
|
||||
Argument(
|
||||
'-j', '--threads',
|
||||
action='store',
|
||||
default=1,
|
||||
help='Number of threads to run SCons with.'),
|
||||
Argument(
|
||||
'-t', '--test-threads',
|
||||
action='store',
|
||||
default=1,
|
||||
help='Number of threads to spawn to run concurrent tests with.'),
|
||||
Argument(
|
||||
'-v',
|
||||
action='count',
|
||||
dest='verbose',
|
||||
default=_StickyInt(),
|
||||
help='Increase verbosity'),
|
||||
Argument(
|
||||
'--config-path',
|
||||
action='store',
|
||||
default=os.getcwd(),
|
||||
help='Path to read a testing.ini config in'
|
||||
),
|
||||
Argument(
|
||||
'--skip-build',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Skip the building component of SCons targets.'
|
||||
),
|
||||
Argument(
|
||||
'--result-path',
|
||||
action='store',
|
||||
help='The path to store results in.'
|
||||
),
|
||||
Argument(
|
||||
'--bin-path',
|
||||
action='store',
|
||||
default=None,
|
||||
help='Path where binaries are stored (downloaded if not present)'
|
||||
),
|
||||
Argument(
|
||||
'--resource-url',
|
||||
action='store',
|
||||
default=config._defaults.resource_url,
|
||||
help='The URL where the resources reside.'
|
||||
),
|
||||
|
||||
]
|
||||
|
||||
# NOTE: There is a limitation which arises due to this format. If you have
|
||||
# multiple arguments with the same name only the final one in the list
|
||||
# will be saved.
|
||||
#
|
||||
# e.g. if you have a -v argument which increments verbosity level and
|
||||
# a separate --verbose flag which 'store's verbosity level. the final
|
||||
# one in the list will be saved.
|
||||
common_args = AttrDict({arg.name:arg for arg in common_args})
|
||||
|
||||
@add_metaclass(abc.ABCMeta)
|
||||
class ArgParser(object):
|
||||
|
||||
def __init__(self, parser):
|
||||
# Copy public methods of the parser.
|
||||
for attr in dir(parser):
|
||||
if not attr.startswith('_'):
|
||||
setattr(self, attr, getattr(parser, attr))
|
||||
self.parser = parser
|
||||
self.add_argument = self.parser.add_argument
|
||||
|
||||
# Argument will be added to all parsers and subparsers.
|
||||
common_args.verbose.add_to(parser)
|
||||
|
||||
|
||||
class CommandParser(ArgParser):
|
||||
'''
|
||||
Main parser which parses command strings and uses those to direct to
|
||||
a subparser.
|
||||
'''
|
||||
def __init__(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
super(CommandParser, self).__init__(parser)
|
||||
self.subparser = self.add_subparsers(dest='command')
|
||||
|
||||
|
||||
class RunParser(ArgParser):
|
||||
'''
|
||||
Parser for the \'run\' command.
|
||||
'''
|
||||
def __init__(self, subparser):
|
||||
parser = subparser.add_parser(
|
||||
'run',
|
||||
help='''Run Tests.'''
|
||||
)
|
||||
|
||||
super(RunParser, self).__init__(parser)
|
||||
|
||||
common_args.uid.add_to(parser)
|
||||
common_args.skip_build.add_to(parser)
|
||||
common_args.directory.add_to(parser)
|
||||
common_args.build_dir.add_to(parser)
|
||||
common_args.base_dir.add_to(parser)
|
||||
common_args.bin_path.add_to(parser)
|
||||
common_args.threads.add_to(parser)
|
||||
common_args.test_threads.add_to(parser)
|
||||
common_args.isa.add_to(parser)
|
||||
common_args.variant.add_to(parser)
|
||||
common_args.length.add_to(parser)
|
||||
common_args.host.add_to(parser)
|
||||
common_args.include_tags.add_to(parser)
|
||||
common_args.exclude_tags.add_to(parser)
|
||||
|
||||
|
||||
class ListParser(ArgParser):
|
||||
'''
|
||||
Parser for the \'list\' command.
|
||||
'''
|
||||
def __init__(self, subparser):
|
||||
parser = subparser.add_parser(
|
||||
'list',
|
||||
help='''List and query test metadata.'''
|
||||
)
|
||||
super(ListParser, self).__init__(parser)
|
||||
|
||||
Argument(
|
||||
'--suites',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='List all test suites.'
|
||||
).add_to(parser)
|
||||
Argument(
|
||||
'--tests',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='List all test cases.'
|
||||
).add_to(parser)
|
||||
Argument(
|
||||
'--fixtures',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='List all fixtures.'
|
||||
).add_to(parser)
|
||||
Argument(
|
||||
'--all-tags',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='List all tags.'
|
||||
).add_to(parser)
|
||||
Argument(
|
||||
'-q',
|
||||
dest='quiet',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Quiet output (machine readable).'
|
||||
).add_to(parser)
|
||||
|
||||
common_args.directory.add_to(parser)
|
||||
common_args.bin_path.add_to(parser)
|
||||
common_args.isa.add_to(parser)
|
||||
common_args.variant.add_to(parser)
|
||||
common_args.length.add_to(parser)
|
||||
common_args.host.add_to(parser)
|
||||
common_args.include_tags.add_to(parser)
|
||||
common_args.exclude_tags.add_to(parser)
|
||||
|
||||
|
||||
class RerunParser(ArgParser):
|
||||
def __init__(self, subparser):
|
||||
parser = subparser.add_parser(
|
||||
'rerun',
|
||||
help='''Rerun failed tests.'''
|
||||
)
|
||||
super(RerunParser, self).__init__(parser)
|
||||
|
||||
common_args.skip_build.add_to(parser)
|
||||
common_args.directory.add_to(parser)
|
||||
common_args.build_dir.add_to(parser)
|
||||
common_args.base_dir.add_to(parser)
|
||||
common_args.bin_path.add_to(parser)
|
||||
common_args.threads.add_to(parser)
|
||||
common_args.test_threads.add_to(parser)
|
||||
common_args.isa.add_to(parser)
|
||||
common_args.variant.add_to(parser)
|
||||
common_args.length.add_to(parser)
|
||||
common_args.host.add_to(parser)
|
||||
|
||||
config = _Config()
|
||||
define_constants(config.constants)
|
||||
|
||||
# Constants are directly exposed and available once this module is created.
|
||||
# All constants MUST be defined before this point.
|
||||
config.constants = FrozenAttrDict(config.constants.__dict__)
|
||||
constants = config.constants
|
||||
|
||||
'''
|
||||
This config object is the singleton config object available throughout the
|
||||
framework.
|
||||
'''
|
||||
def initialize_config():
|
||||
'''
|
||||
Parse the commandline arguments and setup the config varibles.
|
||||
'''
|
||||
global config
|
||||
|
||||
# Setup constants and defaults
|
||||
define_defaults(config._defaults)
|
||||
define_post_processors(config)
|
||||
define_common_args(config)
|
||||
|
||||
# Setup parser and subcommands
|
||||
baseparser = CommandParser()
|
||||
runparser = RunParser(baseparser.subparser)
|
||||
listparser = ListParser(baseparser.subparser)
|
||||
rerunparser = RerunParser(baseparser.subparser)
|
||||
|
||||
# Initialize the config by parsing args and running callbacks.
|
||||
config._init(baseparser)
|
||||
Reference in New Issue
Block a user