ext: Remove dead code from loader.py
[gem5.git] / ext / testlib / helper.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 '''
30 Helper classes for writing tests with this test library.
31 '''
32 from collections import MutableSet
33
34 import difflib
35 import errno
36 import os
37 import re
38 import shutil
39 import stat
40 import subprocess
41 import tempfile
42 import threading
43 import time
44
45 #TODO Tear out duplicate logic from the sandbox IOManager
46 def log_call(logger, command, *popenargs, **kwargs):
47 '''
48 Calls the given process and automatically logs the command and output.
49
50 If stdout or stderr are provided output will also be piped into those
51 streams as well.
52
53 :params stdout: Iterable of items to write to as we read from the
54 subprocess.
55
56 :params stderr: Iterable of items to write to as we read from the
57 subprocess.
58 '''
59 if isinstance(command, str):
60 cmdstr = command
61 else:
62 cmdstr = ' '.join(command)
63
64 logger_callback = logger.trace
65 logger.trace('Logging call to command: %s' % cmdstr)
66
67 stdout_redirect = kwargs.get('stdout', tuple())
68 stderr_redirect = kwargs.get('stderr', tuple())
69
70 if hasattr(stdout_redirect, 'write'):
71 stdout_redirect = (stdout_redirect,)
72 if hasattr(stderr_redirect, 'write'):
73 stderr_redirect = (stderr_redirect,)
74
75 kwargs['stdout'] = subprocess.PIPE
76 kwargs['stderr'] = subprocess.PIPE
77 p = subprocess.Popen(command, *popenargs, **kwargs)
78
79 def log_output(log_callback, pipe, redirects=tuple()):
80 # Read iteractively, don't allow input to fill the pipe.
81 for line in iter(pipe.readline, b''):
82 line = line.decode("utf-8")
83 for r in redirects:
84 r.write(line)
85 log_callback(line.rstrip())
86
87 stdout_thread = threading.Thread(target=log_output,
88 args=(logger_callback, p.stdout, stdout_redirect))
89 stdout_thread.setDaemon(True)
90 stderr_thread = threading.Thread(target=log_output,
91 args=(logger_callback, p.stderr, stderr_redirect))
92 stderr_thread.setDaemon(True)
93
94 stdout_thread.start()
95 stderr_thread.start()
96
97 retval = p.wait()
98 stdout_thread.join()
99 stderr_thread.join()
100 # Return the return exit code of the process.
101 if retval != 0:
102 raise subprocess.CalledProcessError(retval, cmdstr)
103
104 # lru_cache stuff (Introduced in python 3.2+)
105 # Renamed and modified to cacheresult
106 class _HashedSeq(list):
107 '''
108 This class guarantees that hash() will be called no more than once per
109 element. This is important because the cacheresult() will hash the key
110 multiple times on a cache miss.
111
112 .. note:: From cpython 3.7
113 '''
114
115 __slots__ = 'hashvalue'
116
117 def __init__(self, tup, hash=hash):
118 self[:] = tup
119 self.hashvalue = hash(tup)
120
121 def __hash__(self):
122 return self.hashvalue
123
124 def _make_key(args, kwds, typed,
125 kwd_mark = (object(),),
126 fasttypes = {int, str, frozenset, type(None)},
127 tuple=tuple, type=type, len=len):
128 '''
129 Make a cache key from optionally typed positional and keyword arguments.
130 The key is constructed in a way that is flat as possible rather than as
131 a nested structure that would take more memory. If there is only a single
132 argument and its data type is known to cache its hash value, then that
133 argument is returned without a wrapper. This saves space and improves
134 lookup speed.
135
136 .. note:: From cpython 3.7
137 '''
138 key = args
139 if kwds:
140 key += kwd_mark
141 for item in kwds.items():
142 key += item
143 if typed:
144 key += tuple(type(v) for v in args)
145 if kwds:
146 key += tuple(type(v) for v in kwds.values())
147 elif len(key) == 1 and type(key[0]) in fasttypes:
148 return key[0]
149 return _HashedSeq(key)
150
151
152 def cacheresult(function, typed=False):
153 '''
154 :param typed: If typed is True, arguments of different types will be
155 cached separately. I.e. f(3.0) and f(3) will be treated as distinct
156 calls with distinct results.
157
158 .. note:: From cpython 3.7
159 '''
160 sentinel = object() # unique object used to signal cache misses
161 cache = {}
162 def wrapper(*args, **kwds):
163 # Simple caching without ordering or size limit
164 key = _make_key(args, kwds, typed)
165 result = cache.get(key, sentinel)
166 if result is not sentinel:
167 return result
168 result = function(*args, **kwds)
169 cache[key] = result
170 return result
171 return wrapper
172
173 class OrderedSet(MutableSet):
174 '''
175 Maintain ordering of insertion in items to the set with quick iteration.
176
177 http://code.activestate.com/recipes/576694/
178 '''
179
180 def __init__(self, iterable=None):
181 self.end = end = []
182 end += [None, end, end] # sentinel node for doubly linked list
183 self.map = {} # key --> [key, prev, next]
184 if iterable is not None:
185 self |= iterable
186
187 def __len__(self):
188 return len(self.map)
189
190 def __contains__(self, key):
191 return key in self.map
192
193 def add(self, key):
194 if key not in self.map:
195 end = self.end
196 curr = end[1]
197 curr[2] = end[1] = self.map[key] = [key, curr, end]
198
199 def update(self, keys):
200 for key in keys:
201 self.add(key)
202
203 def discard(self, key):
204 if key in self.map:
205 key, prev, next = self.map.pop(key)
206 prev[2] = next
207 next[1] = prev
208
209 def __iter__(self):
210 end = self.end
211 curr = end[2]
212 while curr is not end:
213 yield curr[0]
214 curr = curr[2]
215
216 def __reversed__(self):
217 end = self.end
218 curr = end[1]
219 while curr is not end:
220 yield curr[0]
221 curr = curr[1]
222
223 def pop(self, last=True):
224 if not self:
225 raise KeyError('set is empty')
226 key = self.end[1][0] if last else self.end[2][0]
227 self.discard(key)
228 return key
229
230 def __repr__(self):
231 if not self:
232 return '%s()' % (self.__class__.__name__,)
233 return '%s(%r)' % (self.__class__.__name__, list(self))
234
235 def __eq__(self, other):
236 if isinstance(other, OrderedSet):
237 return len(self) == len(other) and list(self) == list(other)
238 return set(self) == set(other)
239
240 def absdirpath(path):
241 '''
242 Return the directory component of the absolute path of the given path.
243 '''
244 return os.path.dirname(os.path.abspath(path))
245
246 joinpath = os.path.join
247
248 def mkdir_p(path):
249 '''
250 Same thing as mkdir -p
251
252 https://stackoverflow.com/a/600612
253 '''
254 try:
255 os.makedirs(path)
256 except OSError as exc: # Python >2.5
257 if exc.errno == errno.EEXIST and os.path.isdir(path):
258 pass
259 else:
260 raise
261
262
263 class FrozenSetException(Exception):
264 '''Signals one tried to set a value in a 'frozen' object.'''
265 pass
266
267
268 class AttrDict(object):
269 '''Object which exposes its own internal dictionary through attributes.'''
270 def __init__(self, dict_={}):
271 self.update(dict_)
272
273 def __getattr__(self, attr):
274 dict_ = self.__dict__
275 if attr in dict_:
276 return dict_[attr]
277 raise AttributeError('Could not find %s attribute' % attr)
278
279 def __setattr__(self, attr, val):
280 self.__dict__[attr] = val
281
282 def __iter__(self):
283 return iter(self.__dict__)
284
285 def __getitem__(self, item):
286 return self.__dict__[item]
287
288 def update(self, items):
289 self.__dict__.update(items)
290
291
292 class FrozenAttrDict(AttrDict):
293 '''An AttrDict whose attributes cannot be modified directly.'''
294 __initialized = False
295 def __init__(self, dict_={}):
296 super(FrozenAttrDict, self).__init__(dict_)
297 self.__initialized = True
298
299 def __setattr__(self, attr, val):
300 if self.__initialized:
301 raise FrozenSetException(
302 'Cannot modify an attribute in a FozenAttrDict')
303 else:
304 super(FrozenAttrDict, self).__setattr__(attr, val)
305
306 def update(self, items):
307 if self.__initialized:
308 raise FrozenSetException(
309 'Cannot modify an attribute in a FozenAttrDict')
310 else:
311 super(FrozenAttrDict, self).update(items)
312
313
314 class InstanceCollector(object):
315 '''
316 A class used to simplify collecting of Classes.
317
318 >> instance_list = collector.create()
319 >> # Create a bunch of classes which call collector.collect(self)
320 >> # instance_list contains all instances created since
321 >> # collector.create was called
322 >> collector.remove(instance_list)
323 '''
324 def __init__(self):
325 self.collectors = []
326
327 def create(self):
328 collection = []
329 self.collectors.append(collection)
330 return collection
331
332 def remove(self, collector):
333 self.collectors.remove(collector)
334
335 def collect(self, instance):
336 for col in self.collectors:
337 col.append(instance)
338
339
340 def append_dictlist(dict_, key, value):
341 '''
342 Append the `value` to a list associated with `key` in `dict_`.
343 If `key` doesn't exist, create a new list in the `dict_` with value in it.
344 '''
345 list_ = dict_.get(key, [])
346 list_.append(value)
347 dict_[key] = list_
348
349 def _filter_file(fname, filters):
350 with open(fname, "r") as file_:
351 for line in file_:
352 for regex in filters:
353 if re.match(regex, line):
354 break
355 else:
356 yield line
357
358
359 def _copy_file_keep_perms(source, target):
360 '''Copy a file keeping the original permisions of the target.'''
361 st = os.stat(target)
362 shutil.copy2(source, target)
363 os.chown(target, st[stat.ST_UID], st[stat.ST_GID])
364
365
366 def _filter_file_inplace(fname, dir, filters):
367 '''
368 Filter the given file writing filtered lines out to a temporary file, then
369 copy that tempfile back into the original file.
370 '''
371 (_, tfname) = tempfile.mkstemp(dir=dir, text=True)
372 with open(tfname, 'w') as tempfile_:
373 for line in _filter_file(fname, filters):
374 tempfile_.write(line)
375
376 # Now filtered output is into tempfile_
377 _copy_file_keep_perms(tfname, fname)
378
379
380 def diff_out_file(ref_file, out_file, logger, ignore_regexes=tuple()):
381 '''Diff two files returning the diff as a string.'''
382
383 if not os.path.exists(ref_file):
384 raise OSError("%s doesn't exist in reference directory"\
385 % ref_file)
386 if not os.path.exists(out_file):
387 raise OSError("%s doesn't exist in output directory" % out_file)
388
389 _filter_file_inplace(out_file, os.path.dirname(out_file), ignore_regexes)
390 _filter_file_inplace(ref_file, os.path.dirname(out_file), ignore_regexes)
391
392 #try :
393 (_, tfname) = tempfile.mkstemp(dir=os.path.dirname(out_file), text=True)
394 with open(tfname, 'r+') as tempfile_:
395 try:
396 log_call(logger, ['diff', out_file, ref_file], stdout=tempfile_)
397 except OSError:
398 # Likely signals that diff does not exist on this system. fallback
399 # to difflib
400 with open(out_file, 'r') as outf, open(ref_file, 'r') as reff:
401 diff = difflib.unified_diff(iter(reff.readline, ''),
402 iter(outf.readline, ''),
403 fromfile=ref_file,
404 tofile=out_file)
405 return ''.join(diff)
406 except subprocess.CalledProcessError:
407 tempfile_.seek(0)
408 return ''.join(tempfile_.readlines())
409 else:
410 return None
411
412 class Timer():
413 def __init__(self):
414 self.restart()
415
416 def restart(self):
417 self._start = self.timestamp()
418 self._stop = None
419
420 def stop(self):
421 self._stop = self.timestamp()
422 return self._stop - self._start
423
424 def runtime(self):
425 return self._stop - self._start
426
427 def active_time(self):
428 return self.timestamp() - self._start
429
430 @staticmethod
431 def timestamp():
432 return time.time()