def create_dist_hg(dist_name, src_root, bld_root, dist_sub, dist_scripts): if hg_have_dirty_index(src_root): mlog.warning('Repository has uncommitted changes that will not be included in the dist tarball') os.makedirs(dist_sub, exist_ok=True) tarname = os.path.join(dist_sub, dist_name + '.tar') xzname = tarname + '.xz' subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'tar', tarname]) if len(dist_scripts) > 0: mlog.warning('dist scripts are not supported in Mercurial projects') with lzma.open(xzname, 'wb') as xf, open(tarname, 'rb') as tf: shutil.copyfileobj(tf, xf) os.unlink(tarname) # Create only .tar.xz for now. # zipname = os.path.join(dist_sub, dist_name + '.zip') # subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'zip', zipname]) return (xzname, )
def check_direntry_issues(direntry_array): import locale # Warn if the locale is not UTF-8. This can cause various unfixable issues # such as os.stat not being able to decode filenames with unicode in them. # There is no way to reset both the preferred encoding and the filesystem # encoding, so we can just warn about it. e = locale.getpreferredencoding() if e.upper() != 'UTF-8' and not is_windows(): if not isinstance(direntry_array, list): direntry_array = [direntry_array] for de in direntry_array: if is_ascii_string(de): continue mlog.warning('''You are using {!r} which is not a Unicode-compatible ' locale but you are trying to access a file system entry called {!r} which is not pure ASCII. This may cause problems. '''.format(e, de), file=sys.stderr)
def check_direntry_issues(direntry_array): import locale # Warn if the locale is not UTF-8. This can cause various unfixable issues # such as os.stat not being able to decode filenames with unicode in them. # There is no way to reset both the preferred encoding and the filesystem # encoding, so we can just warn about it. e = locale.getpreferredencoding() if e.upper() != 'UTF-8' and not is_windows(): if not isinstance(direntry_array, list): direntry_array = [direntry_array] for de in direntry_array: if is_ascii_string(de): continue mlog.warning( '''You are using {!r} which is not a Unicode-compatible ' locale but you are trying to access a file system entry called {!r} which is not pure ASCII. This may cause problems. '''.format(e, de), file=sys.stderr)
def __exit__(self, _type, value, traceback): # On Windows, shutil.rmtree fails sometimes, because 'the directory is not empty'. # Retrying fixes this. # That's why we don't use tempfile.TemporaryDirectory, but wrap the deletion in the AutoDeletedDir class. retries = 5 for i in range(0, retries): try: shutil.rmtree(self.dir) return # Sometimes we get: ValueError: I/O operation on closed file. except ValueError: return # Deleting can raise OSError or PermissionError on Windows # (most likely because of anti-virus locking the file) except (OSError, PermissionError): if i == retries - 1: mlog.warning('Could not delete temporary directory.') return time.sleep(0.1 * (2**i))
def create_dist_hg(dist_name, archives, src_root, bld_root, dist_sub, dist_scripts): if hg_have_dirty_index(src_root): mlog.warning( 'Repository has uncommitted changes that will not be included in the dist tarball' ) if dist_scripts: mlog.warning('dist scripts are not supported in Mercurial projects') os.makedirs(dist_sub, exist_ok=True) tarname = os.path.join(dist_sub, dist_name + '.tar') xzname = tarname + '.xz' gzname = tarname + '.gz' zipname = os.path.join(dist_sub, dist_name + '.zip') # Note that -X interprets relative paths using the current working # directory, not the repository root, so this must be an absolute path: # https://bz.mercurial-scm.org/show_bug.cgi?id=6267 # # .hg[a-z]* is used instead of .hg* to keep .hg_archival.txt, which may # be useful to link the tarball to the Mercurial revision for either # manual inspection or in case any code interprets it for a --version or # similar. subprocess.check_call([ 'hg', 'archive', '-R', src_root, '-S', '-t', 'tar', '-X', src_root + '/.hg[a-z]*', tarname ]) output_names = [] if 'xztar' in archives: import lzma with lzma.open(xzname, 'wb') as xf, open(tarname, 'rb') as tf: shutil.copyfileobj(tf, xf) output_names.append(xzname) if 'gztar' in archives: with gzip.open(gzname, 'wb') as zf, open(tarname, 'rb') as tf: shutil.copyfileobj(tf, zf) output_names.append(gzname) os.unlink(tarname) if 'zip' in archives: subprocess.check_call( ['hg', 'archive', '-R', src_root, '-S', '-t', 'zip', zipname]) output_names.append(zipname) return output_names
def create_dist_hg(dist_name, src_root, bld_root, dist_sub, dist_scripts): if hg_have_dirty_index(src_root): mlog.warning( 'Repository has uncommitted changes that will not be included in the dist tarball' ) os.makedirs(dist_sub, exist_ok=True) tarname = os.path.join(dist_sub, dist_name + '.tar') xzname = tarname + '.xz' subprocess.check_call( ['hg', 'archive', '-R', src_root, '-S', '-t', 'tar', tarname]) if len(dist_scripts) > 0: mlog.warning('dist scripts are not supported in Mercurial projects') with lzma.open(xzname, 'wb') as xf, open(tarname, 'rb') as tf: shutil.copyfileobj(tf, xf) os.unlink(tarname) # Create only .tar.xz for now. # zipname = os.path.join(dist_sub, dist_name + '.zip') # subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'zip', zipname]) return (xzname, )
def create_dist_git(dist_name, src_root, bld_root, dist_sub, dist_scripts): if git_have_dirty_index(src_root): mlog.warning('Repository has uncommitted changes that will not be included in the dist tarball') distdir = os.path.join(dist_sub, dist_name) if os.path.exists(distdir): shutil.rmtree(distdir) os.makedirs(distdir) subprocess.check_call(['git', 'clone', '--shared', src_root, distdir]) process_submodules(distdir) del_gitfiles(distdir) run_dist_scripts(distdir, dist_scripts) xzname = distdir + '.tar.xz' # Should use shutil but it got xz support only in 3.5. with tarfile.open(xzname, 'w:xz') as tf: tf.add(distdir, dist_name) # Create only .tar.xz for now. # zipname = distdir + '.zip' # create_zip(zipname, distdir) shutil.rmtree(distdir) return (xzname, )
def get_backend_commands(backend, debug=False): install_cmd = [] uninstall_cmd = [] if backend is Backend.vs: cmd = ['msbuild'] clean_cmd = cmd + ['/target:Clean'] test_cmd = cmd + ['RUN_TESTS.vcxproj'] elif backend is Backend.xcode: cmd = ['xcodebuild'] # In Xcode9 new build system's clean command fails when using a custom build directory. # Maybe use it when CI uses Xcode10 we can remove '-UseNewBuildSystem=FALSE' clean_cmd = cmd + ['-alltargets', 'clean', '-UseNewBuildSystem=FALSE'] test_cmd = cmd + ['-target', 'RUN_TESTS'] elif backend is Backend.ninja: global NINJA_1_9_OR_NEWER # Look for 1.9 to see if https://github.com/ninja-build/ninja/issues/1219 # is fixed, else require 1.6 for -w dupbuild=err for v in ('1.9', '1.6'): ninja_cmd = detect_ninja(v) if ninja_cmd is not None: if v == '1.9': NINJA_1_9_OR_NEWER = True else: mlog.warning('Found ninja <1.9, tests will run slower', once=True) if 'CI' in os.environ: raise RuntimeError( 'Require ninja >= 1.9 when running on Meson CI') break cmd = [ninja_cmd, '-w', 'dupbuild=err', '-d', 'explain'] if cmd[0] is None: raise RuntimeError('Could not find Ninja v1.6 or newer') if debug: cmd += ['-v'] clean_cmd = cmd + ['clean'] test_cmd = cmd + ['test', 'benchmark'] install_cmd = cmd + ['install'] uninstall_cmd = cmd + ['uninstall'] else: raise AssertionError('Unknown backend: {!r}'.format(backend)) return cmd, clean_cmd, test_cmd, install_cmd, uninstall_cmd
def process_git_project(src_root, distdir): if git_have_dirty_index(src_root): mlog.warning( 'Repository has uncommitted changes that will not be included in the dist tarball' ) if os.path.exists(distdir): windows_proof_rmtree(distdir) repo_root = git_root(src_root) if repo_root.samefile(src_root): os.makedirs(distdir) copy_git(src_root, distdir) else: subdir = Path(src_root).relative_to(repo_root) tmp_distdir = distdir + '-tmp' if os.path.exists(tmp_distdir): windows_proof_rmtree(tmp_distdir) os.makedirs(tmp_distdir) copy_git(repo_root, tmp_distdir, subdir=str(subdir)) Path(tmp_distdir, subdir).rename(distdir) windows_proof_rmtree(tmp_distdir) process_submodules(src_root, distdir)
def get_meson_script(): ''' Guess the meson that corresponds to the `mesonbuild` that has been imported so we can run configure and other commands in-process, since mesonmain.run needs to know the meson_command to use. Also used by run_unittests.py to determine what meson to run when not running in-process (which is the default). ''' # Is there a meson.py next to the mesonbuild currently in use? mesonbuild_dir = Path(mesonbuild.__file__).resolve().parent.parent meson_script = mesonbuild_dir / 'meson.py' if meson_script.is_file(): return str(meson_script) # Then if mesonbuild is in PYTHONPATH, meson must be in PATH mlog.warning('Could not find meson.py next to the mesonbuild module. ' 'Trying system meson...') meson_cmd = shutil.which('meson') if meson_cmd: return meson_cmd raise RuntimeError('Could not find {!r} or a meson in PATH'.format(meson_script))
def git_clone(src_root, distdir): if git_have_dirty_index(src_root): mlog.warning('Repository has uncommitted changes that will not be included in the dist tarball') if os.path.exists(distdir): windows_proof_rmtree(distdir) repo_root = git_root(src_root) if repo_root.samefile(src_root): os.makedirs(distdir) subprocess.check_call(['git', 'clone', '--shared', src_root, distdir]) else: subdir = Path(src_root).relative_to(repo_root) tmp_distdir = distdir + '-tmp' if os.path.exists(tmp_distdir): windows_proof_rmtree(tmp_distdir) os.makedirs(tmp_distdir) subprocess.check_call(['git', 'clone', '--shared', '--no-checkout', str(repo_root), tmp_distdir]) subprocess.check_call(['git', 'checkout', 'HEAD', '--', str(subdir)], cwd=tmp_distdir) Path(tmp_distdir, subdir).rename(distdir) windows_proof_rmtree(tmp_distdir) process_submodules(distdir) del_gitfiles(distdir)
def create_dist_git(dist_name, archives, src_root, bld_root, dist_sub, dist_scripts): if git_have_dirty_index(src_root): mlog.warning( 'Repository has uncommitted changes that will not be included in the dist tarball' ) distdir = os.path.join(dist_sub, dist_name) if os.path.exists(distdir): shutil.rmtree(distdir) os.makedirs(distdir) subprocess.check_call(['git', 'clone', '--shared', src_root, distdir]) process_submodules(distdir) del_gitfiles(distdir) run_dist_scripts(distdir, dist_scripts) output_names = [] for a in archives: compressed_name = distdir + archive_extension[a] shutil.make_archive(distdir, a, root_dir=dist_sub, base_dir=dist_name) output_names.append(compressed_name) shutil.rmtree(distdir) return output_names
def ensure_backend_detects_changes(backend): global NINJA_1_9_OR_NEWER if backend is not Backend.ninja: return need_workaround = False # We're not running on HFS+ which only stores dates in seconds: # https://developer.apple.com/legacy/library/technotes/tn/tn1150.html#HFSPlusDates # XXX: Upgrade Travis image to Apple FS when that becomes available # TODO: Detect HFS+ vs APFS if mesonlib.is_osx(): mlog.warning('Running on HFS+, enabling timestamp resolution workaround', once=True) need_workaround = True # We're using ninja >= 1.9 which has QuLogic's patch for sub-1s resolution # timestamps if not NINJA_1_9_OR_NEWER: mlog.warning('Don\'t have ninja >= 1.9, enabling timestamp resolution workaround', once=True) need_workaround = True # Increase the difference between build.ninja's timestamp and the timestamp # of whatever you changed: https://github.com/ninja-build/ninja/issues/371 if need_workaround: time.sleep(1)
def get_meson_script(): ''' Guess the meson that corresponds to the `mesonbuild` that has been imported so we can run configure and other commands in-process, since mesonmain.run needs to know the meson_command to use. Also used by run_unittests.py to determine what meson to run when not running in-process (which is the default). ''' # Is there a meson.py next to the mesonbuild currently in use? mesonbuild_dir = Path(mesonmain.__file__).resolve().parent.parent meson_script = mesonbuild_dir / 'meson.py' if meson_script.is_file(): return str(meson_script) # Then if mesonbuild is in PYTHONPATH, meson must be in PATH mlog.warning('Could not find meson.py next to the mesonbuild module. ' 'Trying system meson...') meson_cmd = shutil.which('meson') if meson_cmd: return meson_cmd raise RuntimeError('Could not find {!r} or a meson in PATH'.format(meson_script))
def create_dist_git(dist_name, src_root, bld_root, dist_sub, dist_scripts): if git_have_dirty_index(src_root): mlog.warning( 'Repository has uncommitted changes that will not be included in the dist tarball' ) distdir = os.path.join(dist_sub, dist_name) if os.path.exists(distdir): shutil.rmtree(distdir) os.makedirs(distdir) subprocess.check_call(['git', 'clone', '--shared', src_root, distdir]) process_submodules(distdir) del_gitfiles(distdir) run_dist_scripts(distdir, dist_scripts) xzname = distdir + '.tar.xz' # Should use shutil but it got xz support only in 3.5. with tarfile.open(xzname, 'w:xz') as tf: tf.add(distdir, dist_name) # Create only .tar.xz for now. # zipname = distdir + '.zip' # create_zip(zipname, distdir) shutil.rmtree(distdir) return (xzname, )
def _using_intelcl() -> bool: """ detect if intending to using Intel-Cl compilers (Intel compilers on Windows) Sufficient evidence of intent is that user is working in the Intel compiler shell environment, otherwise this function returns False """ if not mesonlib.is_windows(): return False # handle where user tried to "blank" MKLROOT and left space(s) if not os.environ.get('MKLROOT', '').strip(): return False if (os.environ.get('CC') == 'icl' or os.environ.get('CXX') == 'icl' or os.environ.get('FC') == 'ifort'): return True # Intel-Cl users might not have the CC,CXX,FC envvars set, # but because they're in Intel shell, the exe's below are on PATH if shutil.which('icl') or shutil.which('ifort'): return True mlog.warning( 'It appears you might be intending to use Intel compiler on Windows ' 'since non-empty environment variable MKLROOT is set to {} ' 'However, Meson cannot find the Intel WIndows compiler executables (icl,ifort).' 'Please try using the Intel shell.'.format(os.environ.get('MKLROOT'))) return False
def main(): # Warn if the locale is not UTF-8. This can cause various unfixable issues # such as os.stat not being able to decode filenames with unicode in them. # There is no way to reset both the preferred encoding and the filesystem # encoding, so we can just warn about it. e = locale.getpreferredencoding() if e.upper() != 'UTF-8': mlog.warning('You are using {!r} which is not a a Unicode-compatible ' 'locale.'.format(e)) mlog.warning('You might see errors if you use UTF-8 strings as ' 'filenames, as strings, or as file contents.') mlog.warning('Please switch to a UTF-8 locale for your platform.') # Always resolve the command path so Ninja can find it for regen, tests, etc. launcher = os.path.realpath(sys.argv[0]) return mesonmain.run(launcher, sys.argv[1:])
def _run_cmd(self, cmd): starttime = time.time() if len(self.test.extra_paths) > 0: self.env['PATH'] = os.pathsep.join(self.test.extra_paths + ['']) + self.env['PATH'] if substring_is_in_list('wine', cmd): wine_paths = ['Z:' + p for p in self.test.extra_paths] wine_path = ';'.join(wine_paths) # Don't accidentally end with an `;` because that will add the # current directory and might cause unexpected behaviour if 'WINEPATH' in self.env: self.env['WINEPATH'] = wine_path + ';' + self.env['WINEPATH'] else: self.env['WINEPATH'] = wine_path # If MALLOC_PERTURB_ is not set, or if it is set to an empty value, # (i.e., the test or the environment don't explicitly set it), set # it ourselves. We do this unconditionally for regular tests # because it is extremely useful to have. # Setting MALLOC_PERTURB_="0" will completely disable this feature. if ('MALLOC_PERTURB_' not in self.env or not self.env['MALLOC_PERTURB_']) and not self.options.benchmark: self.env['MALLOC_PERTURB_'] = str(random.randint(1, 255)) stdout = None stderr = None if not self.options.verbose: stdout = tempfile.TemporaryFile("wb+") stderr = tempfile.TemporaryFile("wb+") if self.options and self.options.split else stdout # Let gdb handle ^C instead of us if self.options.gdb: previous_sigint_handler = signal.getsignal(signal.SIGINT) # Make the meson executable ignore SIGINT while gdb is running. signal.signal(signal.SIGINT, signal.SIG_IGN) def preexec_fn(): if self.options.gdb: # Restore the SIGINT handler for the child process to # ensure it can handle it. signal.signal(signal.SIGINT, signal.SIG_DFL) else: # We don't want setsid() in gdb because gdb needs the # terminal in order to handle ^C and not show tcsetpgrp() # errors avoid not being able to use the terminal. os.setsid() p = subprocess.Popen(cmd, stdout=stdout, stderr=stderr, env=self.env, cwd=self.test.workdir, preexec_fn=preexec_fn if not is_windows() else None) timed_out = False kill_test = False if self.test.timeout is None: timeout = None elif self.options.timeout_multiplier is not None: timeout = self.test.timeout * self.options.timeout_multiplier else: timeout = self.test.timeout try: p.communicate(timeout=timeout) except subprocess.TimeoutExpired: if self.options.verbose: print('%s time out (After %d seconds)' % (self.test.name, timeout)) timed_out = True except KeyboardInterrupt: mlog.warning('CTRL-C detected while running %s' % (self.test.name)) kill_test = True finally: if self.options.gdb: # Let us accept ^C again signal.signal(signal.SIGINT, previous_sigint_handler) additional_error = None if kill_test or timed_out: # Python does not provide multiplatform support for # killing a process and all its children so we need # to roll our own. if is_windows(): subprocess.call(['taskkill', '/F', '/T', '/PID', str(p.pid)]) else: try: # Kill the process group that setsid() created. os.killpg(p.pid, signal.SIGKILL) except ProcessLookupError: # Sometimes (e.g. with Wine) this happens. # There's nothing we can do (maybe the process # already died) so carry on. pass try: p.communicate(timeout=1) except subprocess.TimeoutExpired: # An earlier kill attempt has not worked for whatever reason. # Try to kill it one last time with a direct call. # If the process has spawned children, they will remain around. p.kill() try: p.communicate(timeout=1) except subprocess.TimeoutExpired: additional_error = b'Test process could not be killed.' except ValueError: additional_error = b'Could not read output. Maybe the process has redirected its stdout/stderr?' endtime = time.time() duration = endtime - starttime if additional_error is None: if stdout is None: # if stdout is None stderr should be as well stdo = '' stde = '' else: stdout.seek(0) stdo = decode(stdout.read()) if stderr != stdout: stderr.seek(0) stde = decode(stderr.read()) else: stde = "" else: stdo = "" stde = additional_error if timed_out: res = TestResult.TIMEOUT elif p.returncode == GNU_SKIP_RETURNCODE: res = TestResult.SKIP elif self.test.should_fail: res = TestResult.EXPECTEDFAIL if bool(p.returncode) else TestResult.UNEXPECTEDPASS else: res = TestResult.FAIL if bool(p.returncode) else TestResult.OK return TestRun(res, p.returncode, self.test.should_fail, duration, stdo, stde, cmd, self.test.env)
def _run_cmd(self, cmd): starttime = time.time() if len(self.test.extra_paths) > 0: self.env['PATH'] = os.pathsep.join(self.test.extra_paths + ['']) + self.env['PATH'] if substring_is_in_list('wine', cmd): wine_paths = ['Z:' + p for p in self.test.extra_paths] wine_path = ';'.join(wine_paths) # Don't accidentally end with an `;` because that will add the # current directory and might cause unexpected behaviour if 'WINEPATH' in self.env: self.env[ 'WINEPATH'] = wine_path + ';' + self.env['WINEPATH'] else: self.env['WINEPATH'] = wine_path # If MALLOC_PERTURB_ is not set, or if it is set to an empty value, # (i.e., the test or the environment don't explicitly set it), set # it ourselves. We do this unconditionally for regular tests # because it is extremely useful to have. # Setting MALLOC_PERTURB_="0" will completely disable this feature. if ('MALLOC_PERTURB_' not in self.env or not self.env['MALLOC_PERTURB_'] ) and not self.options.benchmark: self.env['MALLOC_PERTURB_'] = str(random.randint(1, 255)) stdout = None stderr = None if not self.options.verbose: stdout = tempfile.TemporaryFile("wb+") stderr = tempfile.TemporaryFile( "wb+") if self.options and self.options.split else stdout # Let gdb handle ^C instead of us if self.options.gdb: previous_sigint_handler = signal.getsignal(signal.SIGINT) # Make the meson executable ignore SIGINT while gdb is running. signal.signal(signal.SIGINT, signal.SIG_IGN) def preexec_fn(): if self.options.gdb: # Restore the SIGINT handler for the child process to # ensure it can handle it. signal.signal(signal.SIGINT, signal.SIG_DFL) else: # We don't want setsid() in gdb because gdb needs the # terminal in order to handle ^C and not show tcsetpgrp() # errors avoid not being able to use the terminal. os.setsid() p = subprocess.Popen( cmd, stdout=stdout, stderr=stderr, env=self.env, cwd=self.test.workdir, preexec_fn=preexec_fn if not is_windows() else None) timed_out = False kill_test = False if self.test.timeout is None: timeout = None elif self.options.timeout_multiplier is not None: timeout = self.test.timeout * self.options.timeout_multiplier else: timeout = self.test.timeout try: p.communicate(timeout=timeout) except subprocess.TimeoutExpired: if self.options.verbose: print('%s time out (After %d seconds)' % (self.test.name, timeout)) timed_out = True except KeyboardInterrupt: mlog.warning('CTRL-C detected while running %s' % (self.test.name)) kill_test = True finally: if self.options.gdb: # Let us accept ^C again signal.signal(signal.SIGINT, previous_sigint_handler) additional_error = None if kill_test or timed_out: # Python does not provide multiplatform support for # killing a process and all its children so we need # to roll our own. if is_windows(): subprocess.call(['taskkill', '/F', '/T', '/PID', str(p.pid)]) else: try: # Kill the process group that setsid() created. os.killpg(p.pid, signal.SIGKILL) except ProcessLookupError: # Sometimes (e.g. with Wine) this happens. # There's nothing we can do (maybe the process # already died) so carry on. pass try: p.communicate(timeout=1) except subprocess.TimeoutExpired: # An earlier kill attempt has not worked for whatever reason. # Try to kill it one last time with a direct call. # If the process has spawned children, they will remain around. p.kill() try: p.communicate(timeout=1) except subprocess.TimeoutExpired: additional_error = b'Test process could not be killed.' except ValueError: additional_error = b'Could not read output. Maybe the process has redirected its stdout/stderr?' endtime = time.time() duration = endtime - starttime if additional_error is None: if stdout is None: # if stdout is None stderr should be as well stdo = '' stde = '' else: stdout.seek(0) stdo = decode(stdout.read()) if stderr != stdout: stderr.seek(0) stde = decode(stderr.read()) else: stde = "" else: stdo = "" stde = additional_error if timed_out: res = TestResult.TIMEOUT elif p.returncode == GNU_SKIP_RETURNCODE: res = TestResult.SKIP elif self.test.should_fail: res = TestResult.EXPECTEDFAIL if bool( p.returncode) else TestResult.UNEXPECTEDPASS else: res = TestResult.FAIL if bool(p.returncode) else TestResult.OK return TestRun(res, p.returncode, self.test.should_fail, duration, stdo, stde, cmd, self.test.env)
def _run_cmd(self, cmd): starttime = time.time() if len(self.test.extra_paths) > 0: self.env['PATH'] = os.pathsep.join(self.test.extra_paths + ['']) + self.env['PATH'] # If MALLOC_PERTURB_ is not set, or if it is set to an empty value, # (i.e., the test or the environment don't explicitly set it), set # it ourselves. We do this unconditionally for regular tests # because it is extremely useful to have. # Setting MALLOC_PERTURB_="0" will completely disable this feature. if ('MALLOC_PERTURB_' not in self.env or not self.env['MALLOC_PERTURB_'] ) and not self.options.benchmark: self.env['MALLOC_PERTURB_'] = str(random.randint(1, 255)) stdout = None stderr = None if not self.options.verbose: stdout = subprocess.PIPE stderr = subprocess.PIPE if self.options and self.options.split else subprocess.STDOUT # Let gdb handle ^C instead of us if self.options.gdb: previous_sigint_handler = signal.getsignal(signal.SIGINT) # Make the meson executable ignore SIGINT while gdb is running. signal.signal(signal.SIGINT, signal.SIG_IGN) def preexec_fn(): if self.options.gdb: # Restore the SIGINT handler for the child process to # ensure it can handle it. signal.signal(signal.SIGINT, signal.SIG_DFL) else: # We don't want setsid() in gdb because gdb needs the # terminal in order to handle ^C and not show tcsetpgrp() # errors avoid not being able to use the terminal. os.setsid() p = subprocess.Popen( cmd, stdout=stdout, stderr=stderr, env=self.env, cwd=self.test.workdir, preexec_fn=preexec_fn if not is_windows() else None) timed_out = False kill_test = False if self.test.timeout is None: timeout = None elif self.options.timeout_multiplier is not None: timeout = self.test.timeout * self.options.timeout_multiplier else: timeout = self.test.timeout try: (stdo, stde) = p.communicate(timeout=timeout) except subprocess.TimeoutExpired: if self.options.verbose: print('%s time out (After %d seconds)' % (self.test.name, timeout)) timed_out = True except KeyboardInterrupt: mlog.warning('CTRL-C detected while running %s' % (self.test.name)) kill_test = True finally: if self.options.gdb: # Let us accept ^C again signal.signal(signal.SIGINT, previous_sigint_handler) if kill_test or timed_out: # Python does not provide multiplatform support for # killing a process and all its children so we need # to roll our own. if is_windows(): subprocess.call(['taskkill', '/F', '/T', '/PID', str(p.pid)]) else: try: os.killpg(os.getpgid(p.pid), signal.SIGKILL) except ProcessLookupError: # Sometimes (e.g. with Wine) this happens. # There's nothing we can do (maybe the process # already died) so carry on. pass (stdo, stde) = p.communicate() endtime = time.time() duration = endtime - starttime stdo = decode(stdo) if stde: stde = decode(stde) if timed_out: res = TestResult.TIMEOUT elif p.returncode == GNU_SKIP_RETURNCODE: res = TestResult.SKIP elif self.test.should_fail == bool(p.returncode): res = TestResult.OK else: res = TestResult.FAIL return TestRun(res, p.returncode, self.test.should_fail, duration, stdo, stde, cmd, self.test.env)
def _run_cmd(self, cmd): starttime = time.time() if len(self.test.extra_paths) > 0: self.env['PATH'] = os.pathsep.join(self.test.extra_paths + ['']) + self.env['PATH'] # If MALLOC_PERTURB_ is not set, or if it is set to an empty value, # (i.e., the test or the environment don't explicitly set it), set # it ourselves. We do this unconditionally for regular tests # because it is extremely useful to have. # Setting MALLOC_PERTURB_="0" will completely disable this feature. if ('MALLOC_PERTURB_' not in self.env or not self.env['MALLOC_PERTURB_']) and not self.options.benchmark: self.env['MALLOC_PERTURB_'] = str(random.randint(1, 255)) stdout = None stderr = None if not self.options.verbose: stdout = subprocess.PIPE stderr = subprocess.PIPE if self.options and self.options.split else subprocess.STDOUT # Let gdb handle ^C instead of us if self.options.gdb: previous_sigint_handler = signal.getsignal(signal.SIGINT) # Make the meson executable ignore SIGINT while gdb is running. signal.signal(signal.SIGINT, signal.SIG_IGN) def preexec_fn(): if self.options.gdb: # Restore the SIGINT handler for the child process to # ensure it can handle it. signal.signal(signal.SIGINT, signal.SIG_DFL) else: # We don't want setsid() in gdb because gdb needs the # terminal in order to handle ^C and not show tcsetpgrp() # errors avoid not being able to use the terminal. os.setsid() p = subprocess.Popen(cmd, stdout=stdout, stderr=stderr, env=self.env, cwd=self.test.workdir, preexec_fn=preexec_fn if not is_windows() else None) timed_out = False kill_test = False if self.test.timeout is None: timeout = None elif self.options.timeout_multiplier is not None: timeout = self.test.timeout * self.options.timeout_multiplier else: timeout = self.test.timeout try: (stdo, stde) = p.communicate(timeout=timeout) except subprocess.TimeoutExpired: if self.options.verbose: print('%s time out (After %d seconds)' % (self.test.name, timeout)) timed_out = True except KeyboardInterrupt: mlog.warning('CTRL-C detected while running %s' % (self.test.name)) kill_test = True finally: if self.options.gdb: # Let us accept ^C again signal.signal(signal.SIGINT, previous_sigint_handler) if kill_test or timed_out: # Python does not provide multiplatform support for # killing a process and all its children so we need # to roll our own. if is_windows(): subprocess.call(['taskkill', '/F', '/T', '/PID', str(p.pid)]) else: try: os.killpg(os.getpgid(p.pid), signal.SIGKILL) except ProcessLookupError: # Sometimes (e.g. with Wine) this happens. # There's nothing we can do (maybe the process # already died) so carry on. pass (stdo, stde) = p.communicate() endtime = time.time() duration = endtime - starttime stdo = decode(stdo) if stde: stde = decode(stde) if timed_out: res = TestResult.TIMEOUT elif p.returncode == GNU_SKIP_RETURNCODE: res = TestResult.SKIP elif self.test.should_fail == bool(p.returncode): res = TestResult.OK else: res = TestResult.FAIL return TestRun(res, p.returncode, self.test.should_fail, duration, stdo, stde, cmd, self.test.env)
def run_single_test(self, test): if test.fname[0].endswith('.jar'): cmd = ['java', '-jar'] + test.fname elif not test.is_cross_built and run_with_mono(test.fname[0]): cmd = ['mono'] + test.fname else: if test.is_cross_built: if test.exe_runner is None: # Can not run test on cross compiled executable # because there is no execute wrapper. cmd = None else: cmd = [test.exe_runner] + test.fname else: cmd = test.fname if cmd is None: res = 'SKIP' duration = 0.0 stdo = 'Not run because can not execute cross compiled binaries.' stde = None returncode = GNU_SKIP_RETURNCODE else: test_opts = deepcopy(self.options) test_env = self.get_test_env(test_opts, test) wrap = self.get_wrapper(test_opts) if test_opts.gdb: test.timeout = None cmd = wrap + cmd + test.cmd_args + self.options.test_args starttime = time.time() if len(test.extra_paths) > 0: test_env['PATH'] = os.pathsep.join(test.extra_paths + ['']) + test_env['PATH'] # If MALLOC_PERTURB_ is not set, or if it is set to an empty value, # (i.e., the test or the environment don't explicitly set it), set # it ourselves. We do this unconditionally for regular tests # because it is extremely useful to have. # Setting MALLOC_PERTURB_="0" will completely disable this feature. if ('MALLOC_PERTURB_' not in test_env or not test_env['MALLOC_PERTURB_'] ) and not self.options.benchmark: test_env['MALLOC_PERTURB_'] = str(random.randint(1, 255)) setsid = None stdout = None stderr = None if not self.options.verbose: stdout = subprocess.PIPE stderr = subprocess.PIPE if self.options and self.options.split else subprocess.STDOUT if not is_windows(): setsid = os.setsid p = subprocess.Popen(cmd, stdout=stdout, stderr=stderr, env=test_env, cwd=test.workdir, preexec_fn=setsid) timed_out = False kill_test = False if test.timeout is None: timeout = None else: timeout = test.timeout * test_opts.timeout_multiplier try: (stdo, stde) = p.communicate(timeout=timeout) except subprocess.TimeoutExpired: if self.options.verbose: print("%s time out (After %d seconds)" % (test.name, timeout)) timed_out = True except KeyboardInterrupt: mlog.warning("CTRL-C detected while running %s" % (test.name)) kill_test = True if kill_test or timed_out: # Python does not provide multiplatform support for # killing a process and all its children so we need # to roll our own. if is_windows(): subprocess.call( ['taskkill', '/F', '/T', '/PID', str(p.pid)]) else: try: os.killpg(os.getpgid(p.pid), signal.SIGKILL) except ProcessLookupError: # Sometimes (e.g. with Wine) this happens. # There's nothing we can do (maybe the process # already died) so carry on. pass (stdo, stde) = p.communicate() endtime = time.time() duration = endtime - starttime stdo = decode(stdo) if stde: stde = decode(stde) if timed_out: res = 'TIMEOUT' self.timeout_count += 1 self.fail_count += 1 elif p.returncode == GNU_SKIP_RETURNCODE: res = 'SKIP' self.skip_count += 1 elif test.should_fail == bool(p.returncode): res = 'OK' self.success_count += 1 else: res = 'FAIL' self.fail_count += 1 returncode = p.returncode result = TestRun(res, returncode, test.should_fail, duration, stdo, stde, cmd, test.env) return result
NINJA_1_9_OR_NEWER = False NINJA_CMD = None # If we're on CI, just assume we have ninja in PATH and it's new enough because # we provide that. This avoids having to detect ninja for every subprocess unit # test that we run. if 'CI' in os.environ: NINJA_1_9_OR_NEWER = True NINJA_CMD = 'ninja' else: # Look for 1.9 to see if https://github.com/ninja-build/ninja/issues/1219 # is fixed NINJA_CMD = detect_ninja('1.9') if NINJA_CMD is not None: NINJA_1_9_OR_NEWER = True else: mlog.warning('Found ninja <1.9, tests will run slower', once=True) NINJA_CMD = detect_ninja() if NINJA_CMD is None: raise RuntimeError('Could not find Ninja v1.7 or newer') def guess_backend(backend, msbuild_exe: str): # Auto-detect backend if unspecified backend_flags = [] if backend is None: if msbuild_exe is not None and (mesonlib.is_windows() and not _using_intelcl()): backend = 'vs' # Meson will auto-detect VS version to use else: backend = 'ninja' # Set backend arguments for Meson if backend.startswith('vs'): backend_flags = ['--backend=' + backend]