|
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,77 @@ 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 | + """Initializes the ExternalTestMixin. |
| 181 | +
|
| 182 | + This class is intended to be used in multiple inheritance alongside `cmd2.Cmd` for an application class. |
| 183 | + When doing this multiple inheritance, it is imperative that this mixin class come first. |
| 184 | +
|
| 185 | + :type self: cmd2.Cmd |
| 186 | + :param args: arguments to pass to the superclass |
| 187 | + :param kwargs: keyword arguments to pass to the superclass |
| 188 | + """ |
| 189 | + # code placed here runs before cmd2 initializes |
| 190 | + super().__init__(*args, **kwargs) |
| 191 | + if not isinstance(self, cmd2.Cmd): |
| 192 | + raise TypeError('The ExternalTestMixin class is intended to be used in multiple inheritance with cmd2.Cmd') |
| 193 | + # code placed here runs after cmd2 initializes |
| 194 | + self._pybridge = cmd2.py_bridge.PyBridge(self) |
| 195 | + |
| 196 | + def app_cmd(self, command: str, echo: bool | None = None) -> cmd2.CommandResult: |
| 197 | + """ |
| 198 | + Run the application command |
| 199 | +
|
| 200 | + :param command: The application command as it would be written on the cmd2 application prompt |
| 201 | + :param echo: Flag whether the command's output should be echoed to stdout/stderr |
| 202 | + :return: A CommandResult object that captures stdout, stderr, and the command's result object |
| 203 | + """ |
| 204 | + try: |
| 205 | + self._in_py = True |
| 206 | + return self._pybridge(command, echo=echo) |
| 207 | + |
| 208 | + finally: |
| 209 | + self._in_py = False |
| 210 | + |
| 211 | + def fixture_setup(self): |
| 212 | + """Replicates the behavior of `cmdloop()` to prepare the application state for testing. |
| 213 | +
|
| 214 | + This method runs all preloop hooks and the preloop method to ensure the |
| 215 | + application is in the correct state before running a test. |
| 216 | +
|
| 217 | + :type self: cmd2.Cmd |
| 218 | + """ |
| 219 | + |
| 220 | + for func in self._preloop_hooks: |
| 221 | + func() |
| 222 | + self.preloop() |
| 223 | + |
| 224 | + def fixture_teardown(self): |
| 225 | + """Replicates the behavior of `cmdloop()` to tear down the application after a test. |
| 226 | +
|
| 227 | + This method runs all postloop hooks and the postloop method to clean up |
| 228 | + the application state and ensure test isolation. |
| 229 | +
|
| 230 | + :type self: cmd2.Cmd |
| 231 | + """ |
| 232 | + for func in self._postloop_hooks: |
| 233 | + func() |
| 234 | + self.postloop() |
| 235 | + |
| 236 | + |
| 237 | +class WithCommandSets(ExternalTestMixin, cmd2.Cmd): |
| 238 | + """Class for testing custom help_* methods which override docstring help.""" |
| 239 | + |
| 240 | + def __init__(self, *args, **kwargs) -> None: |
| 241 | + super().__init__(*args, **kwargs) |
0 commit comments