source: tests/pybin/tools.py @ 2a3b019

ADTarm-ehast-experimentalenumforall-pointer-decayjacob/cs343-translationnew-astnew-ast-unique-exprpthread-emulationqualifiedEnum
Last change on this file since 2a3b019 was d658183, checked in by Thierry Delisle <tdelisle@…>, 5 years ago

added test option to print process id instead of killing it to allow attaching with gdb

  • Property mode set to 100644
File size: 10.3 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
329def fancy_print(text):
330        column = which('column')
331        if column:
332                subprocess.run(column, input=bytes(text + "\n", "UTF-8"))
333        else:
334                print(text)
335
336
337def core_info(path):
338        if not os.path.isfile(path):
339                return 1, "ERR Executable path is wrong"
340
341        cmd   = os.path.join(settings.SRCDIR, "pybin/print-core.gdb")
342        if not os.path.isfile(cmd):
343                return 1, "ERR Printing format for core dumps not found"
344
345        core  = os.path.join(os.getcwd(), "core" )
346
347        if not os.path.isfile(core):
348                return 1, "ERR No core dump"
349
350        return sh('gdb', '-n', path, core, '-batch', '-x', cmd, output_file=subprocess.PIPE)
351
352def core_archive(dst, name, exe):
353        # Get the core dump
354        core = os.path.join(os.getcwd(), "core" )
355
356        # update the path for this test
357        dst  = os.path.join(dst, name)
358
359        # make a directory for this test
360        # mkdir makes the parent directory only so add a dummy
361        mkdir(os.path.join(dst, name ))
362
363        # moves the files
364        mv( core, os.path.join(dst, "core" ) )
365        mv( exe , os.path.join(dst, name   ) )
366
367        # return explanatory test
368        return "Archiving %s (executable and core) to %s" % (os.path.relpath(exe, settings.BUILDDIR), os.path.relpath(dst, settings.original_path))
369
370class Timed:
371        def __enter__(self):
372                self.start = time.time()
373                return self
374
375        def __exit__(self, *args):
376                self.end = time.time()
377                self.duration = self.end - self.start
378
379def timed(src, timeout):
380        expire = time.time() + timeout
381        i = iter(src)
382        with contextlib.suppress(StopIteration):
383                while True:
384                        yield i.next(max(expire - time.time(), 0))
Note: See TracBrowser for help on using the repository browser.