diff --git a/data/shell-completions/bash/meson b/data/shell-completions/bash/meson index 55c9c008d..dc437f10f 100644 --- a/data/shell-completions/bash/meson +++ b/data/shell-completions/bash/meson @@ -566,6 +566,7 @@ _meson-test() { no-rebuild gdb gdb-path + interactive list wrapper suite diff --git a/data/shell-completions/zsh/_meson b/data/shell-completions/zsh/_meson index e6f50f1af..402539f1b 100644 --- a/data/shell-completions/zsh/_meson +++ b/data/shell-completions/zsh/_meson @@ -181,6 +181,7 @@ local -a meson_commands=( '--no-rebuild[do not rebuild before running tests]' '--gdb[run tests under gdb]' '--gdb-path=[program to run for gdb (can be wrapper or compatible program)]:program:_path_commands' + '(--interactive -i)'{'--interactive','-i'}'[run tests with interactive input/output]' '--list[list available tests]' '(--wrapper --wrap)'{'--wrapper=','--wrap='}'[wrapper to run tests with]:wrapper program:_path_commands' "$__meson_cd" diff --git a/docs/markdown/Unit-tests.md b/docs/markdown/Unit-tests.md index dc509a818..73e58dc68 100644 --- a/docs/markdown/Unit-tests.md +++ b/docs/markdown/Unit-tests.md @@ -256,6 +256,16 @@ $ meson test --gdb --gdb-path /path/to/gdb testname $ meson test --print-errorlogs ``` +Running tests interactively can be done with the `--interactive` option. +`meson test --interactive` invokes tests with stdout, stdin and stderr +connected directly to the calling terminal. This can be useful if your test is +an integration test running in a container or virtual machine where a debug +shell is spawned if it fails *(added 1.5.0)*: + +```console +$ meson test --interactive testname +``` + Meson will report the output produced by the failing tests along with other useful information as the environmental variables. This is useful, for example, when you run the tests on Travis-CI, Jenkins and diff --git a/docs/markdown/snippets/test_interactive.md b/docs/markdown/snippets/test_interactive.md new file mode 100644 index 000000000..907147fd9 --- /dev/null +++ b/docs/markdown/snippets/test_interactive.md @@ -0,0 +1,6 @@ +## The Meson test program supports a new "--interactive" argument + +`meson test --interactive` invokes tests with stdout, stdin and stderr +connected directly to the calling terminal. This can be useful when running +integration tests that run in containers or virtual machines which can spawn a +debug shell if a test fails. diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index b9f72fac7..311274f37 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -127,6 +127,8 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: help='Run test under gdb.') parser.add_argument('--gdb-path', default='gdb', dest='gdb_path', help='Path to the gdb binary (default: gdb).') + parser.add_argument('-i', '--interactive', default=False, dest='interactive', + action='store_true', help='Run tests with interactive input/output.') parser.add_argument('--list', default=False, dest='list', action='store_true', help='List available tests.') parser.add_argument('--wrapper', default=None, dest='wrapper', type=split_args, @@ -233,8 +235,8 @@ class ConsoleUser(enum.Enum): # the logger can use the console LOGGER = 0 - # the console is used by gdb - GDB = 1 + # the console is used by gdb or the user + INTERACTIVE = 1 # the console is used to write stdout/stderr STDOUT = 2 @@ -1417,7 +1419,7 @@ class SingleTestRunner: if ('MSAN_OPTIONS' not in env or not env['MSAN_OPTIONS']): env['MSAN_OPTIONS'] = 'halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1' - if self.options.gdb or self.test.timeout is None or self.test.timeout <= 0: + if self.options.interactive or self.test.timeout is None or self.test.timeout <= 0: timeout = None elif self.options.timeout_multiplier is None: timeout = self.test.timeout @@ -1426,12 +1428,12 @@ class SingleTestRunner: else: timeout = self.test.timeout * self.options.timeout_multiplier - is_parallel = test.is_parallel and self.options.num_processes > 1 and not self.options.gdb + is_parallel = test.is_parallel and self.options.num_processes > 1 and not self.options.interactive verbose = (test.verbose or self.options.verbose) and not self.options.quiet self.runobj = TestRun(test, env, name, timeout, is_parallel, verbose) - if self.options.gdb: - self.console_mode = ConsoleUser.GDB + if self.options.interactive: + self.console_mode = ConsoleUser.INTERACTIVE elif self.runobj.direct_stdout: self.console_mode = ConsoleUser.STDOUT else: @@ -1499,13 +1501,13 @@ class SingleTestRunner: stdout: T.Optional[int], stderr: T.Optional[int], env: T.Dict[str, str], cwd: T.Optional[str]) -> TestSubprocess: # Let gdb handle ^C instead of us - if self.options.gdb: + if self.options.interactive: 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() -> None: - if self.options.gdb: + if self.options.interactive: # Restore the SIGINT handler for the child process to # ensure it can handle it. signal.signal(signal.SIGINT, signal.SIG_DFL) @@ -1516,7 +1518,7 @@ class SingleTestRunner: os.setsid() def postwait_fn() -> None: - if self.options.gdb: + if self.options.interactive: # Let us accept ^C again signal.signal(signal.SIGINT, previous_sigint_handler) @@ -1530,7 +1532,7 @@ class SingleTestRunner: postwait_fn=postwait_fn if not is_windows() else None) async def _run_cmd(self, harness: 'TestHarness', cmd: T.List[str]) -> None: - if self.console_mode is ConsoleUser.GDB: + if self.console_mode is ConsoleUser.INTERACTIVE: stdout = None stderr = None else: @@ -1591,7 +1593,7 @@ class TestHarness: self.ninja: T.List[str] = None self.logfile_base: T.Optional[str] = None - if self.options.logbase and not self.options.gdb: + if self.options.logbase and not self.options.interactive: namebase = None self.logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase) @@ -1691,6 +1693,7 @@ class TestHarness: if not options.gdb: options.gdb = current.gdb if options.gdb: + options.interactive = True options.verbose = True if options.timeout_multiplier is None: options.timeout_multiplier = current.timeout_multiplier @@ -2143,7 +2146,7 @@ def rebuild_deps(ninja: T.List[str], wd: str, tests: T.List[TestSerialisation]) return True def run(options: argparse.Namespace) -> int: - if options.benchmark: + if options.benchmark or options.interactive: options.num_processes = 1 if options.verbose and options.quiet: @@ -2152,12 +2155,15 @@ def run(options: argparse.Namespace) -> int: check_bin = None if options.gdb: - options.verbose = True + options.interactive = True if options.wrapper: print('Must not specify both a wrapper and gdb at the same time.') return 1 check_bin = 'gdb' + if options.interactive: + options.verbose = True + if options.wrapper: check_bin = options.wrapper[0]