2
0
Fork 0
wuttjamaican/tests/cmd/test_base.py
Lance Edgar c3914738d5 Move cli framework to wuttjamaican.cmd subpackage
deprecate `wuttjamaican.commands`
2023-11-22 11:13:39 -06:00

241 lines
7.4 KiB
Python

# -*- 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