diff --git a/cratonclient/shell/main.py b/cratonclient/shell/main.py index cba66b9..faf0ee3 100644 --- a/cratonclient/shell/main.py +++ b/cratonclient/shell/main.py @@ -11,10 +11,55 @@ # under the License. """Main shell for parsing arguments directed toward Craton.""" +from __future__ import print_function + +import argparse +import six +import sys + +from oslo_utils import encodeutils + + +class CratonShell(object): + """Class used to handle shell definition and parsing.""" + + def get_base_parser(self): + """Configure base craton arguments and parsing.""" + parser = argparse.ArgumentParser( + prog='craton', + description=__doc__.strip(), + epilog='See "craton help COMMAND" ' + 'for help on a specific command.', + add_help=False, + formatter_class=argparse.HelpFormatter + ) + + parser.add_argument('-h', '--help', + action='store_true', + help=argparse.SUPPRESS) + + return parser + + def main(self, argv): + """Main entry-point for cratonclient shell argument parsing.""" + parser = self.get_base_parser() + (options, args) = parser.parse_known_args(argv) + if options.help or not argv: + parser.print_help() + return 0 + def main(): """Main entry-point for cratonclient's CLI.""" + try: + CratonShell().main(map(encodeutils.safe_decode, sys.argv[1:])) + except Exception as e: + print("ERROR: %s" % encodeutils.safe_encode(six.text_type(e)), + file=sys.stderr) + sys.exit(1) + return 0 + if __name__ == "__main__": main() diff --git a/cratonclient/shell/v1.py b/cratonclient/shell/v1.py index f244b18..7ef4902 100644 --- a/cratonclient/shell/v1.py +++ b/cratonclient/shell/v1.py @@ -12,4 +12,5 @@ """Command-line interface to the OpenStack Craton API V1.""" + # TODO(cmspence): from cratonclient.v1 import client diff --git a/cratonclient/tests/base.py b/cratonclient/tests/base.py index 959578b..b11c450 100644 --- a/cratonclient/tests/base.py +++ b/cratonclient/tests/base.py @@ -16,8 +16,31 @@ # under the License. """Base TestCase for all cratonclient tests.""" +import mock +import six +import sys + from oslotest import base +from cratonclient.shell import main + class TestCase(base.BaseTestCase): """Test case base class for all unit tests.""" + + +class ShellTestCase(base.BaseTestCase): + """Test case base class for all shell unit tests.""" + + def shell(self, arg_str, exitcodes=(0,)): + """Main function for exercising the craton shell.""" + with mock.patch('sys.stdout', new=six.StringIO()) as mock_stdout, \ + mock.patch('sys.stderr', new=six.StringIO()) as mock_stderr: + + try: + main_shell = main.CratonShell() + main_shell.main(arg_str.split()) + except SystemExit: + exc_type, exc_value, exc_traceback = sys.exc_info() + self.assertIn(exc_value.code, exitcodes) + return (mock_stdout.getvalue(), mock_stderr.getvalue()) diff --git a/cratonclient/tests/unit/test_main_shell.py b/cratonclient/tests/unit/test_main_shell.py index d0c6ba3..9e2f6b9 100644 --- a/cratonclient/tests/unit/test_main_shell.py +++ b/cratonclient/tests/unit/test_main_shell.py @@ -12,13 +12,55 @@ """Tests for `cratonclient.shell.main` module.""" + +import mock +import re + +from testtools import matchers + from cratonclient.shell import main from cratonclient.tests import base -class TestMainShell(base.TestCase): +class TestMainShell(base.ShellTestCase): """Test our craton main shell.""" - def test_main_returns_successfully(self): - """Verify that cratonclient shell main returns as expected.""" + re_options = re.DOTALL | re.MULTILINE + + @mock.patch('cratonclient.shell.main.CratonShell.main') + def test_main_returns_successfully(self, cratonShellMainMock): + """Verify that main returns as expected.""" + cratonShellMainMock.return_value = 0 self.assertEqual(main.main(), 0) + + def test_print_help_no_args(self): + """Verify that no arguments prints out help by default.""" + required_help_responses = [ + '.*?^usage: craton', + '.*?^See "craton help COMMAND" ' + 'for help on a specific command.', + ] + stdout, stderr = self.shell('') + for r in required_help_responses: + self.assertThat((stdout + stderr), + matchers.MatchesRegex(r, self.re_options)) + + def test_print_help_with_args(self): + """Verify that help command(s) prints out help text correctly.""" + required_help_responses = [ + '.*?^usage: craton', + '.*?^See "craton help COMMAND" ' + 'for help on a specific command.', + ] + for help_args in ['-h', '--help']: + stdout, stderr = self.shell(help_args) + for r in required_help_responses: + self.assertThat((stdout + stderr), + matchers.MatchesRegex(r, self.re_options)) + + @mock.patch('cratonclient.shell.main.CratonShell.main') + def test_main_catches_exception(self, cratonShellMainMock): + """Verify exceptions will be caught and shell will exit properly.""" + cratonShellMainMock.side_effect = Exception(mock.Mock(status=404), + 'some error') + self.assertRaises(SystemExit, main.main)