source: tests/pybin/tools.py @ b2a37b0

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

Clean up test make file and add autodetection of number of hosts by test.py

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