diff options
-rw-r--r-- | cmd2/pyscript_bridge.py | 41 | ||||
-rw-r--r-- | cmd2/transcript.py | 23 | ||||
-rw-r--r-- | cmd2/utils.py | 58 | ||||
-rw-r--r-- | tests/conftest.py | 22 | ||||
-rw-r--r-- | tests/test_argparse.py | 10 | ||||
-rw-r--r-- | tests/test_autocompletion.py | 12 | ||||
-rw-r--r-- | tests/test_cmd2.py | 60 | ||||
-rw-r--r-- | tests/test_completion.py | 14 | ||||
-rw-r--r-- | tests/test_plugin.py | 74 | ||||
-rw-r--r-- | tests/test_pyscript.py | 20 | ||||
-rw-r--r-- | tests/test_transcript.py | 16 | ||||
-rw-r--r-- | tests/test_utils.py | 55 |
12 files changed, 225 insertions, 180 deletions
diff --git a/cmd2/pyscript_bridge.py b/cmd2/pyscript_bridge.py index 3f58ab84..d8fa5fb9 100644 --- a/cmd2/pyscript_bridge.py +++ b/cmd2/pyscript_bridge.py @@ -12,15 +12,15 @@ import functools import sys from typing import List, Callable +from .argparse_completer import _RangeAction +from .utils import namedtuple_with_defaults, StdSim + # Python 3.4 require contextlib2 for temporarily redirecting stderr and stdout if sys.version_info < (3, 5): from contextlib2 import redirect_stdout, redirect_stderr else: from contextlib import redirect_stdout, redirect_stderr -from .argparse_completer import _RangeAction -from .utils import namedtuple_with_defaults - class CommandResult(namedtuple_with_defaults('CommandResult', ['stdout', 'stderr', 'data'])): """Encapsulates the results from a command. @@ -38,37 +38,12 @@ class CommandResult(namedtuple_with_defaults('CommandResult', ['stdout', 'stderr return not self.stderr and self.data is not None -class CopyStream(object): - """Copies all data written to a stream""" - def __init__(self, inner_stream, echo: bool = False) -> None: - self.buffer = '' - self.inner_stream = inner_stream - self.echo = echo - - def write(self, s): - self.buffer += s - if self.echo: - self.inner_stream.write(s) - - def read(self): - raise NotImplementedError - - def clear(self): - self.buffer = '' - - def __getattr__(self, item: str): - if item in self.__dict__: - return self.__dict__[item] - else: - return getattr(self.inner_stream, item) - - def _exec_cmd(cmd2_app, func: Callable, echo: bool): """Helper to encapsulate executing a command and capturing the results""" - copy_stdout = CopyStream(sys.stdout, echo) - copy_stderr = CopyStream(sys.stderr, echo) + copy_stdout = StdSim(sys.stdout, echo) + copy_stderr = StdSim(sys.stderr, echo) - copy_cmd_stdout = CopyStream(cmd2_app.stdout, echo) + copy_cmd_stdout = StdSim(cmd2_app.stdout, echo) cmd2_app._last_result = None @@ -81,9 +56,9 @@ def _exec_cmd(cmd2_app, func: Callable, echo: bool): cmd2_app.stdout = copy_cmd_stdout.inner_stream # if stderr is empty, set it to None - stderr = copy_stderr.buffer if copy_stderr.buffer else None + stderr = copy_stderr.getvalue() if copy_stderr.getvalue() else None - outbuf = copy_cmd_stdout.buffer if copy_cmd_stdout.buffer else copy_stdout.buffer + outbuf = copy_cmd_stdout.getvalue() if copy_cmd_stdout.getvalue() else copy_stdout.getvalue() result = CommandResult(stdout=outbuf, stderr=stderr, data=cmd2_app._last_result) return result diff --git a/cmd2/transcript.py b/cmd2/transcript.py index 8df58634..2d94f4e4 100644 --- a/cmd2/transcript.py +++ b/cmd2/transcript.py @@ -44,7 +44,7 @@ class Cmd2TestCase(unittest.TestCase): # Trap stdout self._orig_stdout = self.cmdapp.stdout - self.cmdapp.stdout = OutputTrap() + self.cmdapp.stdout = utils.StdSim(self.cmdapp.stdout) def runTest(self): # was testall if self.cmdapp: @@ -203,24 +203,3 @@ class Cmd2TestCase(unittest.TestCase): if self.cmdapp: # Restore stdout self.cmdapp.stdout = self._orig_stdout - -class OutputTrap(object): - """Instantiate an OutputTrap to divert/capture ALL stdout output. - For use in transcript testing. - """ - - def __init__(self): - self.contents = '' - - def write(self, txt: str): - """Add text to the internal contents.""" - self.contents += txt - - def read(self) -> str: - """Read from the internal contents and then clear them out. - - :return: str - text from the internal contents - """ - result = self.contents - self.contents = '' - return result diff --git a/cmd2/utils.py b/cmd2/utils.py index 735221c8..bdb488cc 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -246,3 +246,61 @@ def natural_sort(list_to_sort: List[str]) -> List[str]: :return: the list sorted naturally """ return sorted(list_to_sort, key=natural_keys) + + +class StdSim(object): + """Class to simulate behavior of sys.stdout or sys.stderr. + + Stores contents in internal buffer and optionally echos to the inner stream it is simulating. + """ + class ByteBuf(object): + """Inner class which stores an actual bytes buffer and does the actual output if echo is enabled.""" + def __init__(self, inner_stream, echo: bool = False) -> None: + self.byte_buf = b'' + self.inner_stream = inner_stream + self.echo = echo + + def write(self, b: bytes) -> None: + """Add bytes to internal bytes buffer and if echo is True, echo contents to inner stream.""" + if not isinstance(b, bytes): + raise TypeError('a bytes-like object is required, not {}'.format(type(b))) + self.byte_buf += b + if self.echo: + self.inner_stream.buffer.write(b) + + def __init__(self, inner_stream, echo: bool = False) -> None: + self.buffer = self.ByteBuf(inner_stream, echo) + self.inner_stream = inner_stream + + def write(self, s: str) -> None: + """Add str to internal bytes buffer and if echo is True, echo contents to inner stream.""" + if not isinstance(s, str): + raise TypeError('write() argument must be str, not {}'.format(type(s))) + b = s.encode() + self.buffer.write(b) + + def getvalue(self) -> str: + """Get the internal contents as a str. + + :return string from the internal contents + """ + return self.buffer.byte_buf.decode() + + def read(self) -> str: + """Read from the internal contents as a str and then clear them out. + + :return: string from the internal contents + """ + result = self.getvalue() + self.clear() + return result + + def clear(self) -> None: + """Clear the internal contents.""" + self.buffer.byte_buf = b'' + + def __getattr__(self, item: str): + if item in self.__dict__: + return self.__dict__[item] + else: + return getattr(self.inner_stream, item) diff --git a/tests/conftest.py b/tests/conftest.py index a85135b9..561f281b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,6 +12,7 @@ from unittest import mock from pytest import fixture import cmd2 +from cmd2.utils import StdSim # Prefer statically linked gnureadline if available (for macOS compatibility due to issues with libedit) try: @@ -115,21 +116,6 @@ timing: False # Report execution times """.format(color_str) -class StdOut(object): - """ Toy class for replacing self.stdout in cmd2.Cmd instances for unit testing. """ - def __init__(self): - self.buffer = '' - - def write(self, s): - self.buffer += s - - def read(self): - raise NotImplementedError - - def clear(self): - self.buffer = '' - - def normalize(block): """ Normalize a block of text to perform comparison. @@ -142,10 +128,10 @@ def normalize(block): def run_cmd(app, cmd): - """ Clear StdOut buffer, run the command, extract the buffer contents, """ + """ Clear StdSim buffer, run the command, extract the buffer contents, """ app.stdout.clear() app.onecmd_plus_hooks(cmd) - out = app.stdout.buffer + out = app.stdout.getvalue() app.stdout.clear() return normalize(out) @@ -153,7 +139,7 @@ def run_cmd(app, cmd): @fixture def base_app(): c = cmd2.Cmd() - c.stdout = StdOut() + c.stdout = StdSim(c.stdout) return c diff --git a/tests/test_argparse.py b/tests/test_argparse.py index 469cbe76..bf20710b 100644 --- a/tests/test_argparse.py +++ b/tests/test_argparse.py @@ -6,9 +6,9 @@ import argparse import pytest import cmd2 -from unittest import mock +from cmd2.utils import StdSim -from .conftest import run_cmd, StdOut +from .conftest import run_cmd # Prefer statically linked gnureadline if available (for macOS compatibility due to issues with libedit) try: @@ -115,7 +115,7 @@ class ArgparseApp(cmd2.Cmd): @pytest.fixture def argparse_app(): app = ArgparseApp() - app.stdout = StdOut() + app.stdout = StdSim(app.stdout) return app @@ -217,12 +217,12 @@ class SubcommandApp(cmd2.Cmd): func(self, args) else: # No subcommand was provided, so call help - self.do_help('base') + self.do_help(['base']) @pytest.fixture def subcommand_app(): app = SubcommandApp() - app.stdout = StdOut() + app.stdout = StdSim(app.stdout) return app diff --git a/tests/test_autocompletion.py b/tests/test_autocompletion.py index 8aa26e0e..8035587a 100644 --- a/tests/test_autocompletion.py +++ b/tests/test_autocompletion.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Unit/functional testing for argparse completer in cmd2 @@ -5,16 +6,17 @@ Copyright 2018 Eric Lin <anselor@gmail.com> Released under MIT license, see LICENSE file """ import pytest -from .conftest import run_cmd, normalize, StdOut, complete_tester + +from cmd2.utils import StdSim +from .conftest import run_cmd, normalize, complete_tester from examples.tab_autocompletion import TabCompleteExample @pytest.fixture def cmd2_app(): - c = TabCompleteExample() - c.stdout = StdOut() - - return c + app = TabCompleteExample() + app.stdout = StdSim(app.stdout) + return app SUGGEST_HELP = '''Usage: suggest -t {movie, show} [-h] [-d DURATION{1..2}] diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 1e7e2c3f..4b0687c9 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -25,7 +25,7 @@ import cmd2 from cmd2 import clipboard from cmd2 import utils from .conftest import run_cmd, normalize, BASE_HELP, BASE_HELP_VERBOSE, \ - HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT, SHOW_LONG, StdOut + HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT, SHOW_LONG def test_version(base_app): @@ -930,7 +930,7 @@ def test_base_cmdloop_with_queue(): app.use_rawinput = True intro = 'Hello World, this is an intro ...' app.cmdqueue.append('quit\n') - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog"] @@ -938,7 +938,7 @@ def test_base_cmdloop_with_queue(): with mock.patch.object(sys, 'argv', testargs): # Run the command loop with custom intro app.cmdloop(intro=intro) - out = app.stdout.buffer + out = app.stdout.getvalue() assert out == expected @@ -947,7 +947,7 @@ def test_base_cmdloop_without_queue(): app = cmd2.Cmd() app.use_rawinput = True app.intro = 'Hello World, this is an intro ...' - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input', return_value='quit') @@ -959,7 +959,7 @@ def test_base_cmdloop_without_queue(): with mock.patch.object(sys, 'argv', testargs): # Run the command loop app.cmdloop() - out = app.stdout.buffer + out = app.stdout.getvalue() assert out == expected @@ -969,7 +969,7 @@ def test_cmdloop_without_rawinput(): app.use_rawinput = False app.echo = False app.intro = 'Hello World, this is an intro ...' - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input', return_value='quit') @@ -981,7 +981,7 @@ def test_cmdloop_without_rawinput(): with mock.patch.object(sys, 'argv', testargs): # Run the command loop app.cmdloop() - out = app.stdout.buffer + out = app.stdout.getvalue() assert out == expected @@ -996,7 +996,7 @@ class HookFailureApp(cmd2.Cmd): @pytest.fixture def hook_failure(): app = HookFailureApp() - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) return app def test_precmd_hook_success(base_app): @@ -1020,7 +1020,7 @@ class SayApp(cmd2.Cmd): def say_app(): app = SayApp() app.allow_cli_args = False - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) return app def test_interrupt_quit(say_app): @@ -1034,7 +1034,7 @@ def test_interrupt_quit(say_app): say_app.cmdloop() # And verify the expected output to stdout - out = say_app.stdout.buffer + out = say_app.stdout.getvalue() assert out == 'hello\n' def test_interrupt_noquit(say_app): @@ -1048,7 +1048,7 @@ def test_interrupt_noquit(say_app): say_app.cmdloop() # And verify the expected output to stdout - out = say_app.stdout.buffer + out = say_app.stdout.getvalue() assert out == 'hello\n^C\ngoodbye\n' @@ -1060,7 +1060,7 @@ class ShellApp(cmd2.Cmd): @pytest.fixture def shell_app(): app = ShellApp() - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) return app def test_default_to_shell_unknown(shell_app): @@ -1140,7 +1140,7 @@ class HelpApp(cmd2.Cmd): @pytest.fixture def help_app(): app = HelpApp() - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) return app def test_custom_command_help(help_app): @@ -1203,7 +1203,7 @@ class HelpCategoriesApp(cmd2.Cmd): @pytest.fixture def helpcat_app(): app = HelpCategoriesApp() - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) return app def test_help_cat_base(helpcat_app): @@ -1296,7 +1296,7 @@ class SelectApp(cmd2.Cmd): @pytest.fixture def select_app(): app = SelectApp() - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) return app def test_select_options(select_app): @@ -1461,7 +1461,7 @@ class MultilineApp(cmd2.Cmd): @pytest.fixture def multiline_app(): app = MultilineApp() - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) return app def test_multiline_complete_empty_statement_raises_exception(multiline_app): @@ -1522,7 +1522,7 @@ class CommandResultApp(cmd2.Cmd): @pytest.fixture def commandresult_app(): app = CommandResultApp() - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) return app def test_commandresult_truthy(commandresult_app): @@ -1628,7 +1628,7 @@ def piped_rawinput_true(capsys, echo, command): # run the cmdloop, which should pull input from our mock app._cmdloop() out, err = capsys.readouterr() - return (app, out) + return app, out # using the decorator puts the original input function back when this unit test returns @mock.patch('builtins.input', mock.MagicMock(name='input', side_effect=['set', EOFError])) @@ -1658,7 +1658,7 @@ def piped_rawinput_false(capsys, echo, command): app.echo = echo app._cmdloop() out, err = capsys.readouterr() - return (app, out) + return app, out def test_pseudo_raw_input_piped_rawinput_false_echo_true(capsys): command = 'set' @@ -1715,28 +1715,28 @@ def test_empty_stdin_input(): def test_poutput_string(base_app): msg = 'This is a test' base_app.poutput(msg) - out = base_app.stdout.buffer + out = base_app.stdout.getvalue() expected = msg + '\n' assert out == expected def test_poutput_zero(base_app): msg = 0 base_app.poutput(msg) - out = base_app.stdout.buffer + out = base_app.stdout.getvalue() expected = str(msg) + '\n' assert out == expected def test_poutput_empty_string(base_app): msg = '' base_app.poutput(msg) - out = base_app.stdout.buffer + out = base_app.stdout.getvalue() expected = msg assert out == expected def test_poutput_none(base_app): msg = None base_app.poutput(msg) - out = base_app.stdout.buffer + out = base_app.stdout.getvalue() expected = '' assert out == expected @@ -1826,7 +1826,7 @@ def test_ppaged(base_app): msg = 'testing...' end = '\n' base_app.ppaged(msg) - out = base_app.stdout.buffer + out = base_app.stdout.getvalue() assert out == msg + end # we override cmd.parseline() so we always get consistent @@ -1859,14 +1859,14 @@ def test_readline_remove_history_item(base_app): def test_onecmd_raw_str_continue(base_app): line = "help" stop = base_app.onecmd(line) - out = base_app.stdout.buffer + out = base_app.stdout.getvalue() assert not stop assert out.strip() == BASE_HELP.strip() def test_onecmd_raw_str_quit(base_app): line = "quit" stop = base_app.onecmd(line) - out = base_app.stdout.buffer + out = base_app.stdout.getvalue() assert stop assert out == '' @@ -1996,7 +1996,7 @@ def test_exit_code_default(exit_code_repl): # Create a cmd2.Cmd() instance and make sure basic settings are like we want for test app = exit_code_repl app.use_rawinput = True - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input', return_value='exit') @@ -2008,14 +2008,14 @@ def test_exit_code_default(exit_code_repl): with mock.patch.object(sys, 'argv', testargs): # Run the command loop app.cmdloop() - out = app.stdout.buffer + out = app.stdout.getvalue() assert out == expected def test_exit_code_nonzero(exit_code_repl): # Create a cmd2.Cmd() instance and make sure basic settings are like we want for test app = exit_code_repl app.use_rawinput = True - app.stdout = StdOut() + app.stdout = utils.StdSim(app.stdout) # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input', return_value='exit 23') @@ -2028,5 +2028,5 @@ def test_exit_code_nonzero(exit_code_repl): # Run the command loop with pytest.raises(SystemExit): app.cmdloop() - out = app.stdout.buffer + out = app.stdout.getvalue() assert out == expected diff --git a/tests/test_completion.py b/tests/test_completion.py index 00a120cc..40299954 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -15,7 +15,7 @@ import sys import pytest import cmd2 from cmd2 import utils -from .conftest import complete_tester, StdOut +from .conftest import complete_tester from examples.subcommands import SubcommandsExample # List of strings used with completion functions @@ -114,13 +114,6 @@ def test_complete_bogus_command(cmd2_app): assert first_match is None -def test_cmd2_command_completion_single(cmd2_app): - text = 'hel' - line = text - endidx = len(line) - begidx = endidx - len(text) - assert cmd2_app.completenames(text, line, begidx, endidx) == ['help'] - def test_cmd2_command_completion_multiple(cmd2_app): text = 'h' line = text @@ -694,8 +687,7 @@ def test_add_opening_quote_delimited_space_in_prefix(cmd2_app): @pytest.fixture def sc_app(): c = SubcommandsExample() - c.stdout = StdOut() - + c.stdout = utils.StdSim(c.stdout) return c def test_cmd2_subcommand_completion_single_end(sc_app): @@ -843,7 +835,7 @@ class SubcommandsWithUnknownExample(cmd2.Cmd): func(self, args) else: # No subcommand was provided, so call help - self.do_help('base') + self.do_help(['base']) @pytest.fixture diff --git a/tests/test_plugin.py b/tests/test_plugin.py index e401e837..20f2f32c 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -5,18 +5,14 @@ Test plugin infrastructure and hooks. Copyright 2018 Jared Crapo <jared@kotfu.net> Released under MIT license, see LICENSE file """ - -from typing import Tuple - import pytest import cmd2 from cmd2 import plugin -from .conftest import StdOut class Plugin: - "A mixin class for testing hook registration and calling" + """A mixin class for testing hook registration and calling""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.reset_counters() @@ -35,19 +31,19 @@ class Plugin: # ### def prepost_hook_one(self) -> None: - "Method used for preloop or postloop hooks" + """Method used for preloop or postloop hooks""" self.poutput("one") def prepost_hook_two(self) -> None: - "Another method used for preloop or postloop hooks" + """Another method used for preloop or postloop hooks""" self.poutput("two") def prepost_hook_too_many_parameters(self, param) -> None: - "A preloop or postloop hook with too many parameters" + """A preloop or postloop hook with too many parameters""" pass def prepost_hook_with_wrong_return_annotation(self) -> bool: - "A preloop or postloop hook with incorrect return type" + """A preloop or postloop hook with incorrect return type""" pass ### @@ -56,7 +52,7 @@ class Plugin: # ### def preparse(self, line: str) -> str: - "Preparsing hook" + """Preparsing hook""" self.called_preparse += 1 return line @@ -66,44 +62,44 @@ class Plugin: # ### def postparse_hook(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData: - "A postparsing hook" + """A postparsing hook""" self.called_postparsing += 1 return data def postparse_hook_stop(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData: - "A postparsing hook with requests application exit" + """A postparsing hook with requests application exit""" self.called_postparsing += 1 data.stop = True return data def postparse_hook_emptystatement(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData: - "A postparsing hook with raises an EmptyStatement exception" + """A postparsing hook with raises an EmptyStatement exception""" self.called_postparsing += 1 raise cmd2.EmptyStatement def postparse_hook_exception(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData: - "A postparsing hook which raises an exception" + """A postparsing hook which raises an exception""" self.called_postparsing += 1 raise ValueError def postparse_hook_too_many_parameters(self, data1, data2) -> cmd2.plugin.PostparsingData: - "A postparsing hook with too many parameters" + """A postparsing hook with too many parameters""" pass def postparse_hook_undeclared_parameter_annotation(self, data) -> cmd2.plugin.PostparsingData: - "A postparsing hook with an undeclared parameter type" + """A postparsing hook with an undeclared parameter type""" pass def postparse_hook_wrong_parameter_annotation(self, data: str) -> cmd2.plugin.PostparsingData: - "A postparsing hook with the wrong parameter type" + """A postparsing hook with the wrong parameter type""" pass def postparse_hook_undeclared_return_annotation(self, data: cmd2.plugin.PostparsingData): - "A postparsing hook with an undeclared return type" + """A postparsing hook with an undeclared return type""" pass def postparse_hook_wrong_return_annotation(self, data: cmd2.plugin.PostparsingData) -> str: - "A postparsing hook with the wrong return type" + """A postparsing hook with the wrong return type""" pass ### @@ -112,43 +108,43 @@ class Plugin: # ### def precmd(self, statement: cmd2.Statement) -> cmd2.Statement: - "Override cmd.Cmd method" + """Override cmd.Cmd method""" self.called_precmd += 1 return statement def precmd_hook(self, data: plugin.PrecommandData) -> plugin.PrecommandData: - "A precommand hook" + """A precommand hook""" self.called_precmd += 1 return data def precmd_hook_emptystatement(self, data: plugin.PrecommandData) -> plugin.PrecommandData: - "A precommand hook which raises an EmptyStatement exception" + """A precommand hook which raises an EmptyStatement exception""" self.called_precmd += 1 raise cmd2.EmptyStatement def precmd_hook_exception(self, data: plugin.PrecommandData) -> plugin.PrecommandData: - "A precommand hook which raises an exception" + """A precommand hook which raises an exception""" self.called_precmd += 1 raise ValueError def precmd_hook_not_enough_parameters(self) -> plugin.PrecommandData: - "A precommand hook with no parameters" + """A precommand hook with no parameters""" pass def precmd_hook_too_many_parameters(self, one: plugin.PrecommandData, two: str) -> plugin.PrecommandData: - "A precommand hook with too many parameters" + """A precommand hook with too many parameters""" return one def precmd_hook_no_parameter_annotation(self, data) -> plugin.PrecommandData: - "A precommand hook with no type annotation on the parameter" + """A precommand hook with no type annotation on the parameter""" return data def precmd_hook_wrong_parameter_annotation(self, data: str) -> plugin.PrecommandData: - "A precommand hook with the incorrect type annotation on the parameter" + """A precommand hook with the incorrect type annotation on the parameter""" return data def precmd_hook_no_return_annotation(self, data: plugin.PrecommandData): - "A precommand hook with no type annotation on the return value" + """A precommand hook with no type annotation on the return value""" return data def precmd_hook_wrong_return_annotation(self, data: plugin.PrecommandData) -> cmd2.Statement: @@ -160,38 +156,38 @@ class Plugin: # ### def postcmd(self, stop: bool, statement: cmd2.Statement) -> bool: - "Override cmd.Cmd method" + """Override cmd.Cmd method""" self.called_postcmd += 1 return stop def postcmd_hook(self, data: plugin.PostcommandData) -> plugin.PostcommandData: - "A postcommand hook" + """A postcommand hook""" self.called_postcmd += 1 return data def postcmd_hook_exception(self, data: plugin.PostcommandData) -> plugin.PostcommandData: - "A postcommand hook with raises an exception" + """A postcommand hook with raises an exception""" self.called_postcmd += 1 raise ZeroDivisionError def postcmd_hook_not_enough_parameters(self) -> plugin.PostcommandData: - "A precommand hook with no parameters" + """A precommand hook with no parameters""" pass def postcmd_hook_too_many_parameters(self, one: plugin.PostcommandData, two: str) -> plugin.PostcommandData: - "A precommand hook with too many parameters" + """A precommand hook with too many parameters""" return one def postcmd_hook_no_parameter_annotation(self, data) -> plugin.PostcommandData: - "A precommand hook with no type annotation on the parameter" + """A precommand hook with no type annotation on the parameter""" return data def postcmd_hook_wrong_parameter_annotation(self, data: str) -> plugin.PostcommandData: - "A precommand hook with the incorrect type annotation on the parameter" + """A precommand hook with the incorrect type annotation on the parameter""" return data def postcmd_hook_no_return_annotation(self, data: plugin.PostcommandData): - "A precommand hook with no type annotation on the return value" + """A precommand hook with no type annotation on the return value""" return data def postcmd_hook_wrong_return_annotation(self, data: plugin.PostcommandData) -> cmd2.Statement: @@ -208,13 +204,13 @@ class Plugin: return data def cmdfinalization_hook_stop(self, data: cmd2.plugin.CommandFinalizationData) -> cmd2.plugin.CommandFinalizationData: - "A postparsing hook which requests application exit" + """A postparsing hook which requests application exit""" self.called_cmdfinalization += 1 data.stop = True return data def cmdfinalization_hook_exception(self, data: cmd2.plugin.CommandFinalizationData) -> cmd2.plugin.CommandFinalizationData: - "A postparsing hook which raises an exception" + """A postparsing hook which raises an exception""" self.called_cmdfinalization += 1 raise ValueError @@ -244,7 +240,7 @@ class Plugin: class PluggedApp(Plugin, cmd2.Cmd): - "A sample app with a plugin mixed in" + """A sample app with a plugin mixed in""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/tests/test_pyscript.py b/tests/test_pyscript.py index 73c1a62a..256e63a7 100644 --- a/tests/test_pyscript.py +++ b/tests/test_pyscript.py @@ -1,3 +1,4 @@ +# coding=utf-8 """ Unit/functional testing for argparse completer in cmd2 @@ -8,24 +9,25 @@ import os import pytest from cmd2.cmd2 import Cmd, with_argparser from cmd2 import argparse_completer -from .conftest import run_cmd, StdOut -from cmd2.utils import namedtuple_with_defaults +from .conftest import run_cmd +from cmd2.utils import namedtuple_with_defaults, StdSim + class PyscriptExample(Cmd): ratings_types = ['G', 'PG', 'PG-13', 'R', 'NC-17'] def _do_media_movies(self, args) -> None: if not args.command: - self.do_help('media movies') + self.do_help(['media movies']) else: print('media movies ' + str(args.__dict__)) def _do_media_shows(self, args) -> None: if not args.command: - self.do_help('media shows') + self.do_help(['media shows']) if not args.command: - self.do_help('media shows') + self.do_help(['media shows']) else: print('media shows ' + str(args.__dict__)) @@ -70,7 +72,7 @@ class PyscriptExample(Cmd): func(self, args) else: # No subcommand was provided, so call help - self.do_help('media') + self.do_help(['media']) foo_parser = argparse_completer.ACArgumentParser(prog='foo') foo_parser.add_argument('-c', dest='counter', action='count') @@ -114,8 +116,7 @@ class PyscriptExample(Cmd): @pytest.fixture def ps_app(): c = PyscriptExample() - c.stdout = StdOut() - + c.stdout = StdSim(c.stdout) return c @@ -131,8 +132,7 @@ class PyscriptCustomNameExample(Cmd): @pytest.fixture def ps_echo(): c = PyscriptCustomNameExample() - c.stdout = StdOut() - + c.stdout = StdSim(c.stdout) return c diff --git a/tests/test_transcript.py b/tests/test_transcript.py index 3caf6a37..f854241b 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -16,8 +16,10 @@ from unittest import mock import pytest import cmd2 -from .conftest import run_cmd, StdOut +from .conftest import run_cmd from cmd2 import transcript +from cmd2.utils import StdSim + class CmdLineApp(cmd2.Cmd): @@ -83,9 +85,9 @@ def test_commands_at_invocation(): expected = "This is an intro banner ...\nhello\nGracie\n" with mock.patch.object(sys, 'argv', testargs): app = CmdLineApp() - app.stdout = StdOut() + app.stdout = StdSim(app.stdout) app.cmdloop() - out = app.stdout.buffer + out = app.stdout.getvalue() assert out == expected @pytest.mark.parametrize('filename,feedback_to_output', [ @@ -129,7 +131,7 @@ def test_transcript(request, capsys, filename, feedback_to_output): def test_history_transcript(request, capsys): app = CmdLineApp() - app.stdout = StdOut() + app.stdout = StdSim(app.stdout) run_cmd(app, 'orate this is\na /multiline/\ncommand;\n') run_cmd(app, 'speak /tmp/file.txt is not a regex') @@ -150,13 +152,13 @@ this is a \/multiline\/ command # read in the transcript created by the history command with open(history_fname) as f: - transcript = f.read() + xscript = f.read() - assert transcript == expected + assert xscript == expected def test_history_transcript_bad_filename(request, capsys): app = CmdLineApp() - app.stdout = StdOut() + app.stdout = StdSim(app.stdout) run_cmd(app, 'orate this is\na /multiline/\ncommand;\n') run_cmd(app, 'speak /tmp/file.txt is not a regex') diff --git a/tests/test_utils.py b/tests/test_utils.py index 8c8daa39..53031567 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -5,6 +5,10 @@ Unit testing for cmd2/utils.py module. Copyright 2018 Todd Leonhardt <todd.leonhardt@gmail.com> Released under MIT license, see LICENSE file """ +import sys + +import pytest + from colorama import Fore import cmd2.utils as cu @@ -110,4 +114,55 @@ def test_quot_string_if_needed_no(): assert cu.quote_string_if_needed(your_str) == your_str +@pytest.fixture +def stdout_sim(): + stdsim = cu.StdSim(sys.stdout) + return stdsim + +def test_stdsim_write_str(stdout_sim): + my_str = 'Hello World' + stdout_sim.write(my_str) + assert stdout_sim.getvalue() == my_str + +def test_stdsim_write_bytes(stdout_sim): + b_str = b'Hello World' + with pytest.raises(TypeError): + stdout_sim.write(b_str) + +def test_stdsim_buffer_write_bytes(stdout_sim): + b_str = b'Hello World' + stdout_sim.buffer.write(b_str) + assert stdout_sim.getvalue() == b_str.decode() + +def test_stdsim_buffer_write_str(stdout_sim): + my_str = 'Hello World' + with pytest.raises(TypeError): + stdout_sim.buffer.write(my_str) + +def test_stdsim_read(stdout_sim): + my_str = 'Hello World' + stdout_sim.write(my_str) + # getvalue() returns the value and leaves it unaffected internally + assert stdout_sim.getvalue() == my_str + # read() returns the value and then clears the internal buffer + assert stdout_sim.read() == my_str + assert stdout_sim.getvalue() == '' + +def test_stdsim_clear(stdout_sim): + my_str = 'Hello World' + stdout_sim.write(my_str) + assert stdout_sim.getvalue() == my_str + stdout_sim.clear() + assert stdout_sim.getvalue() == '' + +def test_stdsim_getattr_exist(stdout_sim): + # Here the StdSim getattr is allowing us to access methods within StdSim + my_str = 'Hello World' + stdout_sim.write(my_str) + val_func = getattr(stdout_sim, 'getvalue') + assert val_func() == my_str + +def test_stdsim_getattr_noexist(stdout_sim): + # Here the StdSim getattr is allowing us to access methods defined by the inner stream + assert not stdout_sim.isatty() |