jw-pkg/test/unit/python/jw/pkg/lib/App/test.py

103 lines
3.5 KiB
Python
Raw Normal View History

import asyncio
import sys
from jw.pkg.lib.App import App
from jw.pkg.lib.Cmd import Cmd
# -- A minimal command tree: a root command hosting one child command. The
# root mimics the "run + opts" unit the App can now mount at the top level:
# it adds options, sets up, dispatches to the selected child (or runs its own
# default), and tears down.
class ChildCmd(Cmd):
def __init__(self, parent):
super().__init__(parent, 'child', 'A child command')
self.ran = False
self.got_opt = None
def add_arguments(self, parser):
super().add_arguments(parser)
parser.add_argument('--child-opt', default = 'child-default')
async def _run(self, args):
self.ran = True
self.got_opt = args.child_opt
class RootCmd(Cmd):
made = []
def __init__(self, parent):
super().__init__(parent, 'root', 'Root command')
self.child = ChildCmd(self)
self.add_subcommands(self.child)
self.setup = False
self.teardown = False
self.default = False
RootCmd.made.append(self)
def add_arguments(self, parser):
super().add_arguments(parser)
parser.add_argument('--root-opt', default = 'root-default')
async def _run(self, args):
self.setup = True
try:
if hasattr(args, 'func'):
await args.func(args)
else:
self.default = True
finally:
self.teardown = True
# -- App.__init__ builds the parser from sys.argv, so point it at a clean
# command line while constructing the app.
saved_argv = sys.argv
sys.argv = ['jw-pkg-test']
try:
app = App(description = 'Root slot test', root = RootCmd)
finally:
sys.argv = saved_argv
root = RootCmd.made[0]
child = root.child
# -- Parent chain: the child is parented to the root, the root to the app,
# and both resolve their application through the chain.
assert child.parent is root, 'child.parent should be the root'
assert root.parent is app, 'root.parent should be the app'
assert child.app is app, 'child.app should resolve to the app'
assert root.app is app, 'root.app should resolve to the app'
# -- The root's options are registered on the top-level parser, and the child
# is wired as a top-level subcommand.
args = app.parser.parse_args(['--root-opt', 'RV', 'child', '--child-opt', 'CV'])
assert args.root_opt == 'RV', args
assert args.child_opt == 'CV', args
assert hasattr(args, 'func'), 'selecting a subcommand must set the dispatch target'
# -- Dispatching with a selected child runs the child, wrapped by the root's
# setup and teardown.
asyncio.run(app._run(args))
assert child.ran is True, 'the selected child must run'
assert child.got_opt == 'CV', child.got_opt
assert root.setup is True, 'root setup must run'
assert root.teardown is True, 'root teardown must run'
assert root.default is False, 'default must not run when a child is selected'
# -- Dispatching with no subcommand runs the root's default, still wrapped by
# setup and teardown, and does not run the child.
root.setup = root.teardown = root.default = False
child.ran = False
args = app.parser.parse_args(['--root-opt', 'RV2'])
assert not hasattr(args, 'func'), 'no subcommand must not set a dispatch target'
asyncio.run(app._run(args))
assert root.setup is True, 'root setup must run'
assert root.teardown is True, 'root teardown must run'
assert root.default is True, 'root default must run when no child is selected'
assert child.ran is False, 'the child must not run when no child is selected'
print('All App root-slot tests passed')