blob: c4fd12f52a46195588ff0e8a46a631bc18b82c83 [file] [log] [blame]
# 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)
def skip_cleanup(self):
# Set path to none so it's not deleted
self.path = None
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 = path
self.path = joinpath(self.program_dir, program)
self.url = self.urlbase + self.path
def _download(self):
import urllib
import errno
log.test_log.debug("Downloading " + self.url + " to " + self.path)
if not os.path.exists(self.program_dir):
try:
os.makedirs(self.program_dir)
except OSError as e:
if e.errno != errno.EEXIST:
raise
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()