fixture.py revision 13679:bc1188a6c0f0
1# Copyright (c) 2017 Mark D. Hill and David A. Wood
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met: redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer;
8# redistributions in binary form must reproduce the above copyright
9# notice, this list of conditions and the following disclaimer in the
10# documentation and/or other materials provided with the distribution;
11# neither the name of the copyright holders nor the names of its
12# contributors may be used to endorse or promote products derived from
13# this software without specific prior written permission.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26#
27# Authors: Sean Wilson
28
29import os
30import tempfile
31import shutil
32
33from testlib.fixture import Fixture, globalfixture
34from testlib.config import config, constants
35from testlib.helper import log_call, cacheresult, joinpath, absdirpath
36import testlib.log as log
37
38
39class VariableFixture(Fixture):
40    def __init__(self, value=None, name=None):
41        super(VariableFixture, self).__init__(name=name)
42        self.value = value
43
44
45class TempdirFixture(Fixture):
46    def __init__(self):
47        self.path = None
48        super(TempdirFixture, self).__init__(
49                name=constants.tempdir_fixture_name)
50
51    def setup(self, testitem):
52        self.path = tempfile.mkdtemp(prefix='gem5out')
53
54    def teardown(self, testitem):
55        if self.path is not None:
56            shutil.rmtree(self.path)
57
58
59class SConsFixture(Fixture):
60    '''
61    Fixture will wait until all SCons targets are collected and tests are
62    about to be ran, then will invocate a single instance of SCons for all
63    targets.
64
65    :param directory: The directory which scons will -C (cd) into before
66        executing. If None is provided, will choose the config base_dir.
67    '''
68    def __init__(self, directory=None, target_class=None):
69        self.directory = directory if directory else config.base_dir
70        self.target_class = target_class if target_class else SConsTarget
71        self.threads = config.threads
72        self.targets = set()
73        super(SConsFixture, self).__init__()
74
75    def setup(self, testitem):
76        if config.skip_build:
77            return
78
79        command = [
80            'scons', '-C', self.directory,
81            '-j', str(self.threads),
82            '--ignore-style'
83        ]
84
85        if not self.targets:
86            log.test_log.warn(
87                'No SCons targets specified, this will'
88                ' build the default all target.\n'
89                'This is likely unintended, and you'
90                ' may wish to kill testlib and reconfigure.')
91        else:
92            log.test_log.message(
93                    'Building the following targets.'
94                    ' This may take a while.')
95            log.test_log.message('%s' % (', '.join(self.targets)))
96            log.test_log.message(
97                    "You may want to run with only a single ISA"
98                    "(--isa=), use --skip-build, or use 'rerun'.")
99
100
101
102        command.extend(self.targets)
103        log_call(log.test_log, command)
104
105
106class SConsTarget(Fixture):
107    # The singleton scons fixture we'll use for all targets.
108    default_scons_invocation = None
109
110    def __init__(self, target, build_dir=None, invocation=None):
111        '''
112        Represents a target to be built by an 'invocation' of scons.
113
114        :param target: The target known to scons.
115
116        :param build_dir: The 'build' directory path which will be prepended
117            to the target name.
118
119        :param invocation: Represents an invocation of scons which we will
120            automatically attach this target to. If None provided, uses the
121            main 'scons' invocation.
122        '''
123
124        if build_dir is None:
125            build_dir = config.build_dir
126        self.target = os.path.join(build_dir, target)
127        super(SConsTarget, self).__init__(name=target)
128
129        if invocation is None:
130            if self.default_scons_invocation is None:
131                SConsTarget.default_scons_invocation = SConsFixture()
132                globalfixture(SConsTarget.default_scons_invocation)
133
134            invocation = self.default_scons_invocation
135        self.invocation = invocation
136
137    def schedule_finalized(self, schedule):
138        self.invocation.targets.add(self.target)
139        return Fixture.schedule_finalized(self, schedule)
140
141class Gem5Fixture(SConsTarget):
142    def __init__(self, isa, variant):
143        target = joinpath(isa.upper(), 'gem5.%s' % variant)
144        super(Gem5Fixture, self).__init__(target)
145
146        self.name = constants.gem5_binary_fixture_name
147        self.path = self.target
148        self.isa = isa
149        self.variant = variant
150
151
152class MakeFixture(Fixture):
153    def __init__(self, directory, *args, **kwargs):
154        name = 'make -C %s' % directory
155        super(MakeFixture, self).__init__(build_once=True, lazy_init=False,
156                                          name=name,
157                                          *args, **kwargs)
158        self.targets = []
159        self.directory = directory
160
161    def setup(self):
162        super(MakeFixture, self).setup()
163        targets = set(self.required_by)
164        command = ['make', '-C', self.directory]
165        command.extend([target.target for target in targets])
166        log_call(command)
167
168
169class MakeTarget(Fixture):
170    def __init__(self, target, make_fixture=None, *args, **kwargs):
171        '''
172        :param make_fixture: The make invocation we will be attached to.
173        Since we don't have a single global instance of make in gem5 like we do
174        scons we need to know what invocation to attach to. If none given,
175        creates its own.
176        '''
177        super(MakeTarget, self).__init__(name=target, *args, **kwargs)
178        self.target = self.name
179
180        if make_fixture is None:
181            make_fixture = MakeFixture(
182                    absdirpath(target),
183                    lazy_init=True,
184                    build_once=False)
185
186        self.make_fixture = make_fixture
187
188        # Add our self to the required targets of the main MakeFixture
189        self.require(self.make_fixture)
190
191    def setup(self, testitem):
192        super(MakeTarget, self).setup()
193        self.make_fixture.setup()
194        return self
195
196class TestProgram(MakeTarget):
197    def __init__(self, program, isa, os, recompile=False):
198        make_dir = joinpath('test-progs', program)
199        make_fixture = MakeFixture(make_dir)
200        target = joinpath('bin', isa, os, program)
201        super(TestProgram, self).__init__(target, make_fixture)
202        self.path = joinpath(make_dir, target)
203        self.recompile = recompile
204
205    def setup(self, testitem):
206        # Check if the program exists if it does then only compile if
207        # recompile was given.
208        if self.recompile:
209            super(MakeTarget, self).setup()
210        elif not os.path.exists(self.path):
211            super(MakeTarget, self).setup()
212
213class DownloadedProgram(Fixture):
214    """ Like TestProgram, but checks the version in the gem5 binary repository
215        and downloads an updated version if it is needed.
216    """
217    urlbase = "http://gem5.org/dist/current/"
218
219    def __init__(self, path, program, **kwargs):
220        super(DownloadedProgram, self).__init__("download-" + program,
221                                                build_once=True, **kwargs)
222
223        self.program_dir = path
224        self.path = joinpath(self.program_dir, program)
225        self.url = self.urlbase + self.path
226    def _download(self):
227        import urllib
228        log.test_log.debug("Downloading " + self.url + " to " + self.path)
229        if not os.path.exists(self.program_dir):
230            os.makedirs(self.program_dir)
231        urllib.urlretrieve(self.url, self.path)
232
233    def _getremotetime(self):
234        import  urllib2, datetime, time
235        import _strptime # Needed for python threading bug
236
237        u = urllib2.urlopen(self.url)
238        return time.mktime(datetime.datetime.strptime( \
239                    u.info().getheaders("Last-Modified")[0],
240                    "%a, %d %b %Y %X GMT").timetuple())
241
242    def setup(self, testitem):
243        import urllib2
244        # Check to see if there is a file downloaded
245        if not os.path.exists(self.path):
246            self._download()
247        else:
248            try:
249                t = self._getremotetime()
250            except urllib2.URLError:
251                # Problem checking the server, use the old files.
252                log.debug("Could not contact server. Binaries may be old.")
253                return
254            # If the server version is more recent, download it
255            if t > os.path.getmtime(self.path):
256                self._download()
257