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