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
|
ifndef PY_CHECK_ROOTS
|
||||||
PY_CHECK_ROOTS = $(wildcard $(TOPDIR)/src $(TOPDIR)/tools)
|
PY_CHECK_ROOTS = $(wildcard $(TOPDIR)/src $(TOPDIR)/tools)
|
||||||
|
ifneq ($(wildcard dirs-all.done),)
|
||||||
|
PY_CHECK_ROOTS += $(wildcard $(TOPDIR)/test)
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
all:
|
all:
|
||||||
|
|
|
||||||
|
|
@ -35,17 +35,25 @@ module_path()
|
||||||
cmd_create_conftest_py()
|
cmd_create_conftest_py()
|
||||||
{
|
{
|
||||||
cat <<-'EOT'
|
cat <<-'EOT'
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
|
|
||||||
def pytest_configure(config):
|
from typing import TYPE_CHECKING
|
||||||
import _pytest.terminal
|
|
||||||
import _pytest.timing as timing
|
|
||||||
|
|
||||||
# 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
|
width = shutil.get_terminal_size().columns
|
||||||
d = os.path.basename(os.getcwd())
|
d = os.path.basename(os.getcwd())
|
||||||
text = f"Running pytest for {d}"
|
text = f"Running pytest for {d}"
|
||||||
|
|
@ -59,9 +67,9 @@ cmd_create_conftest_py()
|
||||||
banner = text
|
banner = text
|
||||||
print(banner, file = sys.stderr)
|
print(banner, file = sys.stderr)
|
||||||
self._session = session
|
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
|
EOT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
import asyncio
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, override
|
||||||
|
|
||||||
from jw.pkg.lib.App import App
|
from jw.pkg.lib.App import App
|
||||||
from jw.pkg.lib.Cmd import Cmd
|
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
|
# -- 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:
|
# 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
|
# 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):
|
class ChildCmd(Cmd):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent: App | Cmd) -> None:
|
||||||
super().__init__(parent, 'child', 'A child command')
|
super().__init__(parent, 'child', 'A child command')
|
||||||
self.ran = False
|
self.ran = False
|
||||||
self.got_opt = None
|
self.got_opt = None
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
@override
|
||||||
|
def add_arguments(self, parser: ArgumentParser) -> None:
|
||||||
super().add_arguments(parser)
|
super().add_arguments(parser)
|
||||||
parser.add_argument('--child-opt', default = 'child-default')
|
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.ran = True
|
||||||
self.got_opt = args.child_opt
|
self.got_opt = args.child_opt
|
||||||
|
|
||||||
class RootCmd(Cmd):
|
class RootCmd(Cmd):
|
||||||
|
|
||||||
made = []
|
made: list['RootCmd'] = []
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent: App | Cmd) -> None:
|
||||||
super().__init__(parent, 'root', 'Root command')
|
super().__init__(parent, 'root', 'Root command')
|
||||||
self.child = ChildCmd(self)
|
self.child = ChildCmd(self)
|
||||||
self.add_subcommands(self.child)
|
self.add_subcommands(self.child)
|
||||||
|
|
@ -38,11 +46,13 @@ class RootCmd(Cmd):
|
||||||
self.default = False
|
self.default = False
|
||||||
RootCmd.made.append(self)
|
RootCmd.made.append(self)
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
@override
|
||||||
|
def add_arguments(self, parser: ArgumentParser) -> None:
|
||||||
super().add_arguments(parser)
|
super().add_arguments(parser)
|
||||||
parser.add_argument('--root-opt', default = 'root-default')
|
parser.add_argument('--root-opt', default = 'root-default')
|
||||||
|
|
||||||
async def _run(self, args):
|
@override
|
||||||
|
async def _run(self, args: Namespace) -> None:
|
||||||
self.setup = True
|
self.setup = True
|
||||||
try:
|
try:
|
||||||
if hasattr(args, 'func'):
|
if hasattr(args, 'func'):
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,27 @@
|
||||||
import asyncio
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, override
|
||||||
|
|
||||||
from jw.pkg.lib.Cmd import Cmd
|
from jw.pkg.lib.Cmd import Cmd
|
||||||
from jw.pkg.lib.ExecApp import ExecApp
|
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.
|
# -- A minimal root command so the app can be built without a command tree.
|
||||||
|
|
||||||
class RootCmd(Cmd):
|
class RootCmd(Cmd):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent: App | Cmd) -> None:
|
||||||
super().__init__(parent, 'root', 'Root command')
|
super().__init__(parent, 'root', 'Root command')
|
||||||
|
|
||||||
async def _run(self, args):
|
@override
|
||||||
|
async def _run(self, args: Namespace) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# -- Counts the close() calls made by App.__aexit__() when the async context
|
# -- Counts the close() calls made by App.__aexit__() when the async context
|
||||||
|
|
@ -22,7 +31,8 @@ class RecordingExecApp(ExecApp):
|
||||||
|
|
||||||
closed = 0
|
closed = 0
|
||||||
|
|
||||||
def close(self):
|
@override
|
||||||
|
def close(self) -> None:
|
||||||
RecordingExecApp.closed += 1
|
RecordingExecApp.closed += 1
|
||||||
super().close()
|
super().close()
|
||||||
|
|
||||||
|
|
@ -38,16 +48,14 @@ finally:
|
||||||
|
|
||||||
# -- ExecApp registers the exec-related options on the top-level parser.
|
# -- ExecApp registers the exec-related options on the top-level parser.
|
||||||
|
|
||||||
opts = [
|
opts = [o for a in app.parser._actions for o in getattr(a, 'option_strings', ())]
|
||||||
o for a in app.parser._actions for o in getattr(a, 'option_strings', ())
|
|
||||||
]
|
|
||||||
for opt in ('--interactive', '--verbose', '--target'):
|
for opt in ('--interactive', '--verbose', '--target'):
|
||||||
assert opt in opts, f'{opt} must be registered by ExecApp'
|
assert opt in opts, f'{opt} must be registered by ExecApp'
|
||||||
|
|
||||||
# -- Exiting the async context must close the app, through the
|
# -- Exiting the async context must close the app, through the
|
||||||
# App.__aexit__() that ExecApp.__aexit__() chains to.
|
# App.__aexit__() that ExecApp.__aexit__() chains to.
|
||||||
|
|
||||||
async def exit_context():
|
async def exit_context() -> None:
|
||||||
async with app:
|
async with app:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from jw.pkg.lib.ProjectConf import ProjectConf
|
from jw.pkg.lib.ProjectConf import ProjectConf
|
||||||
|
|
||||||
# -- Helper: create temp file and read it --
|
# -- Helper: create temp file and read it --
|
||||||
|
|
@ -13,7 +14,7 @@ def _load(text: str) -> ProjectConf:
|
||||||
_tmpfiles.append(f.name)
|
_tmpfiles.append(f.name)
|
||||||
return ProjectConf.read(f.name)
|
return ProjectConf.read(f.name)
|
||||||
|
|
||||||
def _cleanup():
|
def _cleanup() -> None:
|
||||||
for _p in _tmpfiles:
|
for _p in _tmpfiles:
|
||||||
os.unlink(_p)
|
os.unlink(_p)
|
||||||
del _p
|
del _p
|
||||||
|
|
|
||||||
|
|
@ -233,16 +233,16 @@ for bad in ['', 'foo =', ' = 1.0']:
|
||||||
# Operators outside the supported set are rejected when parsed,
|
# Operators outside the supported set are rejected when parsed,
|
||||||
# not folded into the package name or deferred to expansion
|
# not folded into the package name or deferred to expansion
|
||||||
for bad in [
|
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:
|
try:
|
||||||
Dependency(bad).full_name
|
Dependency(bad).full_name
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue