source: tests/pybin/tools.py @ 75baaa3

ADTarm-ehast-experimentalenumforall-pointer-decayjacob/cs343-translationnew-ast-unique-exprpthread-emulationqualifiedEnum
Last change on this file since 75baaa3 was 99581ee, checked in by Thierry Delisle <tdelisle@…>, 4 years ago

Tests now support the --ast flag, Makefile still doesn't

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