From fa39e1082c7f718540da349f072b57f9b815aade Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Mon, 26 Mar 2018 12:39:36 +0000 Subject: Timeout on test subprocesses instead of hanging and failing CI completely --- run_unittests.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/run_unittests.py b/run_unittests.py index 0c84475..1546bc6 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -523,16 +523,18 @@ class BasePlatformTests(unittest.TestCase): Run a command while printing the stdout and stderr to stdout, and also return a copy of it ''' - p = subprocess.Popen(command, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, env=os.environ.copy(), - universal_newlines=True, cwd=workdir) - output = p.communicate()[0] - print(output) + # If this call hangs CI will just abort. It is very hard to distinguish + # between CI issue and test bug in that case. Set timeout and fail loud + # instead. + p = subprocess.run(command, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, env=os.environ.copy(), + universal_newlines=True, cwd=workdir, timeout=60 * 5) + print(p.stdout) if p.returncode != 0: - if 'MESON_SKIP_TEST' in output: + if 'MESON_SKIP_TEST' in p.stdout: raise unittest.SkipTest('Project requested skipping.') raise subprocess.CalledProcessError(p.returncode, command) - return output + return p.stdout def init(self, srcdir, extra_args=None, default_args=True, inprocess=False): self.assertPathExists(srcdir) -- cgit v1.1 From 84e3cadc7044b5e177415c121fed1b3d3eee0f08 Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Wed, 28 Mar 2018 16:44:09 +0000 Subject: Use hermetic parameters in run_single_test(), initialize them before in the caller --- mesonbuild/mtest.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 4ed80b1..3a202ab 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -229,7 +229,8 @@ class TestHarness: options.wrapper = current.exe_wrapper return current.env.get_env(os.environ.copy()) - def get_test_env(self, options, test): + def get_test_env(self, test): + options = deepcopy(self.options) if options.setup: env = self.merge_suite_options(options, test) else: @@ -237,9 +238,9 @@ class TestHarness: if isinstance(test.env, build.EnvironmentVariables): test.env = test.env.get_env(env) env.update(test.env) - return env + return env, options - def run_single_test(self, test): + def run_single_test(self, test, test_env, test_opts): if test.fname[0].endswith('.jar'): cmd = ['java', '-jar'] + test.fname elif not test.is_cross_built and run_with_mono(test.fname[0]): @@ -262,14 +263,12 @@ class TestHarness: 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 + cmd = wrap + cmd + test.cmd_args + test_opts.test_args starttime = time.time() if len(test.extra_paths) > 0: @@ -280,14 +279,14 @@ class TestHarness: # 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: + if ('MALLOC_PERTURB_' not in test_env or not test_env['MALLOC_PERTURB_']) and not test_opts.benchmark: test_env['MALLOC_PERTURB_'] = str(random.randint(1, 255)) stdout = None stderr = None - if not self.options.verbose: + if not test_opts.verbose: stdout = subprocess.PIPE - stderr = subprocess.PIPE if self.options and self.options.split else subprocess.STDOUT + stderr = subprocess.PIPE if test_opts and test_opts.split else subprocess.STDOUT # Let gdb handle ^C instead of us if test_opts.gdb: @@ -323,7 +322,7 @@ class TestHarness: try: (stdo, stde) = p.communicate(timeout=timeout) except subprocess.TimeoutExpired: - if self.options.verbose: + if test_opts.verbose: print("%s time out (After %d seconds)" % (test.name, timeout)) timed_out = True except KeyboardInterrupt: @@ -556,12 +555,14 @@ TIMEOUT: %4d if not test.is_parallel or self.options.gdb: self.drain_futures(futures) futures = [] - res = self.run_single_test(test) + test_env, test_opts = self.get_test_env(test) + res = self.run_single_test(test, test_env, test_opts) self.print_stats(numlen, tests, visible_name, res, i) else: if not executor: executor = conc.ThreadPoolExecutor(max_workers=self.options.num_processes) - f = executor.submit(self.run_single_test, test) + test_env, test_opts = self.get_test_env(test) + f = executor.submit(self.run_single_test, test, test_env, test_opts) futures.append((f, numlen, tests, visible_name, i)) if self.options.repeat > 1 and self.fail_count: break -- cgit v1.1 From 2aa1c3d575e10054c7dee0a33742aa8b23578bca Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Wed, 28 Mar 2018 16:51:55 +0000 Subject: Use enum instead of string constants --- mesonbuild/mtest.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 3a202ab..91c4121 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -29,6 +29,7 @@ import platform import signal import random from copy import deepcopy +import enum # GNU autotools interprets a return code of 77 from tests it executes to # mean that the test should be skipped. @@ -106,9 +107,19 @@ class TestException(mesonlib.MesonException): pass +@enum.unique +class TestResult(enum.Enum): + + OK = 'OK' + TIMEOUT = 'TIMEOUT' + SKIP = 'SKIP' + FAIL = 'FAIL' + + class TestRun: def __init__(self, res, returncode, should_fail, duration, stdo, stde, cmd, env): + assert isinstance(res, TestResult) self.res = res self.returncode = returncode self.duration = duration @@ -148,7 +159,7 @@ def decode(stream): def write_json_log(jsonlogfile, test_name, result): jresult = {'name': test_name, 'stdout': result.stdo, - 'result': result.res, + 'result': result.res.value, 'duration': result.duration, 'returncode': result.returncode, 'command': result.cmd} @@ -257,7 +268,7 @@ class TestHarness: cmd = test.fname if cmd is None: - res = 'SKIP' + res = TestResult.SKIP duration = 0.0 stdo = 'Not run because can not execute cross compiled binaries.' stde = None @@ -354,17 +365,17 @@ class TestHarness: if stde: stde = decode(stde) if timed_out: - res = 'TIMEOUT' + res = TestResult.TIMEOUT self.timeout_count += 1 self.fail_count += 1 elif p.returncode == GNU_SKIP_RETURNCODE: - res = 'SKIP' + res = TestResult.SKIP self.skip_count += 1 elif test.should_fail == bool(p.returncode): - res = 'OK' + res = TestResult.OK self.success_count += 1 else: - res = 'FAIL' + res = TestResult.FAIL self.fail_count += 1 returncode = p.returncode result = TestRun(res, returncode, test.should_fail, duration, stdo, stde, cmd, test.env) @@ -375,14 +386,14 @@ class TestHarness: startpad = ' ' * (numlen - len('%d' % (i + 1))) num = '%s%d/%d' % (startpad, i + 1, len(tests)) padding1 = ' ' * (38 - len(name)) - padding2 = ' ' * (8 - len(result.res)) + padding2 = ' ' * (8 - len(result.res.value)) result_str = '%s %s %s%s%s%5.2f s' % \ - (num, name, padding1, result.res, padding2, result.duration) - if not self.options.quiet or result.res != 'OK': - if result.res != 'OK' and mlog.colorize_console: - if result.res == 'FAIL' or result.res == 'TIMEOUT': + (num, name, padding1, result.res.value, padding2, result.duration) + if not self.options.quiet or result.res is not TestResult.OK: + if result.res is not TestResult.OK and mlog.colorize_console: + if result.res is TestResult.FAIL or result.res is TestResult.TIMEOUT: decorator = mlog.red - elif result.res == 'SKIP': + elif result.res is TestResult.SKIP: decorator = mlog.yellow else: sys.exit('Unreachable code was ... well ... reached.') -- cgit v1.1 From 9c01fc0e49665c13f4f262fe5d456e4b133d3634 Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Wed, 28 Mar 2018 17:13:29 +0000 Subject: Do not access counters from parallel code --- mesonbuild/mtest.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 91c4121..61f7823 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -366,22 +366,30 @@ class TestHarness: stde = decode(stde) if timed_out: res = TestResult.TIMEOUT - self.timeout_count += 1 - self.fail_count += 1 elif p.returncode == GNU_SKIP_RETURNCODE: res = TestResult.SKIP - self.skip_count += 1 elif test.should_fail == bool(p.returncode): res = TestResult.OK - self.success_count += 1 else: res = TestResult.FAIL - self.fail_count += 1 returncode = p.returncode result = TestRun(res, returncode, test.should_fail, duration, stdo, stde, cmd, test.env) return result + def process_test_result(self, result): + if result.res is TestResult.TIMEOUT: + self.timeout_count += 1 + self.fail_count += 1 + elif result.res is TestResult.SKIP: + self.skip_count += 1 + elif result.res is TestResult.OK: + self.success_count += 1 + elif result.res is TestResult.FAIL: + self.fail_count += 1 + else: + sys.exit('Unknown test result encountered: {}'.format(result.res)) + def print_stats(self, numlen, tests, name, result, i): startpad = ' ' * (numlen - len('%d' % (i + 1))) num = '%s%d/%d' % (startpad, i + 1, len(tests)) @@ -568,6 +576,7 @@ TIMEOUT: %4d futures = [] test_env, test_opts = self.get_test_env(test) res = self.run_single_test(test, test_env, test_opts) + self.process_test_result(res) self.print_stats(numlen, tests, visible_name, res, i) else: if not executor: @@ -596,6 +605,7 @@ TIMEOUT: %4d result.cancel() if self.options.verbose: result.result() + self.process_test_result(result.result()) self.print_stats(numlen, tests, name, result.result(), i) def run_special(self): -- cgit v1.1 From 3eebb1f83ac30f1e9bfbe29346c351e5456eb0d9 Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Wed, 28 Mar 2018 17:18:45 +0000 Subject: Make run_single_test() method static Closes #3318 --- mesonbuild/mtest.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 61f7823..6f0b057 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -251,7 +251,8 @@ class TestHarness: env.update(test.env) return env, options - def run_single_test(self, test, test_env, test_opts): + @staticmethod + def run_single_test(test, test_env, test_opts): if test.fname[0].endswith('.jar'): cmd = ['java', '-jar'] + test.fname elif not test.is_cross_built and run_with_mono(test.fname[0]): @@ -274,7 +275,7 @@ class TestHarness: stde = None returncode = GNU_SKIP_RETURNCODE else: - wrap = self.get_wrapper(test_opts) + wrap = TestHarness.get_wrapper(test_opts) if test_opts.gdb: test.timeout = None @@ -533,7 +534,8 @@ TIMEOUT: %4d self.logfile.write('Log of Meson test suite run on %s\n\n' % datetime.datetime.now().isoformat()) - def get_wrapper(self, options): + @staticmethod + def get_wrapper(options): wrap = [] if options.gdb: wrap = ['gdb', '--quiet', '--nh'] -- cgit v1.1 From 9596fd6c24f5cf2d4836b53e7c3033b68835d71e Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Wed, 28 Mar 2018 17:32:39 +0000 Subject: Move run_single_test() into separate class --- mesonbuild/mtest.py | 196 +++++++++++++++++++++++++++------------------------- 1 file changed, 101 insertions(+), 95 deletions(-) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 6f0b057..cf800d1 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -192,81 +192,29 @@ def load_tests(build_dir): obj = pickle.load(f) return obj -class TestHarness: - def __init__(self, options): - self.options = options - self.collected_logs = [] - self.fail_count = 0 - self.success_count = 0 - self.skip_count = 0 - self.timeout_count = 0 - self.is_run = False - self.tests = None - self.suites = None - self.logfilename = None - self.logfile = None - self.jsonlogfile = None - if self.options.benchmark: - self.tests = load_benchmarks(options.wd) - else: - self.tests = load_tests(options.wd) - self.load_suites() - def __del__(self): - if self.logfile: - self.logfile.close() - if self.jsonlogfile: - self.jsonlogfile.close() +class SingleTestRunner: - def merge_suite_options(self, options, test): - if ":" in options.setup: - if options.setup not in self.build_data.test_setups: - sys.exit("Unknown test setup '%s'." % options.setup) - current = self.build_data.test_setups[options.setup] - else: - full_name = test.project_name + ":" + options.setup - if full_name not in self.build_data.test_setups: - sys.exit("Test setup '%s' not found from project '%s'." % (options.setup, test.project_name)) - current = self.build_data.test_setups[full_name] - if not options.gdb: - options.gdb = current.gdb - if options.timeout_multiplier is None: - options.timeout_multiplier = current.timeout_multiplier - # if options.env is None: - # options.env = current.env # FIXME, should probably merge options here. - if options.wrapper is not None and current.exe_wrapper is not None: - sys.exit('Conflict: both test setup and command line specify an exe wrapper.') - if options.wrapper is None: - options.wrapper = current.exe_wrapper - return current.env.get_env(os.environ.copy()) - - def get_test_env(self, test): - options = deepcopy(self.options) - if options.setup: - env = self.merge_suite_options(options, test) - else: - env = os.environ.copy() - if isinstance(test.env, build.EnvironmentVariables): - test.env = test.env.get_env(env) - env.update(test.env) - return env, options + def __init__(self, test, env, options): + self.test = test + self.env = env + self.options = options - @staticmethod - def run_single_test(test, test_env, test_opts): - 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 + def run(self): + if self.test.fname[0].endswith('.jar'): + cmd = ['java', '-jar'] + self.test.fname + elif not self.test.is_cross_built and run_with_mono(self.test.fname[0]): + cmd = ['mono'] + self.test.fname else: - if test.is_cross_built: - if test.exe_runner is None: + if self.test.is_cross_built: + if self.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 + cmd = [self.test.exe_runner] + self.test.fname else: - cmd = test.fname + cmd = self.test.fname if cmd is None: res = TestResult.SKIP @@ -275,39 +223,39 @@ class TestHarness: stde = None returncode = GNU_SKIP_RETURNCODE else: - wrap = TestHarness.get_wrapper(test_opts) + wrap = TestHarness.get_wrapper(self.options) - if test_opts.gdb: - test.timeout = None + if self.options.gdb: + self.test.timeout = None - cmd = wrap + cmd + test.cmd_args + test_opts.test_args + cmd = wrap + cmd + self.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 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 test_env or not test_env['MALLOC_PERTURB_']) and not test_opts.benchmark: - test_env['MALLOC_PERTURB_'] = str(random.randint(1, 255)) + 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 test_opts.verbose: + if not self.options.verbose: stdout = subprocess.PIPE - stderr = subprocess.PIPE if test_opts and test_opts.split else subprocess.STDOUT + stderr = subprocess.PIPE if self.options and self.options.split else subprocess.STDOUT # Let gdb handle ^C instead of us - if test_opts.gdb: + 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 test_opts.gdb: + 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) @@ -320,28 +268,28 @@ class TestHarness: p = subprocess.Popen(cmd, stdout=stdout, stderr=stderr, - env=test_env, - cwd=test.workdir, + env=self.env, + cwd=self.test.workdir, preexec_fn=preexec_fn if not is_windows() else None) timed_out = False kill_test = False - if test.timeout is None: + if self.test.timeout is None: timeout = None - elif test_opts.timeout_multiplier is not None: - timeout = test.timeout * test_opts.timeout_multiplier + elif self.options.timeout_multiplier is not None: + timeout = self.test.timeout * self.options.timeout_multiplier else: - timeout = test.timeout + timeout = self.test.timeout try: (stdo, stde) = p.communicate(timeout=timeout) except subprocess.TimeoutExpired: - if test_opts.verbose: - print("%s time out (After %d seconds)" % (test.name, timeout)) + 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" % (test.name)) + mlog.warning("CTRL-C detected while running %s" % (self.test.name)) kill_test = True finally: - if test_opts.gdb: + if self.options.gdb: # Let us accept ^C again signal.signal(signal.SIGINT, previous_sigint_handler) @@ -369,14 +317,72 @@ class TestHarness: res = TestResult.TIMEOUT elif p.returncode == GNU_SKIP_RETURNCODE: res = TestResult.SKIP - elif test.should_fail == bool(p.returncode): + elif self.test.should_fail == bool(p.returncode): res = TestResult.OK else: res = TestResult.FAIL returncode = p.returncode - result = TestRun(res, returncode, test.should_fail, duration, stdo, stde, cmd, test.env) + return TestRun(res, returncode, self.test.should_fail, duration, stdo, stde, cmd, self.test.env) - return result + +class TestHarness: + def __init__(self, options): + self.options = options + self.collected_logs = [] + self.fail_count = 0 + self.success_count = 0 + self.skip_count = 0 + self.timeout_count = 0 + self.is_run = False + self.tests = None + self.suites = None + self.logfilename = None + self.logfile = None + self.jsonlogfile = None + if self.options.benchmark: + self.tests = load_benchmarks(options.wd) + else: + self.tests = load_tests(options.wd) + self.load_suites() + + def __del__(self): + if self.logfile: + self.logfile.close() + if self.jsonlogfile: + self.jsonlogfile.close() + + def merge_suite_options(self, options, test): + if ":" in options.setup: + if options.setup not in self.build_data.test_setups: + sys.exit("Unknown test setup '%s'." % options.setup) + current = self.build_data.test_setups[options.setup] + else: + full_name = test.project_name + ":" + options.setup + if full_name not in self.build_data.test_setups: + sys.exit("Test setup '%s' not found from project '%s'." % (options.setup, test.project_name)) + current = self.build_data.test_setups[full_name] + if not options.gdb: + options.gdb = current.gdb + if options.timeout_multiplier is None: + options.timeout_multiplier = current.timeout_multiplier + # if options.env is None: + # options.env = current.env # FIXME, should probably merge options here. + if options.wrapper is not None and current.exe_wrapper is not None: + sys.exit('Conflict: both test setup and command line specify an exe wrapper.') + if options.wrapper is None: + options.wrapper = current.exe_wrapper + return current.env.get_env(os.environ.copy()) + + def get_test_runner(self, test): + options = deepcopy(self.options) + if options.setup: + env = self.merge_suite_options(options, test) + else: + env = os.environ.copy() + if isinstance(test.env, build.EnvironmentVariables): + test.env = test.env.get_env(env) + env.update(test.env) + return SingleTestRunner(test, env, options) def process_test_result(self, result): if result.res is TestResult.TIMEOUT: @@ -576,15 +582,15 @@ TIMEOUT: %4d if not test.is_parallel or self.options.gdb: self.drain_futures(futures) futures = [] - test_env, test_opts = self.get_test_env(test) - res = self.run_single_test(test, test_env, test_opts) + single_test = self.get_test_runner(test) + res = single_test.run() self.process_test_result(res) self.print_stats(numlen, tests, visible_name, res, i) else: if not executor: executor = conc.ThreadPoolExecutor(max_workers=self.options.num_processes) - test_env, test_opts = self.get_test_env(test) - f = executor.submit(self.run_single_test, test, test_env, test_opts) + single_test = self.get_test_runner(test) + f = executor.submit(single_test.run) futures.append((f, numlen, tests, visible_name, i)) if self.options.repeat > 1 and self.fail_count: break -- cgit v1.1 From 0e8c69b7962bf668567f5aab2911ba25ded773ca Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Wed, 28 Mar 2018 18:14:55 +0000 Subject: Split SingleTestRunner._get_cmd() out of run() --- mesonbuild/mtest.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index cf800d1..d0b8d22 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -200,22 +200,24 @@ class SingleTestRunner: self.env = env self.options = options - def run(self): + def _get_cmd(self): if self.test.fname[0].endswith('.jar'): - cmd = ['java', '-jar'] + self.test.fname + return ['java', '-jar'] + self.test.fname elif not self.test.is_cross_built and run_with_mono(self.test.fname[0]): - cmd = ['mono'] + self.test.fname + return ['mono'] + self.test.fname else: if self.test.is_cross_built: if self.test.exe_runner is None: # Can not run test on cross compiled executable # because there is no execute wrapper. - cmd = None + return None else: - cmd = [self.test.exe_runner] + self.test.fname + return [self.test.exe_runner] + self.test.fname else: - cmd = self.test.fname + return self.test.fname + def run(self): + cmd = self._get_cmd() if cmd is None: res = TestResult.SKIP duration = 0.0 -- cgit v1.1 From 827d33c8b65aaecd4817d67d821f0b7db6048717 Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Wed, 28 Mar 2018 18:57:50 +0000 Subject: Split SingleTestRunner.run() --- mesonbuild/mtest.py | 196 ++++++++++++++++++++++++++-------------------------- 1 file changed, 97 insertions(+), 99 deletions(-) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index d0b8d22..412896e 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -219,112 +219,110 @@ class SingleTestRunner: def run(self): cmd = self._get_cmd() if cmd is None: - res = TestResult.SKIP - duration = 0.0 - stdo = 'Not run because can not execute cross compiled binaries.' - stde = None - returncode = GNU_SKIP_RETURNCODE + skip_stdout = 'Not run because can not execute cross compiled binaries.' + return TestRun(res=TestResult.SKIP, returncode=GNU_SKIP_RETURNCODE, + should_fail=self.test.should_fail, duration=0.0, + stdo=skip_stdout, stde=None, cmd=None, env=self.test.env) else: wrap = TestHarness.get_wrapper(self.options) - if self.options.gdb: self.test.timeout = None - - cmd = wrap + cmd + self.test.cmd_args + self.options.test_args - 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 + return self._run_cmd(wrap + cmd + self.test.cmd_args + self.options.test_args) + + 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: - 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 + # Restore the SIGINT handler for the child process to + # ensure it can handle it. + signal.signal(signal.SIGINT, signal.SIG_DFL) 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 + # 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: - res = TestResult.FAIL - returncode = p.returncode - return TestRun(res, returncode, self.test.should_fail, duration, stdo, stde, cmd, self.test.env) + 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) class TestHarness: -- cgit v1.1 From ddc6f72507a19c32b34f560c5f2b8c07a80682e7 Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Wed, 28 Mar 2018 21:31:04 +0000 Subject: Use consistent quotes --- mesonbuild/mtest.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 412896e..1c2a9ae 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -134,7 +134,7 @@ class TestRun: if self.cmd is None: res += 'NONE\n' else: - res += "%s%s\n" % (''.join(["%s='%s' " % (k, v) for k, v in self.env.items()]), ' ' .join(self.cmd)) + res += '%s%s\n' % (''.join(["%s='%s' " % (k, v) for k, v in self.env.items()]), ' ' .join(self.cmd)) if self.stdo: res += '--- stdout ---\n' res += self.stdo @@ -284,10 +284,10 @@ class SingleTestRunner: (stdo, stde) = p.communicate(timeout=timeout) except subprocess.TimeoutExpired: if self.options.verbose: - print("%s time out (After %d seconds)" % (self.test.name, timeout)) + 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)) + mlog.warning('CTRL-C detected while running %s' % (self.test.name)) kill_test = True finally: if self.options.gdb: @@ -352,7 +352,7 @@ class TestHarness: self.jsonlogfile.close() def merge_suite_options(self, options, test): - if ":" in options.setup: + if ':' in options.setup: if options.setup not in self.build_data.test_setups: sys.exit("Unknown test setup '%s'." % options.setup) current = self.build_data.test_setups[options.setup] @@ -617,7 +617,7 @@ TIMEOUT: %4d self.print_stats(numlen, tests, name, result.result(), i) def run_special(self): - 'Tests run by the user, usually something like "under gdb 1000 times".' + '''Tests run by the user, usually something like "under gdb 1000 times".''' if self.is_run: raise RuntimeError('Can not use run_special after a full run.') tests = self.get_tests() @@ -634,7 +634,7 @@ def list_tests(th): def rebuild_all(wd): if not os.path.isfile(os.path.join(wd, 'build.ninja')): - print("Only ninja backend is supported to rebuild tests before running them.") + print('Only ninja backend is supported to rebuild tests before running them.') return True ninja = environment.detect_ninja() @@ -646,7 +646,7 @@ def rebuild_all(wd): p.communicate() if p.returncode != 0: - print("Could not rebuild") + print('Could not rebuild') return False return True @@ -675,7 +675,7 @@ def run(args): if check_bin is not None: exe = ExternalProgram(check_bin, silent=True) if not exe.found(): - sys.exit("Could not find requested program: %s" % check_bin) + sys.exit('Could not find requested program: %s' % check_bin) options.wd = os.path.abspath(options.wd) if not options.list and not options.no_rebuild: -- cgit v1.1