From 3ac3aff99723d8f2b1e4b05083acff1cd33d229b Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Sat, 25 Apr 2026 07:45:14 +0200 Subject: [PATCH] lib: Fix silent assertitons There are a couple of assert statements in the codebase which can make jw-pkg fail without any detail whatsoever if --backtrace is not specified, fix that. Signed-off-by: Jan Lindemann --- src/python/jw/pkg/cmds/projects/CmdListRepos.py | 2 +- src/python/jw/pkg/lib/App.py | 4 +++- src/python/jw/pkg/lib/Cmd.py | 2 +- src/python/jw/pkg/lib/Distro.py | 6 ++++-- src/python/jw/pkg/lib/ExecContext.py | 9 +++++---- src/python/jw/pkg/lib/FileContext.py | 5 +++-- src/python/jw/pkg/lib/util.py | 3 ++- 7 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/python/jw/pkg/cmds/projects/CmdListRepos.py b/src/python/jw/pkg/cmds/projects/CmdListRepos.py index 799f68bd..56340d64 100644 --- a/src/python/jw/pkg/cmds/projects/CmdListRepos.py +++ b/src/python/jw/pkg/cmds/projects/CmdListRepos.py @@ -59,7 +59,7 @@ class CmdListRepos(Cmd): # export '-H', 'X-GitHub-Api-Version: 2022-11-28', ] if password is not None: - assert username is not None + assert username is not None, f'Assertion failed: username is empty but password isn\'t for "{args.base_url}"' cmd_input = (f'-u {username}:{password}').encode('utf-8') curl_args.extend(['-K-']) curl_args.append(f'https://api.github.com/users/{args.from_owner}/repos') diff --git a/src/python/jw/pkg/lib/App.py b/src/python/jw/pkg/lib/App.py index 6413cc8e..3755d2f2 100644 --- a/src/python/jw/pkg/lib/App.py +++ b/src/python/jw/pkg/lib/App.py @@ -145,7 +145,9 @@ class App: # export except Exception as e: log(ERR, 'Failed: {}'.format(repr(e) if self.__back_trace else str(e))) exit_status = 1 - if self.__back_trace: + # AssertionErrors are programming errors, hence a programmer should + # get a chance to figure it out + if self.__back_trace or isinstance(e, AssertionError): raise finally: if pr is not None: diff --git a/src/python/jw/pkg/lib/Cmd.py b/src/python/jw/pkg/lib/Cmd.py index c4bf8739..a3c6d6f9 100644 --- a/src/python/jw/pkg/lib/Cmd.py +++ b/src/python/jw/pkg/lib/Cmd.py @@ -48,7 +48,7 @@ class Cmd(abc.ABC): # export if isinstance(parent, App): self.__app = parent break - assert parent != parent.__parent + assert parent != parent.__parent, f'Assertion failed: Parent mismatch' parent = parent.__parent return self.__app diff --git a/src/python/jw/pkg/lib/Distro.py b/src/python/jw/pkg/lib/Distro.py index b2a3cc47..10b6d5e7 100644 --- a/src/python/jw/pkg/lib/Distro.py +++ b/src/python/jw/pkg/lib/Distro.py @@ -26,8 +26,10 @@ class Distro(abc.ABC): os_release_str: str|None=None, default_pkg_filter: PackageFilter|None=None, ) -> None: - assert ec is not None - assert id is not None + if id is None: + raise ValueError(f'Tried to instaniate Distro without id') + if ec is None: + raise ValueError(f'Tried to instaniate Distro "{id}" without execution context') self.__exec_context = ec self.__id: str|None = None self.__os_release_str: str|None = os_release_str diff --git a/src/python/jw/pkg/lib/ExecContext.py b/src/python/jw/pkg/lib/ExecContext.py index 8cd7a65e..b4b5e015 100644 --- a/src/python/jw/pkg/lib/ExecContext.py +++ b/src/python/jw/pkg/lib/ExecContext.py @@ -149,7 +149,7 @@ class ExecContext(Base): if interactive is None: interactive = sys.stdin.isatty() self.__cmd_input = None - assert interactive in [ True, False ] + assert interactive in [ True, False ], f'Invalid: interactive = {invalid}' self.__interactive = interactive self.__cmd_input = cmd_input if not isinstance(cmd_input, InputMode) else None @@ -285,7 +285,7 @@ class ExecContext(Base): # Note that in the calls to the wrapped method, cmd_input == None can # be returned by CallContext and is very much allowed - assert cmd_input is not None + assert cmd_input is not None, 'Invalid: cmd_input is None' ret = Result(None, None, 1) with self.CallContext(self, title=title, cmd=cmd, cmd_input=cmd_input, mod_env=mod_env, wd=wd, @@ -379,7 +379,7 @@ class ExecContext(Base): # Note that in the calls to the wrapped method, cmd_input == None can # be returned by CallContext and is very much allowed - assert cmd_input is not None + assert cmd_input is not None, 'Invalid: cmd_input is None' ret = Result(None, None, 1) with self.CallContext(self, title=title, cmd=cmd, cmd_input=cmd_input, @@ -578,7 +578,8 @@ class ExecContext(Base): _raise_stat_error(path, result.stderr, result.status) async def _chown(self, path: str, owner: str|None, group: str|None) -> None: - assert owner is not None or group is not None + if owner is None and group is None: + raise ValueError(f'Tried to chown("{path}") without owner and group') if group is None: ownership = owner elif owner is None: diff --git a/src/python/jw/pkg/lib/FileContext.py b/src/python/jw/pkg/lib/FileContext.py index 0a952b4f..c2ceba08 100644 --- a/src/python/jw/pkg/lib/FileContext.py +++ b/src/python/jw/pkg/lib/FileContext.py @@ -38,7 +38,8 @@ class FileContext(abc.ABC): self.__in_pipe = in_pipe self.__out_pipe = out_pipe self.__open_count = 0 - assert verbose_default is not None + if not verbose_default in [True, False]: + raise ValueError(f'Tried to instantiate FileContext with verbose_default = "{verbose_default}"') async def __aenter__(self): await self.open() @@ -87,7 +88,7 @@ class FileContext(abc.ABC): if self.__open_count == 1: await self._close() self.__open_count -= 1 - assert self.__open_count >= 0 + assert self.__open_count >= 0, f'Closed file context "{self.__uri}" more often than opened' @classmethod def schema_from_uri(cls, uri: str) -> str: diff --git a/src/python/jw/pkg/lib/util.py b/src/python/jw/pkg/lib/util.py index e4f5a8c0..33074f1e 100644 --- a/src/python/jw/pkg/lib/util.py +++ b/src/python/jw/pkg/lib/util.py @@ -70,7 +70,8 @@ async def run_curl(args: list[str], parse_json: bool=False, wd=None, throw=None, return ret, stderr, status async def run_askpass(askpass_env: list[str], key: AskpassKey, host: str|None=None, ec: ExecContext|None=None): - assert host is None # Currently unsupported + if host is not None: # Currently unsupported + raise NotImplementedError(f'Tried to run askpass with host "{host}"') for var in askpass_env: exe = os.getenv(var) if exe is None: