From: Bobby R. Bruce Date: Thu, 16 Apr 2020 18:55:17 +0000 (-0700) Subject: tests,python: Upgrading testlib to function with Python2 X-Git-Tag: v20.0.0.0~14 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=7ffd684334e93fb0268e114802e55e8e118694ee;p=gem5.git tests,python: Upgrading testlib to function with Python2 Change-Id: I9926b1507e9069ae8564c31bdd377b2b916462a2 Issue-on: https://gem5.atlassian.net/browse/GEM5-395 Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/29088 Reviewed-by: Bobby R. Bruce Maintainer: Bobby R. Bruce Tested-by: kokoro --- diff --git a/ext/testlib/__init__.py b/ext/testlib/__init__.py index 893da5433..898205d9a 100644 --- a/ext/testlib/__init__.py +++ b/ext/testlib/__init__.py @@ -29,12 +29,12 @@ 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 index 189f7c1d5..000000000 --- a/ext/testlib/config.py +++ /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 index 000000000..04744e113 --- /dev/null +++ b/ext/testlib/configuration.py @@ -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) diff --git a/ext/testlib/fixture.py b/ext/testlib/fixture.py index 7af6cb289..20568b80f 100644 --- a/ext/testlib/fixture.py +++ b/ext/testlib/fixture.py @@ -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 diff --git a/ext/testlib/handlers.py b/ext/testlib/handlers.py index 6f7694071..3005e01ef 100644 --- a/ext/testlib/handlers.py +++ b/ext/testlib/handlers.py @@ -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): diff --git a/ext/testlib/helper.py b/ext/testlib/helper.py index ac49e468e..4237a765e 100644 --- a/ext/testlib/helper.py +++ b/ext/testlib/helper.py @@ -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()) diff --git a/ext/testlib/loader.py b/ext/testlib/loader.py index 8f8f60e70..bb2fe4ef9 100644 --- a/ext/testlib/loader.py +++ b/ext/testlib/loader.py @@ -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): ''' diff --git a/ext/testlib/log.py b/ext/testlib/log.py index 5ba6f5d4f..cddb9217c 100644 --- a/ext/testlib/log.py +++ b/ext/testlib/log.py @@ -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 diff --git a/ext/testlib/main.py b/ext/testlib/main.py index 3827f7815..9c9ed03dd 100644 --- a/ext/testlib/main.py +++ b/ext/testlib/main.py @@ -29,16 +29,16 @@ 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 diff --git a/ext/testlib/query.py b/ext/testlib/query.py index c66445c44..174af626f 100644 --- a/ext/testlib/query.py +++ b/ext/testlib/query.py @@ -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. diff --git a/ext/testlib/result.py b/ext/testlib/result.py index 786c21b60..38b3322ba 100644 --- a/ext/testlib/result.py +++ b/ext/testlib/result.py @@ -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) diff --git a/ext/testlib/runner.py b/ext/testlib/runner.py index 9868cefb1..ec3c838e7 100644 --- a/ext/testlib/runner.py +++ b/ext/testlib/runner.py @@ -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): ''' diff --git a/ext/testlib/sandbox.py b/ext/testlib/sandbox.py index 49fe133ea..bdc6d8859 100644 --- a/ext/testlib/sandbox.py +++ b/ext/testlib/sandbox.py @@ -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() diff --git a/ext/testlib/suite.py b/ext/testlib/suite.py index 2ce817a6e..eae52fd92 100644 --- a/ext/testlib/suite.py +++ b/ext/testlib/suite.py @@ -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 diff --git a/ext/testlib/terminal.py b/ext/testlib/terminal.py index bdb20edea..bc4c85599 100644 --- a/ext/testlib/terminal.py +++ b/ext/testlib/terminal.py @@ -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 index 18899d6d0..000000000 --- a/ext/testlib/test.py +++ /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 index 000000000..5a0c0a8f1 --- /dev/null +++ b/ext/testlib/test_util.py @@ -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 diff --git a/ext/testlib/uid.py b/ext/testlib/uid.py index fe56252a0..f8951a28d 100644 --- a/ext/testlib/uid.py +++ b/ext/testlib/uid.py @@ -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): diff --git a/ext/testlib/wrappers.py b/ext/testlib/wrappers.py index 4bd22a468..e91970262 100644 --- a/ext/testlib/wrappers.py +++ b/ext/testlib/wrappers.py @@ -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): diff --git a/tests/configs/base_config.py b/tests/configs/base_config.py index 0f7993875..b124a132b 100644 --- a/tests/configs/base_config.py +++ b/tests/configs/base_config.py @@ -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): diff --git a/tests/gem5/__init__.py b/tests/gem5/__init__.py index 614165a48..0955469d0 100644 --- a/tests/gem5/__init__.py +++ b/tests/gem5/__init__.py @@ -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 * diff --git a/tests/gem5/cpu_tests/test.py b/tests/gem5/cpu_tests/test.py index 339d15aae..a21c4b965 100644 --- a/tests/gem5/cpu_tests/test.py +++ b/tests/gem5/cpu_tests/test.py @@ -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] - ) + ) diff --git a/tests/gem5/fixture.py b/tests/gem5/fixture.py index fc31b30c6..f28201b04 100644 --- a/tests/gem5/fixture.py +++ b/tests/gem5/fixture.py @@ -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.") diff --git a/tests/gem5/memory/test.py b/tests/gem5/memory/test.py index 2a4eeb36f..bf87a278c 100644 --- a/tests/gem5/memory/test.py +++ b/tests/gem5/memory/test.py @@ -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, diff --git a/tests/gem5/suite.py b/tests/gem5/suite.py index 25e652e84..4cf0f81a2 100644 --- a/tests/gem5/suite.py +++ b/tests/gem5/suite.py @@ -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 diff --git a/tests/gem5/verifier.py b/tests/gem5/verifier.py index c955c407d..815b9bbaf 100644 --- a/tests/gem5/verifier.py +++ b/tests/gem5/verifier.py @@ -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): diff --git a/tests/main.py b/tests/main.py index 5cd68e91d..3287ef14c 100755 --- a/tests/main.py +++ b/tests/main.py @@ -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__) diff --git a/tests/testing/__init__.py b/tests/testing/__init__.py index 4f97291d5..5dcc684da 100644 --- a/tests/testing/__init__.py +++ b/tests/testing/__init__.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python # # Copyright (c) 2016 ARM Limited # All rights reserved diff --git a/tests/testing/helpers.py b/tests/testing/helpers.py index 6fc1e28a0..f46915c8f 100755 --- a/tests/testing/helpers.py +++ b/tests/testing/helpers.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python # # Copyright (c) 2016 ARM Limited # All rights reserved diff --git a/tests/testing/results.py b/tests/testing/results.py index 42ec24524..eb88d4c23 100644 --- a/tests/testing/results.py +++ b/tests/testing/results.py @@ -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 diff --git a/tests/testing/tests.py b/tests/testing/tests.py index 29bfa78b6..042180d79 100755 --- a/tests/testing/tests.py +++ b/tests/testing/tests.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python # # Copyright (c) 2016-2017 ARM Limited # All rights reserved @@ -38,9 +38,15 @@ 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 diff --git a/tests/testing/units.py b/tests/testing/units.py index 9c9c1e5bf..d1fa6b227 100644 --- a/tests/testing/units.py +++ b/tests/testing/units.py @@ -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 diff --git a/tests/tests.py b/tests/tests.py index b23cb5a1d..df46b414c 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python2.7 +#!/usr/bin/env python # # Copyright (c) 2016 ARM Limited # All rights reserved