source: tests/pybin/tools.py @ 8209e70

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

Merge branch 'master' into distcc

  • Property mode set to 100644
File size: 9.6 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
[d65f92c]25def sh(*cmd, timeout = False, output_file = None, input_file = None, input_text = None, error = subprocess.STDOUT):
[a45fc7b]26        cmd = list(cmd)
[1bb2488]27
[d65f92c]28        if input_file and input_text:
29                return 401, "Cannot use both text and file inputs"
30
[bacc36c]31        # if this is a dry_run, only print the commands that would be ran
32        if settings.dry_run :
[f806b61]33                cmd = "{} cmd: {}".format(os.getcwd(), ' '.join(cmd))
[d65f92c]34                if output_file and not isinstance(output_file, int):
[ea62265]35                        cmd += " > "
[d65f92c]36                        cmd += output_file
[ea62265]37
[f806b61]38                if error and not isinstance(error, int):
[ea62265]39                        cmd += " 2> "
40                        cmd += error
41
[d65f92c]42                if input_file and not isinstance(input_file, int) and os.path.isfile(input_file):
[ea62265]43                        cmd += " < "
[d65f92c]44                        cmd += input_file
[ea62265]45
46                print(cmd)
[c07d724]47                return 0, None
[bacc36c]48
[a45fc7b]49        with contextlib.ExitStack() as onexit:
50                # add input redirection if needed
[d65f92c]51                input_file = openfd(input_file, 'r', onexit, True)
[a45fc7b]52
53                # add output redirection if needed
[d65f92c]54                output_file = openfd(output_file, 'w', onexit, False)
[f806b61]55
56                # add error redirection if needed
57                error = openfd(error, 'w', onexit, False)
[a45fc7b]58
59                # run the desired command
[2b10f95]60                try:
61                        proc = subprocess.run(
62                                cmd,
[d65f92c]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
[2b10f95]67                        )
[d65f92c]68
[2b10f95]69                        return proc.returncode, proc.stdout.decode("utf-8") if proc.stdout else None
70                except subprocess.TimeoutExpired:
71                        return 124, str(None)
[c07d724]72
[f85bc15]73def is_ascii(fname):
[202ad72]74        if settings.dry_run:
75                print("is_ascii: %s" % fname)
76                return True
77
[f85bc15]78        if not os.path.isfile(fname):
79                return False
80
[d65f92c]81        code, out = sh("file %s" % fname, output_file=subprocess.PIPE)
[f85bc15]82        if code != 0:
83                return False
84
85        match = re.search(".*: (.*)", out)
86
87        if not match:
88                return False
89
[e1bdccb]90        return match.group(1).startswith("ASCII text")
[f85bc15]91
[5bf1f3e]92def is_exe(fname):
93        return os.path.isfile(fname) and os.access(fname, os.X_OK)
94
[f806b61]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
[c07d724]109# Remove 1 or more files silently
[bacc36c]110def rm( files ):
[5b993e0]111        if isinstance(files, str ): files = [ files ]
112        for file in files:
[d65f92c]113                sh( 'rm', '-f', file, output_file=subprocess.DEVNULL, error=subprocess.DEVNULL )
[c07d724]114
[a95c117]115# Create 1 or more directory
116def mkdir( files ):
[5b993e0]117        if isinstance(files, str ): files = [ files ]
118        for file in files:
119                p = os.path.normpath( file )
120                d = os.path.dirname ( p )
[d65f92c]121                sh( 'mkdir', '-p', d, output_file=subprocess.DEVNULL, error=subprocess.DEVNULL )
[28582b2]122
[a95c117]123
[c07d724]124def chdir( dest = __main__.__file__ ):
125        abspath = os.path.abspath(dest)
126        dname = os.path.dirname(abspath)
127        os.chdir(dname)
128
[bacc36c]129# diff two files
130def diff( lhs, rhs ):
131        # fetch return code and error from the diff command
[a45fc7b]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,
[d65f92c]144                output_file=subprocess.PIPE
[a45fc7b]145        )
[bacc36c]146
147# call make
[d65f92c]148def make(target, *, flags = '', output_file = None, error = None, error_file = None, silent = False):
[a45fc7b]149        test_param = """test="%s" """ % (error_file) if error_file else None
150        cmd = [
151                *settings.make,
152                '-s' if silent else None,
[bacc36c]153                test_param,
[575a6e5]154                settings.arch.flags,
[f3b9efc]155                settings.debug.flags,
[a5121bf]156                settings.install.flags,
[d65f92c]157                settings.distcc if settings.distribute else None,
[bacc36c]158                flags,
[a45fc7b]159                target
160        ]
161        cmd = [s for s in cmd if s]
[d65f92c]162        return sh(*cmd, output_file=output_file, error=error)
[bacc36c]163
[ed45af6]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
[0c13238]176
[f806b61]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
[35a408b7]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
[bacc36c]198################################################################################
199#               file handling
200################################################################################
[0c13238]201# move a file
202def mv(source, dest):
[a45fc7b]203        ret, _ = sh("mv", source, dest)
[0c13238]204        return ret
205
206# cat one file into the other
207def cat(source, dest):
[d65f92c]208        ret, _ = sh("cat", source, output_file=dest)
[0c13238]209        return ret
[bacc36c]210
[c07d724]211# helper function to replace patterns in a file
212def file_replace(fname, pat, s_after):
[202ad72]213        if settings.dry_run:
214                print("replacing '%s' with '%s' in %s" % (pat, s_after, fname))
215                return
216
[f85bc15]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()
[c07d724]221
[0ad0c55]222# helper function to check if a files contains only a specific string
[5bf1f3e]223def file_contains_only(file, text) :
[c07d724]224        with open(file) as f:
225                ff = f.read().strip()
226                result = ff == text.strip()
227
[5bf1f3e]228                return result
[c07d724]229
[bacc36c]230# transform path to canonical form
[5bf1f3e]231def canonical_path(path):
[f85bc15]232        abspath = os.path.abspath(__main__.__file__)
233        dname = os.path.dirname(abspath)
234        return os.path.join(dname, os.path.normpath(path) )
[c07d724]235
[bacc36c]236# compare path even if form is different
[5bf1f3e]237def path_cmp(lhs, rhs):
238        return canonical_path( lhs ) == canonical_path( rhs )
[c07d724]239
[bacc36c]240# walk all files in a path
[5bf1f3e]241def path_walk( op ):
[56de5932]242        dname = settings.SRCDIR
[5b993e0]243        for dirname, _, names in os.walk(dname):
244                for name in names:
245                        path = os.path.join(dirname, name)
246                        op( path )
[bacc36c]247
248################################################################################
249#               system
250################################################################################
251# count number of jobs to create
[5bf1f3e]252def job_count( options, tests ):
[bacc36c]253        # check if the user already passed in a number of jobs for multi-threading
[d142ec5]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                        options.jobs = multiprocessing.cpu_count()
[bacc36c]264        else :
[d142ec5]265                force = True
[bacc36c]266
267        # make sure we have a valid number of jobs that corresponds to user input
268        if options.jobs <= 0 :
269                print('ERROR: Invalid number of jobs', file=sys.stderr)
270                sys.exit(1)
271
[d142ec5]272        return min( options.jobs, len(tests) ), force
[bacc36c]273
[0c13238]274# enable core dumps for all the test children
275resource.setrlimit(resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
276
[bacc36c]277################################################################################
278#               misc
279################################################################################
280
[d65f92c]281# get hash for given configuration
282def config_hash():
283        path = os.path.normpath(os.path.join(
284                settings.SRCDIR,
285        ))
286
287        distcc_hash = os.path.join(settings.SRCDIR, '../tools/build/distcc_hash')
288        config = "%s-%s" % (settings.arch.target, settings.debug.path)
289        _, out = sh(distcc_hash, config, output_file=subprocess.PIPE)
290        return out.strip()
291
[5c4a473]292# get pretty string for time of day
[dcfedca]293def pretty_now():
294        ts = time.time()
295        print(ts, file=sys.stderr)
296        return datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d_%H:%M:%S')
297
[bacc36c]298# check if arguments is yes or no
299def yes_no(string):
300        if string == "yes" :
301                return True
302        if string == "no" :
303                return False
304        raise argparse.ArgumentTypeError(msg)
[f3b9efc]305
[ed45af6]306def fancy_print(text):
307        column = which('column')
308        if column:
[1bb2488]309                subprocess.run(column, input=bytes(text + "\n", "UTF-8"))
[ed45af6]310        else:
311                print(text)
[0c13238]312
313
[5bf1f3e]314def core_info(path):
[f806b61]315        if not os.path.isfile(path):
316                return 1, "ERR Executable path is wrong"
317
[0c13238]318        cmd   = os.path.join(settings.SRCDIR, "pybin/print-core.gdb")
319        if not os.path.isfile(cmd):
320                return 1, "ERR Printing format for core dumps not found"
321
[f806b61]322        core  = os.path.join(os.getcwd(), "core" )
[0c13238]323
324        if not os.path.isfile(core):
325                return 1, "ERR No core dump"
326
[d65f92c]327        return sh('gdb', '-n', path, core, '-batch', '-x', cmd, output_file=subprocess.PIPE)
[0c13238]328
[dcfedca]329def core_archive(dst, name, exe):
[143e6f3]330        # Get the core dump
[dcfedca]331        core = os.path.join(os.getcwd(), "core" )
332
[143e6f3]333        # update the path for this test
334        dst  = os.path.join(dst, name)
[dcfedca]335
336        # make a directory for this test
[143e6f3]337        # mkdir makes the parent directory only so add a dummy
[dcfedca]338        mkdir(os.path.join(dst, "dir"))
339
340        # moves the files
341        mv( core, os.path.join(dst, "core" ) )
342        mv( exe , os.path.join(dst, name   ) )
343
344        # return explanatory test
345        return "Archiving %s (executable and core) to %s" % (os.path.relpath(exe, settings.BUILDDIR), os.path.relpath(dst, settings.original_path))
346
[0c13238]347class Timed:
348    def __enter__(self):
349        self.start = time.time()
350        return self
351
352    def __exit__(self, *args):
353        self.end = time.time()
[35a408b7]354        self.duration = self.end - self.start
355
356def timed(src, timeout):
357        expire = time.time() + timeout
358        i = iter(src)
359        while True:
360                yield i.next(max(expire - time.time(), 0))
Note: See TracBrowser for help on using the repository browser.