source: tests/pybin/tools.py@ 5936244

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

Fixed incorrect encoding on diff result that could lead to crash

  • Property mode set to 100644
File size: 11.2 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("latin-1") 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_empty(fname):
91 if not os.path.isfile(fname):
92 return True
93
94 if os.stat(fname).st_size == 0:
95 return True
96
97 return False
98
99def is_ascii(fname):
100 if settings.dry_run:
101 print("is_ascii: %s" % fname)
102 return (True, "")
103
104 if not os.path.isfile(fname):
105 return (False, "No file")
106
107 code, out = sh("file", fname, output_file=subprocess.PIPE)
108 if code != 0:
109 return (False, "'file EXPECT' failed with code {}".format(code))
110
111 match = re.search(".*: (.*)", out)
112
113 if not match:
114 return (False, "Unreadable file type: '{}'".format(out))
115
116 if "ASCII text" in match.group(1):
117 return (True, "")
118
119 return (False, "File type should be 'ASCII text', was '{}'".format(match.group(1)))
120
121def is_exe(fname):
122 return os.path.isfile(fname) and os.access(fname, os.X_OK)
123
124def openfd(file, mode, exitstack, checkfile):
125 if not file:
126 return file
127
128 if isinstance(file, int):
129 return file
130
131 if checkfile and not os.path.isfile(file):
132 return None
133
134 file = open(file, mode, encoding="latin-1") # use latin-1 so all chars mean something.
135 exitstack.push(file)
136 return file
137
138# Remove 1 or more files silently
139def rm( files ):
140 if isinstance(files, str ): files = [ files ]
141 for file in files:
142 sh( 'rm', '-f', file, output_file=subprocess.DEVNULL, error=subprocess.DEVNULL )
143
144# Create 1 or more directory
145def mkdir( files ):
146 if isinstance(files, str ): files = [ files ]
147 for file in files:
148 p = os.path.normpath( file )
149 d = os.path.dirname ( p )
150 sh( 'mkdir', '-p', d, output_file=subprocess.DEVNULL, error=subprocess.DEVNULL )
151
152
153def chdir( dest = __main__.__file__ ):
154 abspath = os.path.abspath(dest)
155 dname = os.path.dirname(abspath)
156 os.chdir(dname)
157
158# diff two files
159def diff( lhs, rhs ):
160 # fetch return code and error from the diff command
161 return sh(
162 '''diff''',
163 '''--text''',
164 '''--old-group-format=\t\tmissing lines :\n%<''',
165 '''--new-line-format=\t\t%dn\t%L''',
166 '''--new-group-format=\t\tnew lines : \n%>''',
167 '''--old-line-format=\t\t%dn\t%L''',
168 '''--unchanged-group-format=%=''',
169 '''--changed-group-format=\t\texpected :\n%<\t\tgot :\n%>''',
170 '''--unchanged-line-format=''',
171 lhs,
172 rhs,
173 output_file=subprocess.PIPE
174 )
175
176# call make
177def make(target, *, flags = '', output_file = None, error = None, error_file = None, silent = False):
178 test_param = """test="%s" """ % (error_file) if error_file else None
179 cmd = [
180 *settings.make,
181 '-s' if silent else None,
182 test_param,
183 settings.ast.flags,
184 settings.arch.flags,
185 settings.debug.flags,
186 settings.install.flags,
187 settings.distcc if settings.distribute else None,
188 flags,
189 target
190 ]
191 cmd = [s for s in cmd if s]
192 return sh(*cmd, output_file=output_file, error=error)
193
194def which(program):
195 fpath, fname = os.path.split(program)
196 if fpath:
197 if is_exe(program):
198 return program
199 else:
200 for path in os.environ["PATH"].split(os.pathsep):
201 exe_file = os.path.join(path, program)
202 if is_exe(exe_file):
203 return exe_file
204 return None
205
206@contextlib.contextmanager
207def tempdir():
208 cwd = os.getcwd()
209 with tempfile.TemporaryDirectory() as temp:
210 os.chdir(temp)
211 try:
212 yield temp
213 finally:
214 os.chdir(cwd)
215
216def killgroup():
217 try:
218 os.killpg(os.getpgrp(), signal.SIGINT)
219 except KeyboardInterrupt:
220 pass # expected
221 except Exception as exc:
222 print("Unexpected exception", file=sys.stderr)
223 print(exc, file=sys.stderr)
224 sys.stderr.flush()
225 sys.exit(2)
226
227################################################################################
228# file handling
229################################################################################
230# move a file
231def mv(source, dest):
232 ret, _ = sh("mv", source, dest)
233 return ret
234
235# cat one file into the other
236def cat(source, dest):
237 ret, _ = sh("cat", source, output_file=dest)
238 return ret
239
240# helper function to replace patterns in a file
241def file_replace(fname, pat, s_after):
242 if settings.dry_run:
243 print("replacing '%s' with '%s' in %s" % (pat, s_after, fname))
244 return
245
246 file = fileinput.FileInput(fname, inplace=True, backup='.bak')
247 for line in file:
248 print(line.replace(pat, s_after), end='')
249 file.close()
250
251# helper function to check if a files contains only a specific string
252def file_contains_only(file, text) :
253 with open(file, encoding="latin-1") as f: # use latin-1 so all chars mean something.
254 ff = f.read().strip()
255 result = ff == text.strip()
256
257 return result
258
259# transform path to canonical form
260def canonical_path(path):
261 abspath = os.path.abspath(os.path.realpath(__main__.__file__))
262 dname = os.path.dirname(abspath)
263 return os.path.join(dname, os.path.normpath(path) )
264
265# compare path even if form is different
266def path_cmp(lhs, rhs):
267 return canonical_path( lhs ) == canonical_path( rhs )
268
269# walk all files in a path
270def path_walk( op ):
271 dname = settings.SRCDIR
272 for dirname, _, names in os.walk(dname):
273 for name in names:
274 path = os.path.join(dirname, name)
275 op( path )
276
277################################################################################
278# system
279################################################################################
280# count number of jobs to create
281def job_count( options, tests ):
282 # check if the user already passed in a number of jobs for multi-threading
283 if not options.jobs:
284 make_flags = os.environ.get('MAKEFLAGS')
285 force = bool(make_flags)
286 make_jobs_fds = re.search("--jobserver-(auth|fds)=\s*([0-9]+),([0-9]+)", make_flags) if make_flags else None
287 if make_jobs_fds :
288 tokens = os.read(int(make_jobs_fds.group(2)), 1024)
289 options.jobs = len(tokens)
290 os.write(int(make_jobs_fds.group(3)), tokens)
291 else :
292 if settings.distribute:
293 ret, jstr = sh("distcc", "-j", output_file=subprocess.PIPE, ignore_dry_run=True)
294 if ret == 0:
295 options.jobs = int(jstr.strip())
296 else :
297 options.jobs = multiprocessing.cpu_count()
298 else:
299 options.jobs = multiprocessing.cpu_count()
300 else :
301 force = True
302
303 # make sure we have a valid number of jobs that corresponds to user input
304 if options.jobs <= 0 :
305 print('ERROR: Invalid number of jobs', file=sys.stderr)
306 sys.exit(1)
307
308 return min( options.jobs, len(tests) ), force
309
310# enable core dumps for all the test children
311resource.setrlimit(resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
312
313################################################################################
314# misc
315################################################################################
316
317# get hash for given configuration
318def config_hash():
319 path = os.path.normpath(os.path.join(
320 settings.SRCDIR,
321 ))
322
323 distcc_hash = os.path.join(settings.SRCDIR, '../tools/build/distcc_hash')
324 config = "%s-%s" % (settings.arch.target, settings.debug.path)
325 _, out = sh(distcc_hash, config, output_file=subprocess.PIPE, ignore_dry_run=True)
326 return out.strip()
327
328# get pretty string for time of day
329def pretty_now():
330 ts = time.time()
331 print(ts, file=sys.stderr)
332 return datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d_%H:%M:%S')
333
334# check if arguments is yes or no
335def yes_no(string):
336 if string == "yes" :
337 return True
338 if string == "no" :
339 return False
340 raise argparse.ArgumentTypeError(msg)
341
342# Convert a function that converts a string to one that converts comma separated string.
343def comma_separated(elements):
344 return lambda string: [elements(part) for part in string.split(',')]
345
346def fancy_print(text):
347 column = which('column')
348 if column:
349 subprocess.run(column, input=bytes(text + "\n", "UTF-8"))
350 else:
351 print(text)
352
353
354def core_info(path):
355 if not os.path.isfile(path):
356 return 1, "ERR Executable path is wrong"
357
358 cmd = os.path.join(settings.SRCDIR, "pybin/print-core.gdb")
359 if not os.path.isfile(cmd):
360 return 1, "ERR Printing format for core dumps not found"
361
362 core = os.path.join(os.getcwd(), "core" )
363
364 if not os.path.isfile(core):
365 return 1, "ERR No core dump"
366
367 return sh('gdb', '-n', path, core, '-batch', '-x', cmd, output_file=subprocess.PIPE)
368
369def core_archive(dst, name, exe):
370 # Get the core dump
371 core = os.path.join(os.getcwd(), "core" )
372
373 # update the path for this test
374 dst = os.path.join(dst, name)
375
376 # make a directory for this test
377 # mkdir makes the parent directory only so add a dummy
378 mkdir(os.path.join(dst, name ))
379
380 # moves the files
381 mv( core, os.path.join(dst, "core" ) )
382 mv( exe , os.path.join(dst, name ) )
383
384 # return explanatory test
385 return "Archiving %s (executable and core) to %s" % (os.path.relpath(exe, settings.BUILDDIR), os.path.relpath(dst, settings.original_path))
386
387class Timed:
388 def __enter__(self):
389 self.start = time.time()
390 return self
391
392 def __exit__(self, *args):
393 self.end = time.time()
394 self.duration = self.end - self.start
395
396def timed(src, timeout):
397 expire = time.time() + timeout
398 i = iter(src)
399 with contextlib.suppress(StopIteration):
400 while True:
401 yield i.next(max(expire - time.time(), 0))
402
403def fmtDur( duration ):
404 if duration :
405 hours, rem = divmod(duration, 3600)
406 minutes, rem = divmod(rem, 60)
407 seconds, millis = divmod(rem, 1)
408 return "%2d:%02d.%03d" % (minutes, seconds, millis * 1000)
409 return " n/a"
Note: See TracBrowser for help on using the repository browser.