source: tests/pybin/tools.py@ d21dd3cb

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 d21dd3cb was f866d15, checked in by Thierry Delisle <tdelisle@…>, 5 years ago

test.py now warns if an .expect file is empty

  • Property mode set to 100644
File size: 11.1 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_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.arch.flags,
184 settings.debug.flags,
185 settings.install.flags,
186 settings.distcc if settings.distribute else None,
187 flags,
188 target
189 ]
190 cmd = [s for s in cmd if s]
191 return sh(*cmd, output_file=output_file, error=error)
192
193def which(program):
194 fpath, fname = os.path.split(program)
195 if fpath:
196 if is_exe(program):
197 return program
198 else:
199 for path in os.environ["PATH"].split(os.pathsep):
200 exe_file = os.path.join(path, program)
201 if is_exe(exe_file):
202 return exe_file
203 return None
204
205@contextlib.contextmanager
206def tempdir():
207 cwd = os.getcwd()
208 with tempfile.TemporaryDirectory() as temp:
209 os.chdir(temp)
210 try:
211 yield temp
212 finally:
213 os.chdir(cwd)
214
215def killgroup():
216 try:
217 os.killpg(os.getpgrp(), signal.SIGINT)
218 except KeyboardInterrupt:
219 pass # expected
220 except Exception as exc:
221 print("Unexpected exception", file=sys.stderr)
222 print(exc, file=sys.stderr)
223 sys.stderr.flush()
224 sys.exit(2)
225
226################################################################################
227# file handling
228################################################################################
229# move a file
230def mv(source, dest):
231 ret, _ = sh("mv", source, dest)
232 return ret
233
234# cat one file into the other
235def cat(source, dest):
236 ret, _ = sh("cat", source, output_file=dest)
237 return ret
238
239# helper function to replace patterns in a file
240def file_replace(fname, pat, s_after):
241 if settings.dry_run:
242 print("replacing '%s' with '%s' in %s" % (pat, s_after, fname))
243 return
244
245 file = fileinput.FileInput(fname, inplace=True, backup='.bak')
246 for line in file:
247 print(line.replace(pat, s_after), end='')
248 file.close()
249
250# helper function to check if a files contains only a specific string
251def file_contains_only(file, text) :
252 with open(file, encoding="latin-1") as f: # use latin-1 so all chars mean something.
253 ff = f.read().strip()
254 result = ff == text.strip()
255
256 return result
257
258# transform path to canonical form
259def canonical_path(path):
260 abspath = os.path.abspath(os.path.realpath(__main__.__file__))
261 dname = os.path.dirname(abspath)
262 return os.path.join(dname, os.path.normpath(path) )
263
264# compare path even if form is different
265def path_cmp(lhs, rhs):
266 return canonical_path( lhs ) == canonical_path( rhs )
267
268# walk all files in a path
269def path_walk( op ):
270 dname = settings.SRCDIR
271 for dirname, _, names in os.walk(dname):
272 for name in names:
273 path = os.path.join(dirname, name)
274 op( path )
275
276################################################################################
277# system
278################################################################################
279# count number of jobs to create
280def job_count( options, tests ):
281 # check if the user already passed in a number of jobs for multi-threading
282 if not options.jobs:
283 make_flags = os.environ.get('MAKEFLAGS')
284 force = bool(make_flags)
285 make_jobs_fds = re.search("--jobserver-(auth|fds)=\s*([0-9]+),([0-9]+)", make_flags) if make_flags else None
286 if make_jobs_fds :
287 tokens = os.read(int(make_jobs_fds.group(2)), 1024)
288 options.jobs = len(tokens)
289 os.write(int(make_jobs_fds.group(3)), tokens)
290 else :
291 if settings.distribute:
292 ret, jstr = sh("distcc", "-j", output_file=subprocess.PIPE, ignore_dry_run=True)
293 if ret == 0:
294 options.jobs = int(jstr.strip())
295 else :
296 options.jobs = multiprocessing.cpu_count()
297 else:
298 options.jobs = multiprocessing.cpu_count()
299 else :
300 force = True
301
302 # make sure we have a valid number of jobs that corresponds to user input
303 if options.jobs <= 0 :
304 print('ERROR: Invalid number of jobs', file=sys.stderr)
305 sys.exit(1)
306
307 return min( options.jobs, len(tests) ), force
308
309# enable core dumps for all the test children
310resource.setrlimit(resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
311
312################################################################################
313# misc
314################################################################################
315
316# get hash for given configuration
317def config_hash():
318 path = os.path.normpath(os.path.join(
319 settings.SRCDIR,
320 ))
321
322 distcc_hash = os.path.join(settings.SRCDIR, '../tools/build/distcc_hash')
323 config = "%s-%s" % (settings.arch.target, settings.debug.path)
324 _, out = sh(distcc_hash, config, output_file=subprocess.PIPE, ignore_dry_run=True)
325 return out.strip()
326
327# get pretty string for time of day
328def pretty_now():
329 ts = time.time()
330 print(ts, file=sys.stderr)
331 return datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d_%H:%M:%S')
332
333# check if arguments is yes or no
334def yes_no(string):
335 if string == "yes" :
336 return True
337 if string == "no" :
338 return False
339 raise argparse.ArgumentTypeError(msg)
340
341# Convert a function that converts a string to one that converts comma separated string.
342def comma_separated(elements):
343 return lambda string: [elements(part) for part in string.split(',')]
344
345def fancy_print(text):
346 column = which('column')
347 if column:
348 subprocess.run(column, input=bytes(text + "\n", "UTF-8"))
349 else:
350 print(text)
351
352
353def core_info(path):
354 if not os.path.isfile(path):
355 return 1, "ERR Executable path is wrong"
356
357 cmd = os.path.join(settings.SRCDIR, "pybin/print-core.gdb")
358 if not os.path.isfile(cmd):
359 return 1, "ERR Printing format for core dumps not found"
360
361 core = os.path.join(os.getcwd(), "core" )
362
363 if not os.path.isfile(core):
364 return 1, "ERR No core dump"
365
366 return sh('gdb', '-n', path, core, '-batch', '-x', cmd, output_file=subprocess.PIPE)
367
368def core_archive(dst, name, exe):
369 # Get the core dump
370 core = os.path.join(os.getcwd(), "core" )
371
372 # update the path for this test
373 dst = os.path.join(dst, name)
374
375 # make a directory for this test
376 # mkdir makes the parent directory only so add a dummy
377 mkdir(os.path.join(dst, name ))
378
379 # moves the files
380 mv( core, os.path.join(dst, "core" ) )
381 mv( exe , os.path.join(dst, name ) )
382
383 # return explanatory test
384 return "Archiving %s (executable and core) to %s" % (os.path.relpath(exe, settings.BUILDDIR), os.path.relpath(dst, settings.original_path))
385
386class Timed:
387 def __enter__(self):
388 self.start = time.time()
389 return self
390
391 def __exit__(self, *args):
392 self.end = time.time()
393 self.duration = self.end - self.start
394
395def timed(src, timeout):
396 expire = time.time() + timeout
397 i = iter(src)
398 with contextlib.suppress(StopIteration):
399 while True:
400 yield i.next(max(expire - time.time(), 0))
401
402def fmtDur( duration ):
403 if duration :
404 hours, rem = divmod(duration, 3600)
405 minutes, rem = divmod(rem, 60)
406 seconds, millis = divmod(rem, 1)
407 return "%2d:%02d.%03d" % (minutes, seconds, millis * 1000)
408 return " n/a"
Note: See TracBrowser for help on using the repository browser.