tests,python: Upgrading testlib to function with Python2
authorBobby R. Bruce <bbruce@ucdavis.edu>
Thu, 16 Apr 2020 18:55:17 +0000 (11:55 -0700)
committerBobby R. Bruce <bbruce@ucdavis.edu>
Sun, 24 May 2020 23:29:52 +0000 (23:29 +0000)
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>
33 files changed:
ext/testlib/__init__.py
ext/testlib/config.py [deleted file]
ext/testlib/configuration.py [new file with mode: 0644]
ext/testlib/fixture.py
ext/testlib/handlers.py
ext/testlib/helper.py
ext/testlib/loader.py
ext/testlib/log.py
ext/testlib/main.py
ext/testlib/query.py
ext/testlib/result.py
ext/testlib/runner.py
ext/testlib/sandbox.py
ext/testlib/suite.py
ext/testlib/terminal.py
ext/testlib/test.py [deleted file]
ext/testlib/test_util.py [new file with mode: 0644]
ext/testlib/uid.py
ext/testlib/wrappers.py
tests/configs/base_config.py
tests/gem5/__init__.py
tests/gem5/cpu_tests/test.py
tests/gem5/fixture.py
tests/gem5/memory/test.py
tests/gem5/suite.py
tests/gem5/verifier.py
tests/main.py
tests/testing/__init__.py
tests/testing/helpers.py
tests/testing/results.py
tests/testing/tests.py
tests/testing/units.py
tests/tests.py

index 893da5433a8f91ee0600119c19dbdc597cef4af4..898205d9ab89d33aefe326887269eef3c222547f 100644 (file)
 
 from .state import *
 from .runner import *
-from .test import *
+from .test_util import *
 from .suite import *
 from .loader import *
 from .fixture import *
-from .config import *
-from main import main
+from .configuration import *
+from .main import main
 
 #TODO Remove this awkward bootstrap
 #FIXME
diff --git a/ext/testlib/config.py b/ext/testlib/config.py
deleted file mode 100644 (file)
index 189f7c1..0000000
+++ /dev/null
@@ -1,759 +0,0 @@
-# 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 ConfigParser import ConfigParser
-from pickle import HIGHEST_PROTOCOL as highest_pickle_protocol
-
-from 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})
-
-
-class ArgParser(object):
-    __metaclass__ = abc.ABCMeta
-
-    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)
diff --git a/ext/testlib/configuration.py b/ext/testlib/configuration.py
new file mode 100644 (file)
index 0000000..04744e1
--- /dev/null
@@ -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)
index 7af6cb289f2752c5eec2c98d2ec59202a5935069..20568b80f2277a41eaa42388ac1f0a18c418481c 100644 (file)
@@ -29,8 +29,8 @@
 import copy
 import traceback
 
-import helper
-import log
+import testlib.helper as helper
+import testlib.log as log
 
 class SkipException(Exception):
     def __init__(self, fixture, testitem):
@@ -61,7 +61,7 @@ class Fixture(object):
     collector = helper.InstanceCollector()
 
     def __new__(klass, *args, **kwargs):
-        obj = super(Fixture, klass).__new__(klass, *args, **kwargs)
+        obj = super(Fixture, klass).__new__(klass)
         Fixture.collector.collect(obj)
         return obj
 
index 6f76940712ca8f73abfe55b079cff0f517d66540..3005e01efb282006ed42cb6fd15e953ca7aedcd1 100644 (file)
@@ -35,20 +35,20 @@ from __future__ import print_function
 
 import multiprocessing
 import os
-import Queue
 import sys
 import threading
 import time
 import traceback
 
-import helper
-import log
-import result
-import state
-import test
-import terminal
+import testlib.helper as helper
+import testlib.log as log
+import testlib.result as result
+import testlib.state as state
+import testlib.test_util as test
+import testlib.terminal as terminal
 
-from config import config, constants
+from six.moves import queue as Queue
+from testlib.configuration import config, constants
 
 
 class _TestStreamManager(object):
index ac49e468ebb06df4302cdd7696e0a91d529d1f19..4237a765e1211279f79ef26601fdf2b7b8d4fe5b 100644 (file)
@@ -34,7 +34,6 @@ from collections import MutableSet, OrderedDict
 import difflib
 import errno
 import os
-import Queue
 import re
 import shutil
 import stat
@@ -44,6 +43,8 @@ import threading
 import time
 import traceback
 
+from six.moves import queue as Queue
+
 #TODO Tear out duplicate logic from the sandbox IOManager
 def log_call(logger, command, *popenargs, **kwargs):
     '''
@@ -80,7 +81,8 @@ def log_call(logger, command, *popenargs, **kwargs):
 
     def log_output(log_callback, pipe, redirects=tuple()):
         # Read iteractively, don't allow input to fill the pipe.
-        for line in iter(pipe.readline, ''):
+        for line in iter(pipe.readline, b''):
+            line = line.decode("utf-8")
             for r in redirects:
                 r.write(line)
             log_callback(line.rstrip())
index 8f8f60e707fc578fb58a0d47dc975fec686312ab..bb2fe4ef929e173e93ea4282e480050ae73216a3 100644 (file)
@@ -67,16 +67,17 @@ a :class:`TestSuite` named after the module.
 
 import os
 import re
+import six
 import sys
 import traceback
 
-import config
-import log
-import suite as suite_mod
-import test as test_mod
-import fixture as fixture_mod
-import wrappers
-import uid
+import testlib.configuration as configuration
+import testlib.log as log
+import testlib.suite as suite_mod
+import testlib.test_util as test_mod
+import testlib.fixture as fixture_mod
+import testlib.wrappers as wrappers
+import testlib.uid as uid
 
 class DuplicateTestItemException(Exception):
     '''
@@ -112,7 +113,7 @@ def _assert_files_in_same_dir(files):
         if files:
             directory = os.path.dirname(files[0])
             for f in files:
-                assert os.path.dirname(f) == directory
+                assert(os.path.dirname(f) == directory)
 
 class Loader(object):
     '''
@@ -186,6 +187,7 @@ class Loader(object):
             self._loaded_a_file = True
 
         for directory in self._discover_files(root):
+            directory = list(directory)
             if directory:
                 _assert_files_in_same_dir(directory)
                 for f in directory:
@@ -193,6 +195,7 @@ class Loader(object):
 
     def load_dir(self, directory):
         for dir_ in self._discover_files(directory):
+            directory = list(directory)
             _assert_files_in_same_dir(dir_)
             for f in dir_:
                 self.load_file(f)
@@ -221,29 +224,27 @@ class Loader(object):
         sys.path.insert(0, os.path.dirname(path))
         cwd = os.getcwd()
         os.chdir(os.path.dirname(path))
-        config.config.file_under_load = path
+        configuration.config.file_under_load = path
 
         new_tests = test_mod.TestCase.collector.create()
         new_suites = suite_mod.TestSuite.collector.create()
         new_fixtures = fixture_mod.Fixture.collector.create()
 
-        def cleanup():
-            config.config.file_under_load = None
-            sys.path[:] = old_path
-            os.chdir(cwd)
-            test_mod.TestCase.collector.remove(new_tests)
-            suite_mod.TestSuite.collector.remove(new_suites)
-            fixture_mod.Fixture.collector.remove(new_fixtures)
-
         try:
-            execfile(path, newdict, newdict)
+            exec(open(path).read(), newdict, newdict)
         except Exception as e:
             log.test_log.debug(traceback.format_exc())
             log.test_log.warn(
                               'Exception thrown while loading "%s"\n'
                               'Ignoring all tests in this file.'
                                % (path))
-            cleanup()
+            # Clean up
+            configuration.config.file_under_load = None
+            sys.path[:] = old_path
+            os.chdir(cwd)
+            test_mod.TestCase.collector.remove(new_tests)
+            suite_mod.TestSuite.collector.remove(new_suites)
+            fixture_mod.Fixture.collector.remove(new_fixtures)
             return
 
         # Create a module test suite for those not contained in a suite.
@@ -281,7 +282,13 @@ class Loader(object):
             self.suites.extend(loaded_suites)
             self.suite_uids.update({suite.uid: suite
                     for suite in loaded_suites})
-        cleanup()
+        # Clean up
+        configuration.config.file_under_load = None
+        sys.path[:] = old_path
+        os.chdir(cwd)
+        test_mod.TestCase.collector.remove(new_tests)
+        suite_mod.TestSuite.collector.remove(new_suites)
+        fixture_mod.Fixture.collector.remove(new_fixtures)
 
     def _discover_files(self, root):
         '''
index 5ba6f5d4f35cba40457a304f72435a8538ff7599..cddb9217c681f3b8b1170e24fd6e07062604fea8 100644 (file)
@@ -30,8 +30,9 @@
 This module supplies the global `test_log` object which all testing
 results and messages are reported through.
 '''
-import wrappers
+import testlib.wrappers as wrappers
 
+from six import add_metaclass
 
 class LogLevel():
     Fatal = 0
@@ -55,6 +56,7 @@ class RecordTypeCounterMetaclass(type):
         RecordTypeCounterMetaclass.counter += 1
 
 
+@add_metaclass(RecordTypeCounterMetaclass)
 class Record(object):
     '''
     A generic object that is passed to the :class:`Log` and its handlers.
@@ -62,7 +64,6 @@ class Record(object):
     ..note: Although not statically enforced, all items in the record should be
         be pickleable. This enables logging accross multiple processes.
     '''
-    __metaclass__ = RecordTypeCounterMetaclass
 
     def __init__(self, **data):
         self.data = data
index 3827f7815f2f48dbd76a09eb31c3a2b17fe5816a..9c9ed03dd1ad9ea85a857e0198cdda170ef0d2ae 100644 (file)
 import os
 import itertools
 
-import config
-import fixture as fixture_mod
-import handlers
-import loader as loader_mod
-import log
-import query
-import result
-import runner
-import terminal
-import uid
+import testlib.configuration as configuration
+import testlib.fixture as fixture_mod
+import testlib.handlers as handlers
+import testlib.loader as loader_mod
+import testlib.log as log
+import testlib.query as query
+import testlib.result as result
+import testlib.runner as runner
+import testlib.terminal as terminal
+import testlib.uid as uid
 
 def entry_message():
     log.test_log.message("Running the new gem5 testing script.")
@@ -50,7 +50,7 @@ def entry_message():
 class RunLogHandler():
     def __init__(self):
         term_handler = handlers.TerminalHandler(
-            verbosity=config.config.verbose+log.LogLevel.Info
+            verbosity=configuration.config.verbose+log.LogLevel.Info
         )
         summary_handler = handlers.SummaryHandler()
         self.mp_handler = handlers.MultiprocessingHandlerWrapper(
@@ -62,7 +62,7 @@ class RunLogHandler():
     def schedule_finalized(self, test_schedule):
         # Create the result handler object.
         self.result_handler = handlers.ResultHandler(
-                test_schedule, config.config.result_path)
+                test_schedule, configuration.config.result_path)
         self.mp_handler.add_handler(self.result_handler)
 
     def finish_testing(self):
@@ -87,27 +87,27 @@ class RunLogHandler():
         return self.result_handler.unsuccessful()
 
 def get_config_tags():
-    return getattr(config.config,
-            config.StorePositionalTagsAction.position_kword)
+    return getattr(configuration.config,
+            configuration.StorePositionalTagsAction.position_kword)
 
 def filter_with_config_tags(loaded_library):
     tags = get_config_tags()
     final_tags = []
     regex_fmt = '^%s$'
-    cfg = config.config
+    cfg = configuration.config
 
     def _append_inc_tag_filter(name):
         if hasattr(cfg, name):
             tag_opts = getattr(cfg, name)
             for tag in tag_opts:
-                final_tags.append(config.TagRegex(True, regex_fmt % tag))
+                final_tags.append(configuration.TagRegex(True, regex_fmt % tag))
 
     def _append_rem_tag_filter(name):
         if hasattr(cfg, name):
             tag_opts = getattr(cfg, name)
             for tag in cfg.constants.supported_tags[name]:
                 if tag not in tag_opts:
-                    final_tags.append(config.TagRegex(False, regex_fmt % tag))
+                    final_tags.append(configuration.TagRegex(False, regex_fmt % tag))
 
     # Append additional tags for the isa, length, and variant options.
     # They apply last (they take priority)
@@ -206,13 +206,13 @@ def load_tests():
     testloader = loader_mod.Loader()
     log.test_log.message(terminal.separator())
     log.test_log.message('Loading Tests', bold=True)
-    testloader.load_root(config.config.directory)
+    testloader.load_root(configuration.config.directory)
     return testloader
 
 def do_list():
     term_handler = handlers.TerminalHandler(
-        verbosity=config.config.verbose+log.LogLevel.Info,
-        machine_only=config.config.quiet
+        verbosity=configuration.config.verbose+log.LogLevel.Info,
+        machine_only=configuration.config.quiet
     )
     log.test_log.log_obj.add_handler(term_handler)
 
@@ -223,11 +223,11 @@ def do_list():
 
     qrunner = query.QueryRunner(test_schedule)
 
-    if config.config.suites:
+    if configuration.config.suites:
         qrunner.list_suites()
-    elif config.config.tests:
+    elif configuration.config.tests:
         qrunner.list_tests()
-    elif config.config.all_tags:
+    elif configuration.config.all_tags:
         qrunner.list_tags()
     else:
         qrunner.list_suites()
@@ -259,13 +259,13 @@ def run_schedule(test_schedule, log_handler):
     log.test_log.message('Running Tests from {} suites'
             .format(len(test_schedule.suites)), bold=True)
     log.test_log.message("Results will be stored in {}".format(
-                config.config.result_path))
+                configuration.config.result_path))
     log.test_log.message(terminal.separator())
 
     # Build global fixtures and exectute scheduled test suites.
-    if config.config.test_threads > 1:
+    if configuration.config.test_threads > 1:
         library_runner = runner.LibraryParallelRunner(test_schedule)
-        library_runner.set_threads(config.config.test_threads)
+        library_runner.set_threads(configuration.config.test_threads)
     else:
         library_runner = runner.LibraryRunner(test_schedule)
     library_runner.run()
@@ -279,8 +279,8 @@ def run_schedule(test_schedule, log_handler):
 def do_run():
     # Initialize early parts of the log.
     with RunLogHandler() as log_handler:
-        if config.config.uid:
-            uid_ = uid.UID.from_uid(config.config.uid)
+        if configuration.config.uid:
+            uid_ = uid.UID.from_uid(configuration.config.uid)
             if isinstance(uid_, uid.TestUID):
                 log.test_log.error('Unable to run a standalone test.\n'
                         'Gem5 expects test suites to be the smallest unit '
@@ -305,8 +305,8 @@ def do_rerun():
     with RunLogHandler() as log_handler:
         # Load previous results
         results = result.InternalSavedResults.load(
-                os.path.join(config.config.result_path,
-                config.constants.pickle_filename))
+                os.path.join(configuration.config.result_path,
+                configuration.constants.pickle_filename))
 
         rerun_suites = (suite.uid for suite in results if suite.unsuccessful)
 
@@ -323,10 +323,10 @@ def main():
     Returns 0 on success and 1 otherwise so it can be used as a return code
     for scripts.
     '''
-    config.initialize_config()
+    configuration.initialize_config()
 
     # 'do' the given command.
-    result = globals()['do_'+config.config.command]()
+    result = globals()['do_'+configuration.config.command]()
     log.test_log.close()
 
     return result
index c66445c4461881642c99d54f97b06428c3b5db4e..174af626fedac89ce4405dedbcaf6673b6d5e12c 100644 (file)
@@ -26,8 +26,8 @@
 #
 # Authors: Sean Wilson
 
-import terminal
-import log
+import testlib.terminal as terminal
+import testlib.log as log
 
 # TODO Refactor print logic out of this so the objects
 # created are separate from print logic.
index 786c21b609168eb9137a1e5adc1a64e32ef8f8cc..38b3322ba180f2f2a71959e9377c871927ef0e1b 100644 (file)
@@ -30,10 +30,10 @@ import os
 import pickle
 import xml.sax.saxutils
 
-from config import config
-import helper
-import state
-import log
+from testlib.configuration import config
+import testlib.helper as helper
+import testlib.state as state
+import testlib.log as log
 
 def _create_uid_index(iterable):
     index = {}
@@ -62,7 +62,7 @@ class _CommonMetadataMixin:
         return self._metadata.result.value != state.Result.Passed
 
 
-class InternalTestResult(object, _CommonMetadataMixin):
+class InternalTestResult(_CommonMetadataMixin):
     def __init__(self, obj, suite, directory):
         self._metadata = obj.metadata
         self.suite = suite
@@ -77,7 +77,7 @@ class InternalTestResult(object, _CommonMetadataMixin):
         )
 
 
-class InternalSuiteResult(object, _CommonMetadataMixin):
+class InternalSuiteResult(_CommonMetadataMixin):
     def __init__(self, obj, directory):
         self._metadata = obj.metadata
         self.directory = directory
@@ -104,7 +104,7 @@ class InternalSuiteResult(object, _CommonMetadataMixin):
         return results
 
 
-class InternalLibraryResults(object, _CommonMetadataMixin):
+class InternalLibraryResults(_CommonMetadataMixin):
     def __init__(self, obj, directory):
         self.directory = directory
         self._metadata = obj.metadata
@@ -159,12 +159,12 @@ class InternalSavedResults:
                if exc.errno != errno.EEXIST:
                    raise
 
-        with open(path, 'w') as f:
+        with open(path, 'wb') as f:
             pickle.dump(results, f, protocol)
 
     @staticmethod
     def load(path):
-        with open(path, 'r') as f:
+        with open(path, 'rb') as f:
             return pickle.load(f)
 
 
index 9868cefb11ab1941df618dbe8834cc3f7d593504..ec3c838e7da3d1f508f1f4d0c246b86304370cf7 100644 (file)
@@ -30,13 +30,13 @@ import multiprocessing.dummy
 import threading
 import traceback
 
-import helper
-import state
-import log
-import sandbox
+import testlib.helper as helper
+import testlib.state as state
+import testlib.log as log
+import testlib.sandbox as sandbox
 
-from state import Status, Result
-from fixture import SkipException
+from testlib.state import Status, Result
+from testlib.fixture import SkipException
 
 def compute_aggregate_result(iterable):
     '''
index 49fe133eabc57eeb2b4846961d507584ac4a9189..bdc6d88595546884f1f117f358f58c3f6f6ebb88 100644 (file)
@@ -33,7 +33,7 @@ import sys
 import threading
 import traceback
 
-import log
+import testlib.log as log
 
 pdb._Pdb = pdb.Pdb
 class ForkedPdb(pdb._Pdb):
@@ -81,18 +81,18 @@ class IoManager(object):
         self.old_stdout = os.dup(sys.stdout.fileno())
 
         os.dup2(self.stderr_wp, sys.stderr.fileno())
-        sys.stderr = os.fdopen(self.stderr_wp, 'w', 0)
+        sys.stderr = os.fdopen(self.stderr_wp, 'w')
         os.dup2(self.stdout_wp, sys.stdout.fileno())
-        sys.stdout = os.fdopen(self.stdout_wp, 'w', 0)
+        sys.stdout = os.fdopen(self.stdout_wp, 'w')
 
     def restore_pipes(self):
         self.stderr_wp = os.dup(sys.stderr.fileno())
         self.stdout_wp = os.dup(sys.stdout.fileno())
 
         os.dup2(self.old_stderr, sys.stderr.fileno())
-        sys.stderr = os.fdopen(self.old_stderr, 'w', 0)
+        sys.stderr = open(self.old_stderr, 'w')
         os.dup2(self.old_stdout, sys.stdout.fileno())
-        sys.stdout = os.fdopen(self.old_stdout, 'w', 0)
+        sys.stdout = open(self.old_stdout, 'w')
 
     def start_loggers(self):
         self.log_ouput()
index 2ce817a6e849abb570f9ff5f54594335a171ed3d..eae52fd922511e8f5d557b379736886a49dad96e 100644 (file)
@@ -27,8 +27,8 @@
 # Authors: Sean Wilson
 
 
-import helper
-import runner as runner_mod
+import testlib.helper as helper
+import testlib.runner as runner_mod
 
 class TestSuite(object):
     '''
@@ -52,7 +52,7 @@ class TestSuite(object):
     tags = set()
 
     def __new__(klass, *args, **kwargs):
-        obj = super(TestSuite, klass).__new__(klass, *args, **kwargs)
+        obj = super(TestSuite, klass).__new__(klass)
         TestSuite.collector.collect(obj)
         return obj
 
index bdb20edea47228dc39b45dfe76eb09a82262a6f9..bc4c85599db56d8863b7f5252601338d6035d621 100644 (file)
@@ -28,6 +28,7 @@ import sys
 import fcntl
 import termios
 import struct
+import six
 
 # Intended usage example:
 #
@@ -74,7 +75,7 @@ try:
     def cap_string(s, *args):
         cap = curses.tigetstr(s)
         if cap:
-            return curses.tparm(cap, *args)
+            return curses.tparm(cap, *args).decode("utf-8")
         else:
             return ''
 except:
@@ -84,7 +85,7 @@ class ColorStrings(object):
     def __init__(self, cap_string):
         for i, c in enumerate(color_names):
             setattr(self, c, cap_string('setaf', i))
-        for name, cap in capability_map.iteritems():
+        for name, cap in six.iteritems(capability_map):
             setattr(self, name, cap_string(cap))
 
 termcap = ColorStrings(cap_string)
@@ -137,7 +138,7 @@ def insert_separator(inside, char=default_separator,
     .. seealso:: :func:`separator`
     '''
     # Use a bytearray so it's efficient to manipulate
-    string = bytearray(separator(char, color=color))
+    string = bytearray(separator(char, color=color), 'utf-8')
 
     # Check if we can fit inside with at least min_barrier.
     gap = (len(string) - len(inside)) - min_barrier * 2
@@ -145,27 +146,27 @@ def insert_separator(inside, char=default_separator,
         # We'll need to expand the string to fit us.
         string.extend([ char for _ in range(-gap)])
     # Emplace inside
-    middle = ((len(string)-1)/2)
-    start_idx = middle - len(inside)/2
-    string[start_idx:len(inside)+start_idx] = inside
-    return str(string)
+    middle = (len(string)-1)//2
+    start_idx = middle - len(inside)//2
+    string[start_idx:len(inside)+start_idx] = str.encode(inside)
+    return str(string.decode("utf-8"))
 
 
 if __name__ == '__main__':
     def test_termcap(obj):
         for c_name in color_names:
             c_str = getattr(obj, c_name)
-            print c_str + c_name + obj.Normal
+            print(c_str + c_name + obj.Normal)
             for attr_name in capability_names:
                 if attr_name == 'Normal':
                     continue
                 attr_str = getattr(obj, attr_name)
-                print attr_str + c_str + attr_name + " " + c_name + obj.Normal
-            print obj.Bold + obj.Underline + \
-                  c_name + "Bold Underline " + c_str + obj.Normal
+                print(attr_str + c_str + attr_name + " " + c_name + obj.Normal)
+            print(obj.Bold + obj.Underline + \
+                  c_name + "Bold Underline " + c_str + obj.Normal)
 
-    print "=== termcap enabled ==="
+    print("=== termcap enabled ===")
     test_termcap(termcap)
-    print termcap.Normal
-    print "=== termcap disabled ==="
+    print(termcap.Normal)
+    print("=== termcap disabled ===")
     test_termcap(no_termcap)
diff --git a/ext/testlib/test.py b/ext/testlib/test.py
deleted file mode 100644 (file)
index 18899d6..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-# 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
-
-import functools
-
-import helper
-import runner as runner_mod
-
-class TestingException(Exception):
-    '''Common ancestor for manual Testing Exceptions.'''
-class TestFailException(TestingException):
-    '''Signals that a test has failed.'''
-class TestSkipException(TestingException):
-    '''Signals that a test has been skipped.'''
-
-def fail(message):
-    '''Cause the current test to fail with the given message.'''
-    raise TestFailException(message)
-
-def skip(message):
-    '''Cause the current test to skip with the given message.'''
-    raise TestSkipException(message)
-
-class TestCase(object):
-    '''
-    Base class for all tests.
-
-    ..note::
-        The :func:`__new__` method enables collection of test cases, it must
-        be called in order for test cases to be collected.
-    '''
-    fixtures = []
-
-    # TODO, remove explicit dependency. Use the loader to set the
-    # default runner
-    runner = runner_mod.TestRunner
-    collector = helper.InstanceCollector()
-
-    def __new__(cls, *args, **kwargs):
-        obj = super(TestCase, cls).__new__(cls, *args, **kwargs)
-        TestCase.collector.collect(obj)
-        return obj
-
-    def __init__(self, name=None, fixtures=tuple(), **kwargs):
-        self.fixtures = self.fixtures + list(fixtures)
-        if name is None:
-            name = self.__class__.__name__
-        self.name = name
-
-class TestFunction(TestCase):
-    '''
-    TestCase implementation which uses a callable object as a test.
-    '''
-    def __init__(self, function, name=None, **kwargs):
-        self.test_function = function
-        if name is None:
-            name = function.__name__
-        TestCase.__init__(self, name=name, **kwargs)
-
-    def test(self, *args, **kwargs):
-        self.test_function(*args, **kwargs)
-
-# TODO Change the decorator to make this easier to create copy tests.
-# Good way to do so might be return by reference.
-def testfunction(function=None, name=None, fixtures=tuple()):
-    '''
-    A decorator used to wrap a function as a TestFunction.
-    '''
-    def testfunctiondecorator(function):
-        '''Decorator used to mark a function as a test case.'''
-        kwargs = {}
-        if name is not None:
-            kwargs['name'] = name
-        if fixtures is not None:
-            kwargs['fixtures'] = fixtures
-        TestFunction(function, **kwargs)
-        return function
-    if function is not None:
-        return testfunctiondecorator(function)
-    else:
-        return testfunctiondecorator
diff --git a/ext/testlib/test_util.py b/ext/testlib/test_util.py
new file mode 100644 (file)
index 0000000..5a0c0a8
--- /dev/null
@@ -0,0 +1,106 @@
+# 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
+
+import functools
+
+import testlib.helper as helper
+import testlib.runner as runner_mod
+
+class TestingException(Exception):
+    '''Common ancestor for manual Testing Exceptions.'''
+class TestFailException(TestingException):
+    '''Signals that a test has failed.'''
+class TestSkipException(TestingException):
+    '''Signals that a test has been skipped.'''
+
+def fail(message):
+    '''Cause the current test to fail with the given message.'''
+    raise TestFailException(message)
+
+def skip(message):
+    '''Cause the current test to skip with the given message.'''
+    raise TestSkipException(message)
+
+class TestCase(object):
+    '''
+    Base class for all tests.
+
+    ..note::
+        The :func:`__new__` method enables collection of test cases, it must
+        be called in order for test cases to be collected.
+    '''
+    fixtures = []
+
+    # TODO, remove explicit dependency. Use the loader to set the
+    # default runner
+    runner = runner_mod.TestRunner
+    collector = helper.InstanceCollector()
+
+    def __new__(cls, *args, **kwargs):
+        obj = super(TestCase, cls).__new__(cls)
+        TestCase.collector.collect(obj)
+        return obj
+
+    def __init__(self, name=None, fixtures=tuple(), **kwargs):
+        self.fixtures = self.fixtures + list(fixtures)
+        if name is None:
+            name = self.__class__.__name__
+        self.name = name
+
+class TestFunction(TestCase):
+    '''
+    TestCase implementation which uses a callable object as a test.
+    '''
+    def __init__(self, function, name=None, **kwargs):
+        self.test_function = function
+        if name is None:
+            name = function.__name__
+        TestCase.__init__(self, name=name, **kwargs)
+
+    def test(self, *args, **kwargs):
+        self.test_function(*args, **kwargs)
+
+# TODO Change the decorator to make this easier to create copy tests.
+# Good way to do so might be return by reference.
+def testfunction(function=None, name=None, fixtures=tuple()):
+    '''
+    A decorator used to wrap a function as a TestFunction.
+    '''
+    def testfunctiondecorator(function):
+        '''Decorator used to mark a function as a test case.'''
+        kwargs = {}
+        if name is not None:
+            kwargs['name'] = name
+        if fixtures is not None:
+            kwargs['fixtures'] = fixtures
+        TestFunction(function, **kwargs)
+        return function
+    if function is not None:
+        return testfunctiondecorator(function)
+    else:
+        return testfunctiondecorator
index fe56252a0261510c2bfd811bbdb94bce6fbcb26c..f8951a28da1b5827dedeea3f17375287b6ede5eb 100644 (file)
@@ -29,7 +29,7 @@
 import os
 import itertools
 
-import config
+import testlib.configuration as configuration
 
 class UID(object):
     sep = ':'
@@ -42,12 +42,12 @@ class UID(object):
     @staticmethod
     def _shorten_path(path):
         return os.path.relpath(path,
-                os.path.commonprefix((config.constants.testing_base,
+                os.path.commonprefix((configuration.constants.testing_base,
                                       path)))
 
     @staticmethod
     def _full_path(short_path):
-        return os.path.join(config.constants.testing_base, short_path)
+        return os.path.join(configuration.constants.testing_base, short_path)
 
     @classmethod
     def uid_to_path(cls, uid):
index 4bd22a468ec781fcf8da5ddf7fa6ac7fc6c50972..e91970262e31afc8a40a08aa3bc0b3c51a659714 100644 (file)
@@ -44,9 +44,8 @@ loaded by the testlib :class:`testlib.loader.Loader`.
 '''
 import itertools
 
-import log
-import uid
-from state import Status, Result
+import testlib.uid as uid
+from testlib.state import Status, Result
 
 class TestCaseMetadata():
     def __init__(self, name, uid, path, result, status, suite_uid):
@@ -127,9 +126,11 @@ class LoadedTestable(object):
 
     # TODO Change log to provide status_update, result_update for all types.
     def log_status(self, status):
+        import testlib.log as log
         log.test_log.status_update(self, status)
 
     def log_result(self, result):
+        import testlib.log as log
         log.test_log.result_update(self, result)
 
     def __iter__(self):
index 0f7993875b235b2975a05fabeab0d79f8d81b734..b124a132b0e3b5384d793b473a2926824ece9f35 100644 (file)
@@ -43,9 +43,11 @@ from common import FSConfig
 from common import Options
 from common.Caches import *
 from ruby import Ruby
+from six import add_metaclass
 
 _have_kvm_support = 'BaseKvmCPU' in globals()
 
+@add_metaclass(ABCMeta)
 class BaseSystem(object):
     """Base system builder.
 
@@ -55,8 +57,6 @@ class BaseSystem(object):
     the initialization process.
     """
 
-    __metaclass__ = ABCMeta
-
     def __init__(self, mem_mode='timing', mem_class=SimpleMemory,
                  cpu_class=TimingSimpleCPU, num_cpus=1, num_threads=1,
                  checker=False, mem_size=None, use_ruby=False):
index 614165a482285b1701c20ebf0d6a6222e9dd9098..0955469d085aacb6c0aaefcf972c7815515446d8 100644 (file)
@@ -24,8 +24,8 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-import suite
-import fixture
+import testlib.suite
+import testlib.fixture
 
-from suite import *
-from fixture import *
+from .suite import *
+from .fixture import *
index 339d15aae08d1b2b715b159e252cb7907e1fe733..a21c4b96565c95bdf0dac2281c27d65ddf6a6bdd 100644 (file)
@@ -70,11 +70,11 @@ for isa in valid_isas:
         binary = joinpath(workload_binary.path, workload)
 
         for cpu in valid_isas[isa]:
-           gem5_verify_config(
+            gem5_verify_config(
                   name='cpu_test_{}_{}'.format(cpu,workload),
                   verifiers=verifiers,
                   config=joinpath(getcwd(), 'run.py'),
                   config_args=['--cpu={}'.format(cpu), binary],
                   valid_isas=(isa.upper(),),
                   fixtures=[workload_binary]
-           )
+            )
index fc31b30c67b0f8eb31f4df82c5ec60540832712d..f28201b046ecb21686887069fe23bee212159bdd 100644 (file)
@@ -42,11 +42,11 @@ import shutil
 import sys
 import socket
 import threading
-import urllib
-import urllib2
+
+from six.moves import urllib
 
 from testlib.fixture import Fixture
-from testlib.config import config, constants
+from testlib.configuration import config, constants
 from testlib.helper import log_call, cacheresult, joinpath, absdirpath
 import testlib.log as log
 from testlib.state import Result
@@ -271,15 +271,16 @@ class DownloadedProgram(UniqueFixture):
             except OSError as e:
                 if e.errno != errno.EEXIST:
                     raise
-        urllib.urlretrieve(self.url, self.filename)
+        urllib.request.urlretrieve(self.url, self.filename)
 
     def _getremotetime(self):
         import datetime, time
         import _strptime # Needed for python threading bug
 
-        u = urllib2.urlopen(self.url, timeout=10)
+        u = urllib.request.urlopen(self.url, timeout=10)
+
         return time.mktime(datetime.datetime.strptime( \
-                    u.info().getheaders("Last-Modified")[0],
+                    u.info()["Last-Modified"],
                     "%a, %d %b %Y %X GMT").timetuple())
 
     def _setup(self, testitem):
@@ -289,7 +290,7 @@ class DownloadedProgram(UniqueFixture):
         else:
             try:
                 t = self._getremotetime()
-            except (urllib2.URLError, socket.timeout):
+            except (urllib.error.URLError, socket.timeout):
                 # Problem checking the server, use the old files.
                 log.test_log.debug("Could not contact server. Binaries may be old.")
                 return
@@ -315,7 +316,7 @@ class DownloadedArchive(DownloadedProgram):
         else:
             try:
                 t = self._getremotetime()
-            except (urllib2.URLError, socket.timeout):
+            except (urllib.error.URLError, socket.timeout):
                 # Problem checking the server, use the old files.
                 log.test_log.debug("Could not contact server. "
                                    "Binaries may be old.")
index 2a4eeb36fe67b50cd1cd80a229650eb1639488c4..bf87a278c1a17275d6b03be4166a029b9cc82c3a 100644 (file)
@@ -28,6 +28,8 @@
 Test file for simple memory test
 TODO: Add stats checking
 '''
+import six
+
 from testlib import *
 
 gem5_verify_config(
@@ -48,7 +50,7 @@ simple_mem_params = [
 
 
 for name, params in simple_mem_params:
-    args = ['--' + key + '=' + val for key,val in params.iteritems()]
+    args = ['--' + key + '=' + val for key,val in six.iteritems(params)]
 
     gem5_verify_config(
         name='simple_mem_' + name,
index 25e652e8403f4add0715743e6c897307390a9e6f..4cf0f81a25e3d08e2dcf432e5f0a32dbf04f00bf 100644 (file)
@@ -41,12 +41,13 @@ import copy
 import subprocess
 import sys
 
-from testlib.test import TestFunction
+from testlib.test_util import TestFunction
 from testlib.suite import TestSuite
 from testlib.helper import log_call
-from testlib.config import constants, config
-from fixture import TempdirFixture, Gem5Fixture, VariableFixture
-import verifier
+from testlib.configuration import constants, config
+from .fixture import TempdirFixture, Gem5Fixture, VariableFixture
+
+from . import verifier
 
 def gem5_verify_config(name,
                        config,
@@ -190,6 +191,7 @@ def _create_test_run_gem5(config, config_args, gem5_args):
         command.append(config)
         # Config_args should set up the program args.
         command.extend(config_args)
-        returncode.value = log_call(params.log, command, stderr=sys.stderr)
+        returncode.value = log_call(params.log, command, stdout=sys.stdout,
+                                                         stderr=sys.stderr)
 
     return test_run_gem5
index c955c407df13daa58080220651b314d211eb088b..815b9bbafba759b58c4db732af8a6da3884a05c1 100644 (file)
@@ -29,8 +29,8 @@ Built in test cases that verify particular details about a gem5 run.
 '''
 import re
 
-from testlib import test
-from testlib.config import constants
+from testlib import test_util as test
+from testlib.configuration import constants
 from testlib.helper import joinpath, diff_out_file
 
 class Verifier(object):
index 5cd68e91d1f0e2b1e3fd938d5a0c309c711dfa4a..3287ef14c49b8959b688564a7b4272a5e1307878 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env python
 '''
 The main source for testlib. Ties together the default test runners and
 loaders.
@@ -10,6 +10,8 @@ from __future__ import print_function
 import sys
 import os
 
+os.environ["PYTHONUNBUFFERED"] = "1"
+
 base_dir = os.path.dirname(os.path.abspath(__file__))
 ext_path = os.path.join(base_dir, os.pardir, 'ext')
 
@@ -17,7 +19,7 @@ sys.path.insert(0, base_dir)
 sys.path.insert(0, ext_path)
 
 import testlib.main as testlib
-import testlib.config as config
+import testlib.configuration as config
 import testlib.helper as helper
 
 config.basedir = helper.absdirpath(__file__)
index 4f97291d5c0f1cd61cc309fcce9cab0a69cfc6e9..5dcc684da7cb71283efd08a514c53a732e8428ed 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env python
 #
 # Copyright (c) 2016 ARM Limited
 # All rights reserved
index 6fc1e28a08e7d295d7c32a296e6fea6e3261012c..f46915c8f851ae9b98ce870a44a1a85231c0762f 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env python
 #
 # Copyright (c) 2016 ARM Limited
 # All rights reserved
index 42ec24524e22f65af6a74ca2efd4050cea945d51..eb88d4c238b18270fee7177df8b7daaf0079fc7d 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env python
 #
 # Copyright (c) 2016 ARM Limited
 # All rights reserved
@@ -40,6 +40,7 @@ from __future__ import print_function
 from abc import ABCMeta, abstractmethod
 import inspect
 import pickle
+from six import add_metaclass
 import string
 import sys
 
@@ -141,8 +142,8 @@ class TestResult(object):
     def __nonzero__(self):
         return all([ r for r in self.results ])
 
+@add_metaclass(ABCMeta)
 class ResultFormatter(object):
-    __metaclass__ = ABCMeta
 
     def __init__(self, fout=sys.stdout, verbose=False):
         self.verbose = verbose
index 29bfa78b6682cad01ece93cd5550dccda33b243b..042180d796b0bfa20accf2381ad15b194ffabb74 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env python
 #
 # Copyright (c) 2016-2017 ARM Limited
 # All rights reserved
 from abc import ABCMeta, abstractmethod
 import os
 from collections import namedtuple
-from .units import *
-from .helpers import FileIgnoreList
-from .results import TestResult
+
+from six import add_metaclass
+
+import sys
+sys.path.append(os.path.dirname(__file__))
+
+from units import *
+from helpers import FileIgnoreList
+from results import TestResult
 import shutil
 
 _test_base = os.path.join(os.path.dirname(__file__), "..")
@@ -172,6 +178,7 @@ def get_default_protocol(arch):
 all_categories = ("quick", "long")
 all_modes = ("fs", "se")
 
+@add_metaclass(ABCMeta)
 class Test(object):
     """Test case base class.
 
@@ -182,8 +189,6 @@ class Test(object):
 
     """
 
-    __metaclass__ = ABCMeta
-
     def __init__(self, name):
         self.test_name = name
 
index 9c9c1e5bf418841b348e30a5a9035a274ce93e76..d1fa6b227c450950b0f6867904b7201f450500dc 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env python
 #
 # Copyright (c) 2016 ARM Limited
 # All rights reserved
@@ -41,15 +41,18 @@ import difflib
 import functools
 import os
 import re
+from six import add_metaclass
 import subprocess
 import sys
 import traceback
 
-from .results import UnitResult
-from .helpers import *
+sys.path.append(os.path.dirname(__file__))
+from results import UnitResult
+from helpers import *
 
 _test_base = os.path.join(os.path.dirname(__file__), "..")
 
+@add_metaclass(ABCMeta)
 class TestUnit(object):
     """Base class for all test units.
 
@@ -64,8 +67,6 @@ class TestUnit(object):
 
     """
 
-    __metaclass__ = ABCMeta
-
     def __init__(self, name, ref_dir, test_dir, skip=False):
         self.name = name
         self.ref_dir = ref_dir
index b23cb5a1df11103dc724ca30a0dd3879a8781ee5..df46b414cca18caa2c778be42fc3ca00d62cf1f1 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env python
 #
 # Copyright (c) 2016 ARM Limited
 # All rights reserved