source: tests/pybin/tools.py @ 4cae032

ADTarm-ehast-experimentalenumforall-pointer-decayjacob/cs343-translationjenkins-sandboxnew-astnew-ast-unique-exprpthread-emulationqualifiedEnum
Last change on this file since 4cae032 was 8364209, checked in by Thierry Delisle <tdelisle@…>, 5 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.