source: tests/pybin/tools.py @ 99581ee

arm-ehjacob/cs343-translationnew-ast-unique-expr
Last change on this file since 99581ee was 99581ee, checked in by Thierry Delisle <tdelisle@…>, 13 months ago

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

  • Property mode set to 100644
File size: 11.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                                        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_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
99def is_ascii(fname):
100        if settings.dry_run:
101                print("is_ascii: %s" % fname)
102                return (True, "")
103
104        if not os.path.isfile(fname):
105                return (False, "No file")
106
107        code, out = sh("file", fname, output_file=subprocess.PIPE)
108        if code != 0:
109                return (False, "'file EXPECT' failed with code {}".format(code))
110
111        match = re.search(".*: (.*)", out)
112
113        if not match:
114                return (False, "Unreadable file type: '{}'".format(out))
115
116        if "ASCII text" in match.group(1):
117                return (True, "")
118
119        return (False, "File type should be 'ASCII text', was '{}'".format(match.group(1)))
120
121def is_exe(fname):
122        return os.path.isfile(fname) and os.access(fname, os.X_OK)
123
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
134        file = open(file, mode, encoding="latin-1") # use latin-1 so all chars mean something.
135        exitstack.push(file)
136        return file
137
138# Remove 1 or more files silently
139def rm( files ):
140        if isinstance(files, str ): files = [ files ]
141        for file in files:
142                sh( 'rm', '-f', file, output_file=subprocess.DEVNULL, error=subprocess.DEVNULL )
143
144# Create 1 or more directory
145def mkdir( files ):
146        if isinstance(files, str ): files = [ files ]
147        for file in files:
148                p = os.path.normpath( file )
149                d = os.path.dirname ( p )
150                sh( 'mkdir', '-p', d, output_file=subprocess.DEVNULL, error=subprocess.DEVNULL )
151
152
153def chdir( dest = __main__.__file__ ):
154        abspath = os.path.abspath(dest)
155        dname = os.path.dirname(abspath)
156        os.chdir(dname)
157
158# diff two files
159def diff( lhs, rhs ):
160        # fetch return code and error from the diff command
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,
173                output_file=subprocess.PIPE
174        )
175
176# call make
177def make(target, *, flags = '', output_file = None, error = None, error_file = None, silent = False):
178        test_param = """test="%s" """ % (error_file) if error_file else None
179        cmd = [
180                *settings.make,
181                '-s' if silent else None,
182                test_param,
183                settings.ast.flags,
184                settings.arch.flags,
185                settings.debug.flags,
186                settings.install.flags,
187                settings.distcc if settings.distribute else None,
188                flags,
189                target
190        ]
191        cmd = [s for s in cmd if s]
192        return sh(*cmd, output_file=output_file, error=error)
193
194def which(program):
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
205
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
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
227################################################################################
228#               file handling
229################################################################################
230# move a file
231def mv(source, dest):
232        ret, _ = sh("mv", source, dest)
233        return ret
234
235# cat one file into the other
236def cat(source, dest):
237        ret, _ = sh("cat", source, output_file=dest)
238        return ret
239
240# helper function to replace patterns in a file
241def file_replace(fname, pat, s_after):
242        if settings.dry_run:
243                print("replacing '%s' with '%s' in %s" % (pat, s_after, fname))
244                return
245
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()
250
251# helper function to check if a files contains only a specific string
252def file_contains_only(file, text) :
253        with open(file, encoding="latin-1") as f: # use latin-1 so all chars mean something.
254                ff = f.read().strip()
255                result = ff == text.strip()
256
257                return result
258
259# transform path to canonical form
260def canonical_path(path):
261        abspath = os.path.abspath(os.path.realpath(__main__.__file__))
262        dname = os.path.dirname(abspath)
263        return os.path.join(dname, os.path.normpath(path) )
264
265# compare path even if form is different
266def path_cmp(lhs, rhs):
267        return canonical_path( lhs ) == canonical_path( rhs )
268
269# walk all files in a path
270def path_walk( op ):
271        dname = settings.SRCDIR
272        for dirname, _, names in os.walk(dname):
273                for name in names:
274                        path = os.path.join(dirname, name)
275                        op( path )
276
277################################################################################
278#               system
279################################################################################
280# count number of jobs to create
281def job_count( options, tests ):
282        # check if the user already passed in a number of jobs for multi-threading
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 :
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()
300        else :
301                force = True
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
308        return min( options.jobs, len(tests) ), force
309
310# enable core dumps for all the test children
311resource.setrlimit(resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
312
313################################################################################
314#               misc
315################################################################################
316
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)
325        _, out = sh(distcc_hash, config, output_file=subprocess.PIPE, ignore_dry_run=True)
326        return out.strip()
327
328# get pretty string for time of day
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
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)
341
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
346def fancy_print(text):
347        column = which('column')
348        if column:
349                subprocess.run(column, input=bytes(text + "\n", "UTF-8"))
350        else:
351                print(text)
352
353
354def core_info(path):
355        if not os.path.isfile(path):
356                return 1, "ERR Executable path is wrong"
357
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
362        core  = os.path.join(os.getcwd(), "core" )
363
364        if not os.path.isfile(core):
365                return 1, "ERR No core dump"
366
367        return sh('gdb', '-n', path, core, '-batch', '-x', cmd, output_file=subprocess.PIPE)
368
369def core_archive(dst, name, exe):
370        # Get the core dump
371        core = os.path.join(os.getcwd(), "core" )
372
373        # update the path for this test
374        dst  = os.path.join(dst, name)
375
376        # make a directory for this test
377        # mkdir makes the parent directory only so add a dummy
378        mkdir(os.path.join(dst, name ))
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
387class Timed:
388        def __enter__(self):
389                self.start = time.time()
390                return self
391
392        def __exit__(self, *args):
393                self.end = time.time()
394                self.duration = self.end - self.start
395
396def timed(src, timeout):
397        expire = time.time() + timeout
398        i = iter(src)
399        with contextlib.suppress(StopIteration):
400                while True:
401                        yield i.next(max(expire - time.time(), 0))
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.