summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDoug Hellmann <doug.hellmann@dreamhost.com>2012-04-29 17:27:06 -0400
committerDoug Hellmann <doug.hellmann@dreamhost.com>2012-04-29 17:27:06 -0400
commit25c560489b09b58b27032e4ae2694f6e9cc4880c (patch)
tree90364490510e44ee611a47ab31a6b4b5895c12ed
parent55ef11d7bf1b65705a1293b32c3db8b549694b85 (diff)
parenta531ccf8321f2ca6a707f069b07d2731409d8f38 (diff)
downloadcliff-tablib-25c560489b09b58b27032e4ae2694f6e9cc4880c.tar.gz
Merge branch 'adding-tests'
-rw-r--r--cliff/app.py19
-rw-r--r--cliff/help.py12
-rw-r--r--cliff/interactive.py1
-rw-r--r--tests/test_app.py170
-rw-r--r--tests/test_command.py22
-rw-r--r--tests/test_commandmanager.py46
-rw-r--r--tests/test_help.py75
-rw-r--r--tox.ini7
8 files changed, 338 insertions, 14 deletions
diff --git a/cliff/app.py b/cliff/app.py
index 6b1a851..eec2d5c 100644
--- a/cliff/app.py
+++ b/cliff/app.py
@@ -90,7 +90,7 @@ class App(object):
'-h', '--help',
action=HelpAction,
nargs=0,
- default=self.command_manager, # tricky
+ default=self, # tricky
help="show this help message and exit",
)
parser.add_argument(
@@ -118,7 +118,7 @@ class App(object):
root_logger.addHandler(file_handler)
# Send higher-level messages to the console via stderr
- console = logging.StreamHandler()
+ console = logging.StreamHandler(self.stderr)
console_level = {0: logging.WARNING,
1: logging.INFO,
2: logging.DEBUG,
@@ -177,7 +177,6 @@ class App(object):
def interact(self):
self.interactive_mode = True
interpreter = self.interactive_app_factory(self, self.command_manager, self.stdin, self.stdout)
- interpreter.prompt = '(%s) ' % self.NAME
interpreter.cmdloop()
return 0
@@ -193,11 +192,9 @@ class App(object):
parsed_args = cmd_parser.parse_args(sub_argv)
result = cmd.run(parsed_args)
except Exception as err:
+ LOG.error('ERROR: %s', err)
if self.options.debug:
LOG.exception(err)
- raise
- LOG.error('ERROR: %s', err)
- finally:
try:
self.clean_up(cmd, result, err)
except Exception as err2:
@@ -205,4 +202,14 @@ class App(object):
LOG.exception(err2)
else:
LOG.error('Could not clean up: %s', err2)
+ if self.options.debug:
+ raise
+ else:
+ try:
+ self.clean_up(cmd, result, None)
+ except Exception as err3:
+ if self.options.debug:
+ LOG.exception(err3)
+ else:
+ LOG.error('Could not clean up: %s', err3)
return result
diff --git a/cliff/help.py b/cliff/help.py
index 391d8cc..2ac8bc4 100644
--- a/cliff/help.py
+++ b/cliff/help.py
@@ -12,15 +12,15 @@ class HelpAction(argparse.Action):
instance, passed in as the "default" value for the action.
"""
def __call__(self, parser, namespace, values, option_string=None):
- parser.print_help()
- print('')
- print('Commands:')
- command_manager = self.default
+ app = self.default
+ parser.print_help(app.stdout)
+ app.stdout.write('\nCommands:\n')
+ command_manager = app.command_manager
for name, ep in sorted(command_manager):
factory = ep.load()
cmd = factory(self, None)
one_liner = cmd.get_description().split('\n')[0]
- print(' %-13s %s' % (name, one_liner))
+ app.stdout.write(' %-13s %s\n' % (name, one_liner))
sys.exit(0)
@@ -47,5 +47,5 @@ class HelpCommand(Command):
cmd_parser = cmd.get_parser(full_name)
else:
cmd_parser = self.get_parser(' '.join([self.app.NAME, 'help']))
- cmd_parser.parse_args(['--help'])
+ cmd_parser.print_help(self.app.stdout)
return 0
diff --git a/cliff/interactive.py b/cliff/interactive.py
index 493235c..2543b20 100644
--- a/cliff/interactive.py
+++ b/cliff/interactive.py
@@ -33,6 +33,7 @@ class InteractiveApp(cmd2.Cmd):
def __init__(self, parent_app, command_manager, stdin, stdout):
self.parent_app = parent_app
+ self.prompt = '(%s) ' % parent_app.NAME
self.command_manager = command_manager
cmd2.Cmd.__init__(self, 'tab', stdin=stdin, stdout=stdout)
diff --git a/tests/test_app.py b/tests/test_app.py
new file mode 100644
index 0000000..cae2c32
--- /dev/null
+++ b/tests/test_app.py
@@ -0,0 +1,170 @@
+from cliff.app import App
+from cliff.command import Command
+from cliff.commandmanager import CommandManager
+
+import mock
+
+
+def make_app():
+ cmd_mgr = CommandManager('cliff.tests')
+
+ # Register a command that succeeds
+ command = mock.MagicMock(spec=Command)
+ command_inst = mock.MagicMock(spec=Command)
+ command_inst.run.return_value = 0
+ command.return_value = command_inst
+ cmd_mgr.add_command('mock', command)
+
+ # Register a command that fails
+ err_command = mock.Mock(name='err_command', spec=Command)
+ err_command_inst = mock.Mock(spec=Command)
+ err_command_inst.run = mock.Mock(side_effect=RuntimeError('test exception'))
+ err_command.return_value = err_command_inst
+ cmd_mgr.add_command('error', err_command)
+
+ app = App('testing interactive mode',
+ '1',
+ cmd_mgr,
+ stderr=mock.Mock(), # suppress warning messages
+ )
+ return app, command
+
+
+def test_no_args_triggers_interactive_mode():
+ app, command = make_app()
+ app.interact = mock.MagicMock(name='inspect')
+ app.run([])
+ app.interact.assert_called_once_with()
+
+
+def test_interactive_mode_cmdloop():
+ app, command = make_app()
+ app.interactive_app_factory = mock.MagicMock(name='interactive_app_factory')
+ app.run([])
+ app.interactive_app_factory.return_value.cmdloop.assert_called_once_with()
+
+
+def test_initialize_app():
+ app, command = make_app()
+ app.initialize_app = mock.MagicMock(name='initialize_app')
+ app.run(['mock'])
+ app.initialize_app.assert_called_once_with()
+
+
+def test_prepare_to_run_command():
+ app, command = make_app()
+ app.prepare_to_run_command = mock.MagicMock(name='prepare_to_run_command')
+ app.run(['mock'])
+ app.prepare_to_run_command.assert_called_once_with(command())
+
+
+def test_clean_up_success():
+ app, command = make_app()
+ app.clean_up = mock.MagicMock(name='clean_up')
+ app.run(['mock'])
+ app.clean_up.assert_called_once_with(command.return_value, 0, None)
+
+
+def test_clean_up_error():
+ app, command = make_app()
+
+ app.clean_up = mock.MagicMock(name='clean_up')
+ app.run(['error'])
+
+ app.clean_up.assert_called_once()
+ call_args = app.clean_up.call_args_list[0]
+ assert call_args == mock.call(mock.ANY, 1, mock.ANY)
+ args, kwargs = call_args
+ assert isinstance(args[2], RuntimeError)
+ assert args[2].args == ('test exception',)
+
+
+def test_clean_up_error_debug():
+ app, command = make_app()
+
+ app.clean_up = mock.MagicMock(name='clean_up')
+ try:
+ app.run(['--debug', 'error'])
+ except RuntimeError as err:
+ assert app.clean_up.call_args_list[0][0][2] is err
+ else:
+ assert False, 'Should have had an exception'
+
+ app.clean_up.assert_called_once()
+ call_args = app.clean_up.call_args_list[0]
+ assert call_args == mock.call(mock.ANY, 1, mock.ANY)
+ args, kwargs = call_args
+ assert isinstance(args[2], RuntimeError)
+ assert args[2].args == ('test exception',)
+
+
+def test_error_handling_clean_up_raises_exception():
+ app, command = make_app()
+
+ app.clean_up = mock.MagicMock(
+ name='clean_up',
+ side_effect=RuntimeError('within clean_up'),
+ )
+ app.run(['error'])
+
+ app.clean_up.assert_called_once()
+ call_args = app.clean_up.call_args_list[0]
+ assert call_args == mock.call(mock.ANY, 1, mock.ANY)
+ args, kwargs = call_args
+ assert isinstance(args[2], RuntimeError)
+ assert args[2].args == ('test exception',)
+
+
+def test_error_handling_clean_up_raises_exception_debug():
+ app, command = make_app()
+
+ app.clean_up = mock.MagicMock(
+ name='clean_up',
+ side_effect=RuntimeError('within clean_up'),
+ )
+ try:
+ app.run(['--debug', 'error'])
+ except RuntimeError as err:
+ if not hasattr(err, '__context__'):
+ # The exception passed to clean_up is not the exception
+ # caused *by* clean_up. This test is only valid in python
+ # 2 because under v3 the original exception is re-raised
+ # with the new one as a __context__ attribute.
+ assert app.clean_up.call_args_list[0][0][2] is not err
+ else:
+ assert False, 'Should have had an exception'
+
+ app.clean_up.assert_called_once()
+ call_args = app.clean_up.call_args_list[0]
+ assert call_args == mock.call(mock.ANY, 1, mock.ANY)
+ args, kwargs = call_args
+ assert isinstance(args[2], RuntimeError)
+ assert args[2].args == ('test exception',)
+
+
+def test_normal_clean_up_raises_exception():
+ app, command = make_app()
+
+ app.clean_up = mock.MagicMock(
+ name='clean_up',
+ side_effect=RuntimeError('within clean_up'),
+ )
+ app.run(['mock'])
+
+ app.clean_up.assert_called_once()
+ call_args = app.clean_up.call_args_list[0]
+ assert call_args == mock.call(mock.ANY, 0, None)
+
+
+def test_normal_clean_up_raises_exception_debug():
+ app, command = make_app()
+
+ app.clean_up = mock.MagicMock(
+ name='clean_up',
+ side_effect=RuntimeError('within clean_up'),
+ )
+ app.run(['--debug', 'mock'])
+
+ app.clean_up.assert_called_once()
+ call_args = app.clean_up.call_args_list[0]
+ assert call_args == mock.call(mock.ANY, 0, None)
diff --git a/tests/test_command.py b/tests/test_command.py
new file mode 100644
index 0000000..8b0d217
--- /dev/null
+++ b/tests/test_command.py
@@ -0,0 +1,22 @@
+
+from cliff.command import Command
+
+
+class TestCommand(Command):
+ """Description of command.
+ """
+
+ def run(self, parsed_args):
+ return
+
+
+def test_get_description():
+ cmd = TestCommand(None, None)
+ desc = cmd.get_description()
+ assert desc == "Description of command.\n "
+
+
+def test_get_parser():
+ cmd = TestCommand(None, None)
+ parser = cmd.get_parser('NAME')
+ assert parser.prog == 'NAME'
diff --git a/tests/test_commandmanager.py b/tests/test_commandmanager.py
index 6be244e..1945f2e 100644
--- a/tests/test_commandmanager.py
+++ b/tests/test_commandmanager.py
@@ -1,4 +1,6 @@
+import mock
+
from cliff.commandmanager import CommandManager
@@ -47,3 +49,47 @@ def test_lookup_with_remainder():
]:
yield check, mgr, expected
return
+
+
+def test_find_invalid_command():
+ mgr = TestCommandManager('test')
+ def check_one(argv):
+ try:
+ mgr.find_command(argv)
+ except ValueError as err:
+ assert '-b' in ('%s' % err)
+ else:
+ assert False, 'expected a failure'
+ for argv in [['a', '-b'],
+ ['-b'],
+ ]:
+ yield check_one, argv
+
+
+def test_find_unknown_command():
+ mgr = TestCommandManager('test')
+ try:
+ mgr.find_command(['a', 'b'])
+ except ValueError as err:
+ assert "['a', 'b']" in ('%s' % err)
+ else:
+ assert False, 'expected a failure'
+
+
+def test_add_command():
+ mgr = TestCommandManager('test')
+ mock_cmd = mock.Mock()
+ mgr.add_command('mock', mock_cmd)
+ found_cmd, name, args = mgr.find_command(['mock'])
+ assert found_cmd is mock_cmd
+
+
+def test_load_commands():
+ testcmd = mock.Mock(name='testcmd')
+ testcmd.name.replace.return_value = 'test'
+ mock_pkg_resources = mock.Mock(return_value=[testcmd])
+ with mock.patch('pkg_resources.iter_entry_points', mock_pkg_resources) as iter_entry_points:
+ mgr = CommandManager('test')
+ assert iter_entry_points.called_once_with('test')
+ names = [n for n, v in mgr]
+ assert names == ['test']
diff --git a/tests/test_help.py b/tests/test_help.py
new file mode 100644
index 0000000..4a2fd1c
--- /dev/null
+++ b/tests/test_help.py
@@ -0,0 +1,75 @@
+try:
+ from StringIO import StringIO
+except:
+ from io import StringIO
+
+import mock
+
+from cliff.app import App
+from cliff.command import Command
+from cliff.commandmanager import CommandManager
+from cliff.help import HelpCommand
+
+
+class TestParser(object):
+
+ def print_help(self, stdout):
+ stdout.write('TestParser')
+
+
+class TestCommand(Command):
+
+ @classmethod
+ def load(cls):
+ return cls
+
+ def get_parser(self, ignore):
+ # Make it look like this class is the parser
+ # so parse_args() is called.
+ return TestParser()
+
+ def run(self, args):
+ return
+
+
+class TestCommandManager(CommandManager):
+ def _load_commands(self):
+ self.commands = {
+ 'one': TestCommand,
+ 'two words': TestCommand,
+ 'three word command': TestCommand,
+ }
+
+
+def test_show_help_for_command():
+ # FIXME(dhellmann): Are commands tied too closely to the app? Or
+ # do commands know too much about apps by using them to get to the
+ # command manager?
+ stdout = StringIO()
+ app = App('testing', '1', TestCommandManager('cliff.test'), stdout=stdout)
+ app.NAME = 'test'
+ help_cmd = HelpCommand(app, mock.Mock())
+ parser = help_cmd.get_parser('test')
+ parsed_args = parser.parse_args(['one'])
+ try:
+ help_cmd.run(parsed_args)
+ except SystemExit:
+ pass
+ assert stdout.getvalue() == 'TestParser'
+
+def test_show_help_for_help():
+ # FIXME(dhellmann): Are commands tied too closely to the app? Or
+ # do commands know too much about apps by using them to get to the
+ # command manager?
+ stdout = StringIO()
+ app = App('testing', '1', TestCommandManager('cliff.test'), stdout=stdout)
+ app.NAME = 'test'
+ help_cmd = HelpCommand(app, mock.Mock())
+ parser = help_cmd.get_parser('test')
+ parsed_args = parser.parse_args([])
+ try:
+ help_cmd.run(parsed_args)
+ except SystemExit:
+ pass
+ help_text = stdout.getvalue()
+ assert 'usage: test help [-h]' in help_text
diff --git a/tox.ini b/tox.ini
index 55c9cfc..1052309 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,5 +2,8 @@
envlist = py27,py32
[testenv]
-commands = nosetests -d []
-deps = nose
+commands = nosetests -d --with-coverage --cover-package=cliff []
+deps =
+ nose
+ mock
+ coverage