Backup restore logging drops cog names and raises TypeError for failed libraries

#6792 · open · 1 comments

View on GitHub ↗

potatosips

### What Red version are you using? 3.5.25.dev1 (V3/develop @ 61484f28f7fcfff81309cb2b0b0d5287c921f1ad) ### What were you trying to do? Restore third-party cogs from a Red backup with `redbot-setup restore`. I audited the failure reporting in `redbot.core._downloader._restore_from_backup()` because these messages are the only diagnostic output when a cog cannot be restored. ### What did you expect to happen? Every restore failure should produce a normal log record containing the cog name, repository name, and failed shared-library names. Logging a recovery failure must not itself raise a formatting exception. ### What actually happened? Three log calls in the restore path have missing format arguments. - Lines 989 and 994 log `The commit that %r cog...` without passing `cog.name`, so the literal placeholder is printed instead of the affected cog. - The `failed_libs` branch at lines 1066-1069 has three placeholders but passes only two arguments. Python logging raises `TypeError: not enough arguments for format string`, and the intended failure message is lost. Observed output: WARNING:The commit that %r cog was installed from is unknown ... ERROR:The commit that %r cog was installed from is unknown ... --- Logging error --- TypeError: not enough arguments for format string Message: 'Failed to reinstall shared libraries for %r cog from %r repo: %s' Arguments: ('repo-name', 'lib-name') ### How can we reproduce this error? 1. Check out `V3/develop` at `61484f28f7fcfff81309cb2b0b0d5287c921f1ad` and install the test dependencies on Python 3.11. 2. Attach a `logging.StreamHandler` to `red.core.downloader`. 3. Exercise the two no-commit branches in `_restore_from_backup()`; both records retain a literal `%r`. 4. Return a `CogInstallResult` with a non-empty `failed_libs` tuple from `install_cogs()`. 5. Observe the logging traceback above instead of the intended cog/repository/library diagnostic. The same failure can be isolated with the production format string: log.error( 'Failed to reinstall shared libraries for %r cog from %r repo: %s', 'repo-name', 'lib-name', ) ### Anything else? Environment: Debian Bookworm arm64 container, Python 3.11.15. The unmodified baseline passes `313 passed, 7 skipped`; `pylint --errors-only redbot` independently reports E1206 at line 1066. I searched open and closed issues for the exact messages, backup restore logging errors, failed shared libraries, and missing commit metadata; no duplicate was found. Likely minimal fix: pass `cog.name` to both missing-commit messages, and pass `cog.name`, `cog.repo.name`, then the joined library names to the failed-libraries message. I will add regression coverage for all three formatted records separately.

Comments

potatosips

Implemented and verified separately after filing. ### Fix - Passes the missing cog name to both no-commit warning/error records. - Passes cog name, repository name, and joined library names to the failed-shared-libraries record. - Adds a regression test that drives all three real _restore_from_backup() branches with isolated config, repository, progress, and installer fakes. ### Before/after proof - Fresh unmodified V3/develop at 61484f28f7fcfff81309cb2b0b0d5287c921f1ad plus the regression: **1 failed**, reproducing TypeError: not enough arguments for format string; the other two captured records contain literal %r. - Patched focused regression: **1 passed**. - Patched full suite: **314 passed, 7 skipped**. - python -m compileall -q redbot: passed. - black --check redbot tests: passed; **231 files unchanged**. - pylint --errors-only redbot/core/_downloader/__init__.py: exit 0; the prior E1206 is gone. - git diff --check: passed. - The patch below passed git apply --check in a fresh clone at the exact reported commit. Tested on Debian Bookworm arm64 with Python 3.11.15. No Discord token or live bot connection is required for this restore-path test. ### Apply and verify ```bash git checkout 61484f28f7fcfff81309cb2b0b0d5287c921f1ad git apply red-discordbot-issue-6792-fix.patch pytest -q tests/core/_downloader/test_restore.py pytest -q ``` <details> <summary>Complete apply-ready patch</summary> ```diff diff --git a/redbot/core/_downloader/__init__.py b/redbot/core/_downloader/__init__.py index 03cadc9..48305b1 100644 --- a/redbot/core/_downloader/__init__.py +++ b/redbot/core/_downloader/__init__.py @@ -988,12 +988,14 @@ async def _restore_from_backup() -> None: cog = last_cog_occurrence log.warning( "The commit that %r cog was installed from is unknown" - " - will try to reinstall from latest commit where it's still available." + " - will try to reinstall from latest commit where it's still available.", + cog.name, ) else: log.error( "The commit that %r cog was installed from is unknown" - " and it could not be found in the repo." + " and it could not be found in the repo.", + cog.name, ) continue @@ -1065,6 +1067,7 @@ async def _restore_from_backup() -> None: if install_result.failed_libs: log.error( "Failed to reinstall shared libraries for %r cog from %r repo: %s", + cog.name, cog.repo.name, ", ".join([lib.name for lib in install_result.failed_libs]), ) diff --git a/tests/core/_downloader/test_restore.py b/tests/core/_downloader/test_restore.py new file mode 100644 index 0000000..2de3916 --- /dev/null +++ b/tests/core/_downloader/test_restore.py @@ -0,0 +1,100 @@ +import contextlib +import logging +from types import SimpleNamespace +from unittest.mock import AsyncMock + +from redbot.core import _downloader as downloader + + +class FakeProgress: + def add_task(self, description): + return 1 + + def track(self, sequence, *, task_id): + return sequence + + def update(self, task_id, *, description): + pass + + [email protected] +def fake_detailed_progress(*, unit): + yield FakeProgress() + + +def install_result(*, failed_libs=()): + return downloader.CogInstallResult( + installed_cogs=(), + installed_libs=(), + failed_cogs=(), + failed_libs=failed_libs, + failed_reqs=(), + unavailable_cogs=(), + already_installed=(), + name_already_used=(), + incompatible_python_version=(), + incompatible_bot_version=(), + ) + + +async def test_restore_failure_logs_include_cog_and_repo_names(monkeypatch, tmp_path, caplog): + (tmp_path / "repos.json").write_text("[]", encoding="utf-8") + + fallback_repo = SimpleNamespace( + name="fallback-repo", + get_last_module_occurrence=AsyncMock(), + ) + missing_repo = SimpleNamespace( + name="missing-repo", + get_last_module_occurrence=AsyncMock(return_value=None), + ) + failed_libs_repo = SimpleNamespace(name="failed-libs-repo") + + fallback_cog = SimpleNamespace(name="fallback-cog", commit=None, repo=fallback_repo) + fallback_repo.get_last_module_occurrence.return_value = SimpleNamespace( + name="fallback-cog", + commit="fallback-commit", + disabled=False, + repo=fallback_repo, + ) + missing_cog = SimpleNamespace(name="missing-cog", commit=None, repo=missing_repo) + failed_libs_cog = SimpleNamespace( + name="failed-libs-cog", + commit="failed-libs-commit", + repo=failed_libs_repo, + ) + + async def fake_install_cogs(repo, commit, cog_names): + if cog_names == ["failed-libs-cog"]: + return install_result( + failed_libs=(SimpleNamespace(name="lib-one"), SimpleNamespace(name="lib-two")) + ) + return install_result() + + fake_config = SimpleNamespace( + installed_cogs=SimpleNamespace(clear=AsyncMock()), + installed_libraries=SimpleNamespace(clear=AsyncMock()), + ) + fake_repo_manager = SimpleNamespace(repos_folder=tmp_path / "repos") + + monkeypatch.setattr(downloader, "_config", fake_config, raising=False) + monkeypatch.setattr(downloader, "_repo_manager", fake_repo_manager, raising=False) + monkeypatch.setattr(downloader, "detailed_progress", fake_detailed_progress) + monkeypatch.setattr( + downloader, + "installed_cogs", + AsyncMock(return_value=(fallback_cog, missing_cog, failed_libs_cog)), + ) + monkeypatch.setattr(downloader, "install_cogs", fake_install_cogs) + caplog.set_level(logging.WARNING, logger=downloader.log.name) + + await downloader._restore_from_backup() + + assert caplog.messages == [ + "The commit that 'fallback-cog' cog was installed from is unknown" + " - will try to reinstall from latest commit where it's still available.", + "The commit that 'missing-cog' cog was installed from is unknown" + " and it could not be found in the repo.", + "Failed to reinstall shared libraries for 'failed-libs-cog' cog" + " from 'failed-libs-repo' repo: lib-one, lib-two", + ] ``` </details>