tests,ext: Add a new testing library proposal
[gem5.git] / tests / gem5 / fixture.py
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
29 import os
30 import tempfile
31 import shutil
32
33 from testlib.fixture import Fixture, globalfixture
34 from testlib.config import config, constants
35 from testlib.helper import log_call, cacheresult, joinpath, absdirpath
36 import testlib.log as log
37
38
39 class VariableFixture(Fixture):
40 def __init__(self, value=None, name=None):
41 super(VariableFixture, self).__init__(name=name)
42 self.value = value
43
44
45 class 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
59 class 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
106 class 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
141 class 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
152 class 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
169 class 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
196 class 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
213 class 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 = joinpath('test-progs', path)
224 self.path = joinpath(self.program_dir, program)
225
226 self.url = self.urlbase + self.path
227
228 def _download(self):
229 import urllib
230 log.test_log.debug("Downloading " + self.url + " to " + self.path)
231 if not os.path.exists(self.program_dir):
232 os.makedirs(self.program_dir)
233 urllib.urlretrieve(self.url, self.path)
234
235 def _getremotetime(self):
236 import urllib2, datetime, time
237 import _strptime # Needed for python threading bug
238
239 u = urllib2.urlopen(self.url)
240 return time.mktime(datetime.datetime.strptime( \
241 u.info().getheaders("Last-Modified")[0],
242 "%a, %d %b %Y %X GMT").timetuple())
243
244 def setup(self, testitem):
245 import urllib2
246 # Check to see if there is a file downloaded
247 if not os.path.exists(self.path):
248 self._download()
249 else:
250 try:
251 t = self._getremotetime()
252 except urllib2.URLError:
253 # Problem checking the server, use the old files.
254 log.debug("Could not contact server. Binaries may be old.")
255 return
256 # If the server version is more recent, download it
257 if t > os.path.getmtime(self.path):
258 self._download()