112882Sspwilson2@wisc.edu# Copyright (c) 2017 Mark D. Hill and David A. Wood
212882Sspwilson2@wisc.edu# All rights reserved.
312882Sspwilson2@wisc.edu#
412882Sspwilson2@wisc.edu# Redistribution and use in source and binary forms, with or without
512882Sspwilson2@wisc.edu# modification, are permitted provided that the following conditions are
612882Sspwilson2@wisc.edu# met: redistributions of source code must retain the above copyright
712882Sspwilson2@wisc.edu# notice, this list of conditions and the following disclaimer;
812882Sspwilson2@wisc.edu# redistributions in binary form must reproduce the above copyright
912882Sspwilson2@wisc.edu# notice, this list of conditions and the following disclaimer in the
1012882Sspwilson2@wisc.edu# documentation and/or other materials provided with the distribution;
1112882Sspwilson2@wisc.edu# neither the name of the copyright holders nor the names of its
1212882Sspwilson2@wisc.edu# contributors may be used to endorse or promote products derived from
1312882Sspwilson2@wisc.edu# this software without specific prior written permission.
1412882Sspwilson2@wisc.edu#
1512882Sspwilson2@wisc.edu# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1612882Sspwilson2@wisc.edu# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1712882Sspwilson2@wisc.edu# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1812882Sspwilson2@wisc.edu# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1912882Sspwilson2@wisc.edu# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2012882Sspwilson2@wisc.edu# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2112882Sspwilson2@wisc.edu# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2212882Sspwilson2@wisc.edu# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2312882Sspwilson2@wisc.edu# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2412882Sspwilson2@wisc.edu# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2512882Sspwilson2@wisc.edu# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2612882Sspwilson2@wisc.edu#
2712882Sspwilson2@wisc.edu# Authors: Sean Wilson
2812882Sspwilson2@wisc.edu
2912882Sspwilson2@wisc.edu'''
3012882Sspwilson2@wisc.eduGlobal configuration module which exposes two types of configuration
3112882Sspwilson2@wisc.eduvariables:
3212882Sspwilson2@wisc.edu
3312882Sspwilson2@wisc.edu1. config
3412882Sspwilson2@wisc.edu2. constants (Also attached to the config variable as an attribute)
3512882Sspwilson2@wisc.edu
3612882Sspwilson2@wisc.eduThe main motivation for this module is to have a centralized location for
3712882Sspwilson2@wisc.edudefaults and configuration by command line and files for the test framework.
3812882Sspwilson2@wisc.edu
3912882Sspwilson2@wisc.eduA secondary goal is to reduce programming errors by providing common constant
4012882Sspwilson2@wisc.edustrings and values as python attributes to simplify detection of typos.
4112882Sspwilson2@wisc.eduA simple typo in a string can take a lot of debugging to uncover the issue,
4212882Sspwilson2@wisc.eduattribute errors are easier to notice and most autocompletion systems detect
4312882Sspwilson2@wisc.eduthem.
4412882Sspwilson2@wisc.edu
4512882Sspwilson2@wisc.eduThe config variable is initialzed by calling :func:`initialize_config`.
4612882Sspwilson2@wisc.eduBefore this point only ``constants`` will be availaible. This is to ensure
4712882Sspwilson2@wisc.eduthat library function writers never accidentally get stale config attributes.
4812882Sspwilson2@wisc.edu
4912882Sspwilson2@wisc.eduProgram arguments/flag arguments are available from the config as attributes.
5012882Sspwilson2@wisc.eduIf an attribute was not set by the command line or the optional config file,
5112882Sspwilson2@wisc.eduthen it will fallback to the `_defaults` value, if still the value is not
5212882Sspwilson2@wisc.edufound an AttributeError will be raised.
5312882Sspwilson2@wisc.edu
5412882Sspwilson2@wisc.edu:func define_defaults:
5512882Sspwilson2@wisc.edu    Provided by the config if the attribute is not found in the config or
5612882Sspwilson2@wisc.edu    commandline. For instance, if we are using the list command fixtures might
5712882Sspwilson2@wisc.edu    not be able to count on the build_dir being provided since we aren't going
5812882Sspwilson2@wisc.edu    to build anything.
5912882Sspwilson2@wisc.edu
6012882Sspwilson2@wisc.edu:var constants:
6112882Sspwilson2@wisc.edu    Values not directly exposed by the config, but are attached to the object
6212882Sspwilson2@wisc.edu    for centralized access. I.E. you can reach them with
6312882Sspwilson2@wisc.edu    :code:`config.constants.attribute`. These should be used for setting
6412882Sspwilson2@wisc.edu    common string names used across the test framework.
6512882Sspwilson2@wisc.edu    :code:`_defaults.build_dir = None` Once this module has been imported
6612882Sspwilson2@wisc.edu    constants should not be modified and their base attributes are frozen.
6712882Sspwilson2@wisc.edu'''
6812882Sspwilson2@wisc.eduimport abc
6912882Sspwilson2@wisc.eduimport argparse
7012882Sspwilson2@wisc.eduimport copy
7112882Sspwilson2@wisc.eduimport os
7212882Sspwilson2@wisc.eduimport re
7312882Sspwilson2@wisc.edu
7412882Sspwilson2@wisc.edufrom ConfigParser import ConfigParser
7512882Sspwilson2@wisc.edufrom pickle import HIGHEST_PROTOCOL as highest_pickle_protocol
7612882Sspwilson2@wisc.edu
7712882Sspwilson2@wisc.edufrom helper import absdirpath, AttrDict, FrozenAttrDict
7812882Sspwilson2@wisc.edu
7912882Sspwilson2@wisc.educlass UninitialzedAttributeException(Exception):
8012882Sspwilson2@wisc.edu    '''
8112882Sspwilson2@wisc.edu    Signals that an attribute in the config file was not initialized.
8212882Sspwilson2@wisc.edu    '''
8312882Sspwilson2@wisc.edu    pass
8412882Sspwilson2@wisc.edu
8512882Sspwilson2@wisc.educlass UninitializedConfigException(Exception):
8612882Sspwilson2@wisc.edu    '''
8712882Sspwilson2@wisc.edu    Signals that the config was not initialized before trying to access an
8812882Sspwilson2@wisc.edu    attribute.
8912882Sspwilson2@wisc.edu    '''
9012882Sspwilson2@wisc.edu    pass
9112882Sspwilson2@wisc.edu
9212882Sspwilson2@wisc.educlass TagRegex(object):
9312882Sspwilson2@wisc.edu    def __init__(self, include, regex):
9412882Sspwilson2@wisc.edu        self.include = include
9512882Sspwilson2@wisc.edu        self.regex = re.compile(regex)
9612882Sspwilson2@wisc.edu
9712882Sspwilson2@wisc.edu    def __str__(self):
9812882Sspwilson2@wisc.edu        type_ = 'Include' if self.include else 'Remove'
9912882Sspwilson2@wisc.edu        return '%10s: %s' % (type_, self.regex.pattern)
10012882Sspwilson2@wisc.edu
10112882Sspwilson2@wisc.educlass _Config(object):
10212882Sspwilson2@wisc.edu    _initialized = False
10312882Sspwilson2@wisc.edu
10412882Sspwilson2@wisc.edu    __shared_dict = {}
10512882Sspwilson2@wisc.edu
10612882Sspwilson2@wisc.edu    constants = AttrDict()
10712882Sspwilson2@wisc.edu    _defaults = AttrDict()
10812882Sspwilson2@wisc.edu    _config = {}
10912882Sspwilson2@wisc.edu
11012882Sspwilson2@wisc.edu    _cli_args = {}
11112882Sspwilson2@wisc.edu    _post_processors = {}
11212882Sspwilson2@wisc.edu
11312882Sspwilson2@wisc.edu    def __init__(self):
11412882Sspwilson2@wisc.edu        # This object will act as if it were a singleton.
11512882Sspwilson2@wisc.edu        self.__dict__ = self.__shared_dict
11612882Sspwilson2@wisc.edu
11712882Sspwilson2@wisc.edu    def _init(self, parser):
11812882Sspwilson2@wisc.edu        self._parse_commandline_args(parser)
11912882Sspwilson2@wisc.edu        self._run_post_processors()
12012882Sspwilson2@wisc.edu        self._initialized = True
12112882Sspwilson2@wisc.edu
12212882Sspwilson2@wisc.edu    def _init_with_dicts(self, config, defaults):
12312882Sspwilson2@wisc.edu        self._config = config
12412882Sspwilson2@wisc.edu        self._defaults = defaults
12512882Sspwilson2@wisc.edu        self._initialized = True
12612882Sspwilson2@wisc.edu
12712882Sspwilson2@wisc.edu    def _add_post_processor(self, attr, post_processor):
12812882Sspwilson2@wisc.edu        '''
12912882Sspwilson2@wisc.edu        :param attr: Attribute to pass to and recieve from the
13012882Sspwilson2@wisc.edu        :func:`post_processor`.
13112882Sspwilson2@wisc.edu
13212882Sspwilson2@wisc.edu        :param post_processor: A callback functions called in a chain to
13312882Sspwilson2@wisc.edu            perform additional setup for a config argument. Should return a
13412882Sspwilson2@wisc.edu            tuple containing the new value for the config attr.
13512882Sspwilson2@wisc.edu        '''
13612882Sspwilson2@wisc.edu        if attr not in self._post_processors:
13712882Sspwilson2@wisc.edu            self._post_processors[attr] = []
13812882Sspwilson2@wisc.edu        self._post_processors[attr].append(post_processor)
13912882Sspwilson2@wisc.edu
14012882Sspwilson2@wisc.edu    def _set(self, name, value):
14112882Sspwilson2@wisc.edu        self._config[name] = value
14212882Sspwilson2@wisc.edu
14312882Sspwilson2@wisc.edu    def _parse_commandline_args(self, parser):
14412882Sspwilson2@wisc.edu        args = parser.parse_args()
14512882Sspwilson2@wisc.edu
14612882Sspwilson2@wisc.edu        self._config_file_args = {}
14712882Sspwilson2@wisc.edu
14812882Sspwilson2@wisc.edu        for attr in dir(args):
14912882Sspwilson2@wisc.edu            # Ignore non-argument attributes.
15012882Sspwilson2@wisc.edu            if not attr.startswith('_'):
15112882Sspwilson2@wisc.edu                self._config_file_args[attr] = getattr(args, attr)
15212882Sspwilson2@wisc.edu        self._config.update(self._config_file_args)
15312882Sspwilson2@wisc.edu
15412882Sspwilson2@wisc.edu    def _run_post_processors(self):
15512882Sspwilson2@wisc.edu        for attr, callbacks in self._post_processors.items():
15612882Sspwilson2@wisc.edu            newval = self._lookup_val(attr)
15712882Sspwilson2@wisc.edu            for callback in callbacks:
15812882Sspwilson2@wisc.edu                newval = callback(newval)
15912882Sspwilson2@wisc.edu            if newval is not None:
16012882Sspwilson2@wisc.edu                newval = newval[0]
16112882Sspwilson2@wisc.edu            self._set(attr, newval)
16212882Sspwilson2@wisc.edu
16312882Sspwilson2@wisc.edu
16412882Sspwilson2@wisc.edu    def _lookup_val(self, attr):
16512882Sspwilson2@wisc.edu        '''
16612882Sspwilson2@wisc.edu        Get the attribute from the config or fallback to defaults.
16712882Sspwilson2@wisc.edu
16812882Sspwilson2@wisc.edu        :returns: If the value is not stored return None. Otherwise a tuple
16912882Sspwilson2@wisc.edu            containing the value.
17012882Sspwilson2@wisc.edu        '''
17112882Sspwilson2@wisc.edu        if attr in self._config:
17212882Sspwilson2@wisc.edu            return (self._config[attr],)
17312882Sspwilson2@wisc.edu        elif hasattr(self._defaults, attr):
17412882Sspwilson2@wisc.edu            return (getattr(self._defaults, attr),)
17512882Sspwilson2@wisc.edu
17612882Sspwilson2@wisc.edu    def __getattr__(self, attr):
17712882Sspwilson2@wisc.edu        if attr in dir(super(_Config, self)):
17812882Sspwilson2@wisc.edu            return getattr(super(_Config, self), attr)
17912882Sspwilson2@wisc.edu        elif not self._initialized:
18012882Sspwilson2@wisc.edu            raise UninitializedConfigException(
18112882Sspwilson2@wisc.edu                'Cannot directly access elements from the config before it is'
18212882Sspwilson2@wisc.edu                ' initialized')
18312882Sspwilson2@wisc.edu        else:
18412882Sspwilson2@wisc.edu            val = self._lookup_val(attr)
18512882Sspwilson2@wisc.edu            if val is not None:
18612882Sspwilson2@wisc.edu                return val[0]
18712882Sspwilson2@wisc.edu            else:
18812882Sspwilson2@wisc.edu                raise UninitialzedAttributeException(
18912882Sspwilson2@wisc.edu                    '%s was not initialzed in the config.' % attr)
19012882Sspwilson2@wisc.edu
19112882Sspwilson2@wisc.edu    def get_tags(self):
19212882Sspwilson2@wisc.edu        d = {typ: set(self.__getattr__(typ))
19312882Sspwilson2@wisc.edu            for typ in self.constants.supported_tags}
19412882Sspwilson2@wisc.edu        if any(map(lambda vals: bool(vals), d.values())):
19512882Sspwilson2@wisc.edu            return d
19612882Sspwilson2@wisc.edu        else:
19712882Sspwilson2@wisc.edu            return {}
19812882Sspwilson2@wisc.edu
19912882Sspwilson2@wisc.edudef define_defaults(defaults):
20012882Sspwilson2@wisc.edu    '''
20112882Sspwilson2@wisc.edu    Defaults are provided by the config if the attribute is not found in the
20212882Sspwilson2@wisc.edu    config or commandline. For instance, if we are using the list command
20312882Sspwilson2@wisc.edu    fixtures might not be able to count on the build_dir being provided since
20412882Sspwilson2@wisc.edu    we aren't going to build anything.
20512882Sspwilson2@wisc.edu    '''
20612882Sspwilson2@wisc.edu    defaults.base_dir = os.path.abspath(os.path.join(absdirpath(__file__),
20712882Sspwilson2@wisc.edu                                                      os.pardir,
20812882Sspwilson2@wisc.edu                                                      os.pardir))
20912882Sspwilson2@wisc.edu    defaults.result_path = os.path.join(os.getcwd(), '.testing-results')
21012882Sspwilson2@wisc.edu    defaults.list_only_failed = False
21112882Sspwilson2@wisc.edu
21212882Sspwilson2@wisc.edudef define_constants(constants):
21312882Sspwilson2@wisc.edu    '''
21412882Sspwilson2@wisc.edu    'constants' are values not directly exposed by the config, but are attached
21512882Sspwilson2@wisc.edu    to the object for centralized access. These should be used for setting
21612882Sspwilson2@wisc.edu    common string names used across the test framework. A simple typo in
21712882Sspwilson2@wisc.edu    a string can take a lot of debugging to uncover the issue, attribute errors
21812882Sspwilson2@wisc.edu    are easier to notice and most autocompletion systems detect them.
21912882Sspwilson2@wisc.edu    '''
22012882Sspwilson2@wisc.edu    constants.system_out_name = 'system-out'
22112882Sspwilson2@wisc.edu    constants.system_err_name = 'system-err'
22212882Sspwilson2@wisc.edu
22312882Sspwilson2@wisc.edu    constants.isa_tag_type = 'isa'
22412882Sspwilson2@wisc.edu    constants.x86_tag = 'X86'
22512882Sspwilson2@wisc.edu    constants.sparc_tag = 'SPARC'
22612882Sspwilson2@wisc.edu    constants.alpha_tag = 'ALPHA'
22712882Sspwilson2@wisc.edu    constants.riscv_tag = 'RISCV'
22812882Sspwilson2@wisc.edu    constants.arm_tag = 'ARM'
22912882Sspwilson2@wisc.edu    constants.mips_tag = 'MIPS'
23012882Sspwilson2@wisc.edu    constants.power_tag = 'POWER'
23112882Sspwilson2@wisc.edu    constants.null_tag = 'NULL'
23212882Sspwilson2@wisc.edu
23312882Sspwilson2@wisc.edu    constants.variant_tag_type = 'variant'
23412882Sspwilson2@wisc.edu    constants.opt_tag = 'opt'
23512882Sspwilson2@wisc.edu    constants.debug_tag = 'debug'
23612882Sspwilson2@wisc.edu    constants.fast_tag = 'fast'
23712882Sspwilson2@wisc.edu
23812882Sspwilson2@wisc.edu    constants.length_tag_type = 'length'
23912882Sspwilson2@wisc.edu    constants.quick_tag = 'quick'
24012882Sspwilson2@wisc.edu    constants.long_tag = 'long'
24112882Sspwilson2@wisc.edu
24212882Sspwilson2@wisc.edu    constants.supported_tags = {
24312882Sspwilson2@wisc.edu        constants.isa_tag_type : (
24412882Sspwilson2@wisc.edu            constants.x86_tag,
24512882Sspwilson2@wisc.edu            constants.sparc_tag,
24612882Sspwilson2@wisc.edu            constants.alpha_tag,
24712882Sspwilson2@wisc.edu            constants.riscv_tag,
24812882Sspwilson2@wisc.edu            constants.arm_tag,
24912882Sspwilson2@wisc.edu            constants.mips_tag,
25012882Sspwilson2@wisc.edu            constants.power_tag,
25112882Sspwilson2@wisc.edu            constants.null_tag,
25212882Sspwilson2@wisc.edu            ),
25312882Sspwilson2@wisc.edu        constants.variant_tag_type: (
25412882Sspwilson2@wisc.edu            constants.opt_tag,
25512882Sspwilson2@wisc.edu            constants.debug_tag,
25612882Sspwilson2@wisc.edu            constants.fast_tag,
25712882Sspwilson2@wisc.edu        ),
25812882Sspwilson2@wisc.edu        constants.length_tag_type: (
25912882Sspwilson2@wisc.edu            constants.quick_tag,
26012882Sspwilson2@wisc.edu            constants.long_tag,
26112882Sspwilson2@wisc.edu        ),
26212882Sspwilson2@wisc.edu    }
26312882Sspwilson2@wisc.edu
26412882Sspwilson2@wisc.edu    constants.supported_isas = constants.supported_tags['isa']
26512882Sspwilson2@wisc.edu    constants.supported_variants = constants.supported_tags['variant']
26612882Sspwilson2@wisc.edu    constants.supported_lengths = constants.supported_tags['length']
26712882Sspwilson2@wisc.edu
26812882Sspwilson2@wisc.edu    constants.tempdir_fixture_name = 'tempdir'
26912882Sspwilson2@wisc.edu    constants.gem5_simulation_stderr = 'simerr'
27012882Sspwilson2@wisc.edu    constants.gem5_simulation_stdout = 'simout'
27112882Sspwilson2@wisc.edu    constants.gem5_simulation_stats = 'stats.txt'
27212882Sspwilson2@wisc.edu    constants.gem5_simulation_config_ini = 'config.ini'
27312882Sspwilson2@wisc.edu    constants.gem5_simulation_config_json = 'config.json'
27412882Sspwilson2@wisc.edu    constants.gem5_returncode_fixture_name = 'gem5-returncode'
27512882Sspwilson2@wisc.edu    constants.gem5_binary_fixture_name = 'gem5'
27612882Sspwilson2@wisc.edu    constants.xml_filename = 'results.xml'
27712882Sspwilson2@wisc.edu    constants.pickle_filename = 'results.pickle'
27812882Sspwilson2@wisc.edu    constants.pickle_protocol = highest_pickle_protocol
27912882Sspwilson2@wisc.edu
28012882Sspwilson2@wisc.edu    # The root directory which all test names will be based off of.
28112882Sspwilson2@wisc.edu    constants.testing_base = absdirpath(os.path.join(absdirpath(__file__),
28212882Sspwilson2@wisc.edu                                                     os.pardir))
28312882Sspwilson2@wisc.edu
28412882Sspwilson2@wisc.edudef define_post_processors(config):
28512882Sspwilson2@wisc.edu    '''
28612882Sspwilson2@wisc.edu    post_processors are used to do final configuration of variables. This is
28712882Sspwilson2@wisc.edu    useful if there is a dynamically set default, or some function that needs
28812882Sspwilson2@wisc.edu    to be applied after parsing in order to set a configration value.
28912882Sspwilson2@wisc.edu
29012882Sspwilson2@wisc.edu    Post processors must accept a single argument that will either be a tuple
29112882Sspwilson2@wisc.edu    containing the already set config value or ``None`` if the config value
29212882Sspwilson2@wisc.edu    has not been set to anything. They must return the modified value in the
29312882Sspwilson2@wisc.edu    same format.
29412882Sspwilson2@wisc.edu    '''
29512882Sspwilson2@wisc.edu
29612882Sspwilson2@wisc.edu    def set_default_build_dir(build_dir):
29712882Sspwilson2@wisc.edu        '''
29812882Sspwilson2@wisc.edu        Post-processor to set the default build_dir based on the base_dir.
29912882Sspwilson2@wisc.edu
30012882Sspwilson2@wisc.edu        .. seealso :func:`~_Config._add_post_processor`
30112882Sspwilson2@wisc.edu        '''
30212882Sspwilson2@wisc.edu        if not build_dir or build_dir[0] is None:
30312882Sspwilson2@wisc.edu            base_dir = config._lookup_val('base_dir')[0]
30412882Sspwilson2@wisc.edu            build_dir = (os.path.join(base_dir, 'build'),)
30512882Sspwilson2@wisc.edu        return build_dir
30612882Sspwilson2@wisc.edu
30712882Sspwilson2@wisc.edu    def fix_verbosity_hack(verbose):
30812882Sspwilson2@wisc.edu        return (verbose[0].val,)
30912882Sspwilson2@wisc.edu
31012882Sspwilson2@wisc.edu    def threads_as_int(threads):
31112882Sspwilson2@wisc.edu        if threads is not None:
31212882Sspwilson2@wisc.edu            return (int(threads[0]),)
31312882Sspwilson2@wisc.edu
31412882Sspwilson2@wisc.edu    def test_threads_as_int(test_threads):
31512882Sspwilson2@wisc.edu        if test_threads is not None:
31612882Sspwilson2@wisc.edu            return (int(test_threads[0]),)
31712882Sspwilson2@wisc.edu
31812882Sspwilson2@wisc.edu    def default_isa(isa):
31912882Sspwilson2@wisc.edu        if not isa[0]:
32012882Sspwilson2@wisc.edu            return [constants.supported_tags[constants.isa_tag_type]]
32112882Sspwilson2@wisc.edu        else:
32212882Sspwilson2@wisc.edu            return isa
32312882Sspwilson2@wisc.edu
32412882Sspwilson2@wisc.edu    def default_variant(variant):
32512882Sspwilson2@wisc.edu        if not variant[0]:
32612882Sspwilson2@wisc.edu            # Default variant is only opt. No need to run tests with multiple
32712882Sspwilson2@wisc.edu            # different compilation targets
32812882Sspwilson2@wisc.edu            return [[constants.opt_tag]]
32912882Sspwilson2@wisc.edu        else:
33012882Sspwilson2@wisc.edu            return variant
33112882Sspwilson2@wisc.edu
33212882Sspwilson2@wisc.edu    def default_length(length):
33312882Sspwilson2@wisc.edu        if not length[0]:
33412882Sspwilson2@wisc.edu            return [[constants.quick_tag]]
33512882Sspwilson2@wisc.edu        else:
33612882Sspwilson2@wisc.edu            return length
33712882Sspwilson2@wisc.edu
33812882Sspwilson2@wisc.edu    def compile_tag_regex(positional_tags):
33912882Sspwilson2@wisc.edu        if not positional_tags:
34012882Sspwilson2@wisc.edu            return positional_tags
34112882Sspwilson2@wisc.edu        else:
34212882Sspwilson2@wisc.edu            new_positional_tags_list = []
34312882Sspwilson2@wisc.edu            positional_tags = positional_tags[0]
34412882Sspwilson2@wisc.edu
34512882Sspwilson2@wisc.edu            for flag, regex in positional_tags:
34612882Sspwilson2@wisc.edu                if flag == 'exclude_tags':
34712882Sspwilson2@wisc.edu                    tag_regex = TagRegex(False, regex)
34812882Sspwilson2@wisc.edu                elif flag  == 'include_tags':
34912882Sspwilson2@wisc.edu                    tag_regex = TagRegex(True, regex)
35012882Sspwilson2@wisc.edu                else:
35112882Sspwilson2@wisc.edu                    raise ValueError('Unsupported flag.')
35212882Sspwilson2@wisc.edu                new_positional_tags_list.append(tag_regex)
35312882Sspwilson2@wisc.edu
35412882Sspwilson2@wisc.edu            return (new_positional_tags_list,)
35512882Sspwilson2@wisc.edu
35612882Sspwilson2@wisc.edu    config._add_post_processor('build_dir', set_default_build_dir)
35712882Sspwilson2@wisc.edu    config._add_post_processor('verbose', fix_verbosity_hack)
35812882Sspwilson2@wisc.edu    config._add_post_processor('isa', default_isa)
35912882Sspwilson2@wisc.edu    config._add_post_processor('variant', default_variant)
36012882Sspwilson2@wisc.edu    config._add_post_processor('length', default_length)
36112882Sspwilson2@wisc.edu    config._add_post_processor('threads', threads_as_int)
36212882Sspwilson2@wisc.edu    config._add_post_processor('test_threads', test_threads_as_int)
36312882Sspwilson2@wisc.edu    config._add_post_processor(StorePositionalTagsAction.position_kword,
36412882Sspwilson2@wisc.edu                               compile_tag_regex)
36512882Sspwilson2@wisc.educlass Argument(object):
36612882Sspwilson2@wisc.edu    '''
36712882Sspwilson2@wisc.edu    Class represents a cli argument/flag for a argparse parser.
36812882Sspwilson2@wisc.edu
36912882Sspwilson2@wisc.edu    :attr name: The long name of this object that will be stored in the arg
37012882Sspwilson2@wisc.edu        output by the final parser.
37112882Sspwilson2@wisc.edu    '''
37212882Sspwilson2@wisc.edu    def __init__(self, *flags, **kwargs):
37312882Sspwilson2@wisc.edu        self.flags = flags
37412882Sspwilson2@wisc.edu        self.kwargs = kwargs
37512882Sspwilson2@wisc.edu
37612882Sspwilson2@wisc.edu        if len(flags) == 0:
37712882Sspwilson2@wisc.edu            raise ValueError("Need at least one argument.")
37812882Sspwilson2@wisc.edu        elif 'dest' in kwargs:
37912882Sspwilson2@wisc.edu            self.name = kwargs['dest']
38012882Sspwilson2@wisc.edu        elif len(flags) > 1 or flags[0].startswith('-'):
38112882Sspwilson2@wisc.edu            for flag in flags:
38212882Sspwilson2@wisc.edu                if not flag.startswith('-'):
38312882Sspwilson2@wisc.edu                    raise ValueError("invalid option string %s: must start"
38412882Sspwilson2@wisc.edu                    "with a character '-'" % flag)
38512882Sspwilson2@wisc.edu
38612882Sspwilson2@wisc.edu                if flag.startswith('--'):
38712882Sspwilson2@wisc.edu                    if not hasattr(self, 'name'):
38812882Sspwilson2@wisc.edu                        self.name = flag.lstrip('-')
38912882Sspwilson2@wisc.edu
39012882Sspwilson2@wisc.edu        if not hasattr(self, 'name'):
39112882Sspwilson2@wisc.edu            self.name = flags[0].lstrip('-')
39212882Sspwilson2@wisc.edu        self.name = self.name.replace('-', '_')
39312882Sspwilson2@wisc.edu
39412882Sspwilson2@wisc.edu    def add_to(self, parser):
39512882Sspwilson2@wisc.edu        '''Add this argument to the given parser.'''
39612882Sspwilson2@wisc.edu        parser.add_argument(*self.flags, **self.kwargs)
39712882Sspwilson2@wisc.edu
39812882Sspwilson2@wisc.edu    def copy(self):
39912882Sspwilson2@wisc.edu        '''Copy this argument so you might modify any of its kwargs.'''
40012882Sspwilson2@wisc.edu        return copy.deepcopy(self)
40112882Sspwilson2@wisc.edu
40212882Sspwilson2@wisc.edu
40312882Sspwilson2@wisc.educlass _StickyInt:
40412882Sspwilson2@wisc.edu    '''
40512882Sspwilson2@wisc.edu    A class that is used to cheat the verbosity count incrementer by
40612882Sspwilson2@wisc.edu    pretending to be an int. This makes the int stay on the heap and eat other
40712882Sspwilson2@wisc.edu    real numbers when they are added to it.
40812882Sspwilson2@wisc.edu
40912882Sspwilson2@wisc.edu    We use this so we can allow the verbose flag to be provided before or after
41012882Sspwilson2@wisc.edu    the subcommand. This likely has no utility outside of this use case.
41112882Sspwilson2@wisc.edu    '''
41212882Sspwilson2@wisc.edu    def __init__(self, val=0):
41312882Sspwilson2@wisc.edu        self.val = val
41412882Sspwilson2@wisc.edu        self.type = int
41512882Sspwilson2@wisc.edu    def __add__(self, other):
41612882Sspwilson2@wisc.edu        self.val += other
41712882Sspwilson2@wisc.edu        return self
41812882Sspwilson2@wisc.edu
41912882Sspwilson2@wisc.educommon_args = NotImplemented
42012882Sspwilson2@wisc.edu
42112882Sspwilson2@wisc.educlass StorePositionAction(argparse.Action):
42212882Sspwilson2@wisc.edu    '''Base class for classes wishing to create namespaces where
42312882Sspwilson2@wisc.edu    arguments are stored in the order provided via the command line.
42412882Sspwilson2@wisc.edu    '''
42512882Sspwilson2@wisc.edu    position_kword = 'positional'
42612882Sspwilson2@wisc.edu
42712882Sspwilson2@wisc.edu    def __call__(self, parser, namespace, values, option_string=None):
42812882Sspwilson2@wisc.edu        if not self.position_kword in namespace:
42912882Sspwilson2@wisc.edu            setattr(namespace, self.position_kword, [])
43012882Sspwilson2@wisc.edu        previous = getattr(namespace, self.position_kword)
43112882Sspwilson2@wisc.edu        previous.append((self.dest, values))
43212882Sspwilson2@wisc.edu        setattr(namespace, self.position_kword, previous)
43312882Sspwilson2@wisc.edu
43412882Sspwilson2@wisc.educlass StorePositionalTagsAction(StorePositionAction):
43512882Sspwilson2@wisc.edu    position_kword = 'tag_filters'
43612882Sspwilson2@wisc.edu
43712882Sspwilson2@wisc.edudef define_common_args(config):
43812882Sspwilson2@wisc.edu    '''
43912882Sspwilson2@wisc.edu    Common args are arguments which are likely to be simular between different
44012882Sspwilson2@wisc.edu    subcommands, so they are available to all by placing their definitions
44112882Sspwilson2@wisc.edu    here.
44212882Sspwilson2@wisc.edu    '''
44312882Sspwilson2@wisc.edu    global common_args
44412882Sspwilson2@wisc.edu
44512882Sspwilson2@wisc.edu    # A list of common arguments/flags used across cli parsers.
44612882Sspwilson2@wisc.edu    common_args = [
44712882Sspwilson2@wisc.edu        Argument(
44812882Sspwilson2@wisc.edu            'directory',
44912882Sspwilson2@wisc.edu            nargs='?',
45012882Sspwilson2@wisc.edu            default=os.getcwd(),
45112882Sspwilson2@wisc.edu            help='Directory to start searching for tests in'),
45212882Sspwilson2@wisc.edu        Argument(
45312882Sspwilson2@wisc.edu            '--exclude-tags',
45412882Sspwilson2@wisc.edu            action=StorePositionalTagsAction,
45512882Sspwilson2@wisc.edu            help='A tag comparison used to select tests.'),
45612882Sspwilson2@wisc.edu        Argument(
45712882Sspwilson2@wisc.edu            '--include-tags',
45812882Sspwilson2@wisc.edu            action=StorePositionalTagsAction,
45912882Sspwilson2@wisc.edu            help='A tag comparison used to select tests.'),
46012882Sspwilson2@wisc.edu        Argument(
46112882Sspwilson2@wisc.edu            '--isa',
46212882Sspwilson2@wisc.edu            action='append',
46312882Sspwilson2@wisc.edu            default=[],
46412882Sspwilson2@wisc.edu            help="Only tests that are valid with one of these ISAs. "
46512882Sspwilson2@wisc.edu                 "Comma separated."),
46612882Sspwilson2@wisc.edu        Argument(
46712882Sspwilson2@wisc.edu            '--variant',
46812882Sspwilson2@wisc.edu            action='append',
46912882Sspwilson2@wisc.edu            default=[],
47012882Sspwilson2@wisc.edu            help="Only tests that are valid with one of these binary variants"
47112882Sspwilson2@wisc.edu                 "(e.g., opt, debug). Comma separated."),
47212882Sspwilson2@wisc.edu        Argument(
47312882Sspwilson2@wisc.edu            '--length',
47412882Sspwilson2@wisc.edu            action='append',
47512882Sspwilson2@wisc.edu            default=[],
47612882Sspwilson2@wisc.edu            help="Only tests that are one of these lengths. Comma separated."),
47712882Sspwilson2@wisc.edu        Argument(
47812882Sspwilson2@wisc.edu            '--uid',
47912882Sspwilson2@wisc.edu            action='store',
48012882Sspwilson2@wisc.edu            default=None,
48112882Sspwilson2@wisc.edu            help='UID of a specific test item to run.'),
48212882Sspwilson2@wisc.edu        Argument(
48312882Sspwilson2@wisc.edu            '--build-dir',
48412882Sspwilson2@wisc.edu            action='store',
48512882Sspwilson2@wisc.edu            help='Build directory for SCons'),
48612882Sspwilson2@wisc.edu        Argument(
48712882Sspwilson2@wisc.edu            '--base-dir',
48812882Sspwilson2@wisc.edu            action='store',
48912882Sspwilson2@wisc.edu            default=config._defaults.base_dir,
49012882Sspwilson2@wisc.edu            help='Directory to change to in order to exec scons.'),
49112882Sspwilson2@wisc.edu        Argument(
49212882Sspwilson2@wisc.edu            '-j', '--threads',
49312882Sspwilson2@wisc.edu            action='store',
49412882Sspwilson2@wisc.edu            default=1,
49512882Sspwilson2@wisc.edu            help='Number of threads to run SCons with.'),
49612882Sspwilson2@wisc.edu        Argument(
49712882Sspwilson2@wisc.edu            '-t', '--test-threads',
49812882Sspwilson2@wisc.edu            action='store',
49912882Sspwilson2@wisc.edu            default=1,
50012882Sspwilson2@wisc.edu            help='Number of threads to spawn to run concurrent tests with.'),
50112882Sspwilson2@wisc.edu        Argument(
50212882Sspwilson2@wisc.edu            '-v',
50312882Sspwilson2@wisc.edu            action='count',
50412882Sspwilson2@wisc.edu            dest='verbose',
50512882Sspwilson2@wisc.edu            default=_StickyInt(),
50612882Sspwilson2@wisc.edu            help='Increase verbosity'),
50712882Sspwilson2@wisc.edu        Argument(
50812882Sspwilson2@wisc.edu            '--config-path',
50912882Sspwilson2@wisc.edu            action='store',
51012882Sspwilson2@wisc.edu            default=os.getcwd(),
51112882Sspwilson2@wisc.edu            help='Path to read a testing.ini config in'
51212882Sspwilson2@wisc.edu        ),
51312882Sspwilson2@wisc.edu        Argument(
51412882Sspwilson2@wisc.edu            '--skip-build',
51512882Sspwilson2@wisc.edu            action='store_true',
51612882Sspwilson2@wisc.edu            default=False,
51712882Sspwilson2@wisc.edu            help='Skip the building component of SCons targets.'
51812882Sspwilson2@wisc.edu        ),
51912882Sspwilson2@wisc.edu        Argument(
52012882Sspwilson2@wisc.edu            '--result-path',
52112882Sspwilson2@wisc.edu            action='store',
52212882Sspwilson2@wisc.edu            help='The path to store results in.'
52312882Sspwilson2@wisc.edu        ),
52412882Sspwilson2@wisc.edu    ]
52512882Sspwilson2@wisc.edu
52612882Sspwilson2@wisc.edu    # NOTE: There is a limitation which arises due to this format. If you have
52712882Sspwilson2@wisc.edu    # multiple arguments with the same name only the final one in the list
52812882Sspwilson2@wisc.edu    # will be saved.
52912882Sspwilson2@wisc.edu    #
53012882Sspwilson2@wisc.edu    # e.g. if you have a -v argument which increments verbosity level and
53112882Sspwilson2@wisc.edu    # a separate --verbose flag which 'store's verbosity level. the final
53212882Sspwilson2@wisc.edu    # one in the list will be saved.
53312882Sspwilson2@wisc.edu    common_args = AttrDict({arg.name:arg for arg in common_args})
53412882Sspwilson2@wisc.edu
53512882Sspwilson2@wisc.edu
53612882Sspwilson2@wisc.educlass ArgParser(object):
53712882Sspwilson2@wisc.edu    __metaclass__ = abc.ABCMeta
53812882Sspwilson2@wisc.edu
53912882Sspwilson2@wisc.edu    def __init__(self, parser):
54012882Sspwilson2@wisc.edu        # Copy public methods of the parser.
54112882Sspwilson2@wisc.edu        for attr in dir(parser):
54212882Sspwilson2@wisc.edu            if not attr.startswith('_'):
54312882Sspwilson2@wisc.edu                setattr(self, attr, getattr(parser, attr))
54412882Sspwilson2@wisc.edu        self.parser = parser
54512882Sspwilson2@wisc.edu        self.add_argument = self.parser.add_argument
54612882Sspwilson2@wisc.edu
54712882Sspwilson2@wisc.edu        # Argument will be added to all parsers and subparsers.
54812882Sspwilson2@wisc.edu        common_args.verbose.add_to(parser)
54912882Sspwilson2@wisc.edu
55012882Sspwilson2@wisc.edu
55112882Sspwilson2@wisc.educlass CommandParser(ArgParser):
55212882Sspwilson2@wisc.edu    '''
55312882Sspwilson2@wisc.edu    Main parser which parses command strings and uses those to direct to
55412882Sspwilson2@wisc.edu    a subparser.
55512882Sspwilson2@wisc.edu    '''
55612882Sspwilson2@wisc.edu    def __init__(self):
55712882Sspwilson2@wisc.edu        parser = argparse.ArgumentParser()
55812882Sspwilson2@wisc.edu        super(CommandParser, self).__init__(parser)
55912882Sspwilson2@wisc.edu        self.subparser = self.add_subparsers(dest='command')
56012882Sspwilson2@wisc.edu
56112882Sspwilson2@wisc.edu
56212882Sspwilson2@wisc.educlass RunParser(ArgParser):
56312882Sspwilson2@wisc.edu    '''
56412882Sspwilson2@wisc.edu    Parser for the \'run\' command.
56512882Sspwilson2@wisc.edu    '''
56612882Sspwilson2@wisc.edu    def __init__(self, subparser):
56712882Sspwilson2@wisc.edu        parser = subparser.add_parser(
56812882Sspwilson2@wisc.edu            'run',
56912882Sspwilson2@wisc.edu            help='''Run Tests.'''
57012882Sspwilson2@wisc.edu        )
57112882Sspwilson2@wisc.edu
57212882Sspwilson2@wisc.edu        super(RunParser, self).__init__(parser)
57312882Sspwilson2@wisc.edu
57412882Sspwilson2@wisc.edu        common_args.uid.add_to(parser)
57512882Sspwilson2@wisc.edu        common_args.skip_build.add_to(parser)
57612882Sspwilson2@wisc.edu        common_args.directory.add_to(parser)
57712882Sspwilson2@wisc.edu        common_args.build_dir.add_to(parser)
57812882Sspwilson2@wisc.edu        common_args.base_dir.add_to(parser)
57912882Sspwilson2@wisc.edu        common_args.threads.add_to(parser)
58012882Sspwilson2@wisc.edu        common_args.test_threads.add_to(parser)
58112882Sspwilson2@wisc.edu        common_args.isa.add_to(parser)
58212882Sspwilson2@wisc.edu        common_args.variant.add_to(parser)
58312882Sspwilson2@wisc.edu        common_args.length.add_to(parser)
58412882Sspwilson2@wisc.edu        common_args.include_tags.add_to(parser)
58512882Sspwilson2@wisc.edu        common_args.exclude_tags.add_to(parser)
58612882Sspwilson2@wisc.edu
58712882Sspwilson2@wisc.edu
58812882Sspwilson2@wisc.educlass ListParser(ArgParser):
58912882Sspwilson2@wisc.edu    '''
59012882Sspwilson2@wisc.edu    Parser for the \'list\' command.
59112882Sspwilson2@wisc.edu    '''
59212882Sspwilson2@wisc.edu    def __init__(self, subparser):
59312882Sspwilson2@wisc.edu        parser = subparser.add_parser(
59412882Sspwilson2@wisc.edu            'list',
59512882Sspwilson2@wisc.edu            help='''List and query test metadata.'''
59612882Sspwilson2@wisc.edu        )
59712882Sspwilson2@wisc.edu        super(ListParser, self).__init__(parser)
59812882Sspwilson2@wisc.edu
59912882Sspwilson2@wisc.edu        Argument(
60012882Sspwilson2@wisc.edu            '--suites',
60112882Sspwilson2@wisc.edu            action='store_true',
60212882Sspwilson2@wisc.edu            default=False,
60312882Sspwilson2@wisc.edu            help='List all test suites.'
60412882Sspwilson2@wisc.edu        ).add_to(parser)
60512882Sspwilson2@wisc.edu        Argument(
60612882Sspwilson2@wisc.edu            '--tests',
60712882Sspwilson2@wisc.edu            action='store_true',
60812882Sspwilson2@wisc.edu            default=False,
60912882Sspwilson2@wisc.edu            help='List all test cases.'
61012882Sspwilson2@wisc.edu        ).add_to(parser)
61112882Sspwilson2@wisc.edu        Argument(
61212882Sspwilson2@wisc.edu            '--fixtures',
61312882Sspwilson2@wisc.edu            action='store_true',
61412882Sspwilson2@wisc.edu            default=False,
61512882Sspwilson2@wisc.edu            help='List all fixtures.'
61612882Sspwilson2@wisc.edu        ).add_to(parser)
61712882Sspwilson2@wisc.edu        Argument(
61812882Sspwilson2@wisc.edu            '--all-tags',
61912882Sspwilson2@wisc.edu            action='store_true',
62012882Sspwilson2@wisc.edu            default=False,
62112882Sspwilson2@wisc.edu            help='List all tags.'
62212882Sspwilson2@wisc.edu        ).add_to(parser)
62312882Sspwilson2@wisc.edu        Argument(
62412882Sspwilson2@wisc.edu            '-q',
62512882Sspwilson2@wisc.edu            dest='quiet',
62612882Sspwilson2@wisc.edu            action='store_true',
62712882Sspwilson2@wisc.edu            default=False,
62812882Sspwilson2@wisc.edu            help='Quiet output (machine readable).'
62912882Sspwilson2@wisc.edu        ).add_to(parser)
63012882Sspwilson2@wisc.edu
63112882Sspwilson2@wisc.edu        common_args.directory.add_to(parser)
63212882Sspwilson2@wisc.edu        common_args.isa.add_to(parser)
63312882Sspwilson2@wisc.edu        common_args.variant.add_to(parser)
63412882Sspwilson2@wisc.edu        common_args.length.add_to(parser)
63512882Sspwilson2@wisc.edu        common_args.include_tags.add_to(parser)
63612882Sspwilson2@wisc.edu        common_args.exclude_tags.add_to(parser)
63712882Sspwilson2@wisc.edu
63812882Sspwilson2@wisc.edu
63912882Sspwilson2@wisc.educlass RerunParser(ArgParser):
64012882Sspwilson2@wisc.edu    def __init__(self, subparser):
64112882Sspwilson2@wisc.edu        parser = subparser.add_parser(
64212882Sspwilson2@wisc.edu            'rerun',
64312882Sspwilson2@wisc.edu            help='''Rerun failed tests.'''
64412882Sspwilson2@wisc.edu        )
64512882Sspwilson2@wisc.edu        super(RerunParser, self).__init__(parser)
64612882Sspwilson2@wisc.edu
64712882Sspwilson2@wisc.edu        common_args.skip_build.add_to(parser)
64812882Sspwilson2@wisc.edu        common_args.directory.add_to(parser)
64912882Sspwilson2@wisc.edu        common_args.build_dir.add_to(parser)
65012882Sspwilson2@wisc.edu        common_args.base_dir.add_to(parser)
65112882Sspwilson2@wisc.edu        common_args.threads.add_to(parser)
65212882Sspwilson2@wisc.edu        common_args.test_threads.add_to(parser)
65312882Sspwilson2@wisc.edu        common_args.isa.add_to(parser)
65412882Sspwilson2@wisc.edu        common_args.variant.add_to(parser)
65512882Sspwilson2@wisc.edu        common_args.length.add_to(parser)
65612882Sspwilson2@wisc.edu
65712882Sspwilson2@wisc.educonfig = _Config()
65812882Sspwilson2@wisc.edudefine_constants(config.constants)
65912882Sspwilson2@wisc.edu
66012882Sspwilson2@wisc.edu# Constants are directly exposed and available once this module is created.
66112882Sspwilson2@wisc.edu# All constants MUST be defined before this point.
66212882Sspwilson2@wisc.educonfig.constants = FrozenAttrDict(config.constants.__dict__)
66312882Sspwilson2@wisc.educonstants = config.constants
66412882Sspwilson2@wisc.edu
66512882Sspwilson2@wisc.edu'''
66612882Sspwilson2@wisc.eduThis config object is the singleton config object available throughout the
66712882Sspwilson2@wisc.eduframework.
66812882Sspwilson2@wisc.edu'''
66912882Sspwilson2@wisc.edudef initialize_config():
67012882Sspwilson2@wisc.edu    '''
67112882Sspwilson2@wisc.edu    Parse the commandline arguments and setup the config varibles.
67212882Sspwilson2@wisc.edu    '''
67312882Sspwilson2@wisc.edu    global config
67412882Sspwilson2@wisc.edu
67512882Sspwilson2@wisc.edu    # Setup constants and defaults
67612882Sspwilson2@wisc.edu    define_defaults(config._defaults)
67712882Sspwilson2@wisc.edu    define_post_processors(config)
67812882Sspwilson2@wisc.edu    define_common_args(config)
67912882Sspwilson2@wisc.edu
68012882Sspwilson2@wisc.edu    # Setup parser and subcommands
68112882Sspwilson2@wisc.edu    baseparser = CommandParser()
68212882Sspwilson2@wisc.edu    runparser = RunParser(baseparser.subparser)
68312882Sspwilson2@wisc.edu    listparser = ListParser(baseparser.subparser)
68412882Sspwilson2@wisc.edu    rerunparser = RerunParser(baseparser.subparser)
68512882Sspwilson2@wisc.edu
68612882Sspwilson2@wisc.edu    # Initialize the config by parsing args and running callbacks.
68712882Sspwilson2@wisc.edu    config._init(baseparser)
688