# coding=utf-8 """ Cmd2 unit/functional testing Copyright 2016 Federico Ceratto Released under MIT license, see LICENSE file """ import os import sys import tempfile import mock import pytest import six from code import InteractiveConsole # Used for sm.input: raw_input() for Python 2 or input() for Python 3 import six.moves as sm import cmd2 from conftest import run_cmd, normalize, BASE_HELP, HELP_HISTORY, SHORTCUTS_TXT, SHOW_TXT, SHOW_LONG, StdOut def test_ver(): assert cmd2.__version__ == '0.7.7' def test_empty_statement(base_app): out = run_cmd(base_app, '') expected = normalize('') assert out == expected def test_base_help(base_app): out = run_cmd(base_app, 'help') expected = normalize(BASE_HELP) assert out == expected def test_base_help_history(base_app): out = run_cmd(base_app, 'help history') expected = normalize(HELP_HISTORY) assert out == expected def test_base_options_help(base_app, capsys): run_cmd(base_app, 'show -h') out, err = capsys.readouterr() expected = run_cmd(base_app, 'help show') # 'show -h' is the same as 'help show', other than whitespace differences of an extra newline present in 'help show' assert normalize(str(out)) == expected def test_base_invalid_option(base_app, capsys): run_cmd(base_app, 'show -z') out, err = capsys.readouterr() show_help = run_cmd(base_app, 'help show') expected = ['no such option: -z'] expected.extend(show_help) # 'show -h' is the same as 'help show', other than whitespace differences of an extra newline present in 'help show' assert normalize(str(out)) == expected def test_base_shortcuts(base_app): out = run_cmd(base_app, 'shortcuts') expected = normalize(SHORTCUTS_TXT) assert out == expected def test_base_show(base_app): # force editor to be 'vim' so test is repeatable across platforms base_app.editor = 'vim' out = run_cmd(base_app, 'show') expected = normalize(SHOW_TXT) assert out == expected def test_base_show_long(base_app): # force editor to be 'vim' so test is repeatable across platforms base_app.editor = 'vim' out = run_cmd(base_app, 'show -l') expected = normalize(SHOW_LONG) assert out == expected def test_base_set(base_app): out = run_cmd(base_app, 'set quiet True') expected = normalize(""" quiet - was: False now: True """) assert out == expected out = run_cmd(base_app, 'show quiet') assert out == ['quiet: True'] def test_set_not_supported(base_app, capsys): run_cmd(base_app, 'set qqq True') out, err = capsys.readouterr() expected = normalize(""" EXCEPTION of type 'LookupError' occurred with message: 'Parameter 'qqq' not supported (type 'show' for list of parameters).' To enable full traceback, run the following command: 'set debug true' """) assert normalize(str(err)) == expected def test_set_quiet(base_app): out = run_cmd(base_app, 'set quie True') expected = normalize(""" quiet - was: False now: True """) assert out == expected out = run_cmd(base_app, 'show quiet') assert out == ['quiet: True'] def test_base_shell(base_app, monkeypatch): m = mock.Mock() subprocess = 'subprocess' if six.PY2: subprocess = 'subprocess32' monkeypatch.setattr("{}.Popen".format(subprocess), m) out = run_cmd(base_app, 'shell echo a') assert out == [] assert m.called def test_base_py(base_app, capsys): run_cmd(base_app, 'py qqq=3') out, err = capsys.readouterr() assert out == '' run_cmd(base_app, 'py print(qqq)') out, err = capsys.readouterr() assert out.rstrip() == '3' @pytest.mark.skipif(sys.platform == 'win32', reason="Unit test doesn't work on win32, but feature does") def test_base_run_python_script(base_app, capsys, request): test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'script.py') expected = 'This is a python script running ...\n' run_cmd(base_app, "py run('{}')".format(python_script)) out, err = capsys.readouterr() assert out == expected def test_base_run_pyscript(base_app, capsys, request): test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'script.py') expected = 'This is a python script running ...\n' run_cmd(base_app, "pyscript {}".format(python_script)) out, err = capsys.readouterr() assert out == expected def test_recursive_pyscript_not_allowed(base_app, capsys, request): test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'scripts', 'recursive.py') expected = 'ERROR: Recursively entering interactive Python consoles is not allowed.\n' run_cmd(base_app, "pyscript {}".format(python_script)) out, err = capsys.readouterr() assert err == expected def test_pyscript_with_nonexist_file(base_app, capsys): python_script = 'does_not_exist.py' run_cmd(base_app, "pyscript {}".format(python_script)) out, err = capsys.readouterr() assert err.startswith('ERROR: [Errno 2] No such file or directory:') def test_pyscript_with_exception(base_app, capsys, request): test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, 'scripts', 'raises_exception.py') run_cmd(base_app, "pyscript {}".format(python_script)) out, err = capsys.readouterr() assert err.startswith('Traceback') assert err.endswith("TypeError: unsupported operand type(s) for +: 'int' and 'str'\n") def test_pyscript_requires_an_argument(base_app, capsys): run_cmd(base_app, "pyscript") out, err = capsys.readouterr() assert err.startswith('ERROR: pyscript command requires at least 1 argument ...') def test_base_error(base_app): out = run_cmd(base_app, 'meow') assert out == ["*** Unknown syntax: meow"] def test_base_history(base_app): run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') out = run_cmd(base_app, 'history') expected = normalize(""" -------------------------[1] help -------------------------[2] shortcuts """) assert out == expected out = run_cmd(base_app, 'history he') expected = normalize(""" -------------------------[1] help """) assert out == expected out = run_cmd(base_app, 'history sh') expected = normalize(""" -------------------------[2] shortcuts """) assert out == expected def test_history_script_format(base_app): run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') out = run_cmd(base_app, 'history -s') expected = normalize(""" help shortcuts """) assert out == expected def test_history_with_string_argument(base_app): run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') run_cmd(base_app, 'help history') out = run_cmd(base_app, 'history help') expected = normalize(""" -------------------------[1] help -------------------------[3] help history """) assert out == expected def test_history_with_integer_argument(base_app): run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') out = run_cmd(base_app, 'history 1') expected = normalize(""" -------------------------[1] help """) assert out == expected def test_history_with_integer_span(base_app): run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') run_cmd(base_app, 'help history') out = run_cmd(base_app, 'history 1..2') expected = normalize(""" -------------------------[1] help -------------------------[2] shortcuts """) assert out == expected def test_history_with_span_start(base_app): run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') run_cmd(base_app, 'help history') out = run_cmd(base_app, 'history 2:') expected = normalize(""" -------------------------[2] shortcuts -------------------------[3] help history """) assert out == expected def test_history_with_span_end(base_app): run_cmd(base_app, 'help') run_cmd(base_app, 'shortcuts') run_cmd(base_app, 'help history') out = run_cmd(base_app, 'history :2') expected = normalize(""" -------------------------[1] help -------------------------[2] shortcuts """) assert out == expected def test_history_with_span_index_error(base_app): run_cmd(base_app, 'help') run_cmd(base_app, 'help history') run_cmd(base_app, '!ls -hal :') out = run_cmd(base_app, 'history "hal :"') expected = normalize(""" -------------------------[3] !ls -hal : """) assert out == expected def test_base_cmdenvironment(base_app): out = run_cmd(base_app, 'cmdenvironment') expected = normalize(""" Commands are case-sensitive: {} Commands may be terminated with: {} Arguments at invocation allowed: {} Output redirection and pipes allowed: {} Parsing of @options commands: Shell lexer mode for command argument splitting: {} Strip Quotes after splitting arguments: {} Argument type: {} """.format(not base_app.case_insensitive, base_app.terminators, base_app.allow_cli_args, base_app.allow_redirection, "POSIX" if cmd2.POSIX_SHLEX else "non-POSIX", "True" if cmd2.STRIP_QUOTES_FOR_NON_POSIX and not cmd2.POSIX_SHLEX else "False", "List of argument strings" if cmd2.USE_ARG_LIST else "string of space-separated arguments")) assert out == expected def test_base_load(base_app, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'script.txt') assert base_app.cmdqueue == [] assert base_app._script_dir == [] assert base_app._current_script_dir is None # Run the load command, which populates the command queue and sets the script directory run_cmd(base_app, 'load {}'.format(filename)) assert base_app.cmdqueue == ['help history', 'eos'] sdir = os.path.dirname(filename) assert base_app._script_dir == [sdir] assert base_app._current_script_dir == sdir def test_load_with_empty_args(base_app, capsys): # The way the load command works, we can't directly capture its stdout or stderr run_cmd(base_app, 'load') out, err = capsys.readouterr() # The load command requires a file path argument, so we should get an error message expected = normalize("""ERROR: load command requires a file path:\n""") assert normalize(str(err)) == expected assert base_app.cmdqueue == [] def test_load_with_nonexistent_file(base_app, capsys): # The way the load command works, we can't directly capture its stdout or stderr run_cmd(base_app, 'load does_not_exist.txt') out, err = capsys.readouterr() # The load command requires a path to an existing file assert str(err).startswith("ERROR") assert "does not exist or is not a file" in str(err) assert base_app.cmdqueue == [] def test_load_with_empty_file(base_app, capsys, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'scripts', 'empty.txt') # The way the load command works, we can't directly capture its stdout or stderr run_cmd(base_app, 'load {}'.format(filename)) out, err = capsys.readouterr() # The load command requires non-empty scripts files assert str(err).startswith("ERROR") assert "is empty" in str(err) assert base_app.cmdqueue == [] def test_load_with_binary_file(base_app, capsys, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'scripts', 'binary.bin') # The way the load command works, we can't directly capture its stdout or stderr run_cmd(base_app, 'load {}'.format(filename)) out, err = capsys.readouterr() # The load command requires non-empty scripts files assert str(err).startswith("ERROR") assert "is not an ASCII or UTF-8 encoded text file" in str(err) assert base_app.cmdqueue == [] def test_load_with_utf8_file(base_app, capsys, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'scripts', 'utf8.txt') assert base_app.cmdqueue == [] assert base_app._script_dir == [] assert base_app._current_script_dir is None # Run the load command, which populates the command queue and sets the script directory run_cmd(base_app, 'load {}'.format(filename)) assert base_app.cmdqueue == ['!echo γνωρίζω', 'eos'] sdir = os.path.dirname(filename) assert base_app._script_dir == [sdir] assert base_app._current_script_dir == sdir def test_base_relative_load(base_app, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'script.txt') assert base_app.cmdqueue == [] assert base_app._script_dir == [] assert base_app._current_script_dir is None # Run the load command, which populates the command queue and sets the script directory run_cmd(base_app, '_relative_load {}'.format(filename)) assert base_app.cmdqueue == ['help history', 'eos'] sdir = os.path.dirname(filename) assert base_app._script_dir == [sdir] assert base_app._current_script_dir == sdir def test_relative_load_requires_an_argument(base_app, capsys): run_cmd(base_app, '_relative_load') out, err = capsys.readouterr() assert out == '' assert err.startswith('ERROR: _relative_load command requires a file path:\n') assert base_app.cmdqueue == [] def test_base_save(base_app): # TODO: Use a temporary directory for the file filename = 'deleteme.txt' base_app.feedback_to_output = True run_cmd(base_app, 'help') run_cmd(base_app, 'help save') # Test the * form of save which saves all commands from history out = run_cmd(base_app, 'save * {}'.format(filename)) assert out == normalize('Saved to {}\n'.format(filename)) expected = normalize(""" help help save save * deleteme.txt """) with open(filename) as f: content = normalize(f.read()) assert content == expected # Test the N form of save which saves a numbered command from history out = run_cmd(base_app, 'save 1 {}'.format(filename)) assert out == normalize('Saved to {}\n'.format(filename)) expected = normalize('help') with open(filename) as f: content = normalize(f.read()) assert content == expected # Test the blank form of save which saves the most recent command from history out = run_cmd(base_app, 'save {}'.format(filename)) assert out == normalize('Saved to {}\n'.format(filename)) expected = normalize('save 1 {}'.format(filename)) with open(filename) as f: content = normalize(f.read()) assert content == expected # Delete file that was created os.remove(filename) def test_save_parse_error(base_app, capsys): invalid_file = '~!@' run_cmd(base_app, 'save {}'.format(invalid_file)) out, err = capsys.readouterr() assert out == '' assert err.startswith('ERROR: Could not understand save target {}\n'.format(invalid_file)) def test_save_tempfile(base_app): # Just run help to make sure there is something in the history base_app.feedback_to_output = True run_cmd(base_app, 'help') out = run_cmd(base_app, 'save *') output = out[0] assert output.startswith('Saved to ') # Delete the tempfile which was created temp_file = output.split('Saved to ')[1].strip() os.remove(temp_file) def test_save_invalid_history_index(base_app, capsys): run_cmd(base_app, 'save 5') out, err = capsys.readouterr() assert out == '' assert err.startswith("EXCEPTION of type 'IndexError' occurred with message: 'list index out of range'\n") def test_save_empty_history_and_index(base_app, capsys): run_cmd(base_app, 'save') out, err = capsys.readouterr() assert out == '' assert err.startswith("ERROR: History is empty, nothing to save.\n") def test_save_invalid_path(base_app, capsys): # Just run help to make sure there is something in the history run_cmd(base_app, 'help') invalid_path = '/no_such_path/foobar.txt' run_cmd(base_app, 'save {}'.format(invalid_path)) out, err = capsys.readouterr() assert out == '' assert err.startswith("ERROR: Saving '{}' - ".format(invalid_path)) def test_output_redirection(base_app): fd, filename = tempfile.mkstemp(prefix='cmd2_test', suffix='.txt') os.close(fd) try: # Verify that writing to a file works run_cmd(base_app, 'help > {}'.format(filename)) expected = normalize(BASE_HELP) with open(filename) as f: content = normalize(f.read()) assert content == expected # Verify that appending to a file also works run_cmd(base_app, 'help history >> {}'.format(filename)) expected = normalize(BASE_HELP + '\n' + HELP_HISTORY) with open(filename) as f: content = normalize(f.read()) assert content == expected except: raise finally: os.remove(filename) def test_feedback_to_output_true(base_app): base_app.feedback_to_output = True base_app.timing = True f, filename = tempfile.mkstemp(prefix='cmd2_test', suffix='.txt') os.close(f) try: run_cmd(base_app, 'help > {}'.format(filename)) with open(filename) as f: content = f.readlines() assert content[-1].startswith('Elapsed: ') except: raise finally: os.remove(filename) def test_feedback_to_output_false(base_app, capsys): base_app.feedback_to_output = False base_app.timing = True f, filename = tempfile.mkstemp(prefix='feedback_to_output', suffix='.txt') os.close(f) try: run_cmd(base_app, 'help > {}'.format(filename)) out, err = capsys.readouterr() with open(filename) as f: content = f.readlines() assert not content[-1].startswith('Elapsed: ') assert err.startswith('Elapsed') except: raise finally: os.remove(filename) def test_allow_redirection(base_app): # Set allow_redirection to False base_app.allow_redirection = False filename = 'test_allow_redirect.txt' # Verify output wasn't redirected out = run_cmd(base_app, 'help > {}'.format(filename)) expected = normalize(BASE_HELP) assert out == expected # Verify that no file got created assert not os.path.exists(filename) def test_input_redirection(base_app, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'redirect.txt') # NOTE: File 'redirect.txt" contains 1 word "history" # Verify that redirecting input ffom a file works out = run_cmd(base_app, 'help < {}'.format(filename)) expected = normalize(HELP_HISTORY) assert out == expected def test_pipe_to_shell(base_app, capsys): if sys.platform == "win32": # Windows command = 'help | sort' # Get help menu and pipe it's output to the sort shell command # expected = ['', '', '_relative_load edit history py quit save shell show', # '========================================', # 'cmdenvironment help load pyscript run set shortcuts', # 'Documented commands (type help ):'] # assert out == expected else: # Mac and Linux # Get help on help and pipe it's output to the input of the word count shell command command = 'help help | wc' # # Mac and Linux wc behave the same when piped from shell, but differently when piped stdin from file directly # if sys.platform == 'darwin': # expected = "1 11 70" # else: # expected = "1 11 70" # assert out.strip() == expected.strip() run_cmd(base_app, command) out, err = capsys.readouterr() # Unfortunately with the improved way of piping output to a subprocess, there isn't any good way of getting # access to the output produced by that subprocess within a unit test, but we can verify that no error occured assert not err def test_pipe_to_shell_error(base_app, capsys): # Try to pipe command output to a shell command that doesn't exist in order to produce an error run_cmd(base_app, 'help | foobarbaz.this_does_not_exist') out, err = capsys.readouterr() assert not out expected_error = 'FileNotFoundError' if six.PY2: if sys.platform.startswith('win'): expected_error = 'WindowsError' else: expected_error = 'OSError' assert err.startswith("EXCEPTION of type '{}' occurred with message:".format(expected_error)) @pytest.mark.skipif(not cmd2.can_clip, reason="Pyperclip could not find a copy/paste mechanism for your system") def test_send_to_paste_buffer(base_app): # Test writing to the PasteBuffer/Clipboard run_cmd(base_app, 'help >') expected = normalize(BASE_HELP) assert normalize(cmd2.get_paste_buffer()) == expected # Test appending to the PasteBuffer/Clipboard run_cmd(base_app, 'help history >>') expected = normalize(BASE_HELP + '\n' + HELP_HISTORY) assert normalize(cmd2.get_paste_buffer()) == expected def test_base_timing(base_app, capsys): base_app.feedback_to_output = False out = run_cmd(base_app, 'set timing True') expected = normalize("""timing - was: False now: True """) assert out == expected out, err = capsys.readouterr() if sys.platform == 'win32': assert err.startswith('Elapsed: 0:00:00') else: assert err.startswith('Elapsed: 0:00:00.0') def test_base_debug(base_app, capsys): # Try to set a non-existent parameter with debug set to False by default run_cmd(base_app, 'set does_not_exist 5') out, err = capsys.readouterr() assert err.startswith('EXCEPTION') # Set debug true out = run_cmd(base_app, 'set debug True') expected = normalize(""" debug - was: False now: True """) assert out == expected # Verify that we now see the exception traceback run_cmd(base_app, 'set does_not_exist 5') out, err = capsys.readouterr() assert str(err).startswith('Traceback (most recent call last):') def test_base_colorize(base_app): # If using base_app test fixture it won't get colorized because we replaced self.stdout color_test = base_app.colorize('Test', 'red') assert color_test == 'Test' # But if we create a fresh Cmd() instance, it will fresh_app = cmd2.Cmd() color_test = fresh_app.colorize('Test', 'red') # Actually, colorization only ANSI escape codes is only applied on non-Windows systems if sys.platform == 'win32': assert color_test == 'Test' else: assert color_test == '\x1b[31mTest\x1b[39m' def _expected_no_editor_error(): expected_exception = 'OSError' # If using Python 2 or PyPy (either 2 or 3), expect a different exception than with Python 3 if six.PY2 or hasattr(sys, "pypy_translation_info"): expected_exception = 'EnvironmentError' expected_text = normalize(""" EXCEPTION of type '{}' occurred with message: 'Please use 'set editor' to specify your text editing program of choice.' To enable full traceback, run the following command: 'set debug true' """.format(expected_exception)) return expected_text def test_edit_no_editor(base_app, capsys): # Purposely set the editor to None base_app.editor = None # Make sure we get an exception, but cmd2 handles it run_cmd(base_app, 'edit') out, err = capsys.readouterr() expected = _expected_no_editor_error() assert normalize(str(err)) == expected def test_edit_file(base_app, request, monkeypatch): # Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock base_app.editor = 'fooedit' # Mock out the os.system call so we don't actually open an editor m = mock.MagicMock(name='system') monkeypatch.setattr("os.system", m) test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'script.txt') run_cmd(base_app, 'edit {}'.format(filename)) # We think we have an editor, so should expect a system call m.assert_called_once_with('"{}" "{}"'.format(base_app.editor, filename)) def test_edit_file_with_spaces(base_app, request, monkeypatch): # Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock base_app.editor = 'fooedit' # Mock out the os.system call so we don't actually open an editor m = mock.MagicMock(name='system') monkeypatch.setattr("os.system", m) test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'my commands.txt') run_cmd(base_app, 'edit "{}"'.format(filename)) # We think we have an editor, so should expect a system call m.assert_called_once_with('"{}" "{}"'.format(base_app.editor, filename)) def test_edit_number(base_app, monkeypatch): # Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock base_app.editor = 'fooedit' # Mock out the os.system call so we don't actually open an editor m = mock.MagicMock(name='system') monkeypatch.setattr("os.system", m) # Run help command just so we have a command in history run_cmd(base_app, 'help') run_cmd(base_app, 'edit 1') # We have an editor, so should expect a system call m.assert_called_once() def test_edit_blank(base_app, monkeypatch): # Set a fake editor just to make sure we have one. We aren't really going to call it due to the mock base_app.editor = 'fooedit' # Mock out the os.system call so we don't actually open an editor m = mock.MagicMock(name='system') monkeypatch.setattr("os.system", m) # Run help command just so we have a command in history run_cmd(base_app, 'help') run_cmd(base_app, 'edit') # We have an editor, so should expect a system call m.assert_called_once() def test_edit_empty_history(base_app, capsys): run_cmd(base_app, 'edit') out, err = capsys.readouterr() assert out == '' assert err == 'ERROR: edit must be called with argument if history is empty\n' def test_base_py_interactive(base_app): # Mock out the InteractiveConsole.interact() call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='interact') InteractiveConsole.interact = m run_cmd(base_app, "py") # Make sure our mock was called once and only once m.assert_called_once() def test_base_cmdloop_with_queue(): # Create a cmd2.Cmd() instance and make sure basic settings are like we want for test app = cmd2.Cmd() app.use_rawinput = True intro = 'Hello World, this is an intro ...' app.cmdqueue.append('quit\n') app.stdout = StdOut() # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog"] expected = intro + '\n' with mock.patch.object(sys, 'argv', testargs): # Run the command loop with custom intro app.cmdloop(intro=intro) out = app.stdout.buffer assert out == expected def test_base_cmdloop_without_queue(): # Create a cmd2.Cmd() instance and make sure basic settings are like we want for test app = cmd2.Cmd() app.use_rawinput = True app.intro = 'Hello World, this is an intro ...' app.stdout = 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') sm.input = m # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog"] expected = app.intro + '\n' with mock.patch.object(sys, 'argv', testargs): # Run the command loop app.cmdloop() out = app.stdout.buffer assert out == expected def test_cmdloop_without_rawinput(): # Create a cmd2.Cmd() instance and make sure basic settings are like we want for test app = cmd2.Cmd() app.use_rawinput = False app.intro = 'Hello World, this is an intro ...' app.stdout = 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') sm.input = m # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args testargs = ["prog"] expected = app.intro + '\n{}'.format(app.prompt) with mock.patch.object(sys, 'argv', testargs): # Run the command loop app.cmdloop() out = app.stdout.buffer assert out == expected class HookFailureApp(cmd2.Cmd): def __init__(self, *args, **kwargs): # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x cmd2.Cmd.__init__(self, *args, **kwargs) def postparsing_precmd(self, statement): """Simulate precmd hook failure.""" return True, statement @pytest.fixture def hook_failure(): app = HookFailureApp() app.stdout = StdOut() return app def test_precmd_hook_success(base_app): out = base_app.onecmd_plus_hooks('help') assert out is None def test_precmd_hook_failure(hook_failure): out = hook_failure.onecmd_plus_hooks('help') assert out == True class ShellApp(cmd2.Cmd): def __init__(self, *args, **kwargs): # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x cmd2.Cmd.__init__(self, *args, **kwargs) self.default_to_shell = True @pytest.fixture def shell_app(): app = ShellApp() app.stdout = StdOut() return app def test_default_to_shell_unknown(shell_app): unknown_command = 'zyxcw23' out = run_cmd(shell_app, unknown_command) assert out == ["*** Unknown syntax: {}".format(unknown_command)] def test_default_to_shell_good(capsys): app = cmd2.Cmd() app.default_to_shell = True if sys.platform.startswith('win'): line = 'dir' else: line = 'ls' statement = app.parser_manager.parsed(line) retval = app.default(statement) assert not retval out, err = capsys.readouterr() assert out == '' def test_default_to_shell_failure(capsys): app = cmd2.Cmd() app.default_to_shell = True line = 'ls does_not_exist.xyz' statement = app.parser_manager.parsed(line) retval = app.default(statement) assert not retval out, err = capsys.readouterr() assert out == "*** Unknown syntax: {}\n".format(line) def test_ansi_prompt_not_esacped(base_app): prompt = '(Cmd) ' assert base_app._surround_ansi_escapes(prompt) == prompt def test_ansi_prompt_escaped(): app = cmd2.Cmd() color = 'cyan' prompt = 'InColor' color_prompt = app.colorize(prompt, color) readline_hack_start = "\x01" readline_hack_end = "\x02" readline_safe_prompt = app._surround_ansi_escapes(color_prompt) if sys.platform.startswith('win'): # colorize() does nothing on Windows due to lack of ANSI color support assert prompt == color_prompt assert readline_safe_prompt == prompt else: assert prompt != color_prompt assert readline_safe_prompt.startswith(readline_hack_start + app._colorcodes[color][True] + readline_hack_end) assert readline_safe_prompt.endswith(readline_hack_start + app._colorcodes[color][False] + readline_hack_end) class HelpApp(cmd2.Cmd): """Class for testing custom help_* methods which override docstring help.""" def __init__(self, *args, **kwargs): # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x cmd2.Cmd.__init__(self, *args, **kwargs) def do_squat(self, arg): """This docstring help will never be shown because the help_squat method overrides it.""" pass def help_squat(self): self.stdout.write('This command does diddly squat...\n') def do_edit(self, arg): """This overrides the edit command and does nothing.""" pass # This command will be in the "undocumented" section of the help menu def do_undoc(self, arg): pass @pytest.fixture def help_app(): app = HelpApp() app.stdout = StdOut() return app def test_custom_command_help(help_app): out = run_cmd(help_app, 'help squat') expected = normalize('This command does diddly squat...') assert out == expected def test_custom_help_menu(help_app): out = run_cmd(help_app, 'help') expected = normalize(""" Documented commands (type help ): ======================================== _relative_load edit history py quit save shell show cmdenvironment help load pyscript run set shortcuts squat Undocumented commands: ====================== undoc """) assert out == expected def test_help_undocumented(help_app): out = run_cmd(help_app, 'help undoc') expected = normalize('*** No help on undoc') assert out == expected def test_help_overridden_method(help_app): out = run_cmd(help_app, 'help edit') expected = normalize('This overrides the edit command and does nothing.') assert out == expected class SelectApp(cmd2.Cmd): def do_eat(self, arg): """Eat something, with a selection of sauces to choose from.""" # Pass in a single string of space-separated selections sauce = self.select('sweet salty', 'Sauce? ') result = '{food} with {sauce} sauce, yum!' result = result.format(food=arg, sauce=sauce) self.stdout.write(result + '\n') def do_study(self, arg): """Learn something, with a selection of subjects to choose from.""" # Pass in a list of strings for selections subject = self.select(['math', 'science'], 'Subject? ') result = 'Good luck learning {}!\n'.format(subject) self.stdout.write(result) def do_procrastinate(self, arg): """Waste time in your manner of choice.""" # Pass in a list of tuples for selections leisure_activity = self.select([('Netflix and chill', 'Netflix'), ('Porn', 'WebSurfing')], 'How would you like to procrastinate? ') result = 'Have fun procrasinating with {}!\n'.format(leisure_activity) self.stdout.write(result) def do_play(self, arg): """Play your favorite musical instrument.""" # Pass in an uneven list of tuples for selections instrument = self.select([('Guitar', 'Electric Guitar'), ('Drums',)], 'Instrument? ') result = 'Charm us with the {}...\n'.format(instrument) self.stdout.write(result) @pytest.fixture def select_app(): app = SelectApp() app.stdout = StdOut() return app def test_select_options(select_app): # 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='2') sm.input = m food = 'bacon' out = run_cmd(select_app, "eat {}".format(food)) expected = normalize(""" 1. sweet 2. salty {} with salty sauce, yum! """.format(food)) # Make sure our mock was called with the expected arguments m.assert_called_once_with('Sauce? ') # And verify the expected output to stdout assert out == expected def test_select_invalid_option(select_app): # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.MagicMock(name='input') # If side_effect is an iterable then each call to the mock will return the next value from the iterable. m.side_effect = ['3', '1'] # First pass and invalid selection, then pass a valid one sm.input = m food = 'fish' out = run_cmd(select_app, "eat {}".format(food)) expected = normalize(""" 1. sweet 2. salty 3 isn't a valid choice. Pick a number between 1 and 2: {} with sweet sauce, yum! """.format(food)) # Make sure our mock was called exactly twice with the expected arguments arg = 'Sauce? ' calls = [mock.call(arg), mock.call(arg)] m.assert_has_calls(calls) # And verify the expected output to stdout assert out == expected def test_select_list_of_strings(select_app): # 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='2') sm.input = m out = run_cmd(select_app, "study") expected = normalize(""" 1. math 2. science Good luck learning {}! """.format('science')) # Make sure our mock was called with the expected arguments m.assert_called_once_with('Subject? ') # And verify the expected output to stdout assert out == expected def test_select_list_of_tuples(select_app): # 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='2') sm.input = m out = run_cmd(select_app, "procrastinate") expected = normalize(""" 1. Netflix 2. WebSurfing Have fun procrasinating with {}! """.format('Porn')) # Make sure our mock was called with the expected arguments m.assert_called_once_with('How would you like to procrastinate? ') # And verify the expected output to stdout assert out == expected def test_select_uneven_list_of_tuples(select_app): # 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='2') sm.input = m out = run_cmd(select_app, "play") expected = normalize(""" 1. Electric Guitar 2. Drums Charm us with the {}... """.format('Drums')) # Make sure our mock was called with the expected arguments m.assert_called_once_with('Instrument? ') # And verify the expected output to stdout assert out == expected @pytest.fixture def noarglist_app(): cmd2.set_use_arg_list(False) app = cmd2.Cmd() app.stdout = StdOut() return app def test_pyscript_with_noarglist(noarglist_app, capsys, request): test_dir = os.path.dirname(request.module.__file__) python_script = os.path.join(test_dir, '..', 'examples', 'scripts', 'arg_printer.py') expected = """Running Python script 'arg_printer.py' which was called with 2 arguments arg 1: 'foo' arg 2: 'bar' """ run_cmd(noarglist_app, 'pyscript {} foo bar'.format(python_script)) out, err = capsys.readouterr() assert out == expected class OptionApp(cmd2.Cmd): @cmd2.options([cmd2.make_option('-s', '--shout', action="store_true", help="N00B EMULATION MODE")]) def do_greet(self, arg, opts=None): arg = ''.join(arg) if opts.shout: arg = arg.upper() self.stdout.write(arg + '\n') def test_option_help_with_no_docstring(capsys): app = OptionApp() app.onecmd_plus_hooks('greet -h') out, err = capsys.readouterr() assert err == '' assert out == """Usage: greet [options] arg Options: -h, --help show this help message and exit -s, --shout N00B EMULATION MODE """ @pytest.mark.skipif(sys.platform.startswith('win'), reason="cmd2._which function only used on Mac and Linux") def test_which_editor_good(): editor = 'vi' path = cmd2._which(editor) # Assert that the vi editor was found because it should exist on all Mac and Linux systems assert path @pytest.mark.skipif(sys.platform.startswith('win'), reason="cmd2._which function only used on Mac and Linux") def test_which_editor_bad(): editor = 'notepad.exe' path = cmd2._which(editor) # Assert that the editor wasn't found because no notepad.exe on non-Windows systems ;-) assert path is None class MultilineApp(cmd2.Cmd): def __init__(self, *args, **kwargs): self.multilineCommands = ['orate'] # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x cmd2.Cmd.__init__(self, *args, **kwargs) @cmd2.options([cmd2.make_option('-s', '--shout', action="store_true", help="N00B EMULATION MODE")]) def do_orate(self, arg, opts=None): arg = ''.join(arg) if opts.shout: arg = arg.upper() self.stdout.write(arg + '\n') @pytest.fixture def multiline_app(): app = MultilineApp() app.stdout = StdOut() return app def test_multiline_complete_empty_statement_raises_exception(multiline_app): with pytest.raises(cmd2.EmptyStatement): multiline_app._complete_statement('') def test_multiline_complete_statement_without_terminator(multiline_app): # Mock out the input call so we don't actually wait for a user's response on stdin when it looks for more input m = mock.MagicMock(name='input', return_value='\n') sm.input = m command = 'orate' args = 'hello world' line = '{} {}'.format(command, args) statement = multiline_app._complete_statement(line) assert statement == args assert statement.parsed.command == command def test_clipboard_failure(capsys): # Force cmd2 clipboard to be disabled cmd2.can_clip = False app = cmd2.Cmd() # Redirect command output to the clipboard when a clipboard isn't present app.onecmd_plus_hooks('help > ') # Make sure we got the error output out, err = capsys.readouterr() assert out == '' assert 'Cannot redirect to paste buffer; install ``xclip`` and re-run to enable' in err def test_run_command_with_empty_arg(base_app): command = 'help' base_app.feedback_to_output = True run_cmd(base_app, command) out = run_cmd(base_app, 'run') expected = normalize('{}\n\n'.format(command) + BASE_HELP) assert out == expected def test_run_command_with_empty_history(base_app): base_app.feedback_to_output = True out = run_cmd(base_app, 'run') assert out == [] class CmdResultApp(cmd2.Cmd): def __init__(self, *args, **kwargs): # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x cmd2.Cmd.__init__(self, *args, **kwargs) def do_affirmative(self, arg): self._last_result = cmd2.CmdResult(arg) def do_negative(self, arg): self._last_result = cmd2.CmdResult('', arg) @pytest.fixture def cmdresult_app(): app = CmdResultApp() app.stdout = StdOut() return app def test_cmdresult(cmdresult_app): arg = 'foo' run_cmd(cmdresult_app, 'affirmative {}'.format(arg)) assert cmdresult_app._last_result assert cmdresult_app._last_result == cmd2.CmdResult(arg) arg = 'bar' run_cmd(cmdresult_app, 'negative {}'.format(arg)) assert not cmdresult_app._last_result assert cmdresult_app._last_result == cmd2.CmdResult('', arg) @pytest.fixture def abbrev_app(): app = cmd2.Cmd() app.abbrev = True app.stdout = StdOut() return app def test_exclude_from_history(abbrev_app, monkeypatch): # Run all variants of run run_cmd(abbrev_app, 'run') run_cmd(abbrev_app, 'ru') run_cmd(abbrev_app, 'r') # Mock out the os.system call so we don't actually open an editor m = mock.MagicMock(name='system') monkeypatch.setattr("os.system", m) # Run all variants of edit run_cmd(abbrev_app, 'edit') run_cmd(abbrev_app, 'edi') run_cmd(abbrev_app, 'ed') # Run all variants of history run_cmd(abbrev_app, 'history') run_cmd(abbrev_app, 'histor') run_cmd(abbrev_app, 'histo') run_cmd(abbrev_app, 'hist') run_cmd(abbrev_app, 'his') run_cmd(abbrev_app, 'hi') # Verify that the history is empty out = run_cmd(abbrev_app, 'history') assert out == [] # Now run a command which isn't excluded from the history run_cmd(abbrev_app, 'help') # And verify we have a history now ... out = run_cmd(abbrev_app, 'history') expected = normalize("""-------------------------[1] help""") assert out == expected def test_is_text_file_bad_input(base_app): # Test with a non-existent file file_is_valid = base_app.is_text_file('does_not_exist.txt') assert not file_is_valid # Test with a directory dir_is_valid = base_app.is_text_file('.') assert not dir_is_valid def test_eof(base_app): # Only thing to verify is that it returns True assert base_app.do_eof('dont care') def test_eos(base_app): sdir = 'dummy_dir' base_app._script_dir.append(sdir) assert len(base_app._script_dir) == 1 # Assert that it does NOT return true assert not base_app.do_eos('dont care') # And make sure it reduced the length of the script dir list assert len(base_app._script_dir) == 0 def test_echo(capsys): app = cmd2.Cmd() # Turn echo on and pre-stage some commands in the queue, simulating like we are in the middle of a script app.echo = True command = 'help history' app.cmdqueue = [command, 'quit', 'eos'] app._script_dir.append('some_dir') assert app._current_script_dir is not None # Run the inner _cmdloop app._cmdloop() out, err = capsys.readouterr() # Check the output assert app.cmdqueue == [] assert app._current_script_dir is None assert out.startswith('{}{}\n'.format(app.prompt, command) + 'history [arg]: lists past commands issued') def test_raw_input(base_app): base_app.use_raw_input = True fake_input = 'quit' # Mock out the input call so we don't actually wait for a user's response on stdin m = mock.Mock(name='input', return_value=fake_input) sm.input = m line = base_app.pseudo_raw_input('(cmd2)') assert line == fake_input def test_stdin_input(): app = cmd2.Cmd() app.use_rawinput = False fake_input = 'quit' # Mock out the readline call so we don't actually read from stdin m = mock.Mock(name='readline', return_value=fake_input) app.stdin.readline = m line = app.pseudo_raw_input('(cmd2)') assert line == fake_input def test_empty_stdin_input(): app = cmd2.Cmd() app.use_rawinput = False fake_input = '' # Mock out the readline call so we don't actually read from stdin m = mock.Mock(name='readline', return_value=fake_input) app.stdin.readline = m line = app.pseudo_raw_input('(cmd2)') assert line == 'eof'