diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 8480e3d69..686ddfcde 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -28,6 +28,7 @@ import concurrent.futures as conc import platform import signal import random +from copy import deepcopy # GNU autotools interprets a return code of 77 from tests it executes to # mean that the test should be skipped. @@ -89,7 +90,7 @@ parser.add_argument('-v', '--verbose', default=False, action='store_true', help='Do not redirect stdout and stderr') parser.add_argument('-q', '--quiet', default=False, action='store_true', help='Produce less output to the terminal.') -parser.add_argument('-t', '--timeout-multiplier', type=float, default=None, +parser.add_argument('-t', '--timeout-multiplier', type=float, default=1, help='Define a multiplier for test timeout, for example ' ' when running tests in particular conditions they might take' ' more time to execute.') @@ -192,7 +193,17 @@ class TestHarness: if self.jsonlogfile: self.jsonlogfile.close() - def run_single_test(self, wrap, test): + def get_test_env(self, options, test): + if options.setup: + env = 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 + + 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]): @@ -215,24 +226,26 @@ 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 starttime = time.time() - child_env = os.environ.copy() - child_env.update(self.options.global_env.get_env(child_env)) - if isinstance(test.env, build.EnvironmentVariables): - test.env = test.env.get_env(child_env) - child_env.update(test.env) if len(test.extra_paths) > 0: - child_env['PATH'] = os.pathsep.join(test.extra_paths + ['']) + child_env['PATH'] + 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 child_env or not child_env['MALLOC_PERTURB_']) and not self.options.benchmark: - child_env['MALLOC_PERTURB_'] = str(random.randint(1, 255)) + 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 @@ -247,7 +260,7 @@ class TestHarness: p = subprocess.Popen(cmd, stdout=stdout, stderr=stderr, - env=child_env, + env=test_env, cwd=test.workdir, preexec_fn=setsid) timed_out = False @@ -255,7 +268,7 @@ class TestHarness: if test.timeout is None: timeout = None else: - timeout = test.timeout * self.options.timeout_multiplier + timeout = test.timeout * test_opts.timeout_multiplier try: (stdo, stde) = p.communicate(timeout=timeout) except subprocess.TimeoutExpired: @@ -444,7 +457,7 @@ TIMEOUT: %4d logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase) if self.options.wrapper: - namebase = os.path.basename(self.get_wrapper()[0]) + namebase = os.path.basename(self.get_wrapper(self.options)[0]) elif self.options.setup: namebase = self.options.setup.replace(":", "_") @@ -459,16 +472,16 @@ TIMEOUT: %4d self.logfile.write('Log of Meson test suite run on %s\n\n' % datetime.datetime.now().isoformat()) - def get_wrapper(self): + def get_wrapper(self, options): wrap = [] - if self.options.gdb: + if options.gdb: wrap = ['gdb', '--quiet', '--nh'] - if self.options.repeat > 1: + if options.repeat > 1: wrap += ['-ex', 'run', '-ex', 'quit'] # Signal the end of arguments to gdb wrap += ['--args'] - if self.options.wrapper: - wrap += self.options.wrapper + if options.wrapper: + wrap += options.wrapper assert(isinstance(wrap, list)) return wrap @@ -487,7 +500,6 @@ TIMEOUT: %4d futures = [] numlen = len('%d' % len(tests)) self.open_log_files() - wrap = self.get_wrapper() startdir = os.getcwd() if self.options.wd: os.chdir(self.options.wd) @@ -497,18 +509,15 @@ TIMEOUT: %4d for i, test in enumerate(tests): visible_name = self.get_pretty_suite(test) - if self.options.gdb: - test.timeout = None - if not test.is_parallel or self.options.gdb: self.drain_futures(futures) futures = [] - res = self.run_single_test(wrap, test) + res = self.run_single_test(test) 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, wrap, test) + f = executor.submit(self.run_single_test, test) futures.append((f, numlen, tests, visible_name, i)) if self.options.repeat > 1 and self.fail_count: break @@ -549,15 +558,19 @@ def list_tests(th): for t in tests: print(th.get_pretty_suite(t)) -def merge_suite_options(options): +def merge_suite_options(options, test): buildfile = os.path.join(options.wd, 'meson-private/build.dat') with open(buildfile, 'rb') as f: build = pickle.load(f) - if ":" not in options.setup: - options.setup = (build.subproject if build.subproject else build.project_name) + ":" + options.setup - if options.setup not in build.test_setups: - sys.exit('Unknown test setup: %s' % options.setup) - current = build.test_setups[options.setup] + if ":" in options.setup: + if options.setup not in build.test_setups: + sys.exit("Unknown test setup '%s'." % options.setup) + current = build.test_setups[options.setup] + else: + full_name = test.project_name + ":" + options.setup + if full_name not in build.test_setups: + sys.exit("Test setup '%s' not found from project '%s'." % (options.setup, test.project_name)) + current = build.test_setups[full_name] if not options.gdb: options.gdb = current.gdb if options.timeout_multiplier is None: @@ -568,7 +581,7 @@ def merge_suite_options(options): 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 + return current.env.get_env(os.environ.copy()) def rebuild_all(wd): if not os.path.isfile(os.path.join(wd, 'build.ninja')): @@ -595,15 +608,6 @@ def run(args): if options.benchmark: options.num_processes = 1 - if options.setup is not None: - global_env = merge_suite_options(options) - else: - global_env = build.EnvironmentVariables() - if options.timeout_multiplier is None: - options.timeout_multiplier = 1 - - setattr(options, 'global_env', global_env) - if options.verbose and options.quiet: print('Can not be both quiet and verbose at the same time.') return 1