2023-11-19 14:22:25 -06:00
|
|
|
# -*- coding: utf-8; -*-
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import sys
|
|
|
|
from unittest import TestCase
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
|
2023-11-22 11:13:39 -06:00
|
|
|
from wuttjamaican.cmd import base
|
|
|
|
from wuttjamaican.cmd.setup import Setup
|
2023-11-22 09:11:36 -06:00
|
|
|
from wuttjamaican.testing import FileConfigTestCase
|
2023-11-19 14:22:25 -06:00
|
|
|
|
|
|
|
|
2023-11-22 11:13:39 -06:00
|
|
|
# nb. do this just for coverage
|
|
|
|
from wuttjamaican.commands.base import Command as legacy
|
|
|
|
|
|
|
|
|
2023-11-22 09:11:36 -06:00
|
|
|
class TestCommand(FileConfigTestCase):
|
2023-11-19 14:22:25 -06:00
|
|
|
|
|
|
|
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)
|
2023-11-20 22:29:11 -06:00
|
|
|
self.assertEqual(str(cmd), 'wutta')
|
2023-11-19 14:22:25 -06:00
|
|
|
|
2023-11-21 14:08:26 -06:00
|
|
|
def test_subcommand_entry_points(self):
|
2023-11-22 11:13:39 -06:00
|
|
|
with patch('wuttjamaican.cmd.base.load_entry_points') as load_entry_points:
|
2023-11-21 14:08:26 -06:00
|
|
|
|
|
|
|
# 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')
|
|
|
|
|
2023-11-19 14:22:25 -06:00
|
|
|
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)
|
|
|
|
|
2023-11-22 09:11:36 -06:00
|
|
|
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")
|
|
|
|
|
2023-11-22 11:13:39 -06:00
|
|
|
with patch('wuttjamaican.cmd.base.sys') as sys:
|
2023-11-22 09:11:36 -06:00
|
|
|
|
|
|
|
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')
|
|
|
|
|
2023-11-19 14:22:25 -06:00
|
|
|
|
|
|
|
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):
|
|
|
|
|
2023-11-21 20:48:44 -06:00
|
|
|
def test_basic(self):
|
2023-11-19 14:22:25 -06:00
|
|
|
cmd = base.Command()
|
|
|
|
subcmd = base.Subcommand(cmd)
|
2023-11-21 20:48:44 -06:00
|
|
|
subcmd.name = 'foobar'
|
|
|
|
self.assertEqual(repr(subcmd), 'Subcommand(name=foobar)')
|
2023-11-19 14:22:25 -06:00
|
|
|
# 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)
|
|
|
|
|
2023-11-22 11:13:39 -06:00
|
|
|
with patch('wuttjamaican.cmd.base.sys') as mocksys:
|
2023-11-19 14:22:25 -06:00
|
|
|
mocksys.argv = ['wutta', '--help']
|
|
|
|
mocksys.exit = true_exit
|
|
|
|
|
|
|
|
try:
|
|
|
|
base.main()
|
|
|
|
except SystemExit:
|
|
|
|
pass
|