source: tests/pybin/tools.py@ c1ee231

ADT arm-eh ast-experimental enum forall-pointer-decay jacob/cs343-translation new-ast new-ast-unique-expr pthread-emulation qualifiedEnum
Last change on this file since c1ee231 was 136f86b, checked in by Thierry Delisle <tdelisle@…>, 5 years ago

Test script can now run multiple configuration in a row

  • Property mode set to 100644
File size: 10.5 KB
Line 
1import __main__
2import argparse
3import contextlib
4import datetime
5import fileinput
6import multiprocessing
7import os
8import re
9import resource
10import signal
11import stat
12import subprocess
13import sys
14import tempfile
15import time
16import types
17
18from pybin import settings
19
20################################################################################
21# shell helpers
22################################################################################
23
24# helper functions to run terminal commands
25def sh(*cmd, timeout = False, output_file = None, input_file = None, input_text = None, error = subprocess.STDOUT, ignore_dry_run = False):
26 try:
27 cmd = list(cmd)
28
29 if input_file and input_text:
30 return 401, "Cannot use both text and file inputs"
31
32 # if this is a dry_run, only print the commands that would be ran
33 if settings.dry_run and not ignore_dry_run:
34 cmd = "{} cmd: {}".format(os.getcwd(), ' '.join(cmd))
35 if output_file and not isinstance(output_file, int):
36 cmd += " > "
37 cmd += output_file
38
39 if error and not isinstance(error, int):
40 cmd += " 2> "
41 cmd += error
42
43 if input_file and not isinstance(input_file, int) and os.path.isfile(input_file):
44 cmd += " < "
45 cmd += input_file
46
47 print(cmd)
48 return 0, None
49
50 with contextlib.ExitStack() as onexit:
51 # add input redirection if needed
52 input_file = openfd(input_file, 'r', onexit, True)
53
54 # add output redirection if needed
55 output_file = openfd(output_file, 'w', onexit, False)
56
57 # add error redirection if needed
58 error = openfd(error, 'w', onexit, False)
59
60 # run the desired command
61 # use with statement to make sure proc is cleaned
62 # don't use subprocess.run because we want to send SIGABRT on exit
63 with subprocess.Popen(
64 cmd,
65 **({'input' : bytes(input_text, encoding='utf-8')} if input_text else {'stdin' : input_file}),
66 stdout = output_file,
67 stderr = error
68 ) as proc:
69
70 try:
71 out, _ = proc.communicate(
72 timeout = settings.timeout.single if timeout else None
73 )
74
75 return proc.returncode, out.decode("utf-8") if out else None
76 except subprocess.TimeoutExpired:
77 if settings.timeout2gdb:
78 print("Process {} timeout".format(proc.pid))
79 proc.communicate()
80 return 124, str(None)
81 else:
82 proc.send_signal(signal.SIGABRT)
83 proc.communicate()
84 return 124, str(None)
85
86 except Exception as ex:
87 print ("Unexpected error: %s" % ex)
88 raise
89
90def is_ascii(fname):
91 if settings.dry_run:
92 print("is_ascii: %s" % fname)
93 return True
94
95 if not os.path.isfile(fname):
96 return False
97
98 code, out = sh("file %s" % fname, output_file=subprocess.PIPE)
99 if code != 0:
100 return False
101
102 match = re.search(".*: (.*)", out)
103
104 if not match:
105 return False
106
107 return match.group(1).startswith("ASCII text")
108
109def is_exe(fname):
110 return os.path.isfile(fname) and os.access(fname, os.X_OK)
111
112def openfd(file, mode, exitstack, checkfile):
113 if not file:
114 return file
115
116 if isinstance(file, int):
117 return file
118
119 if checkfile and not os.path.isfile(file):
120 return None
121
122 file = open(file, mode)
123 exitstack.push(file)
124 return file
125
126# Remove 1 or more files silently
127def rm( files ):
128 if isinstance(files, str ): files = [ files ]
129 for file in files:
130 sh( 'rm', '-f', file, output_file=subprocess.DEVNULL, error=subprocess.DEVNULL )
131
132# Create 1 or more directory
133def mkdir( files ):
134 if isinstance(files, str ): files = [ files ]
135 for file in files:
136 p = os.path.normpath( file )
137 d = os.path.dirname ( p )
138 sh( 'mkdir', '-p', d, output_file=subprocess.DEVNULL, error=subprocess.DEVNULL )
139
140
141def chdir( dest = __main__.__file__ ):
142 abspath = os.path.abspath(dest)
143 dname = os.path.dirname(abspath)
144 os.chdir(dname)
145
146# diff two files
147def diff( lhs, rhs ):
148 # fetch return code and error from the diff command
149 return sh(
150 '''diff''',
151 '''--text''',
152 '''--old-group-format=\t\tmissing lines :\n%<''',
153 '''--new-line-format=\t\t%dn\t%L''',
154 '''--new-group-format=\t\tnew lines : \n%>''',
155 '''--old-line-format=\t\t%dn\t%L''',
156 '''--unchanged-group-format=%=''',
157 '''--changed-group-format=\t\texpected :\n%<\t\tgot :\n%>''',
158 '''--unchanged-line-format=''',
159 lhs,
160 rhs,
161 output_file=subprocess.PIPE
162 )
163
164# call make
165def make(target, *, flags = '', output_file = None, error = None, error_file = None, silent = False):
166 test_param = """test="%s" """ % (error_file) if error_file else None
167 cmd = [
168 *settings.make,
169 '-s' if silent else None,
170 test_param,
171 settings.arch.flags,
172 settings.debug.flags,
173 settings.install.flags,
174 settings.distcc if settings.distribute else None,
175 flags,
176 target
177 ]
178 cmd = [s for s in cmd if s]
179 return sh(*cmd, output_file=output_file, error=error)
180
181def which(program):
182 fpath, fname = os.path.split(program)
183 if fpath:
184 if is_exe(program):
185 return program
186 else:
187 for path in os.environ["PATH"].split(os.pathsep):
188 exe_file = os.path.join(path, program)
189 if is_exe(exe_file):
190 return exe_file
191 return None
192
193@contextlib.contextmanager
194def tempdir():
195 cwd = os.getcwd()
196 with tempfile.TemporaryDirectory() as temp:
197 os.chdir(temp)
198 try:
199 yield temp
200 finally:
201 os.chdir(cwd)
202
203def killgroup():
204 try:
205 os.killpg(os.getpgrp(), signal.SIGINT)
206 except KeyboardInterrupt:
207 pass # expected
208 except Exception as exc:
209 print("Unexpected exception", file=sys.stderr)
210 print(exc, file=sys.stderr)
211 sys.stderr.flush()
212 sys.exit(2)
213
214################################################################################
215# file handling
216################################################################################
217# move a file
218def mv(source, dest):
219 ret, _ = sh("mv", source, dest)
220 return ret
221
222# cat one file into the other
223def cat(source, dest):
224 ret, _ = sh("cat", source, output_file=dest)
225 return ret
226
227# helper function to replace patterns in a file
228def file_replace(fname, pat, s_after):
229 if settings.dry_run:
230 print("replacing '%s' with '%s' in %s" % (pat, s_after, fname))
231 return
232
233 file = fileinput.FileInput(fname, inplace=True, backup='.bak')
234 for line in file:
235 print(line.replace(pat, s_after), end='')
236 file.close()
237
238# helper function to check if a files contains only a specific string
239def file_contains_only(file, text) :
240 with open(file) as f:
241 ff = f.read().strip()
242 result = ff == text.strip()
243
244 return result
245
246# transform path to canonical form
247def canonical_path(path):
248 abspath = os.path.abspath(__main__.__file__)
249 dname = os.path.dirname(abspath)
250 return os.path.join(dname, os.path.normpath(path) )
251
252# compare path even if form is different
253def path_cmp(lhs, rhs):
254 return canonical_path( lhs ) == canonical_path( rhs )
255
256# walk all files in a path
257def path_walk( op ):
258 dname = settings.SRCDIR
259 for dirname, _, names in os.walk(dname):
260 for name in names:
261 path = os.path.join(dirname, name)
262 op( path )
263
264################################################################################
265# system
266################################################################################
267# count number of jobs to create
268def job_count( options, tests ):
269 # check if the user already passed in a number of jobs for multi-threading
270 if not options.jobs:
271 make_flags = os.environ.get('MAKEFLAGS')
272 force = bool(make_flags)
273 make_jobs_fds = re.search("--jobserver-(auth|fds)=\s*([0-9]+),([0-9]+)", make_flags) if make_flags else None
274 if make_jobs_fds :
275 tokens = os.read(int(make_jobs_fds.group(2)), 1024)
276 options.jobs = len(tokens)
277 os.write(int(make_jobs_fds.group(3)), tokens)
278 else :
279 if settings.distribute:
280 ret, jstr = sh("distcc", "-j", output_file=subprocess.PIPE, ignore_dry_run=True)
281 if ret == 0:
282 options.jobs = int(jstr.strip())
283 else :
284 options.jobs = multiprocessing.cpu_count()
285 else:
286 options.jobs = multiprocessing.cpu_count()
287 else :
288 force = True
289
290 # make sure we have a valid number of jobs that corresponds to user input
291 if options.jobs <= 0 :
292 print('ERROR: Invalid number of jobs', file=sys.stderr)
293 sys.exit(1)
294
295 return min( options.jobs, len(tests) ), force
296
297# enable core dumps for all the test children
298resource.setrlimit(resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
299
300################################################################################
301# misc
302################################################################################
303
304# get hash for given configuration
305def config_hash():
306 path = os.path.normpath(os.path.join(
307 settings.SRCDIR,
308 ))
309
310 distcc_hash = os.path.join(settings.SRCDIR, '../tools/build/distcc_hash')
311 config = "%s-%s" % (settings.arch.target, settings.debug.path)
312 _, out = sh(distcc_hash, config, output_file=subprocess.PIPE, ignore_dry_run=True)
313 return out.strip()
314
315# get pretty string for time of day
316def pretty_now():
317 ts = time.time()
318 print(ts, file=sys.stderr)
319 return datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d_%H:%M:%S')
320
321# check if arguments is yes or no
322def yes_no(string):
323 if string == "yes" :
324 return True
325 if string == "no" :
326 return False
327 raise argparse.ArgumentTypeError(msg)
328
329# Convert a function that converts a string to one that converts comma separated string.
330def comma_separated(elements):
331 return lambda string: [elements(part) for part in string.split(',')]
332
333def fancy_print(text):
334 column = which('column')
335 if column:
336 subprocess.run(column, input=bytes(text + "\n", "UTF-8"))
337 else:
338 print(text)
339
340
341def core_info(path):
342 if not os.path.isfile(path):
343 return 1, "ERR Executable path is wrong"
344
345 cmd = os.path.join(settings.SRCDIR, "pybin/print-core.gdb")
346 if not os.path.isfile(cmd):
347 return 1, "ERR Printing format for core dumps not found"
348
349 core = os.path.join(os.getcwd(), "core" )
350
351 if not os.path.isfile(core):
352 return 1, "ERR No core dump"
353
354 return sh('gdb', '-n', path, core, '-batch', '-x', cmd, output_file=subprocess.PIPE)
355
356def core_archive(dst, name, exe):
357 # Get the core dump
358 core = os.path.join(os.getcwd(), "core" )
359
360 # update the path for this test
361 dst = os.path.join(dst, name)
362
363 # make a directory for this test
364 # mkdir makes the parent directory only so add a dummy
365 mkdir(os.path.join(dst, name ))
366
367 # moves the files
368 mv( core, os.path.join(dst, "core" ) )
369 mv( exe , os.path.join(dst, name ) )
370
371 # return explanatory test
372 return "Archiving %s (executable and core) to %s" % (os.path.relpath(exe, settings.BUILDDIR), os.path.relpath(dst, settings.original_path))
373
374class Timed:
375 def __enter__(self):
376 self.start = time.time()
377 return self
378
379 def __exit__(self, *args):
380 self.end = time.time()
381 self.duration = self.end - self.start
382
383def timed(src, timeout):
384 expire = time.time() + timeout
385 i = iter(src)
386 with contextlib.suppress(StopIteration):
387 while True:
388 yield i.next(max(expire - time.time(), 0))
Note: See TracBrowser for help on using the repository browser.