source: tests/pybin/tools.py @ 0bf5340

ADTarm-ehast-experimentalenumforall-pointer-decayjacob/cs343-translationjenkins-sandboxnew-astnew-ast-unique-exprpthread-emulationqualifiedEnum
Last change on this file since 0bf5340 was dcfedca, checked in by Thierry Delisle <tdelisle@…>, 5 years ago

Test script can now archive errors (copy executables and core dumps to a specified directory)
run --help command for detail

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