diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 6f0b057c1..cf800d183 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