From ce48e7a7eed87df330e5f71fc5e8e17290a35e1f Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Thu, 18 Jan 2024 17:36:21 +0200 Subject: [PATCH] Address issues and enhance logging in the monitor test --- .github/workflows/test-and-release.yml | 8 ++++++++ .idea/dictionaries/pavel.xml | 1 + noxfile.py | 6 ++++++ tests/cmd/monitor.py | 16 +++++++++------- tests/subprocess.py | 3 ++- yakut/__init__.py | 4 ++-- yakut/register.py | 2 +- 7 files changed, 29 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test-and-release.yml b/.github/workflows/test-and-release.yml index 0f1298b..d8a428c 100644 --- a/.github/workflows/test-and-release.yml +++ b/.github/workflows/test-and-release.yml @@ -55,6 +55,14 @@ jobs: env: FORCE_COLOR: 1 + - name: Upload diagnostic snapshot + uses: actions/upload-artifact@v3 + if: always() + with: + name: ${{github.job}} + path: ${{github.workspace}}/ + retention-days: 3 + yakut-release: name: Release Yakut runs-on: ubuntu-latest diff --git a/.idea/dictionaries/pavel.xml b/.idea/dictionaries/pavel.xml index 327d1ec..e765f30 100644 --- a/.idea/dictionaries/pavel.xml +++ b/.idea/dictionaries/pavel.xml @@ -319,6 +319,7 @@ uname undisable undoc + unexplode unhashable unicast unraisableexception diff --git a/noxfile.py b/noxfile.py index 063c64f..065b658 100644 --- a/noxfile.py +++ b/noxfile.py @@ -64,6 +64,12 @@ def test(session): if not (tmp_dir / fn).exists(): (tmp_dir / fn).symlink_to(ROOT_DIR / fn) + if sys.platform.startswith("linux"): + # Enable packet capture for the Python executable. This is necessary for commands that rely on low-level + # network packet capture, such as the Monitor when used with Cyphal/UDP. + # We do it here because the sudo may ask the user for the password; doing that from the suite is inconvenient. + session.run("sudo", "setcap", "cap_net_raw+eip", str(Path(session.bin, "python").resolve()), external=True) + # The directories to test may be overridden if needed when invoking Nox. src_dirs = [(ROOT_DIR / t) for t in (session.posargs or ["yakut", "tests"])] diff --git a/tests/cmd/monitor.py b/tests/cmd/monitor.py index 0874ea1..d22c21b 100755 --- a/tests/cmd/monitor.py +++ b/tests/cmd/monitor.py @@ -12,6 +12,7 @@ import socket import asyncio import itertools +import logging import pytest import pycyphal from pycyphal.transport.udp import UDPTransport @@ -173,8 +174,7 @@ async def _monitor_and_get_last_screen(duration: float, node_id: Optional[int]) assert len(screens) > 1 assert len(screens) < (duration * 0.5 + 10) last_screen = screens[-1] - print("=== LAST SCREEN ===") - print(last_screen) + _logger.info("=== LAST SCREEN ===\n" + last_screen) return last_screen except Exception: # pragma: no cover proc.kill() @@ -262,7 +262,7 @@ def instantiate(info: NodeInfo, node_id: int, mode: int, health: int, vssc: int) nodes[3].make_subscriber(String_1, "null").receive_in_background(subscription_sink) # No publishers. reg_client_a = nodes[1].make_client(uavcan.register.List_1, 1111) reg_client_b = nodes[1].make_client(uavcan.register.List_1, 3210) - print("NODES STARTED") + _logger.info("NODES STARTED") try: for i in itertools.count(): assert await pub.publish(String_1(f"Hello world! This is message number #{i+1}.")) @@ -275,10 +275,10 @@ def instantiate(info: NodeInfo, node_id: int, mode: int, health: int, vssc: int) except (asyncio.TimeoutError, asyncio.CancelledError): # pragma: no cover pass finally: - print("STOPPING THE NODES...") + _logger.info("STOPPING THE NODES...") for n in nodes: n.close() - print("NODES STOPPED") + _logger.info("NODES STOPPED") async def _run_zombie() -> None: @@ -336,7 +336,7 @@ async def _inject_error() -> None: async def _delay(target: Awaitable[None], delay: float, duration: Optional[float] = None) -> None: await asyncio.sleep(delay) - print("LAUNCHING", target) + _logger.info("LAUNCHING", target) try: if duration is None: await target @@ -344,7 +344,7 @@ async def _delay(target: Awaitable[None], delay: float, duration: Optional[float await asyncio.wait_for(target, duration) except (asyncio.TimeoutError, asyncio.CancelledError): # pragma: no cover pass - print("FINISHED", target) + _logger.info("FINISHED", target) async def _main() -> None: # pragma: no cover @@ -367,5 +367,7 @@ async def _main() -> None: # pragma: no cover ) +_logger = logging.getLogger(__name__) + if __name__ == "__main__": # pragma: no cover asyncio.run(_main()) diff --git a/tests/subprocess.py b/tests/subprocess.py index ce150e9..8757605 100644 --- a/tests/subprocess.py +++ b/tests/subprocess.py @@ -163,10 +163,11 @@ def wait( self._inferior.communicate(timeout=timeout) stdout = _read_stream(self._stdout) stderr = _read_stream(self._stderr) + exit_code = int(self._inferior.returncode) if log: + _logger.debug("PID %d exit code: %d", self.pid, exit_code) _logger.debug("PID %d stdout:\n%s", self.pid, stdout) _logger.debug("PID %d stderr:\n%s", self.pid, stderr) - exit_code = int(self._inferior.returncode) return exit_code, stdout, stderr def kill(self) -> None: diff --git a/yakut/__init__.py b/yakut/__init__.py index e85c83a..1acb6e4 100644 --- a/yakut/__init__.py +++ b/yakut/__init__.py @@ -3,9 +3,9 @@ # Author: Pavel Kirienko import typing -from importlib.resources import read_text as _read_text +from importlib.resources import files as _files -__version__: str = _read_text(__name__, "VERSION", encoding="utf8").strip() +__version__: str = (_files(__name__) / "VERSION").read_text(encoding="utf8").strip() __version_info__: typing.Tuple[int, ...] = tuple(map(int, __version__.split(".")[:3])) __author__ = "OpenCyphal" __email__ = "consortium@opencyphal.org" diff --git a/yakut/register.py b/yakut/register.py index 6f600ca..aa46db8 100644 --- a/yakut/register.py +++ b/yakut/register.py @@ -70,7 +70,7 @@ async def fetch_registers( def unexplode_value(xpl: Any, prototype: Optional["Value"] = None) -> Optional["Value"]: """ - Reverse the effect of :func:`explode`. + Reverse the effect of :func:`explode_value`. Returns None if the exploded form is invalid or not applicable to the prototype. Some simplified exploded forms can be unexploded only if the prototype is given because simplification erases type information.