source: tests/pybin/tools.py @ a45fc7b

ADTarm-ehast-experimentalcleanup-dtorsenumforall-pointer-decayjacob/cs343-translationjenkins-sandboxnew-astnew-ast-unique-exprpthread-emulationqualifiedEnum
Last change on this file since a45fc7b was a45fc7b, checked in by tdelisle <tdelisle@…>, 5 years ago

Tests no longer use subprocess.run(shell=True), which solves leak process problems

  • Property mode set to 100644
File size: 7.2 KB
Line 
1import __main__
2import argparse
3import contextlib
4import fileinput
5import multiprocessing
6import os
7import re
8import resource
9import signal
10import stat
11import subprocess
12import sys
13import time
14import types
15
16from pybin import settings
17from subprocess import Popen, PIPE, STDOUT
18
19################################################################################
20#               shell helpers
21################################################################################
22
23# helper functions to run terminal commands
24def sh(*cmd, timeout = False, output = None, input = None, error = subprocess.STDOUT):
25        cmd = list(cmd)
26
27        # if this is a dry_run, only print the commands that would be ran
28        if settings.dry_run :
29                print("cmd: %s" % ' '.join(cmd))
30                return 0, None
31
32        with contextlib.ExitStack() as onexit:
33                # add input redirection if needed
34                if input and input != subprocess.DEVNULL:
35                        if os.path.isfile(input):
36                                input = open(input)
37                                onexit.push(input)
38                        else:
39                                input = None
40
41                # add output redirection if needed
42                if output and output != subprocess.DEVNULL and output != subprocess.PIPE:
43                        output = open(output, 'w')
44                        onexit.push(output)
45
46                # run the desired command
47                try:
48                        proc = subprocess.run(
49                                cmd,
50                                stdin =input,
51                                stdout=output,
52                                stderr=STDOUT,
53                                timeout=settings.timeout.single if timeout else None
54                        )
55                        return proc.returncode, proc.stdout.decode("utf-8") if proc.stdout else None
56                except subprocess.TimeoutExpired:
57                        return 124, str(None)
58
59def is_ascii(fname):
60        if settings.dry_run:
61                print("is_ascii: %s" % fname)
62                return True
63
64        if not os.path.isfile(fname):
65                return False
66
67        code, out = sh("file %s" % fname, output=subprocess.PIPE)
68        if code != 0:
69                return False
70
71        match = re.search(".*: (.*)", out)
72
73        if not match:
74                return False
75
76        return match.group(1).startswith("ASCII text")
77
78def is_exe(fname):
79        return os.path.isfile(fname) and os.access(fname, os.X_OK)
80
81# Remove 1 or more files silently
82def rm( files ):
83        if isinstance(files, str ): files = [ files ]
84        for file in files:
85                sh( 'rm', '-f', file, output=subprocess.DEVNULL, error=subprocess.DEVNULL )
86
87# Create 1 or more directory
88def mkdir( files ):
89        if isinstance(files, str ): files = [ files ]
90        for file in files:
91                p = os.path.normpath( file )
92                d = os.path.dirname ( p )
93                sh( 'mkdir', '-p', d, output=subprocess.DEVNULL, error=subprocess.DEVNULL )
94
95
96def chdir( dest = __main__.__file__ ):
97        abspath = os.path.abspath(dest)
98        dname = os.path.dirname(abspath)
99        os.chdir(dname)
100
101# diff two files
102def diff( lhs, rhs ):
103        # fetch return code and error from the diff command
104        return sh(
105                '''diff''',
106                '''--text''',
107                '''--old-group-format=\t\tmissing lines :\n%<''',
108                '''--new-line-format=\t\t%dn\t%L''',
109                '''--new-group-format=\t\tnew lines : \n%>''',
110                '''--old-line-format=\t\t%dn\t%L''',
111                '''--unchanged-group-format=%=''',
112                '''--changed-group-format=\t\texpected :\n%<\t\tgot :\n%>''',
113                '''--unchanged-line-format=''',
114                lhs,
115                rhs,
116                output=subprocess.PIPE
117        )
118
119# call make
120def make(target, *, flags = '', output = None, error = None, error_file = None, silent = False):
121        test_param = """test="%s" """ % (error_file) if error_file else None
122        cmd = [
123                *settings.make,
124                '-s' if silent else None,
125                test_param,
126                settings.arch.flags,
127                settings.debug.flags,
128                settings.install.flags,
129                flags,
130                target
131        ]
132        cmd = [s for s in cmd if s]
133        return sh(*cmd, output=output, error=error)
134
135def which(program):
136    fpath, fname = os.path.split(program)
137    if fpath:
138        if is_exe(program):
139            return program
140    else:
141        for path in os.environ["PATH"].split(os.pathsep):
142            exe_file = os.path.join(path, program)
143            if is_exe(exe_file):
144                return exe_file
145
146    return None
147
148################################################################################
149#               file handling
150################################################################################
151# move a file
152def mv(source, dest):
153        ret, _ = sh("mv", source, dest)
154        return ret
155
156# cat one file into the other
157def cat(source, dest):
158        ret, _ = sh("cat", source, output=dest)
159        return ret
160
161# helper function to replace patterns in a file
162def file_replace(fname, pat, s_after):
163        if settings.dry_run:
164                print("replacing '%s' with '%s' in %s" % (pat, s_after, fname))
165                return
166
167        file = fileinput.FileInput(fname, inplace=True, backup='.bak')
168        for line in file:
169                print(line.replace(pat, s_after), end='')
170        file.close()
171
172# helper function to check if a files contains only a specific string
173def file_contains_only(file, text) :
174        with open(file) as f:
175                ff = f.read().strip()
176                result = ff == text.strip()
177
178                return result
179
180# transform path to canonical form
181def canonical_path(path):
182        abspath = os.path.abspath(__main__.__file__)
183        dname = os.path.dirname(abspath)
184        return os.path.join(dname, os.path.normpath(path) )
185
186# compare path even if form is different
187def path_cmp(lhs, rhs):
188        return canonical_path( lhs ) == canonical_path( rhs )
189
190# walk all files in a path
191def path_walk( op ):
192        dname = settings.SRCDIR
193        for dirname, _, names in os.walk(dname):
194                for name in names:
195                        path = os.path.join(dirname, name)
196                        op( path )
197
198################################################################################
199#               system
200################################################################################
201# count number of jobs to create
202def job_count( options, tests ):
203        # check if the user already passed in a number of jobs for multi-threading
204        if not options.jobs:
205                make_flags = os.environ.get('MAKEFLAGS')
206                force = bool(make_flags)
207                make_jobs_fds = re.search("--jobserver-(auth|fds)=\s*([0-9]+),([0-9]+)", make_flags) if make_flags else None
208                if make_jobs_fds :
209                        tokens = os.read(int(make_jobs_fds.group(2)), 1024)
210                        options.jobs = len(tokens)
211                        os.write(int(make_jobs_fds.group(3)), tokens)
212                else :
213                        options.jobs = multiprocessing.cpu_count()
214        else :
215                force = True
216
217        # make sure we have a valid number of jobs that corresponds to user input
218        if options.jobs <= 0 :
219                print('ERROR: Invalid number of jobs', file=sys.stderr)
220                sys.exit(1)
221
222        return min( options.jobs, len(tests) ), force
223
224# enable core dumps for all the test children
225resource.setrlimit(resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
226
227################################################################################
228#               misc
229################################################################################
230
231# check if arguments is yes or no
232def yes_no(string):
233        if string == "yes" :
234                return True
235        if string == "no" :
236                return False
237        raise argparse.ArgumentTypeError(msg)
238
239def fancy_print(text):
240        column = which('column')
241        if column:
242                subprocess.run(column, input=bytes(text + "\n", "UTF-8"))
243        else:
244                print(text)
245
246
247def core_info(path):
248        cmd   = os.path.join(settings.SRCDIR, "pybin/print-core.gdb")
249        if not os.path.isfile(cmd):
250                return 1, "ERR Printing format for core dumps not found"
251
252        dname = os.path.dirname(path)
253        core  = os.path.join(dname, "core" )
254        if not os.path.isfile(path):
255                return 1, "ERR Executable path is wrong"
256
257        if not os.path.isfile(core):
258                return 1, "ERR No core dump"
259
260        return sh('gdb', '-n', path, core, '-batch', '-x', cmd, output=subprocess.PIPE)
261
262class Timed:
263    def __enter__(self):
264        self.start = time.time()
265        return self
266
267    def __exit__(self, *args):
268        self.end = time.time()
269        self.duration = self.end - self.start
Note: See TracBrowser for help on using the repository browser.