source: tests/pybin/tools.py@ c5c0148

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 c5c0148 was ea2074e, checked in by Thierry Delisle <tdelisle@…>, 6 years ago

Fix crash archiving for tests in subfolders

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