source: tests/pybin/tools.py @ f866d15

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

test.py now warns if an .expect file is empty

  • Property mode set to 100644
File size: 11.1 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.arch.flags,
184                settings.debug.flags,
185                settings.install.flags,
186                settings.distcc if settings.distribute else None,
187                flags,
188                target
189        ]
190        cmd = [s for s in cmd if s]
191        return sh(*cmd, output_file=output_file, error=error)
192
193def which(program):
194        fpath, fname = os.path.split(program)
195        if fpath:
196                if is_exe(program):
197                        return program
198        else:
199                for path in os.environ["PATH"].split(os.pathsep):
200                        exe_file = os.path.join(path, program)
201                        if is_exe(exe_file):
202                                return exe_file
203        return None
204
205@contextlib.contextmanager
206def tempdir():
207        cwd = os.getcwd()
208        with tempfile.TemporaryDirectory() as temp:
209                os.chdir(temp)
210                try:
211                        yield temp
212                finally:
213                        os.chdir(cwd)
214
215def killgroup():
216        try:
217                os.killpg(os.getpgrp(), signal.SIGINT)
218        except KeyboardInterrupt:
219                pass # expected
220        except Exception as exc:
221                print("Unexpected exception", file=sys.stderr)
222                print(exc, file=sys.stderr)
223                sys.stderr.flush()
224                sys.exit(2)
225
226################################################################################
227#               file handling
228################################################################################
229# move a file
230def mv(source, dest):
231        ret, _ = sh("mv", source, dest)
232        return ret
233
234# cat one file into the other
235def cat(source, dest):
236        ret, _ = sh("cat", source, output_file=dest)
237        return ret
238
239# helper function to replace patterns in a file
240def file_replace(fname, pat, s_after):
241        if settings.dry_run:
242                print("replacing '%s' with '%s' in %s" % (pat, s_after, fname))
243                return
244
245        file = fileinput.FileInput(fname, inplace=True, backup='.bak')
246        for line in file:
247                print(line.replace(pat, s_after), end='')
248        file.close()
249
250# helper function to check if a files contains only a specific string
251def file_contains_only(file, text) :
252        with open(file, encoding="latin-1") as f: # use latin-1 so all chars mean something.
253                ff = f.read().strip()
254                result = ff == text.strip()
255
256                return result
257
258# transform path to canonical form
259def canonical_path(path):
260        abspath = os.path.abspath(os.path.realpath(__main__.__file__))
261        dname = os.path.dirname(abspath)
262        return os.path.join(dname, os.path.normpath(path) )
263
264# compare path even if form is different
265def path_cmp(lhs, rhs):
266        return canonical_path( lhs ) == canonical_path( rhs )
267
268# walk all files in a path
269def path_walk( op ):
270        dname = settings.SRCDIR
271        for dirname, _, names in os.walk(dname):
272                for name in names:
273                        path = os.path.join(dirname, name)
274                        op( path )
275
276################################################################################
277#               system
278################################################################################
279# count number of jobs to create
280def job_count( options, tests ):
281        # check if the user already passed in a number of jobs for multi-threading
282        if not options.jobs:
283                make_flags = os.environ.get('MAKEFLAGS')
284                force = bool(make_flags)
285                make_jobs_fds = re.search("--jobserver-(auth|fds)=\s*([0-9]+),([0-9]+)", make_flags) if make_flags else None
286                if make_jobs_fds :
287                        tokens = os.read(int(make_jobs_fds.group(2)), 1024)
288                        options.jobs = len(tokens)
289                        os.write(int(make_jobs_fds.group(3)), tokens)
290                else :
291                        if settings.distribute:
292                                ret, jstr = sh("distcc", "-j", output_file=subprocess.PIPE, ignore_dry_run=True)
293                                if ret == 0:
294                                        options.jobs = int(jstr.strip())
295                                else :
296                                        options.jobs = multiprocessing.cpu_count()
297                        else:
298                                options.jobs = multiprocessing.cpu_count()
299        else :
300                force = True
301
302        # make sure we have a valid number of jobs that corresponds to user input
303        if options.jobs <= 0 :
304                print('ERROR: Invalid number of jobs', file=sys.stderr)
305                sys.exit(1)
306
307        return min( options.jobs, len(tests) ), force
308
309# enable core dumps for all the test children
310resource.setrlimit(resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
311
312################################################################################
313#               misc
314################################################################################
315
316# get hash for given configuration
317def config_hash():
318        path = os.path.normpath(os.path.join(
319                settings.SRCDIR,
320        ))
321
322        distcc_hash = os.path.join(settings.SRCDIR, '../tools/build/distcc_hash')
323        config = "%s-%s" % (settings.arch.target, settings.debug.path)
324        _, out = sh(distcc_hash, config, output_file=subprocess.PIPE, ignore_dry_run=True)
325        return out.strip()
326
327# get pretty string for time of day
328def pretty_now():
329        ts = time.time()
330        print(ts, file=sys.stderr)
331        return datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d_%H:%M:%S')
332
333# check if arguments is yes or no
334def yes_no(string):
335        if string == "yes" :
336                return True
337        if string == "no" :
338                return False
339        raise argparse.ArgumentTypeError(msg)
340
341# Convert a function that converts a string to one that converts comma separated string.
342def comma_separated(elements):
343    return lambda string: [elements(part) for part in string.split(',')]
344
345def fancy_print(text):
346        column = which('column')
347        if column:
348                subprocess.run(column, input=bytes(text + "\n", "UTF-8"))
349        else:
350                print(text)
351
352
353def core_info(path):
354        if not os.path.isfile(path):
355                return 1, "ERR Executable path is wrong"
356
357        cmd   = os.path.join(settings.SRCDIR, "pybin/print-core.gdb")
358        if not os.path.isfile(cmd):
359                return 1, "ERR Printing format for core dumps not found"
360
361        core  = os.path.join(os.getcwd(), "core" )
362
363        if not os.path.isfile(core):
364                return 1, "ERR No core dump"
365
366        return sh('gdb', '-n', path, core, '-batch', '-x', cmd, output_file=subprocess.PIPE)
367
368def core_archive(dst, name, exe):
369        # Get the core dump
370        core = os.path.join(os.getcwd(), "core" )
371
372        # update the path for this test
373        dst  = os.path.join(dst, name)
374
375        # make a directory for this test
376        # mkdir makes the parent directory only so add a dummy
377        mkdir(os.path.join(dst, name ))
378
379        # moves the files
380        mv( core, os.path.join(dst, "core" ) )
381        mv( exe , os.path.join(dst, name   ) )
382
383        # return explanatory test
384        return "Archiving %s (executable and core) to %s" % (os.path.relpath(exe, settings.BUILDDIR), os.path.relpath(dst, settings.original_path))
385
386class Timed:
387        def __enter__(self):
388                self.start = time.time()
389                return self
390
391        def __exit__(self, *args):
392                self.end = time.time()
393                self.duration = self.end - self.start
394
395def timed(src, timeout):
396        expire = time.time() + timeout
397        i = iter(src)
398        with contextlib.suppress(StopIteration):
399                while True:
400                        yield i.next(max(expire - time.time(), 0))
401
402def fmtDur( duration ):
403        if duration :
404                hours, rem = divmod(duration, 3600)
405                minutes, rem = divmod(rem, 60)
406                seconds, millis = divmod(rem, 1)
407                return "%2d:%02d.%03d" % (minutes, seconds, millis * 1000)
408        return " n/a"
Note: See TracBrowser for help on using the repository browser.