summaryrefslogtreecommitdiff
path: root/tests/test_cmd2.py
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2018-09-30 11:17:04 -0400
committerTodd Leonhardt <todd.leonhardt@gmail.com>2018-09-30 11:17:04 -0400
commit81ad085cffc8f8af7fb7884bcc5db6fcede09df6 (patch)
treea197a6c81767f10b62d6ac6acfd46e42357a8289 /tests/test_cmd2.py
parent85a21ef39467dc24a6f6f8c4f3ac656f24d6ff90 (diff)
parent61d5703cd3586b3460669a6260cf903c9863b240 (diff)
downloadcmd2-git-81ad085cffc8f8af7fb7884bcc5db6fcede09df6.tar.gz
Merged master into transcript_fixes branch and resolved conflicts
Diffstat (limited to 'tests/test_cmd2.py')
-rw-r--r--tests/test_cmd2.py659
1 files changed, 540 insertions, 119 deletions
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index fdf0f661..3ce7a11d 100644
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -13,6 +13,7 @@ import os
import sys
import tempfile
+from colorama import Fore, Back, Style
import pytest
# Python 3.5 had some regressions in the unitest.mock module, so use 3rd party mock if available
@@ -25,7 +26,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):
@@ -46,6 +47,11 @@ def test_base_help_verbose(base_app):
expected = normalize(BASE_HELP_VERBOSE)
assert out == expected
+ # Make sure :param type lines are filtered out of help summary
+ help_doc = base_app.do_help.__func__.__doc__
+ help_doc += "\n:param fake param"
+ base_app.do_help.__func__.__doc__ = help_doc
+
out = run_cmd(base_app, 'help --verbose')
assert out == expected
@@ -64,7 +70,7 @@ def test_base_argparse_help(base_app, capsys):
assert out1 == out2
assert out1[0].startswith('Usage: set')
assert out1[1] == ''
- assert out1[2].startswith('Sets a settable parameter')
+ assert out1[2].startswith('Set a settable parameter')
def test_base_invalid_option(base_app, capsys):
run_cmd(base_app, 'set -z')
@@ -72,7 +78,7 @@ def test_base_invalid_option(base_app, capsys):
out = normalize(out)
err = normalize(err)
assert 'Error: unrecognized arguments: -z' in err[0]
- assert out[0] == 'Usage: set settable{0..2} [-h] [-a] [-l]'
+ assert out[0] == 'Usage: set [-h] [-a] [-l] [param] [value]'
def test_base_shortcuts(base_app):
out = run_cmd(base_app, 'shortcuts')
@@ -178,6 +184,30 @@ now: True
assert out == ['quiet: True']
+class OnChangeHookApp(cmd2.Cmd):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ def _onchange_quiet(self, old, new) -> None:
+ """Runs when quiet is changed via set command"""
+ self.poutput("You changed quiet")
+
+@pytest.fixture
+def onchange_app():
+ app = OnChangeHookApp()
+ app.stdout = utils.StdSim(app.stdout)
+ return app
+
+def test_set_onchange_hook(onchange_app):
+ out = run_cmd(onchange_app, 'set quiet True')
+ expected = normalize("""
+quiet - was: False
+now: True
+You changed quiet
+""")
+ assert out == expected
+
+
def test_base_shell(base_app, monkeypatch):
m = mock.Mock()
monkeypatch.setattr("{}.Popen".format('subprocess'), m)
@@ -215,13 +245,13 @@ def test_base_run_pyscript(base_app, capsys, request):
out, err = capsys.readouterr()
assert out == expected
-def test_recursive_pyscript_not_allowed(base_app, capsys, request):
+def test_recursive_pyscript_not_allowed(base_app, 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'
+ expected = 'Recursively entering interactive Python consoles is not allowed.'
run_cmd(base_app, "pyscript {}".format(python_script))
- out, err = capsys.readouterr()
+ err = base_app._last_result.stderr
assert err == expected
def test_pyscript_with_nonexist_file(base_app, capsys):
@@ -241,7 +271,7 @@ def test_pyscript_with_exception(base_app, capsys, request):
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 ...')
+ assert "the following arguments are required: script_path" in err
def test_base_error(base_app):
@@ -471,7 +501,7 @@ def test_load_with_empty_args(base_app, capsys):
out, err = capsys.readouterr()
# The load command requires a file path argument, so we should get an error message
- assert "load command requires a file path" in str(err)
+ assert "the following arguments are required" in str(err)
assert base_app.cmdqueue == []
@@ -559,11 +589,11 @@ def test_load_nested_loads(base_app, request):
expected = """
%s
_relative_load precmds.txt
-set colors on
+set colors Always
help
shortcuts
_relative_load postcmds.txt
-set colors off""" % initial_load
+set colors Never""" % initial_load
assert run_cmd(base_app, 'history -s') == normalize(expected)
@@ -581,11 +611,11 @@ def test_base_runcmds_plus_hooks(base_app, request):
'load ' + postfilepath])
expected = """
load %s
-set colors on
+set colors Always
help
shortcuts
load %s
-set colors off""" % (prefilepath, postfilepath)
+set colors Never""" % (prefilepath, postfilepath)
assert run_cmd(base_app, 'history -s') == normalize(expected)
@@ -608,8 +638,7 @@ def test_base_relative_load(base_app, request):
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 'Error: the following arguments' in err
assert base_app.cmdqueue == []
@@ -812,12 +841,7 @@ def test_base_colorize(base_app):
# 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'
-
+ assert color_test == '\x1b[31mTest\x1b[39m'
def _expected_no_editor_error():
expected_exception = 'OSError'
@@ -857,7 +881,8 @@ def test_edit_file(base_app, request, monkeypatch):
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))
+ m.assert_called_once_with('{} {}'.format(utils.quote_string_if_needed(base_app.editor),
+ utils.quote_string_if_needed(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
@@ -873,7 +898,8 @@ def test_edit_file_with_spaces(base_app, request, monkeypatch):
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))
+ m.assert_called_once_with('{} {}'.format(utils.quote_string_if_needed(base_app.editor),
+ utils.quote_string_if_needed(filename)))
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
@@ -930,7 +956,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 +964,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 +973,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 +985,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 +995,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,22 +1007,25 @@ 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
class HookFailureApp(cmd2.Cmd):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+ # register a postparsing hook method
+ self.register_postparsing_hook(self.postparsing_precmd)
- def postparsing_precmd(self, statement):
+ def postparsing_precmd(self, data: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData:
"""Simulate precmd hook failure."""
- return True, statement
+ data.stop = True
+ return data
@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 +1049,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 +1063,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 +1077,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 +1089,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):
@@ -1093,28 +1122,30 @@ def test_default_to_shell_failure(capsys):
def test_ansi_prompt_not_esacped(base_app):
+ from cmd2.rl_utils import rl_make_safe_prompt
prompt = '(Cmd) '
- assert base_app._surround_ansi_escapes(prompt) == prompt
+ assert rl_make_safe_prompt(prompt) == prompt
def test_ansi_prompt_escaped():
+ from cmd2.rl_utils import rl_make_safe_prompt
app = cmd2.Cmd()
- color = 'cyan'
+ color = Fore.CYAN
prompt = 'InColor'
- color_prompt = app.colorize(prompt, color)
+ color_prompt = color + prompt + Fore.RESET
readline_hack_start = "\x01"
readline_hack_end = "\x02"
- readline_safe_prompt = app._surround_ansi_escapes(color_prompt)
+ readline_safe_prompt = rl_make_safe_prompt(color_prompt)
+ assert prompt != 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
+ # PyReadline on Windows doesn't suffer from the GNU readline bug which requires the hack
+ assert readline_safe_prompt.startswith(color)
+ assert readline_safe_prompt.endswith(Fore.RESET)
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)
+ assert readline_safe_prompt.startswith(readline_hack_start + color + readline_hack_end)
+ assert readline_safe_prompt.endswith(readline_hack_start + Fore.RESET + readline_hack_end)
class HelpApp(cmd2.Cmd):
@@ -1140,7 +1171,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):
@@ -1153,8 +1184,8 @@ def test_custom_help_menu(help_app):
expected = normalize("""
Documented commands (type help <topic>):
========================================
-alias help load pyscript set shortcuts unalias
-edit history py quit shell squat
+alias help load py quit shell squat
+edit history macro pyscript set shortcuts
Undocumented commands:
======================
@@ -1203,7 +1234,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):
@@ -1220,7 +1251,7 @@ diddly
Other
=====
-alias help history load py pyscript quit set shell shortcuts unalias
+alias help history load macro py pyscript quit set shell shortcuts
Undocumented commands:
======================
@@ -1243,17 +1274,17 @@ diddly This command does diddly
Other
================================================================================
-alias Define or display aliases
-help List available commands with "help" or detailed help with "help cmd"
+alias Manage aliases
+help List available commands or provide detailed help for a specific command
history View, run, edit, save, or clear previously entered commands
-load Runs commands in script file that is encoded as either ASCII or UTF-8 text
-py Invoke python command, shell, or script
-pyscript Runs a python script file inside the console
-quit Exits this application
-set Sets a settable parameter or shows current settings of parameters
+load Run commands in script file that is encoded as either ASCII or UTF-8 text
+macro Manage macros
+py Invoke Python command or shell
+pyscript Run a Python script file inside the console
+quit Exit this application
+set Set a settable parameter or show current settings of parameters
shell Execute a command as if at the OS prompt
-shortcuts Lists shortcuts available
-unalias Unsets aliases
+shortcuts List available shortcuts
Undocumented commands:
======================
@@ -1296,7 +1327,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 +1492,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 +1553,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):
@@ -1550,7 +1581,7 @@ def test_is_text_file_bad_input(base_app):
def test_eof(base_app):
# Only thing to verify is that it returns True
- assert base_app.do_eof('dont care')
+ assert base_app.do_eof('')
def test_eos(base_app):
sdir = 'dummy_dir'
@@ -1558,7 +1589,7 @@ def test_eos(base_app):
assert len(base_app._script_dir) == 1
# Assert that it does NOT return true
- assert not base_app.do_eos('dont care')
+ assert not base_app.do_eos('')
# And make sure it reduced the length of the script dir list
assert len(base_app._script_dir) == 0
@@ -1628,7 +1659,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 +1689,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,100 +1746,358 @@ 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
+def test_poutput_color_always(base_app):
+ msg = 'Hello World'
+ color = Fore.CYAN
+ base_app.colors = 'Always'
+ base_app.poutput(msg, color=color)
+ out = base_app.stdout.getvalue()
+ expected = color + msg + '\n' + Fore.RESET
+ assert out == expected
-def test_alias(base_app, capsys):
+def test_poutput_color_never(base_app):
+ msg = 'Hello World'
+ color = Fore.CYAN
+ base_app.colors = 'Never'
+ base_app.poutput(msg, color=color)
+ out = base_app.stdout.getvalue()
+ expected = msg + '\n'
+ assert out == expected
+
+
+# These are invalid names for aliases and macros
+invalid_command_name = [
+ '""', # Blank name
+ '!no_shortcut',
+ '">"',
+ '"no>pe"',
+ '"no spaces"',
+ '"nopipe|"',
+ '"noterm;"',
+ 'noembedded"quotes',
+]
+
+def test_get_alias_names(base_app):
+ assert len(base_app.aliases) == 0
+ run_cmd(base_app, 'alias create fake pyscript')
+ run_cmd(base_app, 'alias create ls !ls -hal')
+ assert len(base_app.aliases) == 2
+ assert sorted(base_app.get_alias_names()) == ['fake', 'ls']
+
+def test_get_macro_names(base_app):
+ assert len(base_app.macros) == 0
+ run_cmd(base_app, 'macro create foo !echo foo')
+ run_cmd(base_app, 'macro create bar !echo bar')
+ assert len(base_app.macros) == 2
+ assert sorted(base_app.get_macro_names()) == ['bar', 'foo']
+
+def test_alias_no_subcommand(base_app, capsys):
+ out = run_cmd(base_app, 'alias')
+ assert "Usage: alias [-h]" in out[0]
+
+def test_alias_create(base_app, capsys):
# Create the alias
- out = run_cmd(base_app, 'alias fake pyscript')
+ out = run_cmd(base_app, 'alias create fake pyscript')
assert out == normalize("Alias 'fake' created")
# Use the alias
run_cmd(base_app, 'fake')
out, err = capsys.readouterr()
- assert "pyscript command requires at least 1 argument" in err
+ assert "the following arguments are required: script_path" in err
# See a list of aliases
- out = run_cmd(base_app, 'alias')
- assert out == normalize('alias fake pyscript')
+ out = run_cmd(base_app, 'alias list')
+ assert out == normalize('alias create fake pyscript')
+
+ # Look up the new alias
+ out = run_cmd(base_app, 'alias list fake')
+ assert out == normalize('alias create fake pyscript')
- # Lookup the new alias
- out = run_cmd(base_app, 'alias fake')
- assert out == normalize('alias fake pyscript')
+def test_alias_quoted_name(base_app, capsys):
+ """Demonstrate that names can be quoted in alias commands because they will be stripped"""
+ # Create the alias
+ out = run_cmd(base_app, 'alias create "fake" pyscript')
+
+ # The quotes on names are stripped
+ assert out == normalize("Alias 'fake' created")
+
+ # Look up the new alias and quote the name
+ out = run_cmd(base_app, 'alias list "fake"')
+ assert out == normalize('alias create fake pyscript')
+
+ # Delete the alias using quotes
+ out = run_cmd(base_app, 'alias delete "fake"')
+ assert out == normalize("Alias 'fake' deleted")
+
+def test_alias_create_with_quoted_value(base_app, capsys):
+ """Demonstrate that quotes in alias value will be preserved (except for redirectors)"""
-def test_alias_with_quotes(base_app, capsys):
# Create the alias
- out = run_cmd(base_app, 'alias fake help ">" "out file.txt"')
+ out = run_cmd(base_app, 'alias create fake help ">" "out file.txt"')
assert out == normalize("Alias 'fake' created")
- # Lookup the new alias (Only the redirector should be unquoted)
- out = run_cmd(base_app, 'alias fake')
- assert out == normalize('alias fake help > "out file.txt"')
+ # Look up the new alias (Only the redirector should be unquoted)
+ out = run_cmd(base_app, 'alias list fake')
+ assert out == normalize('alias create fake help > "out file.txt"')
+
+@pytest.mark.parametrize('alias_name', invalid_command_name)
+def test_alias_create_invalid_name(base_app, alias_name, capsys):
+ run_cmd(base_app, 'alias create {} help'.format(alias_name))
+ out, err = capsys.readouterr()
+ assert "Invalid alias name" in err
+
+def test_alias_create_with_macro_name(base_app, capsys):
+ macro = "my_macro"
+ run_cmd(base_app, 'macro create {} help'.format(macro))
+ run_cmd(base_app, 'alias create {} help'.format(macro))
+ out, err = capsys.readouterr()
+ assert "Alias cannot have the same name as a macro" in err
-def test_alias_lookup_invalid_alias(base_app, capsys):
- # Lookup invalid alias
- out = run_cmd(base_app, 'alias invalid')
+def test_alias_list_invalid_alias(base_app, capsys):
+ # Look up invalid alias
+ out = run_cmd(base_app, 'alias list invalid')
out, err = capsys.readouterr()
- assert "not found" in err
+ assert "Alias 'invalid' not found" in err
-def test_unalias(base_app):
+def test_alias_delete(base_app):
# Create an alias
- run_cmd(base_app, 'alias fake pyscript')
+ run_cmd(base_app, 'alias create fake pyscript')
- # Remove the alias
- out = run_cmd(base_app, 'unalias fake')
- assert out == normalize("Alias 'fake' cleared")
+ # Delete the alias
+ out = run_cmd(base_app, 'alias delete fake')
+ assert out == normalize("Alias 'fake' deleted")
-def test_unalias_all(base_app):
- out = run_cmd(base_app, 'unalias -a')
- assert out == normalize("All aliases cleared")
+def test_alias_delete_all(base_app):
+ out = run_cmd(base_app, 'alias delete --all')
+ assert out == normalize("All aliases deleted")
-def test_unalias_non_existing(base_app, capsys):
- run_cmd(base_app, 'unalias fake')
+def test_alias_delete_non_existing(base_app, capsys):
+ run_cmd(base_app, 'alias delete fake')
out, err = capsys.readouterr()
- assert "does not exist" in err
+ assert "Alias 'fake' does not exist" in err
+
+def test_alias_delete_no_name(base_app, capsys):
+ out = run_cmd(base_app, 'alias delete')
+ assert "Usage: alias delete" in out[0]
+
+def test_multiple_aliases(base_app):
+ alias1 = 'h1'
+ alias2 = 'h2'
+ run_cmd(base_app, 'alias create {} help'.format(alias1))
+ run_cmd(base_app, 'alias create {} help -v'.format(alias2))
+ out = run_cmd(base_app, alias1)
+ expected = normalize(BASE_HELP)
+ assert out == expected
-@pytest.mark.parametrize('alias_name', [
- '">"',
- '"no>pe"',
- '"no spaces"',
- '"nopipe|"',
- '"noterm;"',
- 'noembedded"quotes',
-])
-def test_create_invalid_alias(base_app, alias_name, capsys):
- run_cmd(base_app, 'alias {} help'.format(alias_name))
+ out = run_cmd(base_app, alias2)
+ expected = normalize(BASE_HELP_VERBOSE)
+ assert out == expected
+
+def test_macro_no_subcommand(base_app, capsys):
+ out = run_cmd(base_app, 'macro')
+ assert "Usage: macro [-h]" in out[0]
+
+def test_macro_create(base_app, capsys):
+ # Create the macro
+ out = run_cmd(base_app, 'macro create fake pyscript')
+ assert out == normalize("Macro 'fake' created")
+
+ # Use the macro
+ run_cmd(base_app, 'fake')
+ out, err = capsys.readouterr()
+ assert "the following arguments are required: script_path" in err
+
+ # See a list of macros
+ out = run_cmd(base_app, 'macro list')
+ assert out == normalize('macro create fake pyscript')
+
+ # Look up the new macro
+ out = run_cmd(base_app, 'macro list fake')
+ assert out == normalize('macro create fake pyscript')
+
+def test_macro_create_quoted_name(base_app, capsys):
+ """Demonstrate that names can be quoted in macro commands because they will be stripped"""
+ # Create the macro
+ out = run_cmd(base_app, 'macro create "fake" pyscript')
+
+ # The quotes on names are stripped
+ assert out == normalize("Macro 'fake' created")
+
+ # Look up the new macro and quote the name
+ out = run_cmd(base_app, 'macro list "fake"')
+ assert out == normalize('macro create fake pyscript')
+
+ # Delete the macro using quotes
+ out = run_cmd(base_app, 'macro delete "fake"')
+ assert out == normalize("Macro 'fake' deleted")
+
+def test_macro_create_with_quoted_value(base_app, capsys):
+ """Demonstrate that quotes in macro value will be preserved (except for redirectors)"""
+ # Create the macro
+ out = run_cmd(base_app, 'macro create fake help ">" "out file.txt"')
+ assert out == normalize("Macro 'fake' created")
+
+ # Look up the new macro (Only the redirector should be unquoted)
+ out = run_cmd(base_app, 'macro list fake')
+ assert out == normalize('macro create fake help > "out file.txt"')
+
+@pytest.mark.parametrize('macro_name', invalid_command_name)
+def test_macro_create_invalid_name(base_app, macro_name, capsys):
+ run_cmd(base_app, 'macro create {} help'.format(macro_name))
+ out, err = capsys.readouterr()
+ assert "Invalid macro name" in err
+
+def test_macro_create_with_alias_name(base_app, capsys):
+ macro = "my_macro"
+ run_cmd(base_app, 'alias create {} help'.format(macro))
+ run_cmd(base_app, 'macro create {} help'.format(macro))
+ out, err = capsys.readouterr()
+ assert "Macro cannot have the same name as an alias" in err
+
+def test_macro_create_with_command_name(base_app, capsys):
+ macro = "my_macro"
+ run_cmd(base_app, 'macro create help stuff')
+ out, err = capsys.readouterr()
+ assert "Macro cannot have the same name as a command" in err
+
+def test_macro_create_with_args(base_app, capsys):
+ # Create the macro
+ out = run_cmd(base_app, 'macro create fake {1} {2}')
+ assert out == normalize("Macro 'fake' created")
+
+ # Run the macro
+ out = run_cmd(base_app, 'fake help -v')
+ expected = normalize(BASE_HELP_VERBOSE)
+ assert out == expected
+
+def test_macro_create_with_escaped_args(base_app, capsys):
+ # Create the macro
+ out = run_cmd(base_app, 'macro create fake help {{1}}')
+ assert out == normalize("Macro 'fake' created")
+
+ # Run the macro
+ out = run_cmd(base_app, 'fake')
+ assert 'No help on {1}' in out[0]
+
+def test_macro_create_with_missing_arg_nums(base_app, capsys):
+ # Create the macro
+ run_cmd(base_app, 'macro create fake help {1} {3}')
out, err = capsys.readouterr()
- assert "can not contain" in err
+ assert "Not all numbers between 1 and 3" in err
+
+def test_macro_create_with_invalid_arg_num(base_app, capsys):
+ # Create the macro
+ run_cmd(base_app, 'macro create fake help {1} {-1} {0}')
+ out, err = capsys.readouterr()
+ assert "Argument numbers must be greater than 0" in err
+
+def test_macro_create_with_wrong_arg_count(base_app, capsys):
+ # Create the macro
+ out = run_cmd(base_app, 'macro create fake help {1} {2}')
+ assert out == normalize("Macro 'fake' created")
+
+ # Run the macro
+ run_cmd(base_app, 'fake arg1')
+ out, err = capsys.readouterr()
+ assert "expects 2 argument(s)" in err
+
+def test_macro_create_with_unicode_numbered_arg(base_app, capsys):
+ # Create the macro expecting 1 argument
+ out = run_cmd(base_app, 'macro create fake help {\N{ARABIC-INDIC DIGIT ONE}}')
+ assert out == normalize("Macro 'fake' created")
+
+ # Run the macro
+ out = run_cmd(base_app, 'fake')
+ out, err = capsys.readouterr()
+ assert "expects 1 argument(s)" in err
+
+def test_macro_create_with_missing_unicode_arg_nums(base_app, capsys):
+ run_cmd(base_app, 'macro create fake help {1} {\N{ARABIC-INDIC DIGIT THREE}}')
+ out, err = capsys.readouterr()
+ assert "Not all numbers between 1 and 3" in err
+
+def test_macro_list_invalid_macro(base_app, capsys):
+ # Look up invalid macro
+ run_cmd(base_app, 'macro list invalid')
+ out, err = capsys.readouterr()
+ assert "Macro 'invalid' not found" in err
+
+def test_macro_delete(base_app):
+ # Create an macro
+ run_cmd(base_app, 'macro create fake pyscript')
+
+ # Delete the macro
+ out = run_cmd(base_app, 'macro delete fake')
+ assert out == normalize("Macro 'fake' deleted")
+
+def test_macro_delete_all(base_app):
+ out = run_cmd(base_app, 'macro delete --all')
+ assert out == normalize("All macros deleted")
+
+def test_macro_delete_non_existing(base_app, capsys):
+ run_cmd(base_app, 'macro delete fake')
+ out, err = capsys.readouterr()
+ assert "Macro 'fake' does not exist" in err
+
+def test_macro_delete_no_name(base_app, capsys):
+ out = run_cmd(base_app, 'macro delete')
+ assert "Usage: macro delete" in out[0]
+
+def test_multiple_macros(base_app):
+ macro1 = 'h1'
+ macro2 = 'h2'
+ run_cmd(base_app, 'macro create {} help'.format(macro1))
+ run_cmd(base_app, 'macro create {} help -v'.format(macro2))
+ out = run_cmd(base_app, macro1)
+ expected = normalize(BASE_HELP)
+ assert out == expected
+
+ out = run_cmd(base_app, macro2)
+ expected = normalize(BASE_HELP_VERBOSE)
+ assert out == expected
+
+def test_nonexistent_macro(base_app, capsys):
+ from cmd2.parsing import StatementParser
+ exception = None
+
+ try:
+ base_app._run_macro(StatementParser().parse('fake'))
+ except KeyError as e:
+ exception = e
+
+ assert exception is not None
+
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
@@ -1841,14 +2130,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 == ''
@@ -1925,12 +2214,11 @@ def test_bad_history_file_path(capsys, request):
assert 'readline cannot read' in err
-
def test_get_all_commands(base_app):
# Verify that the base app has the expected commands
commands = base_app.get_all_commands()
- expected_commands = ['_relative_load', 'alias', 'edit', 'eof', 'eos', 'help', 'history', 'load', 'py', 'pyscript',
- 'quit', 'set', 'shell', 'shortcuts', 'unalias']
+ expected_commands = ['_relative_load', 'alias', 'edit', 'eof', 'eos', 'help', 'history', 'load', 'macro',
+ 'py', 'pyscript', 'quit', 'set', 'shell', 'shortcuts']
assert commands == expected_commands
def test_get_help_topics(base_app):
@@ -1978,7 +2266,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')
@@ -1990,14 +2278,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')
@@ -2010,5 +2298,138 @@ 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
+
+
+class ColorsApp(cmd2.Cmd):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ def do_echo(self, args):
+ self.poutput(args)
+ self.perror(args, False)
+
+ def do_echo_error(self, args):
+ color_on = Fore.RED + Back.BLACK
+ color_off = Style.RESET_ALL
+ self.poutput(color_on + args + color_off)
+ # perror uses colors by default
+ self.perror(args, False)
+
+def test_colors_default():
+ app = ColorsApp()
+ assert app.colors == cmd2.constants.COLORS_TERMINAL
+
+def test_colors_pouterr_always_tty(mocker, capsys):
+ app = ColorsApp()
+ app.colors = cmd2.constants.COLORS_ALWAYS
+ mocker.patch.object(app.stdout, 'isatty', return_value=True)
+ mocker.patch.object(sys.stderr, 'isatty', return_value=True)
+
+ app.onecmd_plus_hooks('echo_error oopsie')
+ out, err = capsys.readouterr()
+ # if colors are on, the output should have some escape sequences in it
+ assert len(out) > len('oopsie\n')
+ assert 'oopsie' in out
+ assert len(err) > len('Error: oopsie\n')
+ assert 'ERROR: oopsie' in err
+
+ # but this one shouldn't
+ app.onecmd_plus_hooks('echo oopsie')
+ out, err = capsys.readouterr()
+ assert out == 'oopsie\n'
+ # errors always have colors
+ assert len(err) > len('Error: oopsie\n')
+ assert 'ERROR: oopsie' in err
+
+def test_colors_pouterr_always_notty(mocker, capsys):
+ app = ColorsApp()
+ app.colors = cmd2.constants.COLORS_ALWAYS
+ mocker.patch.object(app.stdout, 'isatty', return_value=False)
+ mocker.patch.object(sys.stderr, 'isatty', return_value=False)
+
+ app.onecmd_plus_hooks('echo_error oopsie')
+ out, err = capsys.readouterr()
+ # if colors are on, the output should have some escape sequences in it
+ assert len(out) > len('oopsie\n')
+ assert 'oopsie' in out
+ assert len(err) > len('Error: oopsie\n')
+ assert 'ERROR: oopsie' in err
+
+ # but this one shouldn't
+ app.onecmd_plus_hooks('echo oopsie')
+ out, err = capsys.readouterr()
+ assert out == 'oopsie\n'
+ # errors always have colors
+ assert len(err) > len('Error: oopsie\n')
+ assert 'ERROR: oopsie' in err
+
+def test_colors_terminal_tty(mocker, capsys):
+ app = ColorsApp()
+ app.colors = cmd2.constants.COLORS_TERMINAL
+ mocker.patch.object(app.stdout, 'isatty', return_value=True)
+ mocker.patch.object(sys.stderr, 'isatty', return_value=True)
+
+ app.onecmd_plus_hooks('echo_error oopsie')
+ # if colors are on, the output should have some escape sequences in it
+ out, err = capsys.readouterr()
+ assert len(out) > len('oopsie\n')
+ assert 'oopsie' in out
+ assert len(err) > len('Error: oopsie\n')
+ assert 'ERROR: oopsie' in err
+
+ # but this one shouldn't
+ app.onecmd_plus_hooks('echo oopsie')
+ out, err = capsys.readouterr()
+ assert out == 'oopsie\n'
+ assert len(err) > len('Error: oopsie\n')
+ assert 'ERROR: oopsie' in err
+
+def test_colors_terminal_notty(mocker, capsys):
+ app = ColorsApp()
+ app.colors = cmd2.constants.COLORS_TERMINAL
+ mocker.patch.object(app.stdout, 'isatty', return_value=False)
+ mocker.patch.object(sys.stderr, 'isatty', return_value=False)
+
+ app.onecmd_plus_hooks('echo_error oopsie')
+ out, err = capsys.readouterr()
+ assert out == 'oopsie\n'
+ assert err == 'ERROR: oopsie\n'
+
+ app.onecmd_plus_hooks('echo oopsie')
+ out, err = capsys.readouterr()
+ assert out == 'oopsie\n'
+ assert err == 'ERROR: oopsie\n'
+
+def test_colors_never_tty(mocker, capsys):
+ app = ColorsApp()
+ app.colors = cmd2.constants.COLORS_NEVER
+ mocker.patch.object(app.stdout, 'isatty', return_value=True)
+ mocker.patch.object(sys.stderr, 'isatty', return_value=True)
+
+ app.onecmd_plus_hooks('echo_error oopsie')
+ out, err = capsys.readouterr()
+ assert out == 'oopsie\n'
+ assert err == 'ERROR: oopsie\n'
+
+ app.onecmd_plus_hooks('echo oopsie')
+ out, err = capsys.readouterr()
+ assert out == 'oopsie\n'
+ assert err == 'ERROR: oopsie\n'
+
+def test_colors_never_notty(mocker, capsys):
+ app = ColorsApp()
+ app.colors = cmd2.constants.COLORS_NEVER
+ mocker.patch.object(app.stdout, 'isatty', return_value=False)
+ mocker.patch.object(sys.stderr, 'isatty', return_value=False)
+
+ app.onecmd_plus_hooks('echo_error oopsie')
+ out, err = capsys.readouterr()
+ assert out == 'oopsie\n'
+ assert err == 'ERROR: oopsie\n'
+
+ app.onecmd_plus_hooks('echo oopsie')
+ out, err = capsys.readouterr()
+ assert out == 'oopsie\n'
+ assert err == 'ERROR: oopsie\n'