114140Snikos.nikoleris@arm.com# Copyright (c) 2019 ARM Limited
214140Snikos.nikoleris@arm.com# All rights reserved
314140Snikos.nikoleris@arm.com#
414140Snikos.nikoleris@arm.com# The license below extends only to copyright in the software and shall
514140Snikos.nikoleris@arm.com# not be construed as granting a license to any other intellectual
614140Snikos.nikoleris@arm.com# property including but not limited to intellectual property relating
714140Snikos.nikoleris@arm.com# to a hardware implementation of the functionality of the software
814140Snikos.nikoleris@arm.com# licensed hereunder.  You may use the software subject to the license
914140Snikos.nikoleris@arm.com# terms below provided that you ensure that this notice is replicated
1014140Snikos.nikoleris@arm.com# unmodified and in its entirety in all distributions of the software,
1114140Snikos.nikoleris@arm.com# modified or unmodified, in source code or in binary form.
1214140Snikos.nikoleris@arm.com#
1312882Sspwilson2@wisc.edu# Copyright (c) 2017 Mark D. Hill and David A. Wood
1412882Sspwilson2@wisc.edu# All rights reserved.
1512882Sspwilson2@wisc.edu#
1612882Sspwilson2@wisc.edu# Redistribution and use in source and binary forms, with or without
1712882Sspwilson2@wisc.edu# modification, are permitted provided that the following conditions are
1812882Sspwilson2@wisc.edu# met: redistributions of source code must retain the above copyright
1912882Sspwilson2@wisc.edu# notice, this list of conditions and the following disclaimer;
2012882Sspwilson2@wisc.edu# redistributions in binary form must reproduce the above copyright
2112882Sspwilson2@wisc.edu# notice, this list of conditions and the following disclaimer in the
2212882Sspwilson2@wisc.edu# documentation and/or other materials provided with the distribution;
2312882Sspwilson2@wisc.edu# neither the name of the copyright holders nor the names of its
2412882Sspwilson2@wisc.edu# contributors may be used to endorse or promote products derived from
2512882Sspwilson2@wisc.edu# this software without specific prior written permission.
2612882Sspwilson2@wisc.edu#
2712882Sspwilson2@wisc.edu# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2812882Sspwilson2@wisc.edu# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2912882Sspwilson2@wisc.edu# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
3012882Sspwilson2@wisc.edu# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
3112882Sspwilson2@wisc.edu# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
3212882Sspwilson2@wisc.edu# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
3312882Sspwilson2@wisc.edu# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3412882Sspwilson2@wisc.edu# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3512882Sspwilson2@wisc.edu# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3612882Sspwilson2@wisc.edu# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3712882Sspwilson2@wisc.edu# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3812882Sspwilson2@wisc.edu#
3912882Sspwilson2@wisc.edu# Authors: Sean Wilson
4014142Snikos.nikoleris@arm.com#          Nikos Nikoleris
4112882Sspwilson2@wisc.edu
4212882Sspwilson2@wisc.eduimport os
4312882Sspwilson2@wisc.eduimport tempfile
4412882Sspwilson2@wisc.eduimport shutil
4514140Snikos.nikoleris@arm.comimport threading
4614142Snikos.nikoleris@arm.comimport urllib
4714142Snikos.nikoleris@arm.comimport urllib2
4812882Sspwilson2@wisc.edu
4914141Snikos.nikoleris@arm.comfrom testlib.fixture import Fixture
5012882Sspwilson2@wisc.edufrom testlib.config import config, constants
5112882Sspwilson2@wisc.edufrom testlib.helper import log_call, cacheresult, joinpath, absdirpath
5212882Sspwilson2@wisc.eduimport testlib.log as log
5312882Sspwilson2@wisc.edu
5412882Sspwilson2@wisc.edu
5512882Sspwilson2@wisc.educlass VariableFixture(Fixture):
5612882Sspwilson2@wisc.edu    def __init__(self, value=None, name=None):
5712882Sspwilson2@wisc.edu        super(VariableFixture, self).__init__(name=name)
5812882Sspwilson2@wisc.edu        self.value = value
5912882Sspwilson2@wisc.edu
6012882Sspwilson2@wisc.edu
6112882Sspwilson2@wisc.educlass TempdirFixture(Fixture):
6212882Sspwilson2@wisc.edu    def __init__(self):
6312882Sspwilson2@wisc.edu        self.path = None
6412882Sspwilson2@wisc.edu        super(TempdirFixture, self).__init__(
6512882Sspwilson2@wisc.edu                name=constants.tempdir_fixture_name)
6612882Sspwilson2@wisc.edu
6712882Sspwilson2@wisc.edu    def setup(self, testitem):
6812882Sspwilson2@wisc.edu        self.path = tempfile.mkdtemp(prefix='gem5out')
6912882Sspwilson2@wisc.edu
7012882Sspwilson2@wisc.edu    def teardown(self, testitem):
7112882Sspwilson2@wisc.edu        if self.path is not None:
7212882Sspwilson2@wisc.edu            shutil.rmtree(self.path)
7312882Sspwilson2@wisc.edu
7413790Sjason@lowepower.com    def skip_cleanup(self):
7513790Sjason@lowepower.com        # Set path to none so it's not deleted
7613790Sjason@lowepower.com        self.path = None
7713790Sjason@lowepower.com
7814140Snikos.nikoleris@arm.comclass UniqueFixture(Fixture):
7914140Snikos.nikoleris@arm.com    '''
8014140Snikos.nikoleris@arm.com    Base class for fixtures that generate a target in the
8114140Snikos.nikoleris@arm.com    filesystem. If the same fixture is used by more than one
8214140Snikos.nikoleris@arm.com    test/suite, rather than creating a copy of the fixture, it returns
8314140Snikos.nikoleris@arm.com    the same object and makes sure that setup is only executed
8414140Snikos.nikoleris@arm.com    once. Devired classses should override the _init and _setup
8514140Snikos.nikoleris@arm.com    functions.
8614140Snikos.nikoleris@arm.com
8714140Snikos.nikoleris@arm.com    :param target: The absolute path of the target in the filesystem.
8814140Snikos.nikoleris@arm.com
8914140Snikos.nikoleris@arm.com    '''
9014140Snikos.nikoleris@arm.com    fixtures = {}
9114140Snikos.nikoleris@arm.com
9214140Snikos.nikoleris@arm.com    def __new__(cls, target):
9314140Snikos.nikoleris@arm.com        if target in cls.fixtures:
9414140Snikos.nikoleris@arm.com            obj = cls.fixtures[target]
9514140Snikos.nikoleris@arm.com        else:
9614140Snikos.nikoleris@arm.com            obj = super(UniqueFixture, cls).__new__(cls)
9714140Snikos.nikoleris@arm.com            obj.lock = threading.Lock()
9814140Snikos.nikoleris@arm.com            obj.target = target
9914140Snikos.nikoleris@arm.com            cls.fixtures[target] = obj
10014140Snikos.nikoleris@arm.com        return obj
10114140Snikos.nikoleris@arm.com
10214140Snikos.nikoleris@arm.com    def __init__(self, *args, **kwargs):
10314140Snikos.nikoleris@arm.com        with self.lock:
10414140Snikos.nikoleris@arm.com            if hasattr(self, '_init_done'):
10514140Snikos.nikoleris@arm.com                return
10614140Snikos.nikoleris@arm.com            super(UniqueFixture, self).__init__(self, **kwargs)
10714140Snikos.nikoleris@arm.com            self._init(*args, **kwargs)
10814140Snikos.nikoleris@arm.com            self._init_done = True
10914140Snikos.nikoleris@arm.com
11014140Snikos.nikoleris@arm.com    def setup(self, testitem):
11114140Snikos.nikoleris@arm.com        with self.lock:
11214140Snikos.nikoleris@arm.com            if hasattr(self, '_setup_done'):
11314140Snikos.nikoleris@arm.com                return
11414140Snikos.nikoleris@arm.com            self._setup_done = True
11514140Snikos.nikoleris@arm.com            self._setup(testitem)
11614140Snikos.nikoleris@arm.com
11712882Sspwilson2@wisc.edu
11814141Snikos.nikoleris@arm.comclass SConsFixture(UniqueFixture):
11912882Sspwilson2@wisc.edu    '''
12012882Sspwilson2@wisc.edu    Fixture will wait until all SCons targets are collected and tests are
12112882Sspwilson2@wisc.edu    about to be ran, then will invocate a single instance of SCons for all
12212882Sspwilson2@wisc.edu    targets.
12312882Sspwilson2@wisc.edu
12412882Sspwilson2@wisc.edu    :param directory: The directory which scons will -C (cd) into before
12512882Sspwilson2@wisc.edu        executing. If None is provided, will choose the config base_dir.
12612882Sspwilson2@wisc.edu    '''
12712882Sspwilson2@wisc.edu
12814141Snikos.nikoleris@arm.com    def __new__(cls, target):
12914141Snikos.nikoleris@arm.com        obj = super(SConsFixture, cls).__new__(cls, target)
13014141Snikos.nikoleris@arm.com        return obj
13114141Snikos.nikoleris@arm.com
13214141Snikos.nikoleris@arm.com    def _setup(self, testitem):
13312882Sspwilson2@wisc.edu        if config.skip_build:
13412882Sspwilson2@wisc.edu            return
13512882Sspwilson2@wisc.edu
13612882Sspwilson2@wisc.edu        command = [
13712882Sspwilson2@wisc.edu            'scons', '-C', self.directory,
13814141Snikos.nikoleris@arm.com            '-j', str(config.threads),
13912882Sspwilson2@wisc.edu            '--ignore-style'
14012882Sspwilson2@wisc.edu        ]
14112882Sspwilson2@wisc.edu
14212882Sspwilson2@wisc.edu        if not self.targets:
14312882Sspwilson2@wisc.edu            log.test_log.warn(
14412882Sspwilson2@wisc.edu                'No SCons targets specified, this will'
14512882Sspwilson2@wisc.edu                ' build the default all target.\n'
14612882Sspwilson2@wisc.edu                'This is likely unintended, and you'
14712882Sspwilson2@wisc.edu                ' may wish to kill testlib and reconfigure.')
14812882Sspwilson2@wisc.edu        else:
14912882Sspwilson2@wisc.edu            log.test_log.message(
15012882Sspwilson2@wisc.edu                    'Building the following targets.'
15112882Sspwilson2@wisc.edu                    ' This may take a while.')
15212882Sspwilson2@wisc.edu            log.test_log.message('%s' % (', '.join(self.targets)))
15312882Sspwilson2@wisc.edu            log.test_log.message(
15412882Sspwilson2@wisc.edu                    "You may want to run with only a single ISA"
15512882Sspwilson2@wisc.edu                    "(--isa=), use --skip-build, or use 'rerun'.")
15612882Sspwilson2@wisc.edu
15712882Sspwilson2@wisc.edu        command.extend(self.targets)
15813851Sjason@lowepower.com        if self.options:
15913851Sjason@lowepower.com            command.extend(self.options)
16012882Sspwilson2@wisc.edu        log_call(log.test_log, command)
16112882Sspwilson2@wisc.edu
16214141Snikos.nikoleris@arm.comclass Gem5Fixture(SConsFixture):
16314141Snikos.nikoleris@arm.com    def __new__(cls, isa, variant, protocol=None):
16414141Snikos.nikoleris@arm.com        target_dir = joinpath(config.build_dir, isa.upper())
16514141Snikos.nikoleris@arm.com        if protocol:
16614141Snikos.nikoleris@arm.com            target_dir += '_' + protocol
16714141Snikos.nikoleris@arm.com        target = joinpath(target_dir, 'gem5.%s' % variant)
16814141Snikos.nikoleris@arm.com        obj = super(Gem5Fixture, cls).__new__(cls, target)
16914141Snikos.nikoleris@arm.com        return obj
17012882Sspwilson2@wisc.edu
17114141Snikos.nikoleris@arm.com    def _init(self, isa, variant, protocol=None):
17214141Snikos.nikoleris@arm.com        self.name = constants.gem5_binary_fixture_name
17312882Sspwilson2@wisc.edu
17414141Snikos.nikoleris@arm.com        self.targets = [self.target]
17514141Snikos.nikoleris@arm.com        self.path = self.target
17614141Snikos.nikoleris@arm.com        self.directory = config.base_dir
17712882Sspwilson2@wisc.edu
17814141Snikos.nikoleris@arm.com        self.options = []
17913851Sjason@lowepower.com        if protocol:
18014141Snikos.nikoleris@arm.com            self.options = [ '--default=' + isa.upper(),
18114141Snikos.nikoleris@arm.com                             'PROTOCOL=' + protocol ]
18214141Snikos.nikoleris@arm.com        self.set_global()
18312882Sspwilson2@wisc.edu
18412882Sspwilson2@wisc.educlass MakeFixture(Fixture):
18512882Sspwilson2@wisc.edu    def __init__(self, directory, *args, **kwargs):
18612882Sspwilson2@wisc.edu        name = 'make -C %s' % directory
18712882Sspwilson2@wisc.edu        super(MakeFixture, self).__init__(build_once=True, lazy_init=False,
18812882Sspwilson2@wisc.edu                                          name=name,
18912882Sspwilson2@wisc.edu                                          *args, **kwargs)
19012882Sspwilson2@wisc.edu        self.targets = []
19112882Sspwilson2@wisc.edu        self.directory = directory
19212882Sspwilson2@wisc.edu
19312882Sspwilson2@wisc.edu    def setup(self):
19412882Sspwilson2@wisc.edu        super(MakeFixture, self).setup()
19512882Sspwilson2@wisc.edu        targets = set(self.required_by)
19612882Sspwilson2@wisc.edu        command = ['make', '-C', self.directory]
19712882Sspwilson2@wisc.edu        command.extend([target.target for target in targets])
19812882Sspwilson2@wisc.edu        log_call(command)
19912882Sspwilson2@wisc.edu
20012882Sspwilson2@wisc.edu
20112882Sspwilson2@wisc.educlass MakeTarget(Fixture):
20212882Sspwilson2@wisc.edu    def __init__(self, target, make_fixture=None, *args, **kwargs):
20312882Sspwilson2@wisc.edu        '''
20412882Sspwilson2@wisc.edu        :param make_fixture: The make invocation we will be attached to.
20512882Sspwilson2@wisc.edu        Since we don't have a single global instance of make in gem5 like we do
20612882Sspwilson2@wisc.edu        scons we need to know what invocation to attach to. If none given,
20712882Sspwilson2@wisc.edu        creates its own.
20812882Sspwilson2@wisc.edu        '''
20912882Sspwilson2@wisc.edu        super(MakeTarget, self).__init__(name=target, *args, **kwargs)
21012882Sspwilson2@wisc.edu        self.target = self.name
21112882Sspwilson2@wisc.edu
21212882Sspwilson2@wisc.edu        if make_fixture is None:
21312882Sspwilson2@wisc.edu            make_fixture = MakeFixture(
21412882Sspwilson2@wisc.edu                    absdirpath(target),
21512882Sspwilson2@wisc.edu                    lazy_init=True,
21612882Sspwilson2@wisc.edu                    build_once=False)
21712882Sspwilson2@wisc.edu
21812882Sspwilson2@wisc.edu        self.make_fixture = make_fixture
21912882Sspwilson2@wisc.edu
22012882Sspwilson2@wisc.edu        # Add our self to the required targets of the main MakeFixture
22112882Sspwilson2@wisc.edu        self.require(self.make_fixture)
22212882Sspwilson2@wisc.edu
22312882Sspwilson2@wisc.edu    def setup(self, testitem):
22412882Sspwilson2@wisc.edu        super(MakeTarget, self).setup()
22512882Sspwilson2@wisc.edu        self.make_fixture.setup()
22612882Sspwilson2@wisc.edu        return self
22712882Sspwilson2@wisc.edu
22812882Sspwilson2@wisc.educlass TestProgram(MakeTarget):
22912882Sspwilson2@wisc.edu    def __init__(self, program, isa, os, recompile=False):
23012882Sspwilson2@wisc.edu        make_dir = joinpath('test-progs', program)
23112882Sspwilson2@wisc.edu        make_fixture = MakeFixture(make_dir)
23212882Sspwilson2@wisc.edu        target = joinpath('bin', isa, os, program)
23312882Sspwilson2@wisc.edu        super(TestProgram, self).__init__(target, make_fixture)
23412882Sspwilson2@wisc.edu        self.path = joinpath(make_dir, target)
23512882Sspwilson2@wisc.edu        self.recompile = recompile
23612882Sspwilson2@wisc.edu
23712882Sspwilson2@wisc.edu    def setup(self, testitem):
23812882Sspwilson2@wisc.edu        # Check if the program exists if it does then only compile if
23912882Sspwilson2@wisc.edu        # recompile was given.
24012882Sspwilson2@wisc.edu        if self.recompile:
24112882Sspwilson2@wisc.edu            super(MakeTarget, self).setup()
24212882Sspwilson2@wisc.edu        elif not os.path.exists(self.path):
24312882Sspwilson2@wisc.edu            super(MakeTarget, self).setup()
24412882Sspwilson2@wisc.edu
24514142Snikos.nikoleris@arm.comclass DownloadedProgram(UniqueFixture):
24612882Sspwilson2@wisc.edu    """ Like TestProgram, but checks the version in the gem5 binary repository
24712882Sspwilson2@wisc.edu        and downloads an updated version if it is needed.
24812882Sspwilson2@wisc.edu    """
24912882Sspwilson2@wisc.edu
25014142Snikos.nikoleris@arm.com    def __new__(cls, url, path, filename):
25114142Snikos.nikoleris@arm.com        target = joinpath(path, filename)
25214142Snikos.nikoleris@arm.com        return super(DownloadedProgram, cls).__new__(cls, target)
25314142Snikos.nikoleris@arm.com
25414142Snikos.nikoleris@arm.com    def _init(self, url, path, filename, **kwargs):
25513793Sjason@lowepower.com        """
25614142Snikos.nikoleris@arm.com        url: string
25714142Snikos.nikoleris@arm.com            The url of the archive
25813793Sjason@lowepower.com        path: string
25914142Snikos.nikoleris@arm.com            The absolute path of the directory containing the archive
26014142Snikos.nikoleris@arm.com        filename: string
26114142Snikos.nikoleris@arm.com            The name of the archive
26213793Sjason@lowepower.com        """
26312882Sspwilson2@wisc.edu
26414142Snikos.nikoleris@arm.com        self.url = url
26514142Snikos.nikoleris@arm.com        self.path = path
26614142Snikos.nikoleris@arm.com        self.filename = joinpath(path, filename)
26714142Snikos.nikoleris@arm.com        self.name = "Downloaded:" + self.filename
26813793Sjason@lowepower.com
26912882Sspwilson2@wisc.edu    def _download(self):
27013792Sjason@lowepower.com        import errno
27112882Sspwilson2@wisc.edu        log.test_log.debug("Downloading " + self.url + " to " + self.path)
27214142Snikos.nikoleris@arm.com        if not os.path.exists(self.path):
27313792Sjason@lowepower.com            try:
27414142Snikos.nikoleris@arm.com                os.makedirs(self.path)
27513792Sjason@lowepower.com            except OSError as e:
27613792Sjason@lowepower.com                if e.errno != errno.EEXIST:
27713792Sjason@lowepower.com                    raise
27814142Snikos.nikoleris@arm.com        urllib.urlretrieve(self.url, self.filename)
27912882Sspwilson2@wisc.edu
28012882Sspwilson2@wisc.edu    def _getremotetime(self):
28114142Snikos.nikoleris@arm.com        import datetime, time
28212882Sspwilson2@wisc.edu        import _strptime # Needed for python threading bug
28312882Sspwilson2@wisc.edu
28412882Sspwilson2@wisc.edu        u = urllib2.urlopen(self.url)
28512882Sspwilson2@wisc.edu        return time.mktime(datetime.datetime.strptime( \
28612882Sspwilson2@wisc.edu                    u.info().getheaders("Last-Modified")[0],
28712882Sspwilson2@wisc.edu                    "%a, %d %b %Y %X GMT").timetuple())
28812882Sspwilson2@wisc.edu
28914142Snikos.nikoleris@arm.com    def _setup(self, testitem):
29012882Sspwilson2@wisc.edu        # Check to see if there is a file downloaded
29114142Snikos.nikoleris@arm.com        if not os.path.exists(self.filename):
29212882Sspwilson2@wisc.edu            self._download()
29312882Sspwilson2@wisc.edu        else:
29412882Sspwilson2@wisc.edu            try:
29512882Sspwilson2@wisc.edu                t = self._getremotetime()
29612882Sspwilson2@wisc.edu            except urllib2.URLError:
29712882Sspwilson2@wisc.edu                # Problem checking the server, use the old files.
29814142Snikos.nikoleris@arm.com                log.test_log.debug("Could not contact server. Binaries may be old.")
29912882Sspwilson2@wisc.edu                return
30012882Sspwilson2@wisc.edu            # If the server version is more recent, download it
30114142Snikos.nikoleris@arm.com            if t > os.path.getmtime(self.filename):
30212882Sspwilson2@wisc.edu                self._download()
30314142Snikos.nikoleris@arm.com
30414142Snikos.nikoleris@arm.comclass DownloadedArchive(DownloadedProgram):
30514142Snikos.nikoleris@arm.com    """ Like TestProgram, but checks the version in the gem5 binary repository
30614142Snikos.nikoleris@arm.com        and downloads an updated version if it is needed.
30714142Snikos.nikoleris@arm.com    """
30814142Snikos.nikoleris@arm.com
30914142Snikos.nikoleris@arm.com    def _extract(self):
31014142Snikos.nikoleris@arm.com        import tarfile
31114142Snikos.nikoleris@arm.com        with tarfile.open(self.filename) as tf:
31214142Snikos.nikoleris@arm.com            tf.extractall(self.path)
31314142Snikos.nikoleris@arm.com
31414142Snikos.nikoleris@arm.com    def _setup(self, testitem):
31514142Snikos.nikoleris@arm.com        # Check to see if there is a file downloaded
31614142Snikos.nikoleris@arm.com        if not os.path.exists(self.filename):
31714142Snikos.nikoleris@arm.com            self._download()
31814142Snikos.nikoleris@arm.com            self._extract()
31914142Snikos.nikoleris@arm.com        else:
32014142Snikos.nikoleris@arm.com            try:
32114142Snikos.nikoleris@arm.com                t = self._getremotetime()
32214142Snikos.nikoleris@arm.com            except urllib2.URLError:
32314142Snikos.nikoleris@arm.com                # Problem checking the server, use the old files.
32414142Snikos.nikoleris@arm.com                log.test_log.debug("Could not contact server. Binaries may be old.")
32514142Snikos.nikoleris@arm.com                return
32614142Snikos.nikoleris@arm.com            # If the server version is more recent, download it
32714142Snikos.nikoleris@arm.com            if t > os.path.getmtime(self.filename):
32814142Snikos.nikoleris@arm.com                self._download()
32914142Snikos.nikoleris@arm.com                self._extract()
330