|
5 | 5 | from collections.abc import Callable
|
6 | 6 | from contextlib import redirect_stderr
|
7 | 7 | from typing import (
|
| 8 | + TYPE_CHECKING, |
8 | 9 | ParamSpec,
|
9 | 10 | TextIO,
|
10 | 11 | TypeVar,
|
@@ -164,3 +165,71 @@ def find_subcommand(action: argparse.ArgumentParser, subcmd_names: list[str]) ->
|
164 | 165 | return find_subcommand(choice, subcmd_names)
|
165 | 166 | break
|
166 | 167 | raise ValueError(f"Could not find subcommand '{subcmd_names}'")
|
| 168 | + |
| 169 | + |
| 170 | +if TYPE_CHECKING: |
| 171 | + _Base = cmd2.Cmd |
| 172 | +else: |
| 173 | + _Base = object |
| 174 | + |
| 175 | + |
| 176 | +class ExternalTestMixin(_Base): |
| 177 | + """A cmd2 plugin (mixin class) that exposes an interface to execute application commands from python""" |
| 178 | + |
| 179 | + def __init__(self, *args, **kwargs): |
| 180 | + """ |
| 181 | +
|
| 182 | + :type self: cmd2.Cmd |
| 183 | + :param args: |
| 184 | + :param kwargs: |
| 185 | + """ |
| 186 | + # code placed here runs before cmd2 initializes |
| 187 | + super().__init__(*args, **kwargs) |
| 188 | + assert isinstance(self, cmd2.Cmd) |
| 189 | + # code placed here runs after cmd2 initializes |
| 190 | + self._pybridge = cmd2.py_bridge.PyBridge(self) |
| 191 | + |
| 192 | + def app_cmd(self, command: str, echo: bool | None = None) -> cmd2.CommandResult: |
| 193 | + """ |
| 194 | + Run the application command |
| 195 | +
|
| 196 | + :param command: The application command as it would be written on the cmd2 application prompt |
| 197 | + :param echo: Flag whether the command's output should be echoed to stdout/stderr |
| 198 | + :return: A CommandResult object that captures stdout, stderr, and the command's result object |
| 199 | + """ |
| 200 | + assert isinstance(self, cmd2.Cmd) |
| 201 | + assert isinstance(self, ExternalTestMixin) |
| 202 | + try: |
| 203 | + self._in_py = True |
| 204 | + |
| 205 | + return self._pybridge(command, echo=echo) |
| 206 | + |
| 207 | + finally: |
| 208 | + self._in_py = False |
| 209 | + |
| 210 | + def fixture_setup(self): |
| 211 | + """ |
| 212 | + Replicates the behavior of `cmdloop()` preparing the state of the application |
| 213 | + :type self: cmd2.Cmd |
| 214 | + """ |
| 215 | + |
| 216 | + for func in self._preloop_hooks: |
| 217 | + func() |
| 218 | + self.preloop() |
| 219 | + |
| 220 | + def fixture_teardown(self): |
| 221 | + """ |
| 222 | + Replicates the behavior of `cmdloop()` tearing down the application |
| 223 | +
|
| 224 | + :type self: cmd2.Cmd |
| 225 | + """ |
| 226 | + for func in self._postloop_hooks: |
| 227 | + func() |
| 228 | + self.postloop() |
| 229 | + |
| 230 | + |
| 231 | +class WithCommandSets(ExternalTestMixin, cmd2.Cmd): |
| 232 | + """Class for testing custom help_* methods which override docstring help.""" |
| 233 | + |
| 234 | + def __init__(self, *args, **kwargs) -> None: |
| 235 | + super().__init__(*args, **kwargs) |
0 commit comments