Review gate: PR 820 (CI/CD pipeline + addon-dep detector)#20
Merged
eduralph merged 18 commits intoMay 24, 2026
Merged
Conversation
Introduce GitHub Actions CI inside a shared Docker image on ghcr.io, plus a native Windows runner for cross-platform unit-test coverage, and a shared unittest harness with Gramps-backed fixtures that verifies every addon registers, loads, and exposes valid plugin metadata. CI infrastructure ----------------- - .github/docker/gramps-ci/Dockerfile — Python 3.12 + Gramps 6.0 (pip) + PyGObject + GTK typelibs + xvfb/xauth + ruff, dbf, intltool, gettext, git. GTK lives in the base so addon modules that do `from gi.repository import Gtk` at load time are importable; xvfb and xauth are bundled for tests that actually render. - .github/workflows/docker-build.yml — rebuilds the image on .github/docker/** changes or via workflow_dispatch. - .github/workflows/ci.yml — seven jobs: lint (ruff E9/F63/F7/F82 + trailing whitespace), addon-structure (every addon has po/template.pot), compile-check (py_compile on every .py), unit-test-linux (container), unit-test-windows (native, conda+pip), integration-test (container with --init so xvfb-run does not hang), build (make.py gramps60 build all). - .github/environment.yml — hybrid conda+pip env for Windows. Gramps is not on conda-forge, so pygobject/gtk3 come from conda and gramps/orjson/dbf come from pip. Shared test harness ------------------- - tests/__init__.py — GPL header. - tests/gramps_test_env.py — sys.path / GRAMPS_RESOURCES bootstrap and two unittest base classes: GrampsTestCase (session-cached plugin manager + registry via setUpClass) and GrampsDbTestCase (same plus a fresh in-memory SQLite DB per test). - tests/test_plugin_registration.py — four unittest.TestCase classes covering plugin registration, subprocess-isolated module loading (crash-safe), required metadata (gramps_target_version=6.0, valid id/name/version), and import/export entry-function smoke tests. Gate policy ----------- All seven jobs run on every push and PR. Four are marked continue-on-error: true so they surface issues without blocking merges while the existing tree is cleaned up: - lint (~79 pre-existing ruff E9/F63/F7/F82 errors) - addon-structure (4 addons missing po/template.pot) - unit-test-linux (some addon test modules fail to import today) - unit-test-windows (same) compile-check, integration-test, and build are blocking from day one. Each non-blocking gate will be flipped to blocking in the same follow-up PR that fixes its underlying issues, so the tightening is incremental and visible in history. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Dockerfile bakes in only `dbf`, but addons declare a wider set of Python deps in their .gpr.py `requires_mod` lists (networkx, psycopg2, pygraphviz, lxml, svgwrite, boto3, litellm, life_line_chart, psycopg). Without these installed, per-addon unit tests and the plugin- registration subprocess load fail with ImportError/NameError. Add a pre-test step to unit-test-linux, unit-test-windows, and integration-test that globs every *.gpr.py, extracts the requires_mod union via ast.literal_eval, and pip-installs each package one at a time. Per-package install (not batched) keeps a single build failure (pygraphviz without graphviz-dev, psycopg2 without libpq-dev) from aborting the rest — the affected addon's tests will skip or fail in isolation without blocking others. Mirrors Gramps' Addon Manager install path (gramps/gui/plug/_windows.py __on_install_clicked → req.install → gen/utils/requirements.py), keeping .gpr.py files as the single source of truth for addon deps. New addon deps do not need a parallel update to the Dockerfile or this workflow.
With ci.yml's auto-derive step in place (previous commit), dbf is installed at CI runtime from TMGimporter's .gpr.py requires_mod list. Keeping it baked into the Dockerfile and environment.yml in parallel would defeat the "single source of truth = .gpr.py" goal and drift the moment a new addon declares an additional dep. Remove dbf from both; leave the stable base (PyGObject, pycairo, Gramps, orjson, ruff) since those are not addon deps. Add a comment pointing readers at the auto-derive step so future edits do not re-bake runtime deps back in.
Root cause of "Unit Tests (Linux)" and "Integration Tests (Gramps)"
failures was not broken test modules — the steps never invoked
unittest. The container's default shell is /bin/sh (dash on
python:3.12-slim), and the inline scripts use bash-only parameter
expansions (${f%.py}, ${mod//\//.}) to build the dotted module list.
Dash fails with "Bad substitution" on the first such line; the rest
of the script never runs. continue-on-error: true masked this as a
generic job failure for two CI rounds.
Add "shell: bash" explicitly to:
- unit-test-linux / Run per-addon unit tests (bashisms)
- integration-test / Run per-addon integration tests (bashisms)
- integration-test / Run plugin registration tests (no bashisms today,
but consistent and future-proof)
Compile Check already sets shell: bash. Windows jobs inherit bash
via defaults.run at the job level. No other steps affected.
The Windows unit-test job hung on TMGimporter's DB-backed tests because
make_database("sqlite").load(":memory:", None) deadlocks under the
conda-forge GTK + pip Gramps combination. Rather than patch the hang,
introduce a filename convention so per-addon authors can declare OS
scope up front:
test_*.py general (every OS)
test_linux_*.py Linux-only
test_windows_*.py Windows-only
test_integration_*.py Linux-only, full-pipeline/DB-backed (pre-existing)
unit-test-linux skips test_windows_* and test_integration_*;
unit-test-windows skips test_linux_* and test_integration_*.
Applied to TMGimporter: the 13 DB-backed classes in tests/test_libtmg.py
move to tests/test_linux_libtmg.py (along with the _Rec/_table/_make_db/
_add_person/_MockUser helpers they use). The 7 pure-logic classes
(TestStripTmgCodes, TestTmgDateToGrampsDate, TestNumTo{Month,Date},
TestParseDate, TestRepoTypeFromName, TestUrlFromName) stay in
test_libtmg.py and will run on every OS.
Locally all 175 tests still pass via run-addon-unit.sh TMGimporter.
EditExifMetadata and PhotoTaggingGramplet both do
`from gi.repository import GExiv2` at module load. The CI image
installs several gir1.2-* typelibs (glib, gtk, pango, gdkpixbuf,
atk) but not gexiv2, so plugin-registration smoke tests on those
addons fail with:
cannot import name GExiv2, introspection typelib not found
Add the missing apt package. GObject Introspection typelibs come
from system apt packages, not pip — they cannot be auto-derived
from requires_gi the way requires_mod is.
Companion to addons-source PRs gramps-project#878 (EditExifMetadata declares
requires_gi=[(GExiv2, 0.10)] alongside requires_mod=[Pillow]) and
gramps-project#880 (PhotoTaggingGramplet declares requires_gi=[(GExiv2, 0.10)]).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`git grep '[ \t]$'` in BRE/ERE mode interprets `[ \t]` as the character
class { space, backslash, 't' } — i.e. the bracket contains a literal
't', not a tab. The check therefore matches every line that ends in
't', '\', '[', ']' or a space, fires on ~3,000 false-positive lines
across the tree, and would block any PR touching such a file even
when no real trailing whitespace exists.
Switch to PCRE via `-P` so `\t` means tab. Also tighten to `[ \t]+$`
to make intent obvious (no behavioural change for the match decision).
Verified on the live tree:
- BRE: matches 3182 lines across ~430 files (mostly false positives).
- PCRE: matches 597 lines across 21 files — real trailing whitespace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Gary Griffin's request on PR gramps-project#820: addons whose every register() call sets include_in_listing=False are not built or released by make.py, so the CI does not gate on them. Across the current addon tree this skips 11 directories (CheckPlaceTitles, DetId, FaceDetection, HtmlView, MongoDB, PhpGedView, Query, RebuildTypes, SourceIndex, SourceReferences, WordleGramplet) — the ones Gary called out in his comment, modulo case differences and a couple of names that no longer exist as directories. The check is inlined at each iteration site rather than centralised in a helper script: a small shell function (is_active) per relevant job step, plus an include_in_listing filter in the four addon-iterating tests in tests/test_plugin_registration.py. Repeating ~7 lines of bash across six job steps was preferred to introducing a separate .github/scripts/ helper file, mirroring the pattern the workflow already uses for the requires_mod auto-derive step. To re-enable CI gating for an addon, set include_in_listing=True on at least one register() call in its descriptor (or remove the field entirely — Gramps' default is True). TMGimporter, for example, stays gated because its main register() carries True even though three conditional ones use False. Affected steps: - lint (ruff): builds --exclude args from the skipped list - addon-structure: skips the po/template.pot check - compile-check (py_compile): skips files under skipped dirs - unit-test-linux / unit-test-windows: skips test modules - integration-test (per-addon): skips test modules The integration-test plugin-registration smoke step inherits the filter via the test file changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The auto-derive step parses requires_mod from every .gpr.py and pip- installs each name, which validates the install-side of the contract. But Gramps' end-user dep gate (gen/utils/requirements.py:check_mod) calls find_spec() — the importable name, not the PyPI name. For most addons the two coincide; for Pillow they don't, so requires_mod= ["Pillow"] pip-installs cleanly, CI stays green, and the Addon Manager still rejects the addon on every end-user install. Adds a follow-up step after each "Install addon runtime deps" block (one per job: unit-test-linux, unit-test-windows, integration-test). For each declared requires_mod name: if pip-show confirms the package is installed but find_spec returns None, the declaration is wrong. Pip-install failures are skipped, so missing system deps (graphviz-dev for pygraphviz, libpq-dev for psycopg2) don't cause false positives. Verified locally: catches requires_mod=["Pillow"] with exit 1 and a GitHub annotation; current addons-source tree (all 9 declared mods) passes with no false positives. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ps60 Drops the lint job's continue-on-error: true (and its TODO comment), making ruff E9/F63/F7/F82 a blocking gate per the comment's own instruction. The ruff backlog the comment guarded against was cleared by 24 PRs (gramps-project#843 + gramps-project#847-gramps-project#869) merged into maintenance/gramps60 between 2026-05-12 and 2026-05-18. Verified locally: pipx run ruff check --select=E9,F63,F7,F82 \ --no-fix --exclude='*.gpr.py' . on `gramps-project/addons-source:maintenance/gramps60` reports "All checks passed!". Other continue-on-error gates in this workflow (addon-structure, compile-check, integration) each guard their own backlogs and stay non-blocking until those clear separately. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ared Drops continue-on-error: true (and the TODO comments) from both unit-test jobs. They were guarding against "currently-broken addon unit modules (import failures, stale API usage)" that have since been fixed by the merged plugin-registration / dep-declaration round (gramps-project#875 WordleGramplet, gramps-project#876 SourceReferences, gramps-project#878 EditExifMetadata, gramps-project#879 MongoDB, gramps-project#880 PhotoTaggingGramplet, gramps-project#869 SurnameMappingGramplet, gramps#2299 ClipboardGramplet) plus the lint backlog (gramps-project#843, gramps-project#847-gramps-project#869). Verified by the most recent fork CI run on eduralph/addons-source:maintenance/gramps60 (sha 8aabdd4, 2026-05-18): both Unit Tests (Linux) and Unit Tests (Windows) reported success. Also clears two stale comments that referenced the removed flag: - unit-test-linux's "Run per-addon unit tests" step: drop the trailing "Falls silently under continue-on-error." sentence. - integration's requires_mod-validate step: collapse the now-moot "this is the blocking copy" rationale to a plain pointer at unit-test-linux. The lone remaining continue-on-error is on addon-structure (line 70), gated by the po/template.pot backlog. PRs gramps-project#838-gramps-project#841 were closed without merge; per Eduard's call the po/template.pot check stays non-blocking for now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the seven hardcoded "gramps60" strings in ci.yml with values computed by a new setup job that strips "maintenance/" off the ref and validates the remainder matches grampsNN. Container jobs now pull gramps-ci:<suffix> and the build step calls make.py with the matching suffix, so the same workflow runs unchanged on maintenance/gramps60, maintenance/gramps61, and any future maintenance/grampsNN branch. A non-matching ref (master, topic branch, gramps100) fails fast in the setup job rather than racing through with a malformed image tag. Verified: - YAML parses (python3 -c "import yaml; yaml.safe_load(...)") - bash dry-run of the case statement accepts gramps60/61/62/42 and rejects master, feature/foo, maintenance/gramps60-test, gramps100 - container jobs (lint, compile-check, unit-test-linux, integration-test, build) all carry needs: setup; non-container jobs (addon-structure, unit-test-windows) do not Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the three hardcoded "gramps60" strings in docker-build.yml with values computed from github.ref_name in a new "Compute branch parameters" step. The image is tagged gramps-ci:<suffix> and built with GRAMPS_SERIES=<series> (e.g. gramps60→6.0, gramps61→6.1), so the same workflow produces the correct image on every maintenance branch. Also drops the paths: filter from the push trigger. With the filter the workflow only fired when .github/docker/** changed, which means newly-created maintenance branches (inheriting the Dockerfile from their parent unchanged) would never produce their gramps-ci:<suffix> image, and ci.yml jobs would fail pulling a non-existent tag. Every push to a maintenance branch now runs docker-build; buildx layer cache turns the steady-state case into a ~20-30 s no-op rebuild. Verified: - YAML parses (python3 -c "import yaml; yaml.safe_load(...)") - bash dry-run of the case/series logic produces 6.0, 6.1, 6.2, 7.0, 4.2 for the corresponding maintenance/grampsNN refs and rejects master Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the hardcoded "gramps>=6.0,<6.1" pip pin with
"gramps==\${GRAMPS_SERIES}.*", and pulls GRAMPS_SERIES from a build
arg with no default. Same Dockerfile now builds gramps-ci:gramps60
when invoked with GRAMPS_SERIES=6.0, gramps-ci:gramps61 with 6.1, and
so on. docker-build.yml derives the value from the branch ref.
No default on GRAMPS_SERIES on purpose: a wrong default would silently
produce an image for the wrong Gramps series that "looks fine" but is
mismatched against the branch's addon code. A guard line fails the
build loudly if the arg is missing.
Verified:
- docker build --check --build-arg GRAMPS_SERIES=6.0 → no warnings
- a minimal repro Dockerfile invoking the guard without
GRAMPS_SERIES exits 1 with "GRAMPS_SERIES is required (e.g. 6.0)"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…branches
PR 820's "pip install gramps==${SERIES}.*" only works on maintenance
branches whose Gramps release has been published to PyPI. The
maintenance/gramps61 branch carries the in-development 6.1 series and
has no PyPI release yet, so docker-build fails with "No matching
distribution found for gramps==6.1.*" the moment the branch-neutral
pipeline is exercised against it.
This commit adds a hybrid install path to the Dockerfile and the
matching wiring in docker-build.yml:
- Dockerfile attempts pip install first. On "No matching distribution
found for gramps==..." it falls back to a SHA-pinned git clone of
gramps-project/gramps@maintenance/gramps${SERIES_NODOT} and
"pip install ." from that working tree. Any other pip failure
(network, 503, etc.) is fatal so a transient PyPI hiccup cannot
silently flip a normally-released branch into "test against
moving tip" mode.
- docker-build.yml's params step captures the upstream branch's
current HEAD SHA via git ls-remote and passes it in as
GRAMPS_FALLBACK_SHA. The SHA participates in the buildx cache key
so a moved upstream tip actually re-runs the install layer
(without that, gramps61 CI would stay frozen on whichever revision
was first baked into the image).
Trade-off — and worth flagging to addon contributors: a green CI on an
unreleased branch means "addons work with the current upstream tip,"
not "addons work with X.Y.0." The ::notice:: (PyPI path) vs ::warning::
(fallback path) log distinction in the Dockerfile makes the active
path visible in every docker-build run.
Verified locally:
- docker build --check --build-arg GRAMPS_SERIES=6.0 → clean
- docker build with GRAMPS_SERIES=6.0 + valid fallback SHA →
::notice::installed gramps==6.0.* from PyPI; image reports
"Gramps 6.0.8" (released path unchanged)
- docker build with GRAMPS_SERIES=6.1 + upstream HEAD SHA → fallback
fires; image reports "Gramps 6.1.0-beta1" (only obtainable from
git clone, since no 6.1.* exists on PyPI)
- bash unit test of the failure-mode triage: missing SHA when PyPI
lacks the version exits 1 with ::error::no gramps==... and
GRAMPS_FALLBACK_SHA is unset; non-version pip error exits 1 with
::error::pip install gramps failed (non-version reason) and dumps
the captured stderr
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two doc-only additions covering practical implications of the
branch-neutral CI from the preceding commits:
- .github/workflows/ci.yml header: short NOTE for maintainers
explaining that the first push to a newly-created maintenance
branch will see container jobs fail at "Initialize containers"
because the gramps-ci:<suffix> image is still being built by the
companion docker-build.yml workflow on the same push. Solution
is a one-time re-run after docker-build finishes. This is
inherent to running both workflows on the same push event and is
not a problem on subsequent pushes (the image already exists).
- CONTRIBUTING.md "Work Towards a Merge": short paragraph for
contributors explaining what a green CI check means on an
unreleased maintenance branch. When PyPI lacks a release for the
matching Gramps series, the CI image is built from a SHA-pinned
snapshot of gramps-project/gramps@maintenance/grampsNN, so green
means "addons work with the upstream branch tip at <sha>", not
"addons work with the released X.Y.0". The exact SHA is logged
as a ::warning:: line in the Build Docker Images output.
Verified:
- YAML still parses
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Operational document for the gramps-project/addons-source maintainer
covering the new steps introduced by PR 820 + the branch-neutral
follow-up:
- One-time setup: make the gramps-ci GHCR package public (so fork
PR contributors can pull the image), and expect the first-push
race on maintenance/gramps60 immediately after merge.
- Creating a new maintenance branch: a plain `git branch && git
push` plus a one-time re-run of the failed CI jobs after the
image is built.
- When a Gramps minor release lands on PyPI: nothing to do (the
hybrid Dockerfile auto-detects); optional workflow_dispatch to
rebuild immediately.
- Diagnostic log markers: ::notice:: / ::warning:: / ::error::
annotations emitted from docker-build.yml and the Dockerfile,
with their respective root causes.
- Future-proofing knobs: upstream repo URL and GHCR tag retention,
only mentioned in case they ever become relevant.
The ci.yml header NOTE about the first-push race now points to this
runbook for the full picture (GHCR visibility, log markers, etc.).
Verified:
- YAML parses
- All TOC anchors in the new file resolve to existing headers
- ../MAINTAINERS.md and ../CONTRIBUTING.md (relative links in the
new file) both exist
- CONTRIBUTING.md#work-towards-a-merge anchor exists
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds tests/test_addon_dependencies.py, an independent detector for the
class of bug behind Mantis 13707 (WebConnect packs importing libwebconnect
without declaring depends_on=["libwebconnect"], so install fails when
libwebconnect is absent). Picked up automatically by the integration-test
job's `python3 -m unittest discover -s tests -p "test_*.py"` — no ci.yml
change.
Design
- Independent of Gramps' loader. Does NOT import gramps.gen.plug —
no PluginRegister, no BasePluginManager, no PluginData. Using
Gramps' loader would test addons through the very dependency
resolver whose leniency let 13707 ship, and would tie this test to
Gramps' internal, unstable API.
- Reads every *.gpr.py via an exec-shim of its own: a permissive
globals dict (LOAD_GLOBAL → __missing__ → sentinel, so plugin-type
constants like GRAMPLET / REPORT / TOOL never NameError) plus a
fake register() that records each call's kwargs. Builds the
addon-provided-module set and an id → {modules, depends_on,
requires_mod, dir} map.
- Isolated-load each plugin's registered module in a fresh
subprocess with sys.path = [target_dir] + dep_dirs (declared
depends_on ids resolved → their directories via the index). Uses
`python3 -I`, cwd=/tmp, PYTHONPATH stripped, and strips "" from
sys.path defensively — without those, PEP 420 implicit namespace
packages let sibling addons import as empty packages and silently
defeat the isolation. Gramps remains reachable from system
site-packages, as intended: the isolation is addon-from-addon, not
addon-from-Gramps.
- Pins GI namespace versions Gramps itself pins (Gtk 3.0, PangoCairo
1.0, etc.) before importing the addon. Not Gramps' loader — just
matching the runtime conditions a plugin is loaded under, so
addons whose top-level `from gi.repository import X` is
version-sensitive don't generate false (c) failures from ambiguous
GI defaults.
- Classifies failures: (a) missing name is an addon-provided module
not in this addon's depends_on — FINDING, fails the test;
(b) missing name is in this addon's requires_mod — environment
concern (PR 820's auto-derive owns it), ignored;
(c) anything else — logged separately, not a finding. A given
stderr may name multiple missing modules; (a) wins over (b) wins
over (c) so the highest-signal finding is reported.
Limitation, stated in the module docstring: catches undeclared
dependencies that manifest at LOAD time (top-level imports). MISSES
lazily-imported deps — a sibling addon imported inside a function not
called at module load. No false positives, not exhaustive.
Verified
- Synthetic positive/negative: with depends_on=["libwebconnect"]
stripped from a USWebConnectPack copy, detector flags bucket (a)
`libwebconnect`; with the declaration left in, rc=0 clean load.
(Both checks live under /tmp/, not committed; they validate the
classifier and isolation end-to-end.)
- Full tree run in gramps-ci-local:gramps60-test (PR 820's CI image,
Gramps 6.0.8): 187 plugins indexed, 159 pass, 0 (a), 7 (b)
ignored, 21 (c) logged. Every WebConnect pack on this branch
already declares depends_on=["libwebconnect"] — the 13707
declaration is in effect on gramps60, so the test is green here.
On gramps61 the situation may differ and the remediation round
will be informed by re-running this detector there.
This commit is the detector + the regression check. It does NOT apply
any depends_on fixes — the WebConnect remediation is a separate later
round on maintenance/gramps61.
Issue #13707.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pre-merge review gate (fork-internal mirror of upstream gramps-project#820).
This PR exists so the full diff that will land upstream as PR 820 can be re-read here with fresh eyes before being marked ready upstream. Nothing here is intended to be merged into the fork's
maintenance/gramps60— the fork tracks upstream by sync, not by self-merge. Close without merging once the upstream PR is reviewed.What's on this branch
The branch carries the full PR 820 stack (CI image + workflows + shared unittest harness + plugin-registration tests) plus the latest addition:
ad2815cee—tests: detect addons that import a sibling without depends_ontests/test_addon_dependencies.py: independent detector for the Mantis 13707 bug class (WebConnect packs importinglibwebconnectwithout declaringdepends_on).integration-testjob'sunittest discover -s tests; noci.ymlchange.What to look at first on the new commit
tests/test_addon_dependencies.py— the whole file. Particularly:gramps.gen.plugimports — by design, so the test does not run addons through the very dependency resolver whose leniency lets 13707 ship)..gpr.py(permissive globals + fakeregister()).-I,cwd=/tmp,PYTHONPATHstripped,""removed fromsys.path— without these, PEP 420 implicit namespace packages silently defeat the isolation).Not in this PR / commit
depends_onfixes to any addon — that's a separate later round onmaintenance/gramps61.ci.ymledit for a dedicated job — the test rides the existingdiscover -s testsinvocation; carving it into its owncontinue-on-error: truejob is the follow-up the brief explicitly defers until findings appear.🤖 Generated with Claude Code