From 07ce662bd212246e20d85de1e4f3d537565449d1 Mon Sep 17 00:00:00 2001 From: Sean Wilson Date: Thu, 3 Aug 2017 11:28:49 -0500 Subject: tests,ext: Add a new testing library proposal The new test library is split into two parts: The framework which resides in ext/, and the gem5 helping components in /tests/gem5. Change-Id: Ib4f3ae8d7eb96a7306335a3e739b7e8041aa99b9 Signed-off-by: Sean Wilson Reviewed-on: https://gem5-review.googlesource.com/4421 Reviewed-by: Giacomo Travaglini Maintainer: Jason Lowe-Power --- tests/gem5/fixture.py | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 tests/gem5/fixture.py (limited to 'tests/gem5/fixture.py') diff --git a/tests/gem5/fixture.py b/tests/gem5/fixture.py new file mode 100644 index 000000000..a50d73cff --- /dev/null +++ b/tests/gem5/fixture.py @@ -0,0 +1,258 @@ +# 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 os +import tempfile +import shutil + +from testlib.fixture import Fixture, globalfixture +from testlib.config import config, constants +from testlib.helper import log_call, cacheresult, joinpath, absdirpath +import testlib.log as log + + +class VariableFixture(Fixture): + def __init__(self, value=None, name=None): + super(VariableFixture, self).__init__(name=name) + self.value = value + + +class TempdirFixture(Fixture): + def __init__(self): + self.path = None + super(TempdirFixture, self).__init__( + name=constants.tempdir_fixture_name) + + def setup(self, testitem): + self.path = tempfile.mkdtemp(prefix='gem5out') + + def teardown(self, testitem): + if self.path is not None: + shutil.rmtree(self.path) + + +class SConsFixture(Fixture): + ''' + Fixture will wait until all SCons targets are collected and tests are + about to be ran, then will invocate a single instance of SCons for all + targets. + + :param directory: The directory which scons will -C (cd) into before + executing. If None is provided, will choose the config base_dir. + ''' + def __init__(self, directory=None, target_class=None): + self.directory = directory if directory else config.base_dir + self.target_class = target_class if target_class else SConsTarget + self.threads = config.threads + self.targets = set() + super(SConsFixture, self).__init__() + + def setup(self, testitem): + if config.skip_build: + return + + command = [ + 'scons', '-C', self.directory, + '-j', str(self.threads), + '--ignore-style' + ] + + if not self.targets: + log.test_log.warn( + 'No SCons targets specified, this will' + ' build the default all target.\n' + 'This is likely unintended, and you' + ' may wish to kill testlib and reconfigure.') + else: + log.test_log.message( + 'Building the following targets.' + ' This may take a while.') + log.test_log.message('%s' % (', '.join(self.targets))) + log.test_log.message( + "You may want to run with only a single ISA" + "(--isa=), use --skip-build, or use 'rerun'.") + + + + command.extend(self.targets) + log_call(log.test_log, command) + + +class SConsTarget(Fixture): + # The singleton scons fixture we'll use for all targets. + default_scons_invocation = None + + def __init__(self, target, build_dir=None, invocation=None): + ''' + Represents a target to be built by an 'invocation' of scons. + + :param target: The target known to scons. + + :param build_dir: The 'build' directory path which will be prepended + to the target name. + + :param invocation: Represents an invocation of scons which we will + automatically attach this target to. If None provided, uses the + main 'scons' invocation. + ''' + + if build_dir is None: + build_dir = config.build_dir + self.target = os.path.join(build_dir, target) + super(SConsTarget, self).__init__(name=target) + + if invocation is None: + if self.default_scons_invocation is None: + SConsTarget.default_scons_invocation = SConsFixture() + globalfixture(SConsTarget.default_scons_invocation) + + invocation = self.default_scons_invocation + self.invocation = invocation + + def schedule_finalized(self, schedule): + self.invocation.targets.add(self.target) + return Fixture.schedule_finalized(self, schedule) + +class Gem5Fixture(SConsTarget): + def __init__(self, isa, variant): + target = joinpath(isa.upper(), 'gem5.%s' % variant) + super(Gem5Fixture, self).__init__(target) + + self.name = constants.gem5_binary_fixture_name + self.path = self.target + self.isa = isa + self.variant = variant + + +class MakeFixture(Fixture): + def __init__(self, directory, *args, **kwargs): + name = 'make -C %s' % directory + super(MakeFixture, self).__init__(build_once=True, lazy_init=False, + name=name, + *args, **kwargs) + self.targets = [] + self.directory = directory + + def setup(self): + super(MakeFixture, self).setup() + targets = set(self.required_by) + command = ['make', '-C', self.directory] + command.extend([target.target for target in targets]) + log_call(command) + + +class MakeTarget(Fixture): + def __init__(self, target, make_fixture=None, *args, **kwargs): + ''' + :param make_fixture: The make invocation we will be attached to. + Since we don't have a single global instance of make in gem5 like we do + scons we need to know what invocation to attach to. If none given, + creates its own. + ''' + super(MakeTarget, self).__init__(name=target, *args, **kwargs) + self.target = self.name + + if make_fixture is None: + make_fixture = MakeFixture( + absdirpath(target), + lazy_init=True, + build_once=False) + + self.make_fixture = make_fixture + + # Add our self to the required targets of the main MakeFixture + self.require(self.make_fixture) + + def setup(self, testitem): + super(MakeTarget, self).setup() + self.make_fixture.setup() + return self + +class TestProgram(MakeTarget): + def __init__(self, program, isa, os, recompile=False): + make_dir = joinpath('test-progs', program) + make_fixture = MakeFixture(make_dir) + target = joinpath('bin', isa, os, program) + super(TestProgram, self).__init__(target, make_fixture) + self.path = joinpath(make_dir, target) + self.recompile = recompile + + def setup(self, testitem): + # Check if the program exists if it does then only compile if + # recompile was given. + if self.recompile: + super(MakeTarget, self).setup() + elif not os.path.exists(self.path): + super(MakeTarget, self).setup() + +class DownloadedProgram(Fixture): + """ Like TestProgram, but checks the version in the gem5 binary repository + and downloads an updated version if it is needed. + """ + urlbase = "http://gem5.org/dist/current/" + + def __init__(self, path, program, **kwargs): + super(DownloadedProgram, self).__init__("download-" + program, + build_once=True, **kwargs) + + self.program_dir = joinpath('test-progs', path) + self.path = joinpath(self.program_dir, program) + + self.url = self.urlbase + self.path + + def _download(self): + import urllib + log.test_log.debug("Downloading " + self.url + " to " + self.path) + if not os.path.exists(self.program_dir): + os.makedirs(self.program_dir) + urllib.urlretrieve(self.url, self.path) + + def _getremotetime(self): + import urllib2, datetime, time + import _strptime # Needed for python threading bug + + u = urllib2.urlopen(self.url) + return time.mktime(datetime.datetime.strptime( \ + u.info().getheaders("Last-Modified")[0], + "%a, %d %b %Y %X GMT").timetuple()) + + def setup(self, testitem): + import urllib2 + # Check to see if there is a file downloaded + if not os.path.exists(self.path): + self._download() + else: + try: + t = self._getremotetime() + except urllib2.URLError: + # Problem checking the server, use the old files. + log.debug("Could not contact server. Binaries may be old.") + return + # If the server version is more recent, download it + if t > os.path.getmtime(self.path): + self._download() -- cgit v1.2.3