Skip to content

Commit

Permalink
Merge pull request #13112 from DaanDeMeyer/interactive
Browse files Browse the repository at this point in the history
Add meson test --interactive
  • Loading branch information
jpakkane authored Apr 24, 2024
2 parents 44c279a + d68306c commit 5754140
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 14 deletions.
1 change: 1 addition & 0 deletions data/shell-completions/bash/meson
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ _meson-test() {
no-rebuild
gdb
gdb-path
interactive
list
wrapper
suite
Expand Down
1 change: 1 addition & 0 deletions data/shell-completions/zsh/_meson
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
10 changes: 10 additions & 0 deletions docs/markdown/Unit-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions docs/markdown/snippets/test_interactive.md
Original file line number Diff line number Diff line change
@@ -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.
38 changes: 24 additions & 14 deletions mesonbuild/mtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1417,7 +1419,7 @@ def __init__(self, test: TestSerialisation, env: T.Dict[str, str], name: str,
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
Expand All @@ -1426,12 +1428,12 @@ def __init__(self, test: TestSerialisation, env: T.Dict[str, str], name: str,
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:
Expand Down Expand Up @@ -1495,17 +1497,17 @@ async def run(self, harness: 'TestHarness') -> TestRun:
await self._run_cmd(harness, cmd)
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],
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)
Expand All @@ -1516,11 +1518,12 @@ def preexec_fn() -> None:
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)

p = await asyncio.create_subprocess_exec(*args,
stdin=stdin,
stdout=stdout,
stderr=stderr,
env=env,
Expand All @@ -1530,10 +1533,12 @@ def postwait_fn() -> None:
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:
stdin = None
stdout = None
stderr = None
else:
stdin = asyncio.subprocess.DEVNULL
stdout = asyncio.subprocess.PIPE
stderr = asyncio.subprocess.STDOUT \
if not self.options.split and not self.runobj.needs_parsing \
Expand All @@ -1547,6 +1552,7 @@ async def _run_cmd(self, harness: 'TestHarness', cmd: T.List[str]) -> None:
extra_cmd.append(f'--gtest_output=xml:{gtestname}.xml')

p = await self._run_subprocess(cmd + extra_cmd,
stdin=stdin,
stdout=stdout,
stderr=stderr,
env=self.runobj.env,
Expand Down Expand Up @@ -1591,7 +1597,7 @@ def __init__(self, options: argparse.Namespace):
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)

Expand Down Expand Up @@ -1691,6 +1697,7 @@ def merge_setup_options(self, options: argparse.Namespace, test: TestSerialisati
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
Expand Down Expand Up @@ -2143,7 +2150,7 @@ def convert_path_to_target(path: str) -> str:
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:
Expand All @@ -2152,12 +2159,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]

Expand Down

0 comments on commit 5754140

Please sign in to comment.