source: tests/pybin/tools.py@ 0608e007

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

Tests now send SIGABRT instead and SIGTERM when a test takes too long

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