Move cli framework to wuttjamaican.cmd
subpackage
deprecate `wuttjamaican.commands`
This commit is contained in:
parent
37e42eebbc
commit
c3914738d5
23 changed files with 753 additions and 598 deletions
0
tests/cmd/__init__.py
Normal file
0
tests/cmd/__init__.py
Normal file
240
tests/cmd/test_base.py
Normal file
240
tests/cmd/test_base.py
Normal file
|
@ -0,0 +1,240 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from wuttjamaican.cmd import base
|
||||
from wuttjamaican.cmd.setup import Setup
|
||||
from wuttjamaican.testing import FileConfigTestCase
|
||||
|
||||
|
||||
# nb. do this just for coverage
|
||||
from wuttjamaican.commands.base import Command as legacy
|
||||
|
||||
|
||||
class TestCommand(FileConfigTestCase):
|
||||
|
||||
def test_base(self):
|
||||
# base command is for 'wutta' and has a 'setup' subcommand
|
||||
cmd = base.Command()
|
||||
self.assertEqual(cmd.name, 'wutta')
|
||||
self.assertIn('setup', cmd.subcommands)
|
||||
self.assertEqual(str(cmd), 'wutta')
|
||||
|
||||
def test_subcommand_entry_points(self):
|
||||
with patch('wuttjamaican.cmd.base.load_entry_points') as load_entry_points:
|
||||
|
||||
# empty entry points
|
||||
load_entry_points.side_effect = lambda group: {}
|
||||
cmd = base.Command()
|
||||
self.assertEqual(cmd.subcommands, {})
|
||||
|
||||
# typical entry points
|
||||
load_entry_points.side_effect = lambda group: {'setup': Setup}
|
||||
cmd = base.Command()
|
||||
self.assertEqual(cmd.subcommands, {'setup': Setup})
|
||||
self.assertEqual(cmd.subcommands['setup'].name, 'setup')
|
||||
|
||||
# legacy entry points
|
||||
# nb. mock returns entry points only when legacy name is used
|
||||
load_entry_points.side_effect = lambda group: {} if 'subcommands' in group else {'setup': Setup}
|
||||
cmd = base.Command()
|
||||
self.assertEqual(cmd.subcommands, {'setup': Setup})
|
||||
self.assertEqual(cmd.subcommands['setup'].name, 'setup')
|
||||
|
||||
def test_sorted_subcommands(self):
|
||||
cmd = base.Command(subcommands={'foo': 'FooSubcommand',
|
||||
'bar': 'BarSubcommand'})
|
||||
|
||||
srtd = cmd.sorted_subcommands()
|
||||
self.assertEqual(srtd, ['BarSubcommand', 'FooSubcommand'])
|
||||
|
||||
def test_run_may_print_help(self):
|
||||
|
||||
class Hello(base.Subcommand):
|
||||
name = 'hello'
|
||||
|
||||
cmd = base.Command(subcommands={'hello': Hello})
|
||||
|
||||
# first run is not "tested" per se but gives us some coverage.
|
||||
# (this will *actually* print help, b/c no args specified)
|
||||
try:
|
||||
cmd.run()
|
||||
except SystemExit:
|
||||
pass
|
||||
|
||||
# from now on we mock the help
|
||||
print_help = MagicMock()
|
||||
cmd.print_help = print_help
|
||||
|
||||
# help is shown if no subcommand is given
|
||||
try:
|
||||
cmd.run()
|
||||
except SystemExit:
|
||||
pass
|
||||
print_help.assert_called_once_with()
|
||||
|
||||
# help is shown if -h is given
|
||||
print_help.reset_mock()
|
||||
try:
|
||||
cmd.run('-h')
|
||||
except SystemExit:
|
||||
pass
|
||||
print_help.assert_called_once_with()
|
||||
|
||||
# help is shown if --help is given
|
||||
print_help.reset_mock()
|
||||
try:
|
||||
cmd.run('--help')
|
||||
except SystemExit:
|
||||
pass
|
||||
print_help.assert_called_once_with()
|
||||
|
||||
# help is shown if bad arg is given
|
||||
print_help.reset_mock()
|
||||
try:
|
||||
cmd.run('--this-means-nothing')
|
||||
except SystemExit:
|
||||
pass
|
||||
print_help.assert_called_once_with()
|
||||
|
||||
# help is shown if bad subcmd is given
|
||||
print_help.reset_mock()
|
||||
try:
|
||||
cmd.run('make-sandwich')
|
||||
except SystemExit:
|
||||
pass
|
||||
print_help.assert_called_once_with()
|
||||
|
||||
# main help is *not* shown if subcommand *and* -h are given
|
||||
# (sub help is shown instead in that case)
|
||||
print_help.reset_mock()
|
||||
try:
|
||||
cmd.run('hello', '-h')
|
||||
except SystemExit:
|
||||
pass
|
||||
print_help.assert_not_called()
|
||||
|
||||
def test_run_invokes_subcommand(self):
|
||||
|
||||
class Hello(base.Subcommand):
|
||||
name = 'hello'
|
||||
def add_args(self):
|
||||
self.parser.add_argument('--foo', action='store_true')
|
||||
def run(self, args):
|
||||
self.run_with(foo=args.foo)
|
||||
|
||||
run_with = Hello.run_with = MagicMock()
|
||||
cmd = base.Command(subcommands={'hello': Hello})
|
||||
|
||||
# omit --foo in which case that is false by default
|
||||
cmd.run('hello')
|
||||
run_with.assert_called_once_with(foo=False)
|
||||
|
||||
# specify --foo in which case that is true
|
||||
run_with.reset_mock()
|
||||
cmd.run('hello', '--foo')
|
||||
run_with.assert_called_once_with(foo=True)
|
||||
|
||||
def test_run_uses_stdout_stderr_params(self):
|
||||
myout = self.write_file('my.out', '')
|
||||
myerr = self.write_file('my.err', '')
|
||||
|
||||
class Hello(base.Subcommand):
|
||||
name = 'hello'
|
||||
def run(self, args):
|
||||
self.stdout.write("hello world")
|
||||
self.stderr.write("error text")
|
||||
|
||||
with patch('wuttjamaican.cmd.base.sys') as sys:
|
||||
|
||||
cmd = base.Command(subcommands={'hello': Hello})
|
||||
|
||||
# sys.stdout and sys.stderr should be used by default
|
||||
cmd.run('hello')
|
||||
sys.exit.assert_not_called()
|
||||
sys.stdout.write.assert_called_once_with('hello world')
|
||||
sys.stderr.write.assert_called_once_with('error text')
|
||||
|
||||
# but our files may be used instead if specified
|
||||
sys.reset_mock()
|
||||
cmd.run('hello', '--stdout', myout, '--stderr', myerr)
|
||||
sys.exit.assert_not_called()
|
||||
sys.stdout.write.assert_not_called()
|
||||
sys.stderr.write.assert_not_called()
|
||||
with open(myout, 'rt') as f:
|
||||
self.assertEqual(f.read(), 'hello world')
|
||||
with open(myerr, 'rt') as f:
|
||||
self.assertEqual(f.read(), 'error text')
|
||||
|
||||
|
||||
class TestCommandArgumentParser(TestCase):
|
||||
|
||||
def test_parse_args(self):
|
||||
|
||||
kw = {
|
||||
'prog': 'wutta',
|
||||
'add_help': False,
|
||||
}
|
||||
|
||||
# nb. examples below assume a command line like:
|
||||
# bin/wutta foo --bar
|
||||
|
||||
# first here is what default parser does
|
||||
parser = argparse.ArgumentParser(**kw)
|
||||
parser.add_argument('subcommand', nargs='*')
|
||||
try:
|
||||
args = parser.parse_args(['foo', '--bar'])
|
||||
except SystemExit:
|
||||
# nb. parser was not happy, tried to exit process
|
||||
args = None
|
||||
else:
|
||||
self.assertEqual(args.subcommand, ['foo', '--bar'])
|
||||
self.assertFalse(hasattr(args, 'argv'))
|
||||
|
||||
# now here is was custom parser does
|
||||
# (moves extras to argv for subcommand parser)
|
||||
parser = base.CommandArgumentParser(**kw)
|
||||
parser.add_argument('subcommand', nargs='*')
|
||||
args = parser.parse_args(['foo', '--bar'])
|
||||
self.assertEqual(args.subcommand, ['foo'])
|
||||
self.assertEqual(args.argv, ['--bar'])
|
||||
|
||||
|
||||
class TestSubcommand(TestCase):
|
||||
|
||||
def test_basic(self):
|
||||
cmd = base.Command()
|
||||
subcmd = base.Subcommand(cmd)
|
||||
subcmd.name = 'foobar'
|
||||
self.assertEqual(repr(subcmd), 'Subcommand(name=foobar)')
|
||||
# TODO: this doesn't really test anything per se, but at least
|
||||
# gives us the coverage..
|
||||
subcmd._run()
|
||||
|
||||
|
||||
class TestMain(TestCase):
|
||||
|
||||
# nb. this doesn't test anything per se but gives coverage
|
||||
|
||||
def test_explicit_args(self):
|
||||
try:
|
||||
base.main('--help')
|
||||
except SystemExit:
|
||||
pass
|
||||
|
||||
def test_implicit_args(self):
|
||||
|
||||
def true_exit(*args):
|
||||
sys.exit(*args)
|
||||
|
||||
with patch('wuttjamaican.cmd.base.sys') as mocksys:
|
||||
mocksys.argv = ['wutta', '--help']
|
||||
mocksys.exit = true_exit
|
||||
|
||||
try:
|
||||
base.main()
|
||||
except SystemExit:
|
||||
pass
|
53
tests/cmd/test_make_appdir.py
Normal file
53
tests/cmd/test_make_appdir.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch
|
||||
|
||||
from wuttjamaican.conf import WuttaConfig
|
||||
from wuttjamaican.cmd import Command, make_appdir
|
||||
|
||||
|
||||
# nb. do this just for coverage
|
||||
from wuttjamaican.commands.make_appdir import MakeAppDir as legacy
|
||||
|
||||
|
||||
class TestMakeAppDir(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig(appname='wuttatest')
|
||||
self.command = Command(self.config, subcommands={
|
||||
'make-appdir': make_appdir.MakeAppDir,
|
||||
})
|
||||
|
||||
def test_run(self):
|
||||
|
||||
# appdir is created, and 3 subfolders added by default
|
||||
tempdir = tempfile.mkdtemp()
|
||||
appdir = os.path.join(tempdir, 'app')
|
||||
self.assertFalse(os.path.exists(appdir))
|
||||
self.command.run('make-appdir', '--path', appdir)
|
||||
self.assertTrue(os.path.exists(appdir))
|
||||
self.assertEqual(len(os.listdir(appdir)), 3)
|
||||
shutil.rmtree(tempdir)
|
||||
|
||||
# subfolders still added if appdir already exists
|
||||
tempdir = tempfile.mkdtemp()
|
||||
self.assertTrue(os.path.exists(tempdir))
|
||||
self.assertEqual(len(os.listdir(tempdir)), 0)
|
||||
self.command.run('make-appdir', '--path', tempdir)
|
||||
self.assertEqual(len(os.listdir(tempdir)), 3)
|
||||
shutil.rmtree(tempdir)
|
||||
|
||||
# mock out sys.prefix to get coverage
|
||||
with patch('wuttjamaican.cmd.make_appdir.sys') as sys:
|
||||
tempdir = tempfile.mkdtemp()
|
||||
appdir = os.path.join(tempdir, 'app')
|
||||
sys.prefix = tempdir
|
||||
self.assertFalse(os.path.exists(appdir))
|
||||
self.command.run('make-appdir')
|
||||
self.assertTrue(os.path.exists(appdir))
|
||||
self.assertEqual(len(os.listdir(appdir)), 3)
|
||||
shutil.rmtree(tempdir)
|
20
tests/cmd/test_setup.py
Normal file
20
tests/cmd/test_setup.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from wuttjamaican.cmd import Command, setup
|
||||
|
||||
|
||||
# nb. do this just for coverage
|
||||
from wuttjamaican.commands.setup import Setup as legacy
|
||||
|
||||
|
||||
class TestSetup(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.command = Command()
|
||||
self.subcommand = setup.Setup(self.command)
|
||||
|
||||
def test_run(self):
|
||||
# TODO: this doesn't really test anything yet
|
||||
self.subcommand._run()
|
Loading…
Add table
Add a link
Reference in a new issue