Skip to content

Commit

Permalink
Use cliff for CLI layer
Browse files Browse the repository at this point in the history
This commit makes stestr use cliff[1] for CLI layer. The cliff project
provides a lot of advantages for stestr cli which has subcommands.
Instead of just using argparse, we should leverage cliff to provide a
more plished CLI experience.

[1] https://pypi.python.org/pypi/cliff

Closes Issue #62
  • Loading branch information
masayukig committed Sep 22, 2017
1 parent f5099f8 commit 893f53f
Show file tree
Hide file tree
Showing 11 changed files with 338 additions and 298 deletions.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# process, which may cause wedges in the gate later.
future
pbr!=2.1.0,>=2.0.0 # Apache-2.0
cliff>=2.8.0 # Apache-2.0
python-subunit>=0.18.0 # Apache-2.0/BSD
fixtures>=3.0.0 # Apache-2.0/BSD
six>=1.10.0 # MIT
Expand Down
9 changes: 9 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ packages =
console_scripts =
stestr = stestr.cli:main

stestr.cm =
run = stestr.commands.run:Run
failing = stestr.commands.failing:Failing
init = stestr.commands.init:Init
last = stestr.commands.last:Last
list = stestr.commands.list:List
load = stestr.commands.load:Load
slowest = stestr.commands.slowest:Slowest

[extras]
sql =
subunit2sql>=1.8.0
Expand Down
75 changes: 31 additions & 44 deletions stestr/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,41 @@
# License for the specific language governing permissions and limitations
# under the License.

import argparse
import importlib
import os
import sys

from cliff import app
from cliff import commandmanager
from stestr import version

__version__ = version.version_info.version_string_with_vcs()


class StestrCLI(object):

commands = ['run', 'list', 'slowest', 'failing', 'last', 'init', 'load']
command_module = 'stestr.commands.'
class StestrCLI(app.App):

def __init__(self):
self.parser = self._get_parser()

def _get_parser(self):
self.command_dict = {}
parser = argparse.ArgumentParser()
self._set_common_opts(parser)
subparsers = parser.add_subparsers(help='command help')
for cmd in self.commands:
self.command_dict[cmd] = importlib.import_module(
self.command_module + cmd)
help_str = self.command_dict[cmd].get_cli_help()
command_parser = subparsers.add_parser(cmd, help=help_str)
self.command_dict[cmd].set_cli_opts(command_parser)
command_parser.set_defaults(func=self.command_dict[cmd].run)
super(StestrCLI, self).__init__(
description='stestr application',
version=__version__,
command_manager=commandmanager.CommandManager('stestr.cm'),
deferred_help=True,
)

def initialize_app(self, argv):
self.LOG.debug('initialize_app')

def prepare_to_run_command(self, cmd):
self.LOG.debug('prepare_to_run_command %s', cmd.__class__.__name__)

def clean_up(self, cmd, result, err):
self.LOG.debug('clean_up %s', cmd.__class__.__name__)
if err:
self.LOG.debug('got an error: %s', err)

def build_option_parser(self, description, version, argparse_kwargs=None):
parser = super(StestrCLI,
self).build_option_parser(description, version,
argparse_kwargs)
parser = self._set_common_opts(parser)
return parser

def _set_common_opts(self, parser):
Expand All @@ -49,12 +54,6 @@ def _set_common_opts(self, parser):
"path lookups but does not affect paths "
"supplied to the command.",
default=None, type=str)
parser.add_argument("-q", "--quiet", action="store_true",
default=False,
help="Turn off output other than the primary "
"output for a command and any errors.")
parser.add_argument('--version', action='version',
version=__version__)
parser.add_argument('--config', '-c', dest='config',
default='.stestr.conf',
help="Set a stestr config file to use with this "
Expand Down Expand Up @@ -88,25 +87,13 @@ def _set_common_opts(self, parser):
"both this and the corresponding config file "
"option are set this value will be used.")

return parser


def main():
def main(argv=sys.argv[1:]):
cli = StestrCLI()
args = cli.parser.parse_known_args()
if args[0].here:
os.chdir(args[0].here)
# NOTE(mtreinish): Make sure any subprocesses launch the same version of
# python being run here
if 'PYTHON' not in os.environ:
os.environ['PYTHON'] = sys.executable
if hasattr(args[0], 'func'):
sys.exit(args[0].func(args))
else:
cli.parser.print_help()
# NOTE(andreaf) This point is reached only when using Python 3.x.
# Python 2.x fails with return code 2 in case of no
# command, so using 2 for consistency
sys.exit(2)
return cli.run(argv)


if __name__ == '__main__':
main()
sys.exit(main(sys.argv[1:]))
33 changes: 18 additions & 15 deletions stestr/commands/failing.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,33 @@

import sys

from cliff import command
import testtools

from stestr import output
from stestr.repository import util
from stestr import results


def get_cli_help():
return "Show the current failures known by the repository"
class Failing(command.Command):
def get_description(self):
return "Show the current failures known by the repository"

def get_parser(self, prog_name):
parser = super(Failing, self).get_parser(prog_name)
parser.add_argument(
"--subunit", action="store_true",
default=False, help="Show output as a subunit stream.")
parser.add_argument(
"--list", action="store_true",
default=False, help="Show only a list of failing tests.")
return parser

def set_cli_opts(parser):
parser.add_argument(
"--subunit", action="store_true",
default=False, help="Show output as a subunit stream."),
parser.add_argument(
"--list", action="store_true",
default=False, help="Show only a list of failing tests."),
def take_action(self, parsed_args):
args = parsed_args
return failing(repo_type=self.app_args.repo_type,
repo_url=self.app_args.repo_url,
list_tests=args.list, subunit=args.subunit)


def _show_subunit(run):
Expand All @@ -57,12 +66,6 @@ def _get_id():
return output_result, summary_result


def run(arguments):
args = arguments[0]
return failing(repo_type=args.repo_type, repo_url=args.repo_url,
list_tests=args.list, subunit=args.subunit)


def failing(repo_type='file', repo_url=None, list_tests=False, subunit=False,
stdout=sys.stdout):
"""Print the failing tests from the most recent run in the repository
Expand Down
21 changes: 9 additions & 12 deletions stestr/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@

import sys

from cliff import command

from stestr.repository import util


def run(arguments):
args = arguments[0]
init(args.repo_type, args.repo_url)
class Init(command.Command):
def take_action(self, parsed_args):
init(self.app_args.repo_type, self.app_args.repo_url)

def get_description(self):
help_str = "Create a new repository."
return help_str


def init(repo_type='file', repo_url=None, stdout=sys.stdout):
Expand Down Expand Up @@ -50,12 +56,3 @@ def init(repo_type='file', repo_url=None, stdout=sys.stdout):
'Please check if the repository already exists or '
'select a different path\n' % repo_path)
return 1


def set_cli_opts(parser):
pass


def get_cli_help():
help_str = "Create a new repository."
return help_str
72 changes: 38 additions & 34 deletions stestr/commands/last.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,47 +14,51 @@

import sys

from cliff import command

from stestr import output
from stestr.repository import abstract
from stestr.repository import util
from stestr import results
from stestr import subunit_trace


def get_cli_help():
help_str = """Show the last run loaded into a repository.
Failing tests are shown on the console and a summary of the run is printed
at the end.
Without --subunit, the process exit code will be non-zero if the test run
was not successful. With --subunit, the process exit code is non-zero if
the subunit stream could not be generated successfully.
"""
return help_str


def set_cli_opts(parser):
parser.add_argument(
"--subunit", action="store_true",
default=False, help="Show output as a subunit stream.")
parser.add_argument("--no-subunit-trace", action='store_true',
default=False,
help="Disable output with the subunit-trace output "
"filter")
parser.add_argument('--color', action='store_true', default=False,
help='Enable color output in the subunit-trace output,'
' if subunit-trace output is enabled. (this is '
'the default). If subunit-trace is disable this '
' does nothing.')


def run(arguments):
args = arguments[0]
pretty_out = not args.no_subunit_trace
return last(repo_type=args.repo_type, repo_url=args.repo_url,
subunit_out=args.subunit, pretty_out=pretty_out,
color=args.color)
class Last(command.Command):
def get_description(self):
help_str = """Show the last run loaded into a repository.
Failing tests are shown on the console and a summary of the run is
printed at the end.
Without --subunit, the process exit code will be non-zero if the test
run was not successful. With --subunit, the process exit code is
non-zero if the subunit stream could not be generated successfully.
"""
return help_str

def get_parser(self, prog_name):
parser = super(Last, self).get_parser(prog_name)
parser.add_argument(
"--subunit", action="store_true",
default=False, help="Show output as a subunit stream.")
parser.add_argument("--no-subunit-trace", action='store_true',
default=False,
help="Disable output with the subunit-trace "
"output filter")
parser.add_argument('--color', action='store_true', default=False,
help='Enable color output in the subunit-trace '
'output, if subunit-trace output is enabled. '
'(this is the default). If subunit-trace is '
'disable this does nothing.')
return parser

def take_action(self, parsed_args):
args = parsed_args
pretty_out = not args.no_subunit_trace
return last(repo_type=self.app_args.repo_type,
repo_url=self.app_args.repo_url,
subunit_out=args.subunit, pretty_out=pretty_out,
color=args.color)


def last(repo_type='file', repo_url=None, subunit_out=False, pretty_out=True,
Expand Down

0 comments on commit 893f53f

Please sign in to comment.