Merge pull request #13112 from DaanDeMeyer/interactive

Add meson test --interactive
pull/13136/head
Jussi Pakkanen 7 months ago committed by GitHub
commit 575414094d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      data/shell-completions/bash/meson
  2. 1
      data/shell-completions/zsh/_meson
  3. 10
      docs/markdown/Unit-tests.md
  4. 6
      docs/markdown/snippets/test_interactive.md
  5. 38
      mesonbuild/mtest.py

@ -566,6 +566,7 @@ _meson-test() {
no-rebuild no-rebuild
gdb gdb
gdb-path gdb-path
interactive
list list
wrapper wrapper
suite suite

@ -181,6 +181,7 @@ local -a meson_commands=(
'--no-rebuild[do not rebuild before running tests]' '--no-rebuild[do not rebuild before running tests]'
'--gdb[run tests under gdb]' '--gdb[run tests under gdb]'
'--gdb-path=[program to run for gdb (can be wrapper or compatible program)]:program:_path_commands' '--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]' '--list[list available tests]'
'(--wrapper --wrap)'{'--wrapper=','--wrap='}'[wrapper to run tests with]:wrapper program:_path_commands' '(--wrapper --wrap)'{'--wrapper=','--wrap='}'[wrapper to run tests with]:wrapper program:_path_commands'
"$__meson_cd" "$__meson_cd"

@ -256,6 +256,16 @@ $ meson test --gdb --gdb-path /path/to/gdb testname
$ meson test --print-errorlogs $ 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 Meson will report the output produced by the failing tests along with
other useful information as the environmental variables. This is other useful information as the environmental variables. This is
useful, for example, when you run the tests on Travis-CI, Jenkins and useful, for example, when you run the tests on Travis-CI, Jenkins and

@ -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.

@ -127,6 +127,8 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
help='Run test under gdb.') help='Run test under gdb.')
parser.add_argument('--gdb-path', default='gdb', dest='gdb_path', parser.add_argument('--gdb-path', default='gdb', dest='gdb_path',
help='Path to the gdb binary (default: gdb).') 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', parser.add_argument('--list', default=False, dest='list', action='store_true',
help='List available tests.') help='List available tests.')
parser.add_argument('--wrapper', default=None, dest='wrapper', type=split_args, 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 # the logger can use the console
LOGGER = 0 LOGGER = 0
# the console is used by gdb # the console is used by gdb or the user
GDB = 1 INTERACTIVE = 1
# the console is used to write stdout/stderr # the console is used to write stdout/stderr
STDOUT = 2 STDOUT = 2
@ -1417,7 +1419,7 @@ class SingleTestRunner:
if ('MSAN_OPTIONS' not in env or not env['MSAN_OPTIONS']): 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' 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 timeout = None
elif self.options.timeout_multiplier is None: elif self.options.timeout_multiplier is None:
timeout = self.test.timeout timeout = self.test.timeout
@ -1426,12 +1428,12 @@ class SingleTestRunner:
else: else:
timeout = self.test.timeout * self.options.timeout_multiplier 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 verbose = (test.verbose or self.options.verbose) and not self.options.quiet
self.runobj = TestRun(test, env, name, timeout, is_parallel, verbose) self.runobj = TestRun(test, env, name, timeout, is_parallel, verbose)
if self.options.gdb: if self.options.interactive:
self.console_mode = ConsoleUser.GDB self.console_mode = ConsoleUser.INTERACTIVE
elif self.runobj.direct_stdout: elif self.runobj.direct_stdout:
self.console_mode = ConsoleUser.STDOUT self.console_mode = ConsoleUser.STDOUT
else: else:
@ -1495,17 +1497,17 @@ class SingleTestRunner:
await self._run_cmd(harness, cmd) await self._run_cmd(harness, cmd)
return self.runobj return self.runobj
async def _run_subprocess(self, args: T.List[str], *, async def _run_subprocess(self, args: T.List[str], *, stdin: T.Optional[int],
stdout: T.Optional[int], stderr: T.Optional[int], stdout: T.Optional[int], stderr: T.Optional[int],
env: T.Dict[str, str], cwd: T.Optional[str]) -> TestSubprocess: env: T.Dict[str, str], cwd: T.Optional[str]) -> TestSubprocess:
# Let gdb handle ^C instead of us # Let gdb handle ^C instead of us
if self.options.gdb: if self.options.interactive:
previous_sigint_handler = signal.getsignal(signal.SIGINT) previous_sigint_handler = signal.getsignal(signal.SIGINT)
# Make the meson executable ignore SIGINT while gdb is running. # Make the meson executable ignore SIGINT while gdb is running.
signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGINT, signal.SIG_IGN)
def preexec_fn() -> None: def preexec_fn() -> None:
if self.options.gdb: if self.options.interactive:
# Restore the SIGINT handler for the child process to # Restore the SIGINT handler for the child process to
# ensure it can handle it. # ensure it can handle it.
signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL)
@ -1516,11 +1518,12 @@ class SingleTestRunner:
os.setsid() os.setsid()
def postwait_fn() -> None: def postwait_fn() -> None:
if self.options.gdb: if self.options.interactive:
# Let us accept ^C again # Let us accept ^C again
signal.signal(signal.SIGINT, previous_sigint_handler) signal.signal(signal.SIGINT, previous_sigint_handler)
p = await asyncio.create_subprocess_exec(*args, p = await asyncio.create_subprocess_exec(*args,
stdin=stdin,
stdout=stdout, stdout=stdout,
stderr=stderr, stderr=stderr,
env=env, env=env,
@ -1530,10 +1533,12 @@ class SingleTestRunner:
postwait_fn=postwait_fn if not is_windows() else None) postwait_fn=postwait_fn if not is_windows() else None)
async def _run_cmd(self, harness: 'TestHarness', cmd: T.List[str]) -> 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:
stdin = None
stdout = None stdout = None
stderr = None stderr = None
else: else:
stdin = asyncio.subprocess.DEVNULL
stdout = asyncio.subprocess.PIPE stdout = asyncio.subprocess.PIPE
stderr = asyncio.subprocess.STDOUT \ stderr = asyncio.subprocess.STDOUT \
if not self.options.split and not self.runobj.needs_parsing \ if not self.options.split and not self.runobj.needs_parsing \
@ -1547,6 +1552,7 @@ class SingleTestRunner:
extra_cmd.append(f'--gtest_output=xml:{gtestname}.xml') extra_cmd.append(f'--gtest_output=xml:{gtestname}.xml')
p = await self._run_subprocess(cmd + extra_cmd, p = await self._run_subprocess(cmd + extra_cmd,
stdin=stdin,
stdout=stdout, stdout=stdout,
stderr=stderr, stderr=stderr,
env=self.runobj.env, env=self.runobj.env,
@ -1591,7 +1597,7 @@ class TestHarness:
self.ninja: T.List[str] = None self.ninja: T.List[str] = None
self.logfile_base: T.Optional[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 namebase = None
self.logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase) self.logfile_base = os.path.join(self.options.wd, 'meson-logs', self.options.logbase)
@ -1691,6 +1697,7 @@ class TestHarness:
if not options.gdb: if not options.gdb:
options.gdb = current.gdb options.gdb = current.gdb
if options.gdb: if options.gdb:
options.interactive = True
options.verbose = True options.verbose = True
if options.timeout_multiplier is None: if options.timeout_multiplier is None:
options.timeout_multiplier = current.timeout_multiplier options.timeout_multiplier = current.timeout_multiplier
@ -2143,7 +2150,7 @@ def rebuild_deps(ninja: T.List[str], wd: str, tests: T.List[TestSerialisation])
return True return True
def run(options: argparse.Namespace) -> int: def run(options: argparse.Namespace) -> int:
if options.benchmark: if options.benchmark or options.interactive:
options.num_processes = 1 options.num_processes = 1
if options.verbose and options.quiet: if options.verbose and options.quiet:
@ -2152,12 +2159,15 @@ def run(options: argparse.Namespace) -> int:
check_bin = None check_bin = None
if options.gdb: if options.gdb:
options.verbose = True options.interactive = True
if options.wrapper: if options.wrapper:
print('Must not specify both a wrapper and gdb at the same time.') print('Must not specify both a wrapper and gdb at the same time.')
return 1 return 1
check_bin = 'gdb' check_bin = 'gdb'
if options.interactive:
options.verbose = True
if options.wrapper: if options.wrapper:
check_bin = options.wrapper[0] check_bin = options.wrapper[0]

Loading…
Cancel
Save