jw-pkg/test/unit/python/jw/pkg/lib/ExecApp/test.py
Jan Lindemann 42f6c1d14b
All checks were successful
CI / Packaging - Kali Linux (pull_request) Successful in 10m28s
CI / Packaging - OpenSUSE Tumbleweed (pull_request) Successful in 4m14s
CI / Packaging test (pull_request) Successful in 0s
lib.ExecApp: Add class
ExecApp is a ready-made base class for applications that operate
through an ExecContext: it adds the --interactive, --verbose and
--target options, exposes interactive, verbose and exec_context
properties, and closes the exec context when the async context
manager exits.

The code for that has lived in jw.pkg.App code before, which now
inherits from ExecApp.

A fix along the way: __aexit__() closes the exec context and then chains
to super().__aexit__(), so App.close() runs when the async context
manager exits. Before the change, exiting the async context left the app
unclosed; close() ran only on the run() path. Add a unit test that
builds an ExecApp with a root command and asserts that close() runs on
context exit and that the exec options are registered.

The exec options are now registered before App's own options,
which moves them up in the rendered --help output. Update the
golden file of the help integration test to match.

Signed-off-by: Jan Lindemann <jan@janware.com>
Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.84.2
2026-08-22 17:17:09 +02:00

57 lines
1.4 KiB
Python

import asyncio
import sys
from jw.pkg.lib.Cmd import Cmd
from jw.pkg.lib.ExecApp import ExecApp
# -- A minimal root command so the app can be built without a command tree.
class RootCmd(Cmd):
def __init__(self, parent):
super().__init__(parent, 'root', 'Root command')
async def _run(self, args):
pass
# -- Counts the close() calls made by App.__aexit__() when the async context
# manager exits.
class RecordingExecApp(ExecApp):
closed = 0
def close(self):
RecordingExecApp.closed += 1
super().close()
# -- 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 = RecordingExecApp(description = 'ExecApp test', root = RootCmd)
finally:
sys.argv = saved_argv
# -- ExecApp registers the exec-related options on the top-level parser.
opts = [
o for a in app.parser._actions for o in getattr(a, 'option_strings', ())
]
for opt in ('--interactive', '--verbose', '--target'):
assert opt in opts, f'{opt} must be registered by ExecApp'
# -- Exiting the async context must close the app, through the
# App.__aexit__() that ExecApp.__aexit__() chains to.
async def exit_context():
async with app:
pass
asyncio.run(exit_context())
assert RecordingExecApp.closed == 1, 'exiting the app context must close the app'
print('All ExecApp tests passed')