py-topdir.mk: Add test tree to default check roots #96
6 changed files with 65 additions and 35 deletions
|
|
@ -9,6 +9,9 @@ MYPY_PATH_DIRECTIVE = mypy_path = "$(MYPY_CONFIG_PATH)"
|
|||
|
||||
ifndef PY_CHECK_ROOTS
|
||||
PY_CHECK_ROOTS = $(wildcard $(TOPDIR)/src $(TOPDIR)/tools)
|
||||
ifneq ($(wildcard dirs-all.done),)
|
||||
PY_CHECK_ROOTS += $(wildcard $(TOPDIR)/test)
|
||||
endif
|
||||
endif
|
||||
|
||||
all:
|
||||
|
|
|
|||
|
|
@ -35,17 +35,25 @@ module_path()
|
|||
cmd_create_conftest_py()
|
||||
{
|
||||
cat <<-'EOT'
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
def pytest_configure(config):
|
||||
import _pytest.terminal
|
||||
import _pytest.timing as timing
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
# orig = _pytest.terminal.TerminalReporter.pytest_sessionstart
|
||||
from _pytest.terminal import TerminalReporter
|
||||
from _pytest.timing import Instant
|
||||
|
||||
def patched(self, session):
|
||||
if TYPE_CHECKING:
|
||||
from _pytest.config import Config
|
||||
from _pytest.main import Session
|
||||
|
||||
def pytest_configure(config: Config) -> None:
|
||||
"""Print a banner centered in the terminal when the session starts."""
|
||||
|
||||
def patched(self: TerminalReporter, session: Session) -> None:
|
||||
width = shutil.get_terminal_size().columns
|
||||
d = os.path.basename(os.getcwd())
|
||||
text = f"Running pytest for {d}"
|
||||
|
|
@ -59,9 +67,9 @@ cmd_create_conftest_py()
|
|||
banner = text
|
||||
print(banner, file = sys.stderr)
|
||||
self._session = session
|
||||
self._session_start = timing.Instant()
|
||||
self._session_start = Instant()
|
||||
|
||||
_pytest.terminal.TerminalReporter.pytest_sessionstart = patched
|
||||
TerminalReporter.pytest_sessionstart = patched # type: ignore[method-assign]
|
||||
EOT
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
import asyncio
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from jw.pkg.lib.App import App
|
||||
from jw.pkg.lib.Cmd import Cmd
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from argparse import ArgumentParser, Namespace
|
||||
|
||||
# -- 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
|
||||
|
|
@ -12,24 +18,26 @@ from jw.pkg.lib.Cmd import Cmd
|
|||
|
||||
class ChildCmd(Cmd):
|
||||
|
||||
def __init__(self, parent):
|
||||
def __init__(self, parent: App | Cmd) -> None:
|
||||
super().__init__(parent, 'child', 'A child command')
|
||||
self.ran = False
|
||||
self.got_opt = None
|
||||
|
||||
def add_arguments(self, parser):
|
||||
@override
|
||||
def add_arguments(self, parser: ArgumentParser) -> None:
|
||||
super().add_arguments(parser)
|
||||
parser.add_argument('--child-opt', default = 'child-default')
|
||||
|
||||
async def _run(self, args):
|
||||
@override
|
||||
async def _run(self, args: Namespace) -> None:
|
||||
self.ran = True
|
||||
self.got_opt = args.child_opt
|
||||
|
||||
class RootCmd(Cmd):
|
||||
|
||||
made = []
|
||||
made: list['RootCmd'] = []
|
||||
|
||||
def __init__(self, parent):
|
||||
def __init__(self, parent: App | Cmd) -> None:
|
||||
super().__init__(parent, 'root', 'Root command')
|
||||
self.child = ChildCmd(self)
|
||||
self.add_subcommands(self.child)
|
||||
|
|
@ -38,11 +46,13 @@ class RootCmd(Cmd):
|
|||
self.default = False
|
||||
RootCmd.made.append(self)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
@override
|
||||
def add_arguments(self, parser: ArgumentParser) -> None:
|
||||
super().add_arguments(parser)
|
||||
parser.add_argument('--root-opt', default = 'root-default')
|
||||
|
||||
async def _run(self, args):
|
||||
@override
|
||||
async def _run(self, args: Namespace) -> None:
|
||||
self.setup = True
|
||||
try:
|
||||
if hasattr(args, 'func'):
|
||||
|
|
|
|||
|
|
@ -1,18 +1,27 @@
|
|||
import asyncio
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from jw.pkg.lib.Cmd import Cmd
|
||||
from jw.pkg.lib.ExecApp import ExecApp
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from argparse import Namespace
|
||||
|
||||
from jw.pkg.lib.App import App
|
||||
|
||||
# -- A minimal root command so the app can be built without a command tree.
|
||||
|
||||
class RootCmd(Cmd):
|
||||
|
||||
def __init__(self, parent):
|
||||
def __init__(self, parent: App | Cmd) -> None:
|
||||
super().__init__(parent, 'root', 'Root command')
|
||||
|
||||
async def _run(self, args):
|
||||
@override
|
||||
async def _run(self, args: Namespace) -> None:
|
||||
pass
|
||||
|
||||
# -- Counts the close() calls made by App.__aexit__() when the async context
|
||||
|
|
@ -22,7 +31,8 @@ class RecordingExecApp(ExecApp):
|
|||
|
||||
closed = 0
|
||||
|
||||
def close(self):
|
||||
@override
|
||||
def close(self) -> None:
|
||||
RecordingExecApp.closed += 1
|
||||
super().close()
|
||||
|
||||
|
|
@ -38,16 +48,14 @@ finally:
|
|||
|
||||
# -- 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', ())
|
||||
]
|
||||
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 def exit_context() -> None:
|
||||
async with app:
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import tempfile
|
||||
|
||||
from jw.pkg.lib.ProjectConf import ProjectConf
|
||||
|
||||
# -- Helper: create temp file and read it --
|
||||
|
|
@ -13,7 +14,7 @@ def _load(text: str) -> ProjectConf:
|
|||
_tmpfiles.append(f.name)
|
||||
return ProjectConf.read(f.name)
|
||||
|
||||
def _cleanup():
|
||||
def _cleanup() -> None:
|
||||
for _p in _tmpfiles:
|
||||
os.unlink(_p)
|
||||
del _p
|
||||
|
|
|
|||
|
|
@ -233,16 +233,16 @@ for bad in ['', 'foo =', ' = 1.0']:
|
|||
# Operators outside the supported set are rejected when parsed,
|
||||
# not folded into the package name or deferred to expansion
|
||||
for bad in [
|
||||
'foo ~= 1.0',
|
||||
'foo~=1.0',
|
||||
'foo ~ 1.0',
|
||||
'foo != 1.0',
|
||||
'foo!=1.0',
|
||||
'foo == 1.0',
|
||||
'foo==1.0',
|
||||
'foo === 1.0',
|
||||
'foo << 1.0',
|
||||
'foo >> 1.0',
|
||||
'foo ~= 1.0',
|
||||
'foo~=1.0',
|
||||
'foo ~ 1.0',
|
||||
'foo != 1.0',
|
||||
'foo!=1.0',
|
||||
'foo == 1.0',
|
||||
'foo==1.0',
|
||||
'foo === 1.0',
|
||||
'foo << 1.0',
|
||||
'foo >> 1.0',
|
||||
]:
|
||||
try:
|
||||
Dependency(bad).full_name
|
||||
|
|
|
|||
Loading…
Reference in a new issue